summaryrefslogtreecommitdiffstats
path: root/sbin
diff options
context:
space:
mode:
Diffstat (limited to 'sbin')
-rw-r--r--sbin/Makefile140
-rw-r--r--sbin/Makefile.amd645
-rw-r--r--sbin/Makefile.arm4
-rw-r--r--sbin/Makefile.i3866
-rw-r--r--sbin/Makefile.inc11
-rw-r--r--sbin/Makefile.mips4
-rw-r--r--sbin/Makefile.pc985
-rw-r--r--sbin/Makefile.sparc644
-rw-r--r--sbin/adjkerntz/Makefile6
-rw-r--r--sbin/adjkerntz/adjkerntz.8193
-rw-r--r--sbin/adjkerntz/adjkerntz.c378
-rw-r--r--sbin/adjkerntz/pathnames.h29
-rw-r--r--sbin/atm/Makefile28
-rw-r--r--sbin/atm/Makefile.inc26
-rw-r--r--sbin/atm/atmconfig/Makefile43
-rw-r--r--sbin/atm/atmconfig/atm_oid.list20
-rw-r--r--sbin/atm/atmconfig/atmconfig.8319
-rw-r--r--sbin/atm/atmconfig/atmconfig.h102
-rw-r--r--sbin/atm/atmconfig/atmconfig.help223
-rw-r--r--sbin/atm/atmconfig/atmconfig_device.c444
-rw-r--r--sbin/atm/atmconfig/atmconfig_device.h75
-rw-r--r--sbin/atm/atmconfig/atmconfig_device.help62
-rw-r--r--sbin/atm/atmconfig/diag.c1122
-rw-r--r--sbin/atm/atmconfig/diag.h49
-rw-r--r--sbin/atm/atmconfig/main.c880
-rw-r--r--sbin/atm/atmconfig/natm.c680
-rw-r--r--sbin/atm/atmconfig/private.h62
-rw-r--r--sbin/badsect/Makefile9
-rw-r--r--sbin/badsect/badsect.8133
-rw-r--r--sbin/badsect/badsect.c193
-rw-r--r--sbin/bsdlabel/Makefile17
-rw-r--r--sbin/bsdlabel/bsdlabel.8500
-rw-r--r--sbin/bsdlabel/bsdlabel.c1572
-rw-r--r--sbin/bsdlabel/pathnames.h36
-rw-r--r--sbin/camcontrol/Makefile17
-rw-r--r--sbin/camcontrol/camcontrol.82102
-rw-r--r--sbin/camcontrol/camcontrol.c8668
-rw-r--r--sbin/camcontrol/camcontrol.h74
-rw-r--r--sbin/camcontrol/fwdownload.c470
-rw-r--r--sbin/camcontrol/modeedit.c906
-rw-r--r--sbin/camcontrol/persist.c966
-rw-r--r--sbin/camcontrol/progress.c186
-rw-r--r--sbin/camcontrol/progress.h60
-rw-r--r--sbin/camcontrol/util.c184
-rw-r--r--sbin/casperd/Makefile17
-rw-r--r--sbin/casperd/casperd.8126
-rw-r--r--sbin/casperd/casperd.c715
-rw-r--r--sbin/casperd/zygote.c229
-rw-r--r--sbin/casperd/zygote.h40
-rw-r--r--sbin/ccdconfig/Makefile8
-rw-r--r--sbin/ccdconfig/ccdconfig.8258
-rw-r--r--sbin/ccdconfig/ccdconfig.c451
-rw-r--r--sbin/ccdconfig/pathnames.h38
-rw-r--r--sbin/clri/Makefile8
-rw-r--r--sbin/clri/clri.876
-rw-r--r--sbin/clri/clri.c159
-rw-r--r--sbin/comcontrol/Makefile7
-rw-r--r--sbin/comcontrol/comcontrol.865
-rw-r--r--sbin/comcontrol/comcontrol.c128
-rw-r--r--sbin/conscontrol/Makefile7
-rw-r--r--sbin/conscontrol/conscontrol.8116
-rw-r--r--sbin/conscontrol/conscontrol.c196
-rw-r--r--sbin/ddb/Makefile10
-rw-r--r--sbin/ddb/ddb.8168
-rw-r--r--sbin/ddb/ddb.c134
-rw-r--r--sbin/ddb/ddb.h38
-rw-r--r--sbin/ddb/ddb_capture.c248
-rw-r--r--sbin/ddb/ddb_script.c160
-rw-r--r--sbin/devd/Makefile24
-rw-r--r--sbin/devd/devd.8157
-rw-r--r--sbin/devd/devd.cc1243
-rw-r--r--sbin/devd/devd.conf.5578
-rw-r--r--sbin/devd/devd.h58
-rw-r--r--sbin/devd/devd.hh183
-rw-r--r--sbin/devd/parse.y153
-rw-r--r--sbin/devd/tests/Makefile12
-rw-r--r--sbin/devd/tests/client_test.c198
-rw-r--r--sbin/devd/token.l112
-rw-r--r--sbin/devfs/Makefile7
-rw-r--r--sbin/devfs/devfs.8378
-rw-r--r--sbin/devfs/devfs.c230
-rw-r--r--sbin/devfs/extern.h57
-rw-r--r--sbin/devfs/rule.c462
-rw-r--r--sbin/dhclient/Makefile52
-rw-r--r--sbin/dhclient/alloc.c79
-rw-r--r--sbin/dhclient/bpf.c485
-rw-r--r--sbin/dhclient/clparse.c951
-rw-r--r--sbin/dhclient/conflex.c529
-rw-r--r--sbin/dhclient/convert.c117
-rwxr-xr-xsbin/dhclient/dhclient-script407
-rw-r--r--sbin/dhclient/dhclient-script.8297
-rw-r--r--sbin/dhclient/dhclient.8196
-rw-r--r--sbin/dhclient/dhclient.c2756
-rw-r--r--sbin/dhclient/dhclient.conf39
-rw-r--r--sbin/dhclient/dhclient.conf.5544
-rw-r--r--sbin/dhclient/dhclient.leases.595
-rw-r--r--sbin/dhclient/dhcp-options.5610
-rw-r--r--sbin/dhclient/dhcp.h184
-rw-r--r--sbin/dhclient/dhcpd.h440
-rw-r--r--sbin/dhclient/dhctoken.h136
-rw-r--r--sbin/dhclient/dispatch.c503
-rw-r--r--sbin/dhclient/errwarn.c239
-rw-r--r--sbin/dhclient/hash.c122
-rw-r--r--sbin/dhclient/inet.c121
-rw-r--r--sbin/dhclient/options.c894
-rw-r--r--sbin/dhclient/packet.c248
-rw-r--r--sbin/dhclient/parse.c584
-rw-r--r--sbin/dhclient/privsep.c241
-rw-r--r--sbin/dhclient/privsep.h50
-rw-r--r--sbin/dhclient/tables.c447
-rw-r--r--sbin/dhclient/tests/Makefile16
-rw-r--r--sbin/dhclient/tests/fake.c64
-rw-r--r--sbin/dhclient/tests/option-domain-search.c328
-rw-r--r--sbin/dhclient/tree.c59
-rw-r--r--sbin/dhclient/tree.h66
-rw-r--r--sbin/dmesg/Makefile9
-rw-r--r--sbin/dmesg/dmesg.887
-rw-r--r--sbin/dmesg/dmesg.c217
-rw-r--r--sbin/dump/Makefile24
-rw-r--r--sbin/dump/cache.c146
-rw-r--r--sbin/dump/dump.8568
-rw-r--r--sbin/dump/dump.h183
-rw-r--r--sbin/dump/dumprmt.c375
-rw-r--r--sbin/dump/itime.c265
-rw-r--r--sbin/dump/main.c778
-rw-r--r--sbin/dump/optr.c429
-rw-r--r--sbin/dump/pathnames.h39
-rw-r--r--sbin/dump/tape.c880
-rw-r--r--sbin/dump/traverse.c1009
-rw-r--r--sbin/dump/unctime.c56
-rw-r--r--sbin/dumpfs/Makefile9
-rw-r--r--sbin/dumpfs/dumpfs.8111
-rw-r--r--sbin/dumpfs/dumpfs.c510
-rw-r--r--sbin/dumpon/Makefile6
-rw-r--r--sbin/dumpon/dumpon.8160
-rw-r--r--sbin/dumpon/dumpon.c178
-rw-r--r--sbin/etherswitchcfg/Makefile9
-rw-r--r--sbin/etherswitchcfg/etherswitchcfg.8192
-rw-r--r--sbin/etherswitchcfg/etherswitchcfg.c710
-rw-r--r--sbin/etherswitchcfg/ifmedia.c812
-rw-r--r--sbin/fdisk/Makefile15
-rw-r--r--sbin/fdisk/fdisk.8501
-rw-r--r--sbin/fdisk/fdisk.c1534
-rw-r--r--sbin/fdisk/runtest.sh30
-rw-r--r--sbin/fdisk_pc98/Makefile12
-rw-r--r--sbin/fdisk_pc98/fdisk.8471
-rw-r--r--sbin/fdisk_pc98/fdisk.c918
-rw-r--r--sbin/ffsinfo/Makefile18
-rw-r--r--sbin/ffsinfo/ffsinfo.8145
-rw-r--r--sbin/ffsinfo/ffsinfo.c646
-rw-r--r--sbin/fsck/Makefile8
-rw-r--r--sbin/fsck/fsck.8231
-rw-r--r--sbin/fsck/fsck.c581
-rw-r--r--sbin/fsck/fsutil.c226
-rw-r--r--sbin/fsck/fsutil.h50
-rw-r--r--sbin/fsck/preen.c333
-rw-r--r--sbin/fsck_ffs/Makefile18
-rw-r--r--sbin/fsck_ffs/dir.c709
-rw-r--r--sbin/fsck_ffs/ea.c85
-rw-r--r--sbin/fsck_ffs/fsck.h474
-rw-r--r--sbin/fsck_ffs/fsck_ffs.8395
-rw-r--r--sbin/fsck_ffs/fsutil.c1046
-rw-r--r--sbin/fsck_ffs/gjournal.c510
-rw-r--r--sbin/fsck_ffs/globs.c165
-rw-r--r--sbin/fsck_ffs/inode.c734
-rw-r--r--sbin/fsck_ffs/main.c683
-rw-r--r--sbin/fsck_ffs/pass1.c522
-rw-r--r--sbin/fsck_ffs/pass1b.c117
-rw-r--r--sbin/fsck_ffs/pass2.c668
-rw-r--r--sbin/fsck_ffs/pass3.c127
-rw-r--r--sbin/fsck_ffs/pass4.c153
-rw-r--r--sbin/fsck_ffs/pass5.c596
-rw-r--r--sbin/fsck_ffs/setup.c449
-rw-r--r--sbin/fsck_ffs/suj.c2791
-rw-r--r--sbin/fsck_ffs/utilities.c110
-rw-r--r--sbin/fsck_msdosfs/Makefile13
-rw-r--r--sbin/fsck_msdosfs/boot.c275
-rw-r--r--sbin/fsck_msdosfs/check.c194
-rw-r--r--sbin/fsck_msdosfs/dir.c1012
-rw-r--r--sbin/fsck_msdosfs/dosfs.h139
-rw-r--r--sbin/fsck_msdosfs/ext.h141
-rw-r--r--sbin/fsck_msdosfs/fat.c711
-rw-r--r--sbin/fsck_msdosfs/fsck_msdosfs.8123
-rw-r--r--sbin/fsck_msdosfs/main.c155
-rw-r--r--sbin/fsdb/Makefile15
-rw-r--r--sbin/fsdb/fsdb.8269
-rw-r--r--sbin/fsdb/fsdb.c1210
-rw-r--r--sbin/fsdb/fsdb.h62
-rw-r--r--sbin/fsdb/fsdbutil.c374
-rw-r--r--sbin/fsirand/Makefile8
-rw-r--r--sbin/fsirand/fsirand.8116
-rw-r--r--sbin/fsirand/fsirand.c303
-rw-r--r--sbin/gbde/Makefile32
-rw-r--r--sbin/gbde/gbde.8270
-rw-r--r--sbin/gbde/gbde.c901
-rw-r--r--sbin/gbde/image.uu3305
-rw-r--r--sbin/gbde/template.txt32
-rw-r--r--sbin/gbde/test.sh67
-rw-r--r--sbin/geom/Makefile27
-rw-r--r--sbin/geom/Makefile.inc5
-rw-r--r--sbin/geom/class/Makefile24
-rw-r--r--sbin/geom/class/Makefile.inc13
-rw-r--r--sbin/geom/class/cache/Makefile7
-rw-r--r--sbin/geom/class/cache/gcache.8192
-rw-r--r--sbin/geom/class/cache/geom_cache.c239
-rw-r--r--sbin/geom/class/concat/Makefile7
-rw-r--r--sbin/geom/class/concat/gconcat.8197
-rw-r--r--sbin/geom/class/concat/geom_concat.c247
-rw-r--r--sbin/geom/class/eli/Makefile17
-rw-r--r--sbin/geom/class/eli/geli.81060
-rw-r--r--sbin/geom/class/eli/geom_eli.c1650
-rw-r--r--sbin/geom/class/journal/Makefile12
-rw-r--r--sbin/geom/class/journal/geom_journal.c348
-rw-r--r--sbin/geom/class/journal/geom_journal.h33
-rw-r--r--sbin/geom/class/journal/geom_journal_ufs.c78
-rw-r--r--sbin/geom/class/journal/gjournal.8346
-rw-r--r--sbin/geom/class/label/Makefile7
-rw-r--r--sbin/geom/class/label/geom_label.c225
-rw-r--r--sbin/geom/class/label/glabel.8275
-rw-r--r--sbin/geom/class/mirror/Makefile9
-rw-r--r--sbin/geom/class/mirror/geom_mirror.c487
-rw-r--r--sbin/geom/class/mirror/gmirror.8387
-rw-r--r--sbin/geom/class/mountver/Makefile7
-rw-r--r--sbin/geom/class/mountver/geom_mountver.c56
-rw-r--r--sbin/geom/class/mountver/gmountver.8130
-rw-r--r--sbin/geom/class/multipath/Makefile9
-rw-r--r--sbin/geom/class/multipath/geom_multipath.c322
-rw-r--r--sbin/geom/class/multipath/gmultipath.8365
-rw-r--r--sbin/geom/class/nop/Makefile7
-rw-r--r--sbin/geom/class/nop/geom_nop.c75
-rw-r--r--sbin/geom/class/nop/gnop.8179
-rw-r--r--sbin/geom/class/part/Makefile9
-rw-r--r--sbin/geom/class/part/geom_part.c1327
-rw-r--r--sbin/geom/class/part/gpart.81334
-rw-r--r--sbin/geom/class/raid/Makefile9
-rw-r--r--sbin/geom/class/raid/geom_raid.c92
-rw-r--r--sbin/geom/class/raid/graid.8324
-rw-r--r--sbin/geom/class/raid3/Makefile9
-rw-r--r--sbin/geom/class/raid3/geom_raid3.c335
-rw-r--r--sbin/geom/class/raid3/graid3.8257
-rw-r--r--sbin/geom/class/sched/Makefile8
-rw-r--r--sbin/geom/class/sched/geom_sched.c126
-rw-r--r--sbin/geom/class/sched/gsched.8162
-rw-r--r--sbin/geom/class/shsec/Makefile7
-rw-r--r--sbin/geom/class/shsec/geom_shsec.c259
-rw-r--r--sbin/geom/class/shsec/gshsec.8130
-rw-r--r--sbin/geom/class/stripe/Makefile7
-rw-r--r--sbin/geom/class/stripe/geom_stripe.c285
-rw-r--r--sbin/geom/class/stripe/gstripe.8243
-rw-r--r--sbin/geom/class/virstor/Makefile10
-rw-r--r--sbin/geom/class/virstor/geom_virstor.c583
-rw-r--r--sbin/geom/class/virstor/gvirstor.8299
-rw-r--r--sbin/geom/core/Makefile16
-rw-r--r--sbin/geom/core/geom.8206
-rw-r--r--sbin/geom/core/geom.c1185
-rw-r--r--sbin/geom/core/geom.h75
-rw-r--r--sbin/geom/misc/subr.c537
-rw-r--r--sbin/geom/misc/subr.h54
-rw-r--r--sbin/ggate/Makefile14
-rw-r--r--sbin/ggate/Makefile.inc3
-rw-r--r--sbin/ggate/ggatec/Makefile15
-rw-r--r--sbin/ggate/ggatec/ggatec.8180
-rw-r--r--sbin/ggate/ggatec/ggatec.c642
-rw-r--r--sbin/ggate/ggated/Makefile13
-rw-r--r--sbin/ggate/ggated/ggated.8111
-rw-r--r--sbin/ggate/ggated/ggated.c1050
-rw-r--r--sbin/ggate/ggatel/Makefile14
-rw-r--r--sbin/ggate/ggatel/ggatel.8157
-rw-r--r--sbin/ggate/ggatel/ggatel.c327
-rw-r--r--sbin/ggate/shared/ggate.c409
-rw-r--r--sbin/ggate/shared/ggate.h196
-rw-r--r--sbin/growfs/Makefile28
-rw-r--r--sbin/growfs/debug.c841
-rw-r--r--sbin/growfs/debug.h143
-rw-r--r--sbin/growfs/growfs.8138
-rw-r--r--sbin/growfs/growfs.c1742
-rw-r--r--sbin/growfs/tests/Makefile7
-rwxr-xr-xsbin/growfs/tests/legacy_test.pl89
-rw-r--r--sbin/gvinum/Makefile14
-rw-r--r--sbin/gvinum/gvinum.8446
-rw-r--r--sbin/gvinum/gvinum.c1442
-rw-r--r--sbin/gvinum/gvinum.h40
-rw-r--r--sbin/hastctl/Makefile45
-rw-r--r--sbin/hastctl/hastctl.8228
-rw-r--r--sbin/hastctl/hastctl.c584
-rw-r--r--sbin/hastd/Makefile43
-rw-r--r--sbin/hastd/activemap.c701
-rw-r--r--sbin/hastd/activemap.h69
-rw-r--r--sbin/hastd/control.c522
-rw-r--r--sbin/hastd/control.h49
-rw-r--r--sbin/hastd/crc32.c115
-rw-r--r--sbin/hastd/crc32.h28
-rw-r--r--sbin/hastd/ebuf.c259
-rw-r--r--sbin/hastd/ebuf.h51
-rw-r--r--sbin/hastd/event.c161
-rw-r--r--sbin/hastd/event.h46
-rw-r--r--sbin/hastd/hast.conf.5441
-rw-r--r--sbin/hastd/hast.h269
-rw-r--r--sbin/hastd/hast_checksum.c160
-rw-r--r--sbin/hastd/hast_checksum.h44
-rw-r--r--sbin/hastd/hast_compression.c283
-rw-r--r--sbin/hastd/hast_compression.h44
-rw-r--r--sbin/hastd/hast_proto.c222
-rw-r--r--sbin/hastd/hast_proto.h46
-rw-r--r--sbin/hastd/hastd.8232
-rw-r--r--sbin/hastd/hastd.c1337
-rw-r--r--sbin/hastd/hastd.h54
-rw-r--r--sbin/hastd/hooks.c391
-rw-r--r--sbin/hastd/hooks.h48
-rw-r--r--sbin/hastd/lzf.c406
-rw-r--r--sbin/hastd/lzf.h211
-rw-r--r--sbin/hastd/metadata.c225
-rw-r--r--sbin/hastd/metadata.h48
-rw-r--r--sbin/hastd/nv.c966
-rw-r--r--sbin/hastd/nv.h133
-rw-r--r--sbin/hastd/parse.y1037
-rw-r--r--sbin/hastd/pjdlog.c614
-rw-r--r--sbin/hastd/pjdlog.h117
-rw-r--r--sbin/hastd/primary.c2451
-rw-r--r--sbin/hastd/proto.c446
-rw-r--r--sbin/hastd/proto.h61
-rw-r--r--sbin/hastd/proto_common.c232
-rw-r--r--sbin/hastd/proto_impl.h79
-rw-r--r--sbin/hastd/proto_socketpair.c237
-rw-r--r--sbin/hastd/proto_tcp.c637
-rw-r--r--sbin/hastd/proto_uds.c361
-rw-r--r--sbin/hastd/rangelock.c141
-rw-r--r--sbin/hastd/rangelock.h46
-rw-r--r--sbin/hastd/refcnt.h63
-rw-r--r--sbin/hastd/secondary.c929
-rw-r--r--sbin/hastd/subr.c299
-rw-r--r--sbin/hastd/subr.h56
-rw-r--r--sbin/hastd/synch.h195
-rw-r--r--sbin/hastd/token.l86
-rw-r--r--sbin/ifconfig/Makefile72
-rw-r--r--sbin/ifconfig/af_inet.c211
-rw-r--r--sbin/ifconfig/af_inet6.c539
-rw-r--r--sbin/ifconfig/af_link.c129
-rw-r--r--sbin/ifconfig/af_nd6.c169
-rw-r--r--sbin/ifconfig/carp.c227
-rw-r--r--sbin/ifconfig/ifbridge.c759
-rw-r--r--sbin/ifconfig/ifclone.c194
-rw-r--r--sbin/ifconfig/ifconfig.82858
-rw-r--r--sbin/ifconfig/ifconfig.c1399
-rw-r--r--sbin/ifconfig/ifconfig.h156
-rw-r--r--sbin/ifconfig/iffib.c103
-rw-r--r--sbin/ifconfig/ifgif.c118
-rw-r--r--sbin/ifconfig/ifgre.c123
-rw-r--r--sbin/ifconfig/ifgroup.c185
-rw-r--r--sbin/ifconfig/ifieee80211.c5331
-rw-r--r--sbin/ifconfig/iflagg.c314
-rw-r--r--sbin/ifconfig/ifmac.c121
-rw-r--r--sbin/ifconfig/ifmedia.c855
-rw-r--r--sbin/ifconfig/ifpfsync.c237
-rw-r--r--sbin/ifconfig/ifvlan.c206
-rw-r--r--sbin/ifconfig/ifvxlan.c647
-rw-r--r--sbin/ifconfig/regdomain.c705
-rw-r--r--sbin/ifconfig/regdomain.h121
-rw-r--r--sbin/ifconfig/sfp.c827
-rw-r--r--sbin/ifconfig/tests/Makefile13
-rw-r--r--sbin/init/Makefile13
-rw-r--r--sbin/init/NOTES112
-rw-r--r--sbin/init/init.8360
-rw-r--r--sbin/init/init.c1725
-rw-r--r--sbin/init/pathnames.h41
-rw-r--r--sbin/ipf/Makefile8
-rw-r--r--sbin/ipf/Makefile.inc30
-rw-r--r--sbin/ipf/ipf/Makefile41
-rw-r--r--sbin/ipf/ipfs/Makefile6
-rw-r--r--sbin/ipf/ipfstat/Makefile10
-rw-r--r--sbin/ipf/ipftest/Makefile97
-rw-r--r--sbin/ipf/ipmon/Makefile34
-rw-r--r--sbin/ipf/ipnat/Makefile36
-rw-r--r--sbin/ipf/ippool/Makefile33
-rw-r--r--sbin/ipf/ipresend/Makefile9
-rw-r--r--sbin/ipf/ipsend/Makefile38
-rw-r--r--sbin/ipf/iptest/Makefile11
-rw-r--r--sbin/ipf/libipf/Makefile46
-rw-r--r--sbin/ipf/rules/Makefile17
-rw-r--r--sbin/ipfw/Makefile17
-rw-r--r--sbin/ipfw/altq.c151
-rw-r--r--sbin/ipfw/dummynet.c1410
-rw-r--r--sbin/ipfw/ipfw.83725
-rw-r--r--sbin/ipfw/ipfw2.c5085
-rw-r--r--sbin/ipfw/ipfw2.h350
-rw-r--r--sbin/ipfw/ipv6.c536
-rw-r--r--sbin/ipfw/main.c628
-rw-r--r--sbin/ipfw/nat.c1114
-rw-r--r--sbin/ipfw/tables.c1966
-rw-r--r--sbin/iscontrol/Makefile13
-rw-r--r--sbin/iscontrol/auth_subr.c204
-rw-r--r--sbin/iscontrol/config.c380
-rw-r--r--sbin/iscontrol/fsm.c757
-rw-r--r--sbin/iscontrol/iscontrol.8137
-rw-r--r--sbin/iscontrol/iscontrol.c259
-rw-r--r--sbin/iscontrol/iscontrol.h165
-rw-r--r--sbin/iscontrol/login.c440
-rw-r--r--sbin/iscontrol/misc.c226
-rw-r--r--sbin/iscontrol/pdu.c176
-rw-r--r--sbin/kldconfig/Makefile32
-rw-r--r--sbin/kldconfig/kldconfig.8108
-rw-r--r--sbin/kldconfig/kldconfig.c416
-rw-r--r--sbin/kldload/Makefile32
-rw-r--r--sbin/kldload/kldload.8129
-rw-r--r--sbin/kldload/kldload.c214
-rw-r--r--sbin/kldstat/Makefile32
-rw-r--r--sbin/kldstat/kldstat.877
-rw-r--r--sbin/kldstat/kldstat.c166
-rw-r--r--sbin/kldunload/Makefile32
-rw-r--r--sbin/kldunload/kldunload.881
-rw-r--r--sbin/kldunload/kldunload.c117
-rw-r--r--sbin/ldconfig/Makefile11
-rw-r--r--sbin/ldconfig/elfhints.c302
-rw-r--r--sbin/ldconfig/ldconfig.8198
-rw-r--r--sbin/ldconfig/ldconfig.c640
-rw-r--r--sbin/ldconfig/ldconfig.h41
-rw-r--r--sbin/md5/Makefile18
-rw-r--r--sbin/md5/md5.1157
-rw-r--r--sbin/md5/md5.c395
-rw-r--r--sbin/mdconfig/Makefile14
-rw-r--r--sbin/mdconfig/mdconfig.8322
-rw-r--r--sbin/mdconfig/mdconfig.c568
-rw-r--r--sbin/mdconfig/tests/Makefile10
-rwxr-xr-xsbin/mdconfig/tests/mdconfig_test.sh281
-rw-r--r--sbin/mdmfs/Makefile8
-rw-r--r--sbin/mdmfs/mdmfs.8377
-rw-r--r--sbin/mdmfs/mdmfs.c696
-rw-r--r--sbin/mknod/Makefile7
-rw-r--r--sbin/mknod/mknod.8152
-rw-r--r--sbin/mknod/mknod.c167
-rw-r--r--sbin/mksnap_ffs/Makefile20
-rw-r--r--sbin/mksnap_ffs/mksnap_ffs.890
-rw-r--r--sbin/mksnap_ffs/mksnap_ffs.c142
-rw-r--r--sbin/mount/Makefile11
-rw-r--r--sbin/mount/extern.h33
-rw-r--r--sbin/mount/getmntopts.3181
-rw-r--r--sbin/mount/getmntopts.c183
-rw-r--r--sbin/mount/mntopts.h101
-rw-r--r--sbin/mount/mount.8592
-rw-r--r--sbin/mount/mount.c957
-rw-r--r--sbin/mount/mount.conf.8252
-rw-r--r--sbin/mount/mount_fs.c139
-rw-r--r--sbin/mount/pathnames.h33
-rw-r--r--sbin/mount/vfslist.c89
-rw-r--r--sbin/mount_cd9660/Makefile18
-rw-r--r--sbin/mount_cd9660/mount_cd9660.8160
-rw-r--r--sbin/mount_cd9660/mount_cd9660.c270
-rw-r--r--sbin/mount_fusefs/Makefile32
-rw-r--r--sbin/mount_fusefs/mount_fusefs.8362
-rw-r--r--sbin/mount_fusefs/mount_fusefs.c506
-rw-r--r--sbin/mount_msdosfs/Makefile19
-rw-r--r--sbin/mount_msdosfs/mount_msdosfs.8223
-rw-r--r--sbin/mount_msdosfs/mount_msdosfs.c324
-rw-r--r--sbin/mount_nfs/Makefile15
-rw-r--r--sbin/mount_nfs/mount_nfs.8526
-rw-r--r--sbin/mount_nfs/mount_nfs.c1042
-rw-r--r--sbin/mount_nullfs/Makefile13
-rw-r--r--sbin/mount_nullfs/mount_nullfs.8245
-rw-r--r--sbin/mount_nullfs/mount_nullfs.c143
-rw-r--r--sbin/mount_udf/Makefile16
-rw-r--r--sbin/mount_udf/mount_udf.876
-rw-r--r--sbin/mount_udf/mount_udf.c167
-rw-r--r--sbin/mount_unionfs/Makefile13
-rw-r--r--sbin/mount_unionfs/mount_unionfs.8395
-rw-r--r--sbin/mount_unionfs/mount_unionfs.c195
-rw-r--r--sbin/nandfs/Makefile9
-rw-r--r--sbin/nandfs/lssnap.c112
-rw-r--r--sbin/nandfs/mksnap.c80
-rw-r--r--sbin/nandfs/nandfs.874
-rw-r--r--sbin/nandfs/nandfs.c74
-rw-r--r--sbin/nandfs/nandfs.h40
-rw-r--r--sbin/nandfs/rmsnap.c87
-rw-r--r--sbin/natd/HISTORY146
-rw-r--r--sbin/natd/Makefile9
-rw-r--r--sbin/natd/README50
-rw-r--r--sbin/natd/icmp.c127
-rw-r--r--sbin/natd/natd.8830
-rw-r--r--sbin/natd/natd.c2041
-rw-r--r--sbin/natd/natd.h27
-rw-r--r--sbin/natd/samples/natd.cf.sample92
-rw-r--r--sbin/natd/samples/natd.test14
-rw-r--r--sbin/newfs/Makefile19
-rw-r--r--sbin/newfs/mkfs.c1159
-rw-r--r--sbin/newfs/newfs.8333
-rw-r--r--sbin/newfs/newfs.c509
-rw-r--r--sbin/newfs/newfs.h125
-rw-r--r--sbin/newfs/ref.test7
-rw-r--r--sbin/newfs/runtest00.sh19
-rw-r--r--sbin/newfs/runtest01.sh27
-rw-r--r--sbin/newfs_msdos/Makefile11
-rw-r--r--sbin/newfs_msdos/newfs_msdos.8241
-rw-r--r--sbin/newfs_msdos/newfs_msdos.c1061
-rw-r--r--sbin/newfs_nandfs/Makefile8
-rw-r--r--sbin/newfs_nandfs/newfs_nandfs.876
-rw-r--r--sbin/newfs_nandfs/newfs_nandfs.c1179
-rw-r--r--sbin/nfsiod/Makefile7
-rw-r--r--sbin/nfsiod/nfsiod.8100
-rw-r--r--sbin/nfsiod/nfsiod.c139
-rw-r--r--sbin/nos-tun/Makefile8
-rw-r--r--sbin/nos-tun/nos-tun.892
-rw-r--r--sbin/nos-tun/nos-tun.c398
-rw-r--r--sbin/nvmecontrol/Makefile10
-rw-r--r--sbin/nvmecontrol/devlist.c116
-rw-r--r--sbin/nvmecontrol/firmware.c322
-rw-r--r--sbin/nvmecontrol/identify.c284
-rw-r--r--sbin/nvmecontrol/logpage.c357
-rw-r--r--sbin/nvmecontrol/nvmecontrol.8130
-rw-r--r--sbin/nvmecontrol/nvmecontrol.c234
-rw-r--r--sbin/nvmecontrol/nvmecontrol.h74
-rw-r--r--sbin/nvmecontrol/perftest.c176
-rw-r--r--sbin/nvmecontrol/reset.c71
-rw-r--r--sbin/pfctl/Makefile33
-rw-r--r--sbin/pfctl/parse.y6038
-rw-r--r--sbin/pfctl/pf_print_state.c367
-rw-r--r--sbin/pfctl/pfctl.8686
-rw-r--r--sbin/pfctl/pfctl.c2387
-rw-r--r--sbin/pfctl/pfctl.h130
-rw-r--r--sbin/pfctl/pfctl_altq.c1258
-rw-r--r--sbin/pfctl/pfctl_optimize.c1655
-rw-r--r--sbin/pfctl/pfctl_osfp.c1108
-rw-r--r--sbin/pfctl/pfctl_parser.c1766
-rw-r--r--sbin/pfctl/pfctl_parser.h306
-rw-r--r--sbin/pfctl/pfctl_qstats.c449
-rw-r--r--sbin/pfctl/pfctl_radix.c585
-rw-r--r--sbin/pfctl/pfctl_table.c634
-rw-r--r--sbin/pflogd/Makefile15
-rw-r--r--sbin/ping/Makefile23
-rw-r--r--sbin/ping/ping.8554
-rw-r--r--sbin/ping/ping.c1843
-rw-r--r--sbin/ping6/Makefile15
-rw-r--r--sbin/ping6/ping6.8556
-rw-r--r--sbin/ping6/ping6.c2793
-rw-r--r--sbin/quotacheck/Makefile12
-rw-r--r--sbin/quotacheck/preen.c289
-rw-r--r--sbin/quotacheck/quotacheck.8203
-rw-r--r--sbin/quotacheck/quotacheck.c725
-rw-r--r--sbin/quotacheck/quotacheck.h37
-rw-r--r--sbin/rcorder/Makefile14
-rw-r--r--sbin/rcorder/ealloc.c118
-rw-r--r--sbin/rcorder/ealloc.h7
-rw-r--r--sbin/rcorder/hash.c435
-rw-r--r--sbin/rcorder/hash.h131
-rw-r--r--sbin/rcorder/rcorder.8189
-rw-r--r--sbin/rcorder/rcorder.c806
-rw-r--r--sbin/rcorder/sprite.h113
-rw-r--r--sbin/reboot/Makefile22
-rw-r--r--sbin/reboot/boot_i386.8379
-rw-r--r--sbin/reboot/nextboot.8138
-rw-r--r--sbin/reboot/nextboot.sh112
-rw-r--r--sbin/reboot/reboot.8143
-rw-r--r--sbin/reboot/reboot.c245
-rw-r--r--sbin/recoverdisk/Makefile8
-rw-r--r--sbin/recoverdisk/recoverdisk.1174
-rw-r--r--sbin/recoverdisk/recoverdisk.c322
-rw-r--r--sbin/resolvconf/Makefile37
-rw-r--r--sbin/restore/Makefile15
-rw-r--r--sbin/restore/dirs.c817
-rw-r--r--sbin/restore/extern.h109
-rw-r--r--sbin/restore/interactive.c770
-rw-r--r--sbin/restore/main.c376
-rw-r--r--sbin/restore/restore.8503
-rw-r--r--sbin/restore/restore.c860
-rw-r--r--sbin/restore/restore.h153
-rw-r--r--sbin/restore/symtab.c615
-rw-r--r--sbin/restore/tape.c1756
-rw-r--r--sbin/restore/utilities.c422
-rw-r--r--sbin/route/Makefile27
-rw-r--r--sbin/route/keywords57
-rw-r--r--sbin/route/route.8506
-rw-r--r--sbin/route/route.c1929
-rw-r--r--sbin/routed/Makefile12
-rw-r--r--sbin/routed/Makefile.inc3
-rw-r--r--sbin/routed/defs.h648
-rw-r--r--sbin/routed/if.c1383
-rw-r--r--sbin/routed/input.c1028
-rw-r--r--sbin/routed/main.c969
-rw-r--r--sbin/routed/output.c974
-rw-r--r--sbin/routed/parms.c1038
-rw-r--r--sbin/routed/pathnames.h52
-rw-r--r--sbin/routed/radix.c897
-rw-r--r--sbin/routed/radix.h145
-rw-r--r--sbin/routed/rdisc.c1062
-rw-r--r--sbin/routed/routed.8745
-rw-r--r--sbin/routed/rtquery/Makefile11
-rw-r--r--sbin/routed/rtquery/rtquery.8131
-rw-r--r--sbin/routed/rtquery/rtquery.c919
-rw-r--r--sbin/routed/table.c2155
-rw-r--r--sbin/routed/trace.c1021
-rw-r--r--sbin/rtsol/Makefile28
-rw-r--r--sbin/savecore/Makefile7
-rw-r--r--sbin/savecore/savecore.8170
-rw-r--r--sbin/savecore/savecore.c860
-rw-r--r--sbin/sconfig/Makefile9
-rw-r--r--sbin/sconfig/sconfig.8607
-rw-r--r--sbin/sconfig/sconfig.c1214
-rw-r--r--sbin/setkey/Makefile67
-rw-r--r--sbin/setkey/parse.y1268
-rw-r--r--sbin/setkey/sample.cf219
-rw-r--r--sbin/setkey/scriptdump.pl56
-rw-r--r--sbin/setkey/setkey.8729
-rw-r--r--sbin/setkey/setkey.c632
-rw-r--r--sbin/setkey/test-pfkey.c531
-rw-r--r--sbin/setkey/test-policy.c160
-rw-r--r--sbin/setkey/token.l287
-rw-r--r--sbin/setkey/vchar.h36
-rw-r--r--sbin/shutdown/Makefile13
-rw-r--r--sbin/shutdown/shutdown.8221
-rw-r--r--sbin/shutdown/shutdown.c559
-rw-r--r--sbin/spppcontrol/Makefile7
-rw-r--r--sbin/spppcontrol/spppcontrol.8275
-rw-r--r--sbin/spppcontrol/spppcontrol.c264
-rw-r--r--sbin/sunlabel/Makefile22
-rw-r--r--sbin/sunlabel/runtest.sh157
-rw-r--r--sbin/sunlabel/sunlabel.8432
-rw-r--r--sbin/sunlabel/sunlabel.c1007
-rw-r--r--sbin/swapon/Makefile13
-rw-r--r--sbin/swapon/swapon.8223
-rw-r--r--sbin/swapon/swapon.c851
-rw-r--r--sbin/sysctl/Makefile8
-rw-r--r--sbin/sysctl/sysctl.8324
-rw-r--r--sbin/sysctl/sysctl.c997
-rw-r--r--sbin/tests/Makefile10
-rw-r--r--sbin/tunefs/Makefile10
-rw-r--r--sbin/tunefs/tunefs.8208
-rw-r--r--sbin/tunefs/tunefs.c1135
-rw-r--r--sbin/umount/Makefile15
-rw-r--r--sbin/umount/umount.8149
-rw-r--r--sbin/umount/umount.c615
628 files changed, 239140 insertions, 0 deletions
diff --git a/sbin/Makefile b/sbin/Makefile
new file mode 100644
index 0000000..c828b21
--- /dev/null
+++ b/sbin/Makefile
@@ -0,0 +1,140 @@
+# @(#)Makefile 8.5 (Berkeley) 3/31/94
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+# XXX MISSING: icheck ncheck
+
+SUBDIR=adjkerntz \
+ badsect \
+ camcontrol \
+ clri \
+ comcontrol \
+ conscontrol \
+ ddb \
+ devfs \
+ dhclient \
+ dmesg \
+ dump \
+ dumpfs \
+ dumpon \
+ etherswitchcfg \
+ ffsinfo \
+ fsck \
+ fsck_ffs \
+ fsck_msdosfs \
+ fsdb \
+ fsirand \
+ gbde \
+ geom \
+ ggate \
+ growfs \
+ gvinum \
+ ifconfig \
+ init \
+ kldconfig \
+ kldload \
+ kldstat \
+ kldunload \
+ ldconfig \
+ md5 \
+ mdconfig \
+ mdmfs \
+ mknod \
+ mksnap_ffs \
+ mount \
+ mount_cd9660 \
+ mount_fusefs \
+ mount_msdosfs \
+ mount_nfs \
+ mount_nullfs \
+ mount_udf \
+ mount_unionfs \
+ newfs \
+ newfs_msdos \
+ nfsiod \
+ nos-tun \
+ ping \
+ rcorder \
+ reboot \
+ recoverdisk \
+ resolvconf \
+ restore \
+ route \
+ savecore \
+ setkey \
+ shutdown \
+ spppcontrol \
+ swapon \
+ sysctl \
+ tunefs \
+ umount
+
+.if ${MK_ATM} != "no"
+SUBDIR+= atm
+.endif
+
+.if ${MK_CASPER} != "no"
+SUBDIR+= casperd
+.endif
+
+.if ${MK_CCD} != "no"
+SUBDIR+= ccdconfig
+.endif
+
+.if ${MK_CXX} != "no"
+SUBDIR+= devd
+.endif
+
+.if ${MK_HAST} != "no"
+SUBDIR+= hastctl
+SUBDIR+= hastd
+.endif
+
+.if ${MK_INET6} != "no"
+SUBDIR+= ping6
+SUBDIR+= rtsol
+.endif
+
+.if ${MK_IPFILTER} != "no"
+SUBDIR+= ipf
+.endif
+
+.if ${MK_IPFW} != "no"
+SUBDIR+= ipfw
+SUBDIR+= natd
+.endif
+
+.if ${MK_ISCSI} != "no"
+SUBDIR+= iscontrol
+.endif
+
+.if ${MK_NAND} != "no"
+SUBDIR+= nandfs
+SUBDIR+= newfs_nandfs
+.endif
+
+.if ${MK_PF} != "no"
+SUBDIR+= pfctl
+SUBDIR+= pflogd
+.endif
+
+.if ${MK_QUOTAS} != "no"
+SUBDIR+= quotacheck
+.endif
+
+.if ${MK_ROUTED} != "no"
+SUBDIR+= routed
+.endif
+
+.if ${MK_TESTS} != "no"
+SUBDIR+= tests
+.endif
+
+.include <bsd.arch.inc.mk>
+
+SUBDIR:= ${SUBDIR:O}
+
+SUBDIR_PARALLEL=
+
+.include <bsd.subdir.mk>
diff --git a/sbin/Makefile.amd64 b/sbin/Makefile.amd64
new file mode 100644
index 0000000..d3cf350
--- /dev/null
+++ b/sbin/Makefile.amd64
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+SUBDIR += bsdlabel
+SUBDIR += fdisk
+SUBDIR += nvmecontrol
diff --git a/sbin/Makefile.arm b/sbin/Makefile.arm
new file mode 100644
index 0000000..2d231b0
--- /dev/null
+++ b/sbin/Makefile.arm
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+SUBDIR += bsdlabel
+SUBDIR += fdisk
diff --git a/sbin/Makefile.i386 b/sbin/Makefile.i386
new file mode 100644
index 0000000..0ced312
--- /dev/null
+++ b/sbin/Makefile.i386
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+SUBDIR += bsdlabel
+SUBDIR += fdisk
+SUBDIR += nvmecontrol
+SUBDIR += sconfig
diff --git a/sbin/Makefile.inc b/sbin/Makefile.inc
new file mode 100644
index 0000000..896c64c
--- /dev/null
+++ b/sbin/Makefile.inc
@@ -0,0 +1,11 @@
+# @(#)Makefile.inc 8.1 (Berkeley) 6/8/93
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+BINDIR?= /sbin
+WARNS?= 6
+
+.if ${MK_DYNAMICROOT} == "no"
+NO_SHARED?= YES
+.endif
diff --git a/sbin/Makefile.mips b/sbin/Makefile.mips
new file mode 100644
index 0000000..2d231b0
--- /dev/null
+++ b/sbin/Makefile.mips
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+SUBDIR += bsdlabel
+SUBDIR += fdisk
diff --git a/sbin/Makefile.pc98 b/sbin/Makefile.pc98
new file mode 100644
index 0000000..4aab559
--- /dev/null
+++ b/sbin/Makefile.pc98
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+SUBDIR += bsdlabel
+SUBDIR += fdisk_pc98
+SUBDIR += sconfig
diff --git a/sbin/Makefile.sparc64 b/sbin/Makefile.sparc64
new file mode 100644
index 0000000..9a5bbe6
--- /dev/null
+++ b/sbin/Makefile.sparc64
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+SUBDIR += bsdlabel
+SUBDIR += sunlabel
diff --git a/sbin/adjkerntz/Makefile b/sbin/adjkerntz/Makefile
new file mode 100644
index 0000000..27c1289
--- /dev/null
+++ b/sbin/adjkerntz/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+PROG= adjkerntz
+MAN= adjkerntz.8
+
+.include <bsd.prog.mk>
diff --git a/sbin/adjkerntz/adjkerntz.8 b/sbin/adjkerntz/adjkerntz.8
new file mode 100644
index 0000000..1bd1391
--- /dev/null
+++ b/sbin/adjkerntz/adjkerntz.8
@@ -0,0 +1,193 @@
+.\" Copyright (C) 1993-1998 by Andrey A. Chernov, Moscow, Russia.
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd March 8, 2012
+.Dt ADJKERNTZ 8
+.Os
+.Sh NAME
+.Nm adjkerntz
+.Nd adjust the local time CMOS clock to reflect time zone changes and keep the current timezone offset for the kernel
+.Sh SYNOPSIS
+.Nm
+.Fl i
+.Nm
+.Fl a Op Fl s
+.Sh DESCRIPTION
+The
+.Nm
+utility maintains the proper relationship between the kernel clock, which
+is always set to UTC and the CMOS clock, which may be set to local time.
+The
+.Nm
+utility also informs the kernel about machine timezone shifts in order to
+maintain proper timestamps for local time file systems such as the MS-DOS
+file system.
+The main purpose of maintaining these timestamps properly is to keep the
+timestamps of a
+.Fx
+MS-DOS file system and an MS-DOS operating system synchronized when they are
+installed on the same system rather than fixing broken MS-DOS file
+timestamps.
+If the file
+.Pa /etc/wall_cmos_clock
+exists, it means that the CMOS clock keeps local time (MS-DOS and MS-Windows
+compatible mode).
+If that file does not exist, it means that the CMOS clock keeps UTC time.
+The
+.Nm
+utility passes this state to the
+.Pa machdep.wall_cmos_clock
+kernel variable.
+.Pp
+Adjustments may be needed at system startup and shutdown, and
+whenever a time zone change occurs.
+To handle these different situations,
+.Nm
+is invoked in two ways:
+.Bl -tag -width 4n
+.It Fl i
+This form handles system startups and shutdowns.
+The
+.Nm
+utility is invoked with this option from
+.Pa /etc/rc
+on entry to multi-user mode, before any other daemons have been started.
+The
+.Nm
+utility puts itself into the background.
+Then, for a local time CMOS clock,
+.Nm
+reads the local time from it
+and sets the kernel clock to the corresponding UTC time.
+The
+.Nm
+utility also stores the local time zone offset in the
+.Pa machdep.adjkerntz
+kernel variable, for use by subsequent invocations of
+.Em "'adjkerntz -a'"
+and by local time file systems.
+.Pp
+For a local time CMOS clock
+.Em "'adjkerntz -i'"
+pauses and remains inactive as a background daemon until it
+receives a SIGTERM.
+The SIGTERM will normally be sent by
+.Xr init 8
+when the system leaves multi-user mode (usually, because the system
+is being shut down).
+After receiving the SIGTERM,
+.Nm
+reads the UTC kernel clock and updates the CMOS clock, if necessary,
+to ensure that it reflects the current local time zone.
+Then
+.Nm
+exits.
+.It Fl a Op Fl s
+This form is used to update the local time CMOS clock and kernel
+.Pa machdep.adjkerntz
+variable when time zone changes occur,
+e.g., when entering or leaving daylight savings time.
+The
+.Nm
+utility uses the kernel clock's UTC time,
+the previously stored
+time zone offset, and the changed time zone rule to
+calculate a new time zone offset.
+It stores the new offset into the
+.Pa machdep.adjkerntz
+kernel variable and updates the wall CMOS clock to the new local time.
+If
+.Em "'adjkerntz -a'"
+was started at a nonexistent time (during a timezone change), it exits
+with a warning diagnostic unless the
+.Fl s
+option was used, in which case
+.Nm
+sleeps 30 minutes and tries again.
+.Pp
+This form should be invoked from root's
+.Xr crontab 5
+every half hour between midnight and 5am, when most modern time
+zone changes occur.
+Warning: do not use the
+.Fl s
+option in a
+.Xr crontab 5
+command line, or multiple
+.Em "'adjkerntz -a'"
+instances could conflict with each other.
+.El
+.Pp
+The
+.Nm
+utility clears the kernel timezone structure and makes the kernel clock run
+in the UTC time zone.
+Super-user privileges are required for all operations.
+.Sh ENVIRONMENT
+.Bl -tag -width Fl
+.It Ev TZ
+Time zone change rule, see
+.Xr tzset 3 ;
+not needed when
+.Xr tzsetup 8
+or
+.Xr zic 8
+is used.
+.El
+.Sh FILES
+.Bl -tag -width /etc/wall_cmos_clock -compact
+.It Pa /etc/localtime
+Current zoneinfo file, see
+.Xr tzsetup 8
+and
+.Xr zic 8 .
+.It Pa /etc/wall_cmos_clock
+Empty file.
+Its presence indicates that the machine's CMOS clock is set to local
+time, while its absence indicates a UTC CMOS clock.
+.El
+.Sh DIAGNOSTICS
+No diagnostics.
+If an error occurs,
+.Nm
+logs an error message via
+.Xr syslog 3
+and exits with a nonzero return code.
+.Sh SEE ALSO
+.Xr tzset 3 ,
+.Xr crontab 5 ,
+.Xr mount_msdosfs 8 ,
+.Xr rc 8 ,
+.Xr sysctl 8 ,
+.Xr tzsetup 8 ,
+.Xr zic 8
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Fx 1.0 .
+.Sh AUTHORS
+.An Andrey A. Chernov Aq Mt ache@astral.msk.su
diff --git a/sbin/adjkerntz/adjkerntz.c b/sbin/adjkerntz/adjkerntz.c
new file mode 100644
index 0000000..c42379d
--- /dev/null
+++ b/sbin/adjkerntz/adjkerntz.c
@@ -0,0 +1,378 @@
+/*
+ * Copyright (C) 1993-1998 by Andrey A. Chernov, Moscow, Russia.
+ * 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.
+ *
+ * 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 REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if 0
+#ifndef lint
+static const char copyright[] =
+"@(#)Copyright (C) 1993-1996 by Andrey A. Chernov, Moscow, Russia.\n\
+ All rights reserved.\n";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*
+ * Andrey A. Chernov <ache@astral.msk.su> Dec 20 1993
+ *
+ * Fix kernel time value if machine run wall CMOS clock
+ * (and /etc/wall_cmos_clock file present)
+ * using zoneinfo rules or direct TZ environment variable set.
+ * Use Joerg Wunsch idea for seconds accurate offset calculation
+ * with Garrett Wollman and Bruce Evans fixes.
+ *
+ */
+#include <stdio.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <syslog.h>
+#include <sys/time.h>
+#include <sys/param.h>
+#include <machine/cpu.h>
+#include <sys/sysctl.h>
+
+#include "pathnames.h"
+
+/*#define DEBUG*/
+
+#define True (1)
+#define False (0)
+#define Unknown (-1)
+
+#define REPORT_PERIOD (30*60)
+
+static void fake(int);
+static void usage(void);
+
+static void
+fake(int unused __unused)
+{
+
+ /* Do nothing. */
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct tm local;
+ struct timeval tv, *stv;
+ struct timezone tz, *stz;
+ int kern_offset, wall_clock, disrtcset;
+ size_t len;
+ /* Avoid time_t here, can be unsigned long or worse */
+ long offset, localsec, diff;
+ time_t initial_sec, final_sec;
+ int ch;
+ int initial_isdst = -1, final_isdst;
+ int need_restore = False, sleep_mode = False, looping,
+ init = Unknown;
+ sigset_t mask, emask;
+
+ while ((ch = getopt(argc, argv, "ais")) != -1)
+ switch((char)ch) {
+ case 'i': /* initial call, save offset */
+ if (init != Unknown)
+ usage();
+ init = True;
+ break;
+ case 'a': /* adjustment call, use saved offset */
+ if (init != Unknown)
+ usage();
+ init = False;
+ break;
+ case 's':
+ sleep_mode = True;
+ break;
+ default:
+ usage();
+ }
+ if (init == Unknown)
+ usage();
+
+ if (access(_PATH_CLOCK, F_OK) != 0)
+ return 0;
+
+ if (init)
+ sleep_mode = True;
+
+ sigemptyset(&mask);
+ sigemptyset(&emask);
+ sigaddset(&mask, SIGTERM);
+
+ openlog("adjkerntz", LOG_PID|LOG_PERROR, LOG_DAEMON);
+
+ (void) signal(SIGHUP, SIG_IGN);
+
+ if (init && daemon(0,
+#ifdef DEBUG
+ 1
+#else
+ 0
+#endif
+ )) {
+ syslog(LOG_ERR, "daemon: %m");
+ return 1;
+ }
+
+again:
+ (void) sigprocmask(SIG_BLOCK, &mask, NULL);
+ (void) signal(SIGTERM, fake);
+
+ diff = 0;
+ stv = NULL;
+ stz = NULL;
+ looping = False;
+
+ wall_clock = (access(_PATH_CLOCK, F_OK) == 0);
+ if (init && !sleep_mode) {
+ init = False;
+ if (!wall_clock)
+ return 0;
+ }
+
+ tzset();
+
+ len = sizeof(kern_offset);
+ if (sysctlbyname("machdep.adjkerntz", &kern_offset, &len, NULL, 0) == -1) {
+ syslog(LOG_ERR, "sysctl(\"machdep.adjkerntz\"): %m");
+ return 1;
+ }
+
+/****** Critical section, do all things as fast as possible ******/
+
+ /* get local CMOS clock and possible kernel offset */
+ if (gettimeofday(&tv, &tz)) {
+ syslog(LOG_ERR, "gettimeofday: %m");
+ return 1;
+ }
+
+ /* get the actual local timezone difference */
+ initial_sec = tv.tv_sec;
+
+recalculate:
+ local = *localtime(&initial_sec);
+ if (diff == 0)
+ initial_isdst = local.tm_isdst;
+ local.tm_isdst = initial_isdst;
+
+ /* calculate local CMOS diff from GMT */
+
+ localsec = mktime(&local);
+ if (localsec == -1) {
+ /*
+ * XXX user can only control local time, and it is
+ * unacceptable to fail here for init. 2:30 am in the
+ * middle of the nonexistent hour means 3:30 am.
+ */
+ if (!sleep_mode) {
+ syslog(LOG_WARNING,
+ "Warning: nonexistent local time, try to run later.");
+ syslog(LOG_WARNING, "Giving up.");
+ return 1;
+ }
+ syslog(LOG_WARNING,
+ "Warning: nonexistent local time.");
+ syslog(LOG_WARNING, "Will retry after %d minutes.",
+ REPORT_PERIOD / 60);
+ (void) signal(SIGTERM, SIG_DFL);
+ (void) sigprocmask(SIG_UNBLOCK, &mask, NULL);
+ (void) sleep(REPORT_PERIOD);
+ goto again;
+ }
+ offset = -local.tm_gmtoff;
+#ifdef DEBUG
+ fprintf(stderr, "Initial offset: %ld secs\n", offset);
+#endif
+
+ /* correct the kerneltime for this diffs */
+ /* subtract kernel offset, if present, old offset too */
+
+ diff = offset - tz.tz_minuteswest * 60 - kern_offset;
+
+ if (diff != 0) {
+#ifdef DEBUG
+ fprintf(stderr, "Initial diff: %ld secs\n", diff);
+#endif
+ /* Yet one step for final time */
+
+ final_sec = initial_sec + diff;
+
+ /* get the actual local timezone difference */
+ local = *localtime(&final_sec);
+ final_isdst = diff < 0 ? initial_isdst : local.tm_isdst;
+ if (diff > 0 && initial_isdst != final_isdst) {
+ if (looping)
+ goto bad_final;
+ looping = True;
+ initial_isdst = final_isdst;
+ goto recalculate;
+ }
+ local.tm_isdst = final_isdst;
+
+ localsec = mktime(&local);
+ if (localsec == -1) {
+ bad_final:
+ /*
+ * XXX as above. The user has even less control,
+ * but perhaps we never get here.
+ */
+ if (!sleep_mode) {
+ syslog(LOG_WARNING,
+ "Warning: nonexistent final local time, try to run later.");
+ syslog(LOG_WARNING, "Giving up.");
+ return 1;
+ }
+ syslog(LOG_WARNING,
+ "Warning: nonexistent final local time.");
+ syslog(LOG_WARNING, "Will retry after %d minutes.",
+ REPORT_PERIOD / 60);
+ (void) signal(SIGTERM, SIG_DFL);
+ (void) sigprocmask(SIG_UNBLOCK, &mask, NULL);
+ (void) sleep(REPORT_PERIOD);
+ goto again;
+ }
+ offset = -local.tm_gmtoff;
+#ifdef DEBUG
+ fprintf(stderr, "Final offset: %ld secs\n", offset);
+#endif
+
+ /* correct the kerneltime for this diffs */
+ /* subtract kernel offset, if present, old offset too */
+
+ diff = offset - tz.tz_minuteswest * 60 - kern_offset;
+
+ if (diff != 0) {
+#ifdef DEBUG
+ fprintf(stderr, "Final diff: %ld secs\n", diff);
+#endif
+ /*
+ * stv is abused as a flag. The important value
+ * is in `diff'.
+ */
+ stv = &tv;
+ }
+ }
+
+ if (tz.tz_dsttime != 0 || tz.tz_minuteswest != 0) {
+ tz.tz_dsttime = tz.tz_minuteswest = 0; /* zone info is garbage */
+ stz = &tz;
+ }
+ if (!wall_clock && stz == NULL)
+ stv = NULL;
+
+ /* if init or UTC clock and offset/date will be changed, */
+ /* disable RTC modification for a while. */
+
+ if ( (init && stv != NULL)
+ || ((init || !wall_clock) && kern_offset != offset)
+ ) {
+ len = sizeof(disrtcset);
+ if (sysctlbyname("machdep.disable_rtc_set", &disrtcset, &len, NULL, 0) == -1) {
+ syslog(LOG_ERR, "sysctl(get: \"machdep.disable_rtc_set\"): %m");
+ return 1;
+ }
+ if (disrtcset == 0) {
+ disrtcset = 1;
+ need_restore = True;
+ if (sysctlbyname("machdep.disable_rtc_set", NULL, NULL, &disrtcset, len) == -1) {
+ syslog(LOG_ERR, "sysctl(set: \"machdep.disable_rtc_set\"): %m");
+ return 1;
+ }
+ }
+ }
+
+ if ( (init && (stv != NULL || stz != NULL))
+ || (stz != NULL && stv == NULL)
+ ) {
+ if (stv != NULL) {
+ /*
+ * Get the time again, as close as possible to
+ * adjusting it, to minimise drift.
+ * XXX we'd better not fail between here and
+ * restoring disrtcset, since we don't clean up
+ * anything.
+ */
+ (void)gettimeofday(&tv, NULL);
+ tv.tv_sec += diff;
+ stv = &tv;
+ }
+ if (settimeofday(stv, stz)) {
+ syslog(LOG_ERR, "settimeofday: %m");
+ return 1;
+ }
+ }
+
+ /* setting machdep.adjkerntz have a side effect: resettodr(), which */
+ /* can be disabled by machdep.disable_rtc_set, so if init or UTC clock */
+ /* -- don't write RTC, else write RTC. */
+
+ if (kern_offset != offset) {
+ kern_offset = offset;
+ len = sizeof(kern_offset);
+ if (sysctlbyname("machdep.adjkerntz", NULL, NULL, &kern_offset, len) == -1) {
+ syslog(LOG_ERR, "sysctl(set: \"machdep.adjkerntz\"): %m");
+ return 1;
+ }
+ }
+
+ len = sizeof(wall_clock);
+ if (sysctlbyname("machdep.wall_cmos_clock", NULL, NULL, &wall_clock, len) == -1) {
+ syslog(LOG_ERR, "sysctl(set: \"machdep.wall_cmos_clock\"): %m");
+ return 1;
+ }
+
+ if (need_restore) {
+ need_restore = False;
+ disrtcset = 0;
+ len = sizeof(disrtcset);
+ if (sysctlbyname("machdep.disable_rtc_set", NULL, NULL, &disrtcset, len) == -1) {
+ syslog(LOG_ERR, "sysctl(set: \"machdep.disable_rtc_set\"): %m");
+ return 1;
+ }
+ }
+
+/****** End of critical section ******/
+
+ if (init && wall_clock) {
+ sleep_mode = False;
+ /* wait for signals and acts like -a */
+ (void) sigsuspend(&emask);
+ goto again;
+ }
+
+ return 0;
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "%s\n%s\n%s\n%s\n",
+ "usage: adjkerntz -i",
+ "\t\t(initial call from /etc/rc)",
+ " adjkerntz -a [-s]",
+ "\t\t(adjustment call, -s for sleep/retry mode)");
+ exit(2);
+}
diff --git a/sbin/adjkerntz/pathnames.h b/sbin/adjkerntz/pathnames.h
new file mode 100644
index 0000000..c0e7526
--- /dev/null
+++ b/sbin/adjkerntz/pathnames.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 1993 by Andrew A. Chernov, Moscow, Russia.
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``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.
+ */
+
+#include <paths.h>
+
+#define _PATH_CLOCK "/etc/wall_cmos_clock"
diff --git a/sbin/atm/Makefile b/sbin/atm/Makefile
new file mode 100644
index 0000000..4145d21
--- /dev/null
+++ b/sbin/atm/Makefile
@@ -0,0 +1,28 @@
+# ===================================
+# HARP | Host ATM Research Platform
+# ===================================
+#
+# This Host ATM Research Platform ("HARP") file (the "Software") is
+# made available by Network Computing Services, Inc. ("NetworkCS")
+# "AS IS". NetworkCS does not provide maintenance, improvements or
+# support of any kind.
+#
+# NETWORKCS MAKES NO WARRANTIES OR REPRESENTATIONS, EXPRESS OR IMPLIED,
+# INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS FOR A PARTICULAR PURPOSE, AS TO ANY ELEMENT OF THE
+# SOFTWARE OR ANY SUPPORT PROVIDED IN CONNECTION WITH THIS SOFTWARE.
+# In no event shall NetworkCS be responsible for any damages, including
+# but not limited to consequential damages, arising from or relating to
+# any use of the Software or related support.
+#
+# Copyright 1994-1998 Network Computing Services, Inc.
+#
+# Copies of this Software may be made, however, the above copyright
+# notice must be reproduced on all copies.
+#
+# @(#) $Id: Makefile,v 1.5 1998/07/10 16:01:58 jpt Exp $
+# $FreeBSD$
+
+SUBDIR= atmconfig
+
+.include <bsd.subdir.mk>
diff --git a/sbin/atm/Makefile.inc b/sbin/atm/Makefile.inc
new file mode 100644
index 0000000..3d5738a
--- /dev/null
+++ b/sbin/atm/Makefile.inc
@@ -0,0 +1,26 @@
+# ===================================
+# HARP | Host ATM Research Platform
+# ===================================
+#
+# This Host ATM Research Platform ("HARP") file (the "Software") is
+# made available by Network Computing Services, Inc. ("NetworkCS")
+# "AS IS". NetworkCS does not provide maintenance, improvements or
+# support of any kind.
+#
+# NETWORKCS MAKES NO WARRANTIES OR REPRESENTATIONS, EXPRESS OR IMPLIED,
+# INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS FOR A PARTICULAR PURPOSE, AS TO ANY ELEMENT OF THE
+# SOFTWARE OR ANY SUPPORT PROVIDED IN CONNECTION WITH THIS SOFTWARE.
+# In no event shall NetworkCS be responsible for any damages, including
+# but not limited to consequential damages, arising from or relating to
+# any use of the Software or related support.
+#
+# Copyright 1994-1998 Network Computing Services, Inc.
+#
+# Copies of this Software may be made, however, the above copyright
+# notice must be reproduced on all copies.
+#
+# @(#) $Id: Makefile.inc,v 1.5 1998/07/10 16:01:58 jpt Exp $
+# $FreeBSD$
+
+.include "../Makefile.inc"
diff --git a/sbin/atm/atmconfig/Makefile b/sbin/atm/atmconfig/Makefile
new file mode 100644
index 0000000..1e48f04
--- /dev/null
+++ b/sbin/atm/atmconfig/Makefile
@@ -0,0 +1,43 @@
+# Copyright (c) 2001-2003
+# Fraunhofer Institute for Open Communication Systems (FhG Fokus).
+# All rights reserved.
+# Author: Harti Brandt <brandt@fokus.gmd.de>
+#
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+PROG= atmconfig
+SRCS= main.c diag.c natm.c
+MAN= atmconfig.8
+# CFLAGS+= -DPATH_HELP='".:/usr/share/doc/atm:/usr/local/share/doc/atm"'
+
+CFLAGS+= -I${.OBJDIR}
+
+.if !defined(RESCUE) && ${MK_BSNMP} != "no"
+CFLAGS+= -DWITH_BSNMP
+SRCS+= oid.h atmconfig_device.c
+LIBADD+= bsnmp
+. if ${MK_DYNAMICROOT} == "no" && ${MK_OPENSSL} != "no"
+LIBADD+= crypto
+. endif
+.endif
+
+CLEANFILES+= oid.h
+
+# XXX - this is verboten
+.if ${MACHINE_CPUARCH} == "arm"
+WARNS?= 3
+.endif
+
+FILES= atmconfig.help atmconfig_device.help
+FILESDIR= /usr/share/doc/atm
+
+SNMP_ATM_DEF= ${.CURDIR}/../../../contrib/ngatm/snmp_atm/atm_tree.def \
+ ${.CURDIR}/../../../usr.sbin/bsnmpd/modules/snmp_atm/atm_freebsd.def
+
+oid.h: atm_oid.list ${SNMP_ATM_DEF}
+ cat ${SNMP_ATM_DEF} | gensnmptree -e `tail -n +2 ${.CURDIR}/atm_oid.list` \
+ > ${.TARGET}
+
+.include <bsd.prog.mk>
diff --git a/sbin/atm/atmconfig/atm_oid.list b/sbin/atm/atmconfig/atm_oid.list
new file mode 100644
index 0000000..ca64afa
--- /dev/null
+++ b/sbin/atm/atmconfig/atm_oid.list
@@ -0,0 +1,20 @@
+# $FreeBSD$
+begemotAtmIfTable
+begemotAtmIfName
+begemotAtmIfNodeId
+begemotAtmIfPcr
+begemotAtmIfMedia
+begemotAtmIfVpiBits
+begemotAtmIfVciBits
+begemotAtmIfMaxVpcs
+begemotAtmIfMaxVccs
+begemotAtmIfEsi
+begemotAtmIfCarrierStatus
+begemotAtmIfMode
+begemotAtmIfTableLastChange
+begemotAtmHWTable
+begemotAtmHWVendor
+begemotAtmHWDevice
+begemotAtmHWSerial
+begemotAtmHWVersion
+begemotAtmHWSoftVersion
diff --git a/sbin/atm/atmconfig/atmconfig.8 b/sbin/atm/atmconfig/atmconfig.8
new file mode 100644
index 0000000..dc765ca
--- /dev/null
+++ b/sbin/atm/atmconfig/atmconfig.8
@@ -0,0 +1,319 @@
+.\"
+.\" Copyright (c) 2001-2003
+.\" Fraunhofer Institute for Open Communication Systems (FhG Fokus).
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" Author: Hartmut Brandt <harti@FreeBSD.org>
+.\"
+.\" $FreeBSD$
+.\"
+.Dd August 11, 2003
+.Dt ATMCONFIG 8
+.Os
+.Sh NAME
+.Nm atmconfig
+.Nd "ATM configuration tool"
+.Sh SYNOPSIS
+.Nm
+.Op Fl htv
+.Op Ar command Op Ar sub-command Op ...
+.Op Ar options
+.Op Ar arg ...
+.Sh DESCRIPTION
+The
+.Nm
+tool is used to configure the Netgraph ATM network sub-system.
+.Pp
+The command line of
+.Nm
+generally consists of common options followed by a command string, optionally
+followed by sub-command strings, optional command specific options and
+command specific arguments.
+Commands and sub-commands as well as command
+specific options may be abbreviated as
+long as there is only one match possible.
+.Ss Common Options
+The following common options change the overall behaviour of
+.Nm :
+.Bl -tag -width indent
+.It Fl h
+Print a very short usage info and exit.
+.It Fl t
+Several show-type commands output a header and then several lines
+of information.
+If this option is given, the header is omitted, simplifying the parsing
+of the output.
+.It Fl v
+Be more verbose.
+.El
+.Ss Obtaining Help
+The
+.Ic help
+command has a number of useful sub-commands.
+.Pp
+To get general help use:
+.D1 Nm Ic help
+.Pp
+To get a list of available commands use:
+.D1 Nm Ic help Cm commands
+.Pp
+To get a list of available sub-commands use:
+.D1 Nm Ic help Ar command
+.Pp
+or (if there are deeper levels of sub-commands):
+.D1 Nm Ic help Ar command sub-command ...
+.Pp
+To get a list of options and arguments for a command use:
+.D1 Nm Ic help Ar command sub-command ...
+(given that there are no further sub-command levels).
+.Pp
+To get a list of common options use:
+.D1 Nm Ic help Cm options
+.Ss The Ic diag Command
+The
+.Ic diag
+command allows the inspection of the ATM interfaces on the local host
+and the modification of device parameters.
+Sub-commands are:
+.Cm list
+(print a list of interfaces),
+.Cm config
+(print hardware configuration),
+.Cm phy
+(access PHY chip),
+.Cm stats
+(print statistics) and
+.Cm vcc
+(print list of VCCs).
+.Bl -tag -width indent
+.\"----------------------------------------
+.It Nm Ic diag Cm list
+This sub-command lists all ATM interfaces in the system.
+It takes no options or arguments.
+.\"----------------------------------------
+.It Xo
+.Nm Ic diag Cm config
+.Op Fl atm
+.Op Fl hardware
+.Op Ar device ...
+.Xc
+This command prints the configuration of ATM interfaces.
+If no
+.Ar device
+is given, all devices are listed, otherwise only the specified devices.
+The option
+.Fl atm
+instructs the command to print ATM layer configuration parameters like
+the number of VCI and VPI bits, whereas the
+.Fl hardware
+option requests card specific information like the vendor or the serial
+number.
+If none of the options is given, the defaults is to assume
+.Fl atm .
+.\"----------------------------------------
+.It Xo
+.Nm Ic diag Cm phy print
+.Op Fl numeric
+.Ar device
+.Xc
+This command prints the PHY registers in a (potential)
+human comprehensible format.
+If
+.Fl numeric
+is given, the format is hex bytes.
+Otherwise, textual representation will be printed.
+.\"----------------------------------------
+.It Nm Ic diag Cm phy show Op Ar device ...
+This sub-command prints static information about the PHY device used
+in the ATM card like the type of the PHY and the media.
+.\"----------------------------------------
+.It Xo
+.Nm Ic diag Cm phy set
+.Ar device
+.Ar reg
+.Ar mask
+.Ar val
+.Xc
+This sub-command allows one to change bits in PHY registers.
+This should be used with great care.
+The bits of the given PHY chip register for which the corresponding bit in
+.Ar mask
+is one are set to the values of the corresponding bits in
+.Ar val .
+All register bits that have a zero in
+.Ar mask
+are written back with their original value.
+.\"----------------------------------------
+.It Xo
+.Nm Ic diag Cm phy stats
+.Op Fl clear
+.Ar device
+.Xc
+Print the PHY statistics for the given
+.Ar device .
+When the optional
+.Fl clear
+is given, the statistics are cleared atomically.
+.\"----------------------------------------
+.It Xo
+.Nm Ic diag Cm vcc
+.Op Fl abr
+.Op Fl channel
+.Op Fl traffic
+.Op Ar device
+.Xc
+Retrieve the list of currently active channels on either all
+or the specified interfaces.
+For each channel, the following information is printed depending
+on the options (default is
+.Fl channel ) .
+.Bl -tag -width ".Fl traffic"
+.It Fl abr
+Print ABR specific traffic parameters: ICR, TBE, NRM, TRM, ADTF, RIF, RDF,
+CDF.
+.It Fl channel
+Print basic information: VPI, VCI, AAL, traffic type, MTU and flags.
+.It Fl traffic
+Print traffic parameters: PCR, SCR, MBS, MCR.
+.El
+.\"----------------------------------------
+.It Nm Ic diag Cm stats Ar device
+Print driver specific statistics.
+.El
+.Ss The Ic natm Command
+The
+.Ic natm
+command is used to change
+.Xr natmip 4
+routes on the local host.
+The sub-commands for the routing table are:
+.Cm add
+(to add a new route),
+.Cm delete
+(to delete an existing route) and
+.Cm show
+(to print the currently installed NATM routes).
+.Pp
+.Bl -tag -width indent -compact
+.\"----------------------------------------
+.It Xo
+.Nm Ic natm Cm add
+.Ar dest
+.Ar device
+.Ar vpi
+.Ar vci
+.Ar encaps
+.Xc
+.It Xo
+.Nm Ic natm Cm add
+.Ar dest
+.Ar device
+.Ar vpi
+.Ar vci
+.Ar encaps
+.Cm ubr Op Ar pcr
+.Xc
+.It Xo
+.Nm Ic natm Cm add
+.Ar dest
+.Ar device
+.Ar vpi
+.Ar vci
+.Ar encaps
+.Cm cbr Ar pcr
+.Xc
+.It Xo
+.Nm Ic natm Cm add
+.Ar dest
+.Ar device
+.Ar vpi
+.Ar vci
+.Ar encaps
+.Cm vbr Ar pcr scr mbs
+.Xc
+.It Xo
+.Nm Ic natm Cm add
+.Ar dest
+.Ar device
+.Ar vpi
+.Ar vci
+.Ar encaps
+.Cm abr Ar pcr mcr icr tbe nrm trm adtf rif rdf cdf
+.Xc
+Add a new route to the routing table.
+The destination address (the address
+on the other end of the link) is given in
+.Ar dest .
+The
+.Ar device ,
+.Ar vpi
+and
+.Ar vci
+arguments
+are the name of the ATM device and the VPI and VCI values for the link.
+The
+.Ar encaps
+argument
+may be either
+.Cm AAL5
+or
+.Cm LLC/SNAP
+both of which specify AAL5 encapsulation, the first one without additional
+encapsulation, the second one with LLC/SNAP headers.
+The first two forms of the command add an UBR (unspecified bit rate) channel,
+where the second form allows the optional specification of a peak cell
+rate (PCR).
+The third form adds a CBR (constant bit rate) channel where a PCR
+must be given.
+The fourth form adds a VBR (variable bit rate) channel.
+The arguments are the peak cell rate, the sustainable cell rate and the
+maximum bursts size.
+The last form of the command adds an ABR (available bit rate) channel.
+.\"----------------------------------------
+.Pp
+.It Nm Ic natm Cm delete Ar dest
+.It Xo
+.Nm Ic natm Cm delete
+.Ar device
+.Ar vpi
+.Ar vci
+.Xc
+This commands deletes an NATM route.
+The route may be specified either by the destination address or
+by the
+.Ar device , vpi
+and
+.Ar vci
+triple.
+.\"----------------------------------------
+.Pp
+.It Nm Ic natm Cm show
+List all NATM routes.
+.El
+.Sh SEE ALSO
+.Xr natm 4 ,
+.Xr natmip 4 ,
+.Xr atm 8
+.Sh AUTHORS
+.An Hartmut Brandt Aq Mt harti@FreeBSD.org
diff --git a/sbin/atm/atmconfig/atmconfig.h b/sbin/atm/atmconfig/atmconfig.h
new file mode 100644
index 0000000..5e5b041
--- /dev/null
+++ b/sbin/atm/atmconfig/atmconfig.h
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2001-2003
+ * Fraunhofer Institute for Open Communication Systems (FhG Fokus).
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * Author: Hartmut Brandt <harti@freebsd.org>
+ *
+ * $FreeBSD$
+ */
+#ifndef _ATMCONFIG_H
+#define _ATMCONFIG_H
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/queue.h>
+#include <netgraph/ng_message.h>
+
+#define DEFAULT_INTERFACE "hatm0"
+
+struct cmdtab {
+ const char *string;
+ const struct cmdtab *sub;
+ void (*func)(int, char *[]);
+};
+
+/*
+ * client configuration info
+ */
+struct amodule {
+ const struct cmdtab *cmd;
+};
+
+#define DEF_MODULE(CMDTAB) \
+struct amodule amodule_1 = { CMDTAB }
+
+/* for compiled-in modules */
+void register_module(const struct amodule *);
+
+/* print a message if we are verbose */
+void verb(const char *, ...) __printflike(1, 2);
+
+/* print heading */
+void heading(const char *, ...) __printflike(1, 2);
+
+/* before starting output */
+void heading_init(void);
+
+/* stringify an enumerated value */
+struct penum {
+ int32_t value;
+ const char *str;
+};
+const char *penum(int32_t value, const struct penum *strtab, char *buf);
+int pparse(int32_t *, const struct penum *, const char *);
+
+enum {
+ OPT_NONE,
+ OPT_UINT,
+ OPT_INT,
+ OPT_UINT32,
+ OPT_INT32,
+ OPT_UINT64,
+ OPT_INT64,
+ OPT_FLAG,
+ OPT_VCI,
+ OPT_STRING,
+ OPT_SIMPLE,
+};
+struct option {
+ const char *optstr;
+ int opttype;
+ void *optarg;
+};
+
+int parse_options(int *_pargc, char ***_pargv,
+ const struct option *_opts);
+
+/* XXX while this is compiled in */
+void device_register(void);
+
+#endif /* _ATMCONFIG_H */
diff --git a/sbin/atm/atmconfig/atmconfig.help b/sbin/atm/atmconfig/atmconfig.help
new file mode 100644
index 0000000..8c6e7ce
--- /dev/null
+++ b/sbin/atm/atmconfig/atmconfig.help
@@ -0,0 +1,223 @@
+#
+# Copyright (c) 2001-2003
+# Fraunhofer Institute for Open Communication Systems (FhG Fokus).
+# All rights reserved.
+# Copyright (c) 2004
+# Hartmut Brandt.
+# All rights reserved.
+#
+# Author: Hartmut Brandt <harti@freebsd.org>
+#
+# 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.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# Help file for the atmconfig utility
+#
+# $FreeBSD$
+#
+^0 intro
+ATM configuration utility.
+usage:
+ atmconfig [common-options] command [subcommand] [options]
+
+Use 'atmconfig help' for general help or 'atmconfig help <command>' for
+help on 'command' or 'atmconfig help commands' for a list of commands.
+
+^0 help
+^^ help show help information
+Use one of the following commands to get help on atmconfig:
+
+ atmconfig help options
+ gives you help on common command line options
+ atmconfig help commands
+ prints a list of available commands (and help items)
+ atmconfig help <command>
+ prints help on the given command (including a list of subcommands)
+ atmconfig help <command> <subcommand>
+ gives help on the given subcommand
+
+^0 options
+^^ help options list common options
+Common command line options can be specified for all commands. They
+are written immediately before the command. The following options are
+available:
+
+ -h print short help
+ -t don't print headings for 'show'-type commands
+ -v be verbose about all actions.
+
+^0 commands
+^^ help commands show available commands
+The following commands are available:
+
+$MAIN
+
+^0 diag
+^^ diag show/modify ATM hardware interfaces
+This command shows information about the ATM hardware interfaces in the
+system. A list of ATM devices is obtained by:
+
+ atmconfig [common-options] diag list
+
+Information about the hardware configuration of the ATM interfaces is
+reported by:
+
+ atmconfig [common-options] diag config [options] [<device> ...]
+
+The phy chip can be access with:
+
+ atmconfig [common-options] diag phy print [options] <device>
+ atmconfig [common-options] diag phy show <device>
+ atmconfig [common-options] diag phy set <device> <reg> <msk> <val>
+ atmconfig [common-options] diag phy set stats [options] <device>
+
+A list of open VCCs can be obtained with:
+
+ atmconfig [common-options] diag vcc [<device> ...]
+
+Driver internal statistics are printed with
+
+ atmconfig [common-options] diag stats <device>
+
+^1 list
+usage: atmconfig [common-options] diag list
+
+List all known ATM devices in the system.
+
+^1 config
+usage: atmconfig [common-options] diag config [-hardware] [-atm] [device ...]
+options:
+ -hardware print hardware related information
+ -atm print ATM related information
+
+If now device is given as argument information about all devices is shown.
+The default is to print only ATM related information.
+
+^1 phy
+To show the type of the PHY and its state:
+
+ atmconfig [common-options] diag phy show <device>
+
+Change a PHY register (use with care):
+
+ atmconfig [common-options] diag phy set <device> <reg> <msk> <val>
+
+Print the PHY registers in a human readable form:
+
+ atmconfig [common-options] diag phy print [-numeric] <device>
+
+The PHY statistics can be printed with:
+
+ atmconfig [common-options] diag phy stats [-clear] <device>
+
+^2 show
+usage: atmconfig [common-options] diag phy show <device>
+
+Show configuration and state information about the PHY chip on the given
+ATM interface.
+
+^2 set
+usage: atmconfig [common-options] diag phy set <device> <reg> <msk> <val>
+
+Set the bits of given PHY chip register for which the corresponding bit in
+<msk> is one to the value of the corresponding bit in <val>. All register
+bits that have a zero in <msk> are written back with there original value.
+
+^2 print
+usage: atmconfig [common-options] diag phy print [-numeric] <device>
+options:
+ -numeric print registers in hex
+
+Print the registers of the PHY chip in a human readable format.
+
+^2 stats
+usage: atmconfig [common-options] diag phy stats [-clear] <device>
+options:
+ -clear clear the statistics atomically after reading them
+
+Prints the PHY layer statistics of the PHY chip and optionally clears them.
+
+^1 vcc
+usage: atmconfig [common-options] diag vcc [-abr] [-channel] [-traffic]
+ [<device> ...]
+options:
+ -abr print ABR specific traffic parameters
+ -channel print VPI, VCI, AAL, traffic type and flags (default)
+ -traffic print traffic parameters
+
+Prints a list of all open vccs. The default output is -channel.
+
+^1 stats
+usage: atmconfig [common-options] diag stats <device>
+
+Prints the driver-internal statistics.
+
+^0 natm
+^^ natm simple IP over ATM management (see natmip(4))
+The group of CLIP commands is used to manage classical IP over ATM
+networking via NATM (see natm(4) and natmip(4)). A new PVC is added
+to a CLIP via:
+
+ atmconfig [common-options] natm add <dest> <device> <vpi> <vci>
+ <encaps> [<traffic> [<params> ...]]
+
+The PVC can be deleted with:
+
+ atmconfig [common-options] natm del <device> <vpi> <vci>
+
+The list of PVC that are currently active is retrieved with:
+
+ atmconfig [common-options] natm show
+
+^1 add
+usage: atmconfig [common-options] natm add [-printonly] <dest> <device>
+ <vpi> <vci> <encaps> [<traffic> [<params> ...]]
+options:
+ -printonly don't execute, print the route(8) command
+
+This subcommand adds a new CLIP PVC on the ATM interface <device>. The
+host on the other end of the PVC has IP address <addr>. <encaps> is one
+of llc/snap (LLC/SNAP encapsulated frames in AAL5) or aal5 (AAL5 frames
+without LLC/SNAP). <traffic> specifies the traffic type of the PVC
+and is one of UBR, CBR, VBR or ABR. If not given UBR is assumed. Depending
+on the traffic type none or more parameters can follow:
+
+ ubr [<pcr>]
+ cbr <pcr>
+ vbr <pcr> <scr> <mbs>
+ abr <pcr> <mcr> <icr> <tbe> <nrm> <trm> <adtf> <rif> <rdf> <cdf>
+
+^1 delete
+usage: atmconfig [common-options] natm delete [-printonly] <dest>
+ or: atmconfig [common-options] natm delete [-printonly] <device> <vpi> <vci>
+options:
+ -printonly don't execute, print the route(8) command
+
+This subcommand deletes and existing CLIP PVC that can bei either identified
+by the destination address or by the <device><vpi><vci> triple.
+
+^1 show
+usage: atmconfig [common-options] natm show [-abr] [-numeric]
+options:
+ -abr show ABR parameters for ABR connections
+ -numeric print IP addresses numerically
+
+This subcommand prints all ATM routes.
diff --git a/sbin/atm/atmconfig/atmconfig_device.c b/sbin/atm/atmconfig/atmconfig_device.c
new file mode 100644
index 0000000..f2f0838
--- /dev/null
+++ b/sbin/atm/atmconfig/atmconfig_device.c
@@ -0,0 +1,444 @@
+/*
+ * Copyright (c) 2001-2002
+ * Fraunhofer Institute for Open Communication Systems (FhG Fokus).
+ * All rights reserved.
+ * Copyright (c) 2003-2004
+ * Hartmut Brandt.
+ * All rights reserved.
+ *
+ * Author: Hartmut Brandt <harti@freebsd.org>
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include "atmconfig.h"
+#include "atmconfig_device.h"
+#include "private.h"
+#include "oid.h"
+
+#include <bsnmp/asn1.h>
+#include <bsnmp/snmp.h>
+#include <bsnmp/snmpclient.h>
+
+/*
+ * Description of the begemotAtmIfTable
+ */
+static const struct snmp_table atmif_table = {
+ OIDX_begemotAtmIfTable,
+ OIDX_begemotAtmIfTableLastChange, 2,
+ sizeof(struct atmif),
+ 1, 0x7ffULL,
+ {
+ { 0,
+ SNMP_SYNTAX_INTEGER, offsetof(struct atmif, index) },
+ { OID_begemotAtmIfName,
+ SNMP_SYNTAX_OCTETSTRING, offsetof(struct atmif, ifname) },
+ { OID_begemotAtmIfPcr,
+ SNMP_SYNTAX_GAUGE, offsetof(struct atmif, pcr) },
+ { OID_begemotAtmIfMedia,
+ SNMP_SYNTAX_INTEGER, offsetof(struct atmif, media) },
+ { OID_begemotAtmIfVpiBits,
+ SNMP_SYNTAX_GAUGE, offsetof(struct atmif, vpi_bits) },
+ { OID_begemotAtmIfVciBits,
+ SNMP_SYNTAX_GAUGE, offsetof(struct atmif, vci_bits) },
+ { OID_begemotAtmIfMaxVpcs,
+ SNMP_SYNTAX_GAUGE, offsetof(struct atmif, max_vpcs) },
+ { OID_begemotAtmIfMaxVccs,
+ SNMP_SYNTAX_GAUGE, offsetof(struct atmif, max_vccs) },
+ { OID_begemotAtmIfEsi,
+ SNMP_SYNTAX_OCTETSTRING, offsetof(struct atmif, esi) },
+ { OID_begemotAtmIfCarrierStatus,
+ SNMP_SYNTAX_INTEGER, offsetof(struct atmif, carrier) },
+ { OID_begemotAtmIfMode,
+ SNMP_SYNTAX_INTEGER, offsetof(struct atmif, mode) },
+ { 0, SNMP_SYNTAX_NULL, 0 }
+ }
+};
+
+/* List of all ATM interfaces */
+struct atmif_list atmif_list = TAILQ_HEAD_INITIALIZER(atmif_list);
+
+/*
+ * ATM hardware table
+ */
+struct atmhw {
+ TAILQ_ENTRY(atmhw) link;
+ uint64_t found;
+ int32_t index;
+ u_char *vendor;
+ size_t vendorlen;
+ u_char *device;
+ size_t devicelen;
+ uint32_t serial;
+ uint32_t version;
+ uint32_t soft_version;
+};
+TAILQ_HEAD(atmhw_list, atmhw);
+
+/* list of ATM hardware */
+static struct atmhw_list atmhw_list;
+
+/*
+ * Read ATM hardware table
+ */
+static const struct snmp_table atmhw_table = {
+ OIDX_begemotAtmHWTable,
+ OIDX_begemotAtmIfTableLastChange, 2,
+ sizeof(struct atmhw),
+ 1, 0x3fULL,
+ {
+ { 0,
+ SNMP_SYNTAX_INTEGER, offsetof(struct atmhw, index) },
+ { OID_begemotAtmHWVendor,
+ SNMP_SYNTAX_OCTETSTRING, offsetof(struct atmhw, vendor) },
+ { OID_begemotAtmHWDevice,
+ SNMP_SYNTAX_OCTETSTRING, offsetof(struct atmhw, device) },
+ { OID_begemotAtmHWSerial,
+ SNMP_SYNTAX_GAUGE, offsetof(struct atmhw, serial) },
+ { OID_begemotAtmHWVersion,
+ SNMP_SYNTAX_GAUGE, offsetof(struct atmhw, version) },
+ { OID_begemotAtmHWSoftVersion,
+ SNMP_SYNTAX_GAUGE, offsetof(struct atmhw, soft_version) },
+ { 0, SNMP_SYNTAX_NULL, 0 }
+ }
+};
+
+static void device_status(int, char *[]);
+static void device_hardware(int, char *[]);
+static void device_modify(int, char *[]);
+
+static const struct cmdtab device_tab[] = {
+ { "hardware", NULL, device_hardware },
+ { "status", NULL, device_status },
+ { "modify", NULL, device_modify },
+ { NULL, NULL, NULL }
+};
+
+static const struct cmdtab entry =
+ { "device", device_tab, NULL };
+
+static DEF_MODULE(&entry);
+
+/*
+ * Carrier state to string
+ */
+static const struct penum strcarrier[] = {
+ { 1, "on" },
+ { 2, "off" },
+ { 3, "unknown" },
+ { 4, "none" },
+ { 0, NULL }
+};
+/*
+ * SUNI mode to string
+ */
+static const struct penum strsunimode[] = {
+ { 1, "sonet" },
+ { 2, "sdh" },
+ { 3, "unknown" },
+ { 0, NULL }
+};
+
+/*
+ * OIDs
+ */
+static const struct asn_oid
+ oid_begemotAtmIfMode = OIDX_begemotAtmIfMode;
+
+/*
+ * Print 1st status line
+ */
+static void
+dev_status1(const struct atmif *aif)
+{
+ char buf[100];
+
+ printf("%-5u %-8s %-6u %-4u %-5u %-4u %-5u "
+ "%02x:%02x:%02x:%02x:%02x:%02x %s\n", aif->index,
+ aif->ifname, aif->pcr,
+ (1 << aif->vpi_bits) - 1, (1 << aif->vci_bits) - 1,
+ aif->max_vpcs, aif->max_vccs, aif->esi[0],
+ aif->esi[1], aif->esi[2], aif->esi[3], aif->esi[4], aif->esi[5],
+ penum(aif->carrier, strcarrier, buf));
+}
+
+/*
+ * Print 2nd status line
+ */
+static void
+dev_status2(const struct atmif *aif)
+{
+ char buf[100];
+
+ printf("%-5u %-8s %s\n", aif->index, aif->ifname,
+ penum(aif->mode, strsunimode, buf));
+}
+
+/*
+ * Implement the 'device status' command
+ */
+static void
+device_status(int argc, char *argv[])
+{
+ int opt, i;
+ struct atmif *aif;
+ static const struct option opts[] = {
+ { NULL, 0, NULL }
+ };
+
+ const char dev1[] =
+ "Interface Max Max\n"
+ "Index Name PCR VPI VCI VPCs VCCs ESI Carrier\n";
+ const char dev2[] =
+ "Interface\n"
+ "Index Name Mode\n";
+
+ while ((opt = parse_options(&argc, &argv, opts)) != -1)
+ switch (opt) {
+ }
+
+ snmp_open(NULL, NULL, NULL, NULL);
+ atexit(snmp_close);
+
+ atmif_fetchtable();
+
+ if (TAILQ_EMPTY(&atmif_list))
+ errx(1, "no ATM interfaces found");
+
+ if (argc > 0) {
+ heading_init();
+ for (i = 0; i < argc; i++) {
+ if ((aif = atmif_find_name(argv[i])) == NULL) {
+ warnx("%s: no such ATM interface", argv[i]);
+ continue;
+ }
+ heading(dev1);
+ dev_status1(aif);
+ }
+ heading_init();
+ for (i = 0; i < argc; i++) {
+ if ((aif = atmif_find_name(argv[i])) == NULL)
+ continue;
+ heading(dev2);
+ dev_status2(aif);
+ }
+ } else {
+ heading_init();
+ TAILQ_FOREACH(aif, &atmif_list, link) {
+ heading(dev1);
+ dev_status1(aif);
+ }
+ heading_init();
+ TAILQ_FOREACH(aif, &atmif_list, link) {
+ heading(dev2);
+ dev_status2(aif);
+ }
+ }
+}
+
+/*
+ * Print hardware info line
+ */
+static void
+dev_hardware(const struct atmif *aif)
+{
+ const struct atmhw *hw;
+
+ TAILQ_FOREACH(hw, &atmhw_list, link)
+ if (aif->index == hw->index)
+ break;
+ if (hw == NULL) {
+ warnx("hardware info not found for '%s'", aif->ifname);
+ return;
+ }
+
+ printf("%-5u %-8s %-16s%-10s %-10u %-10u %u\n", aif->index,
+ aif->ifname, hw->vendor, hw->device, hw->serial,
+ hw->version, hw->soft_version);
+}
+
+/*
+ * Show hardware configuration
+ */
+static void
+device_hardware(int argc, char *argv[])
+{
+ int opt, i;
+ struct atmif *aif;
+
+ static const struct option opts[] = {
+ { NULL, 0, NULL }
+ };
+
+ static const char headline[] =
+ "Interface \n"
+ "Index Name Vendor Card Serial HW SW\n";
+
+ while ((opt = parse_options(&argc, &argv, opts)) != -1)
+ switch (opt) {
+ }
+
+ snmp_open(NULL, NULL, NULL, NULL);
+ atexit(snmp_close);
+
+ atmif_fetchtable();
+
+ if (snmp_table_fetch(&atmhw_table, &atmhw_list) != 0)
+ errx(1, "AtmHW table: %s", snmp_client.error);
+
+ if (argc > 0) {
+ heading_init();
+ for (i = 0; i < argc; i++) {
+ if ((aif = atmif_find_name(argv[i])) == NULL) {
+ warnx("interface not found '%s'", argv[i]);
+ continue;
+ }
+ heading(headline);
+ dev_hardware(aif);
+ }
+ } else {
+ heading_init();
+ TAILQ_FOREACH(aif, &atmif_list, link) {
+ heading(headline);
+ dev_hardware(aif);
+ }
+ }
+}
+
+/*
+ * Change device parameters
+ */
+static void
+device_modify(int argc, char *argv[])
+{
+ int opt;
+ struct atmif *aif;
+ int mode = 0;
+ int n;
+ struct snmp_pdu pdu, resp;
+
+ static const struct option opts[] = {
+#define MODIFY_MODE 0
+ { "mode", OPT_STRING, NULL },
+ { NULL, 0, NULL }
+ };
+
+ while ((opt = parse_options(&argc, &argv, opts)) != -1)
+ switch (opt) {
+
+ case MODIFY_MODE:
+ if (pparse(&mode, strsunimode, optarg) == -1 ||
+ mode == 3)
+ errx(1, "illegal mode for -m '%s'", optarg);
+ break;
+ }
+
+ if (argc != 1)
+ errx(1, "device modify needs one argument");
+
+ snmp_open(NULL, NULL, NULL, NULL);
+
+ atexit(snmp_close);
+ atmif_fetchtable();
+
+ if ((aif = atmif_find_name(argv[0])) == NULL)
+ errx(1, "%s: no such ATM interface", argv[0]);
+
+ snmp_pdu_create(&pdu, SNMP_PDU_SET);
+ if (mode != 0) {
+ n = snmp_add_binding(&pdu,
+ &oid_begemotAtmIfMode, SNMP_SYNTAX_INTEGER,
+ NULL);
+ snmp_oid_append(&pdu.bindings[n + 0].var, "i",
+ (asn_subid_t)aif->index);
+ pdu.bindings[n + 0].v.integer = mode;
+ }
+
+ if (pdu.nbindings == 0)
+ errx(1, "must specify something to modify");
+
+ if (snmp_dialog(&pdu, &resp))
+ errx(1, "No response from '%s': %s", snmp_client.chost,
+ snmp_client.error);
+
+ if (snmp_pdu_check(&pdu, &resp) <= 0)
+ errx(1, "Error modifying device");
+
+ snmp_pdu_free(&resp);
+ snmp_pdu_free(&pdu);
+}
+
+/* XXX while this is compiled in */
+void
+device_register(void)
+{
+ register_module(&amodule_1);
+}
+
+/*
+ * Fetch the ATM interface table
+ */
+void
+atmif_fetchtable(void)
+{
+ struct atmif *aif;
+
+ while ((aif = TAILQ_FIRST(&atmif_list)) != NULL) {
+ free(aif->ifname);
+ free(aif->esi);
+ TAILQ_REMOVE(&atmif_list, aif, link);
+ free(aif);
+ }
+
+ if (snmp_table_fetch(&atmif_table, &atmif_list) != 0)
+ errx(1, "AtmIf table: %s", snmp_client.error);
+}
+
+/*
+ * Find a named ATM interface
+ */
+struct atmif *
+atmif_find_name(const char *ifname)
+{
+ struct atmif *atmif;
+
+ TAILQ_FOREACH(atmif, &atmif_list, link)
+ if (strcmp(atmif->ifname, ifname) == 0)
+ return (atmif);
+ return (NULL);
+}
+/*
+ * find an ATM interface by index
+ */
+struct atmif *
+atmif_find(u_int idx)
+{
+ struct atmif *atmif;
+
+ TAILQ_FOREACH(atmif, &atmif_list, link)
+ if (atmif->index == (int32_t)idx)
+ return (atmif);
+ return (NULL);
+}
diff --git a/sbin/atm/atmconfig/atmconfig_device.h b/sbin/atm/atmconfig/atmconfig_device.h
new file mode 100644
index 0000000..1c099fc
--- /dev/null
+++ b/sbin/atm/atmconfig/atmconfig_device.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2001-2002
+ * Fraunhofer Institute for Open Communication Systems (FhG Fokus).
+ * All rights reserved.
+ * Copyright (c) 2003-2004
+ * Hartmut Brandt.
+ * All rights reserved.
+ *
+ * Author: Hartmut Brandt <harti@freebsd.org>
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+#ifndef ATMCONFIG_DEVICE_H_
+#define ATMCONFIG_DEVICE_H_
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <stdint.h>
+
+/*
+ * ATM interface table
+ */
+struct atmif {
+ TAILQ_ENTRY(atmif) link;
+ uint64_t found;
+ int32_t index;
+ char *ifname;
+ size_t ifnamelen;
+ uint32_t pcr;
+ int32_t media;
+ uint32_t vpi_bits;
+ uint32_t vci_bits;
+ uint32_t max_vpcs;
+ uint32_t max_vccs;
+ u_char *esi;
+ size_t esilen;
+ int32_t carrier;
+ int32_t mode;
+};
+TAILQ_HEAD(atmif_list, atmif);
+
+/* list of all ATM interfaces */
+extern struct atmif_list atmif_list;
+
+/* fetch this table */
+void atmif_fetchtable(void);
+
+/* find an ATM interface by name */
+struct atmif *atmif_find_name(const char *);
+
+/* find an ATM interface by index */
+struct atmif *atmif_find(u_int);
+
+#endif
diff --git a/sbin/atm/atmconfig/atmconfig_device.help b/sbin/atm/atmconfig/atmconfig_device.help
new file mode 100644
index 0000000..27237c8
--- /dev/null
+++ b/sbin/atm/atmconfig/atmconfig_device.help
@@ -0,0 +1,62 @@
+# Copyright (c) 2003-2004
+# Hartmut Brandt.
+# All rights reserved.
+#
+# Author: Hartmut Brandt <harti@freebsd.org>
+#
+# 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.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# Help file for the atmconfig utility
+#
+# $FreeBSD$
+#
+^0 device
+^^ device show information about ATM hardware interfaces
+This command shows information about the ATM hardware interfaces on the
+system. Status information can be obtained by:
+
+ atmconfig [common-options] device status [options] [device ...]
+
+Information about the hardware of the ATM interfaces is reported by:
+
+ atmconfig [common-options] device hardware [options] [device ...]
+
+The parameters of the a device can be changed by:
+
+ atmconfig [common-options] device modify [options] <device>
+
+^1 status
+usage: atmconfig [common-options] device status [device ...]
+
+If no device is given as argument information about all devices is shown.
+
+^1 hardware
+usage: atmconfig [common-options] device hardware [device ...]
+
+If now device is given as argument information about all devices is shown.
+
+^1 modify
+usage: atmconfig [common-options] device modify [-mode mode] <device>
+
+options:
+ -mode switch the SUNI mode to either 'sonet' or 'sdh'.
+
diff --git a/sbin/atm/atmconfig/diag.c b/sbin/atm/atmconfig/diag.c
new file mode 100644
index 0000000..3225e10
--- /dev/null
+++ b/sbin/atm/atmconfig/diag.c
@@ -0,0 +1,1122 @@
+/*
+ * Copyright (c) 2001-2003
+ * Fraunhofer Institute for Open Communication Systems (FhG Fokus).
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * Author: Hartmut Brandt <harti@freebsd.org>
+ */
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/sysctl.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/queue.h>
+#include <net/if.h>
+#include <net/if_mib.h>
+#include <net/if_types.h>
+#include <net/if_atm.h>
+#include <net/if_media.h>
+#include <netnatm/natm.h>
+#include <dev/utopia/utopia.h>
+#include <dev/utopia/suni.h>
+#include <dev/utopia/idtphy.h>
+
+#include "atmconfig.h"
+#include "private.h"
+#include "diag.h"
+
+static void diag_list(int, char *[]);
+static void diag_config(int, char *[]);
+static void diag_vcc(int, char *[]);
+static void diag_phy_show(int, char *[]);
+static void diag_phy_set(int, char *[]);
+static void diag_phy_print(int, char *[]);
+static void diag_phy_stats(int, char *[]);
+static void diag_stats(int, char *[]);
+
+static const struct cmdtab diag_phy_tab[] = {
+ { "show", NULL, diag_phy_show },
+ { "set", NULL, diag_phy_set },
+ { "stats", NULL, diag_phy_stats },
+ { "print", NULL, diag_phy_print },
+ { NULL, NULL, NULL },
+};
+
+const struct cmdtab diag_tab[] = {
+ { "list", NULL, diag_list },
+ { "config", NULL, diag_config },
+ { "phy", diag_phy_tab, NULL },
+ { "stats", NULL, diag_stats },
+ { "vcc", NULL, diag_vcc },
+ { NULL, NULL, NULL }
+};
+
+static const struct utopia_print suni_lite[] = { SUNI_PRINT_LITE };
+static const struct utopia_print suni_ultra[] = { SUNI_PRINT_ULTRA };
+static const struct utopia_print suni_622[] = { SUNI_PRINT_622 };
+static const struct utopia_print idt77105[] = { IDTPHY_PRINT_77105 };
+static const struct utopia_print idt77155[] = { IDTPHY_PRINT_77155 };
+
+static const struct {
+ const struct utopia_print *tab;
+ u_int len;
+ u_int type;
+} phy_print[] = {
+ { suni_lite, sizeof(suni_lite) / sizeof(suni_lite[0]),
+ UTP_TYPE_SUNI_LITE },
+ { suni_ultra, sizeof(suni_ultra) / sizeof(suni_ultra[0]),
+ UTP_TYPE_SUNI_ULTRA },
+ { suni_622, sizeof(suni_622) / sizeof(suni_622[0]),
+ UTP_TYPE_SUNI_622 },
+ { idt77105, sizeof(idt77105) / sizeof(idt77105[0]),
+ UTP_TYPE_IDT77105 },
+ { idt77155, sizeof(idt77155) / sizeof(idt77155[0]),
+ UTP_TYPE_IDT77155 },
+};
+
+static const u_int utopia_addreg[] = { UTP_REG_ADD };
+
+/*
+ * Driver statistics printing
+ */
+static const char *const print_stats_pca200e[] = {
+ "cmd_queue_full:",
+ "get_stat_errors:",
+ "clr_stat_errors:",
+ "get_prom_errors:",
+ "suni_reg_errors:",
+ "tx_queue_full:",
+ "tx_queue_almost_full:",
+ "tx_pdu2big:",
+ "tx_too_many_segs:",
+ "tx_retry:",
+ "fix_empty:",
+ "fix_addr_copy:",
+ "fix_addr_noext:",
+ "fix_addr_ext:",
+ "fix_len_noext:",
+ "fix_len_copy:",
+ "fix_len:",
+ "rx_badvc:",
+ "rx_closed:",
+ NULL
+};
+static const char *const print_stats_he[] = {
+ "tdprq_full:",
+ "hbuf_error:",
+ "crc_error:",
+ "len_error:",
+ "flow_closed:",
+ "flow_drop:",
+ "tpd_no_mem:",
+ "rx_seg:",
+ "empty_hbuf:",
+ "short_aal5:",
+ "badlen_aal5:",
+ "bug_bad_isw:",
+ "bug_no_irq_upd:",
+ "itype_tbrq:",
+ "itype_tpd:",
+ "itype_rbps:",
+ "itype_rbpl:",
+ "itype_rbrq:",
+ "itype_rbrqt:",
+ "itype_unknown:",
+ "itype_phys:",
+ "itype_err:",
+ "defrag:",
+ "mcc:",
+ "oec:",
+ "dcc:",
+ "cec:",
+ "no_rcv_mbuf:",
+ NULL
+};
+static const char *const print_stats_eni[] = {
+ "ttrash:",
+ "mfixaddr:",
+ "mfixlen:",
+ "mfixfail:",
+ "txmbovr:",
+ "dmaovr:",
+ "txoutspace:",
+ "txdtqout:",
+ "launch:",
+ "hwpull:",
+ "swadd:",
+ "rxqnotus:",
+ "rxqus:",
+ "rxdrqout:",
+ "rxmbufout:",
+ "txnomap:",
+ "vtrash:",
+ "otrash:",
+ NULL
+};
+
+static const char *const print_stats_idt77211[] = {
+ "need_copy:",
+ "copy_failed:",
+ "out_of_tbds:",
+ "no_txmaps:",
+ "tx_load_err:",
+ "tx_qfull:",
+ NULL
+};
+static const char *const print_stats_idt77252[] = {
+ "raw_cells:",
+ "raw_no_vcc:",
+ "raw_no_buf:",
+ "tx_qfull:",
+ "tx_out_of_tbds:",
+ "tx_out_of_maps:",
+ "tx_load_err:",
+ NULL
+};
+static const char *const print_stats_virtual[] = {
+ "dummy:",
+ NULL
+};
+static const char *const *const print_stats[] = {
+ [ATM_DEVICE_UNKNOWN] = NULL,
+ [ATM_DEVICE_PCA200E] = print_stats_pca200e,
+ [ATM_DEVICE_HE155] = print_stats_he,
+ [ATM_DEVICE_HE622] = print_stats_he,
+ [ATM_DEVICE_ENI155P] = print_stats_eni,
+ [ATM_DEVICE_ADP155P] = print_stats_eni,
+ [ATM_DEVICE_FORELE25] = print_stats_idt77211,
+ [ATM_DEVICE_FORELE155] = print_stats_idt77211,
+ [ATM_DEVICE_NICSTAR25] = print_stats_idt77211,
+ [ATM_DEVICE_NICSTAR155] = print_stats_idt77211,
+ [ATM_DEVICE_IDTABR25] = print_stats_idt77252,
+ [ATM_DEVICE_IDTABR155] = print_stats_idt77252,
+ [ATM_DEVICE_PROATM25] = print_stats_idt77252,
+ [ATM_DEVICE_PROATM155] = print_stats_idt77252,
+ [ATM_DEVICE_VIRTUAL] = print_stats_virtual,
+};
+
+struct diagif_list diagif_list = TAILQ_HEAD_INITIALIZER(diagif_list);
+
+/*
+ * Fetch a phy sysctl
+ */
+static int
+phy_fetch(const char *ifname, const char *var, void *val, size_t len,
+ int err_fatal)
+{
+ char *str;
+
+ if (asprintf(&str, "hw.atm.%s.phy_%s", ifname, var) == -1)
+ err(1, NULL);
+ if (sysctlbyname(str, val, &len, NULL, 0) == -1) {
+ if (err_fatal || errno != ENOENT)
+ err(1, "%s", str);
+ free(str);
+ return (-1);
+ }
+ free(str);
+ return (0);
+}
+
+/*
+ * Fetch the list of all ATM network interfaces and their MIBs.
+ */
+void
+diagif_fetch(void)
+{
+ size_t len;
+ int count;
+ int name[6];
+ struct ifmibdata mib;
+ struct ifatm_mib atm;
+ int idx;
+ struct diagif *d;
+
+ while ((d = TAILQ_FIRST(&diagif_list)) != NULL) {
+ if (d->vtab != NULL)
+ free(d->vtab);
+ TAILQ_REMOVE(&diagif_list, d, link);
+ free(d);
+ }
+
+ len = sizeof(count);
+ if (sysctlbyname("net.link.generic.system.ifcount", &count, &len,
+ NULL, 0) == -1)
+ err(1, "ifcount");
+
+ name[0] = CTL_NET;
+ name[1] = PF_LINK;
+ name[2] = NETLINK_GENERIC;
+ name[3] = IFMIB_IFDATA;
+
+ for (idx = 1; idx <= count; idx++) {
+ name[4] = idx;
+ name[5] = IFDATA_GENERAL;
+ len = sizeof(mib);
+ if (sysctl(name, 6, &mib, &len, NULL, 0) == -1)
+ err(1, "interface %d: general mib", idx);
+ if (mib.ifmd_data.ifi_type == IFT_ATM) {
+ name[5] = IFDATA_LINKSPECIFIC;
+ len = sizeof(atm);
+ if (sysctl(name, 6, &atm, &len, NULL, 0) == -1)
+ err(1, "interface %d: ATM mib", idx);
+
+ d = malloc(sizeof(*d));
+ if (d == NULL)
+ err(1, NULL);
+ bzero(d, sizeof(*d));
+ d->mib = atm;
+ d->index = idx;
+ strcpy(d->ifname, mib.ifmd_name);
+ TAILQ_INSERT_TAIL(&diagif_list, d, link);
+
+ if (phy_fetch(d->ifname, "type", &d->phy_type,
+ sizeof(d->phy_type), 0) == 0) {
+ d->phy_present = 1;
+ phy_fetch(d->ifname, "loopback",
+ &d->phy_loopback,
+ sizeof(d->phy_loopback), 1);
+ phy_fetch(d->ifname, "name", &d->phy_name,
+ sizeof(d->phy_name), 1);
+ phy_fetch(d->ifname, "state", &d->phy_state,
+ sizeof(d->phy_state), 1);
+ phy_fetch(d->ifname, "carrier", &d->phy_carrier,
+ sizeof(d->phy_carrier), 1);
+ }
+ }
+ }
+}
+
+/*
+ * "<radix><bit>STRING\011<mask><pattern>STRING\012<mask><radix>STRING"
+ */
+static char *
+printb8(uint32_t val, const char *descr)
+{
+ static char buffer[1000];
+ char *ptr;
+ int tmp = 0;
+ u_char mask, pattern;
+
+ if (*descr++ == '\010')
+ sprintf(buffer, "%#o", val);
+ else
+ sprintf(buffer, "%#x", val);
+ ptr = buffer + strlen(buffer);
+
+ *ptr++ = '<';
+ while (*descr) {
+ if (*descr == '\11') {
+ descr++;
+ mask = *descr++;
+ pattern = *descr++;
+ if ((val & mask) == pattern) {
+ if (tmp++)
+ *ptr++ = ',';
+ while (*descr >= ' ')
+ *ptr++ = *descr++;
+ } else {
+ while (*descr >= ' ')
+ descr++;
+ }
+ } else if (*descr == '\12') {
+ descr++;
+ mask = *descr++;
+ pattern = *descr++;
+ if (tmp++)
+ *ptr++ = ',';
+ while (*descr >= ' ')
+ *ptr++ = *descr++;
+ *ptr++ = '=';
+ if (pattern == 8)
+ sprintf(ptr, "%#o",
+ (val & mask) >> (ffs(mask)-1));
+ else if (pattern == 10)
+ sprintf(ptr, "%u",
+ (val & mask) >> (ffs(mask)-1));
+ else
+ sprintf(ptr, "%#x",
+ (val & mask) >> (ffs(mask)-1));
+ ptr += strlen(ptr);
+ } else {
+ if (val & (1 << (*descr++ - 1))) {
+ if (tmp++)
+ *ptr++ = ',';
+ while (*descr >= ' ')
+ *ptr++ = *descr++;
+ } else {
+ while (*descr >= ' ')
+ descr++;
+ }
+ }
+ }
+ *ptr++ = '>';
+ *ptr++ = '\0';
+
+ return (buffer);
+}
+
+/*
+ * "<radix><bit>STRING<bit>STRING"
+ */
+static char *
+printb(uint32_t val, const char *descr)
+{
+ static char buffer[1000];
+ char *ptr;
+ int tmp = 0;
+
+ if (*descr++ == '\010')
+ sprintf(buffer, "%#o", val);
+ else
+ sprintf(buffer, "%#x", val);
+ ptr = buffer + strlen(buffer);
+
+ *ptr++ = '<';
+ while (*descr) {
+ if (val & (1 << (*descr++ - 1))) {
+ if (tmp++)
+ *ptr++ = ',';
+ while (*descr > ' ')
+ *ptr++ = *descr++;
+ } else {
+ while (*descr > ' ')
+ descr++;
+ }
+ }
+ *ptr++ = '>';
+ *ptr++ = '\0';
+
+ return (buffer);
+}
+
+
+static void
+diag_loop(int argc, char *argv[], const char *text,
+ void (*func)(const struct diagif *))
+{
+ int i;
+ struct diagif *aif;
+
+ heading_init();
+ if (argc > 0) {
+ for (i = 0; i < argc; i++) {
+ TAILQ_FOREACH(aif, &diagif_list, link) {
+ if (strcmp(argv[i], aif->ifname) == 0) {
+ heading("%s", text);
+ (*func)(aif);
+ break;
+ }
+ }
+ if (aif == NULL)
+ warnx("%s: no such ATM interface", argv[i]);
+ }
+ } else {
+ TAILQ_FOREACH(aif, &diagif_list, link) {
+ heading("%s", text);
+ (*func)(aif);
+ }
+ }
+}
+
+/*
+ * Print the config line for the given interface
+ */
+static void
+config_line1(const struct diagif *aif)
+{
+ printf("%-6u%-9s%-8u%-5u%-6u%-5u%-6u%02x:%02x:%02x:%02x:%02x:%02x\n",
+ aif->index, aif->ifname, aif->mib.pcr, (1 << aif->mib.vpi_bits) - 1,
+ (1 << aif->mib.vci_bits) - 1, aif->mib.max_vpcs, aif->mib.max_vccs,
+ aif->mib.esi[0], aif->mib.esi[1], aif->mib.esi[2],
+ aif->mib.esi[3], aif->mib.esi[4], aif->mib.esi[5]);
+}
+
+static void
+config_line2(const struct diagif *aif)
+{
+ u_int d, i;
+
+ static const struct {
+ const char *dev;
+ const char *vendor;
+ } devs[] = {
+ ATM_DEVICE_NAMES
+ };
+ static const struct {
+ u_int media;
+ const char *const name;
+ } medias[] = IFM_SUBTYPE_ATM_DESCRIPTIONS;
+
+ for (i = 0; medias[i].name; i++)
+ if (aif->mib.media == medias[i].media)
+ break;
+
+ if ((d = aif->mib.device) >= sizeof(devs) / sizeof(devs[0]))
+ d = 0;
+
+ printf("%-6u%-9s%-12.11s%-13.12s%-8u%-6x%-6x %s\n", aif->index,
+ aif->ifname, devs[d].vendor, devs[d].dev, aif->mib.serial,
+ aif->mib.hw_version, aif->mib.sw_version,
+ medias[i].name ? medias[i].name : "unknown");
+}
+
+static void
+diag_config(int argc, char *argv[])
+{
+ int opt;
+
+ static int hardware;
+ static int atm;
+
+ static const struct option opts[] = {
+ { "hardware", OPT_SIMPLE, &hardware },
+ { "atm", OPT_SIMPLE, &atm },
+ { NULL, 0, NULL }
+ };
+
+ static const char config_text1[] =
+ "Interface Max Max\n"
+ "Index Name PCR VPI VCI VPCs VCCs ESI\n";
+ static const char config_text2[] =
+ "Interface Version\n"
+ "Index Name Vendor Card "
+ "Serial HW SW Media\n";
+
+ while ((opt = parse_options(&argc, &argv, opts)) != -1)
+ switch (opt) {
+ }
+
+ diagif_fetch();
+ if (TAILQ_EMPTY(&diagif_list))
+ errx(1, "no ATM interfaces found");
+
+ if (!atm && !hardware)
+ atm = 1;
+
+ if (atm)
+ diag_loop(argc, argv, config_text1, config_line1);
+ if (hardware)
+ diag_loop(argc, argv, config_text2, config_line2);
+
+}
+
+static void
+diag_list(int argc, char *argv[])
+{
+ int opt;
+ struct diagif *aif;
+
+ static const struct option opts[] = {
+ { NULL, 0, NULL }
+ };
+
+ while ((opt = parse_options(&argc, &argv, opts)) != -1)
+ switch (opt) {
+ }
+
+ if (argc > 0)
+ errx(1, "no arguments required for 'diag list'");
+
+ diagif_fetch();
+ if (TAILQ_EMPTY(&diagif_list))
+ errx(1, "no ATM interfaces found");
+
+ TAILQ_FOREACH(aif, &diagif_list, link)
+ printf("%s ", aif->ifname);
+ printf("\n");
+}
+
+/*
+ * Print the config line for the given interface
+ */
+static void
+phy_show_line(const struct diagif *aif)
+{
+ printf("%-6u%-9s", aif->index, aif->ifname);
+ if (aif->phy_present)
+ printf("%-5u%-25s0x%-9x", aif->phy_type,
+ aif->phy_name, aif->phy_loopback);
+ printf("\n");
+}
+
+static void
+diag_phy_show(int argc, char *argv[])
+{
+ int opt;
+
+ static const struct option opts[] = {
+ { NULL, 0, NULL }
+ };
+
+ static const char phy_show_text[] =
+ "Interface Phy\n"
+ "Index Name Type Name Loopback State\n";
+
+ while ((opt = parse_options(&argc, &argv, opts)) != -1)
+ switch (opt) {
+ }
+
+ diagif_fetch();
+ if (TAILQ_EMPTY(&diagif_list))
+ errx(1, "no ATM interfaces found");
+
+ diag_loop(argc, argv, phy_show_text, phy_show_line);
+}
+
+/*
+ * Make sure the interface exists and has a phy
+ */
+static struct diagif *
+diagif_get_phy(const char *arg)
+{
+ struct diagif *aif;
+
+ diagif_fetch();
+ TAILQ_FOREACH(aif, &diagif_list, link)
+ if (strcmp(aif->ifname, arg) == 0)
+ break;
+ if (aif == NULL)
+ errx(1, "no such interface: %s", arg);
+ if (!aif->phy_present)
+ errx(1, "interface %s has no phy", arg);
+
+ return (aif);
+}
+
+static void
+diag_phy_set(int argc, char *argv[])
+{
+ int opt;
+ uint8_t reg[3];
+ u_long res;
+ char *end;
+ char *str;
+
+ static const struct option opts[] = {
+ { NULL, 0, NULL }
+ };
+
+ while ((opt = parse_options(&argc, &argv, opts)) != -1)
+ switch (opt) {
+ }
+
+ if (argc != 4)
+ errx(1, "missing arguments for 'diag phy set'");
+
+ errno = 0;
+ res = strtoul(argv[1], &end, 0);
+ if (errno != 0)
+ err(1, "register number");
+ if (*end != '\0')
+ errx(1, "malformed register number '%s'", argv[1]);
+ if (res > 0xff)
+ errx(1, "register number too large");
+ reg[0] = res;
+
+ errno = 0;
+ res = strtoul(argv[2], &end, 0);
+ if (errno != 0)
+ err(1, "mask");
+ if (*end != '\0')
+ errx(1, "malformed mask '%s'", argv[1]);
+ if (res > 0xff)
+ errx(1, "mask too large");
+ reg[1] = res;
+
+ errno = 0;
+ res = strtoul(argv[3], &end, 0);
+ if (errno != 0)
+ err(1, "value");
+ if (*end != '\0')
+ errx(1, "malformed value '%s'", argv[1]);
+ if (res > 0xff)
+ errx(1, "value too large");
+ reg[2] = res;
+
+ (void)diagif_get_phy(argv[0]);
+
+ if (asprintf(&str, "hw.atm.%s.phy_regs", argv[0]) == -1)
+ err(1, NULL);
+
+ if (sysctlbyname(str, NULL, NULL, reg, 3 * sizeof(uint8_t)))
+ err(1, "%s", str);
+
+ free(str);
+}
+
+static void
+diag_phy_print(int argc, char *argv[])
+{
+ int opt;
+ char *str;
+ size_t len, len1;
+ uint8_t *regs;
+ u_int type, i;
+ const struct utopia_print *p;
+
+ static int numeric;
+
+ static const struct option opts[] = {
+ { "numeric", OPT_SIMPLE, &numeric },
+ { NULL, 0, NULL }
+ };
+
+ while ((opt = parse_options(&argc, &argv, opts)) != -1)
+ switch (opt) {
+ }
+
+ if (argc != 1)
+ errx(1, "need device name for 'diag phy print'");
+
+ (void)diagif_get_phy(argv[0]);
+
+ if (asprintf(&str, "hw.atm.%s.phy_regs", argv[0]) == -1)
+ err(1, NULL);
+ len = 0;
+ if (sysctlbyname(str, NULL, &len, NULL, 0))
+ err(1, "'%s' not found", str);
+
+ regs = malloc(len);
+ if (regs == NULL)
+ err(1, NULL);
+
+ if (sysctlbyname(str, regs, &len, NULL, 0))
+ err(1, "'%s' not found", str);
+ free(str);
+
+ if (numeric) {
+ for (i = 0; i < len; i++) {
+ if (i % 16 == 0)
+ printf("%02x: ", i);
+ if (i % 16 == 8)
+ printf(" ");
+ printf(" %02x", regs[i]);
+ if (i % 16 == 15)
+ printf("\n");
+ }
+ if (i % 16 != 0)
+ printf("\n");
+ } else {
+ if (asprintf(&str, "hw.atm.%s.phy_type", argv[0]) == -1)
+ err(1, NULL);
+ len1 = sizeof(type);
+ if (sysctlbyname(str, &type, &len1, NULL, 0))
+ err(1, "'%s' not found", str);
+ free(str);
+
+ for (i = 0; i < sizeof(phy_print) / sizeof(phy_print[0]); i++)
+ if (type == phy_print[i].type)
+ break;
+ if (i == sizeof(phy_print) / sizeof(phy_print[0]))
+ errx(1, "unknown PHY chip type %u\n", type);
+
+ for (p = phy_print[i].tab;
+ p < phy_print[i].tab + phy_print[i].len;
+ p++) {
+ if (p->reg + utopia_addreg[p->type] > len)
+ /* don't have this register */
+ continue;
+
+ printf("%s:%*s", p->name, 40 - (int)strlen(p->name),"");
+
+ switch (p->type) {
+
+ case UTP_REGT_BITS:
+ printf("%s\n", printb8(regs[p->reg], p->fmt));
+ break;
+
+ case UTP_REGT_INT8:
+ printf("%#x\n", regs[p->reg]);
+ break;
+
+ case UTP_REGT_INT10BITS:
+ printf("%#x %s\n", regs[p->reg] |
+ ((regs[p->reg + 1] & 0x3) << 8),
+ printb8(regs[p->reg + 1], p->fmt));
+ break;
+
+ case UTP_REGT_INT12:
+ printf("%#x\n", regs[p->reg] |
+ ((regs[p->reg + 1] & 0xf) << 8));
+ break;
+
+ case UTP_REGT_INT16:
+ printf("%#x\n", regs[p->reg] |
+ (regs[p->reg + 1] << 8));
+ break;
+
+ case UTP_REGT_INT19:
+ printf("%#x\n", regs[p->reg] |
+ (regs[p->reg + 1] << 8) |
+ ((regs[p->reg + 2] & 0x7) << 16));
+ break;
+
+ case UTP_REGT_INT20:
+ printf("%#x\n", regs[p->reg] |
+ (regs[p->reg + 1] << 8) |
+ ((regs[p->reg + 2] & 0xf) << 16));
+ break;
+
+ case UTP_REGT_INT21:
+ printf("%#x\n", regs[p->reg] |
+ (regs[p->reg + 1] << 8) |
+ ((regs[p->reg + 2] & 0x1f) << 16));
+ break;
+
+ default:
+ abort();
+ }
+ }
+ }
+ free(regs);
+}
+
+static void
+diag_phy_stats(int argc, char *argv[])
+{
+ int opt;
+ size_t len;
+ char *str;
+ struct utopia_stats1 stats1;
+ u_int foo;
+
+ static int clear;
+
+ static const struct option opts[] = {
+ { "clear", OPT_SIMPLE, &clear },
+ { NULL, 0, NULL }
+ };
+
+ while ((opt = parse_options(&argc, &argv, opts)) != -1)
+ switch (opt) {
+ }
+
+ if (argc != 1)
+ errx(1, "need device name for 'diag phy stats'");
+
+ (void)diagif_get_phy(argv[0]);
+
+ if (asprintf(&str, "hw.atm.%s.phy_stats", argv[0]) == -1)
+ err(1, NULL);
+
+ len = sizeof(stats1);
+ if (sysctlbyname(str, &stats1, &len,
+ clear ? &foo : NULL, clear ? sizeof(foo) : 0))
+ err(1, "'%s' not found", str);
+ if (len < sizeof(stats1.version))
+ errx(1, "phy statistics too short %zu", len);
+
+ switch (stats1.version) {
+
+ case 1:
+ if (len != sizeof(stats1))
+ errx(1, "bad phy stats length %zu (expecting %zu)",
+ len, sizeof(stats1));
+ break;
+
+ default:
+ errx(1, "unknown phy stats version %u", stats1.version);
+ }
+
+ free(str);
+
+ printf("rx_sbip: %llu\n", (unsigned long long)stats1.rx_sbip);
+ printf("rx_lbip: %llu\n", (unsigned long long)stats1.rx_lbip);
+ printf("rx_lfebe: %llu\n", (unsigned long long)stats1.rx_lfebe);
+ printf("rx_pbip: %llu\n", (unsigned long long)stats1.rx_pbip);
+ printf("rx_pfebe: %llu\n", (unsigned long long)stats1.rx_pfebe);
+ printf("rx_cells: %llu\n", (unsigned long long)stats1.rx_cells);
+ printf("rx_corr: %llu\n", (unsigned long long)stats1.rx_corr);
+ printf("rx_uncorr: %llu\n", (unsigned long long)stats1.rx_uncorr);
+ printf("rx_symerr: %llu\n", (unsigned long long)stats1.rx_symerr);
+ printf("tx_cells: %llu\n", (unsigned long long)stats1.tx_cells);
+}
+
+/*
+ * Fetch the table of open vccs
+ */
+void
+diagif_fetch_vcc(struct diagif *aif, int fd)
+{
+ struct ifreq ifr;
+
+ if (aif->vtab != NULL)
+ return;
+
+ strncpy(ifr.ifr_name, aif->ifname, IFNAMSIZ);
+ ifr.ifr_name[IFNAMSIZ - 1] = '\0';
+
+ aif->vtab = malloc(sizeof(*aif->vtab) + sizeof(aif->vtab->vccs[0]) *
+ aif->mib.max_vccs);
+ if (aif->vtab == NULL)
+ err(1, NULL);
+ ifr.ifr_data = (caddr_t)aif->vtab;
+
+ if (ioctl(fd, SIOCATMGVCCS, &ifr) == -1)
+ err(1, "SIOCATMGVCCS");
+}
+
+/*
+ * Print the VCC table for this interface.
+ */
+static void
+print_channel(const struct diagif *aif)
+{
+ const struct atmio_vcc *v;
+
+ static const char *const aal_tab[] = {
+ [ATMIO_AAL_0] = "0",
+ [ATMIO_AAL_34] = "3/4",
+ [ATMIO_AAL_5] = "5",
+ [ATMIO_AAL_RAW] = "raw",
+ };
+ static const char *const traffic_tab[] = {
+ [ATMIO_TRAFFIC_UBR] = "ubr",
+ [ATMIO_TRAFFIC_CBR] = "cbr",
+ [ATMIO_TRAFFIC_ABR] = "abr",
+ [ATMIO_TRAFFIC_VBR] = "vbr",
+ };
+
+ for (v = aif->vtab->vccs; v < &aif->vtab->vccs[aif->vtab->count]; v++) {
+ printf("%-6u%-9s%-4u%-6u", aif->index, aif->ifname,
+ v->vpi, v->vci);
+
+ if (v->aal >= sizeof(aal_tab)/sizeof(aal_tab[0]) ||
+ aal_tab[v->aal] == NULL)
+ printf("bad ");
+ else
+ printf("%-4s", aal_tab[v->aal]);
+
+ if (v->traffic >= sizeof(traffic_tab)/sizeof(traffic_tab[0]) ||
+ traffic_tab[v->traffic] == NULL)
+ printf("bad ");
+ else
+ printf("%-8s", traffic_tab[v->traffic]);
+
+ printf("%-6u%-6u%s\n", v->rmtu, v->tmtu,
+ printb(v->flags, ATMIO_FLAGS));
+ }
+}
+
+/*
+ * Print the VCC table for this interface, traffic parameters.
+ */
+static void
+print_traffic(const struct diagif *aif)
+{
+ const struct atmio_vcc *v;
+
+ for (v = aif->vtab->vccs; v < &aif->vtab->vccs[aif->vtab->count]; v++) {
+ printf("%-6u%-9s%-4u%-6u", aif->index, aif->ifname,
+ v->vpi, v->vci);
+
+ switch (v->traffic) {
+
+ case ATMIO_TRAFFIC_CBR:
+ printf("%u", v->tparam.pcr);
+ break;
+
+ case ATMIO_TRAFFIC_UBR:
+ printf("%-8u %u", v->tparam.pcr,
+ v->tparam.mcr);
+ break;
+
+ case ATMIO_TRAFFIC_VBR:
+ printf("%-8u%-8u%-8u", v->tparam.pcr, v->tparam.scr,
+ v->tparam.mbs);
+ break;
+
+ case ATMIO_TRAFFIC_ABR:
+ printf("%-8u %-8u",
+ v->tparam.pcr, v->tparam.mcr);
+ break;
+ }
+ printf("\n");
+ }
+}
+
+/*
+ * Print the VCC table for this interface, ABR traffic parameters.
+ */
+static void
+print_abr(const struct diagif *aif)
+{
+ const struct atmio_vcc *v;
+
+ for (v = aif->vtab->vccs; v < &aif->vtab->vccs[aif->vtab->count]; v++) {
+ printf("%-6u%-9s%-4u%-6u", aif->index, aif->ifname,
+ v->vpi, v->vci);
+
+ if (v->traffic == ATMIO_TRAFFIC_ABR) {
+ printf("%-8u%-8u%-4u%-4u%-5u%-5u%-5u%u",
+ v->tparam.icr, v->tparam.tbe, v->tparam.nrm,
+ v->tparam.trm, v->tparam.adtf, v->tparam.rif,
+ v->tparam.rdf, v->tparam.cdf);
+ }
+ printf("\n");
+ }
+}
+
+static void
+diag_vcc_loop(void (*func)(const struct diagif *), const char *text,
+ int argc, char *argv[], int fd)
+{
+ struct diagif *aif;
+
+ heading_init();
+ if (argc == 0) {
+ TAILQ_FOREACH(aif, &diagif_list, link) {
+ diagif_fetch_vcc(aif, fd);
+ if (aif->vtab->count != 0) {
+ heading("%s", text);
+ (*func)(aif);
+ }
+ }
+
+ } else {
+ for (optind = 0; optind < argc; optind++) {
+ TAILQ_FOREACH(aif, &diagif_list, link)
+ if (strcmp(aif->ifname, argv[optind]) == 0) {
+ diagif_fetch_vcc(aif, fd);
+ if (aif->vtab->count != 0) {
+ heading("%s", text);
+ (*func)(aif);
+ }
+ break;
+ }
+ if (aif == NULL)
+ warnx("no such interface '%s'", argv[optind]);
+ }
+ }
+}
+
+static void
+diag_vcc(int argc, char *argv[])
+{
+ int opt, fd;
+
+ static int channel, traffic, abr;
+ static const struct option opts[] = {
+ { "abr", OPT_SIMPLE, &abr },
+ { "channel", OPT_SIMPLE, &channel },
+ { "traffic", OPT_SIMPLE, &traffic },
+ { NULL, 0, NULL }
+ };
+ static const char head_channel[] =
+ "Interface\n"
+ "Index Name VPI VCI AAL Traffic RxMTU TxMTU Flags\n";
+ static const char head_traffic[] =
+ "Interface Traffic parameters\n"
+ "Index Name VPI VCI PCR SCR MBS MCR\n";
+ static const char head_abr[] =
+ "Interface ABR traffic parameters\n"
+ "Index Name VPI VCI ICR TBE NRM TRM ADTF RIF RDF "
+ "CDF\n";
+
+ while ((opt = parse_options(&argc, &argv, opts)) != -1)
+ switch (opt) {
+ }
+
+ fd = socket(PF_NATM, SOCK_STREAM, PROTO_NATMAAL5);
+ if (fd < 0)
+ err(1, "socket");
+
+ diagif_fetch();
+ if (TAILQ_EMPTY(&diagif_list))
+ errx(1, "no ATM interfaces found");
+
+ if (!channel && !traffic && !abr)
+ channel = 1;
+
+ if (channel)
+ diag_vcc_loop(print_channel, head_channel, argc, argv, fd);
+ if (traffic)
+ diag_vcc_loop(print_traffic, head_traffic, argc, argv, fd);
+ if (abr)
+ diag_vcc_loop(print_abr, head_abr, argc, argv, fd);
+}
+
+/*
+ * Print driver-internal statistics
+ */
+static void
+diag_stats(int argc, char *argv[])
+{
+ int opt;
+ char *str;
+ size_t len;
+ uint32_t *stats;
+ struct diagif *aif;
+ u_int i;
+
+ static const struct option opts[] = {
+ { NULL, 0, NULL }
+ };
+
+ while ((opt = parse_options(&argc, &argv, opts)) != -1)
+ switch (opt) {
+ }
+
+ if (argc != 1)
+ errx(1, "need one arg for 'diag stats'");
+
+ diagif_fetch();
+ TAILQ_FOREACH(aif, &diagif_list, link)
+ if (strcmp(aif->ifname, argv[0]) == 0)
+ break;
+
+ if (aif == NULL)
+ errx(1, "interface '%s' not found", argv[0]);
+
+ if (asprintf(&str, "hw.atm.%s.istats", argv[0]) == -1)
+ err(1, NULL);
+ len = 0;
+ if (sysctlbyname(str, NULL, &len, NULL, 0))
+ err(1, "'%s' not found", str);
+
+ stats = malloc(len);
+ if (stats == NULL)
+ err(1, NULL);
+
+ if (sysctlbyname(str, stats, &len, NULL, 0))
+ err(1, "'%s' not found", str);
+ free(str);
+
+ if (aif->mib.device >= sizeof(print_stats) / sizeof(print_stats[0]) ||
+ print_stats[aif->mib.device] == NULL)
+ errx(1, "unknown stats format (%u)", aif->mib.device);
+
+ for (i = 0; print_stats[aif->mib.device][i] != NULL; i++) {
+ if (i * sizeof(uint32_t) >= len)
+ errx(1, "debug info too short (version mismatch?)");
+ printf("%-22s%u\n", print_stats[aif->mib.device][i], stats[i]);
+ }
+ free(stats);
+
+ if (i != len / sizeof(uint32_t))
+ errx(1, "debug info too long (version mismatch?)");
+}
diff --git a/sbin/atm/atmconfig/diag.h b/sbin/atm/atmconfig/diag.h
new file mode 100644
index 0000000..8b36cd4
--- /dev/null
+++ b/sbin/atm/atmconfig/diag.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2001-2003
+ * Fraunhofer Institute for Open Communication Systems (FhG Fokus).
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * Author: Hartmut Brandt <harti@freebsd.org>
+ *
+ * $FreeBSD$
+ */
+
+struct diagif {
+ TAILQ_ENTRY(diagif) link;
+ char ifname[IFNAMSIZ];
+ u_int index;
+ struct ifatm_mib mib;
+ int phy_present : 1;
+ u_int phy_type;
+ u_int phy_loopback;
+ char phy_name[100];
+ u_int phy_state;
+ u_int phy_carrier;
+ struct atmio_vcctable *vtab;
+};
+TAILQ_HEAD(diagif_list, diagif);
+extern struct diagif_list diagif_list;
+
+void diagif_fetch(void);
+void diagif_fetch_vcc(struct diagif *aif, int fd);
diff --git a/sbin/atm/atmconfig/main.c b/sbin/atm/atmconfig/main.c
new file mode 100644
index 0000000..1374218
--- /dev/null
+++ b/sbin/atm/atmconfig/main.c
@@ -0,0 +1,880 @@
+/*
+ * Copyright (c) 2001-2003
+ * Fraunhofer Institute for Open Communication Systems (FhG Fokus).
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * Author: Hartmut Brandt <harti@freebsd.org>
+ */
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/sysctl.h>
+#include <netdb.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <limits.h>
+#include <stdint.h>
+#include <fnmatch.h>
+#include <dirent.h>
+#ifdef WITH_BSNMP
+#include <bsnmp/asn1.h>
+#include <bsnmp/snmp.h>
+#include <bsnmp/snmpclient.h>
+#endif
+
+#include "atmconfig.h"
+#include "private.h"
+
+/* verbosity level */
+static int verbose;
+
+/* notitle option */
+static int notitle;
+
+/* need to put heading before next output */
+static int need_heading;
+
+/*
+ * TOP LEVEL commands
+ */
+static void help_func(int argc, char *argv[]) __dead2;
+
+static const struct cmdtab static_main_tab[] = {
+ { "help", NULL, help_func },
+ { "options", NULL, NULL },
+ { "commands", NULL, NULL },
+ { "diag", diag_tab, NULL },
+ { "natm", natm_tab, NULL },
+ { NULL, NULL, NULL }
+};
+
+static struct cmdtab *main_tab = NULL;
+static size_t main_tab_size = sizeof(static_main_tab) /
+ sizeof(static_main_tab[0]);
+
+static int
+substr(const char *s1, const char *s2)
+{
+ return (strlen(s1) <= strlen(s2) && strncmp(s1, s2, strlen(s1)) == 0);
+}
+
+/*
+ * Current help file state
+ */
+struct help_file {
+ int file_state; /* 0:looking for main file, 1:found, 2:other */
+ const char *p_start; /* current path pointer */
+ const char *p_end; /* end of current path in path */
+ char *dirname; /* directory name */
+ DIR *dir; /* open directory */
+ char *fname; /* current filename */
+ FILE *fp; /* open file */
+ char line[LINE_MAX]; /* current line */
+ u_int fcnt; /* count of files found */
+};
+
+struct help_pos {
+ off_t pos; /* file position */
+ u_int fcnt; /* number of file */
+ char *fname; /* name of file */
+ const char *p_start; /* current path pointer */
+ const char *p_end; /* end of current path in path */
+};
+
+static int
+help_next_file(struct help_file *hp)
+{
+ const char *fpat;
+ struct dirent *ent;
+
+ if (hp->file_state == 3)
+ return (-1);
+
+ if (hp->file_state == 0)
+ fpat = FILE_HELP;
+ else
+ fpat = FILE_HELP_OTHERS;
+
+ if (hp->file_state == 0 || hp->file_state == 1) {
+ /* start from beginning */
+ hp->p_start = PATH_HELP;
+ hp->file_state++;
+ }
+
+ try_file:
+ if (hp->dir != NULL) {
+ /* directory open (must be state 2) */
+ while ((ent = readdir(hp->dir)) != NULL) {
+ if (fnmatch(fpat, ent->d_name, FNM_NOESCAPE) != 0)
+ continue;
+ if (asprintf(&hp->fname, "%s/%s", hp->dirname,
+ ent->d_name) == -1)
+ err(1, NULL);
+ if ((hp->fp = fopen(hp->fname, "r")) != NULL) {
+ hp->fcnt++;
+ return (0);
+ }
+ free(hp->fname);
+ }
+ /* end of directory */
+ closedir(hp->dir);
+ hp->dir = NULL;
+ free(hp->dirname);
+ goto next_path;
+ }
+
+ /* nothing open - advanc to new path element */
+ try_path:
+ for (hp->p_end = hp->p_start; *hp->p_end != '\0' &&
+ *hp->p_end != ':'; hp->p_end++)
+ ;
+
+ if (asprintf(&hp->dirname, "%.*s", (int)(hp->p_end - hp->p_start),
+ hp->p_start) == -1)
+ err(1, NULL);
+
+ if (hp->file_state == 1) {
+ /* just try to open */
+ if (asprintf(&hp->fname, "%s/%s", hp->dirname, fpat) == -1)
+ err(1, NULL);
+ if ((hp->fp = fopen(hp->fname, "r")) != NULL) {
+ hp->fcnt++;
+ return (0);
+ }
+ free(hp->fname);
+
+ goto next_path;
+ }
+
+ /* open directory */
+ if ((hp->dir = opendir(hp->dirname)) != NULL)
+ goto try_file;
+
+ free(hp->dirname);
+
+ next_path:
+ hp->p_start = hp->p_end;
+ if (*hp->p_start == '\0') {
+ /* end of path */
+ if (hp->file_state == 1)
+ errx(1, "help file not found");
+ return (-1);
+ }
+ hp->p_start++;
+ goto try_path;
+
+}
+
+/*
+ * Save current file position
+ */
+static void
+help_file_tell(struct help_file *hp, struct help_pos *pos)
+{
+ if (pos->fname != NULL)
+ free(pos->fname);
+ if ((pos->fname = strdup(hp->fname)) == NULL)
+ err(1, NULL);
+ pos->fcnt = hp->fcnt;
+ pos->p_start = hp->p_start;
+ pos->p_end = hp->p_end;
+ if ((pos->pos = ftello(hp->fp)) == -1)
+ err(1, "%s", pos->fname);
+}
+
+/*
+ * Go to that position
+ *
+ * We can go either to the original help file or back in the current file.
+ */
+static void
+help_file_seek(struct help_file *hp, struct help_pos *pos)
+{
+ hp->p_start = pos->p_start;
+ hp->p_end = pos->p_end;
+ hp->fcnt = pos->fcnt;
+
+ if (hp->dir != NULL) {
+ free(hp->dirname);
+ closedir(hp->dir);
+ hp->dir = NULL;
+ }
+
+ if (hp->fp != NULL &&strcmp(hp->fname, pos->fname) != 0) {
+ free(hp->fname);
+ fclose(hp->fp);
+ hp->fp = NULL;
+ }
+ if (hp->fp == NULL) {
+ if ((hp->fname = strdup(pos->fname)) == NULL)
+ err(1, NULL);
+ if ((hp->fp = fopen(hp->fname, "r")) == NULL)
+ err(1, "reopen %s", hp->fname);
+ }
+ if (fseeko(hp->fp, pos->pos, SEEK_SET) == -1)
+ err(1, "seek %s", hp->fname);
+
+ if (pos->fcnt == 1)
+ /* go back to state 1 */
+ hp->file_state = 1;
+ else
+ /* lock */
+ hp->file_state = 3;
+}
+
+/*
+ * Rewind to position 0
+ */
+static void
+help_file_rewind(struct help_file *hp)
+{
+
+ if (hp->file_state == 1) {
+ if (fseeko(hp->fp, (off_t)0, SEEK_SET) == -1)
+ err(1, "rewind help file");
+ return;
+ }
+
+ if (hp->dir != NULL) {
+ free(hp->dirname);
+ closedir(hp->dir);
+ hp->dir = NULL;
+ }
+
+ if (hp->fp != NULL) {
+ free(hp->fname);
+ fclose(hp->fp);
+ hp->fp = NULL;
+ }
+ memset(hp, 0, sizeof(*hp));
+}
+
+/*
+ * Get next line from a help file
+ */
+static const char *
+help_next_line(struct help_file *hp)
+{
+ for (;;) {
+ if (hp->fp != NULL) {
+ if (fgets(hp->line, sizeof(hp->line), hp->fp) != NULL)
+ return (hp->line);
+ if (ferror(hp->fp))
+ err(1, "%s", hp->fname);
+ free(hp->fname);
+
+ fclose(hp->fp);
+ hp->fp = NULL;
+ }
+ if (help_next_file(hp) == -1)
+ return (NULL);
+ }
+
+}
+
+/*
+ * This function prints the available 0-level help topics from all
+ * other help files by scanning the files. It assumes, that this is called
+ * only from the main help file.
+ */
+static void
+help_get_0topics(struct help_file *hp)
+{
+ struct help_pos save;
+ const char *line;
+
+ memset(&save, 0, sizeof(save));
+ help_file_tell(hp, &save);
+
+ help_file_rewind(hp);
+ while ((line = help_next_line(hp)) != NULL) {
+ if (line[0] == '^' && line[1] == '^')
+ printf("%s", line + 2);
+ }
+ help_file_seek(hp, &save);
+}
+
+/*
+ * Function to print help. The help argument is in argv[0] here.
+ */
+static void
+help_func(int argc, char *argv[])
+{
+ struct help_file hfile;
+ struct help_pos match, last_match;
+ const char *line;
+ char key[100];
+ int level;
+ int i, has_sub_topics;
+
+ memset(&hfile, 0, sizeof(hfile));
+ memset(&match, 0, sizeof(match));
+ memset(&last_match, 0, sizeof(last_match));
+
+ if (argc == 0) {
+ /* only 'help' - show intro */
+ if ((argv[0] = strdup("intro")) == NULL)
+ err(1, NULL);
+ argc = 1;
+ }
+
+ optind = 0;
+ match.pos = -1;
+ last_match.pos = -1;
+ for (;;) {
+ /* read next line */
+ if ((line = help_next_line(&hfile)) == NULL) {
+ /* EOF */
+ level = 999;
+ goto stop;
+ }
+ if (line[0] != '^' || line[1] == '^')
+ continue;
+
+ if (sscanf(line + 1, "%d%99s", &level, key) != 2)
+ errx(1, "error in help file '%s'", line);
+
+ if (level < optind) {
+ stop:
+ /* next higher level entry - stop this level */
+ if (match.pos == -1) {
+ /* not found */
+ goto not_found;
+ }
+ /* go back to the match */
+ help_file_seek(&hfile, &match);
+ last_match = match;
+ memset(&match, 0, sizeof(match));
+ match.pos = -1;
+
+ /* go to next key */
+ if (++optind >= argc)
+ break;
+ }
+ if (level == optind) {
+ if (substr(argv[optind], key)) {
+ if (match.pos != -1) {
+ printf("Ambiguous topic.");
+ goto list_topics;
+ }
+ help_file_tell(&hfile, &match);
+ }
+ }
+ }
+
+ /* before breaking above we have seeked back to the matching point */
+ for (;;) {
+ if ((line = help_next_line(&hfile)) == NULL)
+ break;
+
+ if (line[0] == '#')
+ continue;
+ if (line[0] == '^') {
+ if (line[1] == '^')
+ continue;
+ break;
+ }
+ if (strncmp(line, "$MAIN", 5) == 0) {
+ help_get_0topics(&hfile);
+ continue;
+ }
+ printf("%s", line);
+ }
+
+ exit(0);
+
+ not_found:
+ printf("Topic not found.");
+
+ list_topics:
+ printf(" Use one of:\natmconfig help");
+ for (i = 0; i < optind; i++)
+ printf(" %s", argv[i]);
+
+ printf(" [");
+
+ /* list all the keys at this level */
+ if (last_match.pos == -1)
+ /* go back to start of help */
+ help_file_rewind(&hfile);
+ else
+ help_file_seek(&hfile, &last_match);
+
+ has_sub_topics = 0;
+ while ((line = help_next_line(&hfile)) != NULL) {
+ if (line[0] == '#' || line[0] != '^' || line[1] == '^')
+ continue;
+
+ if (sscanf(line + 1, "%d%99s", &level, key) != 2)
+ errx(1, "error in help file '%s'", line);
+
+ if (level < optind)
+ break;
+ if (level == optind) {
+ has_sub_topics = 1;
+ printf(" %s", key);
+ }
+ }
+ printf(" ].");
+ if (!has_sub_topics)
+ printf(" No sub-topics found.");
+ printf("\n");
+ exit(1);
+}
+
+#ifdef WITH_BSNMP
+/*
+ * Parse a server specification
+ *
+ * syntax is [trans::][community@][server][:port]
+ */
+static void
+parse_server(char *name)
+{
+ char *p, *s = name;
+
+ /* look for a double colon */
+ for (p = s; *p != '\0'; p++) {
+ if (*p == '\\' && p[1] != '\0') {
+ p++;
+ continue;
+ }
+ if (*p == ':' && p[1] == ':')
+ break;
+ }
+ if (*p != '\0') {
+ if (p > s) {
+ if (p - s == 3 && strncmp(s, "udp", 3) == 0)
+ snmp_client.trans = SNMP_TRANS_UDP;
+ else if (p - s == 6 && strncmp(s, "stream", 6) == 0)
+ snmp_client.trans = SNMP_TRANS_LOC_STREAM;
+ else if (p - s == 5 && strncmp(s, "dgram", 5) == 0)
+ snmp_client.trans = SNMP_TRANS_LOC_DGRAM;
+ else
+ errx(1, "unknown SNMP transport '%.*s'",
+ (int)(p - s), s);
+ }
+ s = p + 2;
+ }
+
+ /* look for a @ */
+ for (p = s; *p != '\0'; p++) {
+ if (*p == '\\' && p[1] != '\0') {
+ p++;
+ continue;
+ }
+ if (*p == '@')
+ break;
+ }
+
+ if (*p != '\0') {
+ if (p - s > SNMP_COMMUNITY_MAXLEN)
+ err(1, "community string too long");
+ strncpy(snmp_client.read_community, s, p - s);
+ snmp_client.read_community[p - s] = '\0';
+ strncpy(snmp_client.write_community, s, p - s);
+ snmp_client.write_community[p - s] = '\0';
+ s = p + 1;
+ }
+
+ /* look for a colon */
+ for (p = s; *p != '\0'; p++) {
+ if (*p == '\\' && p[1] != '\0') {
+ p++;
+ continue;
+ }
+ if (*p == ':')
+ break;
+ }
+
+ if (*p == ':') {
+ if (p > s) {
+ *p = '\0';
+ snmp_client_set_host(&snmp_client, s);
+ *p = ':';
+ }
+ snmp_client_set_port(&snmp_client, p + 1);
+ } else if (p > s)
+ snmp_client_set_host(&snmp_client, s);
+}
+#endif
+
+int
+main(int argc, char *argv[])
+{
+ int opt, i;
+ const struct cmdtab *match, *cc, *tab;
+
+#ifdef WITH_BSNMP
+ snmp_client_init(&snmp_client);
+ snmp_client.trans = SNMP_TRANS_LOC_STREAM;
+ snmp_client_set_host(&snmp_client, PATH_ILMI_SOCK);
+#endif
+
+#ifdef WITH_BSNMP
+#define OPTSTR "htvs:"
+#else
+#define OPTSTR "htv"
+#endif
+
+ while ((opt = getopt(argc, argv, OPTSTR)) != -1)
+ switch (opt) {
+
+ case 'h':
+ help_func(0, argv);
+
+#ifdef WITH_BSNMP
+ case 's':
+ parse_server(optarg);
+ break;
+#endif
+
+ case 'v':
+ verbose++;
+ break;
+
+ case 't':
+ notitle = 1;
+ break;
+ }
+
+ if (argv[optind] == NULL)
+ help_func(0, argv);
+
+ argc -= optind;
+ argv += optind;
+
+ if ((main_tab = malloc(sizeof(static_main_tab))) == NULL)
+ err(1, NULL);
+ memcpy(main_tab, static_main_tab, sizeof(static_main_tab));
+
+#ifdef WITH_BSNMP
+ /* XXX while this is compiled in */
+ device_register();
+#endif
+
+ cc = main_tab;
+ i = 0;
+ for (;;) {
+ /*
+ * Scan the table for a match
+ */
+ tab = cc;
+ match = NULL;
+ while (cc->string != NULL) {
+ if (substr(argv[i], cc->string)) {
+ if (match != NULL) {
+ printf("Ambiguous option '%s'",
+ argv[i]);
+ cc = tab;
+ goto subopts;
+ }
+ match = cc;
+ }
+ cc++;
+ }
+ if ((cc = match) == NULL) {
+ printf("Unknown option '%s'", argv[i]);
+ cc = tab;
+ goto subopts;
+ }
+
+ /*
+ * Have a match. If there is no subtable, there must
+ * be either a handler or the command is only a help entry.
+ */
+ if (cc->sub == NULL) {
+ if (cc->func != NULL)
+ break;
+ printf("Unknown option '%s'", argv[i]);
+ cc = tab;
+ goto subopts;
+ }
+
+ /*
+ * Look at the next argument. If it doesn't exist or it
+ * looks like a switch, terminate the scan here.
+ */
+ if (argv[i + 1] == NULL || argv[i + 1][0] == '-') {
+ if (cc->func != NULL)
+ break;
+ printf("Need sub-option for '%s'", argv[i]);
+ cc = cc->sub;
+ goto subopts;
+ }
+
+ cc = cc->sub;
+ i++;
+ }
+
+ argc -= i + 1;
+ argv += i + 1;
+
+ (*cc->func)(argc, argv);
+
+ return (0);
+
+ subopts:
+ printf(". Select one of:\n");
+ while (cc->string != NULL) {
+ if (cc->func != NULL || cc->sub != NULL)
+ printf("%s ", cc->string);
+ cc++;
+ }
+ printf("\n");
+
+ return (1);
+}
+
+void
+verb(const char *fmt, ...)
+{
+ va_list ap;
+
+ if (verbose) {
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ fprintf(stderr, "\n");
+ va_end(ap);
+ }
+}
+
+void
+heading(const char *fmt, ...)
+{
+ va_list ap;
+
+ if (need_heading) {
+ need_heading = 0;
+ if (!notitle) {
+ va_start(ap, fmt);
+ fprintf(stdout, fmt, ap);
+ va_end(ap);
+ }
+ }
+}
+
+void
+heading_init(void)
+{
+ need_heading = 1;
+}
+
+/*
+ * stringify an enumerated value
+ */
+const char *
+penum(int32_t value, const struct penum *strtab, char *buf)
+{
+ while (strtab->str != NULL) {
+ if (strtab->value == value) {
+ strcpy(buf, strtab->str);
+ return (buf);
+ }
+ strtab++;
+ }
+ warnx("illegal value for enumerated variable '%d'", value);
+ strcpy(buf, "?");
+ return (buf);
+}
+
+/*
+ * And the other way 'round
+ */
+int
+pparse(int32_t *val, const struct penum *tab, const char *str)
+{
+
+ while (tab->str != NULL) {
+ if (strcmp(tab->str, str) == 0) {
+ *val = tab->value;
+ return (0);
+ }
+ tab++;
+ }
+ return (-1);
+}
+
+/*
+ * Parse command line options
+ */
+int
+parse_options(int *pargc, char ***pargv, const struct option *opts)
+{
+ const struct option *o, *m;
+ char *arg;
+ u_long ularg, ularg1;
+ long larg;
+ char *end;
+
+ if (*pargc == 0)
+ return (-1);
+ arg = (*pargv)[0];
+ if (arg[0] != '-' || arg[1] == '\0')
+ return (-1);
+ if (arg[1] == '-' && arg[2] == '\0') {
+ (*pargv)++;
+ (*pargc)--;
+ return (-1);
+ }
+
+ m = NULL;
+ for (o = opts; o->optstr != NULL; o++) {
+ if (strlen(arg + 1) <= strlen(o->optstr) &&
+ strncmp(arg + 1, o->optstr, strlen(arg + 1)) == 0) {
+ if (m != NULL)
+ errx(1, "ambiguous option '%s'", arg);
+ m = o;
+ }
+ }
+ if (m == NULL)
+ errx(1, "unknown option '%s'", arg);
+
+ (*pargv)++;
+ (*pargc)--;
+
+ if (m->opttype == OPT_NONE)
+ return (m - opts);
+
+ if (m->opttype == OPT_SIMPLE) {
+ *(int *)m->optarg = 1;
+ return (m - opts);
+ }
+
+ if (*pargc == 0)
+ errx(1, "option requires argument '%s'", arg);
+ optarg = *(*pargv)++;
+ (*pargc)--;
+
+ switch (m->opttype) {
+
+ case OPT_UINT:
+ ularg = strtoul(optarg, &end, 0);
+ if (*end != '\0')
+ errx(1, "bad unsigned integer argument for '%s'", arg);
+ if (ularg > UINT_MAX)
+ errx(1, "argument to large for option '%s'", arg);
+ *(u_int *)m->optarg = (u_int)ularg;
+ break;
+
+ case OPT_INT:
+ larg = strtol(optarg, &end, 0);
+ if (*end != '\0')
+ errx(1, "bad integer argument for '%s'", arg);
+ if (larg > INT_MAX || larg < INT_MIN)
+ errx(1, "argument out of range for option '%s'", arg);
+ *(int *)m->optarg = (int)larg;
+ break;
+
+ case OPT_UINT32:
+ ularg = strtoul(optarg, &end, 0);
+ if (*end != '\0')
+ errx(1, "bad unsigned integer argument for '%s'", arg);
+ if (ularg > UINT32_MAX)
+ errx(1, "argument to large for option '%s'", arg);
+ *(uint32_t *)m->optarg = (uint32_t)ularg;
+ break;
+
+ case OPT_INT32:
+ larg = strtol(optarg, &end, 0);
+ if (*end != '\0')
+ errx(1, "bad integer argument for '%s'", arg);
+ if (larg > INT32_MAX || larg < INT32_MIN)
+ errx(1, "argument out of range for option '%s'", arg);
+ *(int32_t *)m->optarg = (int32_t)larg;
+ break;
+
+ case OPT_UINT64:
+ *(uint64_t *)m->optarg = strtoull(optarg, &end, 0);
+ if (*end != '\0')
+ errx(1, "bad unsigned integer argument for '%s'", arg);
+ break;
+
+ case OPT_INT64:
+ *(int64_t *)m->optarg = strtoll(optarg, &end, 0);
+ if (*end != '\0')
+ errx(1, "bad integer argument for '%s'", arg);
+ break;
+
+ case OPT_FLAG:
+ if (strcasecmp(optarg, "enable") == 0 ||
+ strcasecmp(optarg, "yes") == 0 ||
+ strcasecmp(optarg, "true") == 0 ||
+ strcasecmp(optarg, "on") == 0 ||
+ strcmp(optarg, "1") == 0)
+ *(int *)m->optarg = 1;
+ else if (strcasecmp(optarg, "disable") == 0 ||
+ strcasecmp(optarg, "no") == 0 ||
+ strcasecmp(optarg, "false") == 0 ||
+ strcasecmp(optarg, "off") == 0 ||
+ strcmp(optarg, "0") == 0)
+ *(int *)m->optarg = 0;
+ else
+ errx(1, "bad boolean argument to '%s'", arg);
+ break;
+
+ case OPT_VCI:
+ ularg = strtoul(optarg, &end, 0);
+ if (*end == '.') {
+ ularg1 = strtoul(end + 1, &end, 0);
+ } else {
+ ularg1 = ularg;
+ ularg = 0;
+ }
+ if (*end != '\0')
+ errx(1, "bad VCI value for option '%s'", arg);
+ if (ularg > 0xff)
+ errx(1, "VPI value too large for option '%s'", arg);
+ if (ularg1 > 0xffff)
+ errx(1, "VCI value too large for option '%s'", arg);
+ ((u_int *)m->optarg)[0] = ularg;
+ ((u_int *)m->optarg)[1] = ularg1;
+ break;
+
+ case OPT_STRING:
+ if (m->optarg != NULL)
+ *(const char **)m->optarg = optarg;
+ break;
+
+ default:
+ errx(1, "(internal) bad option type %u for '%s'",
+ m->opttype, arg);
+ }
+ return (m - opts);
+}
+
+/*
+ * for compiled-in modules
+ */
+void
+register_module(const struct amodule *mod)
+{
+ main_tab_size++;
+ if ((main_tab = realloc(main_tab, main_tab_size * sizeof(main_tab[0])))
+ == NULL)
+ err(1, NULL);
+ main_tab[main_tab_size - 2] = *mod->cmd;
+ memset(&main_tab[main_tab_size - 1], 0, sizeof(main_tab[0]));
+}
diff --git a/sbin/atm/atmconfig/natm.c b/sbin/atm/atmconfig/natm.c
new file mode 100644
index 0000000..29f5ce2
--- /dev/null
+++ b/sbin/atm/atmconfig/natm.c
@@ -0,0 +1,680 @@
+/*
+ * Copyright (c) 2003
+ * Fraunhofer Institute for Open Communication Systems (FhG Fokus).
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * Author: Hartmut Brandt <harti@freebsd.org>
+ */
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+#include <net/if_atm.h>
+#include <net/if_dl.h>
+#include <net/route.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include "atmconfig.h"
+#include "private.h"
+#include "diag.h"
+
+static void natm_add(int, char *[]);
+static void natm_delete(int, char *[]);
+static void natm_show(int, char *[]);
+
+const struct cmdtab natm_tab[] = {
+ { "add", NULL, natm_add },
+ { "delete", NULL, natm_delete },
+ { "show", NULL, natm_show },
+ { NULL, NULL, NULL }
+};
+
+/*
+ * Structure to hold a route
+ */
+struct natm_route {
+ TAILQ_ENTRY(natm_route) link;
+ struct in_addr host;
+ struct diagif *aif;
+ u_int flags;
+ int llcsnap;
+ u_int vpi, vci;
+ u_int traffic;
+ u_int pcr, scr, mbs, icr, mcr;
+ u_int tbe, nrm, trm, adtf, rif, rdf, cdf;
+};
+static TAILQ_HEAD(, natm_route) natm_route_list =
+ TAILQ_HEAD_INITIALIZER(natm_route_list);
+
+static void
+store_route(struct rt_msghdr *rtm)
+{
+ u_int i;
+ struct natm_route *r;
+ char *cp;
+ struct sockaddr *sa;
+ struct sockaddr_in *sain;
+ struct sockaddr_dl *sdl;
+ struct diagif *aif;
+ u_int n;
+
+ r = malloc(sizeof(*r));
+ if (r == NULL)
+ err(1, "allocate route");
+
+ r->flags = rtm->rtm_flags;
+ cp = (char *)(rtm + 1);
+ for (i = 1; i != 0; i <<= 1) {
+ if (rtm->rtm_addrs & i) {
+ sa = (struct sockaddr *)cp;
+ cp += roundup(sa->sa_len, sizeof(long));
+ switch (i) {
+
+ case RTA_DST:
+ if (sa->sa_family != AF_INET) {
+ warnx("RTA_DST not AF_INET %u", sa->sa_family);
+ goto fail;
+ }
+ sain = (struct sockaddr_in *)(void *)sa;
+ if (sain->sin_len < 4)
+ r->host.s_addr = INADDR_ANY;
+ else
+ r->host = sain->sin_addr;
+ break;
+
+ case RTA_GATEWAY:
+ if (sa->sa_family != AF_LINK) {
+ warnx("RTA_GATEWAY not AF_LINK");
+ goto fail;
+ }
+ sdl = (struct sockaddr_dl *)(void *)sa;
+ TAILQ_FOREACH(aif, &diagif_list, link)
+ if (strlen(aif->ifname) ==
+ sdl->sdl_nlen &&
+ strncmp(aif->ifname, sdl->sdl_data,
+ sdl->sdl_nlen) == 0)
+ break;
+ if (aif == NULL) {
+ warnx("interface '%.*s' not found",
+ sdl->sdl_nlen, sdl->sdl_data);
+ goto fail;
+ }
+ r->aif = aif;
+
+ /* parse ATM stuff */
+
+#define GET3() (((sdl->sdl_data[n] & 0xff) << 16) | \
+ ((sdl->sdl_data[n + 1] & 0xff) << 8) | \
+ ((sdl->sdl_data[n + 2] & 0xff) << 0))
+#define GET2() (((sdl->sdl_data[n] & 0xff) << 8) | \
+ ((sdl->sdl_data[n + 1] & 0xff) << 0))
+#define GET1() (((sdl->sdl_data[n] & 0xff) << 0))
+
+ n = sdl->sdl_nlen;
+ if (sdl->sdl_alen < 4) {
+ warnx("RTA_GATEWAY alen too short");
+ goto fail;
+ }
+ r->llcsnap = GET1() & ATM_PH_LLCSNAP;
+ n++;
+ r->vpi = GET1();
+ n++;
+ r->vci = GET2();
+ n += 2;
+ if (sdl->sdl_alen == 4) {
+ /* old address */
+ r->traffic = ATMIO_TRAFFIC_UBR;
+ r->pcr = 0;
+ break;
+ }
+ /* new address */
+ r->traffic = GET1();
+ n++;
+ switch (r->traffic) {
+
+ case ATMIO_TRAFFIC_UBR:
+ if (sdl->sdl_alen >= 5 + 3) {
+ r->pcr = GET3();
+ n += 3;
+ } else
+ r->pcr = 0;
+ break;
+
+ case ATMIO_TRAFFIC_CBR:
+ if (sdl->sdl_alen < 5 + 3) {
+ warnx("CBR address too short");
+ goto fail;
+ }
+ r->pcr = GET3();
+ n += 3;
+ break;
+
+ case ATMIO_TRAFFIC_VBR:
+ if (sdl->sdl_alen < 5 + 3 * 3) {
+ warnx("VBR address too short");
+ goto fail;
+ }
+ r->pcr = GET3();
+ n += 3;
+ r->scr = GET3();
+ n += 3;
+ r->mbs = GET3();
+ n += 3;
+ break;
+
+ case ATMIO_TRAFFIC_ABR:
+ if (sdl->sdl_alen < 5 + 4 * 3 + 2 +
+ 1 * 2 + 3) {
+ warnx("ABR address too short");
+ goto fail;
+ }
+ r->pcr = GET3();
+ n += 3;
+ r->mcr = GET3();
+ n += 3;
+ r->icr = GET3();
+ n += 3;
+ r->tbe = GET3();
+ n += 3;
+ r->nrm = GET1();
+ n++;
+ r->trm = GET1();
+ n++;
+ r->adtf = GET2();
+ n += 2;
+ r->rif = GET1();
+ n++;
+ r->rdf = GET1();
+ n++;
+ r->cdf = GET1();
+ n++;
+ break;
+
+ default:
+ goto fail;
+ }
+ break;
+ }
+ }
+ }
+
+ TAILQ_INSERT_TAIL(&natm_route_list, r, link);
+
+ return;
+ fail:
+ free(r);
+}
+
+/*
+ * Fetch the INET routes that a ours
+ */
+static void
+natm_route_fetch(void)
+{
+ int name[6];
+ size_t needed;
+ u_char *buf, *next;
+ struct rt_msghdr *rtm;
+
+ name[0] = CTL_NET;
+ name[1] = PF_ROUTE;
+ name[2] = 0;
+ name[3] = AF_INET;
+ name[4] = NET_RT_DUMP;
+ name[5] = 0;
+
+ if (sysctl(name, 6, NULL, &needed, NULL, 0) == -1)
+ err(1, "rtable estimate");
+ needed *= 2;
+ if ((buf = malloc(needed)) == NULL)
+ err(1, "rtable buffer (%zu)", needed);
+ if (sysctl(name, 6, buf, &needed, NULL, 0) == -1)
+ err(1, "rtable get");
+
+ next = buf;
+ while (next < buf + needed) {
+ rtm = (struct rt_msghdr *)(void *)next;
+ next += rtm->rtm_msglen;
+
+ if (rtm->rtm_type == RTM_GET) {
+ if ((rtm->rtm_flags & (RTF_UP | RTF_HOST |
+ RTF_STATIC)) == (RTF_UP | RTF_HOST | RTF_STATIC) &&
+ (rtm->rtm_addrs & (RTA_DST | RTA_GATEWAY |
+ RTA_IFP)) == (RTA_DST | RTA_GATEWAY | RTA_IFP))
+ store_route(rtm);
+ }
+ }
+}
+
+static u_long
+parse_num(const char *arg, const char *name, u_long limit)
+{
+ u_long res;
+ char *end;
+
+ errno = 0;
+ res = strtoul(arg, &end, 10);
+ if (*end != '\0' || end == arg || errno != 0)
+ errx(1, "cannot parse %s '%s'", name, arg);
+ if (res > limit)
+ errx(1, "%s out of range (0...%lu)", name, limit);
+ return (res);
+}
+
+static void
+do_route(u_int type, u_int flags, const struct sockaddr_in *sain,
+ const struct sockaddr_dl *sdl)
+{
+ struct {
+ struct rt_msghdr h;
+ char space[512];
+ } msg;
+ char *ptr;
+ int s;
+ ssize_t rlen;
+
+ /* create routing message */
+ bzero(&msg, sizeof(msg));
+ msg.h.rtm_msglen = sizeof(msg.h);
+ msg.h.rtm_version = RTM_VERSION;
+ msg.h.rtm_type = type;
+ msg.h.rtm_index = 0;
+ msg.h.rtm_flags = flags;
+ msg.h.rtm_addrs = RTA_DST | (sdl != NULL ? RTA_GATEWAY : 0);
+ msg.h.rtm_pid = getpid();
+
+ ptr = (char *)&msg + sizeof(msg.h);
+ memcpy(ptr, sain, sain->sin_len);
+ ptr += roundup(sain->sin_len, sizeof(long));
+ msg.h.rtm_msglen += roundup(sain->sin_len, sizeof(long));
+
+ if (sdl != NULL) {
+ memcpy(ptr, sdl, sdl->sdl_len);
+ ptr += roundup(sdl->sdl_len, sizeof(long));
+ msg.h.rtm_msglen += roundup(sdl->sdl_len, sizeof(long));
+ }
+
+ /* open socket */
+ s = socket(PF_ROUTE, SOCK_RAW, AF_INET);
+ if (s == -1)
+ err(1, "cannot open routing socket");
+
+ rlen = write(s, &msg, msg.h.rtm_msglen);
+ if (rlen == -1)
+ err(1, "writing to routing socket");
+ if ((size_t)rlen != msg.h.rtm_msglen)
+ errx(1, "short write to routing socket: %zu %u",
+ (size_t)rlen, msg.h.rtm_msglen);
+ close(s);
+}
+
+/*
+ * Add a new NATM route
+ */
+static void
+natm_add(int argc, char *argv[])
+{
+ int opt;
+ struct hostent *hp;
+ struct sockaddr_in sain;
+ struct sockaddr_dl sdl;
+ struct diagif *aif;
+ u_long num, num1;
+ u_int idx;
+
+ static int printonly;
+
+ static const struct option opts[] = {
+ { "printonly", OPT_SIMPLE, &printonly },
+ { NULL, 0, NULL }
+ };
+
+ while ((opt = parse_options(&argc, &argv, opts)) != -1)
+ switch (opt) {
+ }
+
+ if (argc < 5)
+ errx(1, "missing arguments for 'natm add'");
+
+ memset(&sdl, 0, sizeof(sdl));
+ sdl.sdl_len = sizeof(sdl);
+ sdl.sdl_family = AF_LINK;
+
+ /* get the IP address for <dest> */
+ memset(&sain, 0, sizeof(sain));
+ hp = gethostbyname(argv[0]);
+ if (hp == NULL)
+ errx(1, "bad hostname %s: %s", argv[0], hstrerror(h_errno));
+ if (hp->h_addrtype != AF_INET)
+ errx(1, "bad address type for %s", argv[0]);
+ sain.sin_len = sizeof(sain);
+ sain.sin_family = AF_INET;
+ memcpy(&sain.sin_addr, hp->h_addr, sizeof(sain.sin_addr));
+
+ /* find interface */
+ diagif_fetch();
+ TAILQ_FOREACH(aif, &diagif_list, link)
+ if (strcmp(aif->ifname, argv[1]) == 0)
+ break;
+ if (aif == NULL)
+ errx(1, "unknown ATM interface '%s'", argv[1]);
+ sdl.sdl_index = aif->index;
+ strcpy(sdl.sdl_data, aif->ifname);
+ idx = sdl.sdl_nlen = strlen(aif->ifname);
+ idx++;
+
+ /* verify VPI/VCI */
+ num = parse_num(argv[2], "VPI", (1U << aif->mib.vpi_bits));
+ sdl.sdl_data[idx++] = num & 0xff;
+ num = parse_num(argv[3], "VCI", (1U << aif->mib.vci_bits));
+ if (num == 0)
+ errx(1, "VCI may not be 0");
+ sdl.sdl_data[idx++] = (num >> 8) & 0xff;
+ sdl.sdl_data[idx++] = num & 0xff;
+
+ /* encapsulation */
+ if (strcasecmp(argv[4], "llc/snap") == 0) {
+ sdl.sdl_data[sdl.sdl_nlen] = ATM_PH_LLCSNAP;
+ } else if (strcasecmp(argv[4], "aal5") == 0) {
+ sdl.sdl_data[sdl.sdl_nlen] = 0;
+ } else
+ errx(1, "bad encapsulation type '%s'", argv[4]);
+
+ /* look at the traffic */
+ argc -= 5;
+ argv += 5;
+
+ if (argc != 0) {
+ if (strcasecmp(argv[0], "ubr") == 0) {
+ sdl.sdl_data[idx++] = ATMIO_TRAFFIC_UBR;
+ if (argc == 1)
+ /* ok */;
+ else if (argc == 2) {
+ num = parse_num(argv[1], "PCR", aif->mib.pcr);
+ sdl.sdl_data[idx++] = (num >> 16) & 0xff;
+ sdl.sdl_data[idx++] = (num >> 8) & 0xff;
+ sdl.sdl_data[idx++] = (num >> 0) & 0xff;
+ } else
+ errx(1, "too many parameters for UBR");
+
+ } else if (strcasecmp(argv[0], "cbr") == 0) {
+ sdl.sdl_data[idx++] = ATMIO_TRAFFIC_CBR;
+ if (argc == 1)
+ errx(1, "missing PCR for CBR");
+ if (argc > 2)
+ errx(1, "too many parameters for CBR");
+ num = parse_num(argv[1], "PCR", aif->mib.pcr);
+ sdl.sdl_data[idx++] = (num >> 16) & 0xff;
+ sdl.sdl_data[idx++] = (num >> 8) & 0xff;
+ sdl.sdl_data[idx++] = (num >> 0) & 0xff;
+
+ } else if (strcasecmp(argv[0], "vbr") == 0) {
+ sdl.sdl_data[idx++] = ATMIO_TRAFFIC_VBR;
+
+ if (argc < 4)
+ errx(1, "missing arg(s) for VBR");
+ if (argc > 4)
+ errx(1, "too many parameters for VBR");
+
+ num = parse_num(argv[1], "PCR", aif->mib.pcr);
+ sdl.sdl_data[idx++] = (num >> 16) & 0xff;
+ sdl.sdl_data[idx++] = (num >> 8) & 0xff;
+ sdl.sdl_data[idx++] = (num >> 0) & 0xff;
+ num = parse_num(argv[2], "SCR", num);
+ sdl.sdl_data[idx++] = (num >> 16) & 0xff;
+ sdl.sdl_data[idx++] = (num >> 8) & 0xff;
+ sdl.sdl_data[idx++] = (num >> 0) & 0xff;
+ num = parse_num(argv[3], "MBS", 0xffffffLU);
+ sdl.sdl_data[idx++] = (num >> 16) & 0xff;
+ sdl.sdl_data[idx++] = (num >> 8) & 0xff;
+ sdl.sdl_data[idx++] = (num >> 0) & 0xff;
+
+ } else if (strcasecmp(argv[0], "abr") == 0) {
+ sdl.sdl_data[idx++] = ATMIO_TRAFFIC_ABR;
+ if (argc < 11)
+ errx(1, "missing arg(s) for ABR");
+ if (argc > 11)
+ errx(1, "too many parameters for ABR");
+
+ num = parse_num(argv[1], "PCR", aif->mib.pcr);
+ sdl.sdl_data[idx++] = (num >> 16) & 0xff;
+ sdl.sdl_data[idx++] = (num >> 8) & 0xff;
+ sdl.sdl_data[idx++] = (num >> 0) & 0xff;
+
+ num1 = parse_num(argv[2], "MCR", num);
+ sdl.sdl_data[idx++] = (num1 >> 16) & 0xff;
+ sdl.sdl_data[idx++] = (num1 >> 8) & 0xff;
+ sdl.sdl_data[idx++] = (num1 >> 0) & 0xff;
+
+ num = parse_num(argv[3], "ICR", num);
+ sdl.sdl_data[idx++] = (num >> 16) & 0xff;
+ sdl.sdl_data[idx++] = (num >> 8) & 0xff;
+ sdl.sdl_data[idx++] = (num >> 0) & 0xff;
+
+ if (num < num1)
+ errx(1, "ICR must be >= MCR");
+
+ num = parse_num(argv[4], "TBE", 0xffffffUL);
+ sdl.sdl_data[idx++] = (num >> 16) & 0xff;
+ sdl.sdl_data[idx++] = (num >> 8) & 0xff;
+ sdl.sdl_data[idx++] = (num >> 0) & 0xff;
+
+ num = parse_num(argv[5], "NRM", 0x7UL);
+ sdl.sdl_data[idx++] = (num >> 0) & 0xff;
+
+ num = parse_num(argv[6], "TRM", 0x7UL);
+ sdl.sdl_data[idx++] = (num >> 0) & 0xff;
+
+ num = parse_num(argv[7], "ADTF", 0x3ffUL);
+ sdl.sdl_data[idx++] = (num >> 8) & 0xff;
+ sdl.sdl_data[idx++] = (num >> 0) & 0xff;
+
+ num = parse_num(argv[8], "RIF", 0xfUL);
+ sdl.sdl_data[idx++] = (num >> 0) & 0xff;
+
+ num = parse_num(argv[9], "RDF", 0xfUL);
+ sdl.sdl_data[idx++] = (num >> 0) & 0xff;
+
+ num = parse_num(argv[10], "CDF", 0x7UL);
+ sdl.sdl_data[idx++] = (num >> 0) & 0xff;
+
+ } else
+ errx(1, "bad traffic type '%s'", argv[0]);
+ } else
+ sdl.sdl_data[idx++] = ATMIO_TRAFFIC_UBR;
+
+ sdl.sdl_alen = idx - sdl.sdl_nlen;
+ sdl.sdl_len += sdl.sdl_nlen + sdl.sdl_alen;
+
+ if (printonly) {
+ printf("route add -iface %s -link %.*s",
+ inet_ntoa(sain.sin_addr), sdl.sdl_nlen, sdl.sdl_data);
+ for (idx = 0; idx < sdl.sdl_alen; idx++)
+ printf("%c%x", ".:"[idx == 0],
+ (u_int)sdl.sdl_data[sdl.sdl_nlen + idx] & 0xffU);
+ printf("\n");
+ exit(0);
+ }
+
+ do_route(RTM_ADD, RTF_HOST | RTF_STATIC | RTF_UP, &sain, &sdl);
+}
+
+/*
+ * Delete an NATM route
+ */
+static void
+natm_delete(int argc, char *argv[])
+{
+ int opt;
+ struct hostent *hp;
+ struct sockaddr_in sain;
+ u_int vpi, vci;
+ struct diagif *aif;
+ struct natm_route *r;
+
+ static int printonly;
+
+ static const struct option opts[] = {
+ { "printonly", OPT_SIMPLE, &printonly },
+ { NULL, 0, NULL }
+ };
+
+ while ((opt = parse_options(&argc, &argv, opts)) != -1)
+ switch (opt) {
+ }
+
+ diagif_fetch();
+ natm_route_fetch();
+
+ memset(&sain, 0, sizeof(sain));
+ sain.sin_len = sizeof(sain);
+ sain.sin_family = AF_INET;
+
+ if (argc == 1) {
+ /* get the IP address for <dest> */
+ hp = gethostbyname(argv[0]);
+ if (hp == NULL)
+ errx(1, "bad hostname %s: %s", argv[0],
+ hstrerror(h_errno));
+ if (hp->h_addrtype != AF_INET)
+ errx(1, "bad address type for %s", argv[0]);
+ memcpy(&sain.sin_addr, hp->h_addr, sizeof(sain.sin_addr));
+
+ TAILQ_FOREACH(r, &natm_route_list, link)
+ if (r->host.s_addr == sain.sin_addr.s_addr)
+ break;
+ if (r == NULL)
+ errx(1, "no NATM route to host '%s' (%s)", argv[0],
+ inet_ntoa(sain.sin_addr));
+
+ } else if (argc == 3) {
+ TAILQ_FOREACH(aif, &diagif_list, link)
+ if (strcmp(aif->ifname, argv[0]) == 0)
+ break;
+ if (aif == 0)
+ errx(1, "no such interface '%s'", argv[0]);
+
+ vpi = parse_num(argv[1], "VPI", 0xff);
+ vci = parse_num(argv[2], "VCI", 0xffff);
+
+ TAILQ_FOREACH(r, &natm_route_list, link)
+ if (r->aif == aif && r->vpi == vpi && r->vci == vci)
+ break;
+ if (r == NULL)
+ errx(1, "no such NATM route %s %u %u", argv[0],
+ vpi, vci);
+ sain.sin_addr = r->host;
+
+ } else
+ errx(1, "bad number of arguments for 'natm delete'");
+
+ if (printonly) {
+ printf("route delete %s\n", inet_ntoa(r->host));
+ exit(0);
+ }
+
+ do_route(RTM_DELETE, r->flags, &sain, NULL);
+}
+
+/*
+ * Show NATM routes
+ */
+static void
+natm_show(int argc, char *argv[])
+{
+ int opt;
+ struct natm_route *r;
+ struct hostent *hp;
+
+ static const char *const traffics[] = {
+ [ATMIO_TRAFFIC_UBR] = "UBR",
+ [ATMIO_TRAFFIC_CBR] = "CBR",
+ [ATMIO_TRAFFIC_VBR] = "VBR",
+ [ATMIO_TRAFFIC_ABR] = "ABR"
+ };
+
+ static int numeric, abr;
+
+ static const struct option opts[] = {
+ { "abr", OPT_SIMPLE, &abr },
+ { "numeric", OPT_SIMPLE, &numeric },
+ { NULL, 0, NULL }
+ };
+
+ static const char head[] =
+ "Destination Iface VPI VCI Encaps Trf PCR "
+ "SCR/MCR MBS/ICR\n";
+ static const char head_abr[] =
+ "Destination Iface VPI VCI Encaps Trf PCR "
+ "SCR/MCR MBS/ICR TBE NRM TRM ADTF RIF RDF CDF\n";
+
+ while ((opt = parse_options(&argc, &argv, opts)) != -1)
+ switch (opt) {
+ }
+
+ diagif_fetch();
+ natm_route_fetch();
+
+ heading_init();
+ TAILQ_FOREACH(r, &natm_route_list, link) {
+ heading(abr ? head_abr : head);
+ if (numeric)
+ printf("%-20s", inet_ntoa(r->host));
+ else if (r->host.s_addr == INADDR_ANY)
+ printf("%-20s", "default");
+ else {
+ hp = gethostbyaddr((char *)&r->host, sizeof(r->host),
+ AF_INET);
+ if (hp != NULL)
+ printf("%-20s", hp->h_name);
+ else
+ printf("%-20s", inet_ntoa(r->host));
+ }
+ printf("%-12s%-4u%-6u%-9s%-4s", r->aif->ifname, r->vpi, r->vci,
+ r->llcsnap ? "LLC/SNAP" : "AAL5", traffics[r->traffic]);
+ switch (r->traffic) {
+
+ case ATMIO_TRAFFIC_UBR:
+ case ATMIO_TRAFFIC_CBR:
+ printf("%-8u", r->pcr);
+ break;
+
+ case ATMIO_TRAFFIC_VBR:
+ printf("%-8u%-8u%-8u", r->pcr, r->scr, r->mbs);
+ break;
+
+ case ATMIO_TRAFFIC_ABR:
+ printf("%-8u%-8u%-8u", r->pcr, r->mcr, r->icr);
+ if (abr)
+ printf("%-8u%-4u%-4u%-5u%-4u%-4u%-4u",
+ r->tbe, r->nrm, r->trm, r->adtf,
+ r->rif, r->rdf, r->cdf);
+ break;
+ }
+ printf("\n");
+ }
+}
diff --git a/sbin/atm/atmconfig/private.h b/sbin/atm/atmconfig/private.h
new file mode 100644
index 0000000..9dcf539
--- /dev/null
+++ b/sbin/atm/atmconfig/private.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2001-2003
+ * Fraunhofer Institute for Open Communication Systems (FhG Fokus).
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * Author: Hartmut Brandt <harti@freebsd.org>
+ *
+ * $FreeBSD$
+ */
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/queue.h>
+#include <sys/time.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <err.h>
+#include <errno.h>
+#include <netgraph.h>
+#include <net/if.h>
+#include <netinet/in.h>
+
+#ifndef PATH_HELP
+#define PATH_HELP "/usr/share/doc/atm:/usr/local/share/doc/atm"
+#endif
+#ifndef FILE_HELP
+#define FILE_HELP "atmconfig.help"
+#endif
+#ifndef FILE_HELP_OTHERS
+#define FILE_HELP_OTHERS "atmconfig_*.help"
+#endif
+#ifndef PATH_ILMI_SOCK
+#define PATH_ILMI_SOCK "/var/run/ilmid.sock"
+#endif
+
+/*
+ * Builtin commands
+ */
+extern const struct cmdtab diag_tab[];
+extern const struct cmdtab natm_tab[];
diff --git a/sbin/badsect/Makefile b/sbin/badsect/Makefile
new file mode 100644
index 0000000..c11f8cba
--- /dev/null
+++ b/sbin/badsect/Makefile
@@ -0,0 +1,9 @@
+# @(#)Makefile 8.1 (Berkeley) 6/5/93
+# $FreeBSD$
+
+PROG= badsect
+LIBADD= ufs
+MAN= badsect.8
+WARNS?= 2
+
+.include <bsd.prog.mk>
diff --git a/sbin/badsect/badsect.8 b/sbin/badsect/badsect.8
new file mode 100644
index 0000000..293e958
--- /dev/null
+++ b/sbin/badsect/badsect.8
@@ -0,0 +1,133 @@
+.\" Copyright (c) 1985, 1991, 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.
+.\" 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.
+.\"
+.\" @(#)badsect.8 8.1 (Berkeley) 6/5/93
+.\" $FreeBSD$
+.\"
+.Dd June 5, 1993
+.Dt BADSECT 8
+.Os
+.Sh NAME
+.Nm badsect
+.Nd create files to contain bad sectors
+.Sh SYNOPSIS
+.Nm
+.Ar bbdir sector ...
+.Sh DESCRIPTION
+The
+.Nm
+utility makes a file to contain a bad sector.
+Normally, bad sectors
+are made inaccessible by the standard formatter, which provides
+a forwarding table for bad sectors to the driver.
+If a driver supports the bad blocking standard it is much preferable to
+use that method to isolate bad blocks, since the bad block forwarding
+makes the pack appear perfect, and such packs can then be copied with
+.Xr dd 1 .
+The technique used by this program is also less general than
+bad block forwarding, as
+.Nm
+cannot make amends for
+bad blocks in the i-list of file systems or in swap areas.
+.Pp
+On some disks,
+adding a sector which is suddenly bad to the bad sector table
+currently requires the running of the standard
+.Tn DEC
+formatter.
+Thus to deal with a newly bad block
+or on disks where the drivers
+do not support the bad-blocking standard
+.Nm
+may be used to good effect.
+.Pp
+The
+.Nm
+utility is used on a quiet file system in the following way:
+First mount the file system, and change to its root directory.
+Make a directory
+.Li BAD
+there.
+Run
+.Nm
+giving as argument the
+.Ar BAD
+directory followed by
+all the bad sectors you wish to add.
+(The sector numbers must be relative to the beginning of
+the file system, but this is not hard as the system reports
+relative sector numbers in its console error messages.)
+Then change back to the root directory, unmount the file system
+and run
+.Xr fsck 8
+on the file system.
+The bad sectors should show up in two files
+or in the bad sector files and the free list.
+Have
+.Xr fsck 8
+remove files containing the offending bad sectors, but
+.Em do not
+have it remove the
+.Pa BAD/ Ns Em nnnnn
+files.
+This will leave the bad sectors in only the
+.Li BAD
+files.
+.Pp
+The
+.Nm
+utility works by giving the specified sector numbers in a
+.Xr mknod 2
+system call,
+creating an illegal file whose first block address is the block containing
+bad sector and whose name is the bad sector number.
+When it is discovered by
+.Xr fsck 8
+it will ask
+.Dq Li "HOLD BAD BLOCK \&?" .
+A positive response will cause
+.Xr fsck 8
+to convert the inode to a regular file containing the bad block.
+.Sh DIAGNOSTICS
+The
+.Nm
+utility refuses to attach a block that
+resides in a critical area or is out of range of the file system.
+A warning is issued if the block is already in use.
+.Sh SEE ALSO
+.Xr fsck 8
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Bx 4.1 .
+.Sh BUGS
+If more than one sector which comprise a file system fragment are bad,
+you should specify only one of them to
+.Nm ,
+as the blocks in the bad sector files actually cover all the sectors in a
+file system fragment.
diff --git a/sbin/badsect/badsect.c b/sbin/badsect/badsect.c
new file mode 100644
index 0000000..f180410
--- /dev/null
+++ b/sbin/badsect/badsect.c
@@ -0,0 +1,193 @@
+/*
+ * Copyright (c) 1981, 1983, 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.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if 0
+#ifndef lint
+static const char copyright[] =
+"@(#) Copyright (c) 1981, 1983, 1993\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static const char sccsid[] = "@(#)badsect.c 8.1 (Berkeley) 6/5/93";
+#endif
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*
+ * badsect
+ *
+ * Badsect takes a list of file-system relative sector numbers
+ * and makes files containing the blocks of which these sectors are a part.
+ * It can be used to contain sectors which have problems if these sectors
+ * are not part of the bad file for the pack (see bad144). For instance,
+ * this program can be used if the driver for the file system in question
+ * does not support bad block forwarding.
+ */
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/disklabel.h>
+
+#include <ufs/ufs/dinode.h>
+#include <ufs/ffs/fs.h>
+
+#include <err.h>
+#include <errno.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <libufs.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define sblock disk.d_fs
+#define acg disk.d_cg
+static struct uufsd disk;
+static struct fs *fs = &sblock;
+static int errs;
+
+int chkuse(daddr_t, int);
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: badsect bbdir blkno ...\n");
+ exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+ daddr_t diskbn;
+ daddr_t number;
+ struct stat stbuf, devstat;
+ struct dirent *dp;
+ DIR *dirp;
+ char name[2 * MAXPATHLEN];
+ char *name_dir_end;
+
+ if (argc < 3)
+ usage();
+ if (chdir(argv[1]) < 0 || stat(".", &stbuf) < 0)
+ err(2, "%s", argv[1]);
+ strcpy(name, _PATH_DEV);
+ if ((dirp = opendir(name)) == NULL)
+ err(3, "%s", name);
+ name_dir_end = name + strlen(name);
+ while ((dp = readdir(dirp)) != NULL) {
+ strcpy(name_dir_end, dp->d_name);
+ if (lstat(name, &devstat) < 0)
+ err(4, "%s", name);
+ if (stbuf.st_dev == devstat.st_rdev &&
+ (devstat.st_mode & IFMT) == IFCHR)
+ break;
+ }
+ closedir(dirp);
+ if (dp == NULL) {
+ printf("Cannot find dev 0%lo corresponding to %s\n",
+ (u_long)stbuf.st_rdev, argv[1]);
+ exit(5);
+ }
+ if (ufs_disk_fillout(&disk, name) == -1) {
+ if (disk.d_error != NULL)
+ errx(6, "%s: %s", name, disk.d_error);
+ else
+ err(7, "%s", name);
+ }
+ for (argc -= 2, argv += 2; argc > 0; argc--, argv++) {
+ number = strtol(*argv, NULL, 0);
+ if (errno == EINVAL || errno == ERANGE)
+ err(8, "%s", *argv);
+ if (chkuse(number, 1))
+ continue;
+ /*
+ * Print a warning if converting the block number to a dev_t
+ * will truncate it. badsect was not very useful in versions
+ * of BSD before 4.4 because dev_t was 16 bits and another
+ * bit was lost by bogus sign extensions.
+ */
+ diskbn = dbtofsb(fs, number);
+ if ((dev_t)diskbn != diskbn) {
+ printf("sector %ld cannot be represented as a dev_t\n",
+ (long)number);
+ errs++;
+ }
+ else if (mknod(*argv, IFMT|0600, (dev_t)diskbn) < 0) {
+ warn("%s", *argv);
+ errs++;
+ }
+ }
+ ufs_disk_close(&disk);
+ printf("Don't forget to run ``fsck %s''\n", name);
+ exit(errs);
+}
+
+int
+chkuse(daddr_t blkno, int cnt)
+{
+ int cg;
+ daddr_t fsbn, bn;
+
+ fsbn = dbtofsb(fs, blkno);
+ if ((unsigned)(fsbn+cnt) > fs->fs_size) {
+ printf("block %ld out of range of file system\n", (long)blkno);
+ return (1);
+ }
+ cg = dtog(fs, fsbn);
+ if (fsbn < cgdmin(fs, cg)) {
+ if (cg == 0 || (fsbn+cnt) > cgsblock(fs, cg)) {
+ printf("block %ld in non-data area: cannot attach\n",
+ (long)blkno);
+ return (1);
+ }
+ } else {
+ if ((fsbn+cnt) > cgbase(fs, cg+1)) {
+ printf("block %ld in non-data area: cannot attach\n",
+ (long)blkno);
+ return (1);
+ }
+ }
+ if (cgread1(&disk, cg) != 1) {
+ fprintf(stderr, "cg %d: could not be read\n", cg);
+ errs++;
+ return (1);
+ }
+ if (!cg_chkmagic(&acg)) {
+ fprintf(stderr, "cg %d: bad magic number\n", cg);
+ errs++;
+ return (1);
+ }
+ bn = dtogd(fs, fsbn);
+ if (isclr(cg_blksfree(&acg), bn))
+ printf("Warning: sector %ld is in use\n", (long)blkno);
+ return (0);
+}
diff --git a/sbin/bsdlabel/Makefile b/sbin/bsdlabel/Makefile
new file mode 100644
index 0000000..42f0f1c
--- /dev/null
+++ b/sbin/bsdlabel/Makefile
@@ -0,0 +1,17 @@
+# @(#)Makefile 8.2 (Berkeley) 3/17/94
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../../sys/geom
+
+PROG= bsdlabel
+SRCS= bsdlabel.c geom_bsd_enc.c
+MAN+= bsdlabel.8
+
+.if ${MACHINE_CPUARCH} == "i386" || ${MACHINE_CPUARCH} == "amd64"
+LINKS= ${BINDIR}/bsdlabel ${BINDIR}/disklabel
+MLINKS= bsdlabel.8 disklabel.8
+.endif
+
+LIBADD= geom
+
+.include <bsd.prog.mk>
diff --git a/sbin/bsdlabel/bsdlabel.8 b/sbin/bsdlabel/bsdlabel.8
new file mode 100644
index 0000000..54e2af2
--- /dev/null
+++ b/sbin/bsdlabel/bsdlabel.8
@@ -0,0 +1,500 @@
+.\" Copyright (c) 1987, 1988, 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" Symmetric Computer Systems.
+.\"
+.\" 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.
+.\" 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.
+.\"
+.\" @(#)disklabel.8 8.2 (Berkeley) 4/19/94
+.\" $FreeBSD$
+.\"
+.Dd October 1, 2013
+.Dt BSDLABEL 8
+.Os
+.Sh NAME
+.Nm bsdlabel
+.Nd read and write BSD label
+.Sh SYNOPSIS
+.Nm
+.Op Fl A
+.Ar disk | Fl f Ar file
+.Nm
+.Fl w
+.Op Fl \&An
+.Op Fl B Op Fl b Ar boot
+.Op Fl m Ar machine
+.Ar disk | Fl f Ar file
+.Op Ar type
+.Nm
+.Fl e
+.Op Fl \&An
+.Op Fl B Op Fl b Ar boot
+.Op Fl m Ar machine
+.Ar disk | Fl f Ar file
+.Nm
+.Fl R
+.Op Fl \&An
+.Op Fl B Op Fl b Ar boot
+.Op Fl m Ar machine
+.Op Fl f
+.Ar disk | Fl f Ar file
+.Ar protofile
+.Sh DESCRIPTION
+The
+.Nm
+utility
+installs, examines or modifies the
+.Bx
+label on a disk partition, or on a file containing a partition image.
+In addition,
+.Nm
+can install bootstrap code.
+.Ss Disk Device Name
+When specifying the device (i.e., when the
+.Fl f
+option is not used),
+the
+.Pa /dev/
+path prefix may be omitted;
+the
+.Nm
+utility will automatically prepend it.
+.Ss General Options
+The
+.Fl A
+option enables processing of the historical parts of the
+.Bx
+label.
+If the option is not given, suitable values are set for these fields.
+.Pp
+The
+.Fl f
+option tells
+.Nm
+that the program will operate on a file instead of a disk partition.
+.Pp
+The
+.Fl n
+option stops the
+.Nm
+program right before the disk would have been modified, and displays
+the result instead of writing it.
+.Pp
+The
+.Fl m Ar machine
+argument forces
+.Nm
+to use a layout suitable for a different architecture.
+Current valid values are
+.Cm i386 , amd64 ,
+and
+.Cm pc98 .
+If this option is omitted,
+.Nm
+will use a layout suitable for the current machine.
+.Ss Reading the Disk Label
+To examine the label on a disk drive, use the form
+.Pp
+.Nm
+.Op Fl A
+.Op Fl m Ar machine
+.Ar disk
+.Pp
+.Ar disk
+represents the disk in question, and may be in the form
+.Pa da0
+or
+.Pa /dev/da0 .
+It will display the partition layout.
+.Ss Writing a Standard Label
+To write a standard label, use the form
+.Pp
+.Nm
+.Fl w
+.Op Fl \&An
+.Op Fl m Ar machine
+.Ar disk
+.Op Ar type
+.Pp
+If the drive
+.Ar type
+is specified, the entry of that name in the
+.Xr disktab 5
+file is used; otherwise, or if the type is specified as 'auto', a default
+layout is used.
+.Ss Editing an Existing Disk Label
+To edit an existing disk label, use the form
+.Pp
+.Nm
+.Fl e
+.Op Fl \&An
+.Op Fl m Ar machine
+.Ar disk
+.Pp
+This command opens the disk label in the default editor, and when the editor
+exits, the label is validated and if OK written to disk.
+.Ss Restoring a Disk Label From a File
+To restore a disk label from a file, use the form
+.Pp
+.Nm
+.Fl R
+.Op Fl \&An
+.Op Fl m Ar machine
+.Ar disk protofile
+.Pp
+The
+.Nm
+utility
+is capable of restoring a disk label that was previously saved in a file in
+.Tn ASCII
+format.
+The prototype file used to create the label should be in the same format as that
+produced when reading or editing a label.
+Comments are delimited by
+.Ql #
+and newline.
+.Ss Installing Bootstraps
+If the
+.Fl B
+option is specified, bootstrap code will be read from the file
+.Pa /boot/boot
+and written to the disk.
+The
+.Fl b Ar boot
+option allows a different file to be used.
+.Sh FILES
+.Bl -tag -width ".Pa /etc/disktab" -compact
+.It Pa /boot/boot
+Default boot image.
+.It Pa /etc/disktab
+Disk description file.
+.El
+.Sh SAVED FILE FORMAT
+The
+.Nm
+utility
+uses an
+.Tn ASCII
+version of the label when examining, editing, or restoring a disk
+label.
+The format is:
+.Bd -literal -offset 4n
+
+8 partitions:
+# size offset fstype [fsize bsize bps/cpg]
+ a: 81920 16 4.2BSD 2048 16384 5128
+ b: 1091994 81936 swap
+ c: 1173930 0 unused 0 0 # "raw" part, don't edit
+.Ed
+.Pp
+If the
+.Fl A
+option is specified, the format is:
+.Bd -literal -offset 4n
+# /dev/da1c:
+type: SCSI
+disk: da0s1
+label:
+flags:
+bytes/sector: 512
+sectors/track: 51
+tracks/cylinder: 19
+sectors/cylinder: 969
+cylinders: 1211
+sectors/unit: 1173930
+rpm: 3600
+interleave: 1
+trackskew: 0
+cylinderskew: 0
+headswitch: 0 # milliseconds
+track-to-track seek: 0 # milliseconds
+drivedata: 0
+
+8 partitions:
+# size offset fstype [fsize bsize bps/cpg]
+ a: 81920 16 4.2BSD 1024 8192 16
+ b: 160000 81936 swap
+ c: 1173930 0 unused 0 0 # "raw" part, don't edit
+.Ed
+.Pp
+Lines starting with a
+.Ql #
+mark are comments.
+.Pp
+The partition table can have up to 8 entries.
+It contains the following information:
+.Bl -tag -width indent
+.It Ar #
+The partition identifier is a single letter in the range
+.Ql a
+to
+.Ql h .
+By convention, partition
+.Ql c
+is reserved to describe the entire disk.
+.It Ar size
+The size of the partition in sectors,
+.Cm K
+(kilobytes - 1024),
+.Cm M
+(megabytes - 1024*1024),
+.Cm G
+(gigabytes - 1024*1024*1024),
+.Cm %
+(percentage of free space
+.Em after
+removing any fixed-size partitions other than partition
+.Ql c ) ,
+or
+.Cm *
+(all remaining free space
+.Em after
+fixed-size and percentage partitions).
+For partition
+.Ql c ,
+a size of
+.Cm *
+indicates the entire disk.
+Lowercase versions of suffixes
+.Cm K , M ,
+and
+.Cm G
+are allowed.
+Size and suffix should be specified without any spaces between them.
+.Pp
+Example: 2097152, 1G, 1024M and 1048576K are all the same size
+(assuming 512-byte sectors).
+.It Ar offset
+The offset of the start of the partition from the beginning of the
+drive in sectors, or
+.Cm *
+to have
+.Nm
+calculate the correct offset to use (the end of the previous partition plus
+one, ignoring partition
+.Ql c ) .
+For partition
+.Ql c ,
+.Cm *
+will be interpreted as an offset of 0.
+The first partition should start at offset 16, because the first 16 sectors are
+reserved for metadata.
+.It Ar fstype
+Describes the purpose of the partition.
+The above example shows all currently used partition types.
+For
+.Tn UFS
+file systems and
+.Xr ccd 4
+partitions, use type
+.Cm 4.2BSD .
+For Vinum drives, use type
+.Cm vinum .
+Other common types are
+.Cm swap
+and
+.Cm unused .
+By convention, partition
+.Ql c
+represents the entire slice and should be of type
+.Cm unused ,
+though
+.Nm
+does not enforce this convention.
+The
+.Nm
+utility
+also knows about a number of other partition types,
+none of which are in current use.
+(See the definitions starting with
+.Dv FS_UNUSED
+in
+.In sys/disklabel.h
+for more details.)
+.It Ar fsize
+For
+.Cm 4.2BSD
+file systems only, the fragment size; see
+.Xr newfs 8 .
+.It Ar bsize
+For
+.Cm 4.2BSD
+file systems only, the block size; see
+.Xr newfs 8 .
+.It Ar bps/cpg
+For
+.Cm 4.2BSD
+file systems, the number of cylinders in a cylinder group; see
+.Xr newfs 8 .
+.El
+.Sh EXAMPLES
+Display the label for the first slice of the
+.Pa da0
+disk, as obtained via
+.Pa /dev/da0s1 :
+.Pp
+.Dl "bsdlabel da0s1"
+.Pp
+Save the in-core label for
+.Pa da0s1
+into the file
+.Pa savedlabel .
+This file can be used with the
+.Fl R
+option to restore the label at a later date:
+.Pp
+.Dl "bsdlabel da0s1 > savedlabel"
+.Pp
+Create a label for
+.Pa da0s1 :
+.Pp
+.Dl "bsdlabel -w /dev/da0s1"
+.Pp
+Read the label for
+.Pa da0s1 ,
+edit it, and install the result:
+.Pp
+.Dl "bsdlabel -e da0s1"
+.Pp
+Read the on-disk label for
+.Pa da0s1 ,
+edit it, and display what the new label would be (in sectors).
+It does
+.Em not
+install the new label either in-core or on-disk:
+.Pp
+.Dl "bsdlabel -e -n da0s1"
+.Pp
+Write a default label on
+.Pa da0s1 .
+Use another
+.Nm Fl e
+command to edit the
+partitioning and file system information:
+.Pp
+.Dl "bsdlabel -w da0s1"
+.Pp
+Restore the on-disk and in-core label for
+.Pa da0s1
+from information in
+.Pa savedlabel :
+.Pp
+.Dl "bsdlabel -R da0s1 savedlabel"
+.Pp
+Display what the label would be for
+.Pa da0s1
+using the partition layout in
+.Pa label_layout .
+This is useful for determining how much space would be allotted for various
+partitions with a labeling scheme using
+.Cm % Ns -based
+or
+.Cm *
+partition sizes:
+.Pp
+.Dl "bsdlabel -R -n da0s1 label_layout"
+.Pp
+Install a new bootstrap on
+.Pa da0s1 .
+The boot code comes from
+.Pa /boot/boot :
+.Pp
+.Dl "bsdlabel -B da0s1"
+.Pp
+Install a new label and bootstrap.
+The bootstrap code comes from the file
+.Pa newboot
+in the current working directory:
+.Pp
+.Dl "bsdlabel -w -B -b newboot /dev/da0s1"
+.Pp
+Completely wipe any prior information on the disk, creating a new bootable
+disk with a
+.Tn DOS
+partition table containing one slice, covering the whole disk.
+Initialize the label on this slice,
+then edit it.
+The
+.Xr dd 1
+commands are optional, but may be necessary for some
+.Tn BIOS Ns es
+to properly
+recognize the disk:
+.Bd -literal -offset indent
+dd if=/dev/zero of=/dev/da0 bs=512 count=32
+fdisk -BI da0
+dd if=/dev/zero of=/dev/da0s1 bs=512 count=32
+bsdlabel -w -B da0s1
+bsdlabel -e da0s1
+.Ed
+.Pp
+This is an example disk label that uses some of the new partition size types
+such as
+.Cm % , M , G ,
+and
+.Cm * ,
+which could be used as a source file for
+.Dq Li "bsdlabel -R ada0s1 new_label_file" :
+.Bd -literal -offset 4n
+# /dev/ada0s1:
+
+8 partitions:
+# size offset fstype [fsize bsize bps/cpg]
+ a: 400M 16 4.2BSD 4096 16384 75 # (Cyl. 0 - 812*)
+ b: 1G * swap
+ c: * * unused
+ e: 204800 * 4.2BSD
+ f: 5g * 4.2BSD
+ g: * * 4.2BSD
+.Ed
+.Sh DIAGNOSTICS
+The kernel device drivers will not allow the size of a disk partition
+to be decreased or the offset of a partition to be changed while it is open.
+.Sh COMPATIBILITY
+Due to the use of an
+.Vt uint32_t
+to store the number of sectors,
+.Bx
+labels are restricted to a maximum of 2^32-1 sectors.
+This usually means 2TB of disk space.
+Larger disks should be partitioned using another method such as
+.Xr gpart 8 .
+.Pp
+The various
+.Bx Ns s
+all use slightly different versions of
+.Bx
+labels and
+are not generally compatible.
+.Sh SEE ALSO
+.Xr ccd 4 ,
+.Xr geom 4 ,
+.Xr md 4 ,
+.Xr disktab 5 ,
+.Xr boot0cfg 8 ,
+.Xr fdisk 8 ,
+.Xr gpart 8 ,
+.Xr newfs 8
diff --git a/sbin/bsdlabel/bsdlabel.c b/sbin/bsdlabel/bsdlabel.c
new file mode 100644
index 0000000..b99c279
--- /dev/null
+++ b/sbin/bsdlabel/bsdlabel.c
@@ -0,0 +1,1572 @@
+/*
+ * Copyright (c) 1994, 1995 Gordon W. Ross
+ * Copyright (c) 1994 Theo de Raadt
+ * All rights reserved.
+ * Copyright (c) 1987, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Symmetric Computer Systems.
+ *
+ * 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.
+ * This product includes software developed by Theo de Raadt.
+ * 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.
+ *
+ * from: $NetBSD: disksubr.c,v 1.13 2000/12/17 22:39:18 pk $
+ */
+
+#if 0
+#ifndef lint
+static const char copyright[] =
+"@(#) Copyright (c) 1987, 1993\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static char sccsid[] = "@(#)disklabel.c 8.2 (Berkeley) 1/7/94";
+/* from static char sccsid[] = "@(#)disklabel.c 1.2 (Symmetric) 11/28/85"; */
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <stdint.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/disk.h>
+#define DKTYPENAMES
+#define FSTYPENAMES
+#define MAXPARTITIONS 20
+#include <sys/disklabel.h>
+
+#include <unistd.h>
+#include <string.h>
+#include <stdio.h>
+#include <libgeom.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+
+#include "pathnames.h"
+
+static void makelabel(const char *, struct disklabel *);
+static int geom_class_available(const char *);
+static int writelabel(void);
+static int readlabel(int flag);
+static void display(FILE *, const struct disklabel *);
+static int edit(void);
+static int editit(void);
+static void fixlabel(struct disklabel *);
+static char *skip(char *);
+static char *word(char *);
+static int getasciilabel(FILE *, struct disklabel *);
+static int getasciipartspec(char *, struct disklabel *, int, int);
+static int checklabel(struct disklabel *);
+static void usage(void);
+static struct disklabel *getvirginlabel(void);
+
+#define DEFEDITOR _PATH_VI
+#define DEFPARTITIONS 8
+
+static char *specname;
+static char *pname;
+static char tmpfil[] = PATH_TMPFILE;
+
+static struct disklabel lab;
+static u_char bootarea[BBSIZE];
+static off_t mediasize;
+static ssize_t secsize;
+static char blank[] = "";
+static char unknown[] = "unknown";
+
+#define MAX_PART ('z')
+#define MAX_NUM_PARTS (1 + MAX_PART - 'a')
+static char part_size_type[MAX_NUM_PARTS];
+static char part_offset_type[MAX_NUM_PARTS];
+static int part_set[MAX_NUM_PARTS];
+
+static int installboot; /* non-zero if we should install a boot program */
+static int allfields; /* present all fields in edit */
+static char const *xxboot; /* primary boot */
+
+static uint32_t lba_offset;
+#ifndef LABELSECTOR
+#define LABELSECTOR -1
+#endif
+#ifndef LABELOFFSET
+#define LABELOFFSET -1
+#endif
+static int labelsoffset = LABELSECTOR;
+static int labeloffset = LABELOFFSET;
+static int bbsize = BBSIZE;
+
+static enum {
+ UNSPEC, EDIT, READ, RESTORE, WRITE, WRITEBOOT
+} op = UNSPEC;
+
+
+static int disable_write; /* set to disable writing to disk label */
+static int is_file; /* work on a file (abs. pathname), "-f" opt. */
+
+int
+main(int argc, char *argv[])
+{
+ FILE *t;
+ int ch, error, fd;
+ const char *name;
+
+ error = 0;
+ name = NULL;
+
+ while ((ch = getopt(argc, argv, "ABb:efm:nRrw")) != -1)
+ switch (ch) {
+ case 'A':
+ allfields = 1;
+ break;
+ case 'B':
+ ++installboot;
+ break;
+ case 'b':
+ xxboot = optarg;
+ break;
+ case 'f':
+ is_file=1;
+ break;
+ case 'm':
+ if (!strcmp(optarg, "i386") ||
+ !strcmp(optarg, "amd64") ||
+ !strcmp(optarg, "pc98")) {
+ labelsoffset = 1;
+ labeloffset = 0;
+ bbsize = 8192;
+ } else {
+ errx(1, "Unsupported architecture");
+ }
+ break;
+ case 'n':
+ disable_write = 1;
+ break;
+ case 'R':
+ if (op != UNSPEC)
+ usage();
+ op = RESTORE;
+ break;
+ case 'e':
+ if (op != UNSPEC)
+ usage();
+ op = EDIT;
+ break;
+ case 'r':
+ /*
+ * We accept and ignore -r for compatibility with
+ * historical disklabel usage.
+ */
+ break;
+ case 'w':
+ if (op != UNSPEC)
+ usage();
+ op = WRITE;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 1)
+ usage();
+ if (labelsoffset < 0 || labeloffset < 0)
+ errx(1, "a -m <architecture> option must be specified");
+
+ /* Figure out the names of the thing we're working on */
+ if (is_file) {
+ specname = argv[0];
+ } else {
+ specname = g_device_path(argv[0]);
+ if (specname == NULL) {
+ warn("unable to get correct path for %s", argv[0]);
+ return(1);
+ }
+ fd = open(specname, O_RDONLY);
+ if (fd < 0) {
+ warn("error opening %s", specname);
+ return(1);
+ }
+ pname = g_providername(fd);
+ if (pname == NULL) {
+ warn("error getting providername for %s", specname);
+ close(fd);
+ return(1);
+ }
+ close(fd);
+ }
+
+ if (installboot && op == UNSPEC)
+ op = WRITEBOOT;
+ else if (op == UNSPEC)
+ op = READ;
+
+ switch(op) {
+
+ case UNSPEC:
+ break;
+
+ case EDIT:
+ if (argc != 1)
+ usage();
+ readlabel(1);
+ fixlabel(&lab);
+ error = edit();
+ break;
+
+ case READ:
+ if (argc != 1)
+ usage();
+ readlabel(1);
+ display(stdout, NULL);
+ error = checklabel(NULL);
+ break;
+
+ case RESTORE:
+ if (argc != 2)
+ usage();
+ if (!(t = fopen(argv[1], "r")))
+ err(4, "fopen %s", argv[1]);
+ readlabel(0);
+ if (!getasciilabel(t, &lab))
+ exit(1);
+ error = writelabel();
+ break;
+
+ case WRITE:
+ if (argc == 2)
+ name = argv[1];
+ else if (argc == 1)
+ name = "auto";
+ else
+ usage();
+ readlabel(0);
+ makelabel(name, &lab);
+ fixlabel(&lab);
+ if (checklabel(NULL) == 0)
+ error = writelabel();
+ break;
+
+ case WRITEBOOT:
+
+ readlabel(1);
+ fixlabel(&lab);
+ if (argc == 2)
+ makelabel(argv[1], &lab);
+ if (checklabel(NULL) == 0)
+ error = writelabel();
+ break;
+ }
+ exit(error);
+}
+
+static void
+fixlabel(struct disklabel *lp)
+{
+ struct partition *dp;
+ int i;
+
+ for (i = 0; i < lp->d_npartitions; i++) {
+ if (i == RAW_PART)
+ continue;
+ if (lp->d_partitions[i].p_size)
+ return;
+ }
+
+ dp = &lp->d_partitions[0];
+ dp->p_offset = BBSIZE / secsize;
+ dp->p_size = lp->d_secperunit - dp->p_offset;
+}
+
+/*
+ * Construct a prototype disklabel from /etc/disktab.
+ */
+static void
+makelabel(const char *type, struct disklabel *lp)
+{
+ struct disklabel *dp;
+
+ if (strcmp(type, "auto") == 0)
+ dp = getvirginlabel();
+ else
+ dp = getdiskbyname(type);
+ if (dp == NULL)
+ errx(1, "%s: unknown disk type", type);
+ *lp = *dp;
+ bzero(lp->d_packname, sizeof(lp->d_packname));
+}
+
+static void
+readboot(void)
+{
+ int fd;
+ struct stat st;
+
+ if (xxboot == NULL)
+ xxboot = "/boot/boot";
+ fd = open(xxboot, O_RDONLY);
+ if (fd < 0)
+ err(1, "cannot open %s", xxboot);
+ fstat(fd, &st);
+ if (st.st_size <= BBSIZE) {
+ if (read(fd, bootarea, st.st_size) != st.st_size)
+ err(1, "read error %s", xxboot);
+ close(fd);
+ return;
+ }
+ errx(1, "boot code %s is wrong size", xxboot);
+}
+
+static int
+geom_class_available(const char *name)
+{
+ struct gclass *class;
+ struct gmesh mesh;
+ int error;
+
+ error = geom_gettree(&mesh);
+ if (error != 0)
+ errc(1, error, "Cannot get GEOM tree");
+
+ LIST_FOREACH(class, &mesh.lg_class, lg_class) {
+ if (strcmp(class->lg_name, name) == 0) {
+ geom_deletetree(&mesh);
+ return (1);
+ }
+ }
+
+ geom_deletetree(&mesh);
+
+ return (0);
+}
+
+static int
+writelabel(void)
+{
+ int i, fd, serrno;
+ struct gctl_req *grq;
+ char const *errstr;
+ struct disklabel *lp = &lab;
+
+ if (disable_write) {
+ warnx("write to disk label suppressed - label was as follows:");
+ display(stdout, NULL);
+ return (0);
+ }
+
+ lp->d_magic = DISKMAGIC;
+ lp->d_magic2 = DISKMAGIC;
+ lp->d_checksum = 0;
+ lp->d_checksum = dkcksum(lp);
+ if (installboot)
+ readboot();
+ for (i = 0; i < lab.d_npartitions; i++)
+ if (lab.d_partitions[i].p_size)
+ lab.d_partitions[i].p_offset += lba_offset;
+ bsd_disklabel_le_enc(bootarea + labeloffset + labelsoffset * lab.d_secsize,
+ lp);
+
+ fd = open(specname, O_RDWR);
+ if (fd < 0) {
+ if (is_file) {
+ warn("cannot open file %s for writing label", specname);
+ return(1);
+ } else
+ serrno = errno;
+
+ if (geom_class_available("PART") != 0) {
+ /*
+ * Since we weren't able open provider for
+ * writing, then recommend user to use gpart(8).
+ */
+ warnc(serrno,
+ "cannot open provider %s for writing label",
+ specname);
+ warnx("Try to use gpart(8).");
+ return (1);
+ }
+
+ /* Give up if GEOM_BSD is not available. */
+ if (geom_class_available("BSD") == 0) {
+ warnc(serrno, "%s", specname);
+ return (1);
+ }
+
+ grq = gctl_get_handle();
+ gctl_ro_param(grq, "verb", -1, "write label");
+ gctl_ro_param(grq, "class", -1, "BSD");
+ gctl_ro_param(grq, "geom", -1, pname);
+ gctl_ro_param(grq, "label", 148+16*8,
+ bootarea + labeloffset + labelsoffset * lab.d_secsize);
+ errstr = gctl_issue(grq);
+ if (errstr != NULL) {
+ warnx("%s", errstr);
+ gctl_free(grq);
+ return(1);
+ }
+ gctl_free(grq);
+ if (installboot) {
+ grq = gctl_get_handle();
+ gctl_ro_param(grq, "verb", -1, "write bootcode");
+ gctl_ro_param(grq, "class", -1, "BSD");
+ gctl_ro_param(grq, "geom", -1, pname);
+ gctl_ro_param(grq, "bootcode", BBSIZE, bootarea);
+ errstr = gctl_issue(grq);
+ if (errstr != NULL) {
+ warnx("%s", errstr);
+ gctl_free(grq);
+ return (1);
+ }
+ gctl_free(grq);
+ }
+ } else {
+ if (write(fd, bootarea, bbsize) != bbsize) {
+ warn("write %s", specname);
+ close (fd);
+ return (1);
+ }
+ close (fd);
+ }
+ return (0);
+}
+
+static void
+get_file_parms(int f)
+{
+ int i;
+ struct stat sb;
+
+ if (fstat(f, &sb) != 0)
+ err(4, "fstat failed");
+ i = sb.st_mode & S_IFMT;
+ if (i != S_IFREG && i != S_IFLNK)
+ errx(4, "%s is not a valid file or link", specname);
+ secsize = DEV_BSIZE;
+ mediasize = sb.st_size;
+}
+
+/*
+ * Fetch disklabel for disk.
+ */
+static int
+readlabel(int flag)
+{
+ ssize_t nbytes;
+ uint32_t lba;
+ int f, i;
+ int error;
+
+ f = open(specname, O_RDONLY);
+ if (f < 0)
+ err(1, "%s", specname);
+ if (is_file)
+ get_file_parms(f);
+ else {
+ mediasize = g_mediasize(f);
+ secsize = g_sectorsize(f);
+ if (secsize < 0 || mediasize < 0)
+ err(4, "cannot get disk geometry");
+ }
+ if (mediasize > (off_t)0xffffffff * secsize)
+ errx(1,
+ "disks with more than 2^32-1 sectors are not supported");
+ (void)lseek(f, (off_t)0, SEEK_SET);
+ nbytes = read(f, bootarea, BBSIZE);
+ if (nbytes == -1)
+ err(4, "%s read", specname);
+ if (nbytes != BBSIZE)
+ errx(4, "couldn't read %d bytes from %s", BBSIZE, specname);
+ close (f);
+ error = bsd_disklabel_le_dec(
+ bootarea + (labeloffset + labelsoffset * secsize),
+ &lab, MAXPARTITIONS);
+ if (flag && error)
+ errx(1, "%s: no valid label found", specname);
+
+ if (is_file)
+ return(0);
+
+ /*
+ * Compensate for absolute block addressing by finding the
+ * smallest partition offset and if the offset of the 'c'
+ * partition is equal to that, subtract it from all offsets.
+ */
+ lba = ~0;
+ for (i = 0; i < lab.d_npartitions; i++) {
+ if (lab.d_partitions[i].p_size)
+ lba = MIN(lba, lab.d_partitions[i].p_offset);
+ }
+ if (lba != 0 && lab.d_partitions[RAW_PART].p_offset == lba) {
+ for (i = 0; i < lab.d_npartitions; i++) {
+ if (lab.d_partitions[i].p_size)
+ lab.d_partitions[i].p_offset -= lba;
+ }
+ /*
+ * Save the offset so that we can write the label
+ * back with absolute block addresses.
+ */
+ lba_offset = lba;
+ }
+ return (error);
+}
+
+
+static void
+display(FILE *f, const struct disklabel *lp)
+{
+ int i, j;
+ const struct partition *pp;
+
+ if (lp == NULL)
+ lp = &lab;
+
+ fprintf(f, "# %s:\n", specname);
+ if (allfields) {
+ if (lp->d_type < DKMAXTYPES)
+ fprintf(f, "type: %s\n", dktypenames[lp->d_type]);
+ else
+ fprintf(f, "type: %u\n", lp->d_type);
+ fprintf(f, "disk: %.*s\n", (int)sizeof(lp->d_typename),
+ lp->d_typename);
+ fprintf(f, "label: %.*s\n", (int)sizeof(lp->d_packname),
+ lp->d_packname);
+ fprintf(f, "flags:");
+ if (lp->d_flags & D_REMOVABLE)
+ fprintf(f, " removeable");
+ if (lp->d_flags & D_ECC)
+ fprintf(f, " ecc");
+ if (lp->d_flags & D_BADSECT)
+ fprintf(f, " badsect");
+ fprintf(f, "\n");
+ fprintf(f, "bytes/sector: %lu\n", (u_long)lp->d_secsize);
+ fprintf(f, "sectors/track: %lu\n", (u_long)lp->d_nsectors);
+ fprintf(f, "tracks/cylinder: %lu\n", (u_long)lp->d_ntracks);
+ fprintf(f, "sectors/cylinder: %lu\n", (u_long)lp->d_secpercyl);
+ fprintf(f, "cylinders: %lu\n", (u_long)lp->d_ncylinders);
+ fprintf(f, "sectors/unit: %lu\n", (u_long)lp->d_secperunit);
+ fprintf(f, "rpm: %u\n", lp->d_rpm);
+ fprintf(f, "interleave: %u\n", lp->d_interleave);
+ fprintf(f, "trackskew: %u\n", lp->d_trackskew);
+ fprintf(f, "cylinderskew: %u\n", lp->d_cylskew);
+ fprintf(f, "headswitch: %lu\t\t# milliseconds\n",
+ (u_long)lp->d_headswitch);
+ fprintf(f, "track-to-track seek: %ld\t# milliseconds\n",
+ (u_long)lp->d_trkseek);
+ fprintf(f, "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++)
+ fprintf(f, "%lu ", (u_long)lp->d_drivedata[j]);
+ fprintf(f, "\n\n");
+ }
+ fprintf(f, "%u partitions:\n", lp->d_npartitions);
+ fprintf(f,
+ "# size offset fstype [fsize bsize bps/cpg]\n");
+ pp = lp->d_partitions;
+ for (i = 0; i < lp->d_npartitions; i++, pp++) {
+ if (pp->p_size) {
+ fprintf(f, " %c: %10lu %10lu ", 'a' + i,
+ (u_long)pp->p_size, (u_long)pp->p_offset);
+ if (pp->p_fstype < FSMAXTYPES)
+ fprintf(f, "%8.8s", fstypenames[pp->p_fstype]);
+ else
+ fprintf(f, "%8d", pp->p_fstype);
+ switch (pp->p_fstype) {
+
+ case FS_UNUSED: /* XXX */
+ fprintf(f, " %5lu %5lu %2s",
+ (u_long)pp->p_fsize,
+ (u_long)(pp->p_fsize * pp->p_frag), "");
+ break;
+
+ case FS_BSDFFS:
+ fprintf(f, " %5lu %5lu %5u",
+ (u_long)pp->p_fsize,
+ (u_long)(pp->p_fsize * pp->p_frag),
+ pp->p_cpg);
+ break;
+
+ case FS_BSDLFS:
+ fprintf(f, " %5lu %5lu %5d",
+ (u_long)pp->p_fsize,
+ (u_long)(pp->p_fsize * pp->p_frag),
+ pp->p_cpg);
+ break;
+
+ default:
+ fprintf(f, "%20.20s", "");
+ break;
+ }
+ if (i == RAW_PART) {
+ fprintf(f, " # \"raw\" part, don't edit");
+ }
+ fprintf(f, "\n");
+ }
+ }
+ fflush(f);
+}
+
+static int
+edit(void)
+{
+ int c, fd;
+ struct disklabel label;
+ FILE *fp;
+
+ if ((fd = mkstemp(tmpfil)) == -1 ||
+ (fp = fdopen(fd, "w")) == NULL) {
+ warnx("can't create %s", tmpfil);
+ return (1);
+ }
+ display(fp, NULL);
+ fclose(fp);
+ for (;;) {
+ if (!editit())
+ break;
+ fp = fopen(tmpfil, "r");
+ if (fp == NULL) {
+ warnx("can't reopen %s for reading", tmpfil);
+ break;
+ }
+ bzero((char *)&label, sizeof(label));
+ c = getasciilabel(fp, &label);
+ fclose(fp);
+ if (c) {
+ lab = label;
+ if (writelabel() == 0) {
+ (void) unlink(tmpfil);
+ return (0);
+ }
+ }
+ printf("re-edit the label? [y]: ");
+ fflush(stdout);
+ c = getchar();
+ if (c != EOF && c != (int)'\n')
+ while (getchar() != (int)'\n')
+ ;
+ if (c == (int)'n')
+ break;
+ }
+ (void) unlink(tmpfil);
+ return (1);
+}
+
+static int
+editit(void)
+{
+ int pid, xpid;
+ int locstat, omask;
+ const char *ed;
+ uid_t uid;
+ gid_t gid;
+
+ omask = sigblock(sigmask(SIGINT)|sigmask(SIGQUIT)|sigmask(SIGHUP));
+ while ((pid = fork()) < 0) {
+ if (errno == EPROCLIM) {
+ warnx("you have too many processes");
+ return(0);
+ }
+ if (errno != EAGAIN) {
+ warn("fork");
+ return(0);
+ }
+ sleep(1);
+ }
+ if (pid == 0) {
+ sigsetmask(omask);
+ gid = getgid();
+ if (setresgid(gid, gid, gid) == -1)
+ err(1, "setresgid");
+ uid = getuid();
+ if (setresuid(uid, uid, uid) == -1)
+ err(1, "setresuid");
+ if ((ed = getenv("EDITOR")) == (char *)0)
+ ed = DEFEDITOR;
+ execlp(ed, ed, tmpfil, (char *)0);
+ err(1, "%s", ed);
+ }
+ while ((xpid = wait(&locstat)) >= 0)
+ if (xpid == pid)
+ break;
+ sigsetmask(omask);
+ return(!locstat);
+}
+
+static char *
+skip(char *cp)
+{
+
+ while (*cp != '\0' && isspace(*cp))
+ cp++;
+ if (*cp == '\0' || *cp == '#')
+ return (NULL);
+ return (cp);
+}
+
+static char *
+word(char *cp)
+{
+ char c;
+
+ while (*cp != '\0' && !isspace(*cp) && *cp != '#')
+ cp++;
+ if ((c = *cp) != '\0') {
+ *cp++ = '\0';
+ if (c != '#')
+ return (skip(cp));
+ }
+ return (NULL);
+}
+
+/*
+ * Read an ascii label in from fd f,
+ * in the same format as that put out by display(),
+ * and fill in lp.
+ */
+static int
+getasciilabel(FILE *f, struct disklabel *lp)
+{
+ char *cp, *endp;
+ const char **cpp;
+ u_int part;
+ char *tp, line[BUFSIZ];
+ u_long v;
+ int lineno = 0, errors = 0;
+ int i;
+
+ makelabel("auto", lp);
+ bzero(&part_set, sizeof(part_set));
+ bzero(&part_size_type, sizeof(part_size_type));
+ bzero(&part_offset_type, sizeof(part_offset_type));
+ lp->d_bbsize = BBSIZE; /* XXX */
+ lp->d_sbsize = 0; /* XXX */
+ while (fgets(line, sizeof(line) - 1, f)) {
+ lineno++;
+ if ((cp = strchr(line,'\n')) != 0)
+ *cp = '\0';
+ cp = skip(line);
+ if (cp == NULL)
+ continue;
+ tp = strchr(cp, ':');
+ if (tp == NULL) {
+ fprintf(stderr, "line %d: syntax error\n", lineno);
+ errors++;
+ continue;
+ }
+ *tp++ = '\0', tp = skip(tp);
+ if (!strcmp(cp, "type")) {
+ if (tp == NULL)
+ tp = unknown;
+ cpp = dktypenames;
+ for (; cpp < &dktypenames[DKMAXTYPES]; cpp++)
+ if (*cpp && !strcmp(*cpp, tp)) {
+ lp->d_type = cpp - dktypenames;
+ break;
+ }
+ if (cpp < &dktypenames[DKMAXTYPES])
+ continue;
+ errno = 0;
+ v = strtoul(tp, &endp, 10);
+ if (errno != 0 || *endp != '\0')
+ v = DKMAXTYPES;
+ if (v >= DKMAXTYPES)
+ fprintf(stderr, "line %d:%s %lu\n", lineno,
+ "Warning, unknown disk type", v);
+ else
+ lp->d_type = v;
+ continue;
+ }
+ if (!strcmp(cp, "flags")) {
+ for (v = 0; (cp = tp) && *cp != '\0';) {
+ tp = word(cp);
+ if (!strcmp(cp, "removeable"))
+ v |= D_REMOVABLE;
+ else if (!strcmp(cp, "ecc"))
+ v |= D_ECC;
+ else if (!strcmp(cp, "badsect"))
+ v |= D_BADSECT;
+ else {
+ fprintf(stderr,
+ "line %d: %s: bad flag\n",
+ lineno, cp);
+ errors++;
+ }
+ }
+ lp->d_flags = v;
+ continue;
+ }
+ if (!strcmp(cp, "drivedata")) {
+ for (i = 0; (cp = tp) && *cp != '\0' && i < NDDATA;) {
+ lp->d_drivedata[i++] = strtoul(cp, NULL, 10);
+ tp = word(cp);
+ }
+ continue;
+ }
+ if (sscanf(cp, "%lu partitions", &v) == 1) {
+ if (v > MAXPARTITIONS) {
+ fprintf(stderr,
+ "line %d: bad # of partitions\n", lineno);
+ lp->d_npartitions = MAXPARTITIONS;
+ errors++;
+ } else if (v < DEFPARTITIONS) {
+ fprintf(stderr,
+ "line %d: bad # of partitions\n", lineno);
+ lp->d_npartitions = DEFPARTITIONS;
+ errors++;
+ } else
+ lp->d_npartitions = v;
+ continue;
+ }
+ if (tp == NULL)
+ tp = blank;
+ if (!strcmp(cp, "disk")) {
+ strncpy(lp->d_typename, tp, sizeof (lp->d_typename));
+ continue;
+ }
+ if (!strcmp(cp, "label")) {
+ strncpy(lp->d_packname, tp, sizeof (lp->d_packname));
+ continue;
+ }
+ if (!strcmp(cp, "bytes/sector")) {
+ v = strtoul(tp, NULL, 10);
+ if (v == 0 || (v % DEV_BSIZE) != 0) {
+ fprintf(stderr,
+ "line %d: %s: bad sector size\n",
+ lineno, tp);
+ errors++;
+ } else
+ lp->d_secsize = v;
+ continue;
+ }
+ if (!strcmp(cp, "sectors/track")) {
+ v = strtoul(tp, NULL, 10);
+#if (ULONG_MAX != 0xffffffffUL)
+ if (v == 0 || v > 0xffffffff)
+#else
+ if (v == 0)
+#endif
+ {
+ fprintf(stderr, "line %d: %s: bad %s\n",
+ lineno, tp, cp);
+ errors++;
+ } else
+ lp->d_nsectors = v;
+ continue;
+ }
+ if (!strcmp(cp, "sectors/cylinder")) {
+ v = strtoul(tp, NULL, 10);
+ if (v == 0) {
+ fprintf(stderr, "line %d: %s: bad %s\n",
+ lineno, tp, cp);
+ errors++;
+ } else
+ lp->d_secpercyl = v;
+ continue;
+ }
+ if (!strcmp(cp, "tracks/cylinder")) {
+ v = strtoul(tp, NULL, 10);
+ if (v == 0) {
+ fprintf(stderr, "line %d: %s: bad %s\n",
+ lineno, tp, cp);
+ errors++;
+ } else
+ lp->d_ntracks = v;
+ continue;
+ }
+ if (!strcmp(cp, "cylinders")) {
+ v = strtoul(tp, NULL, 10);
+ if (v == 0) {
+ fprintf(stderr, "line %d: %s: bad %s\n",
+ lineno, tp, cp);
+ errors++;
+ } else
+ lp->d_ncylinders = v;
+ continue;
+ }
+ if (!strcmp(cp, "sectors/unit")) {
+ v = strtoul(tp, NULL, 10);
+ if (v == 0) {
+ fprintf(stderr, "line %d: %s: bad %s\n",
+ lineno, tp, cp);
+ errors++;
+ } else
+ lp->d_secperunit = v;
+ continue;
+ }
+ if (!strcmp(cp, "rpm")) {
+ v = strtoul(tp, NULL, 10);
+ if (v == 0 || v > USHRT_MAX) {
+ fprintf(stderr, "line %d: %s: bad %s\n",
+ lineno, tp, cp);
+ errors++;
+ } else
+ lp->d_rpm = v;
+ continue;
+ }
+ if (!strcmp(cp, "interleave")) {
+ v = strtoul(tp, NULL, 10);
+ if (v == 0 || v > USHRT_MAX) {
+ fprintf(stderr, "line %d: %s: bad %s\n",
+ lineno, tp, cp);
+ errors++;
+ } else
+ lp->d_interleave = v;
+ continue;
+ }
+ if (!strcmp(cp, "trackskew")) {
+ v = strtoul(tp, NULL, 10);
+ if (v > USHRT_MAX) {
+ fprintf(stderr, "line %d: %s: bad %s\n",
+ lineno, tp, cp);
+ errors++;
+ } else
+ lp->d_trackskew = v;
+ continue;
+ }
+ if (!strcmp(cp, "cylinderskew")) {
+ v = strtoul(tp, NULL, 10);
+ if (v > USHRT_MAX) {
+ fprintf(stderr, "line %d: %s: bad %s\n",
+ lineno, tp, cp);
+ errors++;
+ } else
+ lp->d_cylskew = v;
+ continue;
+ }
+ if (!strcmp(cp, "headswitch")) {
+ v = strtoul(tp, NULL, 10);
+ lp->d_headswitch = v;
+ continue;
+ }
+ if (!strcmp(cp, "track-to-track seek")) {
+ v = strtoul(tp, NULL, 10);
+ lp->d_trkseek = v;
+ continue;
+ }
+ /* the ':' was removed above */
+ if (*cp < 'a' || *cp > MAX_PART || cp[1] != '\0') {
+ fprintf(stderr,
+ "line %d: %s: Unknown disklabel field\n", lineno,
+ cp);
+ errors++;
+ continue;
+ }
+
+ /* Process a partition specification line. */
+ part = *cp - 'a';
+ if (part >= lp->d_npartitions) {
+ fprintf(stderr,
+ "line %d: partition name out of range a-%c: %s\n",
+ lineno, 'a' + lp->d_npartitions - 1, cp);
+ errors++;
+ continue;
+ }
+ part_set[part] = 1;
+
+ if (getasciipartspec(tp, lp, part, lineno) != 0) {
+ errors++;
+ break;
+ }
+ }
+ errors += checklabel(lp);
+ return (errors == 0);
+}
+
+#define NXTNUM(n) do { \
+ if (tp == NULL) { \
+ fprintf(stderr, "line %d: too few numeric fields\n", lineno); \
+ return (1); \
+ } else { \
+ cp = tp, tp = word(cp); \
+ (n) = strtoul(cp, NULL, 10); \
+ } \
+} while (0)
+
+/* retain 1 character following number */
+#define NXTWORD(w,n) do { \
+ if (tp == NULL) { \
+ fprintf(stderr, "line %d: too few numeric fields\n", lineno); \
+ return (1); \
+ } else { \
+ char *tmp; \
+ cp = tp, tp = word(cp); \
+ (n) = strtoul(cp, &tmp, 10); \
+ if (tmp) (w) = *tmp; \
+ } \
+} while (0)
+
+/*
+ * Read a partition line into partition `part' in the specified disklabel.
+ * Return 0 on success, 1 on failure.
+ */
+static int
+getasciipartspec(char *tp, struct disklabel *lp, int part, int lineno)
+{
+ struct partition *pp;
+ char *cp, *endp;
+ const char **cpp;
+ u_long v;
+
+ pp = &lp->d_partitions[part];
+ cp = NULL;
+
+ v = 0;
+ NXTWORD(part_size_type[part],v);
+ if (v == 0 && part_size_type[part] != '*') {
+ fprintf(stderr,
+ "line %d: %s: bad partition size\n", lineno, cp);
+ return (1);
+ }
+ pp->p_size = v;
+
+ v = 0;
+ NXTWORD(part_offset_type[part],v);
+ if (v == 0 && part_offset_type[part] != '*' &&
+ part_offset_type[part] != '\0') {
+ fprintf(stderr,
+ "line %d: %s: bad partition offset\n", lineno, cp);
+ return (1);
+ }
+ pp->p_offset = v;
+ if (tp == NULL) {
+ fprintf(stderr, "line %d: missing file system type\n", lineno);
+ return (1);
+ }
+ cp = tp, tp = word(cp);
+ for (cpp = fstypenames; cpp < &fstypenames[FSMAXTYPES]; cpp++)
+ if (*cpp && !strcmp(*cpp, cp))
+ break;
+ if (*cpp != NULL) {
+ pp->p_fstype = cpp - fstypenames;
+ } else {
+ if (isdigit(*cp)) {
+ errno = 0;
+ v = strtoul(cp, &endp, 10);
+ if (errno != 0 || *endp != '\0')
+ v = FSMAXTYPES;
+ } else
+ v = FSMAXTYPES;
+ if (v >= FSMAXTYPES) {
+ fprintf(stderr,
+ "line %d: Warning, unknown file system type %s\n",
+ lineno, cp);
+ v = FS_UNUSED;
+ }
+ pp->p_fstype = v;
+ }
+
+ switch (pp->p_fstype) {
+ case FS_UNUSED:
+ case FS_BSDFFS:
+ case FS_BSDLFS:
+ /* accept defaults for fsize/frag/cpg */
+ if (tp) {
+ NXTNUM(pp->p_fsize);
+ if (pp->p_fsize == 0)
+ break;
+ NXTNUM(v);
+ pp->p_frag = v / pp->p_fsize;
+ if (tp != NULL)
+ NXTNUM(pp->p_cpg);
+ }
+ /* else default to 0's */
+ break;
+ default:
+ break;
+ }
+ return (0);
+}
+
+/*
+ * Check disklabel for errors and fill in
+ * derived fields according to supplied values.
+ */
+static int
+checklabel(struct disklabel *lp)
+{
+ struct partition *pp;
+ int i, errors = 0;
+ char part;
+ u_long base_offset, needed, total_size, total_percent, current_offset;
+ long free_space;
+ int seen_default_offset;
+ int hog_part;
+ int j;
+ struct partition *pp2;
+
+ if (lp == NULL)
+ lp = &lab;
+
+ if (allfields) {
+
+ if (lp->d_secsize == 0) {
+ fprintf(stderr, "sector size 0\n");
+ return (1);
+ }
+ if (lp->d_nsectors == 0) {
+ fprintf(stderr, "sectors/track 0\n");
+ return (1);
+ }
+ if (lp->d_ntracks == 0) {
+ fprintf(stderr, "tracks/cylinder 0\n");
+ return (1);
+ }
+ if (lp->d_ncylinders == 0) {
+ fprintf(stderr, "cylinders/unit 0\n");
+ errors++;
+ }
+ if (lp->d_rpm == 0)
+ warnx("revolutions/minute 0");
+ if (lp->d_secpercyl == 0)
+ lp->d_secpercyl = lp->d_nsectors * lp->d_ntracks;
+ if (lp->d_secperunit == 0)
+ lp->d_secperunit = lp->d_secpercyl * lp->d_ncylinders;
+ if (lp->d_bbsize == 0) {
+ fprintf(stderr, "boot block size 0\n");
+ errors++;
+ } else if (lp->d_bbsize % lp->d_secsize)
+ warnx("boot block size %% sector-size != 0");
+ if (lp->d_npartitions > MAXPARTITIONS) {
+ warnx("number of partitions (%lu) > MAXPARTITIONS (%d)",
+ (u_long)lp->d_npartitions, MAXPARTITIONS);
+ errors++;
+ }
+ if (lp->d_npartitions < DEFPARTITIONS) {
+ warnx("number of partitions (%lu) < DEFPARTITIONS (%d)",
+ (u_long)lp->d_npartitions, DEFPARTITIONS);
+ errors++;
+ }
+ } else {
+ struct disklabel *vl;
+
+ vl = getvirginlabel();
+ if (lp->d_secsize == 0)
+ lp->d_secsize = vl->d_secsize;
+ if (lp->d_nsectors == 0)
+ lp->d_nsectors = vl->d_nsectors;
+ if (lp->d_ntracks == 0)
+ lp->d_ntracks = vl->d_ntracks;
+ if (lp->d_ncylinders == 0)
+ lp->d_ncylinders = vl->d_ncylinders;
+ if (lp->d_rpm == 0)
+ lp->d_rpm = vl->d_rpm;
+ if (lp->d_interleave == 0)
+ lp->d_interleave = vl->d_interleave;
+ if (lp->d_secpercyl == 0)
+ lp->d_secpercyl = vl->d_secpercyl;
+ if (lp->d_secperunit == 0 ||
+ lp->d_secperunit > vl->d_secperunit)
+ lp->d_secperunit = vl->d_secperunit;
+ if (lp->d_bbsize == 0)
+ lp->d_bbsize = vl->d_bbsize;
+ if (lp->d_npartitions < DEFPARTITIONS ||
+ lp->d_npartitions > MAXPARTITIONS)
+ lp->d_npartitions = vl->d_npartitions;
+ }
+
+
+ /* first allocate space to the partitions, then offsets */
+ total_size = 0; /* in sectors */
+ total_percent = 0; /* in percent */
+ hog_part = -1;
+ /* find all fixed partitions */
+ for (i = 0; i < lp->d_npartitions; i++) {
+ pp = &lp->d_partitions[i];
+ if (part_set[i]) {
+ if (part_size_type[i] == '*') {
+ if (i == RAW_PART) {
+ pp->p_size = lp->d_secperunit;
+ } else {
+ if (hog_part != -1)
+ warnx("Too many '*' partitions (%c and %c)",
+ hog_part + 'a',i + 'a');
+ else
+ hog_part = i;
+ }
+ } else {
+ off_t size;
+
+ size = pp->p_size;
+ switch (part_size_type[i]) {
+ case '%':
+ total_percent += size;
+ break;
+ case 't':
+ case 'T':
+ size *= 1024ULL;
+ /* FALLTHROUGH */
+ case 'g':
+ case 'G':
+ size *= 1024ULL;
+ /* FALLTHROUGH */
+ case 'm':
+ case 'M':
+ size *= 1024ULL;
+ /* FALLTHROUGH */
+ case 'k':
+ case 'K':
+ size *= 1024ULL;
+ break;
+ case '\0':
+ break;
+ default:
+ warnx("unknown multiplier suffix '%c' for partition %c (should be K, M, G or T)",
+ part_size_type[i], i + 'a');
+ break;
+ }
+ /* don't count %'s yet */
+ if (part_size_type[i] != '%') {
+ /*
+ * for all not in sectors, convert to
+ * sectors
+ */
+ if (part_size_type[i] != '\0') {
+ if (size % lp->d_secsize != 0)
+ warnx("partition %c not an integer number of sectors",
+ i + 'a');
+ size /= lp->d_secsize;
+ pp->p_size = size;
+ }
+ /* else already in sectors */
+ if (i != RAW_PART)
+ total_size += size;
+ }
+ }
+ }
+ }
+
+ /* Find out the total free space, excluding the boot block area. */
+ base_offset = BBSIZE / secsize;
+ free_space = 0;
+ for (i = 0; i < lp->d_npartitions; i++) {
+ pp = &lp->d_partitions[i];
+ if (!part_set[i] || i == RAW_PART ||
+ part_size_type[i] == '%' || part_size_type[i] == '*')
+ continue;
+ if (pp->p_offset > base_offset)
+ free_space += pp->p_offset - base_offset;
+ if (pp->p_offset + pp->p_size > base_offset)
+ base_offset = pp->p_offset + pp->p_size;
+ }
+ if (base_offset < lp->d_secperunit)
+ free_space += lp->d_secperunit - base_offset;
+
+ /* handle % partitions - note %'s don't need to add up to 100! */
+ if (total_percent != 0) {
+ if (total_percent > 100) {
+ fprintf(stderr,"total percentage %lu is greater than 100\n",
+ total_percent);
+ errors++;
+ }
+
+ if (free_space > 0) {
+ for (i = 0; i < lp->d_npartitions; i++) {
+ pp = &lp->d_partitions[i];
+ if (part_set[i] && part_size_type[i] == '%') {
+ /* careful of overflows! and integer roundoff */
+ pp->p_size = ((double)pp->p_size/100) * free_space;
+ total_size += pp->p_size;
+
+ /* FIX we can lose a sector or so due to roundoff per
+ partition. A more complex algorithm could avoid that */
+ }
+ }
+ } else {
+ fprintf(stderr,
+ "%ld sectors available to give to '*' and '%%' partitions\n",
+ free_space);
+ errors++;
+ /* fix? set all % partitions to size 0? */
+ }
+ }
+ /* give anything remaining to the hog partition */
+ if (hog_part != -1) {
+ /*
+ * Find the range of offsets usable by '*' partitions around
+ * the hog partition and how much space they need.
+ */
+ needed = 0;
+ base_offset = BBSIZE / secsize;
+ for (i = hog_part - 1; i >= 0; i--) {
+ pp = &lp->d_partitions[i];
+ if (!part_set[i] || i == RAW_PART)
+ continue;
+ if (part_offset_type[i] == '*') {
+ needed += pp->p_size;
+ continue;
+ }
+ base_offset = pp->p_offset + pp->p_size;
+ break;
+ }
+ current_offset = lp->d_secperunit;
+ for (i = lp->d_npartitions - 1; i > hog_part; i--) {
+ pp = &lp->d_partitions[i];
+ if (!part_set[i] || i == RAW_PART)
+ continue;
+ if (part_offset_type[i] == '*') {
+ needed += pp->p_size;
+ continue;
+ }
+ current_offset = pp->p_offset;
+ }
+
+ if (current_offset - base_offset <= needed) {
+ fprintf(stderr, "Cannot find space for partition %c\n",
+ hog_part + 'a');
+ fprintf(stderr,
+ "Need more than %lu sectors between %lu and %lu\n",
+ needed, base_offset, current_offset);
+ errors++;
+ lp->d_partitions[hog_part].p_size = 0;
+ } else {
+ lp->d_partitions[hog_part].p_size = current_offset -
+ base_offset - needed;
+ total_size += lp->d_partitions[hog_part].p_size;
+ }
+ }
+
+ /* Now set the offsets for each partition */
+ current_offset = BBSIZE / secsize; /* in sectors */
+ seen_default_offset = 0;
+ for (i = 0; i < lp->d_npartitions; i++) {
+ part = 'a' + i;
+ pp = &lp->d_partitions[i];
+ if (part_set[i]) {
+ if (part_offset_type[i] == '*') {
+ if (i == RAW_PART) {
+ pp->p_offset = 0;
+ } else {
+ pp->p_offset = current_offset;
+ seen_default_offset = 1;
+ }
+ } else {
+ /* allow them to be out of order for old-style tables */
+ if (pp->p_offset < current_offset &&
+ seen_default_offset && i != RAW_PART &&
+ pp->p_fstype != FS_VINUM) {
+ fprintf(stderr,
+"Offset %ld for partition %c overlaps previous partition which ends at %lu\n",
+ (long)pp->p_offset,i+'a',current_offset);
+ fprintf(stderr,
+"Labels with any *'s for offset must be in ascending order by sector\n");
+ errors++;
+ } else if (pp->p_offset != current_offset &&
+ i != RAW_PART && seen_default_offset) {
+ /*
+ * this may give unneeded warnings if
+ * partitions are out-of-order
+ */
+ warnx(
+"Offset %ld for partition %c doesn't match expected value %ld",
+ (long)pp->p_offset, i + 'a', current_offset);
+ }
+ }
+ if (i != RAW_PART)
+ current_offset = pp->p_offset + pp->p_size;
+ }
+ }
+
+ for (i = 0; i < lp->d_npartitions; i++) {
+ part = 'a' + i;
+ pp = &lp->d_partitions[i];
+ if (pp->p_size == 0 && pp->p_offset != 0)
+ warnx("partition %c: size 0, but offset %lu",
+ part, (u_long)pp->p_offset);
+#ifdef notdef
+ if (pp->p_size % lp->d_secpercyl)
+ warnx("partition %c: size %% cylinder-size != 0",
+ part);
+ if (pp->p_offset % lp->d_secpercyl)
+ warnx("partition %c: offset %% cylinder-size != 0",
+ part);
+#endif
+ if (pp->p_offset > lp->d_secperunit) {
+ fprintf(stderr,
+ "partition %c: offset past end of unit\n", part);
+ errors++;
+ }
+ if (pp->p_offset + pp->p_size > lp->d_secperunit) {
+ fprintf(stderr,
+ "partition %c: partition extends past end of unit\n",
+ part);
+ errors++;
+ }
+ if (i == RAW_PART) {
+ if (pp->p_fstype != FS_UNUSED)
+ warnx("partition %c is not marked as unused!",part);
+ if (pp->p_offset != 0)
+ warnx("partition %c doesn't start at 0!",part);
+ if (pp->p_size != lp->d_secperunit)
+ warnx("partition %c doesn't cover the whole unit!",part);
+
+ if ((pp->p_fstype != FS_UNUSED) || (pp->p_offset != 0) ||
+ (pp->p_size != lp->d_secperunit)) {
+ warnx("An incorrect partition %c may cause problems for "
+ "standard system utilities",part);
+ }
+ }
+
+ /* check for overlaps */
+ /* this will check for all possible overlaps once and only once */
+ for (j = 0; j < i; j++) {
+ pp2 = &lp->d_partitions[j];
+ if (j != RAW_PART && i != RAW_PART &&
+ pp->p_fstype != FS_VINUM &&
+ pp2->p_fstype != FS_VINUM &&
+ part_set[i] && part_set[j]) {
+ if (pp2->p_offset < pp->p_offset + pp->p_size &&
+ (pp2->p_offset + pp2->p_size > pp->p_offset ||
+ pp2->p_offset >= pp->p_offset)) {
+ fprintf(stderr,"partitions %c and %c overlap!\n",
+ j + 'a', i + 'a');
+ errors++;
+ }
+ }
+ }
+ }
+ for (; i < lp->d_npartitions; i++) {
+ part = 'a' + i;
+ pp = &lp->d_partitions[i];
+ if (pp->p_size || pp->p_offset)
+ warnx("unused partition %c: size %d offset %lu",
+ 'a' + i, pp->p_size, (u_long)pp->p_offset);
+ }
+ return (errors);
+}
+
+/*
+ * When operating on a "virgin" disk, try getting an initial label
+ * from the associated device driver. This might work for all device
+ * drivers that are able to fetch some initial device parameters
+ * without even having access to a (BSD) disklabel, like SCSI disks,
+ * most IDE drives, or vn devices.
+ *
+ * The device name must be given in its "canonical" form.
+ */
+static struct disklabel *
+getvirginlabel(void)
+{
+ static struct disklabel loclab;
+ struct partition *dp;
+ int f;
+ u_int u;
+
+ if ((f = open(specname, O_RDONLY)) == -1) {
+ warn("cannot open %s", specname);
+ return (NULL);
+ }
+
+ if (is_file)
+ get_file_parms(f);
+ else {
+ mediasize = g_mediasize(f);
+ secsize = g_sectorsize(f);
+ if (secsize < 0 || mediasize < 0) {
+ close (f);
+ return (NULL);
+ }
+ }
+ memset(&loclab, 0, sizeof loclab);
+ loclab.d_magic = DISKMAGIC;
+ loclab.d_magic2 = DISKMAGIC;
+ loclab.d_secsize = secsize;
+ loclab.d_secperunit = mediasize / secsize;
+
+ /*
+ * Nobody in these enlightened days uses the CHS geometry for
+ * anything, but nonetheless try to get it right. If we fail
+ * to get any good ideas from the device, construct something
+ * which is IBM-PC friendly.
+ */
+ if (ioctl(f, DIOCGFWSECTORS, &u) == 0)
+ loclab.d_nsectors = u;
+ else
+ loclab.d_nsectors = 63;
+ if (ioctl(f, DIOCGFWHEADS, &u) == 0)
+ loclab.d_ntracks = u;
+ else if (loclab.d_secperunit <= 63*1*1024)
+ loclab.d_ntracks = 1;
+ else if (loclab.d_secperunit <= 63*16*1024)
+ loclab.d_ntracks = 16;
+ else
+ loclab.d_ntracks = 255;
+ loclab.d_secpercyl = loclab.d_ntracks * loclab.d_nsectors;
+ loclab.d_ncylinders = loclab.d_secperunit / loclab.d_secpercyl;
+ loclab.d_npartitions = DEFPARTITIONS;
+
+ /* Various (unneeded) compat stuff */
+ loclab.d_rpm = 3600;
+ loclab.d_bbsize = BBSIZE;
+ loclab.d_interleave = 1;
+ strncpy(loclab.d_typename, "amnesiac",
+ sizeof(loclab.d_typename));
+
+ dp = &loclab.d_partitions[RAW_PART];
+ dp->p_size = loclab.d_secperunit;
+ loclab.d_checksum = dkcksum(&loclab);
+ close (f);
+ return (&loclab);
+}
+
+static void
+usage(void)
+{
+
+ fprintf(stderr,
+ "%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n",
+ "usage: bsdlabel disk",
+ "\t\t(to read label)",
+ " bsdlabel -w [-n] [-m machine] disk [type]",
+ "\t\t(to write label with existing boot program)",
+ " bsdlabel -e [-n] [-m machine] disk",
+ "\t\t(to edit label)",
+ " bsdlabel -R [-n] [-m machine] disk protofile",
+ "\t\t(to restore label with existing boot program)",
+ " bsdlabel -B [-b boot] [-m machine] disk",
+ "\t\t(to install boot program with existing on-disk label)",
+ " bsdlabel -w -B [-n] [-b boot] [-m machine] disk [type]",
+ "\t\t(to write label and install boot program)",
+ " bsdlabel -R -B [-n] [-b boot] [-m machine] disk protofile",
+ "\t\t(to restore label and install boot program)"
+ );
+ exit(1);
+}
diff --git a/sbin/bsdlabel/pathnames.h b/sbin/bsdlabel/pathnames.h
new file mode 100644
index 0000000..c8a9ef3
--- /dev/null
+++ b/sbin/bsdlabel/pathnames.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 1989, 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.
+ * 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.
+ *
+ * @(#)pathnames.h 8.1 (Berkeley) 6/5/93
+ * $FreeBSD$
+ */
+
+#include <paths.h>
+
+#define _PATH_BOOTDIR "/boot"
+#define PATH_TMPFILE "/tmp/EdDk.XXXXXXXXXX"
diff --git a/sbin/camcontrol/Makefile b/sbin/camcontrol/Makefile
new file mode 100644
index 0000000..f23ef52
--- /dev/null
+++ b/sbin/camcontrol/Makefile
@@ -0,0 +1,17 @@
+# $FreeBSD$
+
+PROG= camcontrol
+SRCS= camcontrol.c util.c
+.if !defined(RELEASE_CRUNCH)
+SRCS+= fwdownload.c modeedit.c persist.c progress.c
+.else
+CFLAGS+= -DMINIMALISTIC
+.endif
+# This is verboten
+.if ${MACHINE_CPUARCH} == "arm"
+WARNS?= 3
+.endif
+LIBADD= cam sbuf util
+MAN= camcontrol.8
+
+.include <bsd.prog.mk>
diff --git a/sbin/camcontrol/camcontrol.8 b/sbin/camcontrol/camcontrol.8
new file mode 100644
index 0000000..15e1862
--- /dev/null
+++ b/sbin/camcontrol/camcontrol.8
@@ -0,0 +1,2102 @@
+.\"
+.\" Copyright (c) 1998, 1999, 2000, 2002, 2005, 2006, 2007 Kenneth D. Merry.
+.\" 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 AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd March 19, 2015
+.Dt CAMCONTROL 8
+.Os
+.Sh NAME
+.Nm camcontrol
+.Nd CAM control program
+.Sh SYNOPSIS
+.Nm
+.Aq Ar command
+.Op device id
+.Op generic args
+.Op command args
+.Nm
+.Ic devlist
+.Op Fl b
+.Op Fl v
+.Nm
+.Ic periphlist
+.Op device id
+.Op Fl n Ar dev_name
+.Op Fl u Ar unit_number
+.Nm
+.Ic tur
+.Op device id
+.Op generic args
+.Nm
+.Ic inquiry
+.Op device id
+.Op generic args
+.Op Fl D
+.Op Fl S
+.Op Fl R
+.Nm
+.Ic identify
+.Op device id
+.Op generic args
+.Op Fl v
+.Nm
+.Ic reportluns
+.Op device id
+.Op generic args
+.Op Fl c
+.Op Fl l
+.Op Fl r Ar reporttype
+.Nm
+.Ic readcap
+.Op device id
+.Op generic args
+.Op Fl b
+.Op Fl h
+.Op Fl H
+.Op Fl N
+.Op Fl q
+.Op Fl s
+.Nm
+.Ic start
+.Op device id
+.Op generic args
+.Nm
+.Ic stop
+.Op device id
+.Op generic args
+.Nm
+.Ic load
+.Op device id
+.Op generic args
+.Nm
+.Ic eject
+.Op device id
+.Op generic args
+.Nm
+.Ic rescan
+.Aq all | bus Ns Op :target:lun
+.Nm
+.Ic reset
+.Aq all | bus Ns Op :target:lun
+.Nm
+.Ic defects
+.Op device id
+.Op generic args
+.Aq Fl f Ar format
+.Op Fl P
+.Op Fl G
+.Op Fl q
+.Op Fl s
+.Op Fl S Ar offset
+.Op Fl X
+.Nm
+.Ic modepage
+.Op device id
+.Op generic args
+.Aq Fl m Ar page | Fl l
+.Op Fl P Ar pgctl
+.Op Fl b | Fl e
+.Op Fl d
+.Nm
+.Ic cmd
+.Op device id
+.Op generic args
+.Aq Fl a Ar cmd Op args
+.Aq Fl c Ar cmd Op args
+.Op Fl d
+.Op Fl f
+.Op Fl i Ar len Ar fmt
+.Bk -words
+.Op Fl o Ar len Ar fmt Op args
+.Op Fl r Ar fmt
+.Ek
+.Nm
+.Ic smpcmd
+.Op device id
+.Op generic args
+.Aq Fl r Ar len Ar fmt Op args
+.Aq Fl R Ar len Ar fmt Op args
+.Nm
+.Ic smprg
+.Op device id
+.Op generic args
+.Op Fl l
+.Nm
+.Ic smppc
+.Op device id
+.Op generic args
+.Aq Fl p Ar phy
+.Op Fl l
+.Op Fl o Ar operation
+.Op Fl d Ar name
+.Op Fl m Ar rate
+.Op Fl M Ar rate
+.Op Fl T Ar pp_timeout
+.Op Fl a Ar enable|disable
+.Op Fl A Ar enable|disable
+.Op Fl s Ar enable|disable
+.Op Fl S Ar enable|disable
+.Nm
+.Ic smpphylist
+.Op device id
+.Op generic args
+.Op Fl l
+.Op Fl q
+.Nm
+.Ic smpmaninfo
+.Op device id
+.Op generic args
+.Op Fl l
+.Nm
+.Ic debug
+.Op Fl I
+.Op Fl P
+.Op Fl T
+.Op Fl S
+.Op Fl X
+.Op Fl c
+.Op Fl p
+.Aq all|off|bus Ns Op :target Ns Op :lun
+.Nm
+.Ic tags
+.Op device id
+.Op generic args
+.Op Fl N Ar tags
+.Op Fl q
+.Op Fl v
+.Nm
+.Ic negotiate
+.Op device id
+.Op generic args
+.Op Fl c
+.Op Fl D Ar enable|disable
+.Op Fl M Ar mode
+.Op Fl O Ar offset
+.Op Fl q
+.Op Fl R Ar syncrate
+.Op Fl T Ar enable|disable
+.Op Fl U
+.Op Fl W Ar bus_width
+.Op Fl v
+.Nm
+.Ic format
+.Op device id
+.Op generic args
+.Op Fl q
+.Op Fl r
+.Op Fl w
+.Op Fl y
+.Nm
+.Ic sanitize
+.Op device id
+.Op generic args
+.Aq Fl a Ar overwrite | block | crypto | exitfailure
+.Op Fl c Ar passes
+.Op Fl I
+.Op Fl P Ar pattern
+.Op Fl q
+.Op Fl U
+.Op Fl r
+.Op Fl w
+.Op Fl y
+.Nm
+.Ic idle
+.Op device id
+.Op generic args
+.Op Fl t Ar time
+.Nm
+.Ic standby
+.Op device id
+.Op generic args
+.Op Fl t Ar time
+.Nm
+.Ic sleep
+.Op device id
+.Op generic args
+.Nm
+.Ic apm
+.Op device id
+.Op generic args
+.Op Fl l Ar level
+.Nm
+.Ic aam
+.Op device id
+.Op generic args
+.Op Fl l Ar level
+.Nm
+.Ic fwdownload
+.Op device id
+.Op generic args
+.Aq Fl f Ar fw_image
+.Op Fl y
+.Op Fl s
+.Nm
+.Ic security
+.Op device id
+.Op generic args
+.Op Fl d Ar pwd
+.Op Fl e Ar pwd
+.Op Fl f
+.Op Fl h Ar pwd
+.Op Fl k Ar pwd
+.Op Fl l Ar high|maximum
+.Op Fl q
+.Op Fl s Ar pwd
+.Op Fl T Ar timeout
+.Op Fl U Ar user|master
+.Op Fl y
+.Nm
+.Ic hpa
+.Op device id
+.Op generic args
+.Op Fl f
+.Op Fl l
+.Op Fl P
+.Op Fl p Ar pwd
+.Op Fl q
+.Op Fl s Ar max_sectors
+.Op Fl U Ar pwd
+.Op Fl y
+.Nm
+.Ic persist
+.Op device id
+.Op generic args
+.Aq Fl i Ar action | Fl o Ar action
+.Op Fl a
+.Op Fl I Ar trans_id
+.Op Fl k Ar key
+.Op Fl K Ar sa_key
+.Op Fl p
+.Op Fl R Ar rel_tgt_port
+.Op Fl s Ar scope
+.Op Fl S
+.Op Fl T Ar res_type
+.Op Fl U
+.Nm
+.Ic help
+.Sh DESCRIPTION
+The
+.Nm
+utility is designed to provide a way for users to access and control the
+.Fx
+CAM subsystem.
+.Pp
+The
+.Nm
+utility
+can cause a loss of data and/or system crashes if used improperly.
+Even
+expert users are encouraged to exercise caution when using this command.
+Novice users should stay away from this utility.
+.Pp
+The
+.Nm
+utility has a number of primary functions, many of which support an optional
+device identifier.
+A device identifier can take one of three forms:
+.Bl -tag -width 14n
+.It deviceUNIT
+Specify a device name and unit number combination, like "da5" or "cd3".
+.It bus:target
+Specify a bus number and target id.
+The bus number can be determined from
+the output of
+.Dq camcontrol devlist .
+The lun defaults to 0.
+.It bus:target:lun
+Specify the bus, target and lun for a device.
+(e.g.\& 1:2:0)
+.El
+.Pp
+The device identifier, if it is specified,
+.Em must
+come immediately after the function name, and before any generic or
+function-specific arguments.
+Note that the
+.Fl n
+and
+.Fl u
+arguments described below will override any device name or unit number
+specified beforehand.
+The
+.Fl n
+and
+.Fl u
+arguments will
+.Em not
+override a specified bus:target or bus:target:lun, however.
+.Pp
+Most of the
+.Nm
+primary functions support these generic arguments:
+.Bl -tag -width 14n
+.It Fl C Ar count
+SCSI command retry count.
+In order for this to work, error recovery
+.Pq Fl E
+must be turned on.
+.It Fl E
+Instruct the kernel to perform generic SCSI error recovery for the given
+command.
+This is needed in order for the retry count
+.Pq Fl C
+to be honored.
+Other than retrying commands, the generic error recovery in
+the code will generally attempt to spin up drives that are not spinning.
+It may take some other actions, depending upon the sense code returned from
+the command.
+.It Fl n Ar dev_name
+Specify the device type to operate on, e.g.\& "da", "cd".
+.It Fl t Ar timeout
+SCSI command timeout in seconds.
+This overrides the default timeout for
+any given command.
+.It Fl u Ar unit_number
+Specify the device unit number, e.g.\& "1", "5".
+.It Fl v
+Be verbose, print out sense information for failed SCSI commands.
+.El
+.Pp
+Primary command functions:
+.Bl -tag -width periphlist
+.It Ic devlist
+List all physical devices (logical units) attached to the CAM subsystem.
+This also includes a list of peripheral drivers attached to each device.
+With the
+.Fl v
+argument, SCSI bus number, adapter name and unit numbers are printed as
+well.
+On the other hand, with the
+.Fl b
+argument, only the bus adapter, and unit information will be printed, and
+device information will be omitted.
+.It Ic periphlist
+List all peripheral drivers attached to a given physical device (logical
+unit).
+.It Ic tur
+Send the SCSI test unit ready (0x00) command to the given device.
+The
+.Nm
+utility will report whether the device is ready or not.
+.It Ic inquiry
+Send a SCSI inquiry command (0x12) to a device.
+By default,
+.Nm
+will print out the standard inquiry data, device serial number, and
+transfer rate information.
+The user can specify that only certain types of
+inquiry data be printed:
+.Bl -tag -width 4n
+.It Fl D
+Get the standard inquiry data.
+.It Fl S
+Print out the serial number.
+If this flag is the only one specified,
+.Nm
+will not print out "Serial Number" before the value returned by the drive.
+This is to aid in script writing.
+.It Fl R
+Print out transfer rate information.
+.El
+.It Ic identify
+Send a ATA identify command (0xec) to a device.
+.It Ic reportluns
+Send the SCSI REPORT LUNS (0xA0) command to the given device.
+By default,
+.Nm
+will print out the list of logical units (LUNs) supported by the target device.
+There are a couple of options to modify the output:
+.Bl -tag -width 14n
+.It Fl c
+Just print out a count of LUNs, not the actual LUN numbers.
+.It Fl l
+Just print out the LUNs, and do not print out the count.
+.It Fl r Ar reporttype
+Specify the type of report to request from the target:
+.Bl -tag -width 012345678
+.It default
+Return the default report.
+This is the
+.Nm
+default.
+Most targets will support this report if they support the REPORT LUNS
+command.
+.It wellknown
+Return only well known LUNs.
+.It all
+Return all available LUNs.
+.El
+.El
+.Pp
+.Nm
+will try to print out LUN numbers in a reasonable format.
+It can understand the peripheral, flat, LUN and extended LUN formats.
+.It Ic readcap
+Send the SCSI READ CAPACITY command to the given device and display
+the results.
+If the device is larger than 2TB, the SCSI READ CAPACITY (16) service
+action will be sent to obtain the full size of the device.
+By default,
+.Nm
+will print out the last logical block of the device, and the blocksize of
+the device in bytes.
+To modify the output format, use the following options:
+.Bl -tag -width 5n
+.It Fl b
+Just print out the blocksize, not the last block or device size.
+This cannot be used with
+.Fl N
+or
+.Fl s .
+.It Fl h
+Print out the device size in human readable (base 2, 1K == 1024) format.
+This implies
+.Fl N
+and cannot be used with
+.Fl q
+or
+.Fl b .
+.It Fl H
+Print out the device size in human readable (base 10, 1K == 1000) format.
+.It Fl N
+Print out the number of blocks in the device instead of the last logical
+block.
+.It Fl q
+Quiet, print out the numbers only (separated by a comma if
+.Fl b
+or
+.Fl s
+are not specified).
+.It Fl s
+Print out the last logical block or the size of the device only, and omit
+the blocksize.
+.El
+.It Ic start
+Send the SCSI Start/Stop Unit (0x1B) command to the given device with the
+start bit set.
+.It Ic stop
+Send the SCSI Start/Stop Unit (0x1B) command to the given device with the
+start bit cleared.
+.It Ic load
+Send the SCSI Start/Stop Unit (0x1B) command to the given device with the
+start bit set and the load/eject bit set.
+.It Ic eject
+Send the SCSI Start/Stop Unit (0x1B) command to the given device with the
+start bit cleared and the load/eject bit set.
+.It Ic rescan
+Tell the kernel to scan all busses in the system (with the
+.Ar all
+argument), the given bus (XPT_SCAN_BUS), or bus:target:lun
+(XPT_SCAN_LUN) for new devices or devices that have gone away.
+The user
+may specify a scan of all busses, a single bus, or a lun.
+Scanning all luns
+on a target is not supported.
+.It Ic reset
+Tell the kernel to reset all busses in the system (with the
+.Ar all
+argument) or the given bus (XPT_RESET_BUS) by issuing a SCSI bus
+reset for that bus, or to reset the given bus:target:lun
+(XPT_RESET_DEV), typically by issuing a BUS DEVICE RESET message after
+connecting to that device.
+Note that this can have a destructive impact
+on the system.
+.It Ic defects
+Send the
+.Tn SCSI
+READ DEFECT DATA (10) command (0x37) or the
+.Tn SCSI
+READ DEFECT DATA (12) command (0xB7) to the given device, and
+print out any combination of: the total number of defects, the primary
+defect list (PLIST), and the grown defect list (GLIST).
+.Bl -tag -width 11n
+.It Fl f Ar format
+Specify the requested format of the defect list.
+The format argument is
+required.
+Most drives support the physical sector format.
+Some drives
+support the logical block format.
+Many drives, if they do not support the
+requested format, return the data in an alternate format, along with sense
+information indicating that the requested data format is not supported.
+The
+.Nm
+utility
+attempts to detect this, and print out whatever format the drive returns.
+If the drive uses a non-standard sense code to report that it does not
+support the requested format,
+.Nm
+will probably see the error as a failure to complete the request.
+.Pp
+The format options are:
+.Bl -tag -width 9n
+.It block
+Print out the list as logical blocks.
+This is limited to 32-bit block sizes, and isn't supported by many modern
+drives.
+.It longblock
+Print out the list as logical blocks.
+This option uses a 64-bit block size.
+.It bfi
+Print out the list in bytes from index format.
+.It extbfi
+Print out the list in extended bytes from index format.
+The extended format allows for ranges of blocks to be printed.
+.It phys
+Print out the list in physical sector format.
+Most drives support this format.
+.It extphys
+Print out the list in extended physical sector format.
+The extended format allows for ranges of blocks to be printed.
+.El
+.It Fl G
+Print out the grown defect list.
+This is a list of bad blocks that have
+been remapped since the disk left the factory.
+.It Fl P
+Print out the primary defect list.
+This is the list of defects that were present in the factory.
+.It Fl q
+When printing status information with
+.Fl s ,
+only print the number of defects.
+.It Fl s
+Just print the number of defects, not the list of defects.
+.It Fl S Ar offset
+Specify the starting offset into the defect list.
+This implies using the
+.Tn SCSI
+READ DEFECT DATA (12) command, as the 10 byte version of the command
+doesn't support the address descriptor index field.
+Not all drives support the 12 byte command, and some drives that support
+the 12 byte command don't support the address descriptor index field.
+.It Fl X
+Print out defects in hexadecimal (base 16) form instead of base 10 form.
+.El
+.Pp
+If neither
+.Fl P
+nor
+.Fl G
+is specified,
+.Nm
+will print out the number of defects given in the READ DEFECT DATA header
+returned from the drive.
+Some drives will report 0 defects if neither the primary or grown defect
+lists are requested.
+.It Ic modepage
+Allows the user to display and optionally edit a SCSI mode page.
+The mode
+page formats are located in
+.Pa /usr/share/misc/scsi_modes .
+This can be overridden by specifying a different file in the
+.Ev SCSI_MODES
+environment variable.
+The
+.Ic modepage
+command takes several arguments:
+.Bl -tag -width 12n
+.It Fl d
+Disable block descriptors for mode sense.
+.It Fl b
+Displays mode page data in binary format.
+.It Fl e
+This flag allows the user to edit values in the mode page.
+The user may
+either edit mode page values with the text editor pointed to by his
+.Ev EDITOR
+environment variable, or supply mode page values via standard input, using
+the same format that
+.Nm
+uses to display mode page values.
+The editor will be invoked if
+.Nm
+detects that standard input is terminal.
+.It Fl l
+Lists all available mode pages.
+.It Fl m Ar mode_page
+This specifies the number of the mode page the user would like to view
+and/or edit.
+This argument is mandatory unless
+.Fl l
+is specified.
+.It Fl P Ar pgctl
+This allows the user to specify the page control field.
+Possible values are:
+.Bl -tag -width xxx -compact
+.It 0
+Current values
+.It 1
+Changeable values
+.It 2
+Default values
+.It 3
+Saved values
+.El
+.El
+.It Ic cmd
+Allows the user to send an arbitrary ATA or SCSI CDB to any device.
+The
+.Ic cmd
+function requires the
+.Fl c
+argument to specify SCSI CDB or the
+.Fl a
+argument to specify ATA Command Block registers values.
+Other arguments are optional, depending on
+the command type.
+The command and data specification syntax is documented
+in
+.Xr cam_cdbparse 3 .
+NOTE: If the CDB specified causes data to be transferred to or from the
+SCSI device in question, you MUST specify either
+.Fl i
+or
+.Fl o .
+.Bl -tag -width 17n
+.It Fl a Ar cmd Op args
+This specifies the content of 12 ATA Command Block registers (command,
+features, lba_low, lba_mid, lba_high, device, lba_low_exp, lba_mid_exp.
+lba_high_exp, features_exp, sector_count, sector_count_exp).
+.It Fl c Ar cmd Op args
+This specifies the SCSI CDB.
+SCSI CDBs may be 6, 10, 12 or 16 bytes.
+.It Fl d
+Specifies DMA protocol to be used for ATA command.
+.It Fl f
+Specifies FPDMA (NCQ) protocol to be used for ATA command.
+.It Fl i Ar len Ar fmt
+This specifies the amount of data to read, and how it should be displayed.
+If the format is
+.Sq - ,
+.Ar len
+bytes of data will be read from the device and written to standard output.
+.It Fl o Ar len Ar fmt Op args
+This specifies the amount of data to be written to a device, and the data
+that is to be written.
+If the format is
+.Sq - ,
+.Ar len
+bytes of data will be read from standard input and written to the device.
+.It Fl r Ar fmt
+This specifies that 11 result ATA Command Block registers should be displayed
+(status, error, lba_low, lba_mid, lba_high, device, lba_low_exp, lba_mid_exp,
+lba_high_exp, sector_count, sector_count_exp), and how.
+If the format is
+.Sq - ,
+11 result registers will be written to standard output in hex.
+.El
+.It Ic smpcmd
+Allows the user to send an arbitrary Serial
+Management Protocol (SMP) command to a device.
+The
+.Ic smpcmd
+function requires the
+.Fl r
+argument to specify the SMP request to be sent, and the
+.Fl R
+argument to specify the format of the SMP response.
+The syntax for the SMP request and response arguments is documented in
+.Xr cam_cdbparse 3 .
+.Pp
+Note that SAS adapters that support SMP passthrough (at least the currently
+known adapters) do not accept CRC bytes from the user in the request and do
+not pass CRC bytes back to the user in the response.
+Therefore users should not include the CRC bytes in the length of the
+request and not expect CRC bytes to be returned in the response.
+.Bl -tag -width 17n
+.It Fl r Ar len Ar fmt Op args
+This specifies the size of the SMP request, without the CRC bytes, and the
+SMP request format.
+If the format is
+.Sq - ,
+.Ar len
+bytes of data will be read from standard input and written as the SMP
+request.
+.It Fl R Ar len Ar fmt Op args
+This specifies the size of the buffer allocated for the SMP response, and
+the SMP response format.
+If the format is
+.Sq - ,
+.Ar len
+bytes of data will be allocated for the response and the response will be
+written to standard output.
+.El
+.It Ic smprg
+Allows the user to send the Serial Management Protocol (SMP) Report General
+command to a device.
+.Nm
+will display the data returned by the Report General command.
+If the SMP target supports the long response format, the additional data
+will be requested and displayed automatically.
+.Bl -tag -width 8n
+.It Fl l
+Request the long response format only.
+Not all SMP targets support the long response format.
+This option causes
+.Nm
+to skip sending the initial report general request without the long bit set
+and only issue a report general request with the long bit set.
+.El
+.It Ic smppc
+Allows the user to issue the Serial Management Protocol (SMP) PHY Control
+command to a device.
+This function should be used with some caution, as it can render devices
+inaccessible, and could potentially cause data corruption as well.
+The
+.Fl p
+argument is required to specify the PHY to operate on.
+.Bl -tag -width 17n
+.It Fl p Ar phy
+Specify the PHY to operate on.
+This argument is required.
+.It Fl l
+Request the long request/response format.
+Not all SMP targets support the long response format.
+For the PHY Control command, this currently only affects whether the
+request length is set to a value other than 0.
+.It Fl o Ar operation
+Specify a PHY control operation.
+Only one
+.Fl o
+operation may be specified.
+The operation may be specified numerically (in decimal, hexadecimal, or octal)
+or one of the following operation names may be specified:
+.Bl -tag -width 16n
+.It nop
+No operation.
+It is not necessary to specify this argument.
+.It linkreset
+Send the LINK RESET command to the phy.
+.It hardreset
+Send the HARD RESET command to the phy.
+.It disable
+Send the DISABLE command to the phy.
+Note that the LINK RESET or HARD RESET commands should re-enable the phy.
+.It clearerrlog
+Send the CLEAR ERROR LOG command.
+This clears the error log counters for the specified phy.
+.It clearaffiliation
+Send the CLEAR AFFILIATION command.
+This clears the affiliation from the STP initiator port with the same SAS
+address as the SMP initiator that requests the clear operation.
+.It sataportsel
+Send the TRANSMIT SATA PORT SELECTION SIGNAL command to the phy.
+This will cause a SATA port selector to use the given phy as its active phy
+and make the other phy inactive.
+.It clearitnl
+Send the CLEAR STP I_T NEXUS LOSS command to the PHY.
+.It setdevname
+Send the SET ATTACHED DEVICE NAME command to the PHY.
+This requires the
+.Fl d
+argument to specify the device name.
+.El
+.It Fl d Ar name
+Specify the attached device name.
+This option is needed with the
+.Fl o Ar setdevname
+phy operation.
+The name is a 64-bit number, and can be specified in decimal, hexadecimal
+or octal format.
+.It Fl m Ar rate
+Set the minimum physical link rate for the phy.
+This is a numeric argument.
+Currently known link rates are:
+.Bl -tag -width 5n
+.It 0x0
+Do not change current value.
+.It 0x8
+1.5 Gbps
+.It 0x9
+3 Gbps
+.It 0xa
+6 Gbps
+.El
+.Pp
+Other values may be specified for newer physical link rates.
+.It Fl M Ar rate
+Set the maximum physical link rate for the phy.
+This is a numeric argument.
+See the
+.Fl m
+argument description for known link rate arguments.
+.It Fl T Ar pp_timeout
+Set the partial pathway timeout value, in microseconds.
+See the
+.Tn ANSI
+.Tn SAS
+Protocol Layer (SPL)
+specification for more information on this field.
+.It Fl a Ar enable|disable
+Enable or disable SATA slumber phy power conditions.
+.It Fl A Ar enable|disable
+Enable or disable SATA partial power conditions.
+.It Fl s Ar enable|disable
+Enable or disable SAS slumber phy power conditions.
+.It Fl S Ar enable|disable
+Enable or disable SAS partial phy power conditions.
+.El
+.It Ic smpphylist
+List phys attached to a SAS expander, the address of the end device
+attached to the phy, and the inquiry data for that device and peripheral
+devices attached to that device.
+The inquiry data and peripheral devices are displayed if available.
+.Bl -tag -width 5n
+.It Fl l
+Turn on the long response format for the underlying SMP commands used for
+this command.
+.It Fl q
+Only print out phys that are attached to a device in the CAM EDT (Existing
+Device Table).
+.El
+.It Ic smpmaninfo
+Send the SMP Report Manufacturer Information command to the device and
+display the response.
+.Bl -tag -width 5n
+.It Fl l
+Turn on the long response format for the underlying SMP commands used for
+this command.
+.El
+.It Ic debug
+Turn on CAM debugging printfs in the kernel.
+This requires options CAMDEBUG
+in your kernel config file.
+WARNING: enabling debugging printfs currently
+causes an EXTREME number of kernel printfs.
+You may have difficulty
+turning off the debugging printfs once they start, since the kernel will be
+busy printing messages and unable to service other requests quickly.
+The
+.Ic debug
+function takes a number of arguments:
+.Bl -tag -width 18n
+.It Fl I
+Enable CAM_DEBUG_INFO printfs.
+.It Fl P
+Enable CAM_DEBUG_PERIPH printfs.
+.It Fl T
+Enable CAM_DEBUG_TRACE printfs.
+.It Fl S
+Enable CAM_DEBUG_SUBTRACE printfs.
+.It Fl X
+Enable CAM_DEBUG_XPT printfs.
+.It Fl c
+Enable CAM_DEBUG_CDB printfs.
+This will cause the kernel to print out the
+SCSI CDBs sent to the specified device(s).
+.It Fl p
+Enable CAM_DEBUG_PROBE printfs.
+.It all
+Enable debugging for all devices.
+.It off
+Turn off debugging for all devices
+.It bus Ns Op :target Ns Op :lun
+Turn on debugging for the given bus, target or lun.
+If the lun or target
+and lun are not specified, they are wildcarded.
+(i.e., just specifying a
+bus turns on debugging printfs for all devices on that bus.)
+.El
+.It Ic tags
+Show or set the number of "tagged openings" or simultaneous transactions
+we attempt to queue to a particular device.
+By default, the
+.Ic tags
+command, with no command-specific arguments (i.e., only generic arguments)
+prints out the "soft" maximum number of transactions that can be queued to
+the device in question.
+For more detailed information, use the
+.Fl v
+argument described below.
+.Bl -tag -width 7n
+.It Fl N Ar tags
+Set the number of tags for the given device.
+This must be between the
+minimum and maximum number set in the kernel quirk table.
+The default for
+most devices that support tagged queueing is a minimum of 2 and a maximum
+of 255.
+The minimum and maximum values for a given device may be
+determined by using the
+.Fl v
+switch.
+The meaning of the
+.Fl v
+switch for this
+.Nm
+subcommand is described below.
+.It Fl q
+Be quiet, and do not report the number of tags.
+This is generally used when
+setting the number of tags.
+.It Fl v
+The verbose flag has special functionality for the
+.Em tags
+argument.
+It causes
+.Nm
+to print out the tagged queueing related fields of the XPT_GDEV_TYPE CCB:
+.Bl -tag -width 13n
+.It dev_openings
+This is the amount of capacity for transactions queued to a given device.
+.It dev_active
+This is the number of transactions currently queued to a device.
+.It devq_openings
+This is the kernel queue space for transactions.
+This count usually mirrors
+dev_openings except during error recovery operations when
+the device queue is frozen (device is not allowed to receive
+commands), the number of dev_openings is reduced, or transaction
+replay is occurring.
+.It devq_queued
+This is the number of transactions waiting in the kernel queue for capacity
+on the device.
+This number is usually zero unless error recovery is in
+progress.
+.It held
+The held count is the number of CCBs held by peripheral drivers that have
+either just been completed or are about to be released to the transport
+layer for service by a device.
+Held CCBs reserve capacity on a given
+device.
+.It mintags
+This is the current "hard" minimum number of transactions that can be
+queued to a device at once.
+The
+.Ar dev_openings
+value above cannot go below this number.
+The default value for
+.Ar mintags
+is 2, although it may be set higher or lower for various devices.
+.It maxtags
+This is the "hard" maximum number of transactions that can be queued to a
+device at one time.
+The
+.Ar dev_openings
+value cannot go above this number.
+The default value for
+.Ar maxtags
+is 255, although it may be set higher or lower for various devices.
+.El
+.El
+.It Ic negotiate
+Show or negotiate various communication parameters.
+Some controllers may
+not support setting or changing some of these values.
+For instance, the
+Adaptec 174x controllers do not support changing a device's sync rate or
+offset.
+The
+.Nm
+utility
+will not attempt to set the parameter if the controller indicates that it
+does not support setting the parameter.
+To find out what the controller
+supports, use the
+.Fl v
+flag.
+The meaning of the
+.Fl v
+flag for the
+.Ic negotiate
+command is described below.
+Also, some controller drivers do not support
+setting negotiation parameters, even if the underlying controller supports
+negotiation changes.
+Some controllers, such as the Advansys wide
+controllers, support enabling and disabling synchronous negotiation for
+a device, but do not support setting the synchronous negotiation rate.
+.Bl -tag -width 17n
+.It Fl a
+Attempt to make the negotiation settings take effect immediately by sending
+a Test Unit Ready command to the device.
+.It Fl c
+Show or set current negotiation settings.
+This is the default.
+.It Fl D Ar enable|disable
+Enable or disable disconnection.
+.It Fl M Ar mode
+Set ATA mode.
+.It Fl O Ar offset
+Set the command delay offset.
+.It Fl q
+Be quiet, do not print anything.
+This is generally useful when you want to
+set a parameter, but do not want any status information.
+.It Fl R Ar syncrate
+Change the synchronization rate for a device.
+The sync rate is a floating
+point value specified in MHz.
+So, for instance,
+.Sq 20.000
+is a legal value, as is
+.Sq 20 .
+.It Fl T Ar enable|disable
+Enable or disable tagged queueing for a device.
+.It Fl U
+Show or set user negotiation settings.
+The default is to show or set
+current negotiation settings.
+.It Fl v
+The verbose switch has special meaning for the
+.Ic negotiate
+subcommand.
+It causes
+.Nm
+to print out the contents of a Path Inquiry (XPT_PATH_INQ) CCB sent to the
+controller driver.
+.It Fl W Ar bus_width
+Specify the bus width to negotiate with a device.
+The bus width is
+specified in bits.
+The only useful values to specify are 8, 16, and 32
+bits.
+The controller must support the bus width in question in order for
+the setting to take effect.
+.El
+.Pp
+In general, sync rate and offset settings will not take effect for a
+device until a command has been sent to the device.
+The
+.Fl a
+switch above will automatically send a Test Unit Ready to the device so
+negotiation parameters will take effect.
+.It Ic format
+Issue the
+.Tn SCSI
+FORMAT UNIT command to the named device.
+.Pp
+.Em WARNING! WARNING! WARNING!
+.Pp
+Low level formatting a disk will destroy ALL data on the disk.
+Use
+extreme caution when issuing this command.
+Many users low-level format
+disks that do not really need to be low-level formatted.
+There are
+relatively few scenarios that call for low-level formatting a disk.
+One reason for
+low-level formatting a disk is to initialize the disk after changing
+its physical sector size.
+Another reason for low-level formatting a disk
+is to revive the disk if you are getting "medium format corrupted" errors
+from the disk in response to read and write requests.
+.Pp
+Some disks take longer than others to format.
+Users should specify a
+timeout long enough to allow the format to complete.
+The default format
+timeout is 3 hours, which should be long enough for most disks.
+Some hard
+disks will complete a format operation in a very short period of time
+(on the order of 5 minutes or less).
+This is often because the drive
+does not really support the FORMAT UNIT command -- it just accepts the
+command, waits a few minutes and then returns it.
+.Pp
+The
+.Sq format
+subcommand takes several arguments that modify its default behavior.
+The
+.Fl q
+and
+.Fl y
+arguments can be useful for scripts.
+.Bl -tag -width 6n
+.It Fl q
+Be quiet, do not print any status messages.
+This option will not disable
+the questions, however.
+To disable questions, use the
+.Fl y
+argument, below.
+.It Fl r
+Run in
+.Dq report only
+mode.
+This will report status on a format that is already running on the drive.
+.It Fl w
+Issue a non-immediate format command.
+By default,
+.Nm
+issues the FORMAT UNIT command with the immediate bit set.
+This tells the
+device to immediately return the format command, before the format has
+actually completed.
+Then,
+.Nm
+gathers
+.Tn SCSI
+sense information from the device every second to determine how far along
+in the format process it is.
+If the
+.Fl w
+argument is specified,
+.Nm
+will issue a non-immediate format command, and will be unable to print any
+information to let the user know what percentage of the disk has been
+formatted.
+.It Fl y
+Do not ask any questions.
+By default,
+.Nm
+will ask the user if he/she really wants to format the disk in question,
+and also if the default format command timeout is acceptable.
+The user
+will not be asked about the timeout if a timeout is specified on the
+command line.
+.El
+.It Ic sanitize
+Issue the
+.Tn SCSI
+SANITIZE command to the named device.
+.Pp
+.Em WARNING! WARNING! WARNING!
+.Pp
+ALL data in the cache and on the disk will be destroyed or made inaccessible.
+Recovery of the data is not possible.
+Use extreme caution when issuing this command.
+.Pp
+The
+.Sq sanitize
+subcommand takes several arguments that modify its default behavior.
+The
+.Fl q
+and
+.Fl y
+arguments can be useful for scripts.
+.Bl -tag -width 6n
+.It Fl a Ar operation
+Specify the sanitize operation to perform.
+.Bl -tag -width 16n
+.It overwrite
+Perform an overwrite operation by writing a user supplied
+data pattern to the device one or more times.
+The pattern is given by the
+.Fl P
+argument.
+The number of times is given by the
+.Fl c
+argument.
+.It block
+Perform a block erase operation.
+All the device's blocks are set to a vendor defined
+value, typically zero.
+.It crypto
+Perform a cryptographic erase operation.
+The encryption keys are changed to prevent the decryption
+of the data.
+.It exitfailure
+Exits a previously failed sanitize operation.
+A failed sanitize operation can only be exited if it was
+run in the unrestricted completion mode, as provided by the
+.Fl U
+argument.
+.El
+.It Fl c Ar passes
+The number of passes when performing an
+.Sq overwrite
+operation.
+Valid values are between 1 and 31.
+The default is 1.
+.It Fl I
+When performing an
+.Sq overwrite
+operation, the pattern is inverted between consecutive passes.
+.It Fl P Ar pattern
+Path to the file containing the pattern to use when
+performing an
+.Sq overwrite
+operation.
+The pattern is repeated as needed to fill each block.
+.It Fl q
+Be quiet, do not print any status messages.
+This option will not disable
+the questions, however.
+To disable questions, use the
+.Fl y
+argument, below.
+.It Fl U
+Perform the sanitize in the unrestricted completion mode.
+If the operation fails, it can later be exited with the
+.Sq exitfailure
+operation.
+.It Fl r
+Run in
+.Dq report only
+mode.
+This will report status on a sanitize that is already running on the drive.
+.It Fl w
+Issue a non-immediate sanitize command.
+By default,
+.Nm
+issues the SANITIZE command with the immediate bit set.
+This tells the
+device to immediately return the sanitize command, before
+the sanitize has actually completed.
+Then,
+.Nm
+gathers
+.Tn SCSI
+sense information from the device every second to determine how far along
+in the sanitize process it is.
+If the
+.Fl w
+argument is specified,
+.Nm
+will issue a non-immediate sanitize command, and will be unable to print any
+information to let the user know what percentage of the disk has been
+sanitized.
+.It Fl y
+Do not ask any questions.
+By default,
+.Nm
+will ask the user if he/she really wants to sanitize the disk in question,
+and also if the default sanitize command timeout is acceptable.
+The user
+will not be asked about the timeout if a timeout is specified on the
+command line.
+.El
+.It Ic idle
+Put ATA device into IDLE state.
+Optional parameter
+.Pq Fl t
+specifies automatic standby timer value in seconds.
+Value 0 disables timer.
+.It Ic standby
+Put ATA device into STANDBY state.
+Optional parameter
+.Pq Fl t
+specifies automatic standby timer value in seconds.
+Value 0 disables timer.
+.It Ic sleep
+Put ATA device into SLEEP state.
+Note that the only way get device out of
+this state may be reset.
+.It Ic apm
+It optional parameter
+.Pq Fl l
+specified, enables and sets advanced power management level, where
+1 -- minimum power, 127 -- maximum performance with standby,
+128 -- minimum power without standby, 254 -- maximum performance.
+If not specified -- APM is disabled.
+.It Ic aam
+It optional parameter
+.Pq Fl l
+specified, enables and sets automatic acoustic management level, where
+1 -- minimum noise, 254 -- maximum performance.
+If not specified -- AAM is disabled.
+.It Ic security
+Update or report security settings, using an ATA identify command (0xec).
+By default,
+.Nm
+will print out the security support and associated settings of the device.
+The
+.Ic security
+command takes several arguments:
+.Bl -tag -width 0n
+.It Fl d Ar pwd
+.Pp
+Disable device security using the given password for the selected user according
+to the devices configured security level.
+.It Fl e Ar pwd
+.Pp
+Erase the device using the given password for the selected user.
+.Pp
+.Em WARNING! WARNING! WARNING!
+.Pp
+Issuing a secure erase will
+.Em ERASE ALL
+user data on the device and may take several hours to complete.
+.Pp
+When this command is used against an SSD drive all its cells will be marked as
+empty, restoring it to factory default write performance.
+For SSD's this action
+usually takes just a few seconds.
+.It Fl f
+.Pp
+Freeze the security configuration of the specified device.
+.Pp
+After command completion any other commands that update the device lock mode
+shall be command aborted.
+Frozen mode is disabled by power-off or hardware reset.
+.It Fl h Ar pwd
+.Pp
+Enhanced erase the device using the given password for the selected user.
+.Pp
+.Em WARNING! WARNING! WARNING!
+.Pp
+Issuing an enhanced secure erase will
+.Em ERASE ALL
+user data on the device and may take several hours to complete.
+.Pp
+An enhanced erase writes predetermined data patterns to all user data areas,
+all previously written user data shall be overwritten, including sectors that
+are no longer in use due to reallocation.
+.It Fl k Ar pwd
+.Pp
+Unlock the device using the given password for the selected user according to
+the devices configured security level.
+.It Fl l Ar high|maximum
+.Pp
+Specifies which security level to set when issuing a
+.Fl s Ar pwd
+command.
+The security level determines device behavior when the master
+password is used to unlock the device.
+When the security level is set to high
+the device requires the unlock command and the master password to unlock.
+When the security level is set to maximum the device requires a secure erase
+with the master password to unlock.
+.Pp
+This option must be used in conjunction with one of the security action commands.
+.Pp
+Defaults to
+.Em high
+.It Fl q
+.Pp
+Be quiet, do not print any status messages.
+This option will not disable the questions, however.
+To disable questions, use the
+.Fl y
+argument, below.
+.It Fl s Ar pwd
+.Pp
+Password the device (enable security) using the given password for the selected
+user.
+This option can be combined with other options such as
+.Fl e Em pwd
+.Pp
+A master password may be set in a addition to the user password. The purpose of
+the master password is to allow an administrator to establish a password that
+is kept secret from the user, and which may be used to unlock the device if the
+user password is lost.
+.Pp
+.Em Note:
+Setting the master password does not enable device security.
+.Pp
+If the master password is set and the drive supports a Master Revision Code
+feature the Master Password Revision Code will be decremented.
+.It Fl T Ar timeout
+.Pp
+Overrides the default timeout, specified in seconds, used for both
+.Fl e
+and
+.Fl h
+this is useful if your system has problems processing long timeouts correctly.
+.Pp
+Usually the timeout is calculated from the information stored on the drive if
+present, otherwise it defaults to 2 hours.
+.It Fl U Ar user|master
+.Pp
+Specifies which user to set / use for the running action command, valid values
+are user or master and defaults to master if not set.
+.Pp
+This option must be used in conjunction with one of the security action commands.
+.Pp
+Defaults to
+.Em master
+.It Fl y
+.Pp
+Confirm yes to dangerous options such as
+.Fl e
+without prompting for confirmation.
+.El
+.Pp
+If the password specified for any action commands does not match the configured
+password for the specified user the command will fail.
+.Pp
+The password in all cases is limited to 32 characters, longer passwords will
+fail.
+.It Ic hpa
+Update or report Host Protected Area details.
+By default
+.Nm
+will print out the HPA support and associated settings of the device.
+The
+.Ic hpa
+command takes several optional arguments:
+.Bl -tag -width 0n
+.It Fl f
+.Pp
+Freeze the HPA configuration of the specified device.
+.Pp
+After command completion any other commands that update the HPA configuration
+shall be command aborted.
+Frozen mode is disabled by power-off or hardware reset.
+.It Fl l
+.Pp
+Lock the HPA configuration of the device until a successful call to unlock or
+the next power-on reset occurs.
+.It Fl P
+.Pp
+Make the HPA max sectors persist across power-on reset or a hardware reset.
+This must be used in combination with
+.Fl s Ar max_sectors
+.
+.It Fl p Ar pwd
+.Pp
+Set the HPA configuration password required for unlock calls.
+.It Fl q
+.Pp
+Be quiet, do not print any status messages.
+This option will not disable the questions.
+To disable questions, use the
+.Fl y
+argument, below.
+.It Fl s Ar max_sectors
+.Pp
+Configures the maximum user accessible sectors of the device.
+This will change the number of sectors the device reports.
+.Pp
+.Em WARNING! WARNING! WARNING!
+.Pp
+Changing the max sectors of a device using this option will make the data on
+the device beyond the specified value inaccessible.
+.Pp
+Only one successful
+.Fl s Ar max_sectors
+call can be made without a power-on reset or a hardware reset of the device.
+.It Fl U Ar pwd
+.Pp
+Unlock the HPA configuration of the specified device using the given password.
+If the password specified does not match the password configured via
+.Fl p Ar pwd
+the command will fail.
+.Pp
+After 5 failed unlock calls, due to password miss-match, the device will refuse
+additional unlock calls until after a power-on reset.
+.It Fl y
+.Pp
+Confirm yes to dangerous options such as
+.Fl e
+without prompting for confirmation
+.El
+.Pp
+The password for all HPA commands is limited to 32 characters, longer passwords
+will fail.
+.It Ic fwdownload
+Program firmware of the named SCSI device using the image file provided.
+.Pp
+Current list of supported vendors:
+.Bl -bullet -offset indent -compact
+.It
+HITACHI
+.It
+HP
+.It
+IBM
+.It
+PLEXTOR
+.It
+QUANTUM
+.It
+SAMSUNG
+.It
+SEAGATE
+.El
+.Pp
+.Em WARNING! WARNING! WARNING!
+.Pp
+Little testing has been done to make sure that different device models from
+each vendor work correctly with the fwdownload command.
+A vendor name appearing in the supported list means only that firmware of at
+least one device type from that vendor has successfully been programmed with
+the fwdownload command.
+Extra caution should be taken when using this command since there is no
+guarantee it will not break a device from the listed vendors.
+Ensure that you have a recent backup of the data on the device before
+performing a firmware update.
+.Bl -tag -width 11n
+.It Fl f Ar fw_image
+Path to the firmware image file to be downloaded to the specified device.
+.It Fl y
+Do not ask for confirmation.
+.It Fl s
+Run in simulation mode.
+Packet sizes that will be sent are shown, but no actual packet is sent to the
+device.
+No confirmation is asked in simulation mode.
+.It Fl v
+Besides showing sense information in case of a failure, the verbose option
+causes
+.Nm
+to output a line for every firmware segment that is sent to the device by the
+fwdownload command
+-- the same as the ones shown in simulation mode.
+.El
+.It Ic persist
+Persistent reservation support.
+Persistent reservations are a way to reserve a particular
+.Tn SCSI
+LUN for use by one or more
+.Tn SCSI
+initiators.
+If the
+.Fl i
+option is specified,
+.Nm
+will issue the
+.Tn SCSI
+PERSISTENT RESERVE IN
+command using the requested service action.
+If the
+.Fl o
+option is specified,
+.Nm
+will issue the
+.Tn SCSI
+PERSISTENT RESERVE OUT
+command using the requested service action.
+One of those two options is required.
+.Pp
+Persistent reservations are complex, and fully explaining them is outside
+the scope of this manual.
+Please visit
+http://www.t10.org
+and download the latest SPC spec for a full explanation of persistent
+reservations.
+.Bl -tag -width 8n
+.It Fl i Ar mode
+Specify the service action for the PERSISTENT RESERVE IN command.
+Supported service actions:
+.Bl -tag -width 19n
+.It read_keys
+Report the current persistent reservation generation (PRgeneration) and any
+registered keys.
+.It read_reservation
+Report the persistent reservation, if any.
+.It report_capabilities
+Report the persistent reservation capabilities of the LUN.
+.It read_full_status
+Report the full status of persistent reservations on the LUN.
+.El
+.It Fl o Ar mode
+Specify the service action for the PERSISTENT RESERVE OUT command.
+For service actions like register that are components of other service
+action names, the entire name must be specified.
+Otherwise, enough of the service action name must be specified to
+distinguish it from other possible service actions.
+Supported service actions:
+.Bl -tag -width 15n
+.It register
+Register a reservation key with the LUN or unregister a reservation key.
+To register a key, specify the requested key as the Service Action
+Reservation Key.
+To unregister a key, specify the previously registered key as the
+Reservation Key.
+To change a key, specify the old key as the Reservation Key and the new
+key as the Service Action Reservation Key.
+.It register_ignore
+This is similar to the register subcommand, except that the Reservation Key
+is ignored.
+The Service Action Reservation Key will overwrite any previous key
+registered for the initiator.
+.It reserve
+Create a reservation.
+A key must be registered with the LUN before the LUN can be reserved, and
+it must be specified as the Reservation Key.
+The type of reservation must also be specified.
+The scope defaults to LUN scope (LU_SCOPE), but may be changed.
+.It release
+Release a reservation.
+The Reservation Key must be specified.
+.It clear
+Release a reservation and remove all keys from the device.
+The Reservation Key must be specified.
+.It preempt
+Remove a reservation belonging to another initiator.
+The Reservation Key must be specified.
+The Service Action Reservation Key may be specified, depending on the
+operation being performed.
+.It preempt_abort
+Remove a reservation belonging to another initiator and abort all
+outstanding commands from that initiator.
+The Reservation Key must be specified.
+The Service Action Reservation Key may be specified, depending on the
+operation being performed.
+.It register_move
+Register another initiator with the LUN, and establish a reservation on the
+LUN for that initiator.
+The Reservation Key and Service Action Reservation Key must be specified.
+.It replace_lost
+Replace Lost Reservation information.
+.El
+.It Fl a
+Set the All Target Ports (ALL_TG_PT) bit.
+This requests that the key registration be applied to all target ports and
+not just the particular target port that receives the command.
+This only applies to the register and register_ignore actions.
+.It Fl I Ar tid
+Specify a Transport ID.
+This only applies to the Register and Register and Move service actions for
+Persistent Reserve Out.
+Multiple Transport IDs may be specified with multiple
+.Fl I
+arguments.
+With the Register service action, specifying one or more Transport IDs
+implicitly enables the
+.Fl S
+option which turns on the SPEC_I_PT bit.
+Transport IDs generally have the format protocol,id.
+.Bl -tag -width 5n
+.It SAS
+A SAS Transport ID consists of
+.Dq sas,
+followed by a 64-bit SAS address.
+For example:
+.Pp
+.Dl sas,0x1234567812345678
+.It FC
+A Fibre Channel Transport ID consists of
+.Dq fcp,
+followed by a 64-bit Fibre Channel World Wide Name.
+For example:
+.Pp
+.Dl fcp,0x1234567812345678
+.It SPI
+A Parallel SCSI address consists of
+.Dq spi,
+followed by a SCSI target ID and a relative target port identifier.
+For example:
+.Pp
+.Dl spi,4,1
+.It 1394
+An IEEE 1394 (Firewire) Transport ID consists of
+.Dq sbp,
+followed by a 64-bit EUI-64 IEEE 1394 node unique identifier.
+For example:
+.Pp
+.Dl sbp,0x1234567812345678
+.It RDMA
+A SCSI over RDMA Transport ID consists of
+.Dq srp,
+followed by a 128-bit RDMA initiator port identifier.
+The port identifier must be exactly 32 or 34 (if the leading 0x is
+included) hexadecimal digits.
+Only hexadecimal (base 16) numbers are supported.
+For example:
+.Pp
+.Dl srp,0x12345678123456781234567812345678
+.It iSCSI
+An iSCSI Transport ID consists an iSCSI name and optionally a separator and
+iSCSI session ID.
+For example, if only the iSCSI name is specified:
+.Pp
+.Dl iqn.2012-06.com.example:target0
+.Pp
+If the iSCSI separator and initiator session ID are specified:
+.Pp
+.Dl iqn.2012-06.com.example:target0,i,0x123
+.It PCIe
+A SCSI over PCIe Transport ID consists of
+.Dq sop,
+followed by a PCIe Routing ID.
+The Routing ID consists of a bus, device and function or in the alternate
+form, a bus and function.
+The bus must be in the range of 0 to 255 inclusive and the device must be
+in the range of 0 to 31 inclusive.
+The function must be in the range of 0 to 7 inclusive if the standard form
+is used, and in the range of 0 to 255 inclusive if the alternate form is
+used.
+For example, if a bus, device and function are specified for the standard
+Routing ID form:
+.Pp
+.Dl sop,4,5,1
+.Pp
+If the alternate Routing ID form is used:
+.Pp
+.Dl sop,4,1
+.El
+.It Fl k Ar key
+Specify the Reservation Key.
+This may be in decimal, octal or hexadecimal format.
+The value is zero by default if not otherwise specified.
+The value must be between 0 and 2^64 - 1, inclusive.
+.It Fl K Ar key
+Specify the Service Action Reservation Key.
+This may be in decimal, octal or hexadecimal format.
+The value is zero by default if not otherwise specified.
+The value must be between 0 and 2^64 - 1, inclusive.
+.It Fl p
+Enable the Activate Persist Through Power Loss bit.
+This is only used for the register and register_ignore actions.
+This requests that the reservation persist across power loss events.
+.It Fl s Ar scope
+Specify the scope of the reservation.
+The scope may be specified by name or by number.
+The scope is ignored for register, register_ignore and clear.
+If the desired scope isn't available by name, you may specify the number.
+.Bl -tag -width 7n
+.It lun
+LUN scope (0x00).
+This encompasses the entire LUN.
+.It extent
+Extent scope (0x01).
+.It element
+Element scope (0x02).
+.El
+.It Fl R Ar rtp
+Specify the Relative Target Port.
+This only applies to the Register and Move service action of the Persistent
+Reserve Out command.
+.It Fl S
+Enable the SPEC_I_PT bit.
+This only applies to the Register service action of Persistent Reserve Out.
+You must also specify at least one Transport ID with
+.Fl I
+if this option is set.
+If you specify a Transport ID, this option is automatically set.
+It is an error to specify this option for any service action other than
+Register.
+.It Fl T Ar type
+Specify the reservation type.
+The reservation type may be specified by name or by number.
+If the desired reservation type isn't available by name, you may specify
+the number.
+Supported reservation type names:
+.Bl -tag -width 11n
+.It read_shared
+Read Shared mode.
+.It wr_ex
+Write Exclusive mode.
+May also be specified as
+.Dq write_exclusive .
+.It rd_ex
+Read Exclusive mode.
+May also be specified as
+.Dq read_exclusive .
+.It ex_ac
+Exclusive access mode.
+May also be specified as
+.Dq exclusive_access .
+.It wr_ex_ro
+Write Exclusive Registrants Only mode.
+May also be specified as
+.Dq write_exclusive_reg_only .
+.It ex_ac_ro
+Exclusive Access Registrants Only mode.
+May also be specified as
+.Dq exclusive_access_reg_only .
+.It wr_ex_ar
+Write Exclusive All Registrants mode.
+May also be specified as
+.Dq write_exclusive_all_regs .
+.It ex_ac_ar
+Exclusive Access All Registrants mode.
+May also be specified as
+.Dq exclusive_access_all_regs .
+.El
+.It Fl U
+Specify that the target should unregister the initiator that sent
+the Register and Move request.
+By default, the target will not unregister the initiator that sends the
+Register and Move request.
+This option only applies to the Register and Move service action of the
+Persistent Reserve Out command.
+.El
+.It Ic help
+Print out verbose usage information.
+.El
+.Sh ENVIRONMENT
+The
+.Ev SCSI_MODES
+variable allows the user to specify an alternate mode page format file.
+.Pp
+The
+.Ev EDITOR
+variable determines which text editor
+.Nm
+starts when editing mode pages.
+.Sh FILES
+.Bl -tag -width /usr/share/misc/scsi_modes -compact
+.It Pa /usr/share/misc/scsi_modes
+is the SCSI mode format database.
+.It Pa /dev/xpt0
+is the transport layer device.
+.It Pa /dev/pass*
+are the CAM application passthrough devices.
+.El
+.Sh EXAMPLES
+.Dl camcontrol eject -n cd -u 1 -v
+.Pp
+Eject the CD from cd1, and print SCSI sense information if the command
+fails.
+.Pp
+.Dl camcontrol tur da0
+.Pp
+Send the SCSI test unit ready command to da0.
+The
+.Nm
+utility will report whether the disk is ready, but will not display sense
+information if the command fails since the
+.Fl v
+switch was not specified.
+.Bd -literal -offset indent
+camcontrol tur da1 -E -C 4 -t 50 -v
+.Ed
+.Pp
+Send a test unit ready command to da1.
+Enable kernel error recovery.
+Specify a retry count of 4, and a timeout of 50 seconds.
+Enable sense
+printing (with the
+.Fl v
+flag) if the command fails.
+Since error recovery is turned on, the
+disk will be spun up if it is not currently spinning.
+The
+.Nm
+utility will report whether the disk is ready.
+.Bd -literal -offset indent
+camcontrol cmd -n cd -u 1 -v -c "3C 00 00 00 00 00 00 00 0e 00" \e
+ -i 0xe "s1 i3 i1 i1 i1 i1 i1 i1 i1 i1 i1 i1"
+.Ed
+.Pp
+Issue a READ BUFFER command (0x3C) to cd1.
+Display the buffer size of cd1,
+and display the first 10 bytes from the cache on cd1.
+Display SCSI sense
+information if the command fails.
+.Bd -literal -offset indent
+camcontrol cmd -n cd -u 1 -v -c "3B 00 00 00 00 00 00 00 0e 00" \e
+ -o 14 "00 00 00 00 1 2 3 4 5 6 v v v v" 7 8 9 8
+.Ed
+.Pp
+Issue a WRITE BUFFER (0x3B) command to cd1.
+Write out 10 bytes of data,
+not including the (reserved) 4 byte header.
+Print out sense information if
+the command fails.
+Be very careful with this command, improper use may
+cause data corruption.
+.Bd -literal -offset indent
+camcontrol modepage da3 -m 1 -e -P 3
+.Ed
+.Pp
+Edit mode page 1 (the Read-Write Error Recover page) for da3, and save the
+settings on the drive.
+Mode page 1 contains a disk drive's auto read and
+write reallocation settings, among other things.
+.Pp
+.Dl camcontrol rescan all
+.Pp
+Rescan all SCSI busses in the system for devices that have been added,
+removed or changed.
+.Pp
+.Dl camcontrol rescan 0
+.Pp
+Rescan SCSI bus 0 for devices that have been added, removed or changed.
+.Pp
+.Dl camcontrol rescan 0:1:0
+.Pp
+Rescan SCSI bus 0, target 1, lun 0 to see if it has been added, removed, or
+changed.
+.Pp
+.Dl camcontrol tags da5 -N 24
+.Pp
+Set the number of concurrent transactions for da5 to 24.
+.Bd -literal -offset indent
+camcontrol negotiate -n da -u 4 -T disable
+.Ed
+.Pp
+Disable tagged queueing for da4.
+.Bd -literal -offset indent
+camcontrol negotiate -n da -u 3 -R 20.000 -O 15 -a
+.Ed
+.Pp
+Negotiate a sync rate of 20MHz and an offset of 15 with da3.
+Then send a
+Test Unit Ready command to make the settings take effect.
+.Bd -literal -offset indent
+camcontrol smpcmd ses0 -v -r 4 "40 0 00 0" -R 1020 "s9 i1"
+.Ed
+.Pp
+Send the SMP REPORT GENERAL command to ses0, and display the number of PHYs
+it contains.
+Display SMP errors if the command fails.
+.Bd -literal -offset indent
+camcontrol security ada0
+.Ed
+.Pp
+Report security support and settings for ada0
+.Bd -literal -offset indent
+camcontrol security ada0 -U user -s MyPass
+.Ed
+.Pp
+Enable security on device ada0 with the password MyPass
+.Bd -literal -offset indent
+camcontrol security ada0 -U user -e MyPass
+.Ed
+.Pp
+Secure erase ada0 which has had security enabled with user password MyPass
+.Pp
+.Em WARNING! WARNING! WARNING!
+.Pp
+This will
+.Em ERASE ALL
+data from the device, so backup your data before using!
+.Pp
+This command can be used against an SSD drive to restoring it to
+factory default write performance.
+.Bd -literal -offset indent
+camcontrol hpa ada0
+.Ed
+.Pp
+Report HPA support and settings for ada0 (also reported via
+identify).
+.Bd -literal -offset indent
+camcontrol hpa ada0 -s 10240
+.Ed
+.Pp
+Enables HPA on ada0 setting the maximum reported sectors to 10240.
+.Pp
+.Em WARNING! WARNING! WARNING!
+.Pp
+This will
+.Em PREVENT ACCESS
+to all data on the device beyond this limit until HPA is disabled by setting
+HPA to native max sectors of the device, which can only be done after a
+power-on or hardware reset!
+.Pp
+.Em DO NOT
+use this on a device which has an active filesystem!
+.Bd -literal -offset indent
+camcontrol persist da0 -v -i read_keys
+.Ed
+.Pp
+This will read any persistent reservation keys registered with da0, and
+display any errors encountered when sending the PERSISTENT RESERVE IN
+.Tn SCSI
+command.
+.Bd -literal -offset indent
+camcontrol persist da0 -v -o register -a -K 0x12345678
+.Ed
+.Pp
+This will register the persistent reservation key 0x12345678 with da0,
+apply that registration to all ports on da0, and display any errors that
+occur when sending the PERSISTENT RESERVE OUT command.
+.Bd -literal -offset indent
+camcontrol persist da0 -v -o reserve -s lun -k 0x12345678 -T ex_ac
+.Ed
+.Pp
+This will reserve da0 for the exlusive use of the initiator issuing the
+command.
+The scope of the reservation is the entire LUN.
+Any errors sending the PERSISTENT RESERVE OUT command will be displayed.
+.Bd -literal -offset indent
+camcontrol persist da0 -v -i read_full
+.Ed
+.Pp
+This will display the full status of all reservations on da0 and print out
+status if there are any errors.
+.Bd -literal -offset indent
+camcontrol persist da0 -v -o release -k 0x12345678 -T ex_ac
+.Ed
+.Pp
+This will release a reservation on da0 of the type ex_ac
+(Exclusive Access).
+The Reservation Key for this registration is 0x12345678.
+Any errors that occur will be displayed.
+.Bd -literal -offset indent
+camcontrol persist da0 -v -o register -K 0x12345678 -S \e
+ -I sas,0x1234567812345678 -I sas,0x8765432187654321
+.Ed
+.Pp
+This will register the key 0x12345678 with da0, specifying that it applies
+to the SAS initiators with SAS addresses 0x1234567812345678 and
+0x8765432187654321.
+.Bd -literal -offset indent
+camcontrol persist da0 -v -o register_move -k 0x87654321 \e
+ -K 0x12345678 -U -p -R 2 -I fcp,0x1234567812345678
+.Ed
+.Pp
+This will move the registration from the current initiator, whose
+Registration Key is 0x87654321, to the Fibre Channel initiator with the
+Fiber Channel World Wide Node Name 0x1234567812345678.
+A new registration key, 0x12345678, will be registered for the initiator
+with the Fibre Channel World Wide Node Name 0x1234567812345678, and the
+current initiator will be unregistered from the target.
+The reservation will be moved to relative target port 2 on the target
+device.
+The registration will persist across power losses.
+.Sh SEE ALSO
+.Xr cam 3 ,
+.Xr cam_cdbparse 3 ,
+.Xr cam 4 ,
+.Xr pass 4 ,
+.Xr xpt 4
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 3.0 .
+.Pp
+The mode page editing code and arbitrary SCSI command code are based upon
+code in the old
+.Xr scsi 8
+utility and
+.Xr scsi 3
+library, written by Julian Elischer and Peter Dufault.
+The
+.Xr scsi 8
+program first appeared in
+.Bx 386 0.1.2.4 ,
+and first appeared in
+.Fx
+in
+.Fx 2.0.5 .
+.Sh AUTHORS
+.An Kenneth Merry Aq Mt ken@FreeBSD.org
+.Sh BUGS
+The code that parses the generic command line arguments does not know that
+some of the subcommands take multiple arguments.
+So if, for instance, you
+tried something like this:
+.Bd -literal -offset indent
+camcontrol cmd -n da -u 1 -c "00 00 00 00 00 v" 0x00 -v
+.Ed
+.Pp
+The sense information from the test unit ready command would not get
+printed out, since the first
+.Xr getopt 3
+call in
+.Nm
+bails out when it sees the second argument to
+.Fl c
+(0x00),
+above.
+Fixing this behavior would take some gross code, or changes to the
+.Xr getopt 3
+interface.
+The best way to circumvent this problem is to always make sure
+to specify generic
+.Nm
+arguments before any command-specific arguments.
diff --git a/sbin/camcontrol/camcontrol.c b/sbin/camcontrol/camcontrol.c
new file mode 100644
index 0000000..c0a1344
--- /dev/null
+++ b/sbin/camcontrol/camcontrol.c
@@ -0,0 +1,8668 @@
+/*
+ * Copyright (c) 1997-2007 Kenneth D. Merry
+ * 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 AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/ioctl.h>
+#include <sys/stdint.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/endian.h>
+#include <sys/sbuf.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <err.h>
+#include <libutil.h>
+#ifndef MINIMALISTIC
+#include <limits.h>
+#include <inttypes.h>
+#endif
+
+#include <cam/cam.h>
+#include <cam/cam_debug.h>
+#include <cam/cam_ccb.h>
+#include <cam/scsi/scsi_all.h>
+#include <cam/scsi/scsi_da.h>
+#include <cam/scsi/scsi_pass.h>
+#include <cam/scsi/scsi_message.h>
+#include <cam/scsi/smp_all.h>
+#include <cam/ata/ata_all.h>
+#include <camlib.h>
+#include "camcontrol.h"
+
+typedef enum {
+ CAM_CMD_NONE = 0x00000000,
+ CAM_CMD_DEVLIST = 0x00000001,
+ CAM_CMD_TUR = 0x00000002,
+ CAM_CMD_INQUIRY = 0x00000003,
+ CAM_CMD_STARTSTOP = 0x00000004,
+ CAM_CMD_RESCAN = 0x00000005,
+ CAM_CMD_READ_DEFECTS = 0x00000006,
+ CAM_CMD_MODE_PAGE = 0x00000007,
+ CAM_CMD_SCSI_CMD = 0x00000008,
+ CAM_CMD_DEVTREE = 0x00000009,
+ CAM_CMD_USAGE = 0x0000000a,
+ CAM_CMD_DEBUG = 0x0000000b,
+ CAM_CMD_RESET = 0x0000000c,
+ CAM_CMD_FORMAT = 0x0000000d,
+ CAM_CMD_TAG = 0x0000000e,
+ CAM_CMD_RATE = 0x0000000f,
+ CAM_CMD_DETACH = 0x00000010,
+ CAM_CMD_REPORTLUNS = 0x00000011,
+ CAM_CMD_READCAP = 0x00000012,
+ CAM_CMD_IDENTIFY = 0x00000013,
+ CAM_CMD_IDLE = 0x00000014,
+ CAM_CMD_STANDBY = 0x00000015,
+ CAM_CMD_SLEEP = 0x00000016,
+ CAM_CMD_SMP_CMD = 0x00000017,
+ CAM_CMD_SMP_RG = 0x00000018,
+ CAM_CMD_SMP_PC = 0x00000019,
+ CAM_CMD_SMP_PHYLIST = 0x0000001a,
+ CAM_CMD_SMP_MANINFO = 0x0000001b,
+ CAM_CMD_DOWNLOAD_FW = 0x0000001c,
+ CAM_CMD_SECURITY = 0x0000001d,
+ CAM_CMD_HPA = 0x0000001e,
+ CAM_CMD_SANITIZE = 0x0000001f,
+ CAM_CMD_PERSIST = 0x00000020,
+ CAM_CMD_APM = 0x00000021,
+ CAM_CMD_AAM = 0x00000022
+} cam_cmdmask;
+
+typedef enum {
+ CAM_ARG_NONE = 0x00000000,
+ CAM_ARG_VERBOSE = 0x00000001,
+ CAM_ARG_DEVICE = 0x00000002,
+ CAM_ARG_BUS = 0x00000004,
+ CAM_ARG_TARGET = 0x00000008,
+ CAM_ARG_LUN = 0x00000010,
+ CAM_ARG_EJECT = 0x00000020,
+ CAM_ARG_UNIT = 0x00000040,
+ CAM_ARG_FORMAT_BLOCK = 0x00000080,
+ CAM_ARG_FORMAT_BFI = 0x00000100,
+ CAM_ARG_FORMAT_PHYS = 0x00000200,
+ CAM_ARG_PLIST = 0x00000400,
+ CAM_ARG_GLIST = 0x00000800,
+ CAM_ARG_GET_SERIAL = 0x00001000,
+ CAM_ARG_GET_STDINQ = 0x00002000,
+ CAM_ARG_GET_XFERRATE = 0x00004000,
+ CAM_ARG_INQ_MASK = 0x00007000,
+ CAM_ARG_MODE_EDIT = 0x00008000,
+ CAM_ARG_PAGE_CNTL = 0x00010000,
+ CAM_ARG_TIMEOUT = 0x00020000,
+ CAM_ARG_CMD_IN = 0x00040000,
+ CAM_ARG_CMD_OUT = 0x00080000,
+ CAM_ARG_DBD = 0x00100000,
+ CAM_ARG_ERR_RECOVER = 0x00200000,
+ CAM_ARG_RETRIES = 0x00400000,
+ CAM_ARG_START_UNIT = 0x00800000,
+ CAM_ARG_DEBUG_INFO = 0x01000000,
+ CAM_ARG_DEBUG_TRACE = 0x02000000,
+ CAM_ARG_DEBUG_SUBTRACE = 0x04000000,
+ CAM_ARG_DEBUG_CDB = 0x08000000,
+ CAM_ARG_DEBUG_XPT = 0x10000000,
+ CAM_ARG_DEBUG_PERIPH = 0x20000000,
+ CAM_ARG_DEBUG_PROBE = 0x40000000,
+} cam_argmask;
+
+struct camcontrol_opts {
+ const char *optname;
+ uint32_t cmdnum;
+ cam_argmask argnum;
+ const char *subopt;
+};
+
+#ifndef MINIMALISTIC
+struct ata_res_pass16 {
+ u_int16_t reserved[5];
+ u_int8_t flags;
+ u_int8_t error;
+ u_int8_t sector_count_exp;
+ u_int8_t sector_count;
+ u_int8_t lba_low_exp;
+ u_int8_t lba_low;
+ u_int8_t lba_mid_exp;
+ u_int8_t lba_mid;
+ u_int8_t lba_high_exp;
+ u_int8_t lba_high;
+ u_int8_t device;
+ u_int8_t status;
+};
+
+struct ata_set_max_pwd
+{
+ u_int16_t reserved1;
+ u_int8_t password[32];
+ u_int16_t reserved2[239];
+};
+
+static const char scsicmd_opts[] = "a:c:dfi:o:r";
+static const char readdefect_opts[] = "f:GPqsS:X";
+static const char negotiate_opts[] = "acD:M:O:qR:T:UW:";
+static const char smprg_opts[] = "l";
+static const char smppc_opts[] = "a:A:d:lm:M:o:p:s:S:T:";
+static const char smpphylist_opts[] = "lq";
+static char pwd_opt;
+#endif
+
+static struct camcontrol_opts option_table[] = {
+#ifndef MINIMALISTIC
+ {"tur", CAM_CMD_TUR, CAM_ARG_NONE, NULL},
+ {"inquiry", CAM_CMD_INQUIRY, CAM_ARG_NONE, "DSR"},
+ {"identify", CAM_CMD_IDENTIFY, CAM_ARG_NONE, NULL},
+ {"start", CAM_CMD_STARTSTOP, CAM_ARG_START_UNIT, NULL},
+ {"stop", CAM_CMD_STARTSTOP, CAM_ARG_NONE, NULL},
+ {"load", CAM_CMD_STARTSTOP, CAM_ARG_START_UNIT | CAM_ARG_EJECT, NULL},
+ {"eject", CAM_CMD_STARTSTOP, CAM_ARG_EJECT, NULL},
+ {"reportluns", CAM_CMD_REPORTLUNS, CAM_ARG_NONE, "clr:"},
+ {"readcapacity", CAM_CMD_READCAP, CAM_ARG_NONE, "bhHNqs"},
+#endif /* MINIMALISTIC */
+ {"rescan", CAM_CMD_RESCAN, CAM_ARG_NONE, NULL},
+ {"reset", CAM_CMD_RESET, CAM_ARG_NONE, NULL},
+#ifndef MINIMALISTIC
+ {"cmd", CAM_CMD_SCSI_CMD, CAM_ARG_NONE, scsicmd_opts},
+ {"command", CAM_CMD_SCSI_CMD, CAM_ARG_NONE, scsicmd_opts},
+ {"smpcmd", CAM_CMD_SMP_CMD, CAM_ARG_NONE, "r:R:"},
+ {"smprg", CAM_CMD_SMP_RG, CAM_ARG_NONE, smprg_opts},
+ {"smpreportgeneral", CAM_CMD_SMP_RG, CAM_ARG_NONE, smprg_opts},
+ {"smppc", CAM_CMD_SMP_PC, CAM_ARG_NONE, smppc_opts},
+ {"smpphycontrol", CAM_CMD_SMP_PC, CAM_ARG_NONE, smppc_opts},
+ {"smpplist", CAM_CMD_SMP_PHYLIST, CAM_ARG_NONE, smpphylist_opts},
+ {"smpphylist", CAM_CMD_SMP_PHYLIST, CAM_ARG_NONE, smpphylist_opts},
+ {"smpmaninfo", CAM_CMD_SMP_MANINFO, CAM_ARG_NONE, "l"},
+ {"defects", CAM_CMD_READ_DEFECTS, CAM_ARG_NONE, readdefect_opts},
+ {"defectlist", CAM_CMD_READ_DEFECTS, CAM_ARG_NONE, readdefect_opts},
+#endif /* MINIMALISTIC */
+ {"devlist", CAM_CMD_DEVTREE, CAM_ARG_NONE, "-b"},
+#ifndef MINIMALISTIC
+ {"periphlist", CAM_CMD_DEVLIST, CAM_ARG_NONE, NULL},
+ {"modepage", CAM_CMD_MODE_PAGE, CAM_ARG_NONE, "bdelm:P:"},
+ {"tags", CAM_CMD_TAG, CAM_ARG_NONE, "N:q"},
+ {"negotiate", CAM_CMD_RATE, CAM_ARG_NONE, negotiate_opts},
+ {"rate", CAM_CMD_RATE, CAM_ARG_NONE, negotiate_opts},
+ {"debug", CAM_CMD_DEBUG, CAM_ARG_NONE, "IPTSXcp"},
+ {"format", CAM_CMD_FORMAT, CAM_ARG_NONE, "qrwy"},
+ {"sanitize", CAM_CMD_SANITIZE, CAM_ARG_NONE, "a:c:IP:qrUwy"},
+ {"idle", CAM_CMD_IDLE, CAM_ARG_NONE, "t:"},
+ {"standby", CAM_CMD_STANDBY, CAM_ARG_NONE, "t:"},
+ {"sleep", CAM_CMD_SLEEP, CAM_ARG_NONE, ""},
+ {"apm", CAM_CMD_APM, CAM_ARG_NONE, "l:"},
+ {"aam", CAM_CMD_AAM, CAM_ARG_NONE, "l:"},
+ {"fwdownload", CAM_CMD_DOWNLOAD_FW, CAM_ARG_NONE, "f:ys"},
+ {"security", CAM_CMD_SECURITY, CAM_ARG_NONE, "d:e:fh:k:l:qs:T:U:y"},
+ {"hpa", CAM_CMD_HPA, CAM_ARG_NONE, "Pflp:qs:U:y"},
+ {"persist", CAM_CMD_PERSIST, CAM_ARG_NONE, "ai:I:k:K:o:ps:ST:U"},
+#endif /* MINIMALISTIC */
+ {"help", CAM_CMD_USAGE, CAM_ARG_NONE, NULL},
+ {"-?", CAM_CMD_USAGE, CAM_ARG_NONE, NULL},
+ {"-h", CAM_CMD_USAGE, CAM_ARG_NONE, NULL},
+ {NULL, 0, 0, NULL}
+};
+
+struct cam_devitem {
+ struct device_match_result dev_match;
+ int num_periphs;
+ struct periph_match_result *periph_matches;
+ struct scsi_vpd_device_id *device_id;
+ int device_id_len;
+ STAILQ_ENTRY(cam_devitem) links;
+};
+
+struct cam_devlist {
+ STAILQ_HEAD(, cam_devitem) dev_queue;
+ path_id_t path_id;
+};
+
+static cam_cmdmask cmdlist;
+static cam_argmask arglist;
+
+camcontrol_optret getoption(struct camcontrol_opts *table, char *arg,
+ uint32_t *cmdnum, cam_argmask *argnum,
+ const char **subopt);
+#ifndef MINIMALISTIC
+static int getdevlist(struct cam_device *device);
+#endif /* MINIMALISTIC */
+static int getdevtree(int argc, char **argv, char *combinedopt);
+#ifndef MINIMALISTIC
+static int testunitready(struct cam_device *device, int retry_count,
+ int timeout, int quiet);
+static int scsistart(struct cam_device *device, int startstop, int loadeject,
+ int retry_count, int timeout);
+static int scsiinquiry(struct cam_device *device, int retry_count, int timeout);
+static int scsiserial(struct cam_device *device, int retry_count, int timeout);
+static int camxferrate(struct cam_device *device);
+#endif /* MINIMALISTIC */
+static int parse_btl(char *tstr, path_id_t *bus, target_id_t *target,
+ lun_id_t *lun, cam_argmask *arglst);
+static int dorescan_or_reset(int argc, char **argv, int rescan);
+static int rescan_or_reset_bus(path_id_t bus, int rescan);
+static int scanlun_or_reset_dev(path_id_t bus, target_id_t target,
+ lun_id_t lun, int scan);
+#ifndef MINIMALISTIC
+static int readdefects(struct cam_device *device, int argc, char **argv,
+ char *combinedopt, int retry_count, int timeout);
+static void modepage(struct cam_device *device, int argc, char **argv,
+ char *combinedopt, int retry_count, int timeout);
+static int scsicmd(struct cam_device *device, int argc, char **argv,
+ char *combinedopt, int retry_count, int timeout);
+static int smpcmd(struct cam_device *device, int argc, char **argv,
+ char *combinedopt, int retry_count, int timeout);
+static int smpreportgeneral(struct cam_device *device, int argc, char **argv,
+ char *combinedopt, int retry_count, int timeout);
+static int smpphycontrol(struct cam_device *device, int argc, char **argv,
+ char *combinedopt, int retry_count, int timeout);
+static int smpmaninfo(struct cam_device *device, int argc, char **argv,
+ char *combinedopt, int retry_count, int timeout);
+static int getdevid(struct cam_devitem *item);
+static int buildbusdevlist(struct cam_devlist *devlist);
+static void freebusdevlist(struct cam_devlist *devlist);
+static struct cam_devitem *findsasdevice(struct cam_devlist *devlist,
+ uint64_t sasaddr);
+static int smpphylist(struct cam_device *device, int argc, char **argv,
+ char *combinedopt, int retry_count, int timeout);
+static int tagcontrol(struct cam_device *device, int argc, char **argv,
+ char *combinedopt);
+static void cts_print(struct cam_device *device,
+ struct ccb_trans_settings *cts);
+static void cpi_print(struct ccb_pathinq *cpi);
+static int get_cpi(struct cam_device *device, struct ccb_pathinq *cpi);
+static int get_cgd(struct cam_device *device, struct ccb_getdev *cgd);
+static int get_print_cts(struct cam_device *device, int user_settings,
+ int quiet, struct ccb_trans_settings *cts);
+static int ratecontrol(struct cam_device *device, int retry_count,
+ int timeout, int argc, char **argv, char *combinedopt);
+static int scsiformat(struct cam_device *device, int argc, char **argv,
+ char *combinedopt, int retry_count, int timeout);
+static int scsisanitize(struct cam_device *device, int argc, char **argv,
+ char *combinedopt, int retry_count, int timeout);
+static int scsireportluns(struct cam_device *device, int argc, char **argv,
+ char *combinedopt, int retry_count, int timeout);
+static int scsireadcapacity(struct cam_device *device, int argc, char **argv,
+ char *combinedopt, int retry_count, int timeout);
+static int atapm(struct cam_device *device, int argc, char **argv,
+ char *combinedopt, int retry_count, int timeout);
+static int atasecurity(struct cam_device *device, int retry_count, int timeout,
+ int argc, char **argv, char *combinedopt);
+static int atahpa(struct cam_device *device, int retry_count, int timeout,
+ int argc, char **argv, char *combinedopt);
+
+#endif /* MINIMALISTIC */
+#ifndef min
+#define min(a,b) (((a)<(b))?(a):(b))
+#endif
+#ifndef max
+#define max(a,b) (((a)>(b))?(a):(b))
+#endif
+
+camcontrol_optret
+getoption(struct camcontrol_opts *table, char *arg, uint32_t *cmdnum,
+ cam_argmask *argnum, const char **subopt)
+{
+ struct camcontrol_opts *opts;
+ int num_matches = 0;
+
+ for (opts = table; (opts != NULL) && (opts->optname != NULL);
+ opts++) {
+ if (strncmp(opts->optname, arg, strlen(arg)) == 0) {
+ *cmdnum = opts->cmdnum;
+ *argnum = opts->argnum;
+ *subopt = opts->subopt;
+ if (++num_matches > 1)
+ return(CC_OR_AMBIGUOUS);
+ }
+ }
+
+ if (num_matches > 0)
+ return(CC_OR_FOUND);
+ else
+ return(CC_OR_NOT_FOUND);
+}
+
+#ifndef MINIMALISTIC
+static int
+getdevlist(struct cam_device *device)
+{
+ union ccb *ccb;
+ char status[32];
+ int error = 0;
+
+ ccb = cam_getccb(device);
+
+ ccb->ccb_h.func_code = XPT_GDEVLIST;
+ ccb->ccb_h.flags = CAM_DIR_NONE;
+ ccb->ccb_h.retry_count = 1;
+ ccb->cgdl.index = 0;
+ ccb->cgdl.status = CAM_GDEVLIST_MORE_DEVS;
+ while (ccb->cgdl.status == CAM_GDEVLIST_MORE_DEVS) {
+ if (cam_send_ccb(device, ccb) < 0) {
+ perror("error getting device list");
+ cam_freeccb(ccb);
+ return(1);
+ }
+
+ status[0] = '\0';
+
+ switch (ccb->cgdl.status) {
+ case CAM_GDEVLIST_MORE_DEVS:
+ strcpy(status, "MORE");
+ break;
+ case CAM_GDEVLIST_LAST_DEVICE:
+ strcpy(status, "LAST");
+ break;
+ case CAM_GDEVLIST_LIST_CHANGED:
+ strcpy(status, "CHANGED");
+ break;
+ case CAM_GDEVLIST_ERROR:
+ strcpy(status, "ERROR");
+ error = 1;
+ break;
+ }
+
+ fprintf(stdout, "%s%d: generation: %d index: %d status: %s\n",
+ ccb->cgdl.periph_name,
+ ccb->cgdl.unit_number,
+ ccb->cgdl.generation,
+ ccb->cgdl.index,
+ status);
+
+ /*
+ * If the list has changed, we need to start over from the
+ * beginning.
+ */
+ if (ccb->cgdl.status == CAM_GDEVLIST_LIST_CHANGED)
+ ccb->cgdl.index = 0;
+ }
+
+ cam_freeccb(ccb);
+
+ return(error);
+}
+#endif /* MINIMALISTIC */
+
+static int
+getdevtree(int argc, char **argv, char *combinedopt)
+{
+ union ccb ccb;
+ int bufsize, fd;
+ unsigned int i;
+ int need_close = 0;
+ int error = 0;
+ int skip_device = 0;
+ int busonly = 0;
+ int c;
+
+ while ((c = getopt(argc, argv, combinedopt)) != -1) {
+ switch(c) {
+ case 'b':
+ if ((arglist & CAM_ARG_VERBOSE) == 0)
+ busonly = 1;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if ((fd = open(XPT_DEVICE, O_RDWR)) == -1) {
+ warn("couldn't open %s", XPT_DEVICE);
+ return(1);
+ }
+
+ bzero(&ccb, sizeof(union ccb));
+
+ ccb.ccb_h.path_id = CAM_XPT_PATH_ID;
+ ccb.ccb_h.target_id = CAM_TARGET_WILDCARD;
+ ccb.ccb_h.target_lun = CAM_LUN_WILDCARD;
+
+ ccb.ccb_h.func_code = XPT_DEV_MATCH;
+ bufsize = sizeof(struct dev_match_result) * 100;
+ ccb.cdm.match_buf_len = bufsize;
+ ccb.cdm.matches = (struct dev_match_result *)malloc(bufsize);
+ if (ccb.cdm.matches == NULL) {
+ warnx("can't malloc memory for matches");
+ close(fd);
+ return(1);
+ }
+ ccb.cdm.num_matches = 0;
+
+ /*
+ * We fetch all nodes, since we display most of them in the default
+ * case, and all in the verbose case.
+ */
+ ccb.cdm.num_patterns = 0;
+ ccb.cdm.pattern_buf_len = 0;
+
+ /*
+ * We do the ioctl multiple times if necessary, in case there are
+ * more than 100 nodes in the EDT.
+ */
+ do {
+ if (ioctl(fd, CAMIOCOMMAND, &ccb) == -1) {
+ warn("error sending CAMIOCOMMAND ioctl");
+ error = 1;
+ break;
+ }
+
+ if ((ccb.ccb_h.status != CAM_REQ_CMP)
+ || ((ccb.cdm.status != CAM_DEV_MATCH_LAST)
+ && (ccb.cdm.status != CAM_DEV_MATCH_MORE))) {
+ warnx("got CAM error %#x, CDM error %d\n",
+ ccb.ccb_h.status, ccb.cdm.status);
+ error = 1;
+ break;
+ }
+
+ for (i = 0; i < ccb.cdm.num_matches; i++) {
+ switch (ccb.cdm.matches[i].type) {
+ case DEV_MATCH_BUS: {
+ struct bus_match_result *bus_result;
+
+ /*
+ * Only print the bus information if the
+ * user turns on the verbose flag.
+ */
+ if ((busonly == 0) &&
+ (arglist & CAM_ARG_VERBOSE) == 0)
+ break;
+
+ bus_result =
+ &ccb.cdm.matches[i].result.bus_result;
+
+ if (need_close) {
+ fprintf(stdout, ")\n");
+ need_close = 0;
+ }
+
+ fprintf(stdout, "scbus%d on %s%d bus %d%s\n",
+ bus_result->path_id,
+ bus_result->dev_name,
+ bus_result->unit_number,
+ bus_result->bus_id,
+ (busonly ? "" : ":"));
+ break;
+ }
+ case DEV_MATCH_DEVICE: {
+ struct device_match_result *dev_result;
+ char vendor[16], product[48], revision[16];
+ char fw[5], tmpstr[256];
+
+ if (busonly == 1)
+ break;
+
+ dev_result =
+ &ccb.cdm.matches[i].result.device_result;
+
+ if ((dev_result->flags
+ & DEV_RESULT_UNCONFIGURED)
+ && ((arglist & CAM_ARG_VERBOSE) == 0)) {
+ skip_device = 1;
+ break;
+ } else
+ skip_device = 0;
+
+ if (dev_result->protocol == PROTO_SCSI) {
+ cam_strvis(vendor, dev_result->inq_data.vendor,
+ sizeof(dev_result->inq_data.vendor),
+ sizeof(vendor));
+ cam_strvis(product,
+ dev_result->inq_data.product,
+ sizeof(dev_result->inq_data.product),
+ sizeof(product));
+ cam_strvis(revision,
+ dev_result->inq_data.revision,
+ sizeof(dev_result->inq_data.revision),
+ sizeof(revision));
+ sprintf(tmpstr, "<%s %s %s>", vendor, product,
+ revision);
+ } else if (dev_result->protocol == PROTO_ATA ||
+ dev_result->protocol == PROTO_SATAPM) {
+ cam_strvis(product,
+ dev_result->ident_data.model,
+ sizeof(dev_result->ident_data.model),
+ sizeof(product));
+ cam_strvis(revision,
+ dev_result->ident_data.revision,
+ sizeof(dev_result->ident_data.revision),
+ sizeof(revision));
+ sprintf(tmpstr, "<%s %s>", product,
+ revision);
+ } else if (dev_result->protocol == PROTO_SEMB) {
+ struct sep_identify_data *sid;
+
+ sid = (struct sep_identify_data *)
+ &dev_result->ident_data;
+ cam_strvis(vendor, sid->vendor_id,
+ sizeof(sid->vendor_id),
+ sizeof(vendor));
+ cam_strvis(product, sid->product_id,
+ sizeof(sid->product_id),
+ sizeof(product));
+ cam_strvis(revision, sid->product_rev,
+ sizeof(sid->product_rev),
+ sizeof(revision));
+ cam_strvis(fw, sid->firmware_rev,
+ sizeof(sid->firmware_rev),
+ sizeof(fw));
+ sprintf(tmpstr, "<%s %s %s %s>",
+ vendor, product, revision, fw);
+ } else {
+ sprintf(tmpstr, "<>");
+ }
+ if (need_close) {
+ fprintf(stdout, ")\n");
+ need_close = 0;
+ }
+
+ fprintf(stdout, "%-33s at scbus%d "
+ "target %d lun %jx (",
+ tmpstr,
+ dev_result->path_id,
+ dev_result->target_id,
+ (uintmax_t)dev_result->target_lun);
+
+ need_close = 1;
+
+ break;
+ }
+ case DEV_MATCH_PERIPH: {
+ struct periph_match_result *periph_result;
+
+ periph_result =
+ &ccb.cdm.matches[i].result.periph_result;
+
+ if (busonly || skip_device != 0)
+ break;
+
+ if (need_close > 1)
+ fprintf(stdout, ",");
+
+ fprintf(stdout, "%s%d",
+ periph_result->periph_name,
+ periph_result->unit_number);
+
+ need_close++;
+ break;
+ }
+ default:
+ fprintf(stdout, "unknown match type\n");
+ break;
+ }
+ }
+
+ } while ((ccb.ccb_h.status == CAM_REQ_CMP)
+ && (ccb.cdm.status == CAM_DEV_MATCH_MORE));
+
+ if (need_close)
+ fprintf(stdout, ")\n");
+
+ close(fd);
+
+ return(error);
+}
+
+#ifndef MINIMALISTIC
+static int
+testunitready(struct cam_device *device, int retry_count, int timeout,
+ int quiet)
+{
+ int error = 0;
+ union ccb *ccb;
+
+ ccb = cam_getccb(device);
+
+ scsi_test_unit_ready(&ccb->csio,
+ /* retries */ retry_count,
+ /* cbfcnp */ NULL,
+ /* tag_action */ MSG_SIMPLE_Q_TAG,
+ /* sense_len */ SSD_FULL_SIZE,
+ /* timeout */ timeout ? timeout : 5000);
+
+ /* Disable freezing the device queue */
+ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
+
+ if (arglist & CAM_ARG_ERR_RECOVER)
+ ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER;
+
+ if (cam_send_ccb(device, ccb) < 0) {
+ if (quiet == 0)
+ perror("error sending test unit ready");
+
+ if (arglist & CAM_ARG_VERBOSE) {
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ }
+
+ cam_freeccb(ccb);
+ return(1);
+ }
+
+ if ((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) {
+ if (quiet == 0)
+ fprintf(stdout, "Unit is ready\n");
+ } else {
+ if (quiet == 0)
+ fprintf(stdout, "Unit is not ready\n");
+ error = 1;
+
+ if (arglist & CAM_ARG_VERBOSE) {
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ }
+ }
+
+ cam_freeccb(ccb);
+
+ return(error);
+}
+
+static int
+scsistart(struct cam_device *device, int startstop, int loadeject,
+ int retry_count, int timeout)
+{
+ union ccb *ccb;
+ int error = 0;
+
+ ccb = cam_getccb(device);
+
+ /*
+ * If we're stopping, send an ordered tag so the drive in question
+ * will finish any previously queued writes before stopping. If
+ * the device isn't capable of tagged queueing, or if tagged
+ * queueing is turned off, the tag action is a no-op.
+ */
+ scsi_start_stop(&ccb->csio,
+ /* retries */ retry_count,
+ /* cbfcnp */ NULL,
+ /* tag_action */ startstop ? MSG_SIMPLE_Q_TAG :
+ MSG_ORDERED_Q_TAG,
+ /* start/stop */ startstop,
+ /* load_eject */ loadeject,
+ /* immediate */ 0,
+ /* sense_len */ SSD_FULL_SIZE,
+ /* timeout */ timeout ? timeout : 120000);
+
+ /* Disable freezing the device queue */
+ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
+
+ if (arglist & CAM_ARG_ERR_RECOVER)
+ ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER;
+
+ if (cam_send_ccb(device, ccb) < 0) {
+ perror("error sending start unit");
+
+ if (arglist & CAM_ARG_VERBOSE) {
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ }
+
+ cam_freeccb(ccb);
+ return(1);
+ }
+
+ if ((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP)
+ if (startstop) {
+ fprintf(stdout, "Unit started successfully");
+ if (loadeject)
+ fprintf(stdout,", Media loaded\n");
+ else
+ fprintf(stdout,"\n");
+ } else {
+ fprintf(stdout, "Unit stopped successfully");
+ if (loadeject)
+ fprintf(stdout, ", Media ejected\n");
+ else
+ fprintf(stdout, "\n");
+ }
+ else {
+ error = 1;
+ if (startstop)
+ fprintf(stdout,
+ "Error received from start unit command\n");
+ else
+ fprintf(stdout,
+ "Error received from stop unit command\n");
+
+ if (arglist & CAM_ARG_VERBOSE) {
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ }
+ }
+
+ cam_freeccb(ccb);
+
+ return(error);
+}
+
+int
+scsidoinquiry(struct cam_device *device, int argc, char **argv,
+ char *combinedopt, int retry_count, int timeout)
+{
+ int c;
+ int error = 0;
+
+ while ((c = getopt(argc, argv, combinedopt)) != -1) {
+ switch(c) {
+ case 'D':
+ arglist |= CAM_ARG_GET_STDINQ;
+ break;
+ case 'R':
+ arglist |= CAM_ARG_GET_XFERRATE;
+ break;
+ case 'S':
+ arglist |= CAM_ARG_GET_SERIAL;
+ break;
+ default:
+ break;
+ }
+ }
+
+ /*
+ * If the user didn't specify any inquiry options, he wants all of
+ * them.
+ */
+ if ((arglist & CAM_ARG_INQ_MASK) == 0)
+ arglist |= CAM_ARG_INQ_MASK;
+
+ if (arglist & CAM_ARG_GET_STDINQ)
+ error = scsiinquiry(device, retry_count, timeout);
+
+ if (error != 0)
+ return(error);
+
+ if (arglist & CAM_ARG_GET_SERIAL)
+ scsiserial(device, retry_count, timeout);
+
+ if (error != 0)
+ return(error);
+
+ if (arglist & CAM_ARG_GET_XFERRATE)
+ error = camxferrate(device);
+
+ return(error);
+}
+
+static int
+scsiinquiry(struct cam_device *device, int retry_count, int timeout)
+{
+ union ccb *ccb;
+ struct scsi_inquiry_data *inq_buf;
+ int error = 0;
+
+ ccb = cam_getccb(device);
+
+ if (ccb == NULL) {
+ warnx("couldn't allocate CCB");
+ return(1);
+ }
+
+ /* cam_getccb cleans up the header, caller has to zero the payload */
+ bzero(&(&ccb->ccb_h)[1],
+ sizeof(struct ccb_scsiio) - sizeof(struct ccb_hdr));
+
+ inq_buf = (struct scsi_inquiry_data *)malloc(
+ sizeof(struct scsi_inquiry_data));
+
+ if (inq_buf == NULL) {
+ cam_freeccb(ccb);
+ warnx("can't malloc memory for inquiry\n");
+ return(1);
+ }
+ bzero(inq_buf, sizeof(*inq_buf));
+
+ /*
+ * Note that although the size of the inquiry buffer is the full
+ * 256 bytes specified in the SCSI spec, we only tell the device
+ * that we have allocated SHORT_INQUIRY_LENGTH bytes. There are
+ * two reasons for this:
+ *
+ * - The SCSI spec says that when a length field is only 1 byte,
+ * a value of 0 will be interpreted as 256. Therefore
+ * scsi_inquiry() will convert an inq_len (which is passed in as
+ * a u_int32_t, but the field in the CDB is only 1 byte) of 256
+ * to 0. Evidently, very few devices meet the spec in that
+ * regard. Some devices, like many Seagate disks, take the 0 as
+ * 0, and don't return any data. One Pioneer DVD-R drive
+ * returns more data than the command asked for.
+ *
+ * So, since there are numerous devices that just don't work
+ * right with the full inquiry size, we don't send the full size.
+ *
+ * - The second reason not to use the full inquiry data length is
+ * that we don't need it here. The only reason we issue a
+ * standard inquiry is to get the vendor name, device name,
+ * and revision so scsi_print_inquiry() can print them.
+ *
+ * If, at some point in the future, more inquiry data is needed for
+ * some reason, this code should use a procedure similar to the
+ * probe code. i.e., issue a short inquiry, and determine from
+ * the additional length passed back from the device how much
+ * inquiry data the device supports. Once the amount the device
+ * supports is determined, issue an inquiry for that amount and no
+ * more.
+ *
+ * KDM, 2/18/2000
+ */
+ scsi_inquiry(&ccb->csio,
+ /* retries */ retry_count,
+ /* cbfcnp */ NULL,
+ /* tag_action */ MSG_SIMPLE_Q_TAG,
+ /* inq_buf */ (u_int8_t *)inq_buf,
+ /* inq_len */ SHORT_INQUIRY_LENGTH,
+ /* evpd */ 0,
+ /* page_code */ 0,
+ /* sense_len */ SSD_FULL_SIZE,
+ /* timeout */ timeout ? timeout : 5000);
+
+ /* Disable freezing the device queue */
+ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
+
+ if (arglist & CAM_ARG_ERR_RECOVER)
+ ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER;
+
+ if (cam_send_ccb(device, ccb) < 0) {
+ perror("error sending SCSI inquiry");
+
+ if (arglist & CAM_ARG_VERBOSE) {
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ }
+
+ cam_freeccb(ccb);
+ return(1);
+ }
+
+ if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
+ error = 1;
+
+ if (arglist & CAM_ARG_VERBOSE) {
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ }
+ }
+
+ cam_freeccb(ccb);
+
+ if (error != 0) {
+ free(inq_buf);
+ return(error);
+ }
+
+ fprintf(stdout, "%s%d: ", device->device_name,
+ device->dev_unit_num);
+ scsi_print_inquiry(inq_buf);
+
+ free(inq_buf);
+
+ return(0);
+}
+
+static int
+scsiserial(struct cam_device *device, int retry_count, int timeout)
+{
+ union ccb *ccb;
+ struct scsi_vpd_unit_serial_number *serial_buf;
+ char serial_num[SVPD_SERIAL_NUM_SIZE + 1];
+ int error = 0;
+
+ ccb = cam_getccb(device);
+
+ if (ccb == NULL) {
+ warnx("couldn't allocate CCB");
+ return(1);
+ }
+
+ /* cam_getccb cleans up the header, caller has to zero the payload */
+ bzero(&(&ccb->ccb_h)[1],
+ sizeof(struct ccb_scsiio) - sizeof(struct ccb_hdr));
+
+ serial_buf = (struct scsi_vpd_unit_serial_number *)
+ malloc(sizeof(*serial_buf));
+
+ if (serial_buf == NULL) {
+ cam_freeccb(ccb);
+ warnx("can't malloc memory for serial number");
+ return(1);
+ }
+
+ scsi_inquiry(&ccb->csio,
+ /*retries*/ retry_count,
+ /*cbfcnp*/ NULL,
+ /* tag_action */ MSG_SIMPLE_Q_TAG,
+ /* inq_buf */ (u_int8_t *)serial_buf,
+ /* inq_len */ sizeof(*serial_buf),
+ /* evpd */ 1,
+ /* page_code */ SVPD_UNIT_SERIAL_NUMBER,
+ /* sense_len */ SSD_FULL_SIZE,
+ /* timeout */ timeout ? timeout : 5000);
+
+ /* Disable freezing the device queue */
+ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
+
+ if (arglist & CAM_ARG_ERR_RECOVER)
+ ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER;
+
+ if (cam_send_ccb(device, ccb) < 0) {
+ warn("error getting serial number");
+
+ if (arglist & CAM_ARG_VERBOSE) {
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ }
+
+ cam_freeccb(ccb);
+ free(serial_buf);
+ return(1);
+ }
+
+ if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
+ error = 1;
+
+ if (arglist & CAM_ARG_VERBOSE) {
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ }
+ }
+
+ cam_freeccb(ccb);
+
+ if (error != 0) {
+ free(serial_buf);
+ return(error);
+ }
+
+ bcopy(serial_buf->serial_num, serial_num, serial_buf->length);
+ serial_num[serial_buf->length] = '\0';
+
+ if ((arglist & CAM_ARG_GET_STDINQ)
+ || (arglist & CAM_ARG_GET_XFERRATE))
+ fprintf(stdout, "%s%d: Serial Number ",
+ device->device_name, device->dev_unit_num);
+
+ fprintf(stdout, "%.60s\n", serial_num);
+
+ free(serial_buf);
+
+ return(0);
+}
+
+static int
+camxferrate(struct cam_device *device)
+{
+ struct ccb_pathinq cpi;
+ u_int32_t freq = 0;
+ u_int32_t speed = 0;
+ union ccb *ccb;
+ u_int mb;
+ int retval = 0;
+
+ if ((retval = get_cpi(device, &cpi)) != 0)
+ return (1);
+
+ ccb = cam_getccb(device);
+
+ if (ccb == NULL) {
+ warnx("couldn't allocate CCB");
+ return(1);
+ }
+
+ bzero(&(&ccb->ccb_h)[1],
+ sizeof(struct ccb_trans_settings) - sizeof(struct ccb_hdr));
+
+ ccb->ccb_h.func_code = XPT_GET_TRAN_SETTINGS;
+ ccb->cts.type = CTS_TYPE_CURRENT_SETTINGS;
+
+ if (((retval = cam_send_ccb(device, ccb)) < 0)
+ || ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP)) {
+ const char error_string[] = "error getting transfer settings";
+
+ if (retval < 0)
+ warn(error_string);
+ else
+ warnx(error_string);
+
+ if (arglist & CAM_ARG_VERBOSE)
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+
+ retval = 1;
+
+ goto xferrate_bailout;
+
+ }
+
+ speed = cpi.base_transfer_speed;
+ freq = 0;
+ if (ccb->cts.transport == XPORT_SPI) {
+ struct ccb_trans_settings_spi *spi =
+ &ccb->cts.xport_specific.spi;
+
+ if ((spi->valid & CTS_SPI_VALID_SYNC_RATE) != 0) {
+ freq = scsi_calc_syncsrate(spi->sync_period);
+ speed = freq;
+ }
+ if ((spi->valid & CTS_SPI_VALID_BUS_WIDTH) != 0) {
+ speed *= (0x01 << spi->bus_width);
+ }
+ } else if (ccb->cts.transport == XPORT_FC) {
+ struct ccb_trans_settings_fc *fc =
+ &ccb->cts.xport_specific.fc;
+
+ if (fc->valid & CTS_FC_VALID_SPEED)
+ speed = fc->bitrate;
+ } else if (ccb->cts.transport == XPORT_SAS) {
+ struct ccb_trans_settings_sas *sas =
+ &ccb->cts.xport_specific.sas;
+
+ if (sas->valid & CTS_SAS_VALID_SPEED)
+ speed = sas->bitrate;
+ } else if (ccb->cts.transport == XPORT_ATA) {
+ struct ccb_trans_settings_pata *pata =
+ &ccb->cts.xport_specific.ata;
+
+ if (pata->valid & CTS_ATA_VALID_MODE)
+ speed = ata_mode2speed(pata->mode);
+ } else if (ccb->cts.transport == XPORT_SATA) {
+ struct ccb_trans_settings_sata *sata =
+ &ccb->cts.xport_specific.sata;
+
+ if (sata->valid & CTS_SATA_VALID_REVISION)
+ speed = ata_revision2speed(sata->revision);
+ }
+
+ mb = speed / 1000;
+ if (mb > 0) {
+ fprintf(stdout, "%s%d: %d.%03dMB/s transfers",
+ device->device_name, device->dev_unit_num,
+ mb, speed % 1000);
+ } else {
+ fprintf(stdout, "%s%d: %dKB/s transfers",
+ device->device_name, device->dev_unit_num,
+ speed);
+ }
+
+ if (ccb->cts.transport == XPORT_SPI) {
+ struct ccb_trans_settings_spi *spi =
+ &ccb->cts.xport_specific.spi;
+
+ if (((spi->valid & CTS_SPI_VALID_SYNC_OFFSET) != 0)
+ && (spi->sync_offset != 0))
+ fprintf(stdout, " (%d.%03dMHz, offset %d", freq / 1000,
+ freq % 1000, spi->sync_offset);
+
+ if (((spi->valid & CTS_SPI_VALID_BUS_WIDTH) != 0)
+ && (spi->bus_width > 0)) {
+ if (((spi->valid & CTS_SPI_VALID_SYNC_OFFSET) != 0)
+ && (spi->sync_offset != 0)) {
+ fprintf(stdout, ", ");
+ } else {
+ fprintf(stdout, " (");
+ }
+ fprintf(stdout, "%dbit)", 8 * (0x01 << spi->bus_width));
+ } else if (((spi->valid & CTS_SPI_VALID_SYNC_OFFSET) != 0)
+ && (spi->sync_offset != 0)) {
+ fprintf(stdout, ")");
+ }
+ } else if (ccb->cts.transport == XPORT_ATA) {
+ struct ccb_trans_settings_pata *pata =
+ &ccb->cts.xport_specific.ata;
+
+ printf(" (");
+ if (pata->valid & CTS_ATA_VALID_MODE)
+ printf("%s, ", ata_mode2string(pata->mode));
+ if ((pata->valid & CTS_ATA_VALID_ATAPI) && pata->atapi != 0)
+ printf("ATAPI %dbytes, ", pata->atapi);
+ if (pata->valid & CTS_ATA_VALID_BYTECOUNT)
+ printf("PIO %dbytes", pata->bytecount);
+ printf(")");
+ } else if (ccb->cts.transport == XPORT_SATA) {
+ struct ccb_trans_settings_sata *sata =
+ &ccb->cts.xport_specific.sata;
+
+ printf(" (");
+ if (sata->valid & CTS_SATA_VALID_REVISION)
+ printf("SATA %d.x, ", sata->revision);
+ else
+ printf("SATA, ");
+ if (sata->valid & CTS_SATA_VALID_MODE)
+ printf("%s, ", ata_mode2string(sata->mode));
+ if ((sata->valid & CTS_SATA_VALID_ATAPI) && sata->atapi != 0)
+ printf("ATAPI %dbytes, ", sata->atapi);
+ if (sata->valid & CTS_SATA_VALID_BYTECOUNT)
+ printf("PIO %dbytes", sata->bytecount);
+ printf(")");
+ }
+
+ if (ccb->cts.protocol == PROTO_SCSI) {
+ struct ccb_trans_settings_scsi *scsi =
+ &ccb->cts.proto_specific.scsi;
+ if (scsi->valid & CTS_SCSI_VALID_TQ) {
+ if (scsi->flags & CTS_SCSI_FLAGS_TAG_ENB) {
+ fprintf(stdout, ", Command Queueing Enabled");
+ }
+ }
+ }
+
+ fprintf(stdout, "\n");
+
+xferrate_bailout:
+
+ cam_freeccb(ccb);
+
+ return(retval);
+}
+
+static void
+atahpa_print(struct ata_params *parm, u_int64_t hpasize, int header)
+{
+ u_int32_t lbasize = (u_int32_t)parm->lba_size_1 |
+ ((u_int32_t)parm->lba_size_2 << 16);
+
+ u_int64_t lbasize48 = ((u_int64_t)parm->lba_size48_1) |
+ ((u_int64_t)parm->lba_size48_2 << 16) |
+ ((u_int64_t)parm->lba_size48_3 << 32) |
+ ((u_int64_t)parm->lba_size48_4 << 48);
+
+ if (header) {
+ printf("\nFeature "
+ "Support Enabled Value\n");
+ }
+
+ printf("Host Protected Area (HPA) ");
+ if (parm->support.command1 & ATA_SUPPORT_PROTECTED) {
+ u_int64_t lba = lbasize48 ? lbasize48 : lbasize;
+ printf("yes %s %ju/%ju\n", (hpasize > lba) ? "yes" : "no ",
+ lba, hpasize);
+
+ printf("HPA - Security ");
+ if (parm->support.command1 & ATA_SUPPORT_MAXSECURITY)
+ printf("yes\n");
+ else
+ printf("no\n");
+ } else {
+ printf("no\n");
+ }
+}
+
+static int
+atasata(struct ata_params *parm)
+{
+
+
+ if (parm->satacapabilities != 0xffff &&
+ parm->satacapabilities != 0x0000)
+ return 1;
+
+ return 0;
+}
+
+static void
+atacapprint(struct ata_params *parm)
+{
+ u_int32_t lbasize = (u_int32_t)parm->lba_size_1 |
+ ((u_int32_t)parm->lba_size_2 << 16);
+
+ u_int64_t lbasize48 = ((u_int64_t)parm->lba_size48_1) |
+ ((u_int64_t)parm->lba_size48_2 << 16) |
+ ((u_int64_t)parm->lba_size48_3 << 32) |
+ ((u_int64_t)parm->lba_size48_4 << 48);
+
+ printf("\n");
+ printf("protocol ");
+ printf("ATA/ATAPI-%d", ata_version(parm->version_major));
+ if (parm->satacapabilities && parm->satacapabilities != 0xffff) {
+ if (parm->satacapabilities & ATA_SATA_GEN3)
+ printf(" SATA 3.x\n");
+ else if (parm->satacapabilities & ATA_SATA_GEN2)
+ printf(" SATA 2.x\n");
+ else if (parm->satacapabilities & ATA_SATA_GEN1)
+ printf(" SATA 1.x\n");
+ else
+ printf(" SATA\n");
+ }
+ else
+ printf("\n");
+ printf("device model %.40s\n", parm->model);
+ printf("firmware revision %.8s\n", parm->revision);
+ printf("serial number %.20s\n", parm->serial);
+ if (parm->enabled.extension & ATA_SUPPORT_64BITWWN) {
+ printf("WWN %04x%04x%04x%04x\n",
+ parm->wwn[0], parm->wwn[1], parm->wwn[2], parm->wwn[3]);
+ }
+ if (parm->enabled.extension & ATA_SUPPORT_MEDIASN) {
+ printf("media serial number %.30s\n",
+ parm->media_serial);
+ }
+
+ printf("cylinders %d\n", parm->cylinders);
+ printf("heads %d\n", parm->heads);
+ printf("sectors/track %d\n", parm->sectors);
+ printf("sector size logical %u, physical %lu, offset %lu\n",
+ ata_logical_sector_size(parm),
+ (unsigned long)ata_physical_sector_size(parm),
+ (unsigned long)ata_logical_sector_offset(parm));
+
+ if (parm->config == ATA_PROTO_CFA ||
+ (parm->support.command2 & ATA_SUPPORT_CFA))
+ printf("CFA supported\n");
+
+ printf("LBA%ssupported ",
+ parm->capabilities1 & ATA_SUPPORT_LBA ? " " : " not ");
+ if (lbasize)
+ printf("%d sectors\n", lbasize);
+ else
+ printf("\n");
+
+ printf("LBA48%ssupported ",
+ parm->support.command2 & ATA_SUPPORT_ADDRESS48 ? " " : " not ");
+ if (lbasize48)
+ printf("%ju sectors\n", (uintmax_t)lbasize48);
+ else
+ printf("\n");
+
+ printf("PIO supported PIO");
+ switch (ata_max_pmode(parm)) {
+ case ATA_PIO4:
+ printf("4");
+ break;
+ case ATA_PIO3:
+ printf("3");
+ break;
+ case ATA_PIO2:
+ printf("2");
+ break;
+ case ATA_PIO1:
+ printf("1");
+ break;
+ default:
+ printf("0");
+ }
+ if ((parm->capabilities1 & ATA_SUPPORT_IORDY) == 0)
+ printf(" w/o IORDY");
+ printf("\n");
+
+ printf("DMA%ssupported ",
+ parm->capabilities1 & ATA_SUPPORT_DMA ? " " : " not ");
+ if (parm->capabilities1 & ATA_SUPPORT_DMA) {
+ if (parm->mwdmamodes & 0xff) {
+ printf("WDMA");
+ if (parm->mwdmamodes & 0x04)
+ printf("2");
+ else if (parm->mwdmamodes & 0x02)
+ printf("1");
+ else if (parm->mwdmamodes & 0x01)
+ printf("0");
+ printf(" ");
+ }
+ if ((parm->atavalid & ATA_FLAG_88) &&
+ (parm->udmamodes & 0xff)) {
+ printf("UDMA");
+ if (parm->udmamodes & 0x40)
+ printf("6");
+ else if (parm->udmamodes & 0x20)
+ printf("5");
+ else if (parm->udmamodes & 0x10)
+ printf("4");
+ else if (parm->udmamodes & 0x08)
+ printf("3");
+ else if (parm->udmamodes & 0x04)
+ printf("2");
+ else if (parm->udmamodes & 0x02)
+ printf("1");
+ else if (parm->udmamodes & 0x01)
+ printf("0");
+ printf(" ");
+ }
+ }
+ printf("\n");
+
+ if (parm->media_rotation_rate == 1) {
+ printf("media RPM non-rotating\n");
+ } else if (parm->media_rotation_rate >= 0x0401 &&
+ parm->media_rotation_rate <= 0xFFFE) {
+ printf("media RPM %d\n",
+ parm->media_rotation_rate);
+ }
+
+ printf("\nFeature "
+ "Support Enabled Value Vendor\n");
+ printf("read ahead %s %s\n",
+ parm->support.command1 & ATA_SUPPORT_LOOKAHEAD ? "yes" : "no",
+ parm->enabled.command1 & ATA_SUPPORT_LOOKAHEAD ? "yes" : "no");
+ printf("write cache %s %s\n",
+ parm->support.command1 & ATA_SUPPORT_WRITECACHE ? "yes" : "no",
+ parm->enabled.command1 & ATA_SUPPORT_WRITECACHE ? "yes" : "no");
+ printf("flush cache %s %s\n",
+ parm->support.command2 & ATA_SUPPORT_FLUSHCACHE ? "yes" : "no",
+ parm->enabled.command2 & ATA_SUPPORT_FLUSHCACHE ? "yes" : "no");
+ printf("overlap %s\n",
+ parm->capabilities1 & ATA_SUPPORT_OVERLAP ? "yes" : "no");
+ printf("Tagged Command Queuing (TCQ) %s %s",
+ parm->support.command2 & ATA_SUPPORT_QUEUED ? "yes" : "no",
+ parm->enabled.command2 & ATA_SUPPORT_QUEUED ? "yes" : "no");
+ if (parm->support.command2 & ATA_SUPPORT_QUEUED) {
+ printf(" %d tags\n",
+ ATA_QUEUE_LEN(parm->queue) + 1);
+ } else
+ printf("\n");
+ printf("Native Command Queuing (NCQ) ");
+ if (parm->satacapabilities != 0xffff &&
+ (parm->satacapabilities & ATA_SUPPORT_NCQ)) {
+ printf("yes %d tags\n",
+ ATA_QUEUE_LEN(parm->queue) + 1);
+ } else
+ printf("no\n");
+
+ printf("NCQ Queue Management %s\n", atasata(parm) &&
+ parm->satacapabilities2 & ATA_SUPPORT_NCQ_QMANAGEMENT ?
+ "yes" : "no");
+ printf("NCQ Streaming %s\n", atasata(parm) &&
+ parm->satacapabilities2 & ATA_SUPPORT_NCQ_STREAM ?
+ "yes" : "no");
+ printf("Receive & Send FPDMA Queued %s\n", atasata(parm) &&
+ parm->satacapabilities2 & ATA_SUPPORT_RCVSND_FPDMA_QUEUED ?
+ "yes" : "no");
+
+ printf("SMART %s %s\n",
+ parm->support.command1 & ATA_SUPPORT_SMART ? "yes" : "no",
+ parm->enabled.command1 & ATA_SUPPORT_SMART ? "yes" : "no");
+ printf("microcode download %s %s\n",
+ parm->support.command2 & ATA_SUPPORT_MICROCODE ? "yes" : "no",
+ parm->enabled.command2 & ATA_SUPPORT_MICROCODE ? "yes" : "no");
+ printf("security %s %s\n",
+ parm->support.command1 & ATA_SUPPORT_SECURITY ? "yes" : "no",
+ parm->enabled.command1 & ATA_SUPPORT_SECURITY ? "yes" : "no");
+ printf("power management %s %s\n",
+ parm->support.command1 & ATA_SUPPORT_POWERMGT ? "yes" : "no",
+ parm->enabled.command1 & ATA_SUPPORT_POWERMGT ? "yes" : "no");
+ printf("advanced power management %s %s",
+ parm->support.command2 & ATA_SUPPORT_APM ? "yes" : "no",
+ parm->enabled.command2 & ATA_SUPPORT_APM ? "yes" : "no");
+ if (parm->support.command2 & ATA_SUPPORT_APM) {
+ printf(" %d/0x%02X\n",
+ parm->apm_value & 0xff, parm->apm_value & 0xff);
+ } else
+ printf("\n");
+ printf("automatic acoustic management %s %s",
+ parm->support.command2 & ATA_SUPPORT_AUTOACOUSTIC ? "yes" :"no",
+ parm->enabled.command2 & ATA_SUPPORT_AUTOACOUSTIC ? "yes" :"no");
+ if (parm->support.command2 & ATA_SUPPORT_AUTOACOUSTIC) {
+ printf(" %d/0x%02X %d/0x%02X\n",
+ ATA_ACOUSTIC_CURRENT(parm->acoustic),
+ ATA_ACOUSTIC_CURRENT(parm->acoustic),
+ ATA_ACOUSTIC_VENDOR(parm->acoustic),
+ ATA_ACOUSTIC_VENDOR(parm->acoustic));
+ } else
+ printf("\n");
+ printf("media status notification %s %s\n",
+ parm->support.command2 & ATA_SUPPORT_NOTIFY ? "yes" : "no",
+ parm->enabled.command2 & ATA_SUPPORT_NOTIFY ? "yes" : "no");
+ printf("power-up in Standby %s %s\n",
+ parm->support.command2 & ATA_SUPPORT_STANDBY ? "yes" : "no",
+ parm->enabled.command2 & ATA_SUPPORT_STANDBY ? "yes" : "no");
+ printf("write-read-verify %s %s",
+ parm->support2 & ATA_SUPPORT_WRITEREADVERIFY ? "yes" : "no",
+ parm->enabled2 & ATA_SUPPORT_WRITEREADVERIFY ? "yes" : "no");
+ if (parm->support2 & ATA_SUPPORT_WRITEREADVERIFY) {
+ printf(" %d/0x%x\n",
+ parm->wrv_mode, parm->wrv_mode);
+ } else
+ printf("\n");
+ printf("unload %s %s\n",
+ parm->support.extension & ATA_SUPPORT_UNLOAD ? "yes" : "no",
+ parm->enabled.extension & ATA_SUPPORT_UNLOAD ? "yes" : "no");
+ printf("general purpose logging %s %s\n",
+ parm->support.extension & ATA_SUPPORT_GENLOG ? "yes" : "no",
+ parm->enabled.extension & ATA_SUPPORT_GENLOG ? "yes" : "no");
+ printf("free-fall %s %s\n",
+ parm->support2 & ATA_SUPPORT_FREEFALL ? "yes" : "no",
+ parm->enabled2 & ATA_SUPPORT_FREEFALL ? "yes" : "no");
+ printf("Data Set Management (DSM/TRIM) ");
+ if (parm->support_dsm & ATA_SUPPORT_DSM_TRIM) {
+ printf("yes\n");
+ printf("DSM - max 512byte blocks ");
+ if (parm->max_dsm_blocks == 0x00)
+ printf("yes not specified\n");
+ else
+ printf("yes %d\n",
+ parm->max_dsm_blocks);
+
+ printf("DSM - deterministic read ");
+ if (parm->support3 & ATA_SUPPORT_DRAT) {
+ if (parm->support3 & ATA_SUPPORT_RZAT)
+ printf("yes zeroed\n");
+ else
+ printf("yes any value\n");
+ } else {
+ printf("no\n");
+ }
+ } else {
+ printf("no\n");
+ }
+}
+
+static int
+scsi_cam_pass_16_send(struct cam_device *device, union ccb *ccb, int quiet)
+{
+ struct ata_pass_16 *ata_pass_16;
+ struct ata_cmd ata_cmd;
+
+ ata_pass_16 = (struct ata_pass_16 *)ccb->csio.cdb_io.cdb_bytes;
+ ata_cmd.command = ata_pass_16->command;
+ ata_cmd.control = ata_pass_16->control;
+ ata_cmd.features = ata_pass_16->features;
+
+ if (arglist & CAM_ARG_VERBOSE) {
+ warnx("sending ATA %s via pass_16 with timeout of %u msecs",
+ ata_op_string(&ata_cmd),
+ ccb->csio.ccb_h.timeout);
+ }
+
+ /* Disable freezing the device queue */
+ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
+
+ if (arglist & CAM_ARG_ERR_RECOVER)
+ ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER;
+
+ if (cam_send_ccb(device, ccb) < 0) {
+ if (quiet != 1 || arglist & CAM_ARG_VERBOSE) {
+ warn("error sending ATA %s via pass_16",
+ ata_op_string(&ata_cmd));
+ }
+
+ if (arglist & CAM_ARG_VERBOSE) {
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ }
+
+ return (1);
+ }
+
+ if (!(ata_pass_16->flags & AP_FLAG_CHK_COND) &&
+ (ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
+ if (quiet != 1 || arglist & CAM_ARG_VERBOSE) {
+ warnx("ATA %s via pass_16 failed",
+ ata_op_string(&ata_cmd));
+ }
+ if (arglist & CAM_ARG_VERBOSE) {
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ }
+
+ return (1);
+ }
+
+ return (0);
+}
+
+
+static int
+ata_cam_send(struct cam_device *device, union ccb *ccb, int quiet)
+{
+ if (arglist & CAM_ARG_VERBOSE) {
+ warnx("sending ATA %s with timeout of %u msecs",
+ ata_op_string(&(ccb->ataio.cmd)),
+ ccb->ataio.ccb_h.timeout);
+ }
+
+ /* Disable freezing the device queue */
+ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
+
+ if (arglist & CAM_ARG_ERR_RECOVER)
+ ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER;
+
+ if (cam_send_ccb(device, ccb) < 0) {
+ if (quiet != 1 || arglist & CAM_ARG_VERBOSE) {
+ warn("error sending ATA %s",
+ ata_op_string(&(ccb->ataio.cmd)));
+ }
+
+ if (arglist & CAM_ARG_VERBOSE) {
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ }
+
+ return (1);
+ }
+
+ if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
+ if (quiet != 1 || arglist & CAM_ARG_VERBOSE) {
+ warnx("ATA %s failed: %d",
+ ata_op_string(&(ccb->ataio.cmd)), quiet);
+ }
+
+ if (arglist & CAM_ARG_VERBOSE) {
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ }
+
+ return (1);
+ }
+
+ return (0);
+}
+
+static int
+ata_do_pass_16(struct cam_device *device, union ccb *ccb, int retries,
+ u_int32_t flags, u_int8_t protocol, u_int8_t ata_flags,
+ u_int8_t tag_action, u_int8_t command, u_int8_t features,
+ u_int64_t lba, u_int8_t sector_count, u_int8_t *data_ptr,
+ u_int16_t dxfer_len, int timeout, int quiet)
+{
+ if (data_ptr != NULL) {
+ ata_flags |= AP_FLAG_BYT_BLOK_BYTES |
+ AP_FLAG_TLEN_SECT_CNT;
+ if (flags & CAM_DIR_OUT)
+ ata_flags |= AP_FLAG_TDIR_TO_DEV;
+ else
+ ata_flags |= AP_FLAG_TDIR_FROM_DEV;
+ } else {
+ ata_flags |= AP_FLAG_TLEN_NO_DATA;
+ }
+
+ bzero(&(&ccb->ccb_h)[1],
+ sizeof(struct ccb_scsiio) - sizeof(struct ccb_hdr));
+
+ scsi_ata_pass_16(&ccb->csio,
+ retries,
+ NULL,
+ flags,
+ tag_action,
+ protocol,
+ ata_flags,
+ features,
+ sector_count,
+ lba,
+ command,
+ /*control*/0,
+ data_ptr,
+ dxfer_len,
+ /*sense_len*/SSD_FULL_SIZE,
+ timeout);
+
+ return scsi_cam_pass_16_send(device, ccb, quiet);
+}
+
+static int
+ata_try_pass_16(struct cam_device *device)
+{
+ struct ccb_pathinq cpi;
+
+ if (get_cpi(device, &cpi) != 0) {
+ warnx("couldn't get CPI");
+ return (-1);
+ }
+
+ if (cpi.protocol == PROTO_SCSI) {
+ /* possibly compatible with pass_16 */
+ return (1);
+ }
+
+ /* likely not compatible with pass_16 */
+ return (0);
+}
+
+static int
+ata_do_28bit_cmd(struct cam_device *device, union ccb *ccb, int retries,
+ u_int32_t flags, u_int8_t protocol, u_int8_t tag_action,
+ u_int8_t command, u_int8_t features, u_int32_t lba,
+ u_int8_t sector_count, u_int8_t *data_ptr, u_int16_t dxfer_len,
+ int timeout, int quiet)
+{
+
+
+ switch (ata_try_pass_16(device)) {
+ case -1:
+ return (1);
+ case 1:
+ /* Try using SCSI Passthrough */
+ return ata_do_pass_16(device, ccb, retries, flags, protocol,
+ 0, tag_action, command, features, lba,
+ sector_count, data_ptr, dxfer_len,
+ timeout, quiet);
+ }
+
+ bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_ataio) -
+ sizeof(struct ccb_hdr));
+ cam_fill_ataio(&ccb->ataio,
+ retries,
+ NULL,
+ flags,
+ tag_action,
+ data_ptr,
+ dxfer_len,
+ timeout);
+
+ ata_28bit_cmd(&ccb->ataio, command, features, lba, sector_count);
+ return ata_cam_send(device, ccb, quiet);
+}
+
+static int
+ata_do_cmd(struct cam_device *device, union ccb *ccb, int retries,
+ u_int32_t flags, u_int8_t protocol, u_int8_t ata_flags,
+ u_int8_t tag_action, u_int8_t command, u_int8_t features,
+ u_int64_t lba, u_int8_t sector_count, u_int8_t *data_ptr,
+ u_int16_t dxfer_len, int timeout, int force48bit)
+{
+ int retval;
+
+ retval = ata_try_pass_16(device);
+ if (retval == -1)
+ return (1);
+
+ if (retval == 1) {
+ int error;
+
+ /* Try using SCSI Passthrough */
+ error = ata_do_pass_16(device, ccb, retries, flags, protocol,
+ ata_flags, tag_action, command, features,
+ lba, sector_count, data_ptr, dxfer_len,
+ timeout, 0);
+
+ if (ata_flags & AP_FLAG_CHK_COND) {
+ /* Decode ata_res from sense data */
+ struct ata_res_pass16 *res_pass16;
+ struct ata_res *res;
+ u_int i;
+ u_int16_t *ptr;
+
+ /* sense_data is 4 byte aligned */
+ ptr = (uint16_t*)(uintptr_t)&ccb->csio.sense_data;
+ for (i = 0; i < sizeof(*res_pass16) / 2; i++)
+ ptr[i] = le16toh(ptr[i]);
+
+ /* sense_data is 4 byte aligned */
+ res_pass16 = (struct ata_res_pass16 *)(uintptr_t)
+ &ccb->csio.sense_data;
+ res = &ccb->ataio.res;
+ res->flags = res_pass16->flags;
+ res->status = res_pass16->status;
+ res->error = res_pass16->error;
+ res->lba_low = res_pass16->lba_low;
+ res->lba_mid = res_pass16->lba_mid;
+ res->lba_high = res_pass16->lba_high;
+ res->device = res_pass16->device;
+ res->lba_low_exp = res_pass16->lba_low_exp;
+ res->lba_mid_exp = res_pass16->lba_mid_exp;
+ res->lba_high_exp = res_pass16->lba_high_exp;
+ res->sector_count = res_pass16->sector_count;
+ res->sector_count_exp = res_pass16->sector_count_exp;
+ }
+
+ return (error);
+ }
+
+ bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_ataio) -
+ sizeof(struct ccb_hdr));
+ cam_fill_ataio(&ccb->ataio,
+ retries,
+ NULL,
+ flags,
+ tag_action,
+ data_ptr,
+ dxfer_len,
+ timeout);
+
+ if (force48bit || lba > ATA_MAX_28BIT_LBA)
+ ata_48bit_cmd(&ccb->ataio, command, features, lba, sector_count);
+ else
+ ata_28bit_cmd(&ccb->ataio, command, features, lba, sector_count);
+
+ if (ata_flags & AP_FLAG_CHK_COND)
+ ccb->ataio.cmd.flags |= CAM_ATAIO_NEEDRESULT;
+
+ return ata_cam_send(device, ccb, 0);
+}
+
+static void
+dump_data(uint16_t *ptr, uint32_t len)
+{
+ u_int i;
+
+ for (i = 0; i < len / 2; i++) {
+ if ((i % 8) == 0)
+ printf(" %3d: ", i);
+ printf("%04hx ", ptr[i]);
+ if ((i % 8) == 7)
+ printf("\n");
+ }
+ if ((i % 8) != 7)
+ printf("\n");
+}
+
+static int
+atahpa_proc_resp(struct cam_device *device, union ccb *ccb,
+ int is48bit, u_int64_t *hpasize)
+{
+ struct ata_res *res;
+
+ res = &ccb->ataio.res;
+ if (res->status & ATA_STATUS_ERROR) {
+ if (arglist & CAM_ARG_VERBOSE) {
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ printf("error = 0x%02x, sector_count = 0x%04x, "
+ "device = 0x%02x, status = 0x%02x\n",
+ res->error, res->sector_count,
+ res->device, res->status);
+ }
+
+ if (res->error & ATA_ERROR_ID_NOT_FOUND) {
+ warnx("Max address has already been set since "
+ "last power-on or hardware reset");
+ }
+
+ return (1);
+ }
+
+ if (arglist & CAM_ARG_VERBOSE) {
+ fprintf(stdout, "%s%d: Raw native max data:\n",
+ device->device_name, device->dev_unit_num);
+ /* res is 4 byte aligned */
+ dump_data((uint16_t*)(uintptr_t)res, sizeof(struct ata_res));
+
+ printf("error = 0x%02x, sector_count = 0x%04x, device = 0x%02x, "
+ "status = 0x%02x\n", res->error, res->sector_count,
+ res->device, res->status);
+ }
+
+ if (hpasize != NULL) {
+ if (is48bit) {
+ *hpasize = (((u_int64_t)((res->lba_high_exp << 16) |
+ (res->lba_mid_exp << 8) | res->lba_low_exp) << 24) |
+ ((res->lba_high << 16) | (res->lba_mid << 8) |
+ res->lba_low)) + 1;
+ } else {
+ *hpasize = (((res->device & 0x0f) << 24) |
+ (res->lba_high << 16) | (res->lba_mid << 8) |
+ res->lba_low) + 1;
+ }
+ }
+
+ return (0);
+}
+
+static int
+ata_read_native_max(struct cam_device *device, int retry_count,
+ u_int32_t timeout, union ccb *ccb,
+ struct ata_params *parm, u_int64_t *hpasize)
+{
+ int error;
+ u_int cmd, is48bit;
+ u_int8_t protocol;
+
+ is48bit = parm->support.command2 & ATA_SUPPORT_ADDRESS48;
+ protocol = AP_PROTO_NON_DATA;
+
+ if (is48bit) {
+ cmd = ATA_READ_NATIVE_MAX_ADDRESS48;
+ protocol |= AP_EXTEND;
+ } else {
+ cmd = ATA_READ_NATIVE_MAX_ADDRESS;
+ }
+
+ error = ata_do_cmd(device,
+ ccb,
+ retry_count,
+ /*flags*/CAM_DIR_NONE,
+ /*protocol*/protocol,
+ /*ata_flags*/AP_FLAG_CHK_COND,
+ /*tag_action*/MSG_SIMPLE_Q_TAG,
+ /*command*/cmd,
+ /*features*/0,
+ /*lba*/0,
+ /*sector_count*/0,
+ /*data_ptr*/NULL,
+ /*dxfer_len*/0,
+ timeout ? timeout : 1000,
+ is48bit);
+
+ if (error)
+ return (error);
+
+ return atahpa_proc_resp(device, ccb, is48bit, hpasize);
+}
+
+static int
+atahpa_set_max(struct cam_device *device, int retry_count,
+ u_int32_t timeout, union ccb *ccb,
+ int is48bit, u_int64_t maxsize, int persist)
+{
+ int error;
+ u_int cmd;
+ u_int8_t protocol;
+
+ protocol = AP_PROTO_NON_DATA;
+
+ if (is48bit) {
+ cmd = ATA_SET_MAX_ADDRESS48;
+ protocol |= AP_EXTEND;
+ } else {
+ cmd = ATA_SET_MAX_ADDRESS;
+ }
+
+ /* lba's are zero indexed so the max lba is requested max - 1 */
+ if (maxsize)
+ maxsize--;
+
+ error = ata_do_cmd(device,
+ ccb,
+ retry_count,
+ /*flags*/CAM_DIR_NONE,
+ /*protocol*/protocol,
+ /*ata_flags*/AP_FLAG_CHK_COND,
+ /*tag_action*/MSG_SIMPLE_Q_TAG,
+ /*command*/cmd,
+ /*features*/ATA_HPA_FEAT_MAX_ADDR,
+ /*lba*/maxsize,
+ /*sector_count*/persist,
+ /*data_ptr*/NULL,
+ /*dxfer_len*/0,
+ timeout ? timeout : 1000,
+ is48bit);
+
+ if (error)
+ return (error);
+
+ return atahpa_proc_resp(device, ccb, is48bit, NULL);
+}
+
+static int
+atahpa_password(struct cam_device *device, int retry_count,
+ u_int32_t timeout, union ccb *ccb,
+ int is48bit, struct ata_set_max_pwd *pwd)
+{
+ int error;
+ u_int cmd;
+ u_int8_t protocol;
+
+ protocol = AP_PROTO_PIO_OUT;
+ cmd = (is48bit) ? ATA_SET_MAX_ADDRESS48 : ATA_SET_MAX_ADDRESS;
+
+ error = ata_do_cmd(device,
+ ccb,
+ retry_count,
+ /*flags*/CAM_DIR_OUT,
+ /*protocol*/protocol,
+ /*ata_flags*/AP_FLAG_CHK_COND,
+ /*tag_action*/MSG_SIMPLE_Q_TAG,
+ /*command*/cmd,
+ /*features*/ATA_HPA_FEAT_SET_PWD,
+ /*lba*/0,
+ /*sector_count*/0,
+ /*data_ptr*/(u_int8_t*)pwd,
+ /*dxfer_len*/sizeof(struct ata_set_max_pwd),
+ timeout ? timeout : 1000,
+ is48bit);
+
+ if (error)
+ return (error);
+
+ return atahpa_proc_resp(device, ccb, is48bit, NULL);
+}
+
+static int
+atahpa_lock(struct cam_device *device, int retry_count,
+ u_int32_t timeout, union ccb *ccb, int is48bit)
+{
+ int error;
+ u_int cmd;
+ u_int8_t protocol;
+
+ protocol = AP_PROTO_NON_DATA;
+ cmd = (is48bit) ? ATA_SET_MAX_ADDRESS48 : ATA_SET_MAX_ADDRESS;
+
+ error = ata_do_cmd(device,
+ ccb,
+ retry_count,
+ /*flags*/CAM_DIR_NONE,
+ /*protocol*/protocol,
+ /*ata_flags*/AP_FLAG_CHK_COND,
+ /*tag_action*/MSG_SIMPLE_Q_TAG,
+ /*command*/cmd,
+ /*features*/ATA_HPA_FEAT_LOCK,
+ /*lba*/0,
+ /*sector_count*/0,
+ /*data_ptr*/NULL,
+ /*dxfer_len*/0,
+ timeout ? timeout : 1000,
+ is48bit);
+
+ if (error)
+ return (error);
+
+ return atahpa_proc_resp(device, ccb, is48bit, NULL);
+}
+
+static int
+atahpa_unlock(struct cam_device *device, int retry_count,
+ u_int32_t timeout, union ccb *ccb,
+ int is48bit, struct ata_set_max_pwd *pwd)
+{
+ int error;
+ u_int cmd;
+ u_int8_t protocol;
+
+ protocol = AP_PROTO_PIO_OUT;
+ cmd = (is48bit) ? ATA_SET_MAX_ADDRESS48 : ATA_SET_MAX_ADDRESS;
+
+ error = ata_do_cmd(device,
+ ccb,
+ retry_count,
+ /*flags*/CAM_DIR_OUT,
+ /*protocol*/protocol,
+ /*ata_flags*/AP_FLAG_CHK_COND,
+ /*tag_action*/MSG_SIMPLE_Q_TAG,
+ /*command*/cmd,
+ /*features*/ATA_HPA_FEAT_UNLOCK,
+ /*lba*/0,
+ /*sector_count*/0,
+ /*data_ptr*/(u_int8_t*)pwd,
+ /*dxfer_len*/sizeof(struct ata_set_max_pwd),
+ timeout ? timeout : 1000,
+ is48bit);
+
+ if (error)
+ return (error);
+
+ return atahpa_proc_resp(device, ccb, is48bit, NULL);
+}
+
+static int
+atahpa_freeze_lock(struct cam_device *device, int retry_count,
+ u_int32_t timeout, union ccb *ccb, int is48bit)
+{
+ int error;
+ u_int cmd;
+ u_int8_t protocol;
+
+ protocol = AP_PROTO_NON_DATA;
+ cmd = (is48bit) ? ATA_SET_MAX_ADDRESS48 : ATA_SET_MAX_ADDRESS;
+
+ error = ata_do_cmd(device,
+ ccb,
+ retry_count,
+ /*flags*/CAM_DIR_NONE,
+ /*protocol*/protocol,
+ /*ata_flags*/AP_FLAG_CHK_COND,
+ /*tag_action*/MSG_SIMPLE_Q_TAG,
+ /*command*/cmd,
+ /*features*/ATA_HPA_FEAT_FREEZE,
+ /*lba*/0,
+ /*sector_count*/0,
+ /*data_ptr*/NULL,
+ /*dxfer_len*/0,
+ timeout ? timeout : 1000,
+ is48bit);
+
+ if (error)
+ return (error);
+
+ return atahpa_proc_resp(device, ccb, is48bit, NULL);
+}
+
+
+static int
+ata_do_identify(struct cam_device *device, int retry_count, int timeout,
+ union ccb *ccb, struct ata_params** ident_bufp)
+{
+ struct ata_params *ident_buf;
+ struct ccb_pathinq cpi;
+ struct ccb_getdev cgd;
+ u_int i, error;
+ int16_t *ptr;
+ u_int8_t command, retry_command;
+
+ if (get_cpi(device, &cpi) != 0) {
+ warnx("couldn't get CPI");
+ return (-1);
+ }
+
+ /* Neither PROTO_ATAPI or PROTO_SATAPM are used in cpi.protocol */
+ if (cpi.protocol == PROTO_ATA) {
+ if (get_cgd(device, &cgd) != 0) {
+ warnx("couldn't get CGD");
+ return (-1);
+ }
+
+ command = (cgd.protocol == PROTO_ATA) ?
+ ATA_ATA_IDENTIFY : ATA_ATAPI_IDENTIFY;
+ retry_command = 0;
+ } else {
+ /* We don't know which for sure so try both */
+ command = ATA_ATA_IDENTIFY;
+ retry_command = ATA_ATAPI_IDENTIFY;
+ }
+
+ ptr = (uint16_t *)calloc(1, sizeof(struct ata_params));
+ if (ptr == NULL) {
+ warnx("can't calloc memory for identify\n");
+ return (1);
+ }
+
+ error = ata_do_28bit_cmd(device,
+ ccb,
+ /*retries*/retry_count,
+ /*flags*/CAM_DIR_IN,
+ /*protocol*/AP_PROTO_PIO_IN,
+ /*tag_action*/MSG_SIMPLE_Q_TAG,
+ /*command*/command,
+ /*features*/0,
+ /*lba*/0,
+ /*sector_count*/(u_int8_t)sizeof(struct ata_params),
+ /*data_ptr*/(u_int8_t *)ptr,
+ /*dxfer_len*/sizeof(struct ata_params),
+ /*timeout*/timeout ? timeout : 30 * 1000,
+ /*quiet*/1);
+
+ if (error != 0) {
+ if (retry_command == 0) {
+ free(ptr);
+ return (1);
+ }
+ error = ata_do_28bit_cmd(device,
+ ccb,
+ /*retries*/retry_count,
+ /*flags*/CAM_DIR_IN,
+ /*protocol*/AP_PROTO_PIO_IN,
+ /*tag_action*/MSG_SIMPLE_Q_TAG,
+ /*command*/retry_command,
+ /*features*/0,
+ /*lba*/0,
+ /*sector_count*/(u_int8_t)
+ sizeof(struct ata_params),
+ /*data_ptr*/(u_int8_t *)ptr,
+ /*dxfer_len*/sizeof(struct ata_params),
+ /*timeout*/timeout ? timeout : 30 * 1000,
+ /*quiet*/0);
+
+ if (error != 0) {
+ free(ptr);
+ return (1);
+ }
+ }
+
+ error = 1;
+ for (i = 0; i < sizeof(struct ata_params) / 2; i++) {
+ ptr[i] = le16toh(ptr[i]);
+ if (ptr[i] != 0)
+ error = 0;
+ }
+
+ if (arglist & CAM_ARG_VERBOSE) {
+ fprintf(stdout, "%s%d: Raw identify data:\n",
+ device->device_name, device->dev_unit_num);
+ dump_data(ptr, sizeof(struct ata_params));
+ }
+
+ /* check for invalid (all zero) response */
+ if (error != 0) {
+ warnx("Invalid identify response detected");
+ free(ptr);
+ return (error);
+ }
+
+ ident_buf = (struct ata_params *)ptr;
+ if (strncmp(ident_buf->model, "FX", 2) &&
+ strncmp(ident_buf->model, "NEC", 3) &&
+ strncmp(ident_buf->model, "Pioneer", 7) &&
+ strncmp(ident_buf->model, "SHARP", 5)) {
+ ata_bswap(ident_buf->model, sizeof(ident_buf->model));
+ ata_bswap(ident_buf->revision, sizeof(ident_buf->revision));
+ ata_bswap(ident_buf->serial, sizeof(ident_buf->serial));
+ ata_bswap(ident_buf->media_serial, sizeof(ident_buf->media_serial));
+ }
+ ata_btrim(ident_buf->model, sizeof(ident_buf->model));
+ ata_bpack(ident_buf->model, ident_buf->model, sizeof(ident_buf->model));
+ ata_btrim(ident_buf->revision, sizeof(ident_buf->revision));
+ ata_bpack(ident_buf->revision, ident_buf->revision, sizeof(ident_buf->revision));
+ ata_btrim(ident_buf->serial, sizeof(ident_buf->serial));
+ ata_bpack(ident_buf->serial, ident_buf->serial, sizeof(ident_buf->serial));
+ ata_btrim(ident_buf->media_serial, sizeof(ident_buf->media_serial));
+ ata_bpack(ident_buf->media_serial, ident_buf->media_serial,
+ sizeof(ident_buf->media_serial));
+
+ *ident_bufp = ident_buf;
+
+ return (0);
+}
+
+
+static int
+ataidentify(struct cam_device *device, int retry_count, int timeout)
+{
+ union ccb *ccb;
+ struct ata_params *ident_buf;
+ u_int64_t hpasize;
+
+ if ((ccb = cam_getccb(device)) == NULL) {
+ warnx("couldn't allocate CCB");
+ return (1);
+ }
+
+ if (ata_do_identify(device, retry_count, timeout, ccb, &ident_buf) != 0) {
+ cam_freeccb(ccb);
+ return (1);
+ }
+
+ if (ident_buf->support.command1 & ATA_SUPPORT_PROTECTED) {
+ if (ata_read_native_max(device, retry_count, timeout, ccb,
+ ident_buf, &hpasize) != 0) {
+ cam_freeccb(ccb);
+ return (1);
+ }
+ } else {
+ hpasize = 0;
+ }
+
+ printf("%s%d: ", device->device_name, device->dev_unit_num);
+ ata_print_ident(ident_buf);
+ camxferrate(device);
+ atacapprint(ident_buf);
+ atahpa_print(ident_buf, hpasize, 0);
+
+ free(ident_buf);
+ cam_freeccb(ccb);
+
+ return (0);
+}
+#endif /* MINIMALISTIC */
+
+
+#ifndef MINIMALISTIC
+enum {
+ ATA_SECURITY_ACTION_PRINT,
+ ATA_SECURITY_ACTION_FREEZE,
+ ATA_SECURITY_ACTION_UNLOCK,
+ ATA_SECURITY_ACTION_DISABLE,
+ ATA_SECURITY_ACTION_ERASE,
+ ATA_SECURITY_ACTION_ERASE_ENHANCED,
+ ATA_SECURITY_ACTION_SET_PASSWORD
+};
+
+static void
+atasecurity_print_time(u_int16_t tw)
+{
+
+ if (tw == 0)
+ printf("unspecified");
+ else if (tw >= 255)
+ printf("> 508 min");
+ else
+ printf("%i min", 2 * tw);
+}
+
+static u_int32_t
+atasecurity_erase_timeout_msecs(u_int16_t timeout)
+{
+
+ if (timeout == 0)
+ return 2 * 3600 * 1000; /* default: two hours */
+ else if (timeout > 255)
+ return (508 + 60) * 60 * 1000; /* spec says > 508 minutes */
+
+ return ((2 * timeout) + 5) * 60 * 1000; /* add a 5min margin */
+}
+
+
+static void
+atasecurity_notify(u_int8_t command, struct ata_security_password *pwd)
+{
+ struct ata_cmd cmd;
+
+ bzero(&cmd, sizeof(cmd));
+ cmd.command = command;
+ printf("Issuing %s", ata_op_string(&cmd));
+
+ if (pwd != NULL) {
+ char pass[sizeof(pwd->password)+1];
+
+ /* pwd->password may not be null terminated */
+ pass[sizeof(pwd->password)] = '\0';
+ strncpy(pass, pwd->password, sizeof(pwd->password));
+ printf(" password='%s', user='%s'",
+ pass,
+ (pwd->ctrl & ATA_SECURITY_PASSWORD_MASTER) ?
+ "master" : "user");
+
+ if (command == ATA_SECURITY_SET_PASSWORD) {
+ printf(", mode='%s'",
+ (pwd->ctrl & ATA_SECURITY_LEVEL_MAXIMUM) ?
+ "maximum" : "high");
+ }
+ }
+
+ printf("\n");
+}
+
+static int
+atasecurity_freeze(struct cam_device *device, union ccb *ccb,
+ int retry_count, u_int32_t timeout, int quiet)
+{
+
+ if (quiet == 0)
+ atasecurity_notify(ATA_SECURITY_FREEZE_LOCK, NULL);
+
+ return ata_do_28bit_cmd(device,
+ ccb,
+ retry_count,
+ /*flags*/CAM_DIR_NONE,
+ /*protocol*/AP_PROTO_NON_DATA,
+ /*tag_action*/MSG_SIMPLE_Q_TAG,
+ /*command*/ATA_SECURITY_FREEZE_LOCK,
+ /*features*/0,
+ /*lba*/0,
+ /*sector_count*/0,
+ /*data_ptr*/NULL,
+ /*dxfer_len*/0,
+ /*timeout*/timeout,
+ /*quiet*/0);
+}
+
+static int
+atasecurity_unlock(struct cam_device *device, union ccb *ccb,
+ int retry_count, u_int32_t timeout,
+ struct ata_security_password *pwd, int quiet)
+{
+
+ if (quiet == 0)
+ atasecurity_notify(ATA_SECURITY_UNLOCK, pwd);
+
+ return ata_do_28bit_cmd(device,
+ ccb,
+ retry_count,
+ /*flags*/CAM_DIR_OUT,
+ /*protocol*/AP_PROTO_PIO_OUT,
+ /*tag_action*/MSG_SIMPLE_Q_TAG,
+ /*command*/ATA_SECURITY_UNLOCK,
+ /*features*/0,
+ /*lba*/0,
+ /*sector_count*/0,
+ /*data_ptr*/(u_int8_t *)pwd,
+ /*dxfer_len*/sizeof(*pwd),
+ /*timeout*/timeout,
+ /*quiet*/0);
+}
+
+static int
+atasecurity_disable(struct cam_device *device, union ccb *ccb,
+ int retry_count, u_int32_t timeout,
+ struct ata_security_password *pwd, int quiet)
+{
+
+ if (quiet == 0)
+ atasecurity_notify(ATA_SECURITY_DISABLE_PASSWORD, pwd);
+ return ata_do_28bit_cmd(device,
+ ccb,
+ retry_count,
+ /*flags*/CAM_DIR_OUT,
+ /*protocol*/AP_PROTO_PIO_OUT,
+ /*tag_action*/MSG_SIMPLE_Q_TAG,
+ /*command*/ATA_SECURITY_DISABLE_PASSWORD,
+ /*features*/0,
+ /*lba*/0,
+ /*sector_count*/0,
+ /*data_ptr*/(u_int8_t *)pwd,
+ /*dxfer_len*/sizeof(*pwd),
+ /*timeout*/timeout,
+ /*quiet*/0);
+}
+
+
+static int
+atasecurity_erase_confirm(struct cam_device *device,
+ struct ata_params* ident_buf)
+{
+
+ printf("\nYou are about to ERASE ALL DATA from the following"
+ " device:\n%s%d,%s%d: ", device->device_name,
+ device->dev_unit_num, device->given_dev_name,
+ device->given_unit_number);
+ ata_print_ident(ident_buf);
+
+ for(;;) {
+ char str[50];
+ printf("\nAre you SURE you want to ERASE ALL DATA? (yes/no) ");
+
+ if (fgets(str, sizeof(str), stdin) != NULL) {
+ if (strncasecmp(str, "yes", 3) == 0) {
+ return (1);
+ } else if (strncasecmp(str, "no", 2) == 0) {
+ return (0);
+ } else {
+ printf("Please answer \"yes\" or "
+ "\"no\"\n");
+ }
+ }
+ }
+
+ /* NOTREACHED */
+ return (0);
+}
+
+static int
+atasecurity_erase(struct cam_device *device, union ccb *ccb,
+ int retry_count, u_int32_t timeout,
+ u_int32_t erase_timeout,
+ struct ata_security_password *pwd, int quiet)
+{
+ int error;
+
+ if (quiet == 0)
+ atasecurity_notify(ATA_SECURITY_ERASE_PREPARE, NULL);
+
+ error = ata_do_28bit_cmd(device,
+ ccb,
+ retry_count,
+ /*flags*/CAM_DIR_NONE,
+ /*protocol*/AP_PROTO_NON_DATA,
+ /*tag_action*/MSG_SIMPLE_Q_TAG,
+ /*command*/ATA_SECURITY_ERASE_PREPARE,
+ /*features*/0,
+ /*lba*/0,
+ /*sector_count*/0,
+ /*data_ptr*/NULL,
+ /*dxfer_len*/0,
+ /*timeout*/timeout,
+ /*quiet*/0);
+
+ if (error != 0)
+ return error;
+
+ if (quiet == 0)
+ atasecurity_notify(ATA_SECURITY_ERASE_UNIT, pwd);
+
+ error = ata_do_28bit_cmd(device,
+ ccb,
+ retry_count,
+ /*flags*/CAM_DIR_OUT,
+ /*protocol*/AP_PROTO_PIO_OUT,
+ /*tag_action*/MSG_SIMPLE_Q_TAG,
+ /*command*/ATA_SECURITY_ERASE_UNIT,
+ /*features*/0,
+ /*lba*/0,
+ /*sector_count*/0,
+ /*data_ptr*/(u_int8_t *)pwd,
+ /*dxfer_len*/sizeof(*pwd),
+ /*timeout*/erase_timeout,
+ /*quiet*/0);
+
+ if (error == 0 && quiet == 0)
+ printf("\nErase Complete\n");
+
+ return error;
+}
+
+static int
+atasecurity_set_password(struct cam_device *device, union ccb *ccb,
+ int retry_count, u_int32_t timeout,
+ struct ata_security_password *pwd, int quiet)
+{
+
+ if (quiet == 0)
+ atasecurity_notify(ATA_SECURITY_SET_PASSWORD, pwd);
+
+ return ata_do_28bit_cmd(device,
+ ccb,
+ retry_count,
+ /*flags*/CAM_DIR_OUT,
+ /*protocol*/AP_PROTO_PIO_OUT,
+ /*tag_action*/MSG_SIMPLE_Q_TAG,
+ /*command*/ATA_SECURITY_SET_PASSWORD,
+ /*features*/0,
+ /*lba*/0,
+ /*sector_count*/0,
+ /*data_ptr*/(u_int8_t *)pwd,
+ /*dxfer_len*/sizeof(*pwd),
+ /*timeout*/timeout,
+ /*quiet*/0);
+}
+
+static void
+atasecurity_print(struct ata_params *parm)
+{
+
+ printf("\nSecurity Option Value\n");
+ if (arglist & CAM_ARG_VERBOSE) {
+ printf("status %04x\n",
+ parm->security_status);
+ }
+ printf("supported %s\n",
+ parm->security_status & ATA_SECURITY_SUPPORTED ? "yes" : "no");
+ if (!(parm->security_status & ATA_SECURITY_SUPPORTED))
+ return;
+ printf("enabled %s\n",
+ parm->security_status & ATA_SECURITY_ENABLED ? "yes" : "no");
+ printf("drive locked %s\n",
+ parm->security_status & ATA_SECURITY_LOCKED ? "yes" : "no");
+ printf("security config frozen %s\n",
+ parm->security_status & ATA_SECURITY_FROZEN ? "yes" : "no");
+ printf("count expired %s\n",
+ parm->security_status & ATA_SECURITY_COUNT_EXP ? "yes" : "no");
+ printf("security level %s\n",
+ parm->security_status & ATA_SECURITY_LEVEL ? "maximum" : "high");
+ printf("enhanced erase supported %s\n",
+ parm->security_status & ATA_SECURITY_ENH_SUPP ? "yes" : "no");
+ printf("erase time ");
+ atasecurity_print_time(parm->erase_time);
+ printf("\n");
+ printf("enhanced erase time ");
+ atasecurity_print_time(parm->enhanced_erase_time);
+ printf("\n");
+ printf("master password rev %04x%s\n",
+ parm->master_passwd_revision,
+ parm->master_passwd_revision == 0x0000 ||
+ parm->master_passwd_revision == 0xFFFF ? " (unsupported)" : "");
+}
+
+/*
+ * Validates and copies the password in optarg to the passed buffer.
+ * If the password in optarg is the same length as the buffer then
+ * the data will still be copied but no null termination will occur.
+ */
+static int
+ata_getpwd(u_int8_t *passwd, int max, char opt)
+{
+ int len;
+
+ len = strlen(optarg);
+ if (len > max) {
+ warnx("-%c password is too long", opt);
+ return (1);
+ } else if (len == 0) {
+ warnx("-%c password is missing", opt);
+ return (1);
+ } else if (optarg[0] == '-'){
+ warnx("-%c password starts with '-' (generic arg?)", opt);
+ return (1);
+ } else if (strlen(passwd) != 0 && strcmp(passwd, optarg) != 0) {
+ warnx("-%c password conflicts with existing password from -%c",
+ opt, pwd_opt);
+ return (1);
+ }
+
+ /* Callers pass in a buffer which does NOT need to be terminated */
+ strncpy(passwd, optarg, max);
+ pwd_opt = opt;
+
+ return (0);
+}
+
+enum {
+ ATA_HPA_ACTION_PRINT,
+ ATA_HPA_ACTION_SET_MAX,
+ ATA_HPA_ACTION_SET_PWD,
+ ATA_HPA_ACTION_LOCK,
+ ATA_HPA_ACTION_UNLOCK,
+ ATA_HPA_ACTION_FREEZE_LOCK
+};
+
+static int
+atahpa_set_confirm(struct cam_device *device, struct ata_params* ident_buf,
+ u_int64_t maxsize, int persist)
+{
+ printf("\nYou are about to configure HPA to limit the user accessible\n"
+ "sectors to %ju %s on the device:\n%s%d,%s%d: ", maxsize,
+ persist ? "persistently" : "temporarily",
+ device->device_name, device->dev_unit_num,
+ device->given_dev_name, device->given_unit_number);
+ ata_print_ident(ident_buf);
+
+ for(;;) {
+ char str[50];
+ printf("\nAre you SURE you want to configure HPA? (yes/no) ");
+
+ if (NULL != fgets(str, sizeof(str), stdin)) {
+ if (0 == strncasecmp(str, "yes", 3)) {
+ return (1);
+ } else if (0 == strncasecmp(str, "no", 2)) {
+ return (0);
+ } else {
+ printf("Please answer \"yes\" or "
+ "\"no\"\n");
+ }
+ }
+ }
+
+ /* NOTREACHED */
+ return (0);
+}
+
+static int
+atahpa(struct cam_device *device, int retry_count, int timeout,
+ int argc, char **argv, char *combinedopt)
+{
+ union ccb *ccb;
+ struct ata_params *ident_buf;
+ struct ccb_getdev cgd;
+ struct ata_set_max_pwd pwd;
+ int error, confirm, quiet, c, action, actions, setpwd, persist;
+ int security, is48bit, pwdsize;
+ u_int64_t hpasize, maxsize;
+
+ actions = 0;
+ setpwd = 0;
+ confirm = 0;
+ quiet = 0;
+ maxsize = 0;
+ persist = 0;
+ security = 0;
+
+ memset(&pwd, 0, sizeof(pwd));
+
+ /* default action is to print hpa information */
+ action = ATA_HPA_ACTION_PRINT;
+ pwdsize = sizeof(pwd.password);
+
+ while ((c = getopt(argc, argv, combinedopt)) != -1) {
+ switch(c){
+ case 's':
+ action = ATA_HPA_ACTION_SET_MAX;
+ maxsize = strtoumax(optarg, NULL, 0);
+ actions++;
+ break;
+
+ case 'p':
+ if (ata_getpwd(pwd.password, pwdsize, c) != 0)
+ return (1);
+ action = ATA_HPA_ACTION_SET_PWD;
+ security = 1;
+ actions++;
+ break;
+
+ case 'l':
+ action = ATA_HPA_ACTION_LOCK;
+ security = 1;
+ actions++;
+ break;
+
+ case 'U':
+ if (ata_getpwd(pwd.password, pwdsize, c) != 0)
+ return (1);
+ action = ATA_HPA_ACTION_UNLOCK;
+ security = 1;
+ actions++;
+ break;
+
+ case 'f':
+ action = ATA_HPA_ACTION_FREEZE_LOCK;
+ security = 1;
+ actions++;
+ break;
+
+ case 'P':
+ persist = 1;
+ break;
+
+ case 'y':
+ confirm++;
+ break;
+
+ case 'q':
+ quiet++;
+ break;
+ }
+ }
+
+ if (actions > 1) {
+ warnx("too many hpa actions specified");
+ return (1);
+ }
+
+ if (get_cgd(device, &cgd) != 0) {
+ warnx("couldn't get CGD");
+ return (1);
+ }
+
+ ccb = cam_getccb(device);
+ if (ccb == NULL) {
+ warnx("couldn't allocate CCB");
+ return (1);
+ }
+
+ error = ata_do_identify(device, retry_count, timeout, ccb, &ident_buf);
+ if (error != 0) {
+ cam_freeccb(ccb);
+ return (1);
+ }
+
+ if (quiet == 0) {
+ printf("%s%d: ", device->device_name, device->dev_unit_num);
+ ata_print_ident(ident_buf);
+ camxferrate(device);
+ }
+
+ if (action == ATA_HPA_ACTION_PRINT) {
+ error = ata_read_native_max(device, retry_count, timeout, ccb,
+ ident_buf, &hpasize);
+ if (error == 0)
+ atahpa_print(ident_buf, hpasize, 1);
+
+ cam_freeccb(ccb);
+ free(ident_buf);
+ return (error);
+ }
+
+ if (!(ident_buf->support.command1 & ATA_SUPPORT_PROTECTED)) {
+ warnx("HPA is not supported by this device");
+ cam_freeccb(ccb);
+ free(ident_buf);
+ return (1);
+ }
+
+ if (security && !(ident_buf->support.command1 & ATA_SUPPORT_MAXSECURITY)) {
+ warnx("HPA Security is not supported by this device");
+ cam_freeccb(ccb);
+ free(ident_buf);
+ return (1);
+ }
+
+ is48bit = ident_buf->support.command2 & ATA_SUPPORT_ADDRESS48;
+
+ /*
+ * The ATA spec requires:
+ * 1. Read native max addr is called directly before set max addr
+ * 2. Read native max addr is NOT called before any other set max call
+ */
+ switch(action) {
+ case ATA_HPA_ACTION_SET_MAX:
+ if (confirm == 0 &&
+ atahpa_set_confirm(device, ident_buf, maxsize,
+ persist) == 0) {
+ cam_freeccb(ccb);
+ free(ident_buf);
+ return (1);
+ }
+
+ error = ata_read_native_max(device, retry_count, timeout,
+ ccb, ident_buf, &hpasize);
+ if (error == 0) {
+ error = atahpa_set_max(device, retry_count, timeout,
+ ccb, is48bit, maxsize, persist);
+ if (error == 0) {
+ /* redo identify to get new lba values */
+ error = ata_do_identify(device, retry_count,
+ timeout, ccb,
+ &ident_buf);
+ atahpa_print(ident_buf, hpasize, 1);
+ }
+ }
+ break;
+
+ case ATA_HPA_ACTION_SET_PWD:
+ error = atahpa_password(device, retry_count, timeout,
+ ccb, is48bit, &pwd);
+ if (error == 0)
+ printf("HPA password has been set\n");
+ break;
+
+ case ATA_HPA_ACTION_LOCK:
+ error = atahpa_lock(device, retry_count, timeout,
+ ccb, is48bit);
+ if (error == 0)
+ printf("HPA has been locked\n");
+ break;
+
+ case ATA_HPA_ACTION_UNLOCK:
+ error = atahpa_unlock(device, retry_count, timeout,
+ ccb, is48bit, &pwd);
+ if (error == 0)
+ printf("HPA has been unlocked\n");
+ break;
+
+ case ATA_HPA_ACTION_FREEZE_LOCK:
+ error = atahpa_freeze_lock(device, retry_count, timeout,
+ ccb, is48bit);
+ if (error == 0)
+ printf("HPA has been frozen\n");
+ break;
+
+ default:
+ errx(1, "Option currently not supported");
+ }
+
+ cam_freeccb(ccb);
+ free(ident_buf);
+
+ return (error);
+}
+
+static int
+atasecurity(struct cam_device *device, int retry_count, int timeout,
+ int argc, char **argv, char *combinedopt)
+{
+ union ccb *ccb;
+ struct ata_params *ident_buf;
+ int error, confirm, quiet, c, action, actions, setpwd;
+ int security_enabled, erase_timeout, pwdsize;
+ struct ata_security_password pwd;
+
+ actions = 0;
+ setpwd = 0;
+ erase_timeout = 0;
+ confirm = 0;
+ quiet = 0;
+
+ memset(&pwd, 0, sizeof(pwd));
+
+ /* default action is to print security information */
+ action = ATA_SECURITY_ACTION_PRINT;
+
+ /* user is master by default as its safer that way */
+ pwd.ctrl |= ATA_SECURITY_PASSWORD_MASTER;
+ pwdsize = sizeof(pwd.password);
+
+ while ((c = getopt(argc, argv, combinedopt)) != -1) {
+ switch(c){
+ case 'f':
+ action = ATA_SECURITY_ACTION_FREEZE;
+ actions++;
+ break;
+
+ case 'U':
+ if (strcasecmp(optarg, "user") == 0) {
+ pwd.ctrl |= ATA_SECURITY_PASSWORD_USER;
+ pwd.ctrl &= ~ATA_SECURITY_PASSWORD_MASTER;
+ } else if (strcasecmp(optarg, "master") == 0) {
+ pwd.ctrl |= ATA_SECURITY_PASSWORD_MASTER;
+ pwd.ctrl &= ~ATA_SECURITY_PASSWORD_USER;
+ } else {
+ warnx("-U argument '%s' is invalid (must be "
+ "'user' or 'master')", optarg);
+ return (1);
+ }
+ break;
+
+ case 'l':
+ if (strcasecmp(optarg, "high") == 0) {
+ pwd.ctrl |= ATA_SECURITY_LEVEL_HIGH;
+ pwd.ctrl &= ~ATA_SECURITY_LEVEL_MAXIMUM;
+ } else if (strcasecmp(optarg, "maximum") == 0) {
+ pwd.ctrl |= ATA_SECURITY_LEVEL_MAXIMUM;
+ pwd.ctrl &= ~ATA_SECURITY_LEVEL_HIGH;
+ } else {
+ warnx("-l argument '%s' is unknown (must be "
+ "'high' or 'maximum')", optarg);
+ return (1);
+ }
+ break;
+
+ case 'k':
+ if (ata_getpwd(pwd.password, pwdsize, c) != 0)
+ return (1);
+ action = ATA_SECURITY_ACTION_UNLOCK;
+ actions++;
+ break;
+
+ case 'd':
+ if (ata_getpwd(pwd.password, pwdsize, c) != 0)
+ return (1);
+ action = ATA_SECURITY_ACTION_DISABLE;
+ actions++;
+ break;
+
+ case 'e':
+ if (ata_getpwd(pwd.password, pwdsize, c) != 0)
+ return (1);
+ action = ATA_SECURITY_ACTION_ERASE;
+ actions++;
+ break;
+
+ case 'h':
+ if (ata_getpwd(pwd.password, pwdsize, c) != 0)
+ return (1);
+ pwd.ctrl |= ATA_SECURITY_ERASE_ENHANCED;
+ action = ATA_SECURITY_ACTION_ERASE_ENHANCED;
+ actions++;
+ break;
+
+ case 's':
+ if (ata_getpwd(pwd.password, pwdsize, c) != 0)
+ return (1);
+ setpwd = 1;
+ if (action == ATA_SECURITY_ACTION_PRINT)
+ action = ATA_SECURITY_ACTION_SET_PASSWORD;
+ /*
+ * Don't increment action as this can be combined
+ * with other actions.
+ */
+ break;
+
+ case 'y':
+ confirm++;
+ break;
+
+ case 'q':
+ quiet++;
+ break;
+
+ case 'T':
+ erase_timeout = atoi(optarg) * 1000;
+ break;
+ }
+ }
+
+ if (actions > 1) {
+ warnx("too many security actions specified");
+ return (1);
+ }
+
+ if ((ccb = cam_getccb(device)) == NULL) {
+ warnx("couldn't allocate CCB");
+ return (1);
+ }
+
+ error = ata_do_identify(device, retry_count, timeout, ccb, &ident_buf);
+ if (error != 0) {
+ cam_freeccb(ccb);
+ return (1);
+ }
+
+ if (quiet == 0) {
+ printf("%s%d: ", device->device_name, device->dev_unit_num);
+ ata_print_ident(ident_buf);
+ camxferrate(device);
+ }
+
+ if (action == ATA_SECURITY_ACTION_PRINT) {
+ atasecurity_print(ident_buf);
+ free(ident_buf);
+ cam_freeccb(ccb);
+ return (0);
+ }
+
+ if ((ident_buf->support.command1 & ATA_SUPPORT_SECURITY) == 0) {
+ warnx("Security not supported");
+ free(ident_buf);
+ cam_freeccb(ccb);
+ return (1);
+ }
+
+ /* default timeout 15 seconds the same as linux hdparm */
+ timeout = timeout ? timeout : 15 * 1000;
+
+ security_enabled = ident_buf->security_status & ATA_SECURITY_ENABLED;
+
+ /* first set the password if requested */
+ if (setpwd == 1) {
+ /* confirm we can erase before setting the password if erasing */
+ if (confirm == 0 &&
+ (action == ATA_SECURITY_ACTION_ERASE_ENHANCED ||
+ action == ATA_SECURITY_ACTION_ERASE) &&
+ atasecurity_erase_confirm(device, ident_buf) == 0) {
+ cam_freeccb(ccb);
+ free(ident_buf);
+ return (error);
+ }
+
+ if (pwd.ctrl & ATA_SECURITY_PASSWORD_MASTER) {
+ pwd.revision = ident_buf->master_passwd_revision;
+ if (pwd.revision != 0 && pwd.revision != 0xfff &&
+ --pwd.revision == 0) {
+ pwd.revision = 0xfffe;
+ }
+ }
+ error = atasecurity_set_password(device, ccb, retry_count,
+ timeout, &pwd, quiet);
+ if (error != 0) {
+ cam_freeccb(ccb);
+ free(ident_buf);
+ return (error);
+ }
+ security_enabled = 1;
+ }
+
+ switch(action) {
+ case ATA_SECURITY_ACTION_FREEZE:
+ error = atasecurity_freeze(device, ccb, retry_count,
+ timeout, quiet);
+ break;
+
+ case ATA_SECURITY_ACTION_UNLOCK:
+ if (security_enabled) {
+ if (ident_buf->security_status & ATA_SECURITY_LOCKED) {
+ error = atasecurity_unlock(device, ccb,
+ retry_count, timeout, &pwd, quiet);
+ } else {
+ warnx("Can't unlock, drive is not locked");
+ error = 1;
+ }
+ } else {
+ warnx("Can't unlock, security is disabled");
+ error = 1;
+ }
+ break;
+
+ case ATA_SECURITY_ACTION_DISABLE:
+ if (security_enabled) {
+ /* First unlock the drive if its locked */
+ if (ident_buf->security_status & ATA_SECURITY_LOCKED) {
+ error = atasecurity_unlock(device, ccb,
+ retry_count,
+ timeout,
+ &pwd,
+ quiet);
+ }
+
+ if (error == 0) {
+ error = atasecurity_disable(device,
+ ccb,
+ retry_count,
+ timeout,
+ &pwd,
+ quiet);
+ }
+ } else {
+ warnx("Can't disable security (already disabled)");
+ error = 1;
+ }
+ break;
+
+ case ATA_SECURITY_ACTION_ERASE:
+ if (security_enabled) {
+ if (erase_timeout == 0) {
+ erase_timeout = atasecurity_erase_timeout_msecs(
+ ident_buf->erase_time);
+ }
+
+ error = atasecurity_erase(device, ccb, retry_count,
+ timeout, erase_timeout, &pwd,
+ quiet);
+ } else {
+ warnx("Can't secure erase (security is disabled)");
+ error = 1;
+ }
+ break;
+
+ case ATA_SECURITY_ACTION_ERASE_ENHANCED:
+ if (security_enabled) {
+ if (ident_buf->security_status & ATA_SECURITY_ENH_SUPP) {
+ if (erase_timeout == 0) {
+ erase_timeout =
+ atasecurity_erase_timeout_msecs(
+ ident_buf->enhanced_erase_time);
+ }
+
+ error = atasecurity_erase(device, ccb,
+ retry_count, timeout,
+ erase_timeout, &pwd,
+ quiet);
+ } else {
+ warnx("Enhanced erase is not supported");
+ error = 1;
+ }
+ } else {
+ warnx("Can't secure erase (enhanced), "
+ "(security is disabled)");
+ error = 1;
+ }
+ break;
+ }
+
+ cam_freeccb(ccb);
+ free(ident_buf);
+
+ return (error);
+}
+#endif /* MINIMALISTIC */
+
+/*
+ * Parse out a bus, or a bus, target and lun in the following
+ * format:
+ * bus
+ * bus:target
+ * bus:target:lun
+ *
+ * Returns the number of parsed components, or 0.
+ */
+static int
+parse_btl(char *tstr, path_id_t *bus, target_id_t *target, lun_id_t *lun,
+ cam_argmask *arglst)
+{
+ char *tmpstr;
+ int convs = 0;
+
+ while (isspace(*tstr) && (*tstr != '\0'))
+ tstr++;
+
+ tmpstr = (char *)strtok(tstr, ":");
+ if ((tmpstr != NULL) && (*tmpstr != '\0')) {
+ *bus = strtol(tmpstr, NULL, 0);
+ *arglst |= CAM_ARG_BUS;
+ convs++;
+ tmpstr = (char *)strtok(NULL, ":");
+ if ((tmpstr != NULL) && (*tmpstr != '\0')) {
+ *target = strtol(tmpstr, NULL, 0);
+ *arglst |= CAM_ARG_TARGET;
+ convs++;
+ tmpstr = (char *)strtok(NULL, ":");
+ if ((tmpstr != NULL) && (*tmpstr != '\0')) {
+ *lun = strtol(tmpstr, NULL, 0);
+ *arglst |= CAM_ARG_LUN;
+ convs++;
+ }
+ }
+ }
+
+ return convs;
+}
+
+static int
+dorescan_or_reset(int argc, char **argv, int rescan)
+{
+ static const char must[] =
+ "you must specify \"all\", a bus, or a bus:target:lun to %s";
+ int rv, error = 0;
+ path_id_t bus = CAM_BUS_WILDCARD;
+ target_id_t target = CAM_TARGET_WILDCARD;
+ lun_id_t lun = CAM_LUN_WILDCARD;
+ char *tstr;
+
+ if (argc < 3) {
+ warnx(must, rescan? "rescan" : "reset");
+ return(1);
+ }
+
+ tstr = argv[optind];
+ while (isspace(*tstr) && (*tstr != '\0'))
+ tstr++;
+ if (strncasecmp(tstr, "all", strlen("all")) == 0)
+ arglist |= CAM_ARG_BUS;
+ else {
+ rv = parse_btl(argv[optind], &bus, &target, &lun, &arglist);
+ if (rv != 1 && rv != 3) {
+ warnx(must, rescan? "rescan" : "reset");
+ return(1);
+ }
+ }
+
+ if ((arglist & CAM_ARG_BUS)
+ && (arglist & CAM_ARG_TARGET)
+ && (arglist & CAM_ARG_LUN))
+ error = scanlun_or_reset_dev(bus, target, lun, rescan);
+ else
+ error = rescan_or_reset_bus(bus, rescan);
+
+ return(error);
+}
+
+static int
+rescan_or_reset_bus(path_id_t bus, int rescan)
+{
+ union ccb ccb, matchccb;
+ int fd, retval;
+ int bufsize;
+
+ retval = 0;
+
+ if ((fd = open(XPT_DEVICE, O_RDWR)) < 0) {
+ warnx("error opening transport layer device %s", XPT_DEVICE);
+ warn("%s", XPT_DEVICE);
+ return(1);
+ }
+
+ if (bus != CAM_BUS_WILDCARD) {
+ ccb.ccb_h.func_code = rescan ? XPT_SCAN_BUS : XPT_RESET_BUS;
+ ccb.ccb_h.path_id = bus;
+ ccb.ccb_h.target_id = CAM_TARGET_WILDCARD;
+ ccb.ccb_h.target_lun = CAM_LUN_WILDCARD;
+ ccb.crcn.flags = CAM_FLAG_NONE;
+
+ /* run this at a low priority */
+ ccb.ccb_h.pinfo.priority = 5;
+
+ if (ioctl(fd, CAMIOCOMMAND, &ccb) == -1) {
+ warn("CAMIOCOMMAND ioctl failed");
+ close(fd);
+ return(1);
+ }
+
+ if ((ccb.ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) {
+ fprintf(stdout, "%s of bus %d was successful\n",
+ rescan ? "Re-scan" : "Reset", bus);
+ } else {
+ fprintf(stdout, "%s of bus %d returned error %#x\n",
+ rescan ? "Re-scan" : "Reset", bus,
+ ccb.ccb_h.status & CAM_STATUS_MASK);
+ retval = 1;
+ }
+
+ close(fd);
+ return(retval);
+
+ }
+
+
+ /*
+ * The right way to handle this is to modify the xpt so that it can
+ * handle a wildcarded bus in a rescan or reset CCB. At the moment
+ * that isn't implemented, so instead we enumerate the busses and
+ * send the rescan or reset to those busses in the case where the
+ * given bus is -1 (wildcard). We don't send a rescan or reset
+ * to the xpt bus; sending a rescan to the xpt bus is effectively a
+ * no-op, sending a rescan to the xpt bus would result in a status of
+ * CAM_REQ_INVALID.
+ */
+ bzero(&(&matchccb.ccb_h)[1],
+ sizeof(struct ccb_dev_match) - sizeof(struct ccb_hdr));
+ matchccb.ccb_h.func_code = XPT_DEV_MATCH;
+ matchccb.ccb_h.path_id = CAM_BUS_WILDCARD;
+ bufsize = sizeof(struct dev_match_result) * 20;
+ matchccb.cdm.match_buf_len = bufsize;
+ matchccb.cdm.matches=(struct dev_match_result *)malloc(bufsize);
+ if (matchccb.cdm.matches == NULL) {
+ warnx("can't malloc memory for matches");
+ retval = 1;
+ goto bailout;
+ }
+ matchccb.cdm.num_matches = 0;
+
+ matchccb.cdm.num_patterns = 1;
+ matchccb.cdm.pattern_buf_len = sizeof(struct dev_match_pattern);
+
+ matchccb.cdm.patterns = (struct dev_match_pattern *)malloc(
+ matchccb.cdm.pattern_buf_len);
+ if (matchccb.cdm.patterns == NULL) {
+ warnx("can't malloc memory for patterns");
+ retval = 1;
+ goto bailout;
+ }
+ matchccb.cdm.patterns[0].type = DEV_MATCH_BUS;
+ matchccb.cdm.patterns[0].pattern.bus_pattern.flags = BUS_MATCH_ANY;
+
+ do {
+ unsigned int i;
+
+ if (ioctl(fd, CAMIOCOMMAND, &matchccb) == -1) {
+ warn("CAMIOCOMMAND ioctl failed");
+ retval = 1;
+ goto bailout;
+ }
+
+ if ((matchccb.ccb_h.status != CAM_REQ_CMP)
+ || ((matchccb.cdm.status != CAM_DEV_MATCH_LAST)
+ && (matchccb.cdm.status != CAM_DEV_MATCH_MORE))) {
+ warnx("got CAM error %#x, CDM error %d\n",
+ matchccb.ccb_h.status, matchccb.cdm.status);
+ retval = 1;
+ goto bailout;
+ }
+
+ for (i = 0; i < matchccb.cdm.num_matches; i++) {
+ struct bus_match_result *bus_result;
+
+ /* This shouldn't happen. */
+ if (matchccb.cdm.matches[i].type != DEV_MATCH_BUS)
+ continue;
+
+ bus_result = &matchccb.cdm.matches[i].result.bus_result;
+
+ /*
+ * We don't want to rescan or reset the xpt bus.
+ * See above.
+ */
+ if (bus_result->path_id == CAM_XPT_PATH_ID)
+ continue;
+
+ ccb.ccb_h.func_code = rescan ? XPT_SCAN_BUS :
+ XPT_RESET_BUS;
+ ccb.ccb_h.path_id = bus_result->path_id;
+ ccb.ccb_h.target_id = CAM_TARGET_WILDCARD;
+ ccb.ccb_h.target_lun = CAM_LUN_WILDCARD;
+ ccb.crcn.flags = CAM_FLAG_NONE;
+
+ /* run this at a low priority */
+ ccb.ccb_h.pinfo.priority = 5;
+
+ if (ioctl(fd, CAMIOCOMMAND, &ccb) == -1) {
+ warn("CAMIOCOMMAND ioctl failed");
+ retval = 1;
+ goto bailout;
+ }
+
+ if ((ccb.ccb_h.status & CAM_STATUS_MASK) ==CAM_REQ_CMP){
+ fprintf(stdout, "%s of bus %d was successful\n",
+ rescan? "Re-scan" : "Reset",
+ bus_result->path_id);
+ } else {
+ /*
+ * Don't bail out just yet, maybe the other
+ * rescan or reset commands will complete
+ * successfully.
+ */
+ fprintf(stderr, "%s of bus %d returned error "
+ "%#x\n", rescan? "Re-scan" : "Reset",
+ bus_result->path_id,
+ ccb.ccb_h.status & CAM_STATUS_MASK);
+ retval = 1;
+ }
+ }
+ } while ((matchccb.ccb_h.status == CAM_REQ_CMP)
+ && (matchccb.cdm.status == CAM_DEV_MATCH_MORE));
+
+bailout:
+
+ if (fd != -1)
+ close(fd);
+
+ if (matchccb.cdm.patterns != NULL)
+ free(matchccb.cdm.patterns);
+ if (matchccb.cdm.matches != NULL)
+ free(matchccb.cdm.matches);
+
+ return(retval);
+}
+
+static int
+scanlun_or_reset_dev(path_id_t bus, target_id_t target, lun_id_t lun, int scan)
+{
+ union ccb ccb;
+ struct cam_device *device;
+ int fd;
+
+ device = NULL;
+
+ if (bus == CAM_BUS_WILDCARD) {
+ warnx("invalid bus number %d", bus);
+ return(1);
+ }
+
+ if (target == CAM_TARGET_WILDCARD) {
+ warnx("invalid target number %d", target);
+ return(1);
+ }
+
+ if (lun == CAM_LUN_WILDCARD) {
+ warnx("invalid lun number %jx", (uintmax_t)lun);
+ return(1);
+ }
+
+ fd = -1;
+
+ bzero(&ccb, sizeof(union ccb));
+
+ if (scan) {
+ if ((fd = open(XPT_DEVICE, O_RDWR)) < 0) {
+ warnx("error opening transport layer device %s\n",
+ XPT_DEVICE);
+ warn("%s", XPT_DEVICE);
+ return(1);
+ }
+ } else {
+ device = cam_open_btl(bus, target, lun, O_RDWR, NULL);
+ if (device == NULL) {
+ warnx("%s", cam_errbuf);
+ return(1);
+ }
+ }
+
+ ccb.ccb_h.func_code = (scan)? XPT_SCAN_LUN : XPT_RESET_DEV;
+ ccb.ccb_h.path_id = bus;
+ ccb.ccb_h.target_id = target;
+ ccb.ccb_h.target_lun = lun;
+ ccb.ccb_h.timeout = 5000;
+ ccb.crcn.flags = CAM_FLAG_NONE;
+
+ /* run this at a low priority */
+ ccb.ccb_h.pinfo.priority = 5;
+
+ if (scan) {
+ if (ioctl(fd, CAMIOCOMMAND, &ccb) < 0) {
+ warn("CAMIOCOMMAND ioctl failed");
+ close(fd);
+ return(1);
+ }
+ } else {
+ if (cam_send_ccb(device, &ccb) < 0) {
+ warn("error sending XPT_RESET_DEV CCB");
+ cam_close_device(device);
+ return(1);
+ }
+ }
+
+ if (scan)
+ close(fd);
+ else
+ cam_close_device(device);
+
+ /*
+ * An error code of CAM_BDR_SENT is normal for a BDR request.
+ */
+ if (((ccb.ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP)
+ || ((!scan)
+ && ((ccb.ccb_h.status & CAM_STATUS_MASK) == CAM_BDR_SENT))) {
+ fprintf(stdout, "%s of %d:%d:%jx was successful\n",
+ scan? "Re-scan" : "Reset", bus, target, (uintmax_t)lun);
+ return(0);
+ } else {
+ fprintf(stdout, "%s of %d:%d:%jx returned error %#x\n",
+ scan? "Re-scan" : "Reset", bus, target, (uintmax_t)lun,
+ ccb.ccb_h.status & CAM_STATUS_MASK);
+ return(1);
+ }
+}
+
+#ifndef MINIMALISTIC
+
+static struct scsi_nv defect_list_type_map[] = {
+ { "block", SRDD10_BLOCK_FORMAT },
+ { "extbfi", SRDD10_EXT_BFI_FORMAT },
+ { "extphys", SRDD10_EXT_PHYS_FORMAT },
+ { "longblock", SRDD10_LONG_BLOCK_FORMAT },
+ { "bfi", SRDD10_BYTES_FROM_INDEX_FORMAT },
+ { "phys", SRDD10_PHYSICAL_SECTOR_FORMAT }
+};
+
+static int
+readdefects(struct cam_device *device, int argc, char **argv,
+ char *combinedopt, int retry_count, int timeout)
+{
+ union ccb *ccb = NULL;
+ struct scsi_read_defect_data_hdr_10 *hdr10 = NULL;
+ struct scsi_read_defect_data_hdr_12 *hdr12 = NULL;
+ size_t hdr_size = 0, entry_size = 0;
+ int use_12byte = 0;
+ int hex_format = 0;
+ u_int8_t *defect_list = NULL;
+ u_int8_t list_format = 0;
+ int list_type_set = 0;
+ u_int32_t dlist_length = 0;
+ u_int32_t returned_length = 0, valid_len = 0;
+ u_int32_t num_returned = 0, num_valid = 0;
+ u_int32_t max_possible_size = 0, hdr_max = 0;
+ u_int32_t starting_offset = 0;
+ u_int8_t returned_format, returned_type;
+ unsigned int i;
+ int summary = 0, quiet = 0;
+ int c, error = 0;
+ int lists_specified = 0;
+ int get_length = 1, first_pass = 1;
+ int mads = 0;
+
+ while ((c = getopt(argc, argv, combinedopt)) != -1) {
+ switch(c){
+ case 'f':
+ {
+ scsi_nv_status status;
+ int entry_num = 0;
+
+ status = scsi_get_nv(defect_list_type_map,
+ sizeof(defect_list_type_map) /
+ sizeof(defect_list_type_map[0]), optarg,
+ &entry_num, SCSI_NV_FLAG_IG_CASE);
+
+ if (status == SCSI_NV_FOUND) {
+ list_format = defect_list_type_map[
+ entry_num].value;
+ list_type_set = 1;
+ } else {
+ warnx("%s: %s %s option %s", __func__,
+ (status == SCSI_NV_AMBIGUOUS) ?
+ "ambiguous" : "invalid", "defect list type",
+ optarg);
+ error = 1;
+ goto defect_bailout;
+ }
+ break;
+ }
+ case 'G':
+ arglist |= CAM_ARG_GLIST;
+ break;
+ case 'P':
+ arglist |= CAM_ARG_PLIST;
+ break;
+ case 'q':
+ quiet = 1;
+ break;
+ case 's':
+ summary = 1;
+ break;
+ case 'S': {
+ char *endptr;
+
+ starting_offset = strtoul(optarg, &endptr, 0);
+ if (*endptr != '\0') {
+ error = 1;
+ warnx("invalid starting offset %s", optarg);
+ goto defect_bailout;
+ }
+ break;
+ }
+ case 'X':
+ hex_format = 1;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (list_type_set == 0) {
+ error = 1;
+ warnx("no defect list format specified");
+ goto defect_bailout;
+ }
+
+ if (arglist & CAM_ARG_PLIST) {
+ list_format |= SRDD10_PLIST;
+ lists_specified++;
+ }
+
+ if (arglist & CAM_ARG_GLIST) {
+ list_format |= SRDD10_GLIST;
+ lists_specified++;
+ }
+
+ /*
+ * This implies a summary, and was the previous behavior.
+ */
+ if (lists_specified == 0)
+ summary = 1;
+
+ ccb = cam_getccb(device);
+
+retry_12byte:
+
+ /*
+ * We start off asking for just the header to determine how much
+ * defect data is available. Some Hitachi drives return an error
+ * if you ask for more data than the drive has. Once we know the
+ * length, we retry the command with the returned length.
+ */
+ if (use_12byte == 0)
+ dlist_length = sizeof(*hdr10);
+ else
+ dlist_length = sizeof(*hdr12);
+
+retry:
+ if (defect_list != NULL) {
+ free(defect_list);
+ defect_list = NULL;
+ }
+ defect_list = malloc(dlist_length);
+ if (defect_list == NULL) {
+ warnx("can't malloc memory for defect list");
+ error = 1;
+ goto defect_bailout;
+ }
+
+next_batch:
+ bzero(defect_list, dlist_length);
+
+ /*
+ * cam_getccb() zeros the CCB header only. So we need to zero the
+ * payload portion of the ccb.
+ */
+ bzero(&(&ccb->ccb_h)[1],
+ sizeof(struct ccb_scsiio) - sizeof(struct ccb_hdr));
+
+ scsi_read_defects(&ccb->csio,
+ /*retries*/ retry_count,
+ /*cbfcnp*/ NULL,
+ /*tag_action*/ MSG_SIMPLE_Q_TAG,
+ /*list_format*/ list_format,
+ /*addr_desc_index*/ starting_offset,
+ /*data_ptr*/ defect_list,
+ /*dxfer_len*/ dlist_length,
+ /*minimum_cmd_size*/ use_12byte ? 12 : 0,
+ /*sense_len*/ SSD_FULL_SIZE,
+ /*timeout*/ timeout ? timeout : 5000);
+
+ /* Disable freezing the device queue */
+ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
+
+ if (cam_send_ccb(device, ccb) < 0) {
+ perror("error reading defect list");
+
+ if (arglist & CAM_ARG_VERBOSE) {
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ }
+
+ error = 1;
+ goto defect_bailout;
+ }
+
+ valid_len = ccb->csio.dxfer_len - ccb->csio.resid;
+
+ if (use_12byte == 0) {
+ hdr10 = (struct scsi_read_defect_data_hdr_10 *)defect_list;
+ hdr_size = sizeof(*hdr10);
+ hdr_max = SRDDH10_MAX_LENGTH;
+
+ if (valid_len >= hdr_size) {
+ returned_length = scsi_2btoul(hdr10->length);
+ returned_format = hdr10->format;
+ } else {
+ returned_length = 0;
+ returned_format = 0;
+ }
+ } else {
+ hdr12 = (struct scsi_read_defect_data_hdr_12 *)defect_list;
+ hdr_size = sizeof(*hdr12);
+ hdr_max = SRDDH12_MAX_LENGTH;
+
+ if (valid_len >= hdr_size) {
+ returned_length = scsi_4btoul(hdr12->length);
+ returned_format = hdr12->format;
+ } else {
+ returned_length = 0;
+ returned_format = 0;
+ }
+ }
+
+ returned_type = returned_format & SRDDH10_DLIST_FORMAT_MASK;
+ switch (returned_type) {
+ case SRDD10_BLOCK_FORMAT:
+ entry_size = sizeof(struct scsi_defect_desc_block);
+ break;
+ case SRDD10_LONG_BLOCK_FORMAT:
+ entry_size = sizeof(struct scsi_defect_desc_long_block);
+ break;
+ case SRDD10_EXT_PHYS_FORMAT:
+ case SRDD10_PHYSICAL_SECTOR_FORMAT:
+ entry_size = sizeof(struct scsi_defect_desc_phys_sector);
+ break;
+ case SRDD10_EXT_BFI_FORMAT:
+ case SRDD10_BYTES_FROM_INDEX_FORMAT:
+ entry_size = sizeof(struct scsi_defect_desc_bytes_from_index);
+ break;
+ default:
+ warnx("Unknown defect format 0x%x\n", returned_type);
+ error = 1;
+ goto defect_bailout;
+ break;
+ }
+
+ max_possible_size = (hdr_max / entry_size) * entry_size;
+ num_returned = returned_length / entry_size;
+ num_valid = min(returned_length, valid_len - hdr_size);
+ num_valid /= entry_size;
+
+ if (get_length != 0) {
+ get_length = 0;
+
+ if ((ccb->ccb_h.status & CAM_STATUS_MASK) ==
+ CAM_SCSI_STATUS_ERROR) {
+ struct scsi_sense_data *sense;
+ int error_code, sense_key, asc, ascq;
+
+ sense = &ccb->csio.sense_data;
+ scsi_extract_sense_len(sense, ccb->csio.sense_len -
+ ccb->csio.sense_resid, &error_code, &sense_key,
+ &asc, &ascq, /*show_errors*/ 1);
+
+ /*
+ * If the drive is reporting that it just doesn't
+ * support the defect list format, go ahead and use
+ * the length it reported. Otherwise, the length
+ * may not be valid, so use the maximum.
+ */
+ if ((sense_key == SSD_KEY_RECOVERED_ERROR)
+ && (asc == 0x1c) && (ascq == 0x00)
+ && (returned_length > 0)) {
+ if ((use_12byte == 0)
+ && (returned_length >= max_possible_size)) {
+ get_length = 1;
+ use_12byte = 1;
+ goto retry_12byte;
+ }
+ dlist_length = returned_length + hdr_size;
+ } else if ((sense_key == SSD_KEY_RECOVERED_ERROR)
+ && (asc == 0x1f) && (ascq == 0x00)
+ && (returned_length > 0)) {
+ /* Partial defect list transfer */
+ /*
+ * Hitachi drives return this error
+ * along with a partial defect list if they
+ * have more defects than the 10 byte
+ * command can support. Retry with the 12
+ * byte command.
+ */
+ if (use_12byte == 0) {
+ get_length = 1;
+ use_12byte = 1;
+ goto retry_12byte;
+ }
+ dlist_length = returned_length + hdr_size;
+ } else if ((sense_key == SSD_KEY_ILLEGAL_REQUEST)
+ && (asc == 0x24) && (ascq == 0x00)) {
+ /* Invalid field in CDB */
+ /*
+ * SBC-3 says that if the drive has more
+ * defects than can be reported with the
+ * 10 byte command, it should return this
+ * error and no data. Retry with the 12
+ * byte command.
+ */
+ if (use_12byte == 0) {
+ get_length = 1;
+ use_12byte = 1;
+ goto retry_12byte;
+ }
+ dlist_length = returned_length + hdr_size;
+ } else {
+ /*
+ * If we got a SCSI error and no valid length,
+ * just use the 10 byte maximum. The 12
+ * byte maximum is too large.
+ */
+ if (returned_length == 0)
+ dlist_length = SRDD10_MAX_LENGTH;
+ else {
+ if ((use_12byte == 0)
+ && (returned_length >=
+ max_possible_size)) {
+ get_length = 1;
+ use_12byte = 1;
+ goto retry_12byte;
+ }
+ dlist_length = returned_length +
+ hdr_size;
+ }
+ }
+ } else if ((ccb->ccb_h.status & CAM_STATUS_MASK) !=
+ CAM_REQ_CMP){
+ error = 1;
+ warnx("Error reading defect header");
+ if (arglist & CAM_ARG_VERBOSE)
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ goto defect_bailout;
+ } else {
+ if ((use_12byte == 0)
+ && (returned_length >= max_possible_size)) {
+ get_length = 1;
+ use_12byte = 1;
+ goto retry_12byte;
+ }
+ dlist_length = returned_length + hdr_size;
+ }
+ if (summary != 0) {
+ fprintf(stdout, "%u", num_returned);
+ if (quiet == 0) {
+ fprintf(stdout, " defect%s",
+ (num_returned != 1) ? "s" : "");
+ }
+ fprintf(stdout, "\n");
+
+ goto defect_bailout;
+ }
+
+ /*
+ * We always limit the list length to the 10-byte maximum
+ * length (0xffff). The reason is that some controllers
+ * can't handle larger I/Os, and we can transfer the entire
+ * 10 byte list in one shot. For drives that support the 12
+ * byte read defects command, we'll step through the list
+ * by specifying a starting offset. For drives that don't
+ * support the 12 byte command's starting offset, we'll
+ * just display the first 64K.
+ */
+ dlist_length = min(dlist_length, SRDD10_MAX_LENGTH);
+
+ goto retry;
+ }
+
+
+ if (((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_SCSI_STATUS_ERROR)
+ && (ccb->csio.scsi_status == SCSI_STATUS_CHECK_COND)
+ && ((ccb->ccb_h.status & CAM_AUTOSNS_VALID) != 0)) {
+ struct scsi_sense_data *sense;
+ int error_code, sense_key, asc, ascq;
+
+ sense = &ccb->csio.sense_data;
+ scsi_extract_sense_len(sense, ccb->csio.sense_len -
+ ccb->csio.sense_resid, &error_code, &sense_key, &asc,
+ &ascq, /*show_errors*/ 1);
+
+ /*
+ * According to the SCSI spec, if the disk doesn't support
+ * the requested format, it will generally return a sense
+ * key of RECOVERED ERROR, and an additional sense code
+ * of "DEFECT LIST NOT FOUND". HGST drives also return
+ * Primary/Grown defect list not found errors. So just
+ * check for an ASC of 0x1c.
+ */
+ if ((sense_key == SSD_KEY_RECOVERED_ERROR)
+ && (asc == 0x1c)) {
+ const char *format_str;
+
+ format_str = scsi_nv_to_str(defect_list_type_map,
+ sizeof(defect_list_type_map) /
+ sizeof(defect_list_type_map[0]),
+ list_format & SRDD10_DLIST_FORMAT_MASK);
+ warnx("requested defect format %s not available",
+ format_str ? format_str : "unknown");
+
+ format_str = scsi_nv_to_str(defect_list_type_map,
+ sizeof(defect_list_type_map) /
+ sizeof(defect_list_type_map[0]), returned_type);
+ if (format_str != NULL) {
+ warnx("Device returned %s format",
+ format_str);
+ } else {
+ error = 1;
+ warnx("Device returned unknown defect"
+ " data format %#x", returned_type);
+ goto defect_bailout;
+ }
+ } else {
+ error = 1;
+ warnx("Error returned from read defect data command");
+ if (arglist & CAM_ARG_VERBOSE)
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ goto defect_bailout;
+ }
+ } else if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
+ error = 1;
+ warnx("Error returned from read defect data command");
+ if (arglist & CAM_ARG_VERBOSE)
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ goto defect_bailout;
+ }
+
+ if (first_pass != 0) {
+ fprintf(stderr, "Got %d defect", num_returned);
+
+ if ((lists_specified == 0) || (num_returned == 0)) {
+ fprintf(stderr, "s.\n");
+ goto defect_bailout;
+ } else if (num_returned == 1)
+ fprintf(stderr, ":\n");
+ else
+ fprintf(stderr, "s:\n");
+
+ first_pass = 0;
+ }
+
+ /*
+ * XXX KDM I should probably clean up the printout format for the
+ * disk defects.
+ */
+ switch (returned_type) {
+ case SRDD10_PHYSICAL_SECTOR_FORMAT:
+ case SRDD10_EXT_PHYS_FORMAT:
+ {
+ struct scsi_defect_desc_phys_sector *dlist;
+
+ dlist = (struct scsi_defect_desc_phys_sector *)
+ (defect_list + hdr_size);
+
+ for (i = 0; i < num_valid; i++) {
+ uint32_t sector;
+
+ sector = scsi_4btoul(dlist[i].sector);
+ if (returned_type == SRDD10_EXT_PHYS_FORMAT) {
+ mads = (sector & SDD_EXT_PHYS_MADS) ?
+ 0 : 1;
+ sector &= ~SDD_EXT_PHYS_FLAG_MASK;
+ }
+ if (hex_format == 0)
+ fprintf(stdout, "%d:%d:%d%s",
+ scsi_3btoul(dlist[i].cylinder),
+ dlist[i].head,
+ scsi_4btoul(dlist[i].sector),
+ mads ? " - " : "\n");
+ else
+ fprintf(stdout, "0x%x:0x%x:0x%x%s",
+ scsi_3btoul(dlist[i].cylinder),
+ dlist[i].head,
+ scsi_4btoul(dlist[i].sector),
+ mads ? " - " : "\n");
+ mads = 0;
+ }
+ if (num_valid < num_returned) {
+ starting_offset += num_valid;
+ goto next_batch;
+ }
+ break;
+ }
+ case SRDD10_BYTES_FROM_INDEX_FORMAT:
+ case SRDD10_EXT_BFI_FORMAT:
+ {
+ struct scsi_defect_desc_bytes_from_index *dlist;
+
+ dlist = (struct scsi_defect_desc_bytes_from_index *)
+ (defect_list + hdr_size);
+
+ for (i = 0; i < num_valid; i++) {
+ uint32_t bfi;
+
+ bfi = scsi_4btoul(dlist[i].bytes_from_index);
+ if (returned_type == SRDD10_EXT_BFI_FORMAT) {
+ mads = (bfi & SDD_EXT_BFI_MADS) ? 1 : 0;
+ bfi &= ~SDD_EXT_BFI_FLAG_MASK;
+ }
+ if (hex_format == 0)
+ fprintf(stdout, "%d:%d:%d%s",
+ scsi_3btoul(dlist[i].cylinder),
+ dlist[i].head,
+ scsi_4btoul(dlist[i].bytes_from_index),
+ mads ? " - " : "\n");
+ else
+ fprintf(stdout, "0x%x:0x%x:0x%x%s",
+ scsi_3btoul(dlist[i].cylinder),
+ dlist[i].head,
+ scsi_4btoul(dlist[i].bytes_from_index),
+ mads ? " - " : "\n");
+
+ mads = 0;
+ }
+ if (num_valid < num_returned) {
+ starting_offset += num_valid;
+ goto next_batch;
+ }
+ break;
+ }
+ case SRDDH10_BLOCK_FORMAT:
+ {
+ struct scsi_defect_desc_block *dlist;
+
+ dlist = (struct scsi_defect_desc_block *)
+ (defect_list + hdr_size);
+
+ for (i = 0; i < num_valid; i++) {
+ if (hex_format == 0)
+ fprintf(stdout, "%u\n",
+ scsi_4btoul(dlist[i].address));
+ else
+ fprintf(stdout, "0x%x\n",
+ scsi_4btoul(dlist[i].address));
+ }
+
+ if (num_valid < num_returned) {
+ starting_offset += num_valid;
+ goto next_batch;
+ }
+
+ break;
+ }
+ case SRDD10_LONG_BLOCK_FORMAT:
+ {
+ struct scsi_defect_desc_long_block *dlist;
+
+ dlist = (struct scsi_defect_desc_long_block *)
+ (defect_list + hdr_size);
+
+ for (i = 0; i < num_valid; i++) {
+ if (hex_format == 0)
+ fprintf(stdout, "%ju\n",
+ (uintmax_t)scsi_8btou64(
+ dlist[i].address));
+ else
+ fprintf(stdout, "0x%jx\n",
+ (uintmax_t)scsi_8btou64(
+ dlist[i].address));
+ }
+
+ if (num_valid < num_returned) {
+ starting_offset += num_valid;
+ goto next_batch;
+ }
+ break;
+ }
+ default:
+ fprintf(stderr, "Unknown defect format 0x%x\n",
+ returned_type);
+ error = 1;
+ break;
+ }
+defect_bailout:
+
+ if (defect_list != NULL)
+ free(defect_list);
+
+ if (ccb != NULL)
+ cam_freeccb(ccb);
+
+ return(error);
+}
+#endif /* MINIMALISTIC */
+
+#if 0
+void
+reassignblocks(struct cam_device *device, u_int32_t *blocks, int num_blocks)
+{
+ union ccb *ccb;
+
+ ccb = cam_getccb(device);
+
+ cam_freeccb(ccb);
+}
+#endif
+
+#ifndef MINIMALISTIC
+void
+mode_sense(struct cam_device *device, int mode_page, int page_control,
+ int dbd, int retry_count, int timeout, u_int8_t *data, int datalen)
+{
+ union ccb *ccb;
+ int retval;
+
+ ccb = cam_getccb(device);
+
+ if (ccb == NULL)
+ errx(1, "mode_sense: couldn't allocate CCB");
+
+ bzero(&(&ccb->ccb_h)[1],
+ sizeof(struct ccb_scsiio) - sizeof(struct ccb_hdr));
+
+ scsi_mode_sense(&ccb->csio,
+ /* retries */ retry_count,
+ /* cbfcnp */ NULL,
+ /* tag_action */ MSG_SIMPLE_Q_TAG,
+ /* dbd */ dbd,
+ /* page_code */ page_control << 6,
+ /* page */ mode_page,
+ /* param_buf */ data,
+ /* param_len */ datalen,
+ /* sense_len */ SSD_FULL_SIZE,
+ /* timeout */ timeout ? timeout : 5000);
+
+ if (arglist & CAM_ARG_ERR_RECOVER)
+ ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER;
+
+ /* Disable freezing the device queue */
+ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
+
+ if (((retval = cam_send_ccb(device, ccb)) < 0)
+ || ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP)) {
+ if (arglist & CAM_ARG_VERBOSE) {
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ }
+ cam_freeccb(ccb);
+ cam_close_device(device);
+ if (retval < 0)
+ err(1, "error sending mode sense command");
+ else
+ errx(1, "error sending mode sense command");
+ }
+
+ cam_freeccb(ccb);
+}
+
+void
+mode_select(struct cam_device *device, int save_pages, int retry_count,
+ int timeout, u_int8_t *data, int datalen)
+{
+ union ccb *ccb;
+ int retval;
+
+ ccb = cam_getccb(device);
+
+ if (ccb == NULL)
+ errx(1, "mode_select: couldn't allocate CCB");
+
+ bzero(&(&ccb->ccb_h)[1],
+ sizeof(struct ccb_scsiio) - sizeof(struct ccb_hdr));
+
+ scsi_mode_select(&ccb->csio,
+ /* retries */ retry_count,
+ /* cbfcnp */ NULL,
+ /* tag_action */ MSG_SIMPLE_Q_TAG,
+ /* scsi_page_fmt */ 1,
+ /* save_pages */ save_pages,
+ /* param_buf */ data,
+ /* param_len */ datalen,
+ /* sense_len */ SSD_FULL_SIZE,
+ /* timeout */ timeout ? timeout : 5000);
+
+ if (arglist & CAM_ARG_ERR_RECOVER)
+ ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER;
+
+ /* Disable freezing the device queue */
+ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
+
+ if (((retval = cam_send_ccb(device, ccb)) < 0)
+ || ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP)) {
+ if (arglist & CAM_ARG_VERBOSE) {
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ }
+ cam_freeccb(ccb);
+ cam_close_device(device);
+
+ if (retval < 0)
+ err(1, "error sending mode select command");
+ else
+ errx(1, "error sending mode select command");
+
+ }
+
+ cam_freeccb(ccb);
+}
+
+void
+modepage(struct cam_device *device, int argc, char **argv, char *combinedopt,
+ int retry_count, int timeout)
+{
+ int c, mode_page = -1, page_control = 0;
+ int binary = 0, list = 0;
+
+ while ((c = getopt(argc, argv, combinedopt)) != -1) {
+ switch(c) {
+ case 'b':
+ binary = 1;
+ break;
+ case 'd':
+ arglist |= CAM_ARG_DBD;
+ break;
+ case 'e':
+ arglist |= CAM_ARG_MODE_EDIT;
+ break;
+ case 'l':
+ list = 1;
+ break;
+ case 'm':
+ mode_page = strtol(optarg, NULL, 0);
+ if (mode_page < 0)
+ errx(1, "invalid mode page %d", mode_page);
+ break;
+ case 'P':
+ page_control = strtol(optarg, NULL, 0);
+ if ((page_control < 0) || (page_control > 3))
+ errx(1, "invalid page control field %d",
+ page_control);
+ arglist |= CAM_ARG_PAGE_CNTL;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (mode_page == -1 && list == 0)
+ errx(1, "you must specify a mode page!");
+
+ if (list) {
+ mode_list(device, page_control, arglist & CAM_ARG_DBD,
+ retry_count, timeout);
+ } else {
+ mode_edit(device, mode_page, page_control,
+ arglist & CAM_ARG_DBD, arglist & CAM_ARG_MODE_EDIT, binary,
+ retry_count, timeout);
+ }
+}
+
+static int
+scsicmd(struct cam_device *device, int argc, char **argv, char *combinedopt,
+ int retry_count, int timeout)
+{
+ union ccb *ccb;
+ u_int32_t flags = CAM_DIR_NONE;
+ u_int8_t *data_ptr = NULL;
+ u_int8_t cdb[20];
+ u_int8_t atacmd[12];
+ struct get_hook hook;
+ int c, data_bytes = 0;
+ int cdb_len = 0;
+ int atacmd_len = 0;
+ int dmacmd = 0;
+ int fpdmacmd = 0;
+ int need_res = 0;
+ char *datastr = NULL, *tstr, *resstr = NULL;
+ int error = 0;
+ int fd_data = 0, fd_res = 0;
+ int retval;
+
+ ccb = cam_getccb(device);
+
+ if (ccb == NULL) {
+ warnx("scsicmd: error allocating ccb");
+ return(1);
+ }
+
+ bzero(&(&ccb->ccb_h)[1],
+ sizeof(union ccb) - sizeof(struct ccb_hdr));
+
+ while ((c = getopt(argc, argv, combinedopt)) != -1) {
+ switch(c) {
+ case 'a':
+ tstr = optarg;
+ while (isspace(*tstr) && (*tstr != '\0'))
+ tstr++;
+ hook.argc = argc - optind;
+ hook.argv = argv + optind;
+ hook.got = 0;
+ atacmd_len = buff_encode_visit(atacmd, sizeof(atacmd), tstr,
+ iget, &hook);
+ /*
+ * Increment optind by the number of arguments the
+ * encoding routine processed. After each call to
+ * getopt(3), optind points to the argument that
+ * getopt should process _next_. In this case,
+ * that means it points to the first command string
+ * argument, if there is one. Once we increment
+ * this, it should point to either the next command
+ * line argument, or it should be past the end of
+ * the list.
+ */
+ optind += hook.got;
+ break;
+ case 'c':
+ tstr = optarg;
+ while (isspace(*tstr) && (*tstr != '\0'))
+ tstr++;
+ hook.argc = argc - optind;
+ hook.argv = argv + optind;
+ hook.got = 0;
+ cdb_len = buff_encode_visit(cdb, sizeof(cdb), tstr,
+ iget, &hook);
+ /*
+ * Increment optind by the number of arguments the
+ * encoding routine processed. After each call to
+ * getopt(3), optind points to the argument that
+ * getopt should process _next_. In this case,
+ * that means it points to the first command string
+ * argument, if there is one. Once we increment
+ * this, it should point to either the next command
+ * line argument, or it should be past the end of
+ * the list.
+ */
+ optind += hook.got;
+ break;
+ case 'd':
+ dmacmd = 1;
+ break;
+ case 'f':
+ fpdmacmd = 1;
+ break;
+ case 'i':
+ if (arglist & CAM_ARG_CMD_OUT) {
+ warnx("command must either be "
+ "read or write, not both");
+ error = 1;
+ goto scsicmd_bailout;
+ }
+ arglist |= CAM_ARG_CMD_IN;
+ flags = CAM_DIR_IN;
+ data_bytes = strtol(optarg, NULL, 0);
+ if (data_bytes <= 0) {
+ warnx("invalid number of input bytes %d",
+ data_bytes);
+ error = 1;
+ goto scsicmd_bailout;
+ }
+ hook.argc = argc - optind;
+ hook.argv = argv + optind;
+ hook.got = 0;
+ optind++;
+ datastr = cget(&hook, NULL);
+ /*
+ * If the user supplied "-" instead of a format, he
+ * wants the data to be written to stdout.
+ */
+ if ((datastr != NULL)
+ && (datastr[0] == '-'))
+ fd_data = 1;
+
+ data_ptr = (u_int8_t *)malloc(data_bytes);
+ if (data_ptr == NULL) {
+ warnx("can't malloc memory for data_ptr");
+ error = 1;
+ goto scsicmd_bailout;
+ }
+ break;
+ case 'o':
+ if (arglist & CAM_ARG_CMD_IN) {
+ warnx("command must either be "
+ "read or write, not both");
+ error = 1;
+ goto scsicmd_bailout;
+ }
+ arglist |= CAM_ARG_CMD_OUT;
+ flags = CAM_DIR_OUT;
+ data_bytes = strtol(optarg, NULL, 0);
+ if (data_bytes <= 0) {
+ warnx("invalid number of output bytes %d",
+ data_bytes);
+ error = 1;
+ goto scsicmd_bailout;
+ }
+ hook.argc = argc - optind;
+ hook.argv = argv + optind;
+ hook.got = 0;
+ datastr = cget(&hook, NULL);
+ data_ptr = (u_int8_t *)malloc(data_bytes);
+ if (data_ptr == NULL) {
+ warnx("can't malloc memory for data_ptr");
+ error = 1;
+ goto scsicmd_bailout;
+ }
+ bzero(data_ptr, data_bytes);
+ /*
+ * If the user supplied "-" instead of a format, he
+ * wants the data to be read from stdin.
+ */
+ if ((datastr != NULL)
+ && (datastr[0] == '-'))
+ fd_data = 1;
+ else
+ buff_encode_visit(data_ptr, data_bytes, datastr,
+ iget, &hook);
+ optind += hook.got;
+ break;
+ case 'r':
+ need_res = 1;
+ hook.argc = argc - optind;
+ hook.argv = argv + optind;
+ hook.got = 0;
+ resstr = cget(&hook, NULL);
+ if ((resstr != NULL) && (resstr[0] == '-'))
+ fd_res = 1;
+ optind += hook.got;
+ break;
+ default:
+ break;
+ }
+ }
+
+ /*
+ * If fd_data is set, and we're writing to the device, we need to
+ * read the data the user wants written from stdin.
+ */
+ if ((fd_data == 1) && (arglist & CAM_ARG_CMD_OUT)) {
+ ssize_t amt_read;
+ int amt_to_read = data_bytes;
+ u_int8_t *buf_ptr = data_ptr;
+
+ for (amt_read = 0; amt_to_read > 0;
+ amt_read = read(STDIN_FILENO, buf_ptr, amt_to_read)) {
+ if (amt_read == -1) {
+ warn("error reading data from stdin");
+ error = 1;
+ goto scsicmd_bailout;
+ }
+ amt_to_read -= amt_read;
+ buf_ptr += amt_read;
+ }
+ }
+
+ if (arglist & CAM_ARG_ERR_RECOVER)
+ flags |= CAM_PASS_ERR_RECOVER;
+
+ /* Disable freezing the device queue */
+ flags |= CAM_DEV_QFRZDIS;
+
+ if (cdb_len) {
+ /*
+ * This is taken from the SCSI-3 draft spec.
+ * (T10/1157D revision 0.3)
+ * The top 3 bits of an opcode are the group code.
+ * The next 5 bits are the command code.
+ * Group 0: six byte commands
+ * Group 1: ten byte commands
+ * Group 2: ten byte commands
+ * Group 3: reserved
+ * Group 4: sixteen byte commands
+ * Group 5: twelve byte commands
+ * Group 6: vendor specific
+ * Group 7: vendor specific
+ */
+ switch((cdb[0] >> 5) & 0x7) {
+ case 0:
+ cdb_len = 6;
+ break;
+ case 1:
+ case 2:
+ cdb_len = 10;
+ break;
+ case 3:
+ case 6:
+ case 7:
+ /* computed by buff_encode_visit */
+ break;
+ case 4:
+ cdb_len = 16;
+ break;
+ case 5:
+ cdb_len = 12;
+ break;
+ }
+
+ /*
+ * We should probably use csio_build_visit or something like that
+ * here, but it's easier to encode arguments as you go. The
+ * alternative would be skipping the CDB argument and then encoding
+ * it here, since we've got the data buffer argument by now.
+ */
+ bcopy(cdb, &ccb->csio.cdb_io.cdb_bytes, cdb_len);
+
+ cam_fill_csio(&ccb->csio,
+ /*retries*/ retry_count,
+ /*cbfcnp*/ NULL,
+ /*flags*/ flags,
+ /*tag_action*/ MSG_SIMPLE_Q_TAG,
+ /*data_ptr*/ data_ptr,
+ /*dxfer_len*/ data_bytes,
+ /*sense_len*/ SSD_FULL_SIZE,
+ /*cdb_len*/ cdb_len,
+ /*timeout*/ timeout ? timeout : 5000);
+ } else {
+ atacmd_len = 12;
+ bcopy(atacmd, &ccb->ataio.cmd.command, atacmd_len);
+ if (need_res)
+ ccb->ataio.cmd.flags |= CAM_ATAIO_NEEDRESULT;
+ if (dmacmd)
+ ccb->ataio.cmd.flags |= CAM_ATAIO_DMA;
+ if (fpdmacmd)
+ ccb->ataio.cmd.flags |= CAM_ATAIO_FPDMA;
+
+ cam_fill_ataio(&ccb->ataio,
+ /*retries*/ retry_count,
+ /*cbfcnp*/ NULL,
+ /*flags*/ flags,
+ /*tag_action*/ 0,
+ /*data_ptr*/ data_ptr,
+ /*dxfer_len*/ data_bytes,
+ /*timeout*/ timeout ? timeout : 5000);
+ }
+
+ if (((retval = cam_send_ccb(device, ccb)) < 0)
+ || ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP)) {
+ const char warnstr[] = "error sending command";
+
+ if (retval < 0)
+ warn(warnstr);
+ else
+ warnx(warnstr);
+
+ if (arglist & CAM_ARG_VERBOSE) {
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ }
+
+ error = 1;
+ goto scsicmd_bailout;
+ }
+
+ if (atacmd_len && need_res) {
+ if (fd_res == 0) {
+ buff_decode_visit(&ccb->ataio.res.status, 11, resstr,
+ arg_put, NULL);
+ fprintf(stdout, "\n");
+ } else {
+ fprintf(stdout,
+ "%02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X\n",
+ ccb->ataio.res.status,
+ ccb->ataio.res.error,
+ ccb->ataio.res.lba_low,
+ ccb->ataio.res.lba_mid,
+ ccb->ataio.res.lba_high,
+ ccb->ataio.res.device,
+ ccb->ataio.res.lba_low_exp,
+ ccb->ataio.res.lba_mid_exp,
+ ccb->ataio.res.lba_high_exp,
+ ccb->ataio.res.sector_count,
+ ccb->ataio.res.sector_count_exp);
+ fflush(stdout);
+ }
+ }
+
+ if (((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP)
+ && (arglist & CAM_ARG_CMD_IN)
+ && (data_bytes > 0)) {
+ if (fd_data == 0) {
+ buff_decode_visit(data_ptr, data_bytes, datastr,
+ arg_put, NULL);
+ fprintf(stdout, "\n");
+ } else {
+ ssize_t amt_written;
+ int amt_to_write = data_bytes;
+ u_int8_t *buf_ptr = data_ptr;
+
+ for (amt_written = 0; (amt_to_write > 0) &&
+ (amt_written =write(1, buf_ptr,amt_to_write))> 0;){
+ amt_to_write -= amt_written;
+ buf_ptr += amt_written;
+ }
+ if (amt_written == -1) {
+ warn("error writing data to stdout");
+ error = 1;
+ goto scsicmd_bailout;
+ } else if ((amt_written == 0)
+ && (amt_to_write > 0)) {
+ warnx("only wrote %u bytes out of %u",
+ data_bytes - amt_to_write, data_bytes);
+ }
+ }
+ }
+
+scsicmd_bailout:
+
+ if ((data_bytes > 0) && (data_ptr != NULL))
+ free(data_ptr);
+
+ cam_freeccb(ccb);
+
+ return(error);
+}
+
+static int
+camdebug(int argc, char **argv, char *combinedopt)
+{
+ int c, fd;
+ path_id_t bus = CAM_BUS_WILDCARD;
+ target_id_t target = CAM_TARGET_WILDCARD;
+ lun_id_t lun = CAM_LUN_WILDCARD;
+ char *tstr, *tmpstr = NULL;
+ union ccb ccb;
+ int error = 0;
+
+ bzero(&ccb, sizeof(union ccb));
+
+ while ((c = getopt(argc, argv, combinedopt)) != -1) {
+ switch(c) {
+ case 'I':
+ arglist |= CAM_ARG_DEBUG_INFO;
+ ccb.cdbg.flags |= CAM_DEBUG_INFO;
+ break;
+ case 'P':
+ arglist |= CAM_ARG_DEBUG_PERIPH;
+ ccb.cdbg.flags |= CAM_DEBUG_PERIPH;
+ break;
+ case 'S':
+ arglist |= CAM_ARG_DEBUG_SUBTRACE;
+ ccb.cdbg.flags |= CAM_DEBUG_SUBTRACE;
+ break;
+ case 'T':
+ arglist |= CAM_ARG_DEBUG_TRACE;
+ ccb.cdbg.flags |= CAM_DEBUG_TRACE;
+ break;
+ case 'X':
+ arglist |= CAM_ARG_DEBUG_XPT;
+ ccb.cdbg.flags |= CAM_DEBUG_XPT;
+ break;
+ case 'c':
+ arglist |= CAM_ARG_DEBUG_CDB;
+ ccb.cdbg.flags |= CAM_DEBUG_CDB;
+ break;
+ case 'p':
+ arglist |= CAM_ARG_DEBUG_PROBE;
+ ccb.cdbg.flags |= CAM_DEBUG_PROBE;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if ((fd = open(XPT_DEVICE, O_RDWR)) < 0) {
+ warnx("error opening transport layer device %s", XPT_DEVICE);
+ warn("%s", XPT_DEVICE);
+ return(1);
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc <= 0) {
+ warnx("you must specify \"off\", \"all\" or a bus,");
+ warnx("bus:target, or bus:target:lun");
+ close(fd);
+ return(1);
+ }
+
+ tstr = *argv;
+
+ while (isspace(*tstr) && (*tstr != '\0'))
+ tstr++;
+
+ if (strncmp(tstr, "off", 3) == 0) {
+ ccb.cdbg.flags = CAM_DEBUG_NONE;
+ arglist &= ~(CAM_ARG_DEBUG_INFO|CAM_ARG_DEBUG_PERIPH|
+ CAM_ARG_DEBUG_TRACE|CAM_ARG_DEBUG_SUBTRACE|
+ CAM_ARG_DEBUG_XPT|CAM_ARG_DEBUG_PROBE);
+ } else if (strncmp(tstr, "all", 3) != 0) {
+ tmpstr = (char *)strtok(tstr, ":");
+ if ((tmpstr != NULL) && (*tmpstr != '\0')){
+ bus = strtol(tmpstr, NULL, 0);
+ arglist |= CAM_ARG_BUS;
+ tmpstr = (char *)strtok(NULL, ":");
+ if ((tmpstr != NULL) && (*tmpstr != '\0')){
+ target = strtol(tmpstr, NULL, 0);
+ arglist |= CAM_ARG_TARGET;
+ tmpstr = (char *)strtok(NULL, ":");
+ if ((tmpstr != NULL) && (*tmpstr != '\0')){
+ lun = strtol(tmpstr, NULL, 0);
+ arglist |= CAM_ARG_LUN;
+ }
+ }
+ } else {
+ error = 1;
+ warnx("you must specify \"all\", \"off\", or a bus,");
+ warnx("bus:target, or bus:target:lun to debug");
+ }
+ }
+
+ if (error == 0) {
+
+ ccb.ccb_h.func_code = XPT_DEBUG;
+ ccb.ccb_h.path_id = bus;
+ ccb.ccb_h.target_id = target;
+ ccb.ccb_h.target_lun = lun;
+
+ if (ioctl(fd, CAMIOCOMMAND, &ccb) == -1) {
+ warn("CAMIOCOMMAND ioctl failed");
+ error = 1;
+ }
+
+ if (error == 0) {
+ if ((ccb.ccb_h.status & CAM_STATUS_MASK) ==
+ CAM_FUNC_NOTAVAIL) {
+ warnx("CAM debugging not available");
+ warnx("you need to put options CAMDEBUG in"
+ " your kernel config file!");
+ error = 1;
+ } else if ((ccb.ccb_h.status & CAM_STATUS_MASK) !=
+ CAM_REQ_CMP) {
+ warnx("XPT_DEBUG CCB failed with status %#x",
+ ccb.ccb_h.status);
+ error = 1;
+ } else {
+ if (ccb.cdbg.flags == CAM_DEBUG_NONE) {
+ fprintf(stderr,
+ "Debugging turned off\n");
+ } else {
+ fprintf(stderr,
+ "Debugging enabled for "
+ "%d:%d:%jx\n",
+ bus, target, (uintmax_t)lun);
+ }
+ }
+ }
+ close(fd);
+ }
+
+ return(error);
+}
+
+static int
+tagcontrol(struct cam_device *device, int argc, char **argv,
+ char *combinedopt)
+{
+ int c;
+ union ccb *ccb;
+ int numtags = -1;
+ int retval = 0;
+ int quiet = 0;
+ char pathstr[1024];
+
+ ccb = cam_getccb(device);
+
+ if (ccb == NULL) {
+ warnx("tagcontrol: error allocating ccb");
+ return(1);
+ }
+
+ while ((c = getopt(argc, argv, combinedopt)) != -1) {
+ switch(c) {
+ case 'N':
+ numtags = strtol(optarg, NULL, 0);
+ if (numtags < 0) {
+ warnx("tag count %d is < 0", numtags);
+ retval = 1;
+ goto tagcontrol_bailout;
+ }
+ break;
+ case 'q':
+ quiet++;
+ break;
+ default:
+ break;
+ }
+ }
+
+ cam_path_string(device, pathstr, sizeof(pathstr));
+
+ if (numtags >= 0) {
+ bzero(&(&ccb->ccb_h)[1],
+ sizeof(struct ccb_relsim) - sizeof(struct ccb_hdr));
+ ccb->ccb_h.func_code = XPT_REL_SIMQ;
+ ccb->ccb_h.flags = CAM_DEV_QFREEZE;
+ ccb->crs.release_flags = RELSIM_ADJUST_OPENINGS;
+ ccb->crs.openings = numtags;
+
+
+ if (cam_send_ccb(device, ccb) < 0) {
+ perror("error sending XPT_REL_SIMQ CCB");
+ retval = 1;
+ goto tagcontrol_bailout;
+ }
+
+ if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
+ warnx("XPT_REL_SIMQ CCB failed");
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ retval = 1;
+ goto tagcontrol_bailout;
+ }
+
+
+ if (quiet == 0)
+ fprintf(stdout, "%stagged openings now %d\n",
+ pathstr, ccb->crs.openings);
+ }
+
+ bzero(&(&ccb->ccb_h)[1],
+ sizeof(struct ccb_getdevstats) - sizeof(struct ccb_hdr));
+
+ ccb->ccb_h.func_code = XPT_GDEV_STATS;
+
+ if (cam_send_ccb(device, ccb) < 0) {
+ perror("error sending XPT_GDEV_STATS CCB");
+ retval = 1;
+ goto tagcontrol_bailout;
+ }
+
+ if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
+ warnx("XPT_GDEV_STATS CCB failed");
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ retval = 1;
+ goto tagcontrol_bailout;
+ }
+
+ if (arglist & CAM_ARG_VERBOSE) {
+ fprintf(stdout, "%s", pathstr);
+ fprintf(stdout, "dev_openings %d\n", ccb->cgds.dev_openings);
+ fprintf(stdout, "%s", pathstr);
+ fprintf(stdout, "dev_active %d\n", ccb->cgds.dev_active);
+ fprintf(stdout, "%s", pathstr);
+ fprintf(stdout, "allocated %d\n", ccb->cgds.allocated);
+ fprintf(stdout, "%s", pathstr);
+ fprintf(stdout, "queued %d\n", ccb->cgds.queued);
+ fprintf(stdout, "%s", pathstr);
+ fprintf(stdout, "held %d\n", ccb->cgds.held);
+ fprintf(stdout, "%s", pathstr);
+ fprintf(stdout, "mintags %d\n", ccb->cgds.mintags);
+ fprintf(stdout, "%s", pathstr);
+ fprintf(stdout, "maxtags %d\n", ccb->cgds.maxtags);
+ } else {
+ if (quiet == 0) {
+ fprintf(stdout, "%s", pathstr);
+ fprintf(stdout, "device openings: ");
+ }
+ fprintf(stdout, "%d\n", ccb->cgds.dev_openings +
+ ccb->cgds.dev_active);
+ }
+
+tagcontrol_bailout:
+
+ cam_freeccb(ccb);
+ return(retval);
+}
+
+static void
+cts_print(struct cam_device *device, struct ccb_trans_settings *cts)
+{
+ char pathstr[1024];
+
+ cam_path_string(device, pathstr, sizeof(pathstr));
+
+ if (cts->transport == XPORT_SPI) {
+ struct ccb_trans_settings_spi *spi =
+ &cts->xport_specific.spi;
+
+ if ((spi->valid & CTS_SPI_VALID_SYNC_RATE) != 0) {
+
+ fprintf(stdout, "%ssync parameter: %d\n", pathstr,
+ spi->sync_period);
+
+ if (spi->sync_offset != 0) {
+ u_int freq;
+
+ freq = scsi_calc_syncsrate(spi->sync_period);
+ fprintf(stdout, "%sfrequency: %d.%03dMHz\n",
+ pathstr, freq / 1000, freq % 1000);
+ }
+ }
+
+ if (spi->valid & CTS_SPI_VALID_SYNC_OFFSET) {
+ fprintf(stdout, "%soffset: %d\n", pathstr,
+ spi->sync_offset);
+ }
+
+ if (spi->valid & CTS_SPI_VALID_BUS_WIDTH) {
+ fprintf(stdout, "%sbus width: %d bits\n", pathstr,
+ (0x01 << spi->bus_width) * 8);
+ }
+
+ if (spi->valid & CTS_SPI_VALID_DISC) {
+ fprintf(stdout, "%sdisconnection is %s\n", pathstr,
+ (spi->flags & CTS_SPI_FLAGS_DISC_ENB) ?
+ "enabled" : "disabled");
+ }
+ }
+ if (cts->transport == XPORT_FC) {
+ struct ccb_trans_settings_fc *fc =
+ &cts->xport_specific.fc;
+
+ if (fc->valid & CTS_FC_VALID_WWNN)
+ fprintf(stdout, "%sWWNN: 0x%llx\n", pathstr,
+ (long long) fc->wwnn);
+ if (fc->valid & CTS_FC_VALID_WWPN)
+ fprintf(stdout, "%sWWPN: 0x%llx\n", pathstr,
+ (long long) fc->wwpn);
+ if (fc->valid & CTS_FC_VALID_PORT)
+ fprintf(stdout, "%sPortID: 0x%x\n", pathstr, fc->port);
+ if (fc->valid & CTS_FC_VALID_SPEED)
+ fprintf(stdout, "%stransfer speed: %d.%03dMB/s\n",
+ pathstr, fc->bitrate / 1000, fc->bitrate % 1000);
+ }
+ if (cts->transport == XPORT_SAS) {
+ struct ccb_trans_settings_sas *sas =
+ &cts->xport_specific.sas;
+
+ if (sas->valid & CTS_SAS_VALID_SPEED)
+ fprintf(stdout, "%stransfer speed: %d.%03dMB/s\n",
+ pathstr, sas->bitrate / 1000, sas->bitrate % 1000);
+ }
+ if (cts->transport == XPORT_ATA) {
+ struct ccb_trans_settings_pata *pata =
+ &cts->xport_specific.ata;
+
+ if ((pata->valid & CTS_ATA_VALID_MODE) != 0) {
+ fprintf(stdout, "%sATA mode: %s\n", pathstr,
+ ata_mode2string(pata->mode));
+ }
+ if ((pata->valid & CTS_ATA_VALID_ATAPI) != 0) {
+ fprintf(stdout, "%sATAPI packet length: %d\n", pathstr,
+ pata->atapi);
+ }
+ if ((pata->valid & CTS_ATA_VALID_BYTECOUNT) != 0) {
+ fprintf(stdout, "%sPIO transaction length: %d\n",
+ pathstr, pata->bytecount);
+ }
+ }
+ if (cts->transport == XPORT_SATA) {
+ struct ccb_trans_settings_sata *sata =
+ &cts->xport_specific.sata;
+
+ if ((sata->valid & CTS_SATA_VALID_REVISION) != 0) {
+ fprintf(stdout, "%sSATA revision: %d.x\n", pathstr,
+ sata->revision);
+ }
+ if ((sata->valid & CTS_SATA_VALID_MODE) != 0) {
+ fprintf(stdout, "%sATA mode: %s\n", pathstr,
+ ata_mode2string(sata->mode));
+ }
+ if ((sata->valid & CTS_SATA_VALID_ATAPI) != 0) {
+ fprintf(stdout, "%sATAPI packet length: %d\n", pathstr,
+ sata->atapi);
+ }
+ if ((sata->valid & CTS_SATA_VALID_BYTECOUNT) != 0) {
+ fprintf(stdout, "%sPIO transaction length: %d\n",
+ pathstr, sata->bytecount);
+ }
+ if ((sata->valid & CTS_SATA_VALID_PM) != 0) {
+ fprintf(stdout, "%sPMP presence: %d\n", pathstr,
+ sata->pm_present);
+ }
+ if ((sata->valid & CTS_SATA_VALID_TAGS) != 0) {
+ fprintf(stdout, "%sNumber of tags: %d\n", pathstr,
+ sata->tags);
+ }
+ if ((sata->valid & CTS_SATA_VALID_CAPS) != 0) {
+ fprintf(stdout, "%sSATA capabilities: %08x\n", pathstr,
+ sata->caps);
+ }
+ }
+ if (cts->protocol == PROTO_ATA) {
+ struct ccb_trans_settings_ata *ata=
+ &cts->proto_specific.ata;
+
+ if (ata->valid & CTS_ATA_VALID_TQ) {
+ fprintf(stdout, "%stagged queueing: %s\n", pathstr,
+ (ata->flags & CTS_ATA_FLAGS_TAG_ENB) ?
+ "enabled" : "disabled");
+ }
+ }
+ if (cts->protocol == PROTO_SCSI) {
+ struct ccb_trans_settings_scsi *scsi=
+ &cts->proto_specific.scsi;
+
+ if (scsi->valid & CTS_SCSI_VALID_TQ) {
+ fprintf(stdout, "%stagged queueing: %s\n", pathstr,
+ (scsi->flags & CTS_SCSI_FLAGS_TAG_ENB) ?
+ "enabled" : "disabled");
+ }
+ }
+
+}
+
+/*
+ * Get a path inquiry CCB for the specified device.
+ */
+static int
+get_cpi(struct cam_device *device, struct ccb_pathinq *cpi)
+{
+ union ccb *ccb;
+ int retval = 0;
+
+ ccb = cam_getccb(device);
+ if (ccb == NULL) {
+ warnx("get_cpi: couldn't allocate CCB");
+ return(1);
+ }
+ bzero(&(&ccb->ccb_h)[1],
+ sizeof(struct ccb_pathinq) - sizeof(struct ccb_hdr));
+ ccb->ccb_h.func_code = XPT_PATH_INQ;
+ if (cam_send_ccb(device, ccb) < 0) {
+ warn("get_cpi: error sending Path Inquiry CCB");
+ if (arglist & CAM_ARG_VERBOSE)
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ retval = 1;
+ goto get_cpi_bailout;
+ }
+ if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
+ if (arglist & CAM_ARG_VERBOSE)
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ retval = 1;
+ goto get_cpi_bailout;
+ }
+ bcopy(&ccb->cpi, cpi, sizeof(struct ccb_pathinq));
+
+get_cpi_bailout:
+ cam_freeccb(ccb);
+ return(retval);
+}
+
+/*
+ * Get a get device CCB for the specified device.
+ */
+static int
+get_cgd(struct cam_device *device, struct ccb_getdev *cgd)
+{
+ union ccb *ccb;
+ int retval = 0;
+
+ ccb = cam_getccb(device);
+ if (ccb == NULL) {
+ warnx("get_cgd: couldn't allocate CCB");
+ return(1);
+ }
+ bzero(&(&ccb->ccb_h)[1],
+ sizeof(struct ccb_pathinq) - sizeof(struct ccb_hdr));
+ ccb->ccb_h.func_code = XPT_GDEV_TYPE;
+ if (cam_send_ccb(device, ccb) < 0) {
+ warn("get_cgd: error sending Path Inquiry CCB");
+ if (arglist & CAM_ARG_VERBOSE)
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ retval = 1;
+ goto get_cgd_bailout;
+ }
+ if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
+ if (arglist & CAM_ARG_VERBOSE)
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ retval = 1;
+ goto get_cgd_bailout;
+ }
+ bcopy(&ccb->cgd, cgd, sizeof(struct ccb_getdev));
+
+get_cgd_bailout:
+ cam_freeccb(ccb);
+ return(retval);
+}
+
+/* return the type of disk (really the command type) */
+static const char *
+get_disk_type(struct cam_device *device)
+{
+ struct ccb_getdev cgd;
+
+ (void) memset(&cgd, 0x0, sizeof(cgd));
+ get_cgd(device, &cgd);
+ switch(cgd.protocol) {
+ case PROTO_SCSI:
+ return "scsi";
+ case PROTO_ATA:
+ case PROTO_ATAPI:
+ case PROTO_SATAPM:
+ return "ata";
+ default:
+ return "unknown";
+ }
+}
+
+static void
+cpi_print(struct ccb_pathinq *cpi)
+{
+ char adapter_str[1024];
+ int i;
+
+ snprintf(adapter_str, sizeof(adapter_str),
+ "%s%d:", cpi->dev_name, cpi->unit_number);
+
+ fprintf(stdout, "%s SIM/HBA version: %d\n", adapter_str,
+ cpi->version_num);
+
+ for (i = 1; i < 0xff; i = i << 1) {
+ const char *str;
+
+ if ((i & cpi->hba_inquiry) == 0)
+ continue;
+
+ fprintf(stdout, "%s supports ", adapter_str);
+
+ switch(i) {
+ case PI_MDP_ABLE:
+ str = "MDP message";
+ break;
+ case PI_WIDE_32:
+ str = "32 bit wide SCSI";
+ break;
+ case PI_WIDE_16:
+ str = "16 bit wide SCSI";
+ break;
+ case PI_SDTR_ABLE:
+ str = "SDTR message";
+ break;
+ case PI_LINKED_CDB:
+ str = "linked CDBs";
+ break;
+ case PI_TAG_ABLE:
+ str = "tag queue messages";
+ break;
+ case PI_SOFT_RST:
+ str = "soft reset alternative";
+ break;
+ case PI_SATAPM:
+ str = "SATA Port Multiplier";
+ break;
+ default:
+ str = "unknown PI bit set";
+ break;
+ }
+ fprintf(stdout, "%s\n", str);
+ }
+
+ for (i = 1; i < 0xff; i = i << 1) {
+ const char *str;
+
+ if ((i & cpi->hba_misc) == 0)
+ continue;
+
+ fprintf(stdout, "%s ", adapter_str);
+
+ switch(i) {
+ case PIM_SCANHILO:
+ str = "bus scans from high ID to low ID";
+ break;
+ case PIM_NOREMOVE:
+ str = "removable devices not included in scan";
+ break;
+ case PIM_NOINITIATOR:
+ str = "initiator role not supported";
+ break;
+ case PIM_NOBUSRESET:
+ str = "user has disabled initial BUS RESET or"
+ " controller is in target/mixed mode";
+ break;
+ case PIM_NO_6_BYTE:
+ str = "do not send 6-byte commands";
+ break;
+ case PIM_SEQSCAN:
+ str = "scan bus sequentially";
+ break;
+ default:
+ str = "unknown PIM bit set";
+ break;
+ }
+ fprintf(stdout, "%s\n", str);
+ }
+
+ for (i = 1; i < 0xff; i = i << 1) {
+ const char *str;
+
+ if ((i & cpi->target_sprt) == 0)
+ continue;
+
+ fprintf(stdout, "%s supports ", adapter_str);
+ switch(i) {
+ case PIT_PROCESSOR:
+ str = "target mode processor mode";
+ break;
+ case PIT_PHASE:
+ str = "target mode phase cog. mode";
+ break;
+ case PIT_DISCONNECT:
+ str = "disconnects in target mode";
+ break;
+ case PIT_TERM_IO:
+ str = "terminate I/O message in target mode";
+ break;
+ case PIT_GRP_6:
+ str = "group 6 commands in target mode";
+ break;
+ case PIT_GRP_7:
+ str = "group 7 commands in target mode";
+ break;
+ default:
+ str = "unknown PIT bit set";
+ break;
+ }
+
+ fprintf(stdout, "%s\n", str);
+ }
+ fprintf(stdout, "%s HBA engine count: %d\n", adapter_str,
+ cpi->hba_eng_cnt);
+ fprintf(stdout, "%s maximum target: %d\n", adapter_str,
+ cpi->max_target);
+ fprintf(stdout, "%s maximum LUN: %d\n", adapter_str,
+ cpi->max_lun);
+ fprintf(stdout, "%s highest path ID in subsystem: %d\n",
+ adapter_str, cpi->hpath_id);
+ fprintf(stdout, "%s initiator ID: %d\n", adapter_str,
+ cpi->initiator_id);
+ fprintf(stdout, "%s SIM vendor: %s\n", adapter_str, cpi->sim_vid);
+ fprintf(stdout, "%s HBA vendor: %s\n", adapter_str, cpi->hba_vid);
+ fprintf(stdout, "%s HBA vendor ID: 0x%04x\n",
+ adapter_str, cpi->hba_vendor);
+ fprintf(stdout, "%s HBA device ID: 0x%04x\n",
+ adapter_str, cpi->hba_device);
+ fprintf(stdout, "%s HBA subvendor ID: 0x%04x\n",
+ adapter_str, cpi->hba_subvendor);
+ fprintf(stdout, "%s HBA subdevice ID: 0x%04x\n",
+ adapter_str, cpi->hba_subdevice);
+ fprintf(stdout, "%s bus ID: %d\n", adapter_str, cpi->bus_id);
+ fprintf(stdout, "%s base transfer speed: ", adapter_str);
+ if (cpi->base_transfer_speed > 1000)
+ fprintf(stdout, "%d.%03dMB/sec\n",
+ cpi->base_transfer_speed / 1000,
+ cpi->base_transfer_speed % 1000);
+ else
+ fprintf(stdout, "%dKB/sec\n",
+ (cpi->base_transfer_speed % 1000) * 1000);
+ fprintf(stdout, "%s maximum transfer size: %u bytes\n",
+ adapter_str, cpi->maxio);
+}
+
+static int
+get_print_cts(struct cam_device *device, int user_settings, int quiet,
+ struct ccb_trans_settings *cts)
+{
+ int retval;
+ union ccb *ccb;
+
+ retval = 0;
+ ccb = cam_getccb(device);
+
+ if (ccb == NULL) {
+ warnx("get_print_cts: error allocating ccb");
+ return(1);
+ }
+
+ bzero(&(&ccb->ccb_h)[1],
+ sizeof(struct ccb_trans_settings) - sizeof(struct ccb_hdr));
+
+ ccb->ccb_h.func_code = XPT_GET_TRAN_SETTINGS;
+
+ if (user_settings == 0)
+ ccb->cts.type = CTS_TYPE_CURRENT_SETTINGS;
+ else
+ ccb->cts.type = CTS_TYPE_USER_SETTINGS;
+
+ if (cam_send_ccb(device, ccb) < 0) {
+ perror("error sending XPT_GET_TRAN_SETTINGS CCB");
+ if (arglist & CAM_ARG_VERBOSE)
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ retval = 1;
+ goto get_print_cts_bailout;
+ }
+
+ if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
+ warnx("XPT_GET_TRANS_SETTINGS CCB failed");
+ if (arglist & CAM_ARG_VERBOSE)
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ retval = 1;
+ goto get_print_cts_bailout;
+ }
+
+ if (quiet == 0)
+ cts_print(device, &ccb->cts);
+
+ if (cts != NULL)
+ bcopy(&ccb->cts, cts, sizeof(struct ccb_trans_settings));
+
+get_print_cts_bailout:
+
+ cam_freeccb(ccb);
+
+ return(retval);
+}
+
+static int
+ratecontrol(struct cam_device *device, int retry_count, int timeout,
+ int argc, char **argv, char *combinedopt)
+{
+ int c;
+ union ccb *ccb;
+ int user_settings = 0;
+ int retval = 0;
+ int disc_enable = -1, tag_enable = -1;
+ int mode = -1;
+ int offset = -1;
+ double syncrate = -1;
+ int bus_width = -1;
+ int quiet = 0;
+ int change_settings = 0, send_tur = 0;
+ struct ccb_pathinq cpi;
+
+ ccb = cam_getccb(device);
+ if (ccb == NULL) {
+ warnx("ratecontrol: error allocating ccb");
+ return(1);
+ }
+ while ((c = getopt(argc, argv, combinedopt)) != -1) {
+ switch(c){
+ case 'a':
+ send_tur = 1;
+ break;
+ case 'c':
+ user_settings = 0;
+ break;
+ case 'D':
+ if (strncasecmp(optarg, "enable", 6) == 0)
+ disc_enable = 1;
+ else if (strncasecmp(optarg, "disable", 7) == 0)
+ disc_enable = 0;
+ else {
+ warnx("-D argument \"%s\" is unknown", optarg);
+ retval = 1;
+ goto ratecontrol_bailout;
+ }
+ change_settings = 1;
+ break;
+ case 'M':
+ mode = ata_string2mode(optarg);
+ if (mode < 0) {
+ warnx("unknown mode '%s'", optarg);
+ retval = 1;
+ goto ratecontrol_bailout;
+ }
+ change_settings = 1;
+ break;
+ case 'O':
+ offset = strtol(optarg, NULL, 0);
+ if (offset < 0) {
+ warnx("offset value %d is < 0", offset);
+ retval = 1;
+ goto ratecontrol_bailout;
+ }
+ change_settings = 1;
+ break;
+ case 'q':
+ quiet++;
+ break;
+ case 'R':
+ syncrate = atof(optarg);
+ if (syncrate < 0) {
+ warnx("sync rate %f is < 0", syncrate);
+ retval = 1;
+ goto ratecontrol_bailout;
+ }
+ change_settings = 1;
+ break;
+ case 'T':
+ if (strncasecmp(optarg, "enable", 6) == 0)
+ tag_enable = 1;
+ else if (strncasecmp(optarg, "disable", 7) == 0)
+ tag_enable = 0;
+ else {
+ warnx("-T argument \"%s\" is unknown", optarg);
+ retval = 1;
+ goto ratecontrol_bailout;
+ }
+ change_settings = 1;
+ break;
+ case 'U':
+ user_settings = 1;
+ break;
+ case 'W':
+ bus_width = strtol(optarg, NULL, 0);
+ if (bus_width < 0) {
+ warnx("bus width %d is < 0", bus_width);
+ retval = 1;
+ goto ratecontrol_bailout;
+ }
+ change_settings = 1;
+ break;
+ default:
+ break;
+ }
+ }
+ bzero(&(&ccb->ccb_h)[1],
+ sizeof(struct ccb_pathinq) - sizeof(struct ccb_hdr));
+ /*
+ * Grab path inquiry information, so we can determine whether
+ * or not the initiator is capable of the things that the user
+ * requests.
+ */
+ ccb->ccb_h.func_code = XPT_PATH_INQ;
+ if (cam_send_ccb(device, ccb) < 0) {
+ perror("error sending XPT_PATH_INQ CCB");
+ if (arglist & CAM_ARG_VERBOSE) {
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ }
+ retval = 1;
+ goto ratecontrol_bailout;
+ }
+ if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
+ warnx("XPT_PATH_INQ CCB failed");
+ if (arglist & CAM_ARG_VERBOSE) {
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ }
+ retval = 1;
+ goto ratecontrol_bailout;
+ }
+ bcopy(&ccb->cpi, &cpi, sizeof(struct ccb_pathinq));
+ bzero(&(&ccb->ccb_h)[1],
+ sizeof(struct ccb_trans_settings) - sizeof(struct ccb_hdr));
+ if (quiet == 0) {
+ fprintf(stdout, "%s parameters:\n",
+ user_settings ? "User" : "Current");
+ }
+ retval = get_print_cts(device, user_settings, quiet, &ccb->cts);
+ if (retval != 0)
+ goto ratecontrol_bailout;
+
+ if (arglist & CAM_ARG_VERBOSE)
+ cpi_print(&cpi);
+
+ if (change_settings) {
+ int didsettings = 0;
+ struct ccb_trans_settings_spi *spi = NULL;
+ struct ccb_trans_settings_pata *pata = NULL;
+ struct ccb_trans_settings_sata *sata = NULL;
+ struct ccb_trans_settings_ata *ata = NULL;
+ struct ccb_trans_settings_scsi *scsi = NULL;
+
+ if (ccb->cts.transport == XPORT_SPI)
+ spi = &ccb->cts.xport_specific.spi;
+ if (ccb->cts.transport == XPORT_ATA)
+ pata = &ccb->cts.xport_specific.ata;
+ if (ccb->cts.transport == XPORT_SATA)
+ sata = &ccb->cts.xport_specific.sata;
+ if (ccb->cts.protocol == PROTO_ATA)
+ ata = &ccb->cts.proto_specific.ata;
+ if (ccb->cts.protocol == PROTO_SCSI)
+ scsi = &ccb->cts.proto_specific.scsi;
+ ccb->cts.xport_specific.valid = 0;
+ ccb->cts.proto_specific.valid = 0;
+ if (spi && disc_enable != -1) {
+ spi->valid |= CTS_SPI_VALID_DISC;
+ if (disc_enable == 0)
+ spi->flags &= ~CTS_SPI_FLAGS_DISC_ENB;
+ else
+ spi->flags |= CTS_SPI_FLAGS_DISC_ENB;
+ didsettings++;
+ }
+ if (tag_enable != -1) {
+ if ((cpi.hba_inquiry & PI_TAG_ABLE) == 0) {
+ warnx("HBA does not support tagged queueing, "
+ "so you cannot modify tag settings");
+ retval = 1;
+ goto ratecontrol_bailout;
+ }
+ if (ata) {
+ ata->valid |= CTS_SCSI_VALID_TQ;
+ if (tag_enable == 0)
+ ata->flags &= ~CTS_ATA_FLAGS_TAG_ENB;
+ else
+ ata->flags |= CTS_ATA_FLAGS_TAG_ENB;
+ didsettings++;
+ } else if (scsi) {
+ scsi->valid |= CTS_SCSI_VALID_TQ;
+ if (tag_enable == 0)
+ scsi->flags &= ~CTS_SCSI_FLAGS_TAG_ENB;
+ else
+ scsi->flags |= CTS_SCSI_FLAGS_TAG_ENB;
+ didsettings++;
+ }
+ }
+ if (spi && offset != -1) {
+ if ((cpi.hba_inquiry & PI_SDTR_ABLE) == 0) {
+ warnx("HBA is not capable of changing offset");
+ retval = 1;
+ goto ratecontrol_bailout;
+ }
+ spi->valid |= CTS_SPI_VALID_SYNC_OFFSET;
+ spi->sync_offset = offset;
+ didsettings++;
+ }
+ if (spi && syncrate != -1) {
+ int prelim_sync_period;
+
+ if ((cpi.hba_inquiry & PI_SDTR_ABLE) == 0) {
+ warnx("HBA is not capable of changing "
+ "transfer rates");
+ retval = 1;
+ goto ratecontrol_bailout;
+ }
+ spi->valid |= CTS_SPI_VALID_SYNC_RATE;
+ /*
+ * The sync rate the user gives us is in MHz.
+ * We need to translate it into KHz for this
+ * calculation.
+ */
+ syncrate *= 1000;
+ /*
+ * Next, we calculate a "preliminary" sync period
+ * in tenths of a nanosecond.
+ */
+ if (syncrate == 0)
+ prelim_sync_period = 0;
+ else
+ prelim_sync_period = 10000000 / syncrate;
+ spi->sync_period =
+ scsi_calc_syncparam(prelim_sync_period);
+ didsettings++;
+ }
+ if (sata && syncrate != -1) {
+ if ((cpi.hba_inquiry & PI_SDTR_ABLE) == 0) {
+ warnx("HBA is not capable of changing "
+ "transfer rates");
+ retval = 1;
+ goto ratecontrol_bailout;
+ }
+ if (!user_settings) {
+ warnx("You can modify only user rate "
+ "settings for SATA");
+ retval = 1;
+ goto ratecontrol_bailout;
+ }
+ sata->revision = ata_speed2revision(syncrate * 100);
+ if (sata->revision < 0) {
+ warnx("Invalid rate %f", syncrate);
+ retval = 1;
+ goto ratecontrol_bailout;
+ }
+ sata->valid |= CTS_SATA_VALID_REVISION;
+ didsettings++;
+ }
+ if ((pata || sata) && mode != -1) {
+ if ((cpi.hba_inquiry & PI_SDTR_ABLE) == 0) {
+ warnx("HBA is not capable of changing "
+ "transfer rates");
+ retval = 1;
+ goto ratecontrol_bailout;
+ }
+ if (!user_settings) {
+ warnx("You can modify only user mode "
+ "settings for ATA/SATA");
+ retval = 1;
+ goto ratecontrol_bailout;
+ }
+ if (pata) {
+ pata->mode = mode;
+ pata->valid |= CTS_ATA_VALID_MODE;
+ } else {
+ sata->mode = mode;
+ sata->valid |= CTS_SATA_VALID_MODE;
+ }
+ didsettings++;
+ }
+ /*
+ * The bus_width argument goes like this:
+ * 0 == 8 bit
+ * 1 == 16 bit
+ * 2 == 32 bit
+ * Therefore, if you shift the number of bits given on the
+ * command line right by 4, you should get the correct
+ * number.
+ */
+ if (spi && bus_width != -1) {
+ /*
+ * We might as well validate things here with a
+ * decipherable error message, rather than what
+ * will probably be an indecipherable error message
+ * by the time it gets back to us.
+ */
+ if ((bus_width == 16)
+ && ((cpi.hba_inquiry & PI_WIDE_16) == 0)) {
+ warnx("HBA does not support 16 bit bus width");
+ retval = 1;
+ goto ratecontrol_bailout;
+ } else if ((bus_width == 32)
+ && ((cpi.hba_inquiry & PI_WIDE_32) == 0)) {
+ warnx("HBA does not support 32 bit bus width");
+ retval = 1;
+ goto ratecontrol_bailout;
+ } else if ((bus_width != 8)
+ && (bus_width != 16)
+ && (bus_width != 32)) {
+ warnx("Invalid bus width %d", bus_width);
+ retval = 1;
+ goto ratecontrol_bailout;
+ }
+ spi->valid |= CTS_SPI_VALID_BUS_WIDTH;
+ spi->bus_width = bus_width >> 4;
+ didsettings++;
+ }
+ if (didsettings == 0) {
+ goto ratecontrol_bailout;
+ }
+ ccb->ccb_h.func_code = XPT_SET_TRAN_SETTINGS;
+ if (cam_send_ccb(device, ccb) < 0) {
+ perror("error sending XPT_SET_TRAN_SETTINGS CCB");
+ if (arglist & CAM_ARG_VERBOSE) {
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ }
+ retval = 1;
+ goto ratecontrol_bailout;
+ }
+ if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
+ warnx("XPT_SET_TRANS_SETTINGS CCB failed");
+ if (arglist & CAM_ARG_VERBOSE) {
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ }
+ retval = 1;
+ goto ratecontrol_bailout;
+ }
+ }
+ if (send_tur) {
+ retval = testunitready(device, retry_count, timeout,
+ (arglist & CAM_ARG_VERBOSE) ? 0 : 1);
+ /*
+ * If the TUR didn't succeed, just bail.
+ */
+ if (retval != 0) {
+ if (quiet == 0)
+ fprintf(stderr, "Test Unit Ready failed\n");
+ goto ratecontrol_bailout;
+ }
+ }
+ if ((change_settings || send_tur) && !quiet &&
+ (ccb->cts.transport == XPORT_ATA ||
+ ccb->cts.transport == XPORT_SATA || send_tur)) {
+ fprintf(stdout, "New parameters:\n");
+ retval = get_print_cts(device, user_settings, 0, NULL);
+ }
+
+ratecontrol_bailout:
+ cam_freeccb(ccb);
+ return(retval);
+}
+
+static int
+scsiformat(struct cam_device *device, int argc, char **argv,
+ char *combinedopt, int retry_count, int timeout)
+{
+ union ccb *ccb;
+ int c;
+ int ycount = 0, quiet = 0;
+ int error = 0, retval = 0;
+ int use_timeout = 10800 * 1000;
+ int immediate = 1;
+ struct format_defect_list_header fh;
+ u_int8_t *data_ptr = NULL;
+ u_int32_t dxfer_len = 0;
+ u_int8_t byte2 = 0;
+ int num_warnings = 0;
+ int reportonly = 0;
+
+ ccb = cam_getccb(device);
+
+ if (ccb == NULL) {
+ warnx("scsiformat: error allocating ccb");
+ return(1);
+ }
+
+ bzero(&(&ccb->ccb_h)[1],
+ sizeof(struct ccb_scsiio) - sizeof(struct ccb_hdr));
+
+ while ((c = getopt(argc, argv, combinedopt)) != -1) {
+ switch(c) {
+ case 'q':
+ quiet++;
+ break;
+ case 'r':
+ reportonly = 1;
+ break;
+ case 'w':
+ immediate = 0;
+ break;
+ case 'y':
+ ycount++;
+ break;
+ }
+ }
+
+ if (reportonly)
+ goto doreport;
+
+ if (quiet == 0) {
+ fprintf(stdout, "You are about to REMOVE ALL DATA from the "
+ "following device:\n");
+
+ error = scsidoinquiry(device, argc, argv, combinedopt,
+ retry_count, timeout);
+
+ if (error != 0) {
+ warnx("scsiformat: error sending inquiry");
+ goto scsiformat_bailout;
+ }
+ }
+
+ if (ycount == 0) {
+ if (!get_confirmation()) {
+ error = 1;
+ goto scsiformat_bailout;
+ }
+ }
+
+ if (timeout != 0)
+ use_timeout = timeout;
+
+ if (quiet == 0) {
+ fprintf(stdout, "Current format timeout is %d seconds\n",
+ use_timeout / 1000);
+ }
+
+ /*
+ * If the user hasn't disabled questions and didn't specify a
+ * timeout on the command line, ask them if they want the current
+ * timeout.
+ */
+ if ((ycount == 0)
+ && (timeout == 0)) {
+ char str[1024];
+ int new_timeout = 0;
+
+ fprintf(stdout, "Enter new timeout in seconds or press\n"
+ "return to keep the current timeout [%d] ",
+ use_timeout / 1000);
+
+ if (fgets(str, sizeof(str), stdin) != NULL) {
+ if (str[0] != '\0')
+ new_timeout = atoi(str);
+ }
+
+ if (new_timeout != 0) {
+ use_timeout = new_timeout * 1000;
+ fprintf(stdout, "Using new timeout value %d\n",
+ use_timeout / 1000);
+ }
+ }
+
+ /*
+ * Keep this outside the if block below to silence any unused
+ * variable warnings.
+ */
+ bzero(&fh, sizeof(fh));
+
+ /*
+ * If we're in immediate mode, we've got to include the format
+ * header
+ */
+ if (immediate != 0) {
+ fh.byte2 = FU_DLH_IMMED;
+ data_ptr = (u_int8_t *)&fh;
+ dxfer_len = sizeof(fh);
+ byte2 = FU_FMT_DATA;
+ } else if (quiet == 0) {
+ fprintf(stdout, "Formatting...");
+ fflush(stdout);
+ }
+
+ scsi_format_unit(&ccb->csio,
+ /* retries */ retry_count,
+ /* cbfcnp */ NULL,
+ /* tag_action */ MSG_SIMPLE_Q_TAG,
+ /* byte2 */ byte2,
+ /* ileave */ 0,
+ /* data_ptr */ data_ptr,
+ /* dxfer_len */ dxfer_len,
+ /* sense_len */ SSD_FULL_SIZE,
+ /* timeout */ use_timeout);
+
+ /* Disable freezing the device queue */
+ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
+
+ if (arglist & CAM_ARG_ERR_RECOVER)
+ ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER;
+
+ if (((retval = cam_send_ccb(device, ccb)) < 0)
+ || ((immediate == 0)
+ && ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP))) {
+ const char errstr[] = "error sending format command";
+
+ if (retval < 0)
+ warn(errstr);
+ else
+ warnx(errstr);
+
+ if (arglist & CAM_ARG_VERBOSE) {
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ }
+ error = 1;
+ goto scsiformat_bailout;
+ }
+
+ /*
+ * If we ran in non-immediate mode, we already checked for errors
+ * above and printed out any necessary information. If we're in
+ * immediate mode, we need to loop through and get status
+ * information periodically.
+ */
+ if (immediate == 0) {
+ if (quiet == 0) {
+ fprintf(stdout, "Format Complete\n");
+ }
+ goto scsiformat_bailout;
+ }
+
+doreport:
+ do {
+ cam_status status;
+
+ bzero(&(&ccb->ccb_h)[1],
+ sizeof(struct ccb_scsiio) - sizeof(struct ccb_hdr));
+
+ /*
+ * There's really no need to do error recovery or
+ * retries here, since we're just going to sit in a
+ * loop and wait for the device to finish formatting.
+ */
+ scsi_test_unit_ready(&ccb->csio,
+ /* retries */ 0,
+ /* cbfcnp */ NULL,
+ /* tag_action */ MSG_SIMPLE_Q_TAG,
+ /* sense_len */ SSD_FULL_SIZE,
+ /* timeout */ 5000);
+
+ /* Disable freezing the device queue */
+ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
+
+ retval = cam_send_ccb(device, ccb);
+
+ /*
+ * If we get an error from the ioctl, bail out. SCSI
+ * errors are expected.
+ */
+ if (retval < 0) {
+ warn("error sending CAMIOCOMMAND ioctl");
+ if (arglist & CAM_ARG_VERBOSE) {
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ }
+ error = 1;
+ goto scsiformat_bailout;
+ }
+
+ status = ccb->ccb_h.status & CAM_STATUS_MASK;
+
+ if ((status != CAM_REQ_CMP)
+ && (status == CAM_SCSI_STATUS_ERROR)
+ && ((ccb->ccb_h.status & CAM_AUTOSNS_VALID) != 0)) {
+ struct scsi_sense_data *sense;
+ int error_code, sense_key, asc, ascq;
+
+ sense = &ccb->csio.sense_data;
+ scsi_extract_sense_len(sense, ccb->csio.sense_len -
+ ccb->csio.sense_resid, &error_code, &sense_key,
+ &asc, &ascq, /*show_errors*/ 1);
+
+ /*
+ * According to the SCSI-2 and SCSI-3 specs, a
+ * drive that is in the middle of a format should
+ * return NOT READY with an ASC of "logical unit
+ * not ready, format in progress". The sense key
+ * specific bytes will then be a progress indicator.
+ */
+ if ((sense_key == SSD_KEY_NOT_READY)
+ && (asc == 0x04) && (ascq == 0x04)) {
+ uint8_t sks[3];
+
+ if ((scsi_get_sks(sense, ccb->csio.sense_len -
+ ccb->csio.sense_resid, sks) == 0)
+ && (quiet == 0)) {
+ int val;
+ u_int64_t percentage;
+
+ val = scsi_2btoul(&sks[1]);
+ percentage = 10000 * val;
+
+ fprintf(stdout,
+ "\rFormatting: %ju.%02u %% "
+ "(%d/%d) done",
+ (uintmax_t)(percentage /
+ (0x10000 * 100)),
+ (unsigned)((percentage /
+ 0x10000) % 100),
+ val, 0x10000);
+ fflush(stdout);
+ } else if ((quiet == 0)
+ && (++num_warnings <= 1)) {
+ warnx("Unexpected SCSI Sense Key "
+ "Specific value returned "
+ "during format:");
+ scsi_sense_print(device, &ccb->csio,
+ stderr);
+ warnx("Unable to print status "
+ "information, but format will "
+ "proceed.");
+ warnx("will exit when format is "
+ "complete");
+ }
+ sleep(1);
+ } else {
+ warnx("Unexpected SCSI error during format");
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ error = 1;
+ goto scsiformat_bailout;
+ }
+
+ } else if (status != CAM_REQ_CMP) {
+ warnx("Unexpected CAM status %#x", status);
+ if (arglist & CAM_ARG_VERBOSE)
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ error = 1;
+ goto scsiformat_bailout;
+ }
+
+ } while((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP);
+
+ if (quiet == 0)
+ fprintf(stdout, "\nFormat Complete\n");
+
+scsiformat_bailout:
+
+ cam_freeccb(ccb);
+
+ return(error);
+}
+
+static int
+scsisanitize(struct cam_device *device, int argc, char **argv,
+ char *combinedopt, int retry_count, int timeout)
+{
+ union ccb *ccb;
+ u_int8_t action = 0;
+ int c;
+ int ycount = 0, quiet = 0;
+ int error = 0, retval = 0;
+ int use_timeout = 10800 * 1000;
+ int immediate = 1;
+ int invert = 0;
+ int passes = 0;
+ int ause = 0;
+ int fd = -1;
+ const char *pattern = NULL;
+ u_int8_t *data_ptr = NULL;
+ u_int32_t dxfer_len = 0;
+ u_int8_t byte2 = 0;
+ int num_warnings = 0;
+ int reportonly = 0;
+
+ ccb = cam_getccb(device);
+
+ if (ccb == NULL) {
+ warnx("scsisanitize: error allocating ccb");
+ return(1);
+ }
+
+ bzero(&(&ccb->ccb_h)[1],
+ sizeof(struct ccb_scsiio) - sizeof(struct ccb_hdr));
+
+ while ((c = getopt(argc, argv, combinedopt)) != -1) {
+ switch(c) {
+ case 'a':
+ if (strcasecmp(optarg, "overwrite") == 0)
+ action = SSZ_SERVICE_ACTION_OVERWRITE;
+ else if (strcasecmp(optarg, "block") == 0)
+ action = SSZ_SERVICE_ACTION_BLOCK_ERASE;
+ else if (strcasecmp(optarg, "crypto") == 0)
+ action = SSZ_SERVICE_ACTION_CRYPTO_ERASE;
+ else if (strcasecmp(optarg, "exitfailure") == 0)
+ action = SSZ_SERVICE_ACTION_EXIT_MODE_FAILURE;
+ else {
+ warnx("invalid service operation \"%s\"",
+ optarg);
+ error = 1;
+ goto scsisanitize_bailout;
+ }
+ break;
+ case 'c':
+ passes = strtol(optarg, NULL, 0);
+ if (passes < 1 || passes > 31) {
+ warnx("invalid passes value %d", passes);
+ error = 1;
+ goto scsisanitize_bailout;
+ }
+ break;
+ case 'I':
+ invert = 1;
+ break;
+ case 'P':
+ pattern = optarg;
+ break;
+ case 'q':
+ quiet++;
+ break;
+ case 'U':
+ ause = 1;
+ break;
+ case 'r':
+ reportonly = 1;
+ break;
+ case 'w':
+ immediate = 0;
+ break;
+ case 'y':
+ ycount++;
+ break;
+ }
+ }
+
+ if (reportonly)
+ goto doreport;
+
+ if (action == 0) {
+ warnx("an action is required");
+ error = 1;
+ goto scsisanitize_bailout;
+ } else if (action == SSZ_SERVICE_ACTION_OVERWRITE) {
+ struct scsi_sanitize_parameter_list *pl;
+ struct stat sb;
+ ssize_t sz, amt;
+
+ if (pattern == NULL) {
+ warnx("overwrite action requires -P argument");
+ error = 1;
+ goto scsisanitize_bailout;
+ }
+ fd = open(pattern, O_RDONLY);
+ if (fd < 0) {
+ warn("cannot open pattern file %s", pattern);
+ error = 1;
+ goto scsisanitize_bailout;
+ }
+ if (fstat(fd, &sb) < 0) {
+ warn("cannot stat pattern file %s", pattern);
+ error = 1;
+ goto scsisanitize_bailout;
+ }
+ sz = sb.st_size;
+ if (sz > SSZPL_MAX_PATTERN_LENGTH) {
+ warnx("pattern file size exceeds maximum value %d",
+ SSZPL_MAX_PATTERN_LENGTH);
+ error = 1;
+ goto scsisanitize_bailout;
+ }
+ dxfer_len = sizeof(*pl) + sz;
+ data_ptr = calloc(1, dxfer_len);
+ if (data_ptr == NULL) {
+ warnx("cannot allocate parameter list buffer");
+ error = 1;
+ goto scsisanitize_bailout;
+ }
+
+ amt = read(fd, data_ptr + sizeof(*pl), sz);
+ if (amt < 0) {
+ warn("cannot read pattern file");
+ error = 1;
+ goto scsisanitize_bailout;
+ } else if (amt != sz) {
+ warnx("short pattern file read");
+ error = 1;
+ goto scsisanitize_bailout;
+ }
+
+ pl = (struct scsi_sanitize_parameter_list *)data_ptr;
+ if (passes == 0)
+ pl->byte1 = 1;
+ else
+ pl->byte1 = passes;
+ if (invert != 0)
+ pl->byte1 |= SSZPL_INVERT;
+ scsi_ulto2b(sz, pl->length);
+ } else {
+ const char *arg;
+
+ if (passes != 0)
+ arg = "-c";
+ else if (invert != 0)
+ arg = "-I";
+ else if (pattern != NULL)
+ arg = "-P";
+ else
+ arg = NULL;
+ if (arg != NULL) {
+ warnx("%s argument only valid with overwrite "
+ "operation", arg);
+ error = 1;
+ goto scsisanitize_bailout;
+ }
+ }
+
+ if (quiet == 0) {
+ fprintf(stdout, "You are about to REMOVE ALL DATA from the "
+ "following device:\n");
+
+ error = scsidoinquiry(device, argc, argv, combinedopt,
+ retry_count, timeout);
+
+ if (error != 0) {
+ warnx("scsisanitize: error sending inquiry");
+ goto scsisanitize_bailout;
+ }
+ }
+
+ if (ycount == 0) {
+ if (!get_confirmation()) {
+ error = 1;
+ goto scsisanitize_bailout;
+ }
+ }
+
+ if (timeout != 0)
+ use_timeout = timeout;
+
+ if (quiet == 0) {
+ fprintf(stdout, "Current sanitize timeout is %d seconds\n",
+ use_timeout / 1000);
+ }
+
+ /*
+ * If the user hasn't disabled questions and didn't specify a
+ * timeout on the command line, ask them if they want the current
+ * timeout.
+ */
+ if ((ycount == 0)
+ && (timeout == 0)) {
+ char str[1024];
+ int new_timeout = 0;
+
+ fprintf(stdout, "Enter new timeout in seconds or press\n"
+ "return to keep the current timeout [%d] ",
+ use_timeout / 1000);
+
+ if (fgets(str, sizeof(str), stdin) != NULL) {
+ if (str[0] != '\0')
+ new_timeout = atoi(str);
+ }
+
+ if (new_timeout != 0) {
+ use_timeout = new_timeout * 1000;
+ fprintf(stdout, "Using new timeout value %d\n",
+ use_timeout / 1000);
+ }
+ }
+
+ byte2 = action;
+ if (ause != 0)
+ byte2 |= SSZ_UNRESTRICTED_EXIT;
+ if (immediate != 0)
+ byte2 |= SSZ_IMMED;
+
+ scsi_sanitize(&ccb->csio,
+ /* retries */ retry_count,
+ /* cbfcnp */ NULL,
+ /* tag_action */ MSG_SIMPLE_Q_TAG,
+ /* byte2 */ byte2,
+ /* control */ 0,
+ /* data_ptr */ data_ptr,
+ /* dxfer_len */ dxfer_len,
+ /* sense_len */ SSD_FULL_SIZE,
+ /* timeout */ use_timeout);
+
+ /* Disable freezing the device queue */
+ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
+
+ if (arglist & CAM_ARG_ERR_RECOVER)
+ ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER;
+
+ if (cam_send_ccb(device, ccb) < 0) {
+ warn("error sending sanitize command");
+ error = 1;
+ goto scsisanitize_bailout;
+ }
+
+ if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
+ struct scsi_sense_data *sense;
+ int error_code, sense_key, asc, ascq;
+
+ if ((ccb->ccb_h.status & CAM_STATUS_MASK) ==
+ CAM_SCSI_STATUS_ERROR) {
+ sense = &ccb->csio.sense_data;
+ scsi_extract_sense_len(sense, ccb->csio.sense_len -
+ ccb->csio.sense_resid, &error_code, &sense_key,
+ &asc, &ascq, /*show_errors*/ 1);
+
+ if (sense_key == SSD_KEY_ILLEGAL_REQUEST &&
+ asc == 0x20 && ascq == 0x00)
+ warnx("sanitize is not supported by "
+ "this device");
+ else
+ warnx("error sanitizing this device");
+ } else
+ warnx("error sanitizing this device");
+
+ if (arglist & CAM_ARG_VERBOSE) {
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ }
+ error = 1;
+ goto scsisanitize_bailout;
+ }
+
+ /*
+ * If we ran in non-immediate mode, we already checked for errors
+ * above and printed out any necessary information. If we're in
+ * immediate mode, we need to loop through and get status
+ * information periodically.
+ */
+ if (immediate == 0) {
+ if (quiet == 0) {
+ fprintf(stdout, "Sanitize Complete\n");
+ }
+ goto scsisanitize_bailout;
+ }
+
+doreport:
+ do {
+ cam_status status;
+
+ bzero(&(&ccb->ccb_h)[1],
+ sizeof(struct ccb_scsiio) - sizeof(struct ccb_hdr));
+
+ /*
+ * There's really no need to do error recovery or
+ * retries here, since we're just going to sit in a
+ * loop and wait for the device to finish sanitizing.
+ */
+ scsi_test_unit_ready(&ccb->csio,
+ /* retries */ 0,
+ /* cbfcnp */ NULL,
+ /* tag_action */ MSG_SIMPLE_Q_TAG,
+ /* sense_len */ SSD_FULL_SIZE,
+ /* timeout */ 5000);
+
+ /* Disable freezing the device queue */
+ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
+
+ retval = cam_send_ccb(device, ccb);
+
+ /*
+ * If we get an error from the ioctl, bail out. SCSI
+ * errors are expected.
+ */
+ if (retval < 0) {
+ warn("error sending CAMIOCOMMAND ioctl");
+ if (arglist & CAM_ARG_VERBOSE) {
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ }
+ error = 1;
+ goto scsisanitize_bailout;
+ }
+
+ status = ccb->ccb_h.status & CAM_STATUS_MASK;
+
+ if ((status != CAM_REQ_CMP)
+ && (status == CAM_SCSI_STATUS_ERROR)
+ && ((ccb->ccb_h.status & CAM_AUTOSNS_VALID) != 0)) {
+ struct scsi_sense_data *sense;
+ int error_code, sense_key, asc, ascq;
+
+ sense = &ccb->csio.sense_data;
+ scsi_extract_sense_len(sense, ccb->csio.sense_len -
+ ccb->csio.sense_resid, &error_code, &sense_key,
+ &asc, &ascq, /*show_errors*/ 1);
+
+ /*
+ * According to the SCSI-3 spec, a drive that is in the
+ * middle of a sanitize should return NOT READY with an
+ * ASC of "logical unit not ready, sanitize in
+ * progress". The sense key specific bytes will then
+ * be a progress indicator.
+ */
+ if ((sense_key == SSD_KEY_NOT_READY)
+ && (asc == 0x04) && (ascq == 0x1b)) {
+ uint8_t sks[3];
+
+ if ((scsi_get_sks(sense, ccb->csio.sense_len -
+ ccb->csio.sense_resid, sks) == 0)
+ && (quiet == 0)) {
+ int val;
+ u_int64_t percentage;
+
+ val = scsi_2btoul(&sks[1]);
+ percentage = 10000 * val;
+
+ fprintf(stdout,
+ "\rSanitizing: %ju.%02u %% "
+ "(%d/%d) done",
+ (uintmax_t)(percentage /
+ (0x10000 * 100)),
+ (unsigned)((percentage /
+ 0x10000) % 100),
+ val, 0x10000);
+ fflush(stdout);
+ } else if ((quiet == 0)
+ && (++num_warnings <= 1)) {
+ warnx("Unexpected SCSI Sense Key "
+ "Specific value returned "
+ "during sanitize:");
+ scsi_sense_print(device, &ccb->csio,
+ stderr);
+ warnx("Unable to print status "
+ "information, but sanitze will "
+ "proceed.");
+ warnx("will exit when sanitize is "
+ "complete");
+ }
+ sleep(1);
+ } else {
+ warnx("Unexpected SCSI error during sanitize");
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ error = 1;
+ goto scsisanitize_bailout;
+ }
+
+ } else if (status != CAM_REQ_CMP) {
+ warnx("Unexpected CAM status %#x", status);
+ if (arglist & CAM_ARG_VERBOSE)
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ error = 1;
+ goto scsisanitize_bailout;
+ }
+ } while((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP);
+
+ if (quiet == 0)
+ fprintf(stdout, "\nSanitize Complete\n");
+
+scsisanitize_bailout:
+ if (fd >= 0)
+ close(fd);
+ if (data_ptr != NULL)
+ free(data_ptr);
+ cam_freeccb(ccb);
+
+ return(error);
+}
+
+static int
+scsireportluns(struct cam_device *device, int argc, char **argv,
+ char *combinedopt, int retry_count, int timeout)
+{
+ union ccb *ccb;
+ int c, countonly, lunsonly;
+ struct scsi_report_luns_data *lundata;
+ int alloc_len;
+ uint8_t report_type;
+ uint32_t list_len, i, j;
+ int retval;
+
+ retval = 0;
+ lundata = NULL;
+ report_type = RPL_REPORT_DEFAULT;
+ ccb = cam_getccb(device);
+
+ if (ccb == NULL) {
+ warnx("%s: error allocating ccb", __func__);
+ return (1);
+ }
+
+ bzero(&(&ccb->ccb_h)[1],
+ sizeof(struct ccb_scsiio) - sizeof(struct ccb_hdr));
+
+ countonly = 0;
+ lunsonly = 0;
+
+ while ((c = getopt(argc, argv, combinedopt)) != -1) {
+ switch (c) {
+ case 'c':
+ countonly++;
+ break;
+ case 'l':
+ lunsonly++;
+ break;
+ case 'r':
+ if (strcasecmp(optarg, "default") == 0)
+ report_type = RPL_REPORT_DEFAULT;
+ else if (strcasecmp(optarg, "wellknown") == 0)
+ report_type = RPL_REPORT_WELLKNOWN;
+ else if (strcasecmp(optarg, "all") == 0)
+ report_type = RPL_REPORT_ALL;
+ else {
+ warnx("%s: invalid report type \"%s\"",
+ __func__, optarg);
+ retval = 1;
+ goto bailout;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ if ((countonly != 0)
+ && (lunsonly != 0)) {
+ warnx("%s: you can only specify one of -c or -l", __func__);
+ retval = 1;
+ goto bailout;
+ }
+ /*
+ * According to SPC-4, the allocation length must be at least 16
+ * bytes -- enough for the header and one LUN.
+ */
+ alloc_len = sizeof(*lundata) + 8;
+
+retry:
+
+ lundata = malloc(alloc_len);
+
+ if (lundata == NULL) {
+ warn("%s: error mallocing %d bytes", __func__, alloc_len);
+ retval = 1;
+ goto bailout;
+ }
+
+ scsi_report_luns(&ccb->csio,
+ /*retries*/ retry_count,
+ /*cbfcnp*/ NULL,
+ /*tag_action*/ MSG_SIMPLE_Q_TAG,
+ /*select_report*/ report_type,
+ /*rpl_buf*/ lundata,
+ /*alloc_len*/ alloc_len,
+ /*sense_len*/ SSD_FULL_SIZE,
+ /*timeout*/ timeout ? timeout : 5000);
+
+ /* Disable freezing the device queue */
+ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
+
+ if (arglist & CAM_ARG_ERR_RECOVER)
+ ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER;
+
+ if (cam_send_ccb(device, ccb) < 0) {
+ warn("error sending REPORT LUNS command");
+
+ if (arglist & CAM_ARG_VERBOSE)
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+
+ retval = 1;
+ goto bailout;
+ }
+
+ if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
+ cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr);
+ retval = 1;
+ goto bailout;
+ }
+
+
+ list_len = scsi_4btoul(lundata->length);
+
+ /*
+ * If we need to list the LUNs, and our allocation
+ * length was too short, reallocate and retry.
+ */
+ if ((countonly == 0)
+ && (list_len > (alloc_len - sizeof(*lundata)))) {
+ alloc_len = list_len + sizeof(*lundata);
+ free(lundata);
+ goto retry;
+ }
+
+ if (lunsonly == 0)
+ fprintf(stdout, "%u LUN%s found\n", list_len / 8,
+ ((list_len / 8) > 1) ? "s" : "");
+
+ if (countonly != 0)
+ goto bailout;
+
+ for (i = 0; i < (list_len / 8); i++) {
+ int no_more;
+
+ no_more = 0;
+ for (j = 0; j < sizeof(lundata->luns[i].lundata); j += 2) {
+ if (j != 0)
+ fprintf(stdout, ",");
+ switch (lundata->luns[i].lundata[j] &
+ RPL_LUNDATA_ATYP_MASK) {
+ case RPL_LUNDATA_ATYP_PERIPH:
+ if ((lundata->luns[i].lundata[j] &
+ RPL_LUNDATA_PERIPH_BUS_MASK) != 0)
+ fprintf(stdout, "%d:",
+ lundata->luns[i].lundata[j] &
+ RPL_LUNDATA_PERIPH_BUS_MASK);
+ else if ((j == 0)
+ && ((lundata->luns[i].lundata[j+2] &
+ RPL_LUNDATA_PERIPH_BUS_MASK) == 0))
+ no_more = 1;
+
+ fprintf(stdout, "%d",
+ lundata->luns[i].lundata[j+1]);
+ break;
+ case RPL_LUNDATA_ATYP_FLAT: {
+ uint8_t tmplun[2];
+ tmplun[0] = lundata->luns[i].lundata[j] &
+ RPL_LUNDATA_FLAT_LUN_MASK;
+ tmplun[1] = lundata->luns[i].lundata[j+1];
+
+ fprintf(stdout, "%d", scsi_2btoul(tmplun));
+ no_more = 1;
+ break;
+ }
+ case RPL_LUNDATA_ATYP_LUN:
+ fprintf(stdout, "%d:%d:%d",
+ (lundata->luns[i].lundata[j+1] &
+ RPL_LUNDATA_LUN_BUS_MASK) >> 5,
+ lundata->luns[i].lundata[j] &
+ RPL_LUNDATA_LUN_TARG_MASK,
+ lundata->luns[i].lundata[j+1] &
+ RPL_LUNDATA_LUN_LUN_MASK);
+ break;
+ case RPL_LUNDATA_ATYP_EXTLUN: {
+ int field_len_code, eam_code;
+
+ eam_code = lundata->luns[i].lundata[j] &
+ RPL_LUNDATA_EXT_EAM_MASK;
+ field_len_code = (lundata->luns[i].lundata[j] &
+ RPL_LUNDATA_EXT_LEN_MASK) >> 4;
+
+ if ((eam_code == RPL_LUNDATA_EXT_EAM_WK)
+ && (field_len_code == 0x00)) {
+ fprintf(stdout, "%d",
+ lundata->luns[i].lundata[j+1]);
+ } else if ((eam_code ==
+ RPL_LUNDATA_EXT_EAM_NOT_SPEC)
+ && (field_len_code == 0x03)) {
+ uint8_t tmp_lun[8];
+
+ /*
+ * This format takes up all 8 bytes.
+ * If we aren't starting at offset 0,
+ * that's a bug.
+ */
+ if (j != 0) {
+ fprintf(stdout, "Invalid "
+ "offset %d for "
+ "Extended LUN not "
+ "specified format", j);
+ no_more = 1;
+ break;
+ }
+ bzero(tmp_lun, sizeof(tmp_lun));
+ bcopy(&lundata->luns[i].lundata[j+1],
+ &tmp_lun[1], sizeof(tmp_lun) - 1);
+ fprintf(stdout, "%#jx",
+ (intmax_t)scsi_8btou64(tmp_lun));
+ no_more = 1;
+ } else {
+ fprintf(stderr, "Unknown Extended LUN"
+ "Address method %#x, length "
+ "code %#x", eam_code,
+ field_len_code);
+ no_more = 1;
+ }
+ break;
+ }
+ default:
+ fprintf(stderr, "Unknown LUN address method "
+ "%#x\n", lundata->luns[i].lundata[0] &
+ RPL_LUNDATA_ATYP_MASK);
+ break;
+ }
+ /*
+ * For the flat addressing method, there are no
+ * other levels after it.
+ */
+ if (no_more != 0)
+ break;
+ }
+ fprintf(stdout, "\n");
+ }
+
+bailout:
+
+ cam_freeccb(ccb);
+
+ free(lundata);
+
+ return (retval);
+}
+
+static int
+scsireadcapacity(struct cam_device *device, int argc, char **argv,
+ char *combinedopt, int retry_count, int timeout)
+{
+ union ccb *ccb;
+ int blocksizeonly, humanize, numblocks, quiet, sizeonly, baseten;
+ struct scsi_read_capacity_data rcap;
+ struct scsi_read_capacity_data_long rcaplong;
+ uint64_t maxsector;
+ uint32_t block_len;
+ int retval;
+ int c;
+
+ blocksizeonly = 0;
+ humanize = 0;
+ numblocks = 0;
+ quiet = 0;
+ sizeonly = 0;
+ baseten = 0;
+ retval = 0;
+
+ ccb = cam_getccb(device);
+
+ if (ccb == NULL) {
+ warnx("%s: error allocating ccb", __func__);
+ return (1);
+ }
+
+ bzero(&(&ccb->ccb_h)[1],
+ sizeof(struct ccb_scsiio) - sizeof(struct ccb_hdr));
+
+ while ((c = getopt(argc, argv, combinedopt)) != -1) {
+ switch (c) {
+ case 'b':
+ blocksizeonly++;
+ break;
+ case 'h':
+ humanize++;
+ baseten = 0;
+ break;
+ case 'H':
+ humanize++;
+ baseten++;
+ break;
+ case 'N':
+ numblocks++;
+ break;
+ case 'q':
+ quiet++;
+ break;
+ case 's':
+ sizeonly++;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if ((blocksizeonly != 0)
+ && (numblocks != 0)) {
+ warnx("%s: you can only specify one of -b or -N", __func__);
+ retval = 1;
+ goto bailout;
+ }
+
+ if ((blocksizeonly != 0)
+ && (sizeonly != 0)) {
+ warnx("%s: you can only specify one of -b or -s", __func__);
+ retval = 1;
+ goto bailout;
+ }
+
+ if ((humanize != 0)
+ && (quiet != 0)) {
+ warnx("%s: you can only specify one of -h/-H or -q", __func__);
+ retval = 1;
+ goto bailout;
+ }
+
+ if ((humanize != 0)
+ && (blocksizeonly != 0)) {
+ warnx("%s: you can only specify one of -h/-H or -b", __func__);
+ retval = 1;
+ goto bailout;
+ }
+
+ scsi_read_capacity(&ccb->csio,
+ /*retries*/ retry_count,
+ /*cbfcnp*/ NULL,
+ /*tag_action*/ MSG_SIMPLE_Q_TAG,
+ &rcap,
+ SSD_FULL_SIZE,
+ /*timeout*/ timeout ? timeout : 5000);
+
+ /* Disable freezing the device queue */
+ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
+
+ if (arglist & CAM_ARG_ERR_RECOVER)
+ ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER;
+
+ if (cam_send_ccb(device, ccb) < 0) {
+ warn("error sending READ CAPACITY command");
+
+ if (arglist & CAM_ARG_VERBOSE)
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+
+ retval = 1;
+ goto bailout;
+ }
+
+ if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
+ cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr);
+ retval = 1;
+ goto bailout;
+ }
+
+ maxsector = scsi_4btoul(rcap.addr);
+ block_len = scsi_4btoul(rcap.length);
+
+ /*
+ * A last block of 2^32-1 means that the true capacity is over 2TB,
+ * and we need to issue the long READ CAPACITY to get the real
+ * capacity. Otherwise, we're all set.
+ */
+ if (maxsector != 0xffffffff)
+ goto do_print;
+
+ scsi_read_capacity_16(&ccb->csio,
+ /*retries*/ retry_count,
+ /*cbfcnp*/ NULL,
+ /*tag_action*/ MSG_SIMPLE_Q_TAG,
+ /*lba*/ 0,
+ /*reladdr*/ 0,
+ /*pmi*/ 0,
+ /*rcap_buf*/ (uint8_t *)&rcaplong,
+ /*rcap_buf_len*/ sizeof(rcaplong),
+ /*sense_len*/ SSD_FULL_SIZE,
+ /*timeout*/ timeout ? timeout : 5000);
+
+ /* Disable freezing the device queue */
+ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
+
+ if (arglist & CAM_ARG_ERR_RECOVER)
+ ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER;
+
+ if (cam_send_ccb(device, ccb) < 0) {
+ warn("error sending READ CAPACITY (16) command");
+
+ if (arglist & CAM_ARG_VERBOSE)
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+
+ retval = 1;
+ goto bailout;
+ }
+
+ if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
+ cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr);
+ retval = 1;
+ goto bailout;
+ }
+
+ maxsector = scsi_8btou64(rcaplong.addr);
+ block_len = scsi_4btoul(rcaplong.length);
+
+do_print:
+ if (blocksizeonly == 0) {
+ /*
+ * Humanize implies !quiet, and also implies numblocks.
+ */
+ if (humanize != 0) {
+ char tmpstr[6];
+ int64_t tmpbytes;
+ int ret;
+
+ tmpbytes = (maxsector + 1) * block_len;
+ ret = humanize_number(tmpstr, sizeof(tmpstr),
+ tmpbytes, "", HN_AUTOSCALE,
+ HN_B | HN_DECIMAL |
+ ((baseten != 0) ?
+ HN_DIVISOR_1000 : 0));
+ if (ret == -1) {
+ warnx("%s: humanize_number failed!", __func__);
+ retval = 1;
+ goto bailout;
+ }
+ fprintf(stdout, "Device Size: %s%s", tmpstr,
+ (sizeonly == 0) ? ", " : "\n");
+ } else if (numblocks != 0) {
+ fprintf(stdout, "%s%ju%s", (quiet == 0) ?
+ "Blocks: " : "", (uintmax_t)maxsector + 1,
+ (sizeonly == 0) ? ", " : "\n");
+ } else {
+ fprintf(stdout, "%s%ju%s", (quiet == 0) ?
+ "Last Block: " : "", (uintmax_t)maxsector,
+ (sizeonly == 0) ? ", " : "\n");
+ }
+ }
+ if (sizeonly == 0)
+ fprintf(stdout, "%s%u%s\n", (quiet == 0) ?
+ "Block Length: " : "", block_len, (quiet == 0) ?
+ " bytes" : "");
+bailout:
+ cam_freeccb(ccb);
+
+ return (retval);
+}
+
+static int
+smpcmd(struct cam_device *device, int argc, char **argv, char *combinedopt,
+ int retry_count, int timeout)
+{
+ int c, error = 0;
+ union ccb *ccb;
+ uint8_t *smp_request = NULL, *smp_response = NULL;
+ int request_size = 0, response_size = 0;
+ int fd_request = 0, fd_response = 0;
+ char *datastr = NULL;
+ struct get_hook hook;
+ int retval;
+ int flags = 0;
+
+ /*
+ * Note that at the moment we don't support sending SMP CCBs to
+ * devices that aren't probed by CAM.
+ */
+ ccb = cam_getccb(device);
+ if (ccb == NULL) {
+ warnx("%s: error allocating CCB", __func__);
+ return (1);
+ }
+
+ bzero(&(&ccb->ccb_h)[1],
+ sizeof(union ccb) - sizeof(struct ccb_hdr));
+
+ while ((c = getopt(argc, argv, combinedopt)) != -1) {
+ switch (c) {
+ case 'R':
+ arglist |= CAM_ARG_CMD_IN;
+ response_size = strtol(optarg, NULL, 0);
+ if (response_size <= 0) {
+ warnx("invalid number of response bytes %d",
+ response_size);
+ error = 1;
+ goto smpcmd_bailout;
+ }
+ hook.argc = argc - optind;
+ hook.argv = argv + optind;
+ hook.got = 0;
+ optind++;
+ datastr = cget(&hook, NULL);
+ /*
+ * If the user supplied "-" instead of a format, he
+ * wants the data to be written to stdout.
+ */
+ if ((datastr != NULL)
+ && (datastr[0] == '-'))
+ fd_response = 1;
+
+ smp_response = (u_int8_t *)malloc(response_size);
+ if (smp_response == NULL) {
+ warn("can't malloc memory for SMP response");
+ error = 1;
+ goto smpcmd_bailout;
+ }
+ break;
+ case 'r':
+ arglist |= CAM_ARG_CMD_OUT;
+ request_size = strtol(optarg, NULL, 0);
+ if (request_size <= 0) {
+ warnx("invalid number of request bytes %d",
+ request_size);
+ error = 1;
+ goto smpcmd_bailout;
+ }
+ hook.argc = argc - optind;
+ hook.argv = argv + optind;
+ hook.got = 0;
+ datastr = cget(&hook, NULL);
+ smp_request = (u_int8_t *)malloc(request_size);
+ if (smp_request == NULL) {
+ warn("can't malloc memory for SMP request");
+ error = 1;
+ goto smpcmd_bailout;
+ }
+ bzero(smp_request, request_size);
+ /*
+ * If the user supplied "-" instead of a format, he
+ * wants the data to be read from stdin.
+ */
+ if ((datastr != NULL)
+ && (datastr[0] == '-'))
+ fd_request = 1;
+ else
+ buff_encode_visit(smp_request, request_size,
+ datastr,
+ iget, &hook);
+ optind += hook.got;
+ break;
+ default:
+ break;
+ }
+ }
+
+ /*
+ * If fd_data is set, and we're writing to the device, we need to
+ * read the data the user wants written from stdin.
+ */
+ if ((fd_request == 1) && (arglist & CAM_ARG_CMD_OUT)) {
+ ssize_t amt_read;
+ int amt_to_read = request_size;
+ u_int8_t *buf_ptr = smp_request;
+
+ for (amt_read = 0; amt_to_read > 0;
+ amt_read = read(STDIN_FILENO, buf_ptr, amt_to_read)) {
+ if (amt_read == -1) {
+ warn("error reading data from stdin");
+ error = 1;
+ goto smpcmd_bailout;
+ }
+ amt_to_read -= amt_read;
+ buf_ptr += amt_read;
+ }
+ }
+
+ if (((arglist & CAM_ARG_CMD_IN) == 0)
+ || ((arglist & CAM_ARG_CMD_OUT) == 0)) {
+ warnx("%s: need both the request (-r) and response (-R) "
+ "arguments", __func__);
+ error = 1;
+ goto smpcmd_bailout;
+ }
+
+ flags |= CAM_DEV_QFRZDIS;
+
+ cam_fill_smpio(&ccb->smpio,
+ /*retries*/ retry_count,
+ /*cbfcnp*/ NULL,
+ /*flags*/ flags,
+ /*smp_request*/ smp_request,
+ /*smp_request_len*/ request_size,
+ /*smp_response*/ smp_response,
+ /*smp_response_len*/ response_size,
+ /*timeout*/ timeout ? timeout : 5000);
+
+ ccb->smpio.flags = SMP_FLAG_NONE;
+
+ if (((retval = cam_send_ccb(device, ccb)) < 0)
+ || ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP)) {
+ const char warnstr[] = "error sending command";
+
+ if (retval < 0)
+ warn(warnstr);
+ else
+ warnx(warnstr);
+
+ if (arglist & CAM_ARG_VERBOSE) {
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ }
+ }
+
+ if (((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP)
+ && (response_size > 0)) {
+ if (fd_response == 0) {
+ buff_decode_visit(smp_response, response_size,
+ datastr, arg_put, NULL);
+ fprintf(stdout, "\n");
+ } else {
+ ssize_t amt_written;
+ int amt_to_write = response_size;
+ u_int8_t *buf_ptr = smp_response;
+
+ for (amt_written = 0; (amt_to_write > 0) &&
+ (amt_written = write(STDOUT_FILENO, buf_ptr,
+ amt_to_write)) > 0;){
+ amt_to_write -= amt_written;
+ buf_ptr += amt_written;
+ }
+ if (amt_written == -1) {
+ warn("error writing data to stdout");
+ error = 1;
+ goto smpcmd_bailout;
+ } else if ((amt_written == 0)
+ && (amt_to_write > 0)) {
+ warnx("only wrote %u bytes out of %u",
+ response_size - amt_to_write,
+ response_size);
+ }
+ }
+ }
+smpcmd_bailout:
+ if (ccb != NULL)
+ cam_freeccb(ccb);
+
+ if (smp_request != NULL)
+ free(smp_request);
+
+ if (smp_response != NULL)
+ free(smp_response);
+
+ return (error);
+}
+
+static int
+smpreportgeneral(struct cam_device *device, int argc, char **argv,
+ char *combinedopt, int retry_count, int timeout)
+{
+ union ccb *ccb;
+ struct smp_report_general_request *request = NULL;
+ struct smp_report_general_response *response = NULL;
+ struct sbuf *sb = NULL;
+ int error = 0;
+ int c, long_response = 0;
+ int retval;
+
+ /*
+ * Note that at the moment we don't support sending SMP CCBs to
+ * devices that aren't probed by CAM.
+ */
+ ccb = cam_getccb(device);
+ if (ccb == NULL) {
+ warnx("%s: error allocating CCB", __func__);
+ return (1);
+ }
+
+ bzero(&(&ccb->ccb_h)[1],
+ sizeof(union ccb) - sizeof(struct ccb_hdr));
+
+ while ((c = getopt(argc, argv, combinedopt)) != -1) {
+ switch (c) {
+ case 'l':
+ long_response = 1;
+ break;
+ default:
+ break;
+ }
+ }
+ request = malloc(sizeof(*request));
+ if (request == NULL) {
+ warn("%s: unable to allocate %zd bytes", __func__,
+ sizeof(*request));
+ error = 1;
+ goto bailout;
+ }
+
+ response = malloc(sizeof(*response));
+ if (response == NULL) {
+ warn("%s: unable to allocate %zd bytes", __func__,
+ sizeof(*response));
+ error = 1;
+ goto bailout;
+ }
+
+try_long:
+ smp_report_general(&ccb->smpio,
+ retry_count,
+ /*cbfcnp*/ NULL,
+ request,
+ /*request_len*/ sizeof(*request),
+ (uint8_t *)response,
+ /*response_len*/ sizeof(*response),
+ /*long_response*/ long_response,
+ timeout);
+
+ if (((retval = cam_send_ccb(device, ccb)) < 0)
+ || ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP)) {
+ const char warnstr[] = "error sending command";
+
+ if (retval < 0)
+ warn(warnstr);
+ else
+ warnx(warnstr);
+
+ if (arglist & CAM_ARG_VERBOSE) {
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ }
+ error = 1;
+ goto bailout;
+ }
+
+ /*
+ * If the device supports the long response bit, try again and see
+ * if we can get all of the data.
+ */
+ if ((response->long_response & SMP_RG_LONG_RESPONSE)
+ && (long_response == 0)) {
+ ccb->ccb_h.status = CAM_REQ_INPROG;
+ bzero(&(&ccb->ccb_h)[1],
+ sizeof(union ccb) - sizeof(struct ccb_hdr));
+ long_response = 1;
+ goto try_long;
+ }
+
+ /*
+ * XXX KDM detect and decode SMP errors here.
+ */
+ sb = sbuf_new_auto();
+ if (sb == NULL) {
+ warnx("%s: error allocating sbuf", __func__);
+ goto bailout;
+ }
+
+ smp_report_general_sbuf(response, sizeof(*response), sb);
+
+ if (sbuf_finish(sb) != 0) {
+ warnx("%s: sbuf_finish", __func__);
+ goto bailout;
+ }
+
+ printf("%s", sbuf_data(sb));
+
+bailout:
+ if (ccb != NULL)
+ cam_freeccb(ccb);
+
+ if (request != NULL)
+ free(request);
+
+ if (response != NULL)
+ free(response);
+
+ if (sb != NULL)
+ sbuf_delete(sb);
+
+ return (error);
+}
+
+static struct camcontrol_opts phy_ops[] = {
+ {"nop", SMP_PC_PHY_OP_NOP, CAM_ARG_NONE, NULL},
+ {"linkreset", SMP_PC_PHY_OP_LINK_RESET, CAM_ARG_NONE, NULL},
+ {"hardreset", SMP_PC_PHY_OP_HARD_RESET, CAM_ARG_NONE, NULL},
+ {"disable", SMP_PC_PHY_OP_DISABLE, CAM_ARG_NONE, NULL},
+ {"clearerrlog", SMP_PC_PHY_OP_CLEAR_ERR_LOG, CAM_ARG_NONE, NULL},
+ {"clearaffiliation", SMP_PC_PHY_OP_CLEAR_AFFILIATON, CAM_ARG_NONE,NULL},
+ {"sataportsel", SMP_PC_PHY_OP_TRANS_SATA_PSS, CAM_ARG_NONE, NULL},
+ {"clearitnl", SMP_PC_PHY_OP_CLEAR_STP_ITN_LS, CAM_ARG_NONE, NULL},
+ {"setdevname", SMP_PC_PHY_OP_SET_ATT_DEV_NAME, CAM_ARG_NONE, NULL},
+ {NULL, 0, 0, NULL}
+};
+
+static int
+smpphycontrol(struct cam_device *device, int argc, char **argv,
+ char *combinedopt, int retry_count, int timeout)
+{
+ union ccb *ccb;
+ struct smp_phy_control_request *request = NULL;
+ struct smp_phy_control_response *response = NULL;
+ int long_response = 0;
+ int retval = 0;
+ int phy = -1;
+ uint32_t phy_operation = SMP_PC_PHY_OP_NOP;
+ int phy_op_set = 0;
+ uint64_t attached_dev_name = 0;
+ int dev_name_set = 0;
+ uint32_t min_plr = 0, max_plr = 0;
+ uint32_t pp_timeout_val = 0;
+ int slumber_partial = 0;
+ int set_pp_timeout_val = 0;
+ int c;
+
+ /*
+ * Note that at the moment we don't support sending SMP CCBs to
+ * devices that aren't probed by CAM.
+ */
+ ccb = cam_getccb(device);
+ if (ccb == NULL) {
+ warnx("%s: error allocating CCB", __func__);
+ return (1);
+ }
+
+ bzero(&(&ccb->ccb_h)[1],
+ sizeof(union ccb) - sizeof(struct ccb_hdr));
+
+ while ((c = getopt(argc, argv, combinedopt)) != -1) {
+ switch (c) {
+ case 'a':
+ case 'A':
+ case 's':
+ case 'S': {
+ int enable = -1;
+
+ if (strcasecmp(optarg, "enable") == 0)
+ enable = 1;
+ else if (strcasecmp(optarg, "disable") == 0)
+ enable = 2;
+ else {
+ warnx("%s: Invalid argument %s", __func__,
+ optarg);
+ retval = 1;
+ goto bailout;
+ }
+ switch (c) {
+ case 's':
+ slumber_partial |= enable <<
+ SMP_PC_SAS_SLUMBER_SHIFT;
+ break;
+ case 'S':
+ slumber_partial |= enable <<
+ SMP_PC_SAS_PARTIAL_SHIFT;
+ break;
+ case 'a':
+ slumber_partial |= enable <<
+ SMP_PC_SATA_SLUMBER_SHIFT;
+ break;
+ case 'A':
+ slumber_partial |= enable <<
+ SMP_PC_SATA_PARTIAL_SHIFT;
+ break;
+ default:
+ warnx("%s: programmer error", __func__);
+ retval = 1;
+ goto bailout;
+ break; /*NOTREACHED*/
+ }
+ break;
+ }
+ case 'd':
+ attached_dev_name = (uintmax_t)strtoumax(optarg,
+ NULL,0);
+ dev_name_set = 1;
+ break;
+ case 'l':
+ long_response = 1;
+ break;
+ case 'm':
+ /*
+ * We don't do extensive checking here, so this
+ * will continue to work when new speeds come out.
+ */
+ min_plr = strtoul(optarg, NULL, 0);
+ if ((min_plr == 0)
+ || (min_plr > 0xf)) {
+ warnx("%s: invalid link rate %x",
+ __func__, min_plr);
+ retval = 1;
+ goto bailout;
+ }
+ break;
+ case 'M':
+ /*
+ * We don't do extensive checking here, so this
+ * will continue to work when new speeds come out.
+ */
+ max_plr = strtoul(optarg, NULL, 0);
+ if ((max_plr == 0)
+ || (max_plr > 0xf)) {
+ warnx("%s: invalid link rate %x",
+ __func__, max_plr);
+ retval = 1;
+ goto bailout;
+ }
+ break;
+ case 'o': {
+ camcontrol_optret optreturn;
+ cam_argmask argnums;
+ const char *subopt;
+
+ if (phy_op_set != 0) {
+ warnx("%s: only one phy operation argument "
+ "(-o) allowed", __func__);
+ retval = 1;
+ goto bailout;
+ }
+
+ phy_op_set = 1;
+
+ /*
+ * Allow the user to specify the phy operation
+ * numerically, as well as with a name. This will
+ * future-proof it a bit, so options that are added
+ * in future specs can be used.
+ */
+ if (isdigit(optarg[0])) {
+ phy_operation = strtoul(optarg, NULL, 0);
+ if ((phy_operation == 0)
+ || (phy_operation > 0xff)) {
+ warnx("%s: invalid phy operation %#x",
+ __func__, phy_operation);
+ retval = 1;
+ goto bailout;
+ }
+ break;
+ }
+ optreturn = getoption(phy_ops, optarg, &phy_operation,
+ &argnums, &subopt);
+
+ if (optreturn == CC_OR_AMBIGUOUS) {
+ warnx("%s: ambiguous option %s", __func__,
+ optarg);
+ usage(0);
+ retval = 1;
+ goto bailout;
+ } else if (optreturn == CC_OR_NOT_FOUND) {
+ warnx("%s: option %s not found", __func__,
+ optarg);
+ usage(0);
+ retval = 1;
+ goto bailout;
+ }
+ break;
+ }
+ case 'p':
+ phy = atoi(optarg);
+ break;
+ case 'T':
+ pp_timeout_val = strtoul(optarg, NULL, 0);
+ if (pp_timeout_val > 15) {
+ warnx("%s: invalid partial pathway timeout "
+ "value %u, need a value less than 16",
+ __func__, pp_timeout_val);
+ retval = 1;
+ goto bailout;
+ }
+ set_pp_timeout_val = 1;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (phy == -1) {
+ warnx("%s: a PHY (-p phy) argument is required",__func__);
+ retval = 1;
+ goto bailout;
+ }
+
+ if (((dev_name_set != 0)
+ && (phy_operation != SMP_PC_PHY_OP_SET_ATT_DEV_NAME))
+ || ((phy_operation == SMP_PC_PHY_OP_SET_ATT_DEV_NAME)
+ && (dev_name_set == 0))) {
+ warnx("%s: -d name and -o setdevname arguments both "
+ "required to set device name", __func__);
+ retval = 1;
+ goto bailout;
+ }
+
+ request = malloc(sizeof(*request));
+ if (request == NULL) {
+ warn("%s: unable to allocate %zd bytes", __func__,
+ sizeof(*request));
+ retval = 1;
+ goto bailout;
+ }
+
+ response = malloc(sizeof(*response));
+ if (response == NULL) {
+ warn("%s: unable to allocate %zd bytes", __func__,
+ sizeof(*request));
+ retval = 1;
+ goto bailout;
+ }
+
+ smp_phy_control(&ccb->smpio,
+ retry_count,
+ /*cbfcnp*/ NULL,
+ request,
+ sizeof(*request),
+ (uint8_t *)response,
+ sizeof(*response),
+ long_response,
+ /*expected_exp_change_count*/ 0,
+ phy,
+ phy_operation,
+ (set_pp_timeout_val != 0) ? 1 : 0,
+ attached_dev_name,
+ min_plr,
+ max_plr,
+ slumber_partial,
+ pp_timeout_val,
+ timeout);
+
+ if (((retval = cam_send_ccb(device, ccb)) < 0)
+ || ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP)) {
+ const char warnstr[] = "error sending command";
+
+ if (retval < 0)
+ warn(warnstr);
+ else
+ warnx(warnstr);
+
+ if (arglist & CAM_ARG_VERBOSE) {
+ /*
+ * Use CAM_EPF_NORMAL so we only get one line of
+ * SMP command decoding.
+ */
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_NORMAL, stderr);
+ }
+ retval = 1;
+ goto bailout;
+ }
+
+ /* XXX KDM print out something here for success? */
+bailout:
+ if (ccb != NULL)
+ cam_freeccb(ccb);
+
+ if (request != NULL)
+ free(request);
+
+ if (response != NULL)
+ free(response);
+
+ return (retval);
+}
+
+static int
+smpmaninfo(struct cam_device *device, int argc, char **argv,
+ char *combinedopt, int retry_count, int timeout)
+{
+ union ccb *ccb;
+ struct smp_report_manuf_info_request request;
+ struct smp_report_manuf_info_response response;
+ struct sbuf *sb = NULL;
+ int long_response = 0;
+ int retval = 0;
+ int c;
+
+ /*
+ * Note that at the moment we don't support sending SMP CCBs to
+ * devices that aren't probed by CAM.
+ */
+ ccb = cam_getccb(device);
+ if (ccb == NULL) {
+ warnx("%s: error allocating CCB", __func__);
+ return (1);
+ }
+
+ bzero(&(&ccb->ccb_h)[1],
+ sizeof(union ccb) - sizeof(struct ccb_hdr));
+
+ while ((c = getopt(argc, argv, combinedopt)) != -1) {
+ switch (c) {
+ case 'l':
+ long_response = 1;
+ break;
+ default:
+ break;
+ }
+ }
+ bzero(&request, sizeof(request));
+ bzero(&response, sizeof(response));
+
+ smp_report_manuf_info(&ccb->smpio,
+ retry_count,
+ /*cbfcnp*/ NULL,
+ &request,
+ sizeof(request),
+ (uint8_t *)&response,
+ sizeof(response),
+ long_response,
+ timeout);
+
+ if (((retval = cam_send_ccb(device, ccb)) < 0)
+ || ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP)) {
+ const char warnstr[] = "error sending command";
+
+ if (retval < 0)
+ warn(warnstr);
+ else
+ warnx(warnstr);
+
+ if (arglist & CAM_ARG_VERBOSE) {
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ }
+ retval = 1;
+ goto bailout;
+ }
+
+ sb = sbuf_new_auto();
+ if (sb == NULL) {
+ warnx("%s: error allocating sbuf", __func__);
+ goto bailout;
+ }
+
+ smp_report_manuf_info_sbuf(&response, sizeof(response), sb);
+
+ if (sbuf_finish(sb) != 0) {
+ warnx("%s: sbuf_finish", __func__);
+ goto bailout;
+ }
+
+ printf("%s", sbuf_data(sb));
+
+bailout:
+
+ if (ccb != NULL)
+ cam_freeccb(ccb);
+
+ if (sb != NULL)
+ sbuf_delete(sb);
+
+ return (retval);
+}
+
+static int
+getdevid(struct cam_devitem *item)
+{
+ int retval = 0;
+ union ccb *ccb = NULL;
+
+ struct cam_device *dev;
+
+ dev = cam_open_btl(item->dev_match.path_id,
+ item->dev_match.target_id,
+ item->dev_match.target_lun, O_RDWR, NULL);
+
+ if (dev == NULL) {
+ warnx("%s", cam_errbuf);
+ retval = 1;
+ goto bailout;
+ }
+
+ item->device_id_len = 0;
+
+ ccb = cam_getccb(dev);
+ if (ccb == NULL) {
+ warnx("%s: error allocating CCB", __func__);
+ retval = 1;
+ goto bailout;
+ }
+
+ bzero(&(&ccb->ccb_h)[1],
+ sizeof(union ccb) - sizeof(struct ccb_hdr));
+
+ /*
+ * On the first try, we just probe for the size of the data, and
+ * then allocate that much memory and try again.
+ */
+retry:
+ ccb->ccb_h.func_code = XPT_DEV_ADVINFO;
+ ccb->ccb_h.flags = CAM_DIR_IN;
+ ccb->cdai.flags = CDAI_FLAG_NONE;
+ ccb->cdai.buftype = CDAI_TYPE_SCSI_DEVID;
+ ccb->cdai.bufsiz = item->device_id_len;
+ if (item->device_id_len != 0)
+ ccb->cdai.buf = (uint8_t *)item->device_id;
+
+ if (cam_send_ccb(dev, ccb) < 0) {
+ warn("%s: error sending XPT_GDEV_ADVINFO CCB", __func__);
+ retval = 1;
+ goto bailout;
+ }
+
+ if (ccb->ccb_h.status != CAM_REQ_CMP) {
+ warnx("%s: CAM status %#x", __func__, ccb->ccb_h.status);
+ retval = 1;
+ goto bailout;
+ }
+
+ if (item->device_id_len == 0) {
+ /*
+ * This is our first time through. Allocate the buffer,
+ * and then go back to get the data.
+ */
+ if (ccb->cdai.provsiz == 0) {
+ warnx("%s: invalid .provsiz field returned with "
+ "XPT_GDEV_ADVINFO CCB", __func__);
+ retval = 1;
+ goto bailout;
+ }
+ item->device_id_len = ccb->cdai.provsiz;
+ item->device_id = malloc(item->device_id_len);
+ if (item->device_id == NULL) {
+ warn("%s: unable to allocate %d bytes", __func__,
+ item->device_id_len);
+ retval = 1;
+ goto bailout;
+ }
+ ccb->ccb_h.status = CAM_REQ_INPROG;
+ goto retry;
+ }
+
+bailout:
+ if (dev != NULL)
+ cam_close_device(dev);
+
+ if (ccb != NULL)
+ cam_freeccb(ccb);
+
+ return (retval);
+}
+
+/*
+ * XXX KDM merge this code with getdevtree()?
+ */
+static int
+buildbusdevlist(struct cam_devlist *devlist)
+{
+ union ccb ccb;
+ int bufsize, fd = -1;
+ struct dev_match_pattern *patterns;
+ struct cam_devitem *item = NULL;
+ int skip_device = 0;
+ int retval = 0;
+
+ if ((fd = open(XPT_DEVICE, O_RDWR)) == -1) {
+ warn("couldn't open %s", XPT_DEVICE);
+ return(1);
+ }
+
+ bzero(&ccb, sizeof(union ccb));
+
+ ccb.ccb_h.path_id = CAM_XPT_PATH_ID;
+ ccb.ccb_h.target_id = CAM_TARGET_WILDCARD;
+ ccb.ccb_h.target_lun = CAM_LUN_WILDCARD;
+
+ ccb.ccb_h.func_code = XPT_DEV_MATCH;
+ bufsize = sizeof(struct dev_match_result) * 100;
+ ccb.cdm.match_buf_len = bufsize;
+ ccb.cdm.matches = (struct dev_match_result *)malloc(bufsize);
+ if (ccb.cdm.matches == NULL) {
+ warnx("can't malloc memory for matches");
+ close(fd);
+ return(1);
+ }
+ ccb.cdm.num_matches = 0;
+ ccb.cdm.num_patterns = 2;
+ ccb.cdm.pattern_buf_len = sizeof(struct dev_match_pattern) *
+ ccb.cdm.num_patterns;
+
+ patterns = (struct dev_match_pattern *)malloc(ccb.cdm.pattern_buf_len);
+ if (patterns == NULL) {
+ warnx("can't malloc memory for patterns");
+ retval = 1;
+ goto bailout;
+ }
+
+ ccb.cdm.patterns = patterns;
+ bzero(patterns, ccb.cdm.pattern_buf_len);
+
+ patterns[0].type = DEV_MATCH_DEVICE;
+ patterns[0].pattern.device_pattern.flags = DEV_MATCH_PATH;
+ patterns[0].pattern.device_pattern.path_id = devlist->path_id;
+ patterns[1].type = DEV_MATCH_PERIPH;
+ patterns[1].pattern.periph_pattern.flags = PERIPH_MATCH_PATH;
+ patterns[1].pattern.periph_pattern.path_id = devlist->path_id;
+
+ /*
+ * We do the ioctl multiple times if necessary, in case there are
+ * more than 100 nodes in the EDT.
+ */
+ do {
+ unsigned int i;
+
+ if (ioctl(fd, CAMIOCOMMAND, &ccb) == -1) {
+ warn("error sending CAMIOCOMMAND ioctl");
+ retval = 1;
+ goto bailout;
+ }
+
+ if ((ccb.ccb_h.status != CAM_REQ_CMP)
+ || ((ccb.cdm.status != CAM_DEV_MATCH_LAST)
+ && (ccb.cdm.status != CAM_DEV_MATCH_MORE))) {
+ warnx("got CAM error %#x, CDM error %d\n",
+ ccb.ccb_h.status, ccb.cdm.status);
+ retval = 1;
+ goto bailout;
+ }
+
+ for (i = 0; i < ccb.cdm.num_matches; i++) {
+ switch (ccb.cdm.matches[i].type) {
+ case DEV_MATCH_DEVICE: {
+ struct device_match_result *dev_result;
+
+ dev_result =
+ &ccb.cdm.matches[i].result.device_result;
+
+ if (dev_result->flags &
+ DEV_RESULT_UNCONFIGURED) {
+ skip_device = 1;
+ break;
+ } else
+ skip_device = 0;
+
+ item = malloc(sizeof(*item));
+ if (item == NULL) {
+ warn("%s: unable to allocate %zd bytes",
+ __func__, sizeof(*item));
+ retval = 1;
+ goto bailout;
+ }
+ bzero(item, sizeof(*item));
+ bcopy(dev_result, &item->dev_match,
+ sizeof(*dev_result));
+ STAILQ_INSERT_TAIL(&devlist->dev_queue, item,
+ links);
+
+ if (getdevid(item) != 0) {
+ retval = 1;
+ goto bailout;
+ }
+ break;
+ }
+ case DEV_MATCH_PERIPH: {
+ struct periph_match_result *periph_result;
+
+ periph_result =
+ &ccb.cdm.matches[i].result.periph_result;
+
+ if (skip_device != 0)
+ break;
+ item->num_periphs++;
+ item->periph_matches = realloc(
+ item->periph_matches,
+ item->num_periphs *
+ sizeof(struct periph_match_result));
+ if (item->periph_matches == NULL) {
+ warn("%s: error allocating periph "
+ "list", __func__);
+ retval = 1;
+ goto bailout;
+ }
+ bcopy(periph_result, &item->periph_matches[
+ item->num_periphs - 1],
+ sizeof(*periph_result));
+ break;
+ }
+ default:
+ fprintf(stderr, "%s: unexpected match "
+ "type %d\n", __func__,
+ ccb.cdm.matches[i].type);
+ retval = 1;
+ goto bailout;
+ break; /*NOTREACHED*/
+ }
+ }
+ } while ((ccb.ccb_h.status == CAM_REQ_CMP)
+ && (ccb.cdm.status == CAM_DEV_MATCH_MORE));
+bailout:
+
+ if (fd != -1)
+ close(fd);
+
+ free(patterns);
+
+ free(ccb.cdm.matches);
+
+ if (retval != 0)
+ freebusdevlist(devlist);
+
+ return (retval);
+}
+
+static void
+freebusdevlist(struct cam_devlist *devlist)
+{
+ struct cam_devitem *item, *item2;
+
+ STAILQ_FOREACH_SAFE(item, &devlist->dev_queue, links, item2) {
+ STAILQ_REMOVE(&devlist->dev_queue, item, cam_devitem,
+ links);
+ free(item->device_id);
+ free(item->periph_matches);
+ free(item);
+ }
+}
+
+static struct cam_devitem *
+findsasdevice(struct cam_devlist *devlist, uint64_t sasaddr)
+{
+ struct cam_devitem *item;
+
+ STAILQ_FOREACH(item, &devlist->dev_queue, links) {
+ struct scsi_vpd_id_descriptor *idd;
+
+ /*
+ * XXX KDM look for LUN IDs as well?
+ */
+ idd = scsi_get_devid(item->device_id,
+ item->device_id_len,
+ scsi_devid_is_sas_target);
+ if (idd == NULL)
+ continue;
+
+ if (scsi_8btou64(idd->identifier) == sasaddr)
+ return (item);
+ }
+
+ return (NULL);
+}
+
+static int
+smpphylist(struct cam_device *device, int argc, char **argv,
+ char *combinedopt, int retry_count, int timeout)
+{
+ struct smp_report_general_request *rgrequest = NULL;
+ struct smp_report_general_response *rgresponse = NULL;
+ struct smp_discover_request *disrequest = NULL;
+ struct smp_discover_response *disresponse = NULL;
+ struct cam_devlist devlist;
+ union ccb *ccb;
+ int long_response = 0;
+ int num_phys = 0;
+ int quiet = 0;
+ int retval;
+ int i, c;
+
+ /*
+ * Note that at the moment we don't support sending SMP CCBs to
+ * devices that aren't probed by CAM.
+ */
+ ccb = cam_getccb(device);
+ if (ccb == NULL) {
+ warnx("%s: error allocating CCB", __func__);
+ return (1);
+ }
+
+ bzero(&(&ccb->ccb_h)[1],
+ sizeof(union ccb) - sizeof(struct ccb_hdr));
+ STAILQ_INIT(&devlist.dev_queue);
+
+ rgrequest = malloc(sizeof(*rgrequest));
+ if (rgrequest == NULL) {
+ warn("%s: unable to allocate %zd bytes", __func__,
+ sizeof(*rgrequest));
+ retval = 1;
+ goto bailout;
+ }
+
+ rgresponse = malloc(sizeof(*rgresponse));
+ if (rgresponse == NULL) {
+ warn("%s: unable to allocate %zd bytes", __func__,
+ sizeof(*rgresponse));
+ retval = 1;
+ goto bailout;
+ }
+
+ while ((c = getopt(argc, argv, combinedopt)) != -1) {
+ switch (c) {
+ case 'l':
+ long_response = 1;
+ break;
+ case 'q':
+ quiet = 1;
+ break;
+ default:
+ break;
+ }
+ }
+
+ smp_report_general(&ccb->smpio,
+ retry_count,
+ /*cbfcnp*/ NULL,
+ rgrequest,
+ /*request_len*/ sizeof(*rgrequest),
+ (uint8_t *)rgresponse,
+ /*response_len*/ sizeof(*rgresponse),
+ /*long_response*/ long_response,
+ timeout);
+
+ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
+
+ if (((retval = cam_send_ccb(device, ccb)) < 0)
+ || ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP)) {
+ const char warnstr[] = "error sending command";
+
+ if (retval < 0)
+ warn(warnstr);
+ else
+ warnx(warnstr);
+
+ if (arglist & CAM_ARG_VERBOSE) {
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ }
+ retval = 1;
+ goto bailout;
+ }
+
+ num_phys = rgresponse->num_phys;
+
+ if (num_phys == 0) {
+ if (quiet == 0)
+ fprintf(stdout, "%s: No Phys reported\n", __func__);
+ retval = 1;
+ goto bailout;
+ }
+
+ devlist.path_id = device->path_id;
+
+ retval = buildbusdevlist(&devlist);
+ if (retval != 0)
+ goto bailout;
+
+ if (quiet == 0) {
+ fprintf(stdout, "%d PHYs:\n", num_phys);
+ fprintf(stdout, "PHY Attached SAS Address\n");
+ }
+
+ disrequest = malloc(sizeof(*disrequest));
+ if (disrequest == NULL) {
+ warn("%s: unable to allocate %zd bytes", __func__,
+ sizeof(*disrequest));
+ retval = 1;
+ goto bailout;
+ }
+
+ disresponse = malloc(sizeof(*disresponse));
+ if (disresponse == NULL) {
+ warn("%s: unable to allocate %zd bytes", __func__,
+ sizeof(*disresponse));
+ retval = 1;
+ goto bailout;
+ }
+
+ for (i = 0; i < num_phys; i++) {
+ struct cam_devitem *item;
+ struct device_match_result *dev_match;
+ char vendor[16], product[48], revision[16];
+ char tmpstr[256];
+ int j;
+
+ bzero(&(&ccb->ccb_h)[1],
+ sizeof(union ccb) - sizeof(struct ccb_hdr));
+
+ ccb->ccb_h.status = CAM_REQ_INPROG;
+ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
+
+ smp_discover(&ccb->smpio,
+ retry_count,
+ /*cbfcnp*/ NULL,
+ disrequest,
+ sizeof(*disrequest),
+ (uint8_t *)disresponse,
+ sizeof(*disresponse),
+ long_response,
+ /*ignore_zone_group*/ 0,
+ /*phy*/ i,
+ timeout);
+
+ if (((retval = cam_send_ccb(device, ccb)) < 0)
+ || (((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP)
+ && (disresponse->function_result != SMP_FR_PHY_VACANT))) {
+ const char warnstr[] = "error sending command";
+
+ if (retval < 0)
+ warn(warnstr);
+ else
+ warnx(warnstr);
+
+ if (arglist & CAM_ARG_VERBOSE) {
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ }
+ retval = 1;
+ goto bailout;
+ }
+
+ if (disresponse->function_result == SMP_FR_PHY_VACANT) {
+ if (quiet == 0)
+ fprintf(stdout, "%3d <vacant>\n", i);
+ continue;
+ }
+
+ if (disresponse->attached_device == SMP_DIS_AD_TYPE_NONE) {
+ item = NULL;
+ } else {
+ item = findsasdevice(&devlist,
+ scsi_8btou64(disresponse->attached_sas_address));
+ }
+
+ if ((quiet == 0)
+ || (item != NULL)) {
+ fprintf(stdout, "%3d 0x%016jx", i,
+ (uintmax_t)scsi_8btou64(
+ disresponse->attached_sas_address));
+ if (item == NULL) {
+ fprintf(stdout, "\n");
+ continue;
+ }
+ } else if (quiet != 0)
+ continue;
+
+ dev_match = &item->dev_match;
+
+ if (dev_match->protocol == PROTO_SCSI) {
+ cam_strvis(vendor, dev_match->inq_data.vendor,
+ sizeof(dev_match->inq_data.vendor),
+ sizeof(vendor));
+ cam_strvis(product, dev_match->inq_data.product,
+ sizeof(dev_match->inq_data.product),
+ sizeof(product));
+ cam_strvis(revision, dev_match->inq_data.revision,
+ sizeof(dev_match->inq_data.revision),
+ sizeof(revision));
+ sprintf(tmpstr, "<%s %s %s>", vendor, product,
+ revision);
+ } else if ((dev_match->protocol == PROTO_ATA)
+ || (dev_match->protocol == PROTO_SATAPM)) {
+ cam_strvis(product, dev_match->ident_data.model,
+ sizeof(dev_match->ident_data.model),
+ sizeof(product));
+ cam_strvis(revision, dev_match->ident_data.revision,
+ sizeof(dev_match->ident_data.revision),
+ sizeof(revision));
+ sprintf(tmpstr, "<%s %s>", product, revision);
+ } else {
+ sprintf(tmpstr, "<>");
+ }
+ fprintf(stdout, " %-33s ", tmpstr);
+
+ /*
+ * If we have 0 periphs, that's a bug...
+ */
+ if (item->num_periphs == 0) {
+ fprintf(stdout, "\n");
+ continue;
+ }
+
+ fprintf(stdout, "(");
+ for (j = 0; j < item->num_periphs; j++) {
+ if (j > 0)
+ fprintf(stdout, ",");
+
+ fprintf(stdout, "%s%d",
+ item->periph_matches[j].periph_name,
+ item->periph_matches[j].unit_number);
+
+ }
+ fprintf(stdout, ")\n");
+ }
+bailout:
+ if (ccb != NULL)
+ cam_freeccb(ccb);
+
+ free(rgrequest);
+
+ free(rgresponse);
+
+ free(disrequest);
+
+ free(disresponse);
+
+ freebusdevlist(&devlist);
+
+ return (retval);
+}
+
+static int
+atapm(struct cam_device *device, int argc, char **argv,
+ char *combinedopt, int retry_count, int timeout)
+{
+ union ccb *ccb;
+ int retval = 0;
+ int t = -1;
+ int c;
+ u_char cmd, sc;
+
+ ccb = cam_getccb(device);
+
+ if (ccb == NULL) {
+ warnx("%s: error allocating ccb", __func__);
+ return (1);
+ }
+
+ while ((c = getopt(argc, argv, combinedopt)) != -1) {
+ switch (c) {
+ case 't':
+ t = atoi(optarg);
+ break;
+ default:
+ break;
+ }
+ }
+ if (strcmp(argv[1], "idle") == 0) {
+ if (t == -1)
+ cmd = ATA_IDLE_IMMEDIATE;
+ else
+ cmd = ATA_IDLE_CMD;
+ } else if (strcmp(argv[1], "standby") == 0) {
+ if (t == -1)
+ cmd = ATA_STANDBY_IMMEDIATE;
+ else
+ cmd = ATA_STANDBY_CMD;
+ } else {
+ cmd = ATA_SLEEP;
+ t = -1;
+ }
+
+ if (t < 0)
+ sc = 0;
+ else if (t <= (240 * 5))
+ sc = (t + 4) / 5;
+ else if (t <= (252 * 5))
+ /* special encoding for 21 minutes */
+ sc = 252;
+ else if (t <= (11 * 30 * 60))
+ sc = (t - 1) / (30 * 60) + 241;
+ else
+ sc = 253;
+
+ retval = ata_do_28bit_cmd(device,
+ ccb,
+ /*retries*/retry_count,
+ /*flags*/CAM_DIR_NONE,
+ /*protocol*/AP_PROTO_NON_DATA,
+ /*tag_action*/MSG_SIMPLE_Q_TAG,
+ /*command*/cmd,
+ /*features*/0,
+ /*lba*/0,
+ /*sector_count*/sc,
+ /*data_ptr*/NULL,
+ /*dxfer_len*/0,
+ /*timeout*/timeout ? timeout : 30 * 1000,
+ /*quiet*/1);
+
+ cam_freeccb(ccb);
+ return (retval);
+}
+
+static int
+ataaxm(struct cam_device *device, int argc, char **argv,
+ char *combinedopt, int retry_count, int timeout)
+{
+ union ccb *ccb;
+ int retval = 0;
+ int l = -1;
+ int c;
+ u_char cmd, sc;
+
+ ccb = cam_getccb(device);
+
+ if (ccb == NULL) {
+ warnx("%s: error allocating ccb", __func__);
+ return (1);
+ }
+
+ while ((c = getopt(argc, argv, combinedopt)) != -1) {
+ switch (c) {
+ case 'l':
+ l = atoi(optarg);
+ break;
+ default:
+ break;
+ }
+ }
+ sc = 0;
+ if (strcmp(argv[1], "apm") == 0) {
+ if (l == -1)
+ cmd = 0x85;
+ else {
+ cmd = 0x05;
+ sc = l;
+ }
+ } else /* aam */ {
+ if (l == -1)
+ cmd = 0xC2;
+ else {
+ cmd = 0x42;
+ sc = l;
+ }
+ }
+
+ retval = ata_do_28bit_cmd(device,
+ ccb,
+ /*retries*/retry_count,
+ /*flags*/CAM_DIR_NONE,
+ /*protocol*/AP_PROTO_NON_DATA,
+ /*tag_action*/MSG_SIMPLE_Q_TAG,
+ /*command*/ATA_SETFEATURES,
+ /*features*/cmd,
+ /*lba*/0,
+ /*sector_count*/sc,
+ /*data_ptr*/NULL,
+ /*dxfer_len*/0,
+ /*timeout*/timeout ? timeout : 30 * 1000,
+ /*quiet*/1);
+
+ cam_freeccb(ccb);
+ return (retval);
+}
+
+#endif /* MINIMALISTIC */
+
+void
+usage(int printlong)
+{
+
+ fprintf(printlong ? stdout : stderr,
+"usage: camcontrol <command> [device id][generic args][command args]\n"
+" camcontrol devlist [-v]\n"
+#ifndef MINIMALISTIC
+" camcontrol periphlist [dev_id][-n dev_name] [-u unit]\n"
+" camcontrol tur [dev_id][generic args]\n"
+" camcontrol inquiry [dev_id][generic args] [-D] [-S] [-R]\n"
+" camcontrol identify [dev_id][generic args] [-v]\n"
+" camcontrol reportluns [dev_id][generic args] [-c] [-l] [-r report]\n"
+" camcontrol readcap [dev_id][generic args] [-b] [-h] [-H] [-N]\n"
+" [-q] [-s]\n"
+" camcontrol start [dev_id][generic args]\n"
+" camcontrol stop [dev_id][generic args]\n"
+" camcontrol load [dev_id][generic args]\n"
+" camcontrol eject [dev_id][generic args]\n"
+#endif /* MINIMALISTIC */
+" camcontrol rescan <all | bus[:target:lun]>\n"
+" camcontrol reset <all | bus[:target:lun]>\n"
+#ifndef MINIMALISTIC
+" camcontrol defects [dev_id][generic args] <-f format> [-P][-G]\n"
+" [-q][-s][-S offset][-X]\n"
+" camcontrol modepage [dev_id][generic args] <-m page | -l>\n"
+" [-P pagectl][-e | -b][-d]\n"
+" camcontrol cmd [dev_id][generic args]\n"
+" <-a cmd [args] | -c cmd [args]>\n"
+" [-d] [-f] [-i len fmt|-o len fmt [args]] [-r fmt]\n"
+" camcontrol smpcmd [dev_id][generic args]\n"
+" <-r len fmt [args]> <-R len fmt [args]>\n"
+" camcontrol smprg [dev_id][generic args][-l]\n"
+" camcontrol smppc [dev_id][generic args] <-p phy> [-l]\n"
+" [-o operation][-d name][-m rate][-M rate]\n"
+" [-T pp_timeout][-a enable|disable]\n"
+" [-A enable|disable][-s enable|disable]\n"
+" [-S enable|disable]\n"
+" camcontrol smpphylist [dev_id][generic args][-l][-q]\n"
+" camcontrol smpmaninfo [dev_id][generic args][-l]\n"
+" camcontrol debug [-I][-P][-T][-S][-X][-c]\n"
+" <all|bus[:target[:lun]]|off>\n"
+" camcontrol tags [dev_id][generic args] [-N tags] [-q] [-v]\n"
+" camcontrol negotiate [dev_id][generic args] [-a][-c]\n"
+" [-D <enable|disable>][-M mode][-O offset]\n"
+" [-q][-R syncrate][-v][-T <enable|disable>]\n"
+" [-U][-W bus_width]\n"
+" camcontrol format [dev_id][generic args][-q][-r][-w][-y]\n"
+" camcontrol sanitize [dev_id][generic args]\n"
+" [-a overwrite|block|crypto|exitfailure]\n"
+" [-c passes][-I][-P pattern][-q][-U][-r][-w]\n"
+" [-y]\n"
+" camcontrol idle [dev_id][generic args][-t time]\n"
+" camcontrol standby [dev_id][generic args][-t time]\n"
+" camcontrol sleep [dev_id][generic args]\n"
+" camcontrol apm [dev_id][generic args][-l level]\n"
+" camcontrol aam [dev_id][generic args][-l level]\n"
+" camcontrol fwdownload [dev_id][generic args] <-f fw_image> [-y][-s]\n"
+" camcontrol security [dev_id][generic args]\n"
+" <-d pwd | -e pwd | -f | -h pwd | -k pwd>\n"
+" [-l <high|maximum>] [-q] [-s pwd] [-T timeout]\n"
+" [-U <user|master>] [-y]\n"
+" camcontrol hpa [dev_id][generic args] [-f] [-l] [-P] [-p pwd]\n"
+" [-q] [-s max_sectors] [-U pwd] [-y]\n"
+" camcontrol persist [dev_id][generic args] <-i action|-o action>\n"
+" [-a][-I tid][-k key][-K sa_key][-p][-R rtp]\n"
+" [-s scope][-S][-T type][-U]\n"
+#endif /* MINIMALISTIC */
+" camcontrol help\n");
+ if (!printlong)
+ return;
+#ifndef MINIMALISTIC
+ fprintf(stdout,
+"Specify one of the following options:\n"
+"devlist list all CAM devices\n"
+"periphlist list all CAM peripheral drivers attached to a device\n"
+"tur send a test unit ready to the named device\n"
+"inquiry send a SCSI inquiry command to the named device\n"
+"identify send a ATA identify command to the named device\n"
+"reportluns send a SCSI report luns command to the device\n"
+"readcap send a SCSI read capacity command to the device\n"
+"start send a Start Unit command to the device\n"
+"stop send a Stop Unit command to the device\n"
+"load send a Start Unit command to the device with the load bit set\n"
+"eject send a Stop Unit command to the device with the eject bit set\n"
+"rescan rescan all busses, the given bus, or bus:target:lun\n"
+"reset reset all busses, the given bus, or bus:target:lun\n"
+"defects read the defect list of the specified device\n"
+"modepage display or edit (-e) the given mode page\n"
+"cmd send the given SCSI command, may need -i or -o as well\n"
+"smpcmd send the given SMP command, requires -o and -i\n"
+"smprg send the SMP Report General command\n"
+"smppc send the SMP PHY Control command, requires -p\n"
+"smpphylist display phys attached to a SAS expander\n"
+"smpmaninfo send the SMP Report Manufacturer Info command\n"
+"debug turn debugging on/off for a bus, target, or lun, or all devices\n"
+"tags report or set the number of transaction slots for a device\n"
+"negotiate report or set device negotiation parameters\n"
+"format send the SCSI FORMAT UNIT command to the named device\n"
+"sanitize send the SCSI SANITIZE command to the named device\n"
+"idle send the ATA IDLE command to the named device\n"
+"standby send the ATA STANDBY command to the named device\n"
+"sleep send the ATA SLEEP command to the named device\n"
+"fwdownload program firmware of the named device with the given image\n"
+"security report or send ATA security commands to the named device\n"
+"persist send the SCSI PERSISTENT RESERVE IN or OUT commands\n"
+"help this message\n"
+"Device Identifiers:\n"
+"bus:target specify the bus and target, lun defaults to 0\n"
+"bus:target:lun specify the bus, target and lun\n"
+"deviceUNIT specify the device name, like \"da4\" or \"cd2\"\n"
+"Generic arguments:\n"
+"-v be verbose, print out sense information\n"
+"-t timeout command timeout in seconds, overrides default timeout\n"
+"-n dev_name specify device name, e.g. \"da\", \"cd\"\n"
+"-u unit specify unit number, e.g. \"0\", \"5\"\n"
+"-E have the kernel attempt to perform SCSI error recovery\n"
+"-C count specify the SCSI command retry count (needs -E to work)\n"
+"modepage arguments:\n"
+"-l list all available mode pages\n"
+"-m page specify the mode page to view or edit\n"
+"-e edit the specified mode page\n"
+"-b force view to binary mode\n"
+"-d disable block descriptors for mode sense\n"
+"-P pgctl page control field 0-3\n"
+"defects arguments:\n"
+"-f format specify defect list format (block, bfi or phys)\n"
+"-G get the grown defect list\n"
+"-P get the permanent defect list\n"
+"inquiry arguments:\n"
+"-D get the standard inquiry data\n"
+"-S get the serial number\n"
+"-R get the transfer rate, etc.\n"
+"reportluns arguments:\n"
+"-c only report a count of available LUNs\n"
+"-l only print out luns, and not a count\n"
+"-r <reporttype> specify \"default\", \"wellknown\" or \"all\"\n"
+"readcap arguments\n"
+"-b only report the blocksize\n"
+"-h human readable device size, base 2\n"
+"-H human readable device size, base 10\n"
+"-N print the number of blocks instead of last block\n"
+"-q quiet, print numbers only\n"
+"-s only report the last block/device size\n"
+"cmd arguments:\n"
+"-c cdb [args] specify the SCSI CDB\n"
+"-i len fmt specify input data and input data format\n"
+"-o len fmt [args] specify output data and output data fmt\n"
+"smpcmd arguments:\n"
+"-r len fmt [args] specify the SMP command to be sent\n"
+"-R len fmt [args] specify SMP response format\n"
+"smprg arguments:\n"
+"-l specify the long response format\n"
+"smppc arguments:\n"
+"-p phy specify the PHY to operate on\n"
+"-l specify the long request/response format\n"
+"-o operation specify the phy control operation\n"
+"-d name set the attached device name\n"
+"-m rate set the minimum physical link rate\n"
+"-M rate set the maximum physical link rate\n"
+"-T pp_timeout set the partial pathway timeout value\n"
+"-a enable|disable enable or disable SATA slumber\n"
+"-A enable|disable enable or disable SATA partial phy power\n"
+"-s enable|disable enable or disable SAS slumber\n"
+"-S enable|disable enable or disable SAS partial phy power\n"
+"smpphylist arguments:\n"
+"-l specify the long response format\n"
+"-q only print phys with attached devices\n"
+"smpmaninfo arguments:\n"
+"-l specify the long response format\n"
+"debug arguments:\n"
+"-I CAM_DEBUG_INFO -- scsi commands, errors, data\n"
+"-T CAM_DEBUG_TRACE -- routine flow tracking\n"
+"-S CAM_DEBUG_SUBTRACE -- internal routine command flow\n"
+"-c CAM_DEBUG_CDB -- print out SCSI CDBs only\n"
+"tags arguments:\n"
+"-N tags specify the number of tags to use for this device\n"
+"-q be quiet, don't report the number of tags\n"
+"-v report a number of tag-related parameters\n"
+"negotiate arguments:\n"
+"-a send a test unit ready after negotiation\n"
+"-c report/set current negotiation settings\n"
+"-D <arg> \"enable\" or \"disable\" disconnection\n"
+"-M mode set ATA mode\n"
+"-O offset set command delay offset\n"
+"-q be quiet, don't report anything\n"
+"-R syncrate synchronization rate in MHz\n"
+"-T <arg> \"enable\" or \"disable\" tagged queueing\n"
+"-U report/set user negotiation settings\n"
+"-W bus_width set the bus width in bits (8, 16 or 32)\n"
+"-v also print a Path Inquiry CCB for the controller\n"
+"format arguments:\n"
+"-q be quiet, don't print status messages\n"
+"-r run in report only mode\n"
+"-w don't send immediate format command\n"
+"-y don't ask any questions\n"
+"sanitize arguments:\n"
+"-a operation operation mode: overwrite, block, crypto or exitfailure\n"
+"-c passes overwrite passes to perform (1 to 31)\n"
+"-I invert overwrite pattern after each pass\n"
+"-P pattern path to overwrite pattern file\n"
+"-q be quiet, don't print status messages\n"
+"-r run in report only mode\n"
+"-U run operation in unrestricted completion exit mode\n"
+"-w don't send immediate sanitize command\n"
+"-y don't ask any questions\n"
+"idle/standby arguments:\n"
+"-t <arg> number of seconds before respective state.\n"
+"fwdownload arguments:\n"
+"-f fw_image path to firmware image file\n"
+"-y don't ask any questions\n"
+"-s run in simulation mode\n"
+"-v print info for every firmware segment sent to device\n"
+"security arguments:\n"
+"-d pwd disable security using the given password for the selected\n"
+" user\n"
+"-e pwd erase the device using the given pwd for the selected user\n"
+"-f freeze the security configuration of the specified device\n"
+"-h pwd enhanced erase the device using the given pwd for the\n"
+" selected user\n"
+"-k pwd unlock the device using the given pwd for the selected\n"
+" user\n"
+"-l <high|maximum> specifies which security level to set: high or maximum\n"
+"-q be quiet, do not print any status messages\n"
+"-s pwd password the device (enable security) using the given\n"
+" pwd for the selected user\n"
+"-T timeout overrides the timeout (seconds) used for erase operation\n"
+"-U <user|master> specifies which user to set: user or master\n"
+"-y don't ask any questions\n"
+"hpa arguments:\n"
+"-f freeze the HPA configuration of the device\n"
+"-l lock the HPA configuration of the device\n"
+"-P make the HPA max sectors persist\n"
+"-p pwd Set the HPA configuration password required for unlock\n"
+" calls\n"
+"-q be quiet, do not print any status messages\n"
+"-s sectors configures the maximum user accessible sectors of the\n"
+" device\n"
+"-U pwd unlock the HPA configuration of the device\n"
+"-y don't ask any questions\n"
+"persist arguments:\n"
+"-i action specify read_keys, read_reservation, report_cap, or\n"
+" read_full_status\n"
+"-o action specify register, register_ignore, reserve, release,\n"
+" clear, preempt, preempt_abort, register_move, replace_lost\n"
+"-a set the All Target Ports (ALL_TG_PT) bit\n"
+"-I tid specify a Transport ID, e.g.: sas,0x1234567812345678\n"
+"-k key specify the Reservation Key\n"
+"-K sa_key specify the Service Action Reservation Key\n"
+"-p set the Activate Persist Through Power Loss bit\n"
+"-R rtp specify the Relative Target Port\n"
+"-s scope specify the scope: lun, extent, element or a number\n"
+"-S specify Transport ID for register, requires -I\n"
+"-T res_type specify the reservation type: read_shared, wr_ex, rd_ex,\n"
+" ex_ac, wr_ex_ro, ex_ac_ro, wr_ex_ar, ex_ac_ar\n"
+"-U unregister the current initiator for register_move\n"
+);
+#endif /* MINIMALISTIC */
+}
+
+int
+main(int argc, char **argv)
+{
+ int c;
+ char *device = NULL;
+ int unit = 0;
+ struct cam_device *cam_dev = NULL;
+ int timeout = 0, retry_count = 1;
+ camcontrol_optret optreturn;
+ char *tstr;
+ const char *mainopt = "C:En:t:u:v";
+ const char *subopt = NULL;
+ char combinedopt[256];
+ int error = 0, optstart = 2;
+ int devopen = 1;
+#ifndef MINIMALISTIC
+ path_id_t bus;
+ target_id_t target;
+ lun_id_t lun;
+#endif /* MINIMALISTIC */
+
+ cmdlist = CAM_CMD_NONE;
+ arglist = CAM_ARG_NONE;
+
+ if (argc < 2) {
+ usage(0);
+ exit(1);
+ }
+
+ /*
+ * Get the base option.
+ */
+ optreturn = getoption(option_table,argv[1], &cmdlist, &arglist,&subopt);
+
+ if (optreturn == CC_OR_AMBIGUOUS) {
+ warnx("ambiguous option %s", argv[1]);
+ usage(0);
+ exit(1);
+ } else if (optreturn == CC_OR_NOT_FOUND) {
+ warnx("option %s not found", argv[1]);
+ usage(0);
+ exit(1);
+ }
+
+ /*
+ * Ahh, getopt(3) is a pain.
+ *
+ * This is a gross hack. There really aren't many other good
+ * options (excuse the pun) for parsing options in a situation like
+ * this. getopt is kinda braindead, so you end up having to run
+ * through the options twice, and give each invocation of getopt
+ * the option string for the other invocation.
+ *
+ * You would think that you could just have two groups of options.
+ * The first group would get parsed by the first invocation of
+ * getopt, and the second group would get parsed by the second
+ * invocation of getopt. It doesn't quite work out that way. When
+ * the first invocation of getopt finishes, it leaves optind pointing
+ * to the argument _after_ the first argument in the second group.
+ * So when the second invocation of getopt comes around, it doesn't
+ * recognize the first argument it gets and then bails out.
+ *
+ * A nice alternative would be to have a flag for getopt that says
+ * "just keep parsing arguments even when you encounter an unknown
+ * argument", but there isn't one. So there's no real clean way to
+ * easily parse two sets of arguments without having one invocation
+ * of getopt know about the other.
+ *
+ * Without this hack, the first invocation of getopt would work as
+ * long as the generic arguments are first, but the second invocation
+ * (in the subfunction) would fail in one of two ways. In the case
+ * where you don't set optreset, it would fail because optind may be
+ * pointing to the argument after the one it should be pointing at.
+ * In the case where you do set optreset, and reset optind, it would
+ * fail because getopt would run into the first set of options, which
+ * it doesn't understand.
+ *
+ * All of this would "sort of" work if you could somehow figure out
+ * whether optind had been incremented one option too far. The
+ * mechanics of that, however, are more daunting than just giving
+ * both invocations all of the expect options for either invocation.
+ *
+ * Needless to say, I wouldn't mind if someone invented a better
+ * (non-GPL!) command line parsing interface than getopt. I
+ * wouldn't mind if someone added more knobs to getopt to make it
+ * work better. Who knows, I may talk myself into doing it someday,
+ * if the standards weenies let me. As it is, it just leads to
+ * hackery like this and causes people to avoid it in some cases.
+ *
+ * KDM, September 8th, 1998
+ */
+ if (subopt != NULL)
+ sprintf(combinedopt, "%s%s", mainopt, subopt);
+ else
+ sprintf(combinedopt, "%s", mainopt);
+
+ /*
+ * For these options we do not parse optional device arguments and
+ * we do not open a passthrough device.
+ */
+ if ((cmdlist == CAM_CMD_RESCAN)
+ || (cmdlist == CAM_CMD_RESET)
+ || (cmdlist == CAM_CMD_DEVTREE)
+ || (cmdlist == CAM_CMD_USAGE)
+ || (cmdlist == CAM_CMD_DEBUG))
+ devopen = 0;
+
+#ifndef MINIMALISTIC
+ if ((devopen == 1)
+ && (argc > 2 && argv[2][0] != '-')) {
+ char name[30];
+ int rv;
+
+ if (isdigit(argv[2][0])) {
+ /* device specified as bus:target[:lun] */
+ rv = parse_btl(argv[2], &bus, &target, &lun, &arglist);
+ if (rv < 2)
+ errx(1, "numeric device specification must "
+ "be either bus:target, or "
+ "bus:target:lun");
+ /* default to 0 if lun was not specified */
+ if ((arglist & CAM_ARG_LUN) == 0) {
+ lun = 0;
+ arglist |= CAM_ARG_LUN;
+ }
+ optstart++;
+ } else {
+ if (cam_get_device(argv[2], name, sizeof name, &unit)
+ == -1)
+ errx(1, "%s", cam_errbuf);
+ device = strdup(name);
+ arglist |= CAM_ARG_DEVICE | CAM_ARG_UNIT;
+ optstart++;
+ }
+ }
+#endif /* MINIMALISTIC */
+ /*
+ * Start getopt processing at argv[2/3], since we've already
+ * accepted argv[1..2] as the command name, and as a possible
+ * device name.
+ */
+ optind = optstart;
+
+ /*
+ * Now we run through the argument list looking for generic
+ * options, and ignoring options that possibly belong to
+ * subfunctions.
+ */
+ while ((c = getopt(argc, argv, combinedopt))!= -1){
+ switch(c) {
+ case 'C':
+ retry_count = strtol(optarg, NULL, 0);
+ if (retry_count < 0)
+ errx(1, "retry count %d is < 0",
+ retry_count);
+ arglist |= CAM_ARG_RETRIES;
+ break;
+ case 'E':
+ arglist |= CAM_ARG_ERR_RECOVER;
+ break;
+ case 'n':
+ arglist |= CAM_ARG_DEVICE;
+ tstr = optarg;
+ while (isspace(*tstr) && (*tstr != '\0'))
+ tstr++;
+ device = (char *)strdup(tstr);
+ break;
+ case 't':
+ timeout = strtol(optarg, NULL, 0);
+ if (timeout < 0)
+ errx(1, "invalid timeout %d", timeout);
+ /* Convert the timeout from seconds to ms */
+ timeout *= 1000;
+ arglist |= CAM_ARG_TIMEOUT;
+ break;
+ case 'u':
+ arglist |= CAM_ARG_UNIT;
+ unit = strtol(optarg, NULL, 0);
+ break;
+ case 'v':
+ arglist |= CAM_ARG_VERBOSE;
+ break;
+ default:
+ break;
+ }
+ }
+
+#ifndef MINIMALISTIC
+ /*
+ * For most commands we'll want to open the passthrough device
+ * associated with the specified device. In the case of the rescan
+ * commands, we don't use a passthrough device at all, just the
+ * transport layer device.
+ */
+ if (devopen == 1) {
+ if (((arglist & (CAM_ARG_BUS|CAM_ARG_TARGET)) == 0)
+ && (((arglist & CAM_ARG_DEVICE) == 0)
+ || ((arglist & CAM_ARG_UNIT) == 0))) {
+ errx(1, "subcommand \"%s\" requires a valid device "
+ "identifier", argv[1]);
+ }
+
+ if ((cam_dev = ((arglist & (CAM_ARG_BUS | CAM_ARG_TARGET))?
+ cam_open_btl(bus, target, lun, O_RDWR, NULL) :
+ cam_open_spec_device(device,unit,O_RDWR,NULL)))
+ == NULL)
+ errx(1,"%s", cam_errbuf);
+ }
+#endif /* MINIMALISTIC */
+
+ /*
+ * Reset optind to 2, and reset getopt, so these routines can parse
+ * the arguments again.
+ */
+ optind = optstart;
+ optreset = 1;
+
+ switch(cmdlist) {
+#ifndef MINIMALISTIC
+ case CAM_CMD_DEVLIST:
+ error = getdevlist(cam_dev);
+ break;
+ case CAM_CMD_HPA:
+ error = atahpa(cam_dev, retry_count, timeout,
+ argc, argv, combinedopt);
+ break;
+#endif /* MINIMALISTIC */
+ case CAM_CMD_DEVTREE:
+ error = getdevtree(argc, argv, combinedopt);
+ break;
+#ifndef MINIMALISTIC
+ case CAM_CMD_TUR:
+ error = testunitready(cam_dev, retry_count, timeout, 0);
+ break;
+ case CAM_CMD_INQUIRY:
+ error = scsidoinquiry(cam_dev, argc, argv, combinedopt,
+ retry_count, timeout);
+ break;
+ case CAM_CMD_IDENTIFY:
+ error = ataidentify(cam_dev, retry_count, timeout);
+ break;
+ case CAM_CMD_STARTSTOP:
+ error = scsistart(cam_dev, arglist & CAM_ARG_START_UNIT,
+ arglist & CAM_ARG_EJECT, retry_count,
+ timeout);
+ break;
+#endif /* MINIMALISTIC */
+ case CAM_CMD_RESCAN:
+ error = dorescan_or_reset(argc, argv, 1);
+ break;
+ case CAM_CMD_RESET:
+ error = dorescan_or_reset(argc, argv, 0);
+ break;
+#ifndef MINIMALISTIC
+ case CAM_CMD_READ_DEFECTS:
+ error = readdefects(cam_dev, argc, argv, combinedopt,
+ retry_count, timeout);
+ break;
+ case CAM_CMD_MODE_PAGE:
+ modepage(cam_dev, argc, argv, combinedopt,
+ retry_count, timeout);
+ break;
+ case CAM_CMD_SCSI_CMD:
+ error = scsicmd(cam_dev, argc, argv, combinedopt,
+ retry_count, timeout);
+ break;
+ case CAM_CMD_SMP_CMD:
+ error = smpcmd(cam_dev, argc, argv, combinedopt,
+ retry_count, timeout);
+ break;
+ case CAM_CMD_SMP_RG:
+ error = smpreportgeneral(cam_dev, argc, argv,
+ combinedopt, retry_count,
+ timeout);
+ break;
+ case CAM_CMD_SMP_PC:
+ error = smpphycontrol(cam_dev, argc, argv, combinedopt,
+ retry_count, timeout);
+ break;
+ case CAM_CMD_SMP_PHYLIST:
+ error = smpphylist(cam_dev, argc, argv, combinedopt,
+ retry_count, timeout);
+ break;
+ case CAM_CMD_SMP_MANINFO:
+ error = smpmaninfo(cam_dev, argc, argv, combinedopt,
+ retry_count, timeout);
+ break;
+ case CAM_CMD_DEBUG:
+ error = camdebug(argc, argv, combinedopt);
+ break;
+ case CAM_CMD_TAG:
+ error = tagcontrol(cam_dev, argc, argv, combinedopt);
+ break;
+ case CAM_CMD_RATE:
+ error = ratecontrol(cam_dev, retry_count, timeout,
+ argc, argv, combinedopt);
+ break;
+ case CAM_CMD_FORMAT:
+ error = scsiformat(cam_dev, argc, argv,
+ combinedopt, retry_count, timeout);
+ break;
+ case CAM_CMD_REPORTLUNS:
+ error = scsireportluns(cam_dev, argc, argv,
+ combinedopt, retry_count,
+ timeout);
+ break;
+ case CAM_CMD_READCAP:
+ error = scsireadcapacity(cam_dev, argc, argv,
+ combinedopt, retry_count,
+ timeout);
+ break;
+ case CAM_CMD_IDLE:
+ case CAM_CMD_STANDBY:
+ case CAM_CMD_SLEEP:
+ error = atapm(cam_dev, argc, argv,
+ combinedopt, retry_count, timeout);
+ break;
+ case CAM_CMD_APM:
+ case CAM_CMD_AAM:
+ error = ataaxm(cam_dev, argc, argv,
+ combinedopt, retry_count, timeout);
+ break;
+ case CAM_CMD_SECURITY:
+ error = atasecurity(cam_dev, retry_count, timeout,
+ argc, argv, combinedopt);
+ break;
+ case CAM_CMD_DOWNLOAD_FW:
+ error = fwdownload(cam_dev, argc, argv, combinedopt,
+ arglist & CAM_ARG_VERBOSE, retry_count, timeout,
+ get_disk_type(cam_dev));
+ break;
+ case CAM_CMD_SANITIZE:
+ error = scsisanitize(cam_dev, argc, argv,
+ combinedopt, retry_count, timeout);
+ break;
+ case CAM_CMD_PERSIST:
+ error = scsipersist(cam_dev, argc, argv, combinedopt,
+ retry_count, timeout, arglist & CAM_ARG_VERBOSE,
+ arglist & CAM_ARG_ERR_RECOVER);
+ break;
+#endif /* MINIMALISTIC */
+ case CAM_CMD_USAGE:
+ usage(1);
+ break;
+ default:
+ usage(0);
+ error = 1;
+ break;
+ }
+
+ if (cam_dev != NULL)
+ cam_close_device(cam_dev);
+
+ exit(error);
+}
diff --git a/sbin/camcontrol/camcontrol.h b/sbin/camcontrol/camcontrol.h
new file mode 100644
index 0000000..7c249bf
--- /dev/null
+++ b/sbin/camcontrol/camcontrol.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 1998 Kenneth D. Merry.
+ * 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 AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _CAMCONTROL_H
+#define _CAMCONTROL_H
+
+typedef enum {
+ CC_OR_NOT_FOUND,
+ CC_OR_AMBIGUOUS,
+ CC_OR_FOUND
+} camcontrol_optret;
+
+/*
+ * get_hook: Structure for evaluating args in a callback.
+ */
+struct get_hook
+{
+ int argc;
+ char **argv;
+ int got;
+};
+
+extern int verbose;
+
+int fwdownload(struct cam_device *device, int argc, char **argv,
+ char *combinedopt, int printerrors, int retry_count, int timeout,
+ const char */*type*/);
+void mode_sense(struct cam_device *device, int mode_page, int page_control,
+ int dbd, int retry_count, int timeout, u_int8_t *data,
+ int datalen);
+void mode_select(struct cam_device *device, int save_pages, int retry_count,
+ int timeout, u_int8_t *data, int datalen);
+void mode_edit(struct cam_device *device, int page, int page_control, int dbd,
+ int edit, int binary, int retry_count, int timeout);
+void mode_list(struct cam_device *device, int page_control, int dbd,
+ int retry_count, int timeout);
+int scsidoinquiry(struct cam_device *device, int argc, char **argv,
+ char *combinedopt, int retry_count, int timeout);
+int scsipersist(struct cam_device *device, int argc, char **argv,
+ char *combinedopt, int retry_count, int timeout, int verbose,
+ int err_recover);
+char *cget(void *hook, char *name);
+int iget(void *hook, char *name);
+void arg_put(void *hook, int letter, void *arg, int count, char *name);
+int get_confirmation(void);
+void usage(int printlong);
+#endif /* _CAMCONTROL_H */
diff --git a/sbin/camcontrol/fwdownload.c b/sbin/camcontrol/fwdownload.c
new file mode 100644
index 0000000..181c234
--- /dev/null
+++ b/sbin/camcontrol/fwdownload.c
@@ -0,0 +1,470 @@
+/*-
+ * Copyright (c) 2011 Sandvine Incorporated. All rights reserved.
+ * Copyright (c) 2002-2011 Andre Albsmeier <andre@albsmeier.net>
+ * 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,
+ * without modification, immediately at the beginning of the file.
+ * 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.
+ *
+ * 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.
+ */
+
+/*
+ * This software is derived from Andre Albsmeier's fwprog.c which contained
+ * the following note:
+ *
+ * Many thanks goes to Marc Frajola <marc@terasolutions.com> from
+ * TeraSolutions for the initial idea and his programme for upgrading
+ * the firmware of I*M DDYS drives.
+ */
+
+/*
+ * BEWARE:
+ *
+ * The fact that you see your favorite vendor listed below does not
+ * imply that your equipment won't break when you use this software
+ * with it. It only means that the firmware of at least one device type
+ * of each vendor listed has been programmed successfully using this code.
+ *
+ * The -s option simulates a download but does nothing apart from that.
+ * It can be used to check what chunk sizes would have been used with the
+ * specified device.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <cam/scsi/scsi_all.h>
+#include <cam/scsi/scsi_message.h>
+#include <camlib.h>
+
+#include "progress.h"
+
+#include "camcontrol.h"
+
+#define CMD_TIMEOUT 50000 /* 50 seconds */
+
+typedef enum {
+ VENDOR_HITACHI,
+ VENDOR_HP,
+ VENDOR_IBM,
+ VENDOR_PLEXTOR,
+ VENDOR_QUALSTAR,
+ VENDOR_QUANTUM,
+ VENDOR_SAMSUNG,
+ VENDOR_SEAGATE,
+ VENDOR_UNKNOWN
+} fw_vendor_t;
+
+struct fw_vendor {
+ fw_vendor_t type;
+ const char *pattern;
+ int max_pkt_size;
+ u_int8_t cdb_byte2;
+ u_int8_t cdb_byte2_last;
+ int inc_cdb_buffer_id;
+ int inc_cdb_offset;
+};
+
+static const struct fw_vendor vendors_list[] = {
+ {VENDOR_HITACHI, "HITACHI", 0x8000, 0x05, 0x05, 1, 0},
+ {VENDOR_HP, "HP", 0x8000, 0x07, 0x07, 0, 1},
+ {VENDOR_IBM, "IBM", 0x8000, 0x05, 0x05, 1, 0},
+ {VENDOR_PLEXTOR, "PLEXTOR", 0x2000, 0x04, 0x05, 0, 1},
+ {VENDOR_QUALSTAR, "QUALSTAR", 0x2030, 0x05, 0x05, 0, 0},
+ {VENDOR_QUANTUM, "QUANTUM", 0x2000, 0x04, 0x05, 0, 1},
+ {VENDOR_SAMSUNG, "SAMSUNG", 0x8000, 0x07, 0x07, 0, 1},
+ {VENDOR_SEAGATE, "SEAGATE", 0x8000, 0x07, 0x07, 0, 1},
+ /* the next 2 are SATA disks going through SAS HBA */
+ {VENDOR_SEAGATE, "ATA ST", 0x8000, 0x07, 0x07, 0, 1},
+ {VENDOR_HITACHI, "ATA HDS", 0x8000, 0x05, 0x05, 1, 0},
+ {VENDOR_UNKNOWN, NULL, 0x0000, 0x00, 0x00, 0, 0}
+};
+
+#ifndef ATA_DOWNLOAD_MICROCODE
+#define ATA_DOWNLOAD_MICROCODE 0x92
+#endif
+
+#define USE_OFFSETS_FEATURE 0x3
+
+#ifndef LOW_SECTOR_SIZE
+#define LOW_SECTOR_SIZE 512
+#endif
+
+#define ATA_MAKE_LBA(o, p) \
+ ((((((o) / LOW_SECTOR_SIZE) >> 8) & 0xff) << 16) | \
+ ((((o) / LOW_SECTOR_SIZE) & 0xff) << 8) | \
+ ((((p) / LOW_SECTOR_SIZE) >> 8) & 0xff))
+
+#define ATA_MAKE_SECTORS(p) (((p) / 512) & 0xff)
+
+#ifndef UNKNOWN_MAX_PKT_SIZE
+#define UNKNOWN_MAX_PKT_SIZE 0x8000
+#endif
+
+static const struct fw_vendor *fw_get_vendor(struct cam_device *cam_dev);
+static char *fw_read_img(const char *fw_img_path,
+ const struct fw_vendor *vp, int *num_bytes);
+static int fw_download_img(struct cam_device *cam_dev,
+ const struct fw_vendor *vp, char *buf, int img_size,
+ int sim_mode, int printerrors, int retry_count, int timeout,
+ const char */*name*/, const char */*type*/);
+
+/*
+ * Find entry in vendors list that belongs to
+ * the vendor of given cam device.
+ */
+static const struct fw_vendor *
+fw_get_vendor(struct cam_device *cam_dev)
+{
+ char vendor[SID_VENDOR_SIZE + 1];
+ const struct fw_vendor *vp;
+
+ if (cam_dev == NULL)
+ return (NULL);
+ cam_strvis((u_char *)vendor, (u_char *)cam_dev->inq_data.vendor,
+ sizeof(cam_dev->inq_data.vendor), sizeof(vendor));
+ for (vp = vendors_list; vp->pattern != NULL; vp++) {
+ if (!cam_strmatch((const u_char *)vendor,
+ (const u_char *)vp->pattern, strlen(vendor)))
+ break;
+ }
+ return (vp);
+}
+
+/*
+ * Allocate a buffer and read fw image file into it
+ * from given path. Number of bytes read is stored
+ * in num_bytes.
+ */
+static char *
+fw_read_img(const char *fw_img_path, const struct fw_vendor *vp, int *num_bytes)
+{
+ int fd;
+ struct stat stbuf;
+ char *buf;
+ off_t img_size;
+ int skip_bytes = 0;
+
+ if ((fd = open(fw_img_path, O_RDONLY)) < 0) {
+ warn("Could not open image file %s", fw_img_path);
+ return (NULL);
+ }
+ if (fstat(fd, &stbuf) < 0) {
+ warn("Could not stat image file %s", fw_img_path);
+ goto bailout1;
+ }
+ if ((img_size = stbuf.st_size) == 0) {
+ warnx("Zero length image file %s", fw_img_path);
+ goto bailout1;
+ }
+ if ((buf = malloc(img_size)) == NULL) {
+ warnx("Could not allocate buffer to read image file %s",
+ fw_img_path);
+ goto bailout1;
+ }
+ /* Skip headers if applicable. */
+ switch (vp->type) {
+ case VENDOR_SEAGATE:
+ if (read(fd, buf, 16) != 16) {
+ warn("Could not read image file %s", fw_img_path);
+ goto bailout;
+ }
+ if (lseek(fd, 0, SEEK_SET) == -1) {
+ warn("Unable to lseek");
+ goto bailout;
+ }
+ if ((strncmp(buf, "SEAGATE,SEAGATE ", 16) == 0) ||
+ (img_size % 512 == 80))
+ skip_bytes = 80;
+ break;
+ case VENDOR_QUALSTAR:
+ skip_bytes = img_size % 1030;
+ break;
+ default:
+ break;
+ }
+ if (skip_bytes != 0) {
+ fprintf(stdout, "Skipping %d byte header.\n", skip_bytes);
+ if (lseek(fd, skip_bytes, SEEK_SET) == -1) {
+ warn("Could not lseek");
+ goto bailout;
+ }
+ img_size -= skip_bytes;
+ }
+ /* Read image into a buffer. */
+ if (read(fd, buf, img_size) != img_size) {
+ warn("Could not read image file %s", fw_img_path);
+ goto bailout;
+ }
+ *num_bytes = img_size;
+ close(fd);
+ return (buf);
+bailout:
+ free(buf);
+bailout1:
+ close(fd);
+ *num_bytes = 0;
+ return (NULL);
+}
+
+/*
+ * Download firmware stored in buf to cam_dev. If simulation mode
+ * is enabled, only show what packet sizes would be sent to the
+ * device but do not sent any actual packets
+ */
+static int
+fw_download_img(struct cam_device *cam_dev, const struct fw_vendor *vp,
+ char *buf, int img_size, int sim_mode, int printerrors, int retry_count,
+ int timeout, const char *imgname, const char *type)
+{
+ struct scsi_write_buffer cdb;
+ progress_t progress;
+ int size;
+ union ccb *ccb;
+ int pkt_count = 0;
+ int max_pkt_size;
+ u_int32_t pkt_size = 0;
+ char *pkt_ptr = buf;
+ u_int32_t offset;
+ int last_pkt = 0;
+ int16_t *ptr;
+
+ if ((ccb = cam_getccb(cam_dev)) == NULL) {
+ warnx("Could not allocate CCB");
+ return (1);
+ }
+ if (strcmp(type, "scsi") == 0) {
+ scsi_test_unit_ready(&ccb->csio, 0, NULL, MSG_SIMPLE_Q_TAG,
+ SSD_FULL_SIZE, 5000);
+ } else if (strcmp(type, "ata") == 0) {
+ /* cam_getccb cleans up the header, caller has to zero the payload */
+ bzero(&(&ccb->ccb_h)[1],
+ sizeof(struct ccb_ataio) - sizeof(struct ccb_hdr));
+
+ ptr = (uint16_t *)malloc(sizeof(struct ata_params));
+
+ if (ptr == NULL) {
+ cam_freeccb(ccb);
+ warnx("can't malloc memory for identify\n");
+ return(1);
+ }
+ bzero(ptr, sizeof(struct ata_params));
+ cam_fill_ataio(&ccb->ataio,
+ 1,
+ NULL,
+ /*flags*/CAM_DIR_IN,
+ MSG_SIMPLE_Q_TAG,
+ /*data_ptr*/(uint8_t *)ptr,
+ /*dxfer_len*/sizeof(struct ata_params),
+ timeout ? timeout : 30 * 1000);
+ ata_28bit_cmd(&ccb->ataio, ATA_ATA_IDENTIFY, 0, 0, 0);
+ } else {
+ warnx("weird disk type '%s'", type);
+ cam_freeccb(ccb);
+ return 1;
+ }
+ /* Disable freezing the device queue. */
+ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
+ if (cam_send_ccb(cam_dev, ccb) < 0) {
+ warnx("Error sending identify/test unit ready");
+ if (printerrors)
+ cam_error_print(cam_dev, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ cam_freeccb(ccb);
+ return(1);
+ }
+ if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
+ warnx("Device is not ready");
+ if (printerrors)
+ cam_error_print(cam_dev, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ cam_freeccb(ccb);
+ return (1);
+ }
+ max_pkt_size = vp->max_pkt_size;
+ if (vp->max_pkt_size == 0 && strcmp(type, "ata") == 0) {
+ max_pkt_size = UNKNOWN_MAX_PKT_SIZE;
+ }
+ pkt_size = vp->max_pkt_size;
+ progress_init(&progress, imgname, size = img_size);
+ /* Download single fw packets. */
+ do {
+ if (img_size <= max_pkt_size) {
+ last_pkt = 1;
+ pkt_size = img_size;
+ }
+ progress_update(&progress, size - img_size);
+ progress_draw(&progress);
+ bzero(&cdb, sizeof(cdb));
+ if (strcmp(type, "scsi") == 0) {
+ cdb.opcode = WRITE_BUFFER;
+ cdb.control = 0;
+ /* Parameter list length. */
+ scsi_ulto3b(pkt_size, &cdb.length[0]);
+ offset = vp->inc_cdb_offset ? (pkt_ptr - buf) : 0;
+ scsi_ulto3b(offset, &cdb.offset[0]);
+ cdb.byte2 = last_pkt ? vp->cdb_byte2_last : vp->cdb_byte2;
+ cdb.buffer_id = vp->inc_cdb_buffer_id ? pkt_count : 0;
+ /* Zero out payload of ccb union after ccb header. */
+ bzero((u_char *)ccb + sizeof(struct ccb_hdr),
+ sizeof(struct ccb_scsiio) - sizeof(struct ccb_hdr));
+ /* Copy previously constructed cdb into ccb_scsiio struct. */
+ bcopy(&cdb, &ccb->csio.cdb_io.cdb_bytes[0],
+ sizeof(struct scsi_write_buffer));
+ /* Fill rest of ccb_scsiio struct. */
+ if (!sim_mode) {
+ cam_fill_csio(&ccb->csio, /* ccb_scsiio */
+ retry_count, /* retries */
+ NULL, /* cbfcnp */
+ CAM_DIR_OUT | CAM_DEV_QFRZDIS, /* flags */
+ CAM_TAG_ACTION_NONE, /* tag_action */
+ (u_char *)pkt_ptr, /* data_ptr */
+ pkt_size, /* dxfer_len */
+ SSD_FULL_SIZE, /* sense_len */
+ sizeof(struct scsi_write_buffer), /* cdb_len */
+ timeout ? timeout : CMD_TIMEOUT); /* timeout */
+ }
+ } else if (strcmp(type, "ata") == 0) {
+ bzero(&(&ccb->ccb_h)[1],
+ sizeof(struct ccb_ataio) - sizeof(struct ccb_hdr));
+ if (!sim_mode) {
+ uint32_t off;
+
+ cam_fill_ataio(&ccb->ataio,
+ (last_pkt) ? 256 : retry_count,
+ NULL,
+ /*flags*/CAM_DIR_OUT | CAM_DEV_QFRZDIS,
+ CAM_TAG_ACTION_NONE,
+ /*data_ptr*/(uint8_t *)pkt_ptr,
+ /*dxfer_len*/pkt_size,
+ timeout ? timeout : 30 * 1000);
+ off = (uint32_t)(pkt_ptr - buf);
+ ata_28bit_cmd(&ccb->ataio, ATA_DOWNLOAD_MICROCODE,
+ USE_OFFSETS_FEATURE,
+ ATA_MAKE_LBA(off, pkt_size),
+ ATA_MAKE_SECTORS(pkt_size));
+ }
+ }
+ if (!sim_mode) {
+ /* Execute the command. */
+ if (cam_send_ccb(cam_dev, ccb) < 0 ||
+ (ccb->ccb_h.status & CAM_STATUS_MASK) !=
+ CAM_REQ_CMP) {
+ warnx("Error writing image to device");
+ if (printerrors)
+ cam_error_print(cam_dev, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ goto bailout;
+ }
+ }
+ /* Prepare next round. */
+ pkt_count++;
+ pkt_ptr += pkt_size;
+ img_size -= pkt_size;
+ } while(!last_pkt);
+ progress_complete(&progress, size - img_size);
+ cam_freeccb(ccb);
+ return (0);
+bailout:
+ progress_complete(&progress, size - img_size);
+ cam_freeccb(ccb);
+ return (1);
+}
+
+int
+fwdownload(struct cam_device *device, int argc, char **argv,
+ char *combinedopt, int printerrors, int retry_count, int timeout,
+ const char *type)
+{
+ const struct fw_vendor *vp;
+ char *fw_img_path = NULL;
+ char *buf;
+ int img_size;
+ int c;
+ int sim_mode = 0;
+ int confirmed = 0;
+
+ while ((c = getopt(argc, argv, combinedopt)) != -1) {
+ switch (c) {
+ case 's':
+ sim_mode = 1;
+ confirmed = 1;
+ break;
+ case 'f':
+ fw_img_path = optarg;
+ break;
+ case 'y':
+ confirmed = 1;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (fw_img_path == NULL)
+ errx(1, "you must specify a firmware image file using -f option");
+
+ vp = fw_get_vendor(device);
+ if (vp == NULL)
+ errx(1, "NULL vendor");
+ if (vp->type == VENDOR_UNKNOWN)
+ warnx("Unsupported device - flashing through an HBA?");
+
+ buf = fw_read_img(fw_img_path, vp, &img_size);
+ if (buf == NULL)
+ goto fail;
+
+ if (!confirmed) {
+ fprintf(stdout, "You are about to download firmware image (%s)"
+ " into the following device:\n",
+ fw_img_path);
+ fprintf(stdout, "\nIt may damage your drive. ");
+ if (!get_confirmation())
+ goto fail;
+ }
+ if (sim_mode)
+ fprintf(stdout, "Running in simulation mode\n");
+
+ if (fw_download_img(device, vp, buf, img_size, sim_mode, printerrors,
+ retry_count, timeout, fw_img_path, type) != 0) {
+ fprintf(stderr, "Firmware download failed\n");
+ goto fail;
+ }
+ else
+ fprintf(stdout, "Firmware download successful\n");
+
+ free(buf);
+ return (0);
+fail:
+ if (buf != NULL)
+ free(buf);
+ return (1);
+}
+
diff --git a/sbin/camcontrol/modeedit.c b/sbin/camcontrol/modeedit.c
new file mode 100644
index 0000000..00ab974
--- /dev/null
+++ b/sbin/camcontrol/modeedit.c
@@ -0,0 +1,906 @@
+/*-
+ * Copyright (c) 2000 Kelly Yancey <kbyanc@posi.net>
+ * Derived from work done by Julian Elischer <julian@tfs.com,
+ * julian@dialix.oz.au>, 1993, and Peter Dufault <dufault@hda.com>, 1994.
+ * 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,
+ * without modification, immediately at the beginning of the file.
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/queue.h>
+#include <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include <cam/scsi/scsi_all.h>
+#include <cam/cam.h>
+#include <cam/cam_ccb.h>
+#include <camlib.h>
+#include "camcontrol.h"
+
+#define DEFAULT_SCSI_MODE_DB "/usr/share/misc/scsi_modes"
+#define DEFAULT_EDITOR "vi"
+#define MAX_FORMAT_SPEC 4096 /* Max CDB format specifier. */
+#define MAX_PAGENUM_LEN 10 /* Max characters in page num. */
+#define MAX_PAGENAME_LEN 64 /* Max characters in page name. */
+#define PAGEDEF_START '{' /* Page definition delimiter. */
+#define PAGEDEF_END '}' /* Page definition delimiter. */
+#define PAGENAME_START '"' /* Page name delimiter. */
+#define PAGENAME_END '"' /* Page name delimiter. */
+#define PAGEENTRY_END ';' /* Page entry terminator (optional). */
+#define MAX_COMMAND_SIZE 255 /* Mode/Log sense data buffer size. */
+#define PAGE_CTRL_SHIFT 6 /* Bit offset to page control field. */
+
+
+/* Macros for working with mode pages. */
+#define MODE_PAGE_HEADER(mh) \
+ (struct scsi_mode_page_header *)find_mode_page_6(mh)
+
+#define MODE_PAGE_DATA(mph) \
+ (u_int8_t *)(mph) + sizeof(struct scsi_mode_page_header)
+
+
+struct editentry {
+ STAILQ_ENTRY(editentry) link;
+ char *name;
+ char type;
+ int editable;
+ int size;
+ union {
+ int ivalue;
+ char *svalue;
+ } value;
+};
+static STAILQ_HEAD(, editentry) editlist; /* List of page entries. */
+static int editlist_changed = 0; /* Whether any entries were changed. */
+
+struct pagename {
+ SLIST_ENTRY(pagename) link;
+ int pagenum;
+ char *name;
+};
+static SLIST_HEAD(, pagename) namelist; /* Page number to name mappings. */
+
+static char format[MAX_FORMAT_SPEC]; /* Buffer for scsi cdb format def. */
+
+static FILE *edit_file = NULL; /* File handle for edit file. */
+static char edit_path[] = "/tmp/camXXXXXX";
+
+
+/* Function prototypes. */
+static void editentry_create(void *hook, int letter, void *arg,
+ int count, char *name);
+static void editentry_update(void *hook, int letter, void *arg,
+ int count, char *name);
+static int editentry_save(void *hook, char *name);
+static struct editentry *editentry_lookup(char *name);
+static int editentry_set(char *name, char *newvalue,
+ int editonly);
+static void editlist_populate(struct cam_device *device,
+ int modepage, int page_control,
+ int dbd, int retries, int timeout);
+static void editlist_save(struct cam_device *device, int modepage,
+ int page_control, int dbd, int retries,
+ int timeout);
+static void nameentry_create(int pagenum, char *name);
+static struct pagename *nameentry_lookup(int pagenum);
+static int load_format(const char *pagedb_path, int page);
+static int modepage_write(FILE *file, int editonly);
+static int modepage_read(FILE *file);
+static void modepage_edit(void);
+static void modepage_dump(struct cam_device *device, int page,
+ int page_control, int dbd, int retries,
+ int timeout);
+static void cleanup_editfile(void);
+
+
+#define returnerr(code) do { \
+ errno = code; \
+ return (-1); \
+} while (0)
+
+
+#define RTRIM(string) do { \
+ int _length; \
+ while (isspace(string[_length = strlen(string) - 1])) \
+ string[_length] = '\0'; \
+} while (0)
+
+
+static void
+editentry_create(void *hook __unused, int letter, void *arg, int count,
+ char *name)
+{
+ struct editentry *newentry; /* Buffer to hold new entry. */
+
+ /* Allocate memory for the new entry and a copy of the entry name. */
+ if ((newentry = malloc(sizeof(struct editentry))) == NULL ||
+ (newentry->name = strdup(name)) == NULL)
+ err(EX_OSERR, NULL);
+
+ /* Trim any trailing whitespace for the entry name. */
+ RTRIM(newentry->name);
+
+ newentry->editable = (arg != NULL);
+ newentry->type = letter;
+ newentry->size = count; /* Placeholder; not accurate. */
+ newentry->value.svalue = NULL;
+
+ STAILQ_INSERT_TAIL(&editlist, newentry, link);
+}
+
+static void
+editentry_update(void *hook __unused, int letter, void *arg, int count,
+ char *name)
+{
+ struct editentry *dest; /* Buffer to hold entry to update. */
+
+ dest = editentry_lookup(name);
+ assert(dest != NULL);
+
+ dest->type = letter;
+ dest->size = count; /* We get the real size now. */
+
+ switch (dest->type) {
+ case 'i': /* Byte-sized integral type. */
+ case 'b': /* Bit-sized integral types. */
+ case 't':
+ dest->value.ivalue = (intptr_t)arg;
+ break;
+
+ case 'c': /* Character array. */
+ case 'z': /* Null-padded string. */
+ editentry_set(name, (char *)arg, 0);
+ break;
+ default:
+ ; /* NOTREACHED */
+ }
+}
+
+static int
+editentry_save(void *hook __unused, char *name)
+{
+ struct editentry *src; /* Entry value to save. */
+
+ src = editentry_lookup(name);
+ assert(src != NULL);
+
+ switch (src->type) {
+ case 'i': /* Byte-sized integral type. */
+ case 'b': /* Bit-sized integral types. */
+ case 't':
+ return (src->value.ivalue);
+ /* NOTREACHED */
+
+ case 'c': /* Character array. */
+ case 'z': /* Null-padded string. */
+ return ((intptr_t)src->value.svalue);
+ /* NOTREACHED */
+
+ default:
+ ; /* NOTREACHED */
+ }
+
+ return (0); /* This should never happen. */
+}
+
+static struct editentry *
+editentry_lookup(char *name)
+{
+ struct editentry *scan;
+
+ assert(name != NULL);
+
+ STAILQ_FOREACH(scan, &editlist, link) {
+ if (strcasecmp(scan->name, name) == 0)
+ return (scan);
+ }
+
+ /* Not found during list traversal. */
+ return (NULL);
+}
+
+static int
+editentry_set(char *name, char *newvalue, int editonly)
+{
+ struct editentry *dest; /* Modepage entry to update. */
+ char *cval; /* Pointer to new string value. */
+ char *convertend; /* End-of-conversion pointer. */
+ int ival; /* New integral value. */
+ int resolution; /* Resolution in bits for integer conversion. */
+
+/*
+ * Macro to determine the maximum value of the given size for the current
+ * resolution.
+ * XXX Lovely x86's optimize out the case of shifting by 32 and gcc doesn't
+ * currently workaround it (even for int64's), so we have to kludge it.
+ */
+#define RESOLUTION_MAX(size) ((resolution * (size) == 32)? \
+ (int)0xffffffff: (1 << (resolution * (size))) - 1)
+
+ assert(newvalue != NULL);
+ if (*newvalue == '\0')
+ return (0); /* Nothing to do. */
+
+ if ((dest = editentry_lookup(name)) == NULL)
+ returnerr(ENOENT);
+ if (!dest->editable && editonly)
+ returnerr(EPERM);
+
+ switch (dest->type) {
+ case 'i': /* Byte-sized integral type. */
+ case 'b': /* Bit-sized integral types. */
+ case 't':
+ /* Convert the value string to an integer. */
+ resolution = (dest->type == 'i')? 8: 1;
+ ival = (int)strtol(newvalue, &convertend, 0);
+ if (*convertend != '\0')
+ returnerr(EINVAL);
+ if (ival > RESOLUTION_MAX(dest->size) || ival < 0) {
+ int newival = (ival < 0)? 0: RESOLUTION_MAX(dest->size);
+ warnx("value %d is out of range for entry %s; clipping "
+ "to %d", ival, name, newival);
+ ival = newival;
+ }
+ if (dest->value.ivalue != ival)
+ editlist_changed = 1;
+ dest->value.ivalue = ival;
+ break;
+
+ case 'c': /* Character array. */
+ case 'z': /* Null-padded string. */
+ if ((cval = malloc(dest->size + 1)) == NULL)
+ err(EX_OSERR, NULL);
+ bzero(cval, dest->size + 1);
+ strncpy(cval, newvalue, dest->size);
+ if (dest->type == 'z') {
+ /* Convert trailing spaces to nulls. */
+ char *convertend2;
+
+ for (convertend2 = cval + dest->size;
+ convertend2 >= cval; convertend2--) {
+ if (*convertend2 == ' ')
+ *convertend2 = '\0';
+ else if (*convertend2 != '\0')
+ break;
+ }
+ }
+ if (strncmp(dest->value.svalue, cval, dest->size) == 0) {
+ /* Nothing changed, free the newly allocated string. */
+ free(cval);
+ break;
+ }
+ if (dest->value.svalue != NULL) {
+ /* Free the current string buffer. */
+ free(dest->value.svalue);
+ dest->value.svalue = NULL;
+ }
+ dest->value.svalue = cval;
+ editlist_changed = 1;
+ break;
+
+ default:
+ ; /* NOTREACHED */
+ }
+
+ return (0);
+#undef RESOLUTION_MAX
+}
+
+static void
+nameentry_create(int pagenum, char *name) {
+ struct pagename *newentry;
+
+ if (pagenum < 0 || name == NULL || name[0] == '\0')
+ return;
+
+ /* Allocate memory for the new entry and a copy of the entry name. */
+ if ((newentry = malloc(sizeof(struct pagename))) == NULL ||
+ (newentry->name = strdup(name)) == NULL)
+ err(EX_OSERR, NULL);
+
+ /* Trim any trailing whitespace for the page name. */
+ RTRIM(newentry->name);
+
+ newentry->pagenum = pagenum;
+ SLIST_INSERT_HEAD(&namelist, newentry, link);
+}
+
+static struct pagename *
+nameentry_lookup(int pagenum) {
+ struct pagename *scan;
+
+ SLIST_FOREACH(scan, &namelist, link) {
+ if (pagenum == scan->pagenum)
+ return (scan);
+ }
+
+ /* Not found during list traversal. */
+ return (NULL);
+}
+
+static int
+load_format(const char *pagedb_path, int page)
+{
+ FILE *pagedb;
+ char str_pagenum[MAX_PAGENUM_LEN];
+ char str_pagename[MAX_PAGENAME_LEN];
+ int pagenum;
+ int depth; /* Quoting depth. */
+ int found;
+ int lineno;
+ enum { LOCATE, PAGENAME, PAGEDEF } state;
+ int ch;
+ char c;
+
+#define SETSTATE_LOCATE do { \
+ str_pagenum[0] = '\0'; \
+ str_pagename[0] = '\0'; \
+ pagenum = -1; \
+ state = LOCATE; \
+} while (0)
+
+#define SETSTATE_PAGENAME do { \
+ str_pagename[0] = '\0'; \
+ state = PAGENAME; \
+} while (0)
+
+#define SETSTATE_PAGEDEF do { \
+ format[0] = '\0'; \
+ state = PAGEDEF; \
+} while (0)
+
+#define UPDATE_LINENO do { \
+ if (c == '\n') \
+ lineno++; \
+} while (0)
+
+#define BUFFERFULL(buffer) (strlen(buffer) + 1 >= sizeof(buffer))
+
+ if ((pagedb = fopen(pagedb_path, "r")) == NULL)
+ returnerr(ENOENT);
+
+ SLIST_INIT(&namelist);
+
+ c = '\0';
+ depth = 0;
+ lineno = 0;
+ found = 0;
+ SETSTATE_LOCATE;
+ while ((ch = fgetc(pagedb)) != EOF) {
+
+ /* Keep a line count to make error messages more useful. */
+ UPDATE_LINENO;
+
+ /* Skip over comments anywhere in the mode database. */
+ if (ch == '#') {
+ do {
+ ch = fgetc(pagedb);
+ } while (ch != '\n' && ch != EOF);
+ UPDATE_LINENO;
+ continue;
+ }
+ c = ch;
+
+ /* Strip out newline characters. */
+ if (c == '\n')
+ continue;
+
+ /* Keep track of the nesting depth for braces. */
+ if (c == PAGEDEF_START)
+ depth++;
+ else if (c == PAGEDEF_END) {
+ depth--;
+ if (depth < 0) {
+ errx(EX_OSFILE, "%s:%d: %s", pagedb_path,
+ lineno, "mismatched bracket");
+ }
+ }
+
+ switch (state) {
+ case LOCATE:
+ /*
+ * Locate the page the user is interested in, skipping
+ * all others.
+ */
+ if (isspace(c)) {
+ /* Ignore all whitespace between pages. */
+ break;
+ } else if (depth == 0 && c == PAGEENTRY_END) {
+ /*
+ * A page entry terminator will reset page
+ * scanning (useful for assigning names to
+ * modes without providing a mode definition).
+ */
+ /* Record the name of this page. */
+ pagenum = strtol(str_pagenum, NULL, 0);
+ nameentry_create(pagenum, str_pagename);
+ SETSTATE_LOCATE;
+ } else if (depth == 0 && c == PAGENAME_START) {
+ SETSTATE_PAGENAME;
+ } else if (c == PAGEDEF_START) {
+ pagenum = strtol(str_pagenum, NULL, 0);
+ if (depth == 1) {
+ /* Record the name of this page. */
+ nameentry_create(pagenum, str_pagename);
+ /*
+ * Only record the format if this is
+ * the page we are interested in.
+ */
+ if (page == pagenum && !found)
+ SETSTATE_PAGEDEF;
+ }
+ } else if (c == PAGEDEF_END) {
+ /* Reset the processor state. */
+ SETSTATE_LOCATE;
+ } else if (depth == 0 && ! BUFFERFULL(str_pagenum)) {
+ strncat(str_pagenum, &c, 1);
+ } else if (depth == 0) {
+ errx(EX_OSFILE, "%s:%d: %s %zd %s", pagedb_path,
+ lineno, "page identifier exceeds",
+ sizeof(str_pagenum) - 1, "characters");
+ }
+ break;
+
+ case PAGENAME:
+ if (c == PAGENAME_END) {
+ /*
+ * Return to LOCATE state without resetting the
+ * page number buffer.
+ */
+ state = LOCATE;
+ } else if (! BUFFERFULL(str_pagename)) {
+ strncat(str_pagename, &c, 1);
+ } else {
+ errx(EX_OSFILE, "%s:%d: %s %zd %s", pagedb_path,
+ lineno, "page name exceeds",
+ sizeof(str_pagenum) - 1, "characters");
+ }
+ break;
+
+ case PAGEDEF:
+ /*
+ * Transfer the page definition into a format buffer
+ * suitable for use with CDB encoding/decoding routines.
+ */
+ if (depth == 0) {
+ found = 1;
+ SETSTATE_LOCATE;
+ } else if (! BUFFERFULL(format)) {
+ strncat(format, &c, 1);
+ } else {
+ errx(EX_OSFILE, "%s:%d: %s %zd %s", pagedb_path,
+ lineno, "page definition exceeds",
+ sizeof(format) - 1, "characters");
+ }
+ break;
+
+ default:
+ ; /* NOTREACHED */
+ }
+
+ /* Repeat processing loop with next character. */
+ }
+
+ if (ferror(pagedb))
+ err(EX_OSFILE, "%s", pagedb_path);
+
+ /* Close the SCSI page database. */
+ fclose(pagedb);
+
+ if (!found) /* Never found a matching page. */
+ returnerr(ESRCH);
+
+ return (0);
+}
+
+static void
+editlist_populate(struct cam_device *device, int modepage, int page_control,
+ int dbd, int retries, int timeout)
+{
+ u_int8_t data[MAX_COMMAND_SIZE];/* Buffer to hold sense data. */
+ u_int8_t *mode_pars; /* Pointer to modepage params. */
+ struct scsi_mode_header_6 *mh; /* Location of mode header. */
+ struct scsi_mode_page_header *mph;
+
+ STAILQ_INIT(&editlist);
+
+ /* Fetch changeable values; use to build initial editlist. */
+ mode_sense(device, modepage, 1, dbd, retries, timeout, data,
+ sizeof(data));
+
+ mh = (struct scsi_mode_header_6 *)data;
+ mph = MODE_PAGE_HEADER(mh);
+ mode_pars = MODE_PAGE_DATA(mph);
+
+ /* Decode the value data, creating edit_entries for each value. */
+ buff_decode_visit(mode_pars, mh->data_length, format,
+ editentry_create, 0);
+
+ /* Fetch the current/saved values; use to set editentry values. */
+ mode_sense(device, modepage, page_control, dbd, retries, timeout, data,
+ sizeof(data));
+ buff_decode_visit(mode_pars, mh->data_length, format,
+ editentry_update, 0);
+}
+
+static void
+editlist_save(struct cam_device *device, int modepage, int page_control,
+ int dbd, int retries, int timeout)
+{
+ u_int8_t data[MAX_COMMAND_SIZE];/* Buffer to hold sense data. */
+ u_int8_t *mode_pars; /* Pointer to modepage params. */
+ struct scsi_mode_header_6 *mh; /* Location of mode header. */
+ struct scsi_mode_page_header *mph;
+
+ /* Make sure that something changed before continuing. */
+ if (! editlist_changed)
+ return;
+
+ /*
+ * Preload the CDB buffer with the current mode page data.
+ * XXX If buff_encode_visit would return the number of bytes encoded
+ * we *should* use that to build a header from scratch. As it is
+ * now, we need mode_sense to find out the page length.
+ */
+ mode_sense(device, modepage, page_control, dbd, retries, timeout, data,
+ sizeof(data));
+
+ /* Initial headers & offsets. */
+ mh = (struct scsi_mode_header_6 *)data;
+ mph = MODE_PAGE_HEADER(mh);
+ mode_pars = MODE_PAGE_DATA(mph);
+
+ /* Encode the value data to be passed back to the device. */
+ buff_encode_visit(mode_pars, mh->data_length, format,
+ editentry_save, 0);
+
+ /* Eliminate block descriptors. */
+ bcopy(mph, ((u_int8_t *)mh) + sizeof(*mh),
+ sizeof(*mph) + mph->page_length);
+
+ /* Recalculate headers & offsets. */
+ mh->blk_desc_len = 0; /* No block descriptors. */
+ mh->dev_spec = 0; /* Clear device-specific parameters. */
+ mph = MODE_PAGE_HEADER(mh);
+ mode_pars = MODE_PAGE_DATA(mph);
+
+ mph->page_code &= SMS_PAGE_CODE;/* Isolate just the page code. */
+ mh->data_length = 0; /* Reserved for MODE SELECT command. */
+
+ /*
+ * Write the changes back to the device. If the user editted control
+ * page 3 (saved values) then request the changes be permanently
+ * recorded.
+ */
+ mode_select(device,
+ (page_control << PAGE_CTRL_SHIFT == SMS_PAGE_CTRL_SAVED),
+ retries, timeout, (u_int8_t *)mh,
+ sizeof(*mh) + mh->blk_desc_len + sizeof(*mph) + mph->page_length);
+}
+
+static int
+modepage_write(FILE *file, int editonly)
+{
+ struct editentry *scan;
+ int written = 0;
+
+ STAILQ_FOREACH(scan, &editlist, link) {
+ if (scan->editable || !editonly) {
+ written++;
+ if (scan->type == 'c' || scan->type == 'z') {
+ fprintf(file, "%s: %s\n", scan->name,
+ scan->value.svalue);
+ } else {
+ fprintf(file, "%s: %d\n", scan->name,
+ scan->value.ivalue);
+ }
+ }
+ }
+ return (written);
+}
+
+static int
+modepage_read(FILE *file)
+{
+ char *buffer; /* Pointer to dynamic line buffer. */
+ char *line; /* Pointer to static fgetln buffer. */
+ char *name; /* Name portion of the line buffer. */
+ char *value; /* Value portion of line buffer. */
+ size_t length; /* Length of static fgetln buffer. */
+
+#define ABORT_READ(message, param) do { \
+ warnx(message, param); \
+ free(buffer); \
+ returnerr(EAGAIN); \
+} while (0)
+
+ while ((line = fgetln(file, &length)) != NULL) {
+ /* Trim trailing whitespace (including optional newline). */
+ while (length > 0 && isspace(line[length - 1]))
+ length--;
+
+ /* Allocate a buffer to hold the line + terminating null. */
+ if ((buffer = malloc(length + 1)) == NULL)
+ err(EX_OSERR, NULL);
+ memcpy(buffer, line, length);
+ buffer[length] = '\0';
+
+ /* Strip out comments. */
+ if ((value = strchr(buffer, '#')) != NULL)
+ *value = '\0';
+
+ /* The name is first in the buffer. Trim whitespace.*/
+ name = buffer;
+ RTRIM(name);
+ while (isspace(*name))
+ name++;
+
+ /* Skip empty lines. */
+ if (strlen(name) == 0)
+ continue;
+
+ /* The name ends at the colon; the value starts there. */
+ if ((value = strrchr(buffer, ':')) == NULL)
+ ABORT_READ("no value associated with %s", name);
+ *value = '\0'; /* Null-terminate name. */
+ value++; /* Value starts afterwards. */
+
+ /* Trim leading and trailing whitespace. */
+ RTRIM(value);
+ while (isspace(*value))
+ value++;
+
+ /* Make sure there is a value left. */
+ if (strlen(value) == 0)
+ ABORT_READ("no value associated with %s", name);
+
+ /* Update our in-memory copy of the modepage entry value. */
+ if (editentry_set(name, value, 1) != 0) {
+ if (errno == ENOENT) {
+ /* No entry by the name. */
+ ABORT_READ("no such modepage entry \"%s\"",
+ name);
+ } else if (errno == EINVAL) {
+ /* Invalid value. */
+ ABORT_READ("Invalid value for entry \"%s\"",
+ name);
+ } else if (errno == ERANGE) {
+ /* Value out of range for entry type. */
+ ABORT_READ("value out of range for %s", name);
+ } else if (errno == EPERM) {
+ /* Entry is not editable; not fatal. */
+ warnx("modepage entry \"%s\" is read-only; "
+ "skipping.", name);
+ }
+ }
+
+ free(buffer);
+ }
+ return (ferror(file)? -1: 0);
+
+#undef ABORT_READ
+}
+
+static void
+modepage_edit(void)
+{
+ const char *editor;
+ char *commandline;
+ int fd;
+ int written;
+
+ if (!isatty(fileno(stdin))) {
+ /* Not a tty, read changes from stdin. */
+ modepage_read(stdin);
+ return;
+ }
+
+ /* Lookup editor to invoke. */
+ if ((editor = getenv("EDITOR")) == NULL)
+ editor = DEFAULT_EDITOR;
+
+ /* Create temp file for editor to modify. */
+ if ((fd = mkstemp(edit_path)) == -1)
+ errx(EX_CANTCREAT, "mkstemp failed");
+
+ atexit(cleanup_editfile);
+
+ if ((edit_file = fdopen(fd, "w")) == NULL)
+ err(EX_NOINPUT, "%s", edit_path);
+
+ written = modepage_write(edit_file, 1);
+
+ fclose(edit_file);
+ edit_file = NULL;
+
+ if (written == 0) {
+ warnx("no editable entries");
+ cleanup_editfile();
+ return;
+ }
+
+ /*
+ * Allocate memory to hold the command line (the 2 extra characters
+ * are to hold the argument separator (a space), and the terminating
+ * null character.
+ */
+ commandline = malloc(strlen(editor) + strlen(edit_path) + 2);
+ if (commandline == NULL)
+ err(EX_OSERR, NULL);
+ sprintf(commandline, "%s %s", editor, edit_path);
+
+ /* Invoke the editor on the temp file. */
+ if (system(commandline) == -1)
+ err(EX_UNAVAILABLE, "could not invoke %s", editor);
+ free(commandline);
+
+ if ((edit_file = fopen(edit_path, "r")) == NULL)
+ err(EX_NOINPUT, "%s", edit_path);
+
+ /* Read any changes made to the temp file. */
+ modepage_read(edit_file);
+
+ cleanup_editfile();
+}
+
+static void
+modepage_dump(struct cam_device *device, int page, int page_control, int dbd,
+ int retries, int timeout)
+{
+ u_int8_t data[MAX_COMMAND_SIZE];/* Buffer to hold sense data. */
+ u_int8_t *mode_pars; /* Pointer to modepage params. */
+ struct scsi_mode_header_6 *mh; /* Location of mode header. */
+ struct scsi_mode_page_header *mph;
+ int indx; /* Index for scanning mode params. */
+
+ mode_sense(device, page, page_control, dbd, retries, timeout, data,
+ sizeof(data));
+
+ mh = (struct scsi_mode_header_6 *)data;
+ mph = MODE_PAGE_HEADER(mh);
+ mode_pars = MODE_PAGE_DATA(mph);
+
+ /* Print the raw mode page data with newlines each 8 bytes. */
+ for (indx = 0; indx < mph->page_length; indx++) {
+ printf("%02x%c",mode_pars[indx],
+ (((indx + 1) % 8) == 0) ? '\n' : ' ');
+ }
+ putchar('\n');
+}
+
+static void
+cleanup_editfile(void)
+{
+ if (edit_file == NULL)
+ return;
+ if (fclose(edit_file) != 0 || unlink(edit_path) != 0)
+ warn("%s", edit_path);
+ edit_file = NULL;
+}
+
+void
+mode_edit(struct cam_device *device, int page, int page_control, int dbd,
+ int edit, int binary, int retry_count, int timeout)
+{
+ const char *pagedb_path; /* Path to modepage database. */
+
+ if (edit && binary)
+ errx(EX_USAGE, "cannot edit in binary mode.");
+
+ if (! binary) {
+ if ((pagedb_path = getenv("SCSI_MODES")) == NULL)
+ pagedb_path = DEFAULT_SCSI_MODE_DB;
+
+ if (load_format(pagedb_path, page) != 0 && (edit || verbose)) {
+ if (errno == ENOENT) {
+ /* Modepage database file not found. */
+ warn("cannot open modepage database \"%s\"",
+ pagedb_path);
+ } else if (errno == ESRCH) {
+ /* Modepage entry not found in database. */
+ warnx("modepage %d not found in database"
+ "\"%s\"", page, pagedb_path);
+ }
+ /* We can recover in display mode, otherwise we exit. */
+ if (!edit) {
+ warnx("reverting to binary display only");
+ binary = 1;
+ } else
+ exit(EX_OSFILE);
+ }
+
+ editlist_populate(device, page, page_control, dbd, retry_count,
+ timeout);
+ }
+
+ if (edit) {
+ if (page_control << PAGE_CTRL_SHIFT != SMS_PAGE_CTRL_CURRENT &&
+ page_control << PAGE_CTRL_SHIFT != SMS_PAGE_CTRL_SAVED)
+ errx(EX_USAGE, "it only makes sense to edit page 0 "
+ "(current) or page 3 (saved values)");
+ modepage_edit();
+ editlist_save(device, page, page_control, dbd, retry_count,
+ timeout);
+ } else if (binary || STAILQ_EMPTY(&editlist)) {
+ /* Display without formatting information. */
+ modepage_dump(device, page, page_control, dbd, retry_count,
+ timeout);
+ } else {
+ /* Display with format. */
+ modepage_write(stdout, 0);
+ }
+}
+
+void
+mode_list(struct cam_device *device, int page_control, int dbd,
+ int retry_count, int timeout)
+{
+ u_int8_t data[MAX_COMMAND_SIZE];/* Buffer to hold sense data. */
+ struct scsi_mode_header_6 *mh; /* Location of mode header. */
+ struct scsi_mode_page_header *mph;
+ struct pagename *nameentry;
+ const char *pagedb_path;
+ int len;
+
+ if ((pagedb_path = getenv("SCSI_MODES")) == NULL)
+ pagedb_path = DEFAULT_SCSI_MODE_DB;
+
+ if (load_format(pagedb_path, 0) != 0 && verbose && errno == ENOENT) {
+ /* Modepage database file not found. */
+ warn("cannot open modepage database \"%s\"", pagedb_path);
+ }
+
+ /* Build the list of all mode pages by querying the "all pages" page. */
+ mode_sense(device, SMS_ALL_PAGES_PAGE, page_control, dbd, retry_count,
+ timeout, data, sizeof(data));
+
+ mh = (struct scsi_mode_header_6 *)data;
+ len = sizeof(*mh) + mh->blk_desc_len; /* Skip block descriptors. */
+ /* Iterate through the pages in the reply. */
+ while (len < mh->data_length) {
+ /* Locate the next mode page header. */
+ mph = (struct scsi_mode_page_header *)
+ ((intptr_t)mh + len);
+
+ mph->page_code &= SMS_PAGE_CODE;
+ nameentry = nameentry_lookup(mph->page_code);
+
+ if (nameentry == NULL || nameentry->name == NULL)
+ printf("0x%02x\n", mph->page_code);
+ else
+ printf("0x%02x\t%s\n", mph->page_code,
+ nameentry->name);
+ len += mph->page_length + sizeof(*mph);
+ }
+}
diff --git a/sbin/camcontrol/persist.c b/sbin/camcontrol/persist.c
new file mode 100644
index 0000000..bcc1073
--- /dev/null
+++ b/sbin/camcontrol/persist.c
@@ -0,0 +1,966 @@
+/*-
+ * Copyright (c) 2013 Spectra Logic Corporation
+ * 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,
+ * without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ * substantially similar to the "NO WARRANTY" disclaimer below
+ * ("Disclaimer") and any redistribution must be conditioned upon
+ * including a substantially similar Disclaimer requirement for further
+ * binary redistribution.
+ *
+ * NO WARRANTY
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES.
+ *
+ * Authors: Ken Merry (Spectra Logic Corporation)
+ */
+/*
+ * SCSI Persistent Reservation support for camcontrol(8).
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/ioctl.h>
+#include <sys/stdint.h>
+#include <sys/types.h>
+#include <sys/endian.h>
+#include <sys/sbuf.h>
+#include <sys/queue.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <unistd.h>
+#include <string.h>
+#include <strings.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <limits.h>
+#include <err.h>
+
+#include <cam/cam.h>
+#include <cam/cam_debug.h>
+#include <cam/cam_ccb.h>
+#include <cam/scsi/scsi_all.h>
+#include <cam/scsi/scsi_pass.h>
+#include <cam/scsi/scsi_message.h>
+#include <camlib.h>
+#include "camcontrol.h"
+
+struct persist_transport_id {
+ struct scsi_transportid_header *hdr;
+ unsigned int alloc_len;
+ STAILQ_ENTRY(persist_transport_id) links;
+};
+
+/*
+ * Service Actions for PERSISTENT RESERVE IN.
+ */
+static struct scsi_nv persist_in_actions[] = {
+ { "read_keys", SPRI_RK },
+ { "read_reservation", SPRI_RR },
+ { "report_capabilities", SPRI_RC },
+ { "read_full_status", SPRI_RS }
+};
+
+/*
+ * Service Actions for PERSISTENT RESERVE OUT.
+ */
+static struct scsi_nv persist_out_actions[] = {
+ { "register", SPRO_REGISTER },
+ { "reserve", SPRO_RESERVE },
+ { "release" , SPRO_RELEASE },
+ { "clear", SPRO_CLEAR },
+ { "preempt", SPRO_PREEMPT },
+ { "preempt_abort", SPRO_PRE_ABO },
+ { "register_ignore", SPRO_REG_IGNO },
+ { "register_move", SPRO_REG_MOVE },
+ { "replace_lost", SPRO_REPL_LOST_RES }
+};
+
+/*
+ * Known reservation scopes. As of SPC-4, only LU_SCOPE is used in the
+ * spec. The others are obsolete.
+ */
+static struct scsi_nv persist_scope_table[] = {
+ { "lun", SPR_LU_SCOPE },
+ { "extent", SPR_EXTENT_SCOPE },
+ { "element", SPR_ELEMENT_SCOPE }
+};
+
+/*
+ * Reservation types. The longer name for a given reservation type is
+ * listed first, so that it makes more sense when we print out the
+ * reservation type. We step through the table linearly when looking for
+ * the text name for a particular numeric reservation type value.
+ */
+static struct scsi_nv persist_type_table[] = {
+ { "read_shared", SPR_TYPE_RD_SHARED },
+ { "write_exclusive", SPR_TYPE_WR_EX },
+ { "wr_ex", SPR_TYPE_WR_EX },
+ { "read_exclusive", SPR_TYPE_RD_EX },
+ { "rd_ex", SPR_TYPE_RD_EX },
+ { "exclusive_access", SPR_TYPE_EX_AC },
+ { "ex_ac", SPR_TYPE_EX_AC },
+ { "write_exclusive_reg_only", SPR_TYPE_WR_EX_RO },
+ { "wr_ex_ro", SPR_TYPE_WR_EX_RO },
+ { "exclusive_access_reg_only", SPR_TYPE_EX_AC_RO },
+ { "ex_ac_ro", SPR_TYPE_EX_AC_RO },
+ { "write_exclusive_all_regs", SPR_TYPE_WR_EX_AR },
+ { "wr_ex_ar", SPR_TYPE_WR_EX_AR },
+ { "exclusive_access_all_regs", SPR_TYPE_EX_AC_AR },
+ { "ex_ac_ar", SPR_TYPE_EX_AC_AR }
+};
+
+/*
+ * Print out the standard scope/type field.
+ */
+static void
+persist_print_scopetype(uint8_t scopetype)
+{
+ const char *tmpstr;
+ int num_entries;
+
+ num_entries = sizeof(persist_scope_table) /
+ sizeof(persist_scope_table[0]);
+ tmpstr = scsi_nv_to_str(persist_scope_table, num_entries,
+ scopetype & SPR_SCOPE_MASK);
+ fprintf(stdout, "Scope: %s (%#x)\n", (tmpstr != NULL) ? tmpstr :
+ "Unknown", (scopetype & SPR_SCOPE_MASK) >> SPR_SCOPE_SHIFT);
+
+ num_entries = sizeof(persist_type_table) /
+ sizeof(persist_type_table[0]);
+ tmpstr = scsi_nv_to_str(persist_type_table, num_entries,
+ scopetype & SPR_TYPE_MASK);
+ fprintf(stdout, "Type: %s (%#x)\n", (tmpstr != NULL) ? tmpstr :
+ "Unknown", scopetype & SPR_TYPE_MASK);
+}
+
+static void
+persist_print_transportid(uint8_t *buf, uint32_t len)
+{
+ struct sbuf *sb;
+
+ sb = sbuf_new_auto();
+ if (sb == NULL)
+ fprintf(stderr, "Unable to allocate sbuf\n");
+
+ scsi_transportid_sbuf(sb, (struct scsi_transportid_header *)buf, len);
+
+ sbuf_finish(sb);
+
+ fprintf(stdout, "%s\n", sbuf_data(sb));
+
+ sbuf_delete(sb);
+}
+
+/*
+ * Print out a persistent reservation. This is used with the READ
+ * RESERVATION (0x01) service action of the PERSISTENT RESERVE IN command.
+ */
+static void
+persist_print_res(struct scsi_per_res_in_header *hdr, uint32_t valid_len)
+{
+ uint32_t length;
+ struct scsi_per_res_in_rsrv *res;
+
+ length = scsi_4btoul(hdr->length);
+ length = MIN(length, valid_len);
+
+ res = (struct scsi_per_res_in_rsrv *)hdr;
+
+ if (length < sizeof(res->data) - sizeof(res->data.extent_length)) {
+ if (length == 0)
+ fprintf(stdout, "No reservations.\n");
+ else
+ warnx("unable to print reservation, only got %u "
+ "valid bytes", length);
+ return;
+ }
+ fprintf(stdout, "PRgeneration: %#x\n",
+ scsi_4btoul(res->header.generation));
+ fprintf(stdout, "Reservation Key: %#jx\n",
+ (uintmax_t)scsi_8btou64(res->data.reservation));
+ fprintf(stdout, "Scope address: %#x\n",
+ scsi_4btoul(res->data.scope_addr));
+
+ persist_print_scopetype(res->data.scopetype);
+
+ fprintf(stdout, "Extent length: %u\n",
+ scsi_2btoul(res->data.extent_length));
+}
+
+/*
+ * Print out persistent reservation keys. This is used with the READ KEYS
+ * service action of the PERSISTENT RESERVE IN command.
+ */
+static void
+persist_print_keys(struct scsi_per_res_in_header *hdr, uint32_t valid_len)
+{
+ uint32_t length, num_keys, i;
+ struct scsi_per_res_key *key;
+
+ length = scsi_4btoul(hdr->length);
+ length = MIN(length, valid_len);
+
+ num_keys = length / sizeof(*key);
+
+ fprintf(stdout, "PRgeneration: %#x\n", scsi_4btoul(hdr->generation));
+ fprintf(stdout, "%u key%s%s\n", num_keys, (num_keys == 1) ? "" : "s",
+ (num_keys == 0) ? "." : ":");
+
+ for (i = 0, key = (struct scsi_per_res_key *)&hdr[1]; i < num_keys;
+ i++, key++) {
+ fprintf(stdout, "%u: %#jx\n", i,
+ (uintmax_t)scsi_8btou64(key->key));
+ }
+}
+
+/*
+ * Print out persistent reservation capabilities. This is used with the
+ * REPORT CAPABILITIES service action of the PERSISTENT RESERVE IN command.
+ */
+static void
+persist_print_cap(struct scsi_per_res_cap *cap, uint32_t valid_len)
+{
+ uint32_t length;
+ int check_type_mask = 0;
+
+ length = scsi_2btoul(cap->length);
+ length = MIN(length, valid_len);
+
+ if (length < __offsetof(struct scsi_per_res_cap, type_mask)) {
+ fprintf(stdout, "Insufficient data (%u bytes) to report "
+ "full capabilities\n", length);
+ return;
+ }
+ if (length >= __offsetof(struct scsi_per_res_cap, reserved))
+ check_type_mask = 1;
+
+ fprintf(stdout, "Replace Lost Reservation Capable (RLR_C): %d\n",
+ (cap->flags1 & SPRI_RLR_C) ? 1 : 0);
+ fprintf(stdout, "Compatible Reservation Handling (CRH): %d\n",
+ (cap->flags1 & SPRI_CRH) ? 1 : 0);
+ fprintf(stdout, "Specify Initiator Ports Capable (SIP_C): %d\n",
+ (cap->flags1 & SPRI_SIP_C) ? 1 : 0);
+ fprintf(stdout, "All Target Ports Capable (ATP_C): %d\n",
+ (cap->flags1 & SPRI_ATP_C) ? 1 : 0);
+ fprintf(stdout, "Persist Through Power Loss Capable (PTPL_C): %d\n",
+ (cap->flags1 & SPRI_PTPL_C) ? 1 : 0);
+ fprintf(stdout, "ALLOW COMMANDS field: (%#x)\n",
+ (cap->flags2 & SPRI_ALLOW_CMD_MASK) >> SPRI_ALLOW_CMD_SHIFT);
+ /*
+ * These cases are cut-and-pasted from SPC4r36l. There is no
+ * succinct way to describe these otherwise, and even with the
+ * verbose description, the user will probably have to refer to
+ * the spec to fully understand what is going on.
+ */
+ switch (cap->flags2 & SPRI_ALLOW_CMD_MASK) {
+ case SPRI_ALLOW_1:
+ fprintf(stdout,
+" The device server allows the TEST UNIT READY command through Write\n"
+" Exclusive type reservations and Exclusive Access type reservations\n"
+" and does not provide information about whether the following commands\n"
+" are allowed through Write Exclusive type reservations:\n"
+" a) the MODE SENSE command, READ ATTRIBUTE command, READ BUFFER\n"
+" command, RECEIVE COPY RESULTS command, RECEIVE DIAGNOSTIC\n"
+" RESULTS command, REPORT SUPPORTED OPERATION CODES command,\n"
+" and REPORT SUPPORTED TASK MANAGEMENT FUNCTION command; and\n"
+" b) the READ DEFECT DATA command (see SBC-3).\n");
+ break;
+ case SPRI_ALLOW_2:
+ fprintf(stdout,
+" The device server allows the TEST UNIT READY command through Write\n"
+" Exclusive type reservations and Exclusive Access type reservations\n"
+" and does not allow the following commands through Write Exclusive type\n"
+" reservations:\n"
+" a) the MODE SENSE command, READ ATTRIBUTE command, READ BUFFER\n"
+" command, RECEIVE DIAGNOSTIC RESULTS command, REPORT SUPPORTED\n"
+" OPERATION CODES command, and REPORT SUPPORTED TASK MANAGEMENT\n"
+" FUNCTION command; and\n"
+" b) the READ DEFECT DATA command.\n"
+" The device server does not allow the RECEIVE COPY RESULTS command\n"
+" through Write Exclusive type reservations or Exclusive Access type\n"
+" reservations.\n");
+ break;
+ case SPRI_ALLOW_3:
+ fprintf(stdout,
+" The device server allows the TEST UNIT READY command through Write\n"
+" Exclusive type reservations and Exclusive Access type reservations\n"
+" and allows the following commands through Write Exclusive type\n"
+" reservations:\n"
+" a) the MODE SENSE command, READ ATTRIBUTE command, READ BUFFER\n"
+" command, RECEIVE DIAGNOSTIC RESULTS command, REPORT SUPPORTED\n"
+" OPERATION CODES command, and REPORT SUPPORTED TASK MANAGEMENT\n"
+" FUNCTION command; and\n"
+" b) the READ DEFECT DATA command.\n"
+" The device server does not allow the RECEIVE COPY RESULTS command\n"
+" through Write Exclusive type reservations or Exclusive Access type\n"
+" reservations.\n");
+ break;
+ case SPRI_ALLOW_4:
+ fprintf(stdout,
+" The device server allows the TEST UNIT READY command and the RECEIVE\n"
+" COPY RESULTS command through Write Exclusive type reservations and\n"
+" Exclusive Access type reservations and allows the following commands\n"
+" through Write Exclusive type reservations:\n"
+" a) the MODE SENSE command, READ ATTRIBUTE command, READ BUFFER\n"
+" command, RECEIVE DIAGNOSTIC RESULTS command, REPORT SUPPORTED\n"
+" OPERATION CODES command, and REPORT SUPPORTED TASK MANAGEMENT\n"
+" FUNCTION command; and\n"
+" b) the READ DEFECT DATA command.\n");
+ break;
+ case SPRI_ALLOW_NA:
+ fprintf(stdout,
+" No information is provided about whether certain commands are allowed\n"
+" through certain types of persistent reservations.\n");
+ break;
+ default:
+ fprintf(stdout,
+" Unknown ALLOW COMMANDS value %#x\n",
+ (cap->flags2 & SPRI_ALLOW_CMD_MASK) >>
+ SPRI_ALLOW_CMD_SHIFT);
+ break;
+ }
+ fprintf(stdout, "Persist Through Power Loss Activated (PTPL_A): %d\n",
+ (cap->flags2 & SPRI_PTPL_A) ? 1 : 0);
+ if ((check_type_mask != 0)
+ && (cap->flags2 & SPRI_TMV)) {
+ fprintf(stdout, "Supported Persistent Reservation Types:\n");
+ fprintf(stdout, " Write Exclusive - All Registrants "
+ "(WR_EX_AR): %d\n",
+ (cap->type_mask[0] & SPRI_TM_WR_EX_AR)? 1 : 0);
+ fprintf(stdout, " Exclusive Access - Registrants Only "
+ "(EX_AC_RO): %d\n",
+ (cap->type_mask[0] & SPRI_TM_EX_AC_RO) ? 1 : 0);
+ fprintf(stdout, " Write Exclusive - Registrants Only "
+ "(WR_EX_RO): %d\n",
+ (cap->type_mask[0] & SPRI_TM_WR_EX_RO)? 1 : 0);
+ fprintf(stdout, " Exclusive Access (EX_AC): %d\n",
+ (cap->type_mask[0] & SPRI_TM_EX_AC) ? 1 : 0);
+ fprintf(stdout, " Write Exclusive (WR_EX): %d\n",
+ (cap->type_mask[0] & SPRI_TM_WR_EX) ? 1 : 0);
+ fprintf(stdout, " Exclusive Access - All Registrants "
+ "(EX_AC_AR): %d\n",
+ (cap->type_mask[1] & SPRI_TM_EX_AC_AR) ? 1 : 0);
+ } else {
+ fprintf(stdout, "Persistent Reservation Type Mask is NOT "
+ "valid\n");
+ }
+
+
+}
+
+static void
+persist_print_full(struct scsi_per_res_in_header *hdr, uint32_t valid_len)
+{
+ uint32_t length, len_to_go = 0;
+ struct scsi_per_res_in_full_desc *desc;
+ uint8_t *cur_pos;
+ int i;
+
+ length = scsi_4btoul(hdr->length);
+ length = MIN(length, valid_len);
+
+ if (length < sizeof(*desc)) {
+ if (length == 0)
+ fprintf(stdout, "No reservations.\n");
+ else
+ warnx("unable to print reservation, only got %u "
+ "valid bytes", length);
+ return;
+ }
+
+ fprintf(stdout, "PRgeneration: %#x\n", scsi_4btoul(hdr->generation));
+ cur_pos = (uint8_t *)&hdr[1];
+ for (len_to_go = length, i = 0,
+ desc = (struct scsi_per_res_in_full_desc *)cur_pos;
+ len_to_go >= sizeof(*desc);
+ desc = (struct scsi_per_res_in_full_desc *)cur_pos, i++) {
+ uint32_t additional_length, cur_length;
+
+
+ fprintf(stdout, "Reservation Key: %#jx\n",
+ (uintmax_t)scsi_8btou64(desc->res_key.key));
+ fprintf(stdout, "All Target Ports (ALL_TG_PT): %d\n",
+ (desc->flags & SPRI_FULL_ALL_TG_PT) ? 1 : 0);
+ fprintf(stdout, "Reservation Holder (R_HOLDER): %d\n",
+ (desc->flags & SPRI_FULL_R_HOLDER) ? 1 : 0);
+
+ if (desc->flags & SPRI_FULL_R_HOLDER)
+ persist_print_scopetype(desc->scopetype);
+
+ if ((desc->flags & SPRI_FULL_ALL_TG_PT) == 0)
+ fprintf(stdout, "Relative Target Port ID: %#x\n",
+ scsi_2btoul(desc->rel_trgt_port_id));
+
+ additional_length = scsi_4btoul(desc->additional_length);
+
+ persist_print_transportid(desc->transport_id,
+ additional_length);
+
+ cur_length = sizeof(*desc) + additional_length;
+ len_to_go -= cur_length;
+ cur_pos += cur_length;
+ }
+}
+
+int
+scsipersist(struct cam_device *device, int argc, char **argv, char *combinedopt,
+ int retry_count, int timeout, int verbosemode, int err_recover)
+{
+ union ccb *ccb = NULL;
+ int c, in = 0, out = 0;
+ int action = -1, num_ids = 0;
+ int error = 0;
+ uint32_t res_len = 0;
+ unsigned long rel_tgt_port = 0;
+ uint8_t *res_buf = NULL;
+ int scope = SPR_LU_SCOPE, res_type = 0, key_set = 0, sa_key_set = 0;
+ struct persist_transport_id *id, *id2;
+ STAILQ_HEAD(, persist_transport_id) transport_id_list;
+ uint64_t key = 0, sa_key = 0;
+ struct scsi_nv *table = NULL;
+ size_t table_size = 0, id_len = 0;
+ uint32_t valid_len = 0;
+ int all_tg_pt = 0, aptpl = 0, spec_i_pt = 0, unreg = 0,rel_port_set = 0;
+
+ STAILQ_INIT(&transport_id_list);
+
+ ccb = cam_getccb(device);
+ if (ccb == NULL) {
+ warnx("%s: error allocating CCB", __func__);
+ error = 1;
+ goto bailout;
+ }
+
+ bzero(&(&ccb->ccb_h)[1],
+ sizeof(union ccb) - sizeof(struct ccb_hdr));
+
+ while ((c = getopt(argc, argv, combinedopt)) != -1) {
+ switch (c) {
+ case 'a':
+ all_tg_pt = 1;
+ break;
+ case 'I': {
+ int error_str_len = 128;
+ char error_str[error_str_len];
+ char *id_str;
+
+ id = malloc(sizeof(*id));
+ if (id == NULL) {
+ warnx("%s: error allocating %zu bytes",
+ __func__, sizeof(*id));
+ error = 1;
+ goto bailout;
+ }
+ bzero(id, sizeof(*id));
+
+ id_str = strdup(optarg);
+ if (id_str == NULL) {
+ warnx("%s: error duplicating string %s",
+ __func__, optarg);
+ free(id);
+ error = 1;
+ goto bailout;
+ }
+ error = scsi_parse_transportid(id_str, &id->hdr,
+ &id->alloc_len, error_str, error_str_len);
+ if (error != 0) {
+ warnx("%s", error_str);
+ error = 1;
+ free(id);
+ free(id_str);
+ goto bailout;
+ }
+ free(id_str);
+
+ STAILQ_INSERT_TAIL(&transport_id_list, id, links);
+ num_ids++;
+ id_len += id->alloc_len;
+ break;
+ }
+ case 'k':
+ case 'K': {
+ char *endptr;
+ uint64_t tmpval;
+
+ tmpval = strtoumax(optarg, &endptr, 0);
+ if (*endptr != '\0') {
+ warnx("%s: invalid key argument %s", __func__,
+ optarg);
+ error = 1;
+ goto bailout;
+ }
+ if (c == 'k') {
+ key = tmpval;
+ key_set = 1;
+ } else {
+ sa_key = tmpval;
+ sa_key_set = 1;
+ }
+ break;
+ }
+ case 'i':
+ case 'o': {
+ scsi_nv_status status;
+ int table_entry = 0;
+
+ if (c == 'i') {
+ in = 1;
+ table = persist_in_actions;
+ table_size = sizeof(persist_in_actions) /
+ sizeof(persist_in_actions[0]);
+ } else {
+ out = 1;
+ table = persist_out_actions;
+ table_size = sizeof(persist_out_actions) /
+ sizeof(persist_out_actions[0]);
+ }
+
+ if ((in + out) > 1) {
+ warnx("%s: only one in (-i) or out (-o) "
+ "action is allowed", __func__);
+ error = 1;
+ goto bailout;
+ }
+
+ status = scsi_get_nv(table, table_size, optarg,
+ &table_entry,SCSI_NV_FLAG_IG_CASE);
+ if (status == SCSI_NV_FOUND)
+ action = table[table_entry].value;
+ else {
+ warnx("%s: %s %s option %s", __func__,
+ (status == SCSI_NV_AMBIGUOUS) ?
+ "ambiguous" : "invalid", in ? "in" :
+ "out", optarg);
+ error = 1;
+ goto bailout;
+ }
+ break;
+ }
+ case 'p':
+ aptpl = 1;
+ break;
+ case 'R': {
+ char *endptr;
+
+ rel_tgt_port = strtoul(optarg, &endptr, 0);
+ if (*endptr != '\0') {
+ warnx("%s: invalid relative target port %s",
+ __func__, optarg);
+ error = 1;
+ goto bailout;
+ }
+ rel_port_set = 1;
+ break;
+ }
+ case 's': {
+ size_t scope_size;
+ struct scsi_nv *scope_table = NULL;
+ scsi_nv_status status;
+ int table_entry = 0;
+ char *endptr;
+
+ /*
+ * First check to see if the user gave us a numeric
+ * argument. If so, we'll try using it.
+ */
+ if (isdigit(optarg[0])) {
+ scope = strtol(optarg, &endptr, 0);
+ if (*endptr != '\0') {
+ warnx("%s: invalid scope %s",
+ __func__, optarg);
+ error = 1;
+ goto bailout;
+ }
+ scope = (scope << SPR_SCOPE_SHIFT) &
+ SPR_SCOPE_MASK;
+ break;
+ }
+
+ scope_size = sizeof(persist_scope_table) /
+ sizeof(persist_scope_table[0]);
+ scope_table = persist_scope_table;
+ status = scsi_get_nv(scope_table, scope_size, optarg,
+ &table_entry,SCSI_NV_FLAG_IG_CASE);
+ if (status == SCSI_NV_FOUND)
+ scope = scope_table[table_entry].value;
+ else {
+ warnx("%s: %s scope %s", __func__,
+ (status == SCSI_NV_AMBIGUOUS) ?
+ "ambiguous" : "invalid", optarg);
+ error = 1;
+ goto bailout;
+ }
+ break;
+ }
+ case 'S':
+ spec_i_pt = 1;
+ break;
+ case 'T': {
+ size_t res_type_size;
+ struct scsi_nv *rtype_table = NULL;
+ scsi_nv_status status;
+ char *endptr;
+ int table_entry = 0;
+
+ /*
+ * First check to see if the user gave us a numeric
+ * argument. If so, we'll try using it.
+ */
+ if (isdigit(optarg[0])) {
+ res_type = strtol(optarg, &endptr, 0);
+ if (*endptr != '\0') {
+ warnx("%s: invalid reservation type %s",
+ __func__, optarg);
+ error = 1;
+ goto bailout;
+ }
+ break;
+ }
+
+ res_type_size = sizeof(persist_type_table) /
+ sizeof(persist_type_table[0]);
+ rtype_table = persist_type_table;
+ status = scsi_get_nv(rtype_table, res_type_size,
+ optarg, &table_entry,
+ SCSI_NV_FLAG_IG_CASE);
+ if (status == SCSI_NV_FOUND)
+ res_type = rtype_table[table_entry].value;
+ else {
+ warnx("%s: %s reservation type %s", __func__,
+ (status == SCSI_NV_AMBIGUOUS) ?
+ "ambiguous" : "invalid", optarg);
+ error = 1;
+ goto bailout;
+ }
+ break;
+ }
+ case 'U':
+ unreg = 1;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if ((in + out) != 1) {
+ warnx("%s: you must specify one of -i or -o", __func__);
+ error = 1;
+ goto bailout;
+ }
+
+ /*
+ * Note that we don't really try to figure out whether the user
+ * needs to specify one or both keys. There are a number of
+ * scenarios, and sometimes 0 is a valid and desired value.
+ */
+ if (in != 0) {
+ switch (action) {
+ case SPRI_RK:
+ case SPRI_RR:
+ case SPRI_RS:
+ /*
+ * Allocate the maximum length possible for these
+ * service actions. According to the spec, the
+ * target is supposed to return the available
+ * length in the header, regardless of the
+ * allocation length. In practice, though, with
+ * the READ FULL STATUS (SPRI_RS) service action,
+ * some Seagate drives (in particular a
+ * Constellation ES, <SEAGATE ST32000444SS 0006>)
+ * don't return the available length if you only
+ * allocate the length of the header. So just
+ * allocate the maximum here so we don't miss
+ * anything.
+ */
+ res_len = SPRI_MAX_LEN;
+ break;
+ case SPRI_RC:
+ res_len = sizeof(struct scsi_per_res_cap);
+ break;
+ default:
+ /* In theory we should catch this above */
+ warnx("%s: invalid action %d", __func__, action);
+ error = 1;
+ goto bailout;
+ break;
+ }
+ } else {
+
+ /*
+ * XXX KDM need to add length for transport IDs for the
+ * register and move service action and the register
+ * service action with the SPEC_I_PT bit set.
+ */
+ if (action == SPRO_REG_MOVE) {
+ if (num_ids != 1) {
+ warnx("%s: register and move requires a "
+ "single transport ID (-I)", __func__);
+ error = 1;
+ goto bailout;
+ }
+ if (rel_port_set == 0) {
+ warnx("%s: register and move requires a "
+ "relative target port (-R)", __func__);
+ error = 1;
+ goto bailout;
+ }
+ res_len = sizeof(struct scsi_per_res_reg_move) + id_len;
+ } else {
+ res_len = sizeof(struct scsi_per_res_out_parms);
+ if ((action == SPRO_REGISTER)
+ && (num_ids != 0)) {
+ /*
+ * If the user specifies any IDs with the
+ * register service action, turn on the
+ * spec_i_pt bit.
+ */
+ spec_i_pt = 1;
+ res_len += id_len;
+ res_len +=
+ sizeof(struct scsi_per_res_out_trans_ids);
+ }
+ }
+ }
+retry:
+ if (res_buf != NULL) {
+ free(res_buf);
+ res_buf = NULL;
+ }
+ res_buf = malloc(res_len);
+ if (res_buf == NULL) {
+ warn("%s: error allocating %d bytes", __func__, res_len);
+ error = 1;
+ goto bailout;
+ }
+ bzero(res_buf, res_len);
+
+ if (in != 0) {
+ scsi_persistent_reserve_in(&ccb->csio,
+ /*retries*/ retry_count,
+ /*cbfcnp*/ NULL,
+ /*tag_action*/ MSG_SIMPLE_Q_TAG,
+ /*service_action*/ action,
+ /*data_ptr*/ res_buf,
+ /*dxfer_len*/ res_len,
+ /*sense_len*/ SSD_FULL_SIZE,
+ /*timeout*/ timeout ? timeout :5000);
+
+ } else {
+ switch (action) {
+ case SPRO_REGISTER:
+ if (spec_i_pt != 0) {
+ struct scsi_per_res_out_trans_ids *id_hdr;
+ uint8_t *bufptr;
+
+ bufptr = res_buf +
+ sizeof(struct scsi_per_res_out_parms) +
+ sizeof(struct scsi_per_res_out_trans_ids);
+ STAILQ_FOREACH(id, &transport_id_list, links) {
+ bcopy(id->hdr, bufptr, id->alloc_len);
+ bufptr += id->alloc_len;
+ }
+ id_hdr = (struct scsi_per_res_out_trans_ids *)
+ (res_buf +
+ sizeof(struct scsi_per_res_out_parms));
+ scsi_ulto4b(id_len, id_hdr->additional_length);
+ }
+ case SPRO_REG_IGNO:
+ case SPRO_PREEMPT:
+ case SPRO_PRE_ABO:
+ case SPRO_RESERVE:
+ case SPRO_RELEASE:
+ case SPRO_CLEAR:
+ case SPRO_REPL_LOST_RES: {
+ struct scsi_per_res_out_parms *parms;
+
+ parms = (struct scsi_per_res_out_parms *)res_buf;
+
+ scsi_u64to8b(key, parms->res_key.key);
+ scsi_u64to8b(sa_key, parms->serv_act_res_key);
+ if (spec_i_pt != 0)
+ parms->flags |= SPR_SPEC_I_PT;
+ if (all_tg_pt != 0)
+ parms->flags |= SPR_ALL_TG_PT;
+ if (aptpl != 0)
+ parms->flags |= SPR_APTPL;
+ break;
+ }
+ case SPRO_REG_MOVE: {
+ struct scsi_per_res_reg_move *reg_move;
+ uint8_t *bufptr;
+
+ reg_move = (struct scsi_per_res_reg_move *)res_buf;
+
+ scsi_u64to8b(key, reg_move->res_key.key);
+ scsi_u64to8b(sa_key, reg_move->serv_act_res_key);
+ if (unreg != 0)
+ reg_move->flags |= SPR_REG_MOVE_UNREG;
+ if (aptpl != 0)
+ reg_move->flags |= SPR_REG_MOVE_APTPL;
+ scsi_ulto2b(rel_tgt_port, reg_move->rel_trgt_port_id);
+ id = STAILQ_FIRST(&transport_id_list);
+ /*
+ * This shouldn't happen, since we already checked
+ * the number of IDs above.
+ */
+ if (id == NULL) {
+ warnx("%s: No transport IDs found!", __func__);
+ error = 1;
+ goto bailout;
+ }
+ bufptr = (uint8_t *)&reg_move[1];
+ bcopy(id->hdr, bufptr, id->alloc_len);
+ scsi_ulto4b(id->alloc_len,
+ reg_move->transport_id_length);
+ break;
+ }
+ default:
+ break;
+ }
+ scsi_persistent_reserve_out(&ccb->csio,
+ /*retries*/ retry_count,
+ /*cbfcnp*/ NULL,
+ /*tag_action*/ MSG_SIMPLE_Q_TAG,
+ /*service_action*/ action,
+ /*scope*/ scope,
+ /*res_type*/ res_type,
+ /*data_ptr*/ res_buf,
+ /*dxfer_len*/ res_len,
+ /*sense_len*/ SSD_FULL_SIZE,
+ /*timeout*/ timeout ?timeout :5000);
+ }
+
+ /* Disable freezing the device queue */
+ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
+
+ if (err_recover != 0)
+ ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER;
+
+ if (cam_send_ccb(device, ccb) < 0) {
+ warn("error sending PERSISTENT RESERVE %s", (in != 0) ?
+ "IN" : "OUT");
+
+ if (verbosemode != 0) {
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ }
+
+ error = 1;
+ goto bailout;
+ }
+
+ if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
+ if (verbosemode != 0) {
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ }
+ error = 1;
+ goto bailout;
+ }
+
+ if (in == 0)
+ goto bailout;
+
+ valid_len = res_len - ccb->csio.resid;
+
+ switch (action) {
+ case SPRI_RK:
+ case SPRI_RR:
+ case SPRI_RS: {
+ struct scsi_per_res_in_header *hdr;
+ uint32_t hdr_len;
+
+ if (valid_len < sizeof(*hdr)) {
+ warnx("%s: only got %d valid bytes, need %zd",
+ __func__, valid_len, sizeof(*hdr));
+ error = 1;
+ goto bailout;
+ }
+ hdr = (struct scsi_per_res_in_header *)res_buf;
+ hdr_len = scsi_4btoul(hdr->length);
+
+ if (hdr_len > (res_len - sizeof(*hdr))) {
+ res_len = hdr_len + sizeof(*hdr);
+ goto retry;
+ }
+
+ if (action == SPRI_RK) {
+ persist_print_keys(hdr, valid_len);
+ } else if (action == SPRI_RR) {
+ persist_print_res(hdr, valid_len);
+ } else {
+ persist_print_full(hdr, valid_len);
+ }
+ break;
+ }
+ case SPRI_RC: {
+ struct scsi_per_res_cap *cap;
+ uint32_t cap_len;
+
+ if (valid_len < sizeof(*cap)) {
+ warnx("%s: only got %u valid bytes, need %zd",
+ __func__, valid_len, sizeof(*cap));
+ error = 1;
+ goto bailout;
+ }
+ cap = (struct scsi_per_res_cap *)res_buf;
+ cap_len = scsi_2btoul(cap->length);
+ if (cap_len != sizeof(*cap)) {
+ /*
+ * We should be able to deal with this,
+ * it's just more trouble.
+ */
+ warnx("%s: reported size %u is different "
+ "than expected size %zd", __func__,
+ cap_len, sizeof(*cap));
+ }
+
+ /*
+ * If there is more data available, grab it all,
+ * even though we don't really know what to do with
+ * the extra data since it obviously wasn't in the
+ * spec when this code was written.
+ */
+ if (cap_len > res_len) {
+ res_len = cap_len;
+ goto retry;
+ }
+ persist_print_cap(cap, valid_len);
+ break;
+ }
+ default:
+ break;
+ }
+
+bailout:
+ free(res_buf);
+
+ if (ccb != NULL)
+ cam_freeccb(ccb);
+
+ STAILQ_FOREACH_SAFE(id, &transport_id_list, links, id2) {
+ STAILQ_REMOVE(&transport_id_list, id, persist_transport_id,
+ links);
+ free(id);
+ }
+ return (error);
+}
diff --git a/sbin/camcontrol/progress.c b/sbin/camcontrol/progress.c
new file mode 100644
index 0000000..dc6109f
--- /dev/null
+++ b/sbin/camcontrol/progress.c
@@ -0,0 +1,186 @@
+/* $NetBSD: progressbar.c,v 1.21 2009/04/12 10:18:52 lukem Exp $ */
+
+/*-
+ * Copyright (c) 1997-2009 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Luke Mewburn.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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 <sys/types.h>
+#include <sys/param.h>
+#include <sys/ioctl.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include "progress.h"
+
+static const char * const suffixes[] = {
+ "", /* 2^0 (byte) */
+ "KiB", /* 2^10 Kibibyte */
+ "MiB", /* 2^20 Mebibyte */
+ "GiB", /* 2^30 Gibibyte */
+ "TiB", /* 2^40 Tebibyte */
+ "PiB", /* 2^50 Pebibyte */
+ "EiB", /* 2^60 Exbibyte */
+};
+
+#define NSUFFIXES (sizeof(suffixes) / sizeof(suffixes[0]))
+#define SECSPERHOUR (60 * 60)
+#define DEFAULT_TTYWIDTH 80
+
+/* initialise progress meter structure */
+int
+progress_init(progress_t *prog, const char *prefix, uint64_t total)
+{
+ struct winsize winsize;
+ int oerrno = errno;
+
+ (void) memset(prog, 0x0, sizeof(*prog));
+ prog->size = total;
+ prog->prefix = strdup(prefix);
+ prog->start = time(NULL);
+ if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &winsize) != -1 &&
+ winsize.ws_col != 0) {
+ prog->ttywidth = winsize.ws_col;
+ } else {
+ prog->ttywidth = DEFAULT_TTYWIDTH;
+ }
+ errno = oerrno;
+ return 1;
+}
+
+/* update the values in the progress meter */
+int
+progress_update(progress_t *prog, uint64_t done)
+{
+ prog->done = done;
+ prog->percent = (prog->done * 100) / prog->size;
+ prog->now = time(NULL);
+ prog->elapsed = prog->now - prog->start;
+ if (done == 0 || prog->elapsed == 0 || prog->done / prog->elapsed == 0) {
+ prog->eta = 0;
+ } else {
+ prog->eta = prog->size / (prog->done / prog->elapsed) - prog->elapsed;
+ }
+ return 1;
+}
+
+/* update the values in the progress meter */
+int
+progress_reset_size(progress_t *prog, uint64_t size)
+{
+ prog->size = size;
+ return 1;
+}
+
+/* make it look pretty at the end - display done bytes (usually total) */
+int
+progress_complete(progress_t *prog, uint64_t done)
+{
+ progress_update(prog, done);
+ progress_draw(prog);
+ printf("\n");
+ return 1;
+}
+
+/* draw the progress meter */
+int
+progress_draw(progress_t *prog)
+{
+#define BAROVERHEAD 45 /* non `*' portion of progress bar */
+ /*
+ * stars should contain at least
+ * sizeof(buf) - BAROVERHEAD entries
+ */
+ static const char stars[] =
+"*****************************************************************************"
+"*****************************************************************************"
+"*****************************************************************************";
+ unsigned bytesabbrev;
+ unsigned bpsabbrev;
+ int64_t secs;
+ uint64_t bytespersec;
+ uint64_t abbrevsize;
+ int64_t secsleft;
+ size_t barlength;
+ size_t starc;
+ char hours[12];
+ char buf[256];
+ int len;
+
+ barlength = MIN(sizeof(buf) - 1, (unsigned)prog->ttywidth) - BAROVERHEAD - strlen(prog->prefix);
+ starc = (barlength * prog->percent) / 100;
+ abbrevsize = prog->done;
+ for (bytesabbrev = 0; abbrevsize >= 100000 && bytesabbrev < NSUFFIXES; bytesabbrev++) {
+ abbrevsize >>= 10;
+ }
+ if (bytesabbrev == NSUFFIXES) {
+ bytesabbrev--;
+ }
+ bytespersec = 0;
+ if (prog->done > 0) {
+ bytespersec = prog->done;
+ if (prog->elapsed > 0) {
+ bytespersec /= prog->elapsed;
+ }
+ }
+ for (bpsabbrev = 1; bytespersec >= 1024000 && bpsabbrev < NSUFFIXES; bpsabbrev++) {
+ bytespersec >>= 10;
+ }
+ if (prog->done == 0 || prog->elapsed <= 0 || prog->done > prog->size) {
+ secsleft = 0;
+ } else {
+ secsleft = prog->eta;
+ }
+ if ((secs = secsleft / SECSPERHOUR) > 0) {
+ (void) snprintf(hours, sizeof(hours), "%2lld:", (long long)secs);
+ } else {
+ (void) snprintf(hours, sizeof(hours), " ");
+ }
+ secs = secsleft % SECSPERHOUR;
+ len = snprintf(buf, sizeof(buf),
+ "\r%s %3lld%% |%.*s%*s| %5lld %-3s %3lld.%02d %.2sB/s %s%02d:%02d ETA",
+ (prog->prefix) ? prog->prefix : "",
+ (long long)prog->percent,
+ (int)starc, stars, (int)(barlength - starc), "",
+ (long long)abbrevsize,
+ suffixes[bytesabbrev],
+ (long long)(bytespersec / 1024),
+ (int)((bytespersec % 1024) * 100 / 1024),
+ suffixes[bpsabbrev],
+ hours,
+ (int)secs / 60, (int)secs % 60);
+ return (int)write(STDOUT_FILENO, buf, len);
+}
diff --git a/sbin/camcontrol/progress.h b/sbin/camcontrol/progress.h
new file mode 100644
index 0000000..a457c32
--- /dev/null
+++ b/sbin/camcontrol/progress.h
@@ -0,0 +1,60 @@
+/* $NetBSD: progressbar.c,v 1.21 2009/04/12 10:18:52 lukem Exp $ */
+
+/*-
+ * Copyright (c) 1997-2012 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Luke Mewburn.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef PROGRESS_H_
+#define PROGRESS_H_ 20100228
+
+#include <sys/types.h>
+
+#include <inttypes.h>
+
+/* structure used to display a progress meter */
+typedef struct progress_t {
+ char *prefix; /* any prefix explanation */
+ uint64_t size; /* total of bytes/units to be counted */
+ uint64_t done; /* number of units counted to date */
+ uint64_t percent; /* cache the percentage complete */
+ time_t start; /* time we started this */
+ time_t now; /* time now */
+ time_t eta; /* estimated # of secs until completion */
+ int64_t elapsed; /* cached # of elapsed seconds */
+ int32_t ttywidth; /* width of tty in columns */
+} progress_t;
+
+int progress_init(progress_t */*meter*/, const char */*prefix*/, uint64_t /*size*/);
+int progress_update(progress_t */*meter*/, uint64_t /*done*/);
+int progress_draw(progress_t */*meter*/);
+int progress_reset_size(progress_t */*meter*/, uint64_t /*size*/);
+int progress_complete(progress_t */*meter*/, uint64_t /*done*/);
+
+#endif
diff --git a/sbin/camcontrol/util.c b/sbin/camcontrol/util.c
new file mode 100644
index 0000000..3609281
--- /dev/null
+++ b/sbin/camcontrol/util.c
@@ -0,0 +1,184 @@
+/*
+ * Written By Julian ELischer
+ * Copyright julian Elischer 1993.
+ * Permission is granted to use or redistribute this file in any way as long
+ * as this notice remains. Julian Elischer does not guarantee that this file
+ * is totally correct for any given task and users of this file must
+ * accept responsibility for any damage that occurs from the application of this
+ * file.
+ *
+ * (julian@tfs.com julian@dialix.oz.au)
+ *
+ * User SCSI hooks added by Peter Dufault:
+ *
+ * Copyright (c) 1994 HD Associates
+ * (contact: dufault@hda.com)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of HD Associates
+ * may not be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY HD ASSOCIATES ``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 HD ASSOCIATES BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+/*
+ * Taken from the original scsi(8) program.
+ * from: scsi.c,v 1.17 1998/01/12 07:57:57 charnier Exp $";
+ */
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/stdint.h>
+#include <sys/types.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <camlib.h>
+#include "camcontrol.h"
+
+int verbose;
+
+/* iget: Integer argument callback
+ */
+int
+iget(void *hook, char *name)
+{
+ struct get_hook *h = (struct get_hook *)hook;
+ int arg;
+
+ if (h->got >= h->argc)
+ {
+ fprintf(stderr, "Expecting an integer argument.\n");
+ usage(0);
+ exit(1);
+ }
+ arg = strtol(h->argv[h->got], 0, 0);
+ h->got++;
+
+ if (verbose && name && *name)
+ printf("%s: %d\n", name, arg);
+
+ return arg;
+}
+
+/* cget: char * argument callback
+ */
+char *
+cget(void *hook, char *name)
+{
+ struct get_hook *h = (struct get_hook *)hook;
+ char *arg;
+
+ if (h->got >= h->argc)
+ {
+ fprintf(stderr, "Expecting a character pointer argument.\n");
+ usage(0);
+ exit(1);
+ }
+ arg = h->argv[h->got];
+ h->got++;
+
+ if (verbose && name)
+ printf("cget: %s: %s", name, arg);
+
+ return arg;
+}
+
+/* arg_put: "put argument" callback
+ */
+void
+arg_put(void *hook __unused, int letter, void *arg, int count, char *name)
+{
+ if (verbose && name && *name)
+ printf("%s: ", name);
+
+ switch(letter)
+ {
+ case 'i':
+ case 'b':
+ printf("%jd ", (intmax_t)(intptr_t)arg);
+ break;
+
+ case 'c':
+ case 'z':
+ {
+ char *p;
+
+ p = malloc(count + 1);
+ if (p == NULL) {
+ fprintf(stderr, "can't malloc memory for p\n");
+ exit(1);
+ }
+
+ bzero(p, count +1);
+ strncpy(p, (char *)arg, count);
+ if (letter == 'z')
+ {
+ int i;
+ for (i = count - 1; i >= 0; i--)
+ if (p[i] == ' ')
+ p[i] = 0;
+ else
+ break;
+ }
+ printf("%s ", p);
+
+ free(p);
+ }
+
+ break;
+
+ default:
+ printf("Unknown format letter: '%c'\n", letter);
+ }
+ if (verbose)
+ putchar('\n');
+}
+
+/*
+ * Get confirmation from user
+ * Return values:
+ * 1: confirmed
+ * 0: unconfirmed
+ */
+int
+get_confirmation(void)
+{
+ char str[1024];
+ int response = -1;
+
+ do {
+ fprintf(stdout, "Are you SURE you want to do this? (yes/no) ");
+ if (fgets(str, sizeof(str), stdin) != NULL) {
+ if (strncasecmp(str, "yes", 3) == 0)
+ response = 1;
+ else if (strncasecmp(str, "no", 2) == 0)
+ response = 0;
+ else
+ fprintf(stdout,
+ "Please answer \"yes\" or \"no\"\n");
+ } else
+ response = 0;
+ } while (response == -1);
+ return (response);
+}
diff --git a/sbin/casperd/Makefile b/sbin/casperd/Makefile
new file mode 100644
index 0000000..93f145c
--- /dev/null
+++ b/sbin/casperd/Makefile
@@ -0,0 +1,17 @@
+# $FreeBSD$
+
+PROG= casperd
+
+SRCS= casperd.c zygote.c
+
+LIBADD= casper nv pjdlog util
+
+MAN= casperd.8
+
+CFLAGS+=-I${.CURDIR}
+CFLAGS+=-I${.CURDIR}/../../lib/libcapsicum
+CFLAGS+=-I${.CURDIR}/../../lib/libcasper
+CFLAGS+=-I${.CURDIR}/../../lib/libnv
+CFLAGS+=-I${.CURDIR}/../../lib/libpjdlog
+
+.include <bsd.prog.mk>
diff --git a/sbin/casperd/casperd.8 b/sbin/casperd/casperd.8
new file mode 100644
index 0000000..d60b9f3
--- /dev/null
+++ b/sbin/casperd/casperd.8
@@ -0,0 +1,126 @@
+.\" Copyright (c) 2013 The FreeBSD Foundation
+.\" All rights reserved.
+.\"
+.\" This documentation was written by Pawel Jakub Dawidek under sponsorship
+.\" from the FreeBSD Foundation.
+.\"
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd October 26, 2013
+.Dt CASPERD 8
+.Os
+.Sh NAME
+.Nm casperd
+.Nd "Capability Services friendly daemon"
+.Sh SYNOPSIS
+.Nm
+[-Fhv] [-D servconfdir] [-P pidfile] [-S sockpath]
+.Op Fl Fhv
+.Op Fl D Ar servconfdir
+.Op Fl P Ar pidfile
+.Op Fl S Ar sockpath
+.Sh DESCRIPTION
+The
+.Nm
+daemon hosts various services that can be accessed through
+libcapsicum's capabilities by programs running in sandboxes.
+For example it is prohibited to send UDP packets to arbitrary destinations
+when operating in capability mode, which makes DNS resolution impossible.
+To make it possible the
+.Nm
+daemon provides the
+.Nm system.dns
+service that proxies DNS resolution requests through a dedicated,
+non-sandboxed process provided by
+.Nm .
+.Pp
+The
+.Nm
+daemon can be started with the following command line arguments:
+.Bl -tag -width ".Fl D Ar servconfdir"
+.It Fl D Ar servconfdir
+Specify alternative location of the service configuration directory.
+The default location is
+.Pa /etc/casper/ .
+.It Fl F
+Start the
+.Nm
+daemon in the foreground.
+By default
+.Nm
+starts in the background.
+.It Fl h
+Print the
+.Nm
+usage message.
+.It Fl P Ar pidfile
+Specify alternative location of a file where main process PID will be
+stored.
+The default location is
+.Pa /var/run/casperd.pid .
+.It Fl S Ar sockpath
+Specify alternative location of the
+.Xr unix 4
+domain socket used to connect to the
+.Nm
+daemon.
+The default location is
+.Pa /var/run/casper .
+.It Fl v
+Print or log verbose/debugging information.
+This option can be specified multiple times to raise the verbosity
+level.
+.El
+.Sh FILES
+.Bl -tag -width ".Pa /var/run/casperd.pid" -compact
+.It Pa /etc/casper/
+The configuration directory for
+.Nm
+services.
+.It Pa /var/run/casper
+.Xr unix 4
+domain socket used to connect to the
+.Nm
+daemon.
+.It Pa /var/run/casperd.pid
+The default location of the
+.Nm
+PID file.
+.El
+.Sh EXIT STATUS
+The
+.Nm
+daemon exits 0 on success, and >0 if an error occurs.
+.Sh SEE ALSO
+.Xr cap_enter 2 ,
+.Xr libcapsicum 3 ,
+.Xr pidfile 3 ,
+.Xr capsicum 4 ,
+.Xr unix 4
+.Sh AUTHORS
+The
+.Nm
+was implemented by
+.An Pawel Jakub Dawidek Aq Mt pawel@dawidek.net
+under sponsorship from the FreeBSD Foundation.
diff --git a/sbin/casperd/casperd.c b/sbin/casperd/casperd.c
new file mode 100644
index 0000000..f838811
--- /dev/null
+++ b/sbin/casperd/casperd.c
@@ -0,0 +1,715 @@
+/*-
+ * Copyright (c) 2012 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/capsicum.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+
+#include <assert.h>
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libutil.h>
+#include <paths.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+
+#include <libcapsicum.h>
+#include <libcapsicum_impl.h>
+#include <libcasper.h>
+#include <libcasper_impl.h>
+#include <msgio.h>
+#include <nv.h>
+#include <pjdlog.h>
+
+#include "msgio.h"
+
+#include "zygote.h"
+
+#define CASPERD_PIDFILE "/var/run/casperd.pid"
+#define CASPERD_SERVCONFDIR "/etc/casper"
+#define CASPERD_SOCKPATH "/var/run/casper"
+
+typedef void service_function_t(struct service_connection *, const nvlist_t *,
+ nvlist_t *);
+
+struct casper_service {
+ const char *cs_name;
+ const char *cs_execpath;
+ struct service *cs_service;
+ nvlist_t *cs_attrs;
+ TAILQ_ENTRY(casper_service) cs_next;
+};
+
+static TAILQ_HEAD(, casper_service) casper_services =
+ TAILQ_HEAD_INITIALIZER(casper_services);
+
+#define SERVICE_IS_CORE(service) ((service)->cs_execpath == NULL)
+
+static void service_external_execute(int chanfd);
+
+#define KEEP_ERRNO(work) do { \
+ int _serrno; \
+ \
+ _serrno = errno; \
+ work; \
+ errno = _serrno; \
+} while (0)
+
+static struct casper_service *
+service_find(const char *name)
+{
+ struct casper_service *casserv;
+
+ TAILQ_FOREACH(casserv, &casper_services, cs_next) {
+ if (strcmp(casserv->cs_name, name) == 0)
+ break;
+ }
+ return (casserv);
+}
+
+/*
+ * Function always consumes the given attrs.
+ */
+static void
+service_register(nvlist_t *attrs)
+{
+ struct casper_service *casserv;
+ const char *name;
+
+ PJDLOG_ASSERT(nvlist_exists_string(attrs, "name"));
+ PJDLOG_ASSERT(nvlist_exists_string(attrs, "execpath") ||
+ (nvlist_exists_number(attrs, "commandfunc") &&
+ nvlist_exists_number(attrs, "limitfunc")));
+
+ name = nvlist_get_string(attrs, "name");
+ PJDLOG_ASSERT(name != NULL);
+ if (name[0] == '\0') {
+ pjdlog_error("Unable to register service with an empty name.");
+ nvlist_destroy(attrs);
+ return;
+ }
+ if (service_find(name) != NULL) {
+ pjdlog_error("Service \"%s\" is already registered.", name);
+ nvlist_destroy(attrs);
+ return;
+ }
+
+ casserv = malloc(sizeof(*casserv));
+ if (casserv == NULL) {
+ pjdlog_errno(LOG_ERR, "Unable to register service \"%s\"",
+ name);
+ nvlist_destroy(attrs);
+ return;
+ }
+ casserv->cs_name = name;
+ if (nvlist_exists_string(attrs, "execpath")) {
+ struct stat sb;
+
+ PJDLOG_ASSERT(!nvlist_exists_number(attrs, "commandfunc"));
+ PJDLOG_ASSERT(!nvlist_exists_number(attrs, "limitfunc"));
+
+ casserv->cs_service = NULL;
+
+ casserv->cs_execpath = nvlist_get_string(attrs, "execpath");
+ if (casserv->cs_execpath == NULL ||
+ casserv->cs_execpath[0] == '\0') {
+ pjdlog_error("Unable to register service with an empty execpath.");
+ free(casserv);
+ nvlist_destroy(attrs);
+ return;
+ }
+ if (stat(casserv->cs_execpath, &sb) == -1) {
+ pjdlog_errno(LOG_ERR,
+ "Unable to register service \"%s\", problem with executable \"%s\"",
+ name, casserv->cs_execpath);
+ free(casserv);
+ nvlist_destroy(attrs);
+ return;
+ }
+ } else /* if (nvlist_exists_number(attrs, "commandfunc")) */ {
+ PJDLOG_ASSERT(!nvlist_exists_string(attrs, "execpath"));
+
+ casserv->cs_execpath = NULL;
+
+ casserv->cs_service = service_alloc(name,
+ (void *)(uintptr_t)nvlist_get_number(attrs, "limitfunc"),
+ (void *)(uintptr_t)nvlist_get_number(attrs, "commandfunc"));
+ if (casserv->cs_service == NULL) {
+ pjdlog_errno(LOG_ERR,
+ "Unable to register service \"%s\"", name);
+ free(casserv);
+ nvlist_destroy(attrs);
+ return;
+ }
+ }
+ casserv->cs_attrs = attrs;
+ TAILQ_INSERT_TAIL(&casper_services, casserv, cs_next);
+ pjdlog_debug(1, "Service %s successfully registered.",
+ casserv->cs_name);
+}
+
+static bool
+casper_allowed_service(const nvlist_t *limits, const char *service)
+{
+
+ if (limits == NULL)
+ return (true);
+
+ if (nvlist_exists_null(limits, service))
+ return (true);
+
+ return (false);
+}
+
+static int
+casper_limit(const nvlist_t *oldlimits, const nvlist_t *newlimits)
+{
+ const char *name;
+ int type;
+ void *cookie;
+
+ cookie = NULL;
+ while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) {
+ if (type != NV_TYPE_NULL)
+ return (EINVAL);
+ if (!casper_allowed_service(oldlimits, name))
+ return (ENOTCAPABLE);
+ }
+
+ return (0);
+}
+
+static int
+casper_command(const char *cmd, const nvlist_t *limits, nvlist_t *nvlin,
+ nvlist_t *nvlout)
+{
+ struct casper_service *casserv;
+ const char *servname;
+ nvlist_t *nvl;
+ int chanfd, execfd, procfd, error;
+
+ if (strcmp(cmd, "open") != 0)
+ return (EINVAL);
+ if (!nvlist_exists_string(nvlin, "service"))
+ return (EINVAL);
+
+ servname = nvlist_get_string(nvlin, "service");
+
+ casserv = service_find(servname);
+ if (casserv == NULL)
+ return (ENOENT);
+
+ if (!casper_allowed_service(limits, servname))
+ return (ENOTCAPABLE);
+
+#ifdef O_EXEC_WORKING
+ execfd = open(casserv->cs_execpath, O_EXEC);
+#else
+ execfd = open(casserv->cs_execpath, O_RDONLY);
+#endif
+ if (execfd < -1) {
+ error = errno;
+ pjdlog_errno(LOG_ERR,
+ "Unable to open executable '%s' of service '%s'",
+ casserv->cs_execpath, casserv->cs_name);
+ return (error);
+ }
+
+ if (zygote_clone(service_external_execute, 0, &chanfd, &procfd) == -1) {
+ error = errno;
+ close(execfd);
+ return (error);
+ }
+
+ nvl = nvlist_create(0);
+ nvlist_add_string(nvl, "service", casserv->cs_name);
+ if (nvlist_exists_descriptor(nvlin, "stderrfd")) {
+ nvlist_move_descriptor(nvl, "stderrfd",
+ nvlist_take_descriptor(nvlin, "stderrfd"));
+ }
+ nvlist_move_descriptor(nvl, "execfd", execfd);
+ nvlist_move_descriptor(nvl, "procfd", procfd);
+ if (nvlist_send(chanfd, nvl) == -1) {
+ error = errno;
+ pjdlog_errno(LOG_ERR, "Unable to send nvlist");
+ nvlist_destroy(nvl);
+ close(chanfd);
+ return (error);
+ }
+ nvlist_destroy(nvl);
+
+ nvlist_move_descriptor(nvlout, "chanfd", chanfd);
+
+ return (0);
+}
+
+static void
+fdswap(int *fd0, int *fd1)
+{
+ int tmpfd;
+
+ PJDLOG_VERIFY((tmpfd = dup(*fd0)) != -1);
+ PJDLOG_VERIFY(dup2(*fd1, *fd0) != -1);
+ PJDLOG_VERIFY(dup2(tmpfd, *fd1) != -1);
+ close(tmpfd);
+ tmpfd = *fd0;
+ *fd0 = *fd1;
+ *fd1 = tmpfd;
+}
+
+static void
+fdmove(int *oldfdp, int newfd)
+{
+
+ if (*oldfdp != newfd) {
+ PJDLOG_VERIFY(dup2(*oldfdp, newfd) != -1);
+ close(*oldfdp);
+ *oldfdp = newfd;
+ }
+}
+
+static void
+fdcloexec(int fd)
+{
+ int flags;
+
+ flags = fcntl(fd, F_GETFD);
+ PJDLOG_ASSERT(flags != -1);
+ if ((flags & FD_CLOEXEC) != 0)
+ PJDLOG_VERIFY(fcntl(fd, F_SETFD, flags & ~FD_CLOEXEC) != -1);
+}
+
+static void
+service_register_core(void)
+{
+ nvlist_t *nvl;
+
+ nvl = nvlist_create(0);
+ nvlist_add_string(nvl, "name", "core.casper");
+ nvlist_add_number(nvl, "limitfunc", (uint64_t)(uintptr_t)casper_limit);
+ nvlist_add_number(nvl, "commandfunc",
+ (uint64_t)(uintptr_t)casper_command);
+ service_register(nvl);
+}
+
+static int
+setup_creds(int sock)
+{
+ struct cmsgcred cred;
+
+ if (cred_recv(sock, &cred) == -1)
+ return (-1);
+
+ if (setgroups((int)cred.cmcred_ngroups, cred.cmcred_groups) == -1)
+ return (-1);
+
+ if (setgid(cred.cmcred_groups[0]) == -1)
+ return (-1);
+
+ if (setuid(cred.cmcred_euid) == -1)
+ return (-1);
+
+ return (0);
+}
+
+static void
+service_external_execute(int chanfd)
+{
+ char *service, *argv[3];
+ int stderrfd, execfd, procfd;
+ nvlist_t *nvl;
+
+ nvl = nvlist_recv(chanfd, 0);
+ if (nvl == NULL)
+ pjdlog_exit(1, "Unable to receive nvlist");
+ service = nvlist_take_string(nvl, "service");
+ PJDLOG_ASSERT(service != NULL);
+ if (nvlist_exists_descriptor(nvl, "stderrfd")) {
+ stderrfd = nvlist_take_descriptor(nvl, "stderrfd");
+ } else {
+ stderrfd = open(_PATH_DEVNULL, O_RDWR);
+ if (stderrfd < 0)
+ pjdlog_exit(1, "Unable to open %s", _PATH_DEVNULL);
+ }
+ execfd = nvlist_take_descriptor(nvl, "execfd");
+ procfd = nvlist_take_descriptor(nvl, "procfd");
+ nvlist_destroy(nvl);
+
+ /*
+ * Move all descriptors into right slots.
+ */
+
+ if (stderrfd != STDERR_FILENO) {
+ if (chanfd == STDERR_FILENO)
+ fdswap(&stderrfd, &chanfd);
+ else if (execfd == STDERR_FILENO)
+ fdswap(&stderrfd, &execfd);
+ else if (procfd == STDERR_FILENO)
+ fdswap(&stderrfd, &procfd);
+ fdmove(&stderrfd, STDERR_FILENO);
+ }
+ fdcloexec(stderrfd);
+
+ if (chanfd != PARENT_FILENO) {
+ if (execfd == PARENT_FILENO)
+ fdswap(&chanfd, &execfd);
+ else if (procfd == PARENT_FILENO)
+ fdswap(&chanfd, &procfd);
+ fdmove(&chanfd, PARENT_FILENO);
+ }
+ fdcloexec(chanfd);
+
+ if (execfd != EXECUTABLE_FILENO) {
+ if (procfd == EXECUTABLE_FILENO)
+ fdswap(&execfd, &procfd);
+ fdmove(&execfd, EXECUTABLE_FILENO);
+ }
+ fdcloexec(execfd);
+
+ if (procfd != PROC_FILENO)
+ fdmove(&procfd, PROC_FILENO);
+ fdcloexec(procfd);
+
+ /*
+ * Use credentials of the caller process.
+ */
+ setup_creds(chanfd);
+
+ argv[0] = service;
+ asprintf(&argv[1], "%d", pjdlog_debug_get());
+ argv[2] = NULL;
+
+ fexecve(execfd, argv, NULL);
+ pjdlog_exit(1, "Unable to execute service %s", service);
+}
+
+static void
+service_register_external_one(const char *dirpath, int dfd,
+ const char *filename)
+{
+ char execpath[FILENAME_MAX];
+ nvlist_t *nvl;
+ ssize_t done;
+ int fd;
+
+ fd = openat(dfd, filename, O_RDONLY);
+ if (fd == -1) {
+ pjdlog_errno(LOG_ERR, "Unable to open \"%s/%s\"", dirpath,
+ filename);
+ return;
+ }
+
+ done = read(fd, execpath, sizeof(execpath));
+ if (done == -1) {
+ pjdlog_errno(LOG_ERR, "Unable to read content of \"%s/%s\"",
+ dirpath, filename);
+ close(fd);
+ return;
+ }
+ close(fd);
+ if (done == sizeof(execpath)) {
+ pjdlog_error("Executable path too long in \"%s/%s\".", dirpath,
+ filename);
+ return;
+ }
+ execpath[done] = '\0';
+ while (done > 0) {
+ if (execpath[--done] == '\n')
+ execpath[done] = '\0';
+ }
+
+ nvl = nvlist_create(0);
+ nvlist_add_string(nvl, "name", filename);
+ nvlist_add_string(nvl, "execpath", execpath);
+ if (nvlist_error(nvl) != 0) {
+ pjdlog_common(LOG_ERR, 0, nvlist_error(nvl),
+ "Unable to allocate attributes for service \"%s/%s\"",
+ dirpath, filename);
+ nvlist_destroy(nvl);
+ return;
+ }
+
+ service_register(nvl);
+ /* service_register() consumed nvl. */
+}
+
+static uint8_t
+file_type(int dfd, const char *filename)
+{
+ struct stat sb;
+
+ if (fstatat(dfd, filename, &sb, AT_SYMLINK_NOFOLLOW) == -1) {
+ pjdlog_errno(LOG_ERR, "Unable to stat \"%s\"", filename);
+ return (DT_UNKNOWN);
+ }
+ return (IFTODT(sb.st_mode));
+}
+
+static void
+service_register_external(const char *dirpath)
+{
+ DIR *dirp;
+ struct dirent *dp;
+ int dfd;
+
+ dirp = opendir(dirpath);
+ if (dirp == NULL) {
+ pjdlog_errno(LOG_WARNING, "Unable to open \"%s\"", dirpath);
+ return;
+ }
+ dfd = dirfd(dirp);
+ PJDLOG_ASSERT(dfd >= 0);
+ while ((dp = readdir(dirp)) != NULL) {
+ dp->d_type = file_type(dfd, dp->d_name);
+ /* We are only interested in regular files, skip the rest. */
+ if (dp->d_type != DT_REG) {
+ pjdlog_debug(1,
+ "File \"%s/%s\" is not a regular file, skipping.",
+ dirpath, dp->d_name);
+ continue;
+ }
+ service_register_external_one(dirpath, dfd, dp->d_name);
+ }
+ closedir(dirp);
+}
+
+static void
+casper_accept(int lsock)
+{
+ struct casper_service *casserv;
+ struct service_connection *sconn;
+ int sock;
+
+ sock = accept(lsock, NULL, NULL);
+ if (sock == -1) {
+ pjdlog_errno(LOG_ERR, "Unable to accept casper connection");
+ return;
+ }
+ casserv = service_find("core.casper");
+ PJDLOG_ASSERT(casserv != NULL);
+
+ sconn = service_connection_add(casserv->cs_service, sock, NULL);
+ if (sconn == NULL) {
+ close(sock);
+ return;
+ }
+}
+
+static void
+main_loop(const char *sockpath, struct pidfh *pfh)
+{
+ fd_set fds;
+ struct sockaddr_un sun;
+ struct casper_service *casserv;
+ struct service_connection *sconn, *sconntmp;
+ int lsock, sock, maxfd, ret;
+ mode_t oldumask;
+
+ lsock = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (lsock == -1)
+ pjdlog_exit(1, "Unable to create socket");
+
+ (void)unlink(sockpath);
+
+ bzero(&sun, sizeof(sun));
+ sun.sun_family = AF_UNIX;
+ PJDLOG_VERIFY(strlcpy(sun.sun_path, sockpath, sizeof(sun.sun_path)) <
+ sizeof(sun.sun_path));
+ sun.sun_len = SUN_LEN(&sun);
+
+ oldumask = umask(S_IXUSR | S_IXGRP | S_IXOTH);
+ if (bind(lsock, (struct sockaddr *)&sun, sizeof(sun)) == -1)
+ pjdlog_exit(1, "Unable to bind to %s", sockpath);
+ (void)umask(oldumask);
+ if (listen(lsock, 8) == -1)
+ pjdlog_exit(1, "Unable to listen on %s", sockpath);
+
+ for (;;) {
+ FD_ZERO(&fds);
+ FD_SET(lsock, &fds);
+ maxfd = lsock;
+ TAILQ_FOREACH(casserv, &casper_services, cs_next) {
+ /* We handle only core services. */
+ if (!SERVICE_IS_CORE(casserv))
+ continue;
+ for (sconn = service_connection_first(casserv->cs_service);
+ sconn != NULL;
+ sconn = service_connection_next(sconn)) {
+ sock = service_connection_get_sock(sconn);
+ FD_SET(sock, &fds);
+ maxfd = sock > maxfd ? sock : maxfd;
+ }
+ }
+ maxfd++;
+
+ PJDLOG_ASSERT(maxfd <= (int)FD_SETSIZE);
+ ret = select(maxfd, &fds, NULL, NULL, NULL);
+ PJDLOG_ASSERT(ret == -1 || ret > 0); /* select() cannot timeout */
+ if (ret == -1) {
+ if (errno == EINTR)
+ continue;
+ KEEP_ERRNO((void)pidfile_remove(pfh));
+ pjdlog_exit(1, "select() failed");
+ }
+
+ if (FD_ISSET(lsock, &fds))
+ casper_accept(lsock);
+ TAILQ_FOREACH(casserv, &casper_services, cs_next) {
+ /* We handle only core services. */
+ if (!SERVICE_IS_CORE(casserv))
+ continue;
+ for (sconn = service_connection_first(casserv->cs_service);
+ sconn != NULL; sconn = sconntmp) {
+ /*
+ * Prepare for connection to be removed from
+ * the list on failure.
+ */
+ sconntmp = service_connection_next(sconn);
+ sock = service_connection_get_sock(sconn);
+ if (FD_ISSET(sock, &fds)) {
+ service_message(casserv->cs_service,
+ sconn);
+ }
+ }
+ }
+ }
+}
+
+static void
+usage(void)
+{
+
+ pjdlog_exitx(1,
+ "usage: casperd [-Fhv] [-D servconfdir] [-P pidfile] [-S sockpath]");
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct pidfh *pfh;
+ const char *pidfile, *servconfdir, *sockpath;
+ pid_t otherpid;
+ int ch, debug;
+ bool foreground;
+
+ pjdlog_init(PJDLOG_MODE_STD);
+
+ debug = 0;
+ foreground = false;
+ pidfile = CASPERD_PIDFILE;
+ servconfdir = CASPERD_SERVCONFDIR;
+ sockpath = CASPERD_SOCKPATH;
+
+ while ((ch = getopt(argc, argv, "D:FhP:S:v")) != -1) {
+ switch (ch) {
+ case 'D':
+ servconfdir = optarg;
+ break;
+ case 'F':
+ foreground = true;
+ break;
+ case 'P':
+ pidfile = optarg;
+ break;
+ case 'S':
+ sockpath = optarg;
+ break;
+ case 'v':
+ debug++;
+ break;
+ case 'h':
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 0)
+ usage();
+
+ if (!foreground)
+ pjdlog_mode_set(PJDLOG_MODE_SYSLOG);
+ pjdlog_prefix_set("(casperd) ");
+ pjdlog_debug_set(debug);
+
+ if (zygote_init() < 0)
+ pjdlog_exit(1, "Unable to create zygote process");
+
+ pfh = pidfile_open(pidfile, 0600, &otherpid);
+ if (pfh == NULL) {
+ if (errno == EEXIST) {
+ pjdlog_exitx(1, "casperd already running, pid: %jd.",
+ (intmax_t)otherpid);
+ }
+ pjdlog_errno(LOG_WARNING, "Cannot open or create pidfile %s",
+ pidfile);
+ }
+
+ if (!foreground) {
+ if (daemon(0, 0) == -1) {
+ KEEP_ERRNO((void)pidfile_remove(pfh));
+ pjdlog_exit(1, "Unable to go into background");
+ }
+ }
+
+ /* Write PID to a file. */
+ if (pidfile_write(pfh) == -1) {
+ pjdlog_errno(LOG_WARNING, "Unable to write to pidfile %s",
+ pidfile);
+ } else {
+ pjdlog_debug(1, "PID stored in %s.", pidfile);
+ }
+
+ /*
+ * Register core services.
+ */
+ service_register_core();
+ /*
+ * Register external services.
+ */
+ service_register_external(servconfdir);
+
+ /*
+ * Wait for connections.
+ */
+ main_loop(sockpath, pfh);
+}
diff --git a/sbin/casperd/zygote.c b/sbin/casperd/zygote.c
new file mode 100644
index 0000000..c460bd3
--- /dev/null
+++ b/sbin/casperd/zygote.c
@@ -0,0 +1,229 @@
+/*-
+ * Copyright (c) 2012 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/capsicum.h>
+#include <sys/procdesc.h>
+#include <sys/socket.h>
+
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <paths.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <unistd.h>
+
+#include <libcapsicum.h>
+#include <libcapsicum_impl.h>
+#include <nv.h>
+#include <pjdlog.h>
+
+#include "zygote.h"
+
+/* Zygote info. */
+static int zygote_sock = -1;
+
+static void
+stdnull(void)
+{
+ int fd;
+
+ fd = open(_PATH_DEVNULL, O_RDWR);
+ if (fd == -1)
+ errx(1, "Unable to open %s", _PATH_DEVNULL);
+
+ if (setsid() == -1)
+ errx(1, "Unable to detach from session");
+
+ if (dup2(fd, STDIN_FILENO) == -1)
+ errx(1, "Unable to cover stdin");
+ if (dup2(fd, STDOUT_FILENO) == -1)
+ errx(1, "Unable to cover stdout");
+ if (dup2(fd, STDERR_FILENO) == -1)
+ errx(1, "Unable to cover stderr");
+
+ close(fd);
+}
+
+int
+zygote_clone(zygote_func_t *func, int flags, int *chanfdp, int *procfdp)
+{
+ nvlist_t *nvl;
+ int error;
+
+ if (zygote_sock == -1) {
+ /* Zygote didn't start. */
+ errno = ENXIO;
+ return (-1);
+ }
+
+ nvl = nvlist_create(0);
+ nvlist_add_number(nvl, "func", (uint64_t)(uintptr_t)func);
+ nvlist_add_number(nvl, "flags", (uint64_t)flags);
+ nvl = nvlist_xfer(zygote_sock, nvl, 0);
+ if (nvl == NULL)
+ return (-1);
+ if (nvlist_exists_number(nvl, "error")) {
+ error = (int)nvlist_get_number(nvl, "error");
+ nvlist_destroy(nvl);
+ errno = error;
+ return (-1);
+ }
+
+ *chanfdp = nvlist_take_descriptor(nvl, "chanfd");
+ *procfdp = nvlist_take_descriptor(nvl, "procfd");
+
+ nvlist_destroy(nvl);
+ return (0);
+}
+
+/*
+ * This function creates sandboxes on-demand whoever has access to it via
+ * 'sock' socket. Function sends two descriptors to the caller: process
+ * descriptor of the sandbox and socket pair descriptor for communication
+ * between sandbox and its owner.
+ */
+static void
+zygote_main(int sock)
+{
+ int error, fd, flags, procfd;
+ int chanfd[2];
+ nvlist_t *nvlin, *nvlout;
+ zygote_func_t *func;
+ pid_t pid;
+
+ assert(sock > STDERR_FILENO);
+
+ setproctitle("zygote");
+
+ if (pjdlog_mode_get() != PJDLOG_MODE_STD)
+ stdnull();
+ for (fd = STDERR_FILENO + 1; fd < sock; fd++)
+ close(fd);
+ closefrom(sock + 1);
+
+ for (;;) {
+ nvlin = nvlist_recv(sock, 0);
+ if (nvlin == NULL) {
+ if (errno == ENOTCONN) {
+ /* Casperd exited. */
+ exit(0);
+ }
+ continue;
+ }
+ func = (zygote_func_t *)(uintptr_t)nvlist_get_number(nvlin,
+ "func");
+ flags = (int)nvlist_get_number(nvlin, "flags");
+ nvlist_destroy(nvlin);
+
+ /*
+ * Someone is requesting a new process, create one.
+ */
+ procfd = -1;
+ chanfd[0] = -1;
+ chanfd[1] = -1;
+ error = 0;
+ if (socketpair(PF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0,
+ chanfd) == -1) {
+ error = errno;
+ goto send;
+ }
+ pid = pdfork(&procfd, 0);
+ switch (pid) {
+ case -1:
+ /* Failure. */
+ error = errno;
+ break;
+ case 0:
+ /* Child. */
+ close(sock);
+ close(chanfd[0]);
+ func(chanfd[1]);
+ /* NOTREACHED */
+ exit(1);
+ default:
+ /* Parent. */
+ close(chanfd[1]);
+ break;
+ }
+send:
+ nvlout = nvlist_create(0);
+ if (error != 0) {
+ nvlist_add_number(nvlout, "error", (uint64_t)error);
+ if (chanfd[0] >= 0)
+ close(chanfd[0]);
+ if (procfd >= 0)
+ close(procfd);
+ } else {
+ nvlist_move_descriptor(nvlout, "chanfd", chanfd[0]);
+ nvlist_move_descriptor(nvlout, "procfd", procfd);
+ }
+ (void)nvlist_send(sock, nvlout);
+ nvlist_destroy(nvlout);
+ }
+ /* NOTREACHED */
+}
+
+int
+zygote_init(void)
+{
+ int serrno, sp[2];
+ pid_t pid;
+
+ if (socketpair(PF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0, sp) == -1)
+ return (-1);
+
+ pid = fork();
+ switch (pid) {
+ case -1:
+ /* Failure. */
+ serrno = errno;
+ close(sp[0]);
+ close(sp[1]);
+ errno = serrno;
+ return (-1);
+ case 0:
+ /* Child. */
+ close(sp[0]);
+ zygote_main(sp[1]);
+ /* NOTREACHED */
+ abort();
+ default:
+ /* Parent. */
+ zygote_sock = sp[0];
+ close(sp[1]);
+ return (0);
+ }
+ /* NOTREACHED */
+}
diff --git a/sbin/casperd/zygote.h b/sbin/casperd/zygote.h
new file mode 100644
index 0000000..75ef2ef
--- /dev/null
+++ b/sbin/casperd/zygote.h
@@ -0,0 +1,40 @@
+/*-
+ * Copyright (c) 2012 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _ZYGOTE_H_
+#define _ZYGOTE_H_
+
+typedef void zygote_func_t(int);
+
+int zygote_init(void);
+int zygote_clone(zygote_func_t *func, int flags, int *chanfdp, int *procfdp);
+
+#endif /* !_ZYGOTE_H_ */
diff --git a/sbin/ccdconfig/Makefile b/sbin/ccdconfig/Makefile
new file mode 100644
index 0000000..3b64daf
--- /dev/null
+++ b/sbin/ccdconfig/Makefile
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+PROG= ccdconfig
+MAN= ccdconfig.8
+
+LIBADD= geom
+
+.include <bsd.prog.mk>
diff --git a/sbin/ccdconfig/ccdconfig.8 b/sbin/ccdconfig/ccdconfig.8
new file mode 100644
index 0000000..977da81
--- /dev/null
+++ b/sbin/ccdconfig/ccdconfig.8
@@ -0,0 +1,258 @@
+.\" $NetBSD: ccdconfig.8,v 1.1.2.1 1995/11/11 02:43:33 thorpej Exp $
+.\"
+.\" Copyright (c) 1995 Jason R. Thorpe.
+.\" 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.
+.\" 4. The name of the author may not be used to endorse or promote products
+.\" derived from this software without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+.\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+.\" BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+.\" LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+.\" AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+.\" OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd October 1, 2013
+.Dt CCDCONFIG 8
+.Os
+.Sh NAME
+.Nm ccdconfig
+.Nd configuration utility for the concatenated disk driver
+.Sh SYNOPSIS
+.Nm
+.Op Fl cv
+.Ar ccd
+.Ar ileave
+.Op Ar flags
+.Ar dev ...
+.Nm
+.Fl C
+.Op Fl v
+.Op Fl f Ar config_file
+.Nm
+.Fl u
+.Op Fl v
+.Ar ccd ...
+.Nm
+.Fl U
+.Op Fl v
+.Op Fl f Ar config_file
+.Nm
+.Fl g
+.Op Ar ccd ...
+.Sh DESCRIPTION
+The
+.Nm
+utility is used to dynamically configure and unconfigure concatenated disk
+devices, or ccds.
+For more information about the ccd, see
+.Xr ccd 4 .
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl c
+Configure a ccd.
+This is the default behavior of
+.Nm .
+.It Fl C
+Configure all ccd devices listed in the ccd configuration file.
+.It Fl f Ar config_file
+When configuring or unconfiguring all devices, read the file
+.Pa config_file
+instead of the default
+.Pa /etc/ccd.conf .
+.It Fl g
+Dump the current ccd configuration in a format suitable for use as the
+ccd configuration file.
+If no arguments are specified, every configured
+ccd is dumped.
+Otherwise, the configuration of each listed ccd is dumped.
+.It Fl u
+Unconfigure a ccd.
+.It Fl U
+Unconfigure all ccd devices listed the ccd configuration file.
+.It Fl v
+Cause
+.Nm
+to be verbose.
+.El
+.Pp
+A ccd is described on the command line and in the ccd configuration
+file by the name of the ccd, the interleave factor, the ccd configuration
+flags, and a list of one or more devices.
+The flags may be represented
+as a decimal number, a hexadecimal number, a comma-separated list
+of strings, or the word
+.Dq none .
+The flags are as follows:
+.Bd -literal -offset indent
+CCDF_UNIFORM 0x02 Use uniform interleave
+CCDF_MIRROR 0x04 Support mirroring
+CCDF_NO_OFFSET 0x08 Do not use an offset
+CCDF_LINUX 0x0A Linux md(4) compatibility
+.Ed
+.Pp
+The format in the
+configuration file appears exactly as if it were entered on the command line.
+Note that on the command line and in the configuration file, the
+.Pa flags
+argument is optional.
+.Bd -literal -offset indent
+#
+# /etc/ccd.conf
+# Configuration file for concatenated disk devices
+#
+
+# ccd ileave flags component devices
+ccd0 16 none /dev/da2s1 /dev/da3s1
+.Ed
+.Pp
+The component devices need to name partitions of type
+.Li FS_BSDFFS
+(or
+.Dq 4.2BSD
+as shown by
+.Xr disklabel 8 ) .
+.Pp
+If you want to use the
+.Tn Linux
+.Xr md 4
+compatibility mode, please be sure
+to read the notes in
+.Xr ccd 4 .
+.Sh FILES
+.Bl -tag -width /etc/ccd.conf -compact
+.It Pa /etc/ccd.conf
+default ccd configuration file
+.El
+.Sh EXAMPLES
+A number of
+.Nm
+examples are shown below.
+The arguments passed
+to
+.Nm
+are exactly the same as you might place in the
+.Pa /etc/ccd.conf
+configuration file.
+The first example creates a 4-disk stripe out of
+four scsi disk partitions.
+The stripe uses a 64 sector interleave.
+The second example is an example of a complex stripe/mirror combination.
+It reads as a two disk stripe of da4 and da5 which is mirrored
+to a two disk stripe of da6 and da7.
+The last example is a simple
+mirror.
+The 2nd slice of /dev/da8 is mirrored with the 3rd slice of /dev/da9
+and assigned to ccd0.
+.Bd -literal
+# ccdconfig ccd0 64 none /dev/da0s1 /dev/da1s1 /dev/da2s1 /dev/da3s1
+# ccdconfig ccd0 128 CCDF_MIRROR /dev/da4 /dev/da5 /dev/da6 /dev/da7
+# ccdconfig ccd0 128 CCDF_MIRROR /dev/da8s2 /dev/da9s3
+.Ed
+.Pp
+The following are matching commands in
+.Tn Linux
+and
+.Fx
+to create a RAID-0 in
+.Tn Linux
+and read it from
+.Fx .
+.Bd -literal
+# Create a RAID-0 on Linux:
+mdadm --create --chunk=32 --level=0 --raid-devices=2 /dev/md0 \\
+ /dev/hda1 /dev/hdb1
+# Make the RAID-0 just created available on FreeBSD:
+ccdconfig -c /dev/ccd0 32 linux /dev/ada0s1 /dev/ada0s2
+.Ed
+.Pp
+When you create a new ccd disk you generally want to
+.Xr fdisk 8
+and
+.Xr disklabel 8
+it before doing anything else.
+Once you create the initial label you can
+edit it, adding additional partitions.
+The label itself takes up the first
+16 sectors of the ccd disk.
+If all you are doing is creating file systems
+with newfs, you do not have to worry about this as newfs will skip the
+label area.
+However, if you intend to
+.Xr dd 1
+to or from a ccd partition it is usually a good idea to construct the
+partition such that it does not overlap the label area.
+For example, if
+you have A ccd disk with 10000 sectors you might create a 'd' partition
+with offset 16 and size 9984.
+.Bd -literal
+# disklabel ccd0 > /tmp/disklabel.ccd0
+# disklabel -Rr ccd0 /tmp/disklabel.ccd0
+# disklabel -e ccd0
+.Ed
+.Pp
+The disklabeling of a ccd disk is usually a one-time affair.
+If you reboot the machine and reconfigure the ccd disk,
+the disklabel you
+had created before will still be there and not require reinitialization.
+Beware that changing any ccd parameters: interleave, flags, or the
+device list making up the ccd disk, will usually destroy any prior
+data on that ccd disk.
+If this occurs it is usually a good idea to
+reinitialize the label before [re]constructing your ccd disk.
+.Sh RECOVERY
+An error on a ccd disk is usually unrecoverable unless you are using the
+mirroring option.
+But mirroring has its own perils: It assumes that
+both copies of the data at any given sector are the same.
+This holds true
+until a write error occurs or until you replace either side of the mirror.
+This is a poor-man's mirroring implementation.
+It works well enough that if
+you begin to get disk errors you should be able to backup the ccd disk,
+replace the broken hardware, and then regenerate the ccd disk.
+If you need
+more than this you should look into external hardware RAID SCSI boxes,
+RAID controllers (see GENERIC),
+or software RAID systems such as
+.Xr geom 8
+and
+.Xr gvinum 8 .
+.Sh SEE ALSO
+.Xr dd 1 ,
+.Xr ccd 4 ,
+.Xr disklabel 8 ,
+.Xr fdisk 8 ,
+.Xr gvinum 8 ,
+.Xr rc 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Nx 1.0a .
+.Sh BUGS
+The initial disklabel returned by
+.Xr ccd 4
+specifies only 3 partitions.
+One needs to change the number of partitions to 8 using
+.Dq Nm disklabel Fl e
+to get the usual
+.Bx
+expectations.
diff --git a/sbin/ccdconfig/ccdconfig.c b/sbin/ccdconfig/ccdconfig.c
new file mode 100644
index 0000000..76867ba
--- /dev/null
+++ b/sbin/ccdconfig/ccdconfig.c
@@ -0,0 +1,451 @@
+/*
+ * Copyright (c) 2003 Poul-Henning Kamp
+ * Copyright (c) 1995 Jason R. Thorpe.
+ * 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 for the NetBSD Project
+ * by Jason R. Thorpe.
+ * 4. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/linker.h>
+#include <sys/module.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <libgeom.h>
+
+#define CCDF_UNIFORM 0x02 /* use LCCD of sizes for uniform interleave */
+#define CCDF_MIRROR 0x04 /* use mirroring */
+#define CCDF_NO_OFFSET 0x08 /* do not leave space in front */
+#define CCDF_LINUX 0x10 /* use Linux compatibility mode */
+
+#include "pathnames.h"
+
+static int lineno = 0;
+static int verbose = 0;
+static const char *ccdconf = _PATH_CCDCONF;
+
+static struct flagval {
+ const char *fv_flag;
+ int fv_val;
+} flagvaltab[] = {
+ { "CCDF_UNIFORM", CCDF_UNIFORM },
+ { "uniform", CCDF_UNIFORM },
+ { "CCDF_MIRROR", CCDF_MIRROR },
+ { "mirror", CCDF_MIRROR },
+ { "CCDF_NO_OFFSET", CCDF_NO_OFFSET },
+ { "no_offset", CCDF_NO_OFFSET },
+ { "CCDF_LINUX", CCDF_LINUX },
+ { "linux", CCDF_LINUX },
+ { "none", 0 },
+ { NULL, 0 },
+};
+
+#define CCD_CONFIG 0 /* configure a device */
+#define CCD_CONFIGALL 1 /* configure all devices */
+#define CCD_UNCONFIG 2 /* unconfigure a device */
+#define CCD_UNCONFIGALL 3 /* unconfigure all devices */
+#define CCD_DUMP 4 /* dump a ccd's configuration */
+
+static int do_single(int, char **, int);
+static int do_all(int);
+static int dump_ccd(int, char **);
+static int flags_to_val(char *);
+static int resolve_ccdname(char *);
+static void usage(void);
+
+int
+main(int argc, char *argv[])
+{
+ int ch, options = 0, action = CCD_CONFIG;
+
+ while ((ch = getopt(argc, argv, "cCf:guUv")) != -1) {
+ switch (ch) {
+ case 'c':
+ action = CCD_CONFIG;
+ ++options;
+ break;
+
+ case 'C':
+ action = CCD_CONFIGALL;
+ ++options;
+ break;
+
+ case 'f':
+ ccdconf = optarg;
+ break;
+
+ case 'g':
+ action = CCD_DUMP;
+ break;
+
+ case 'u':
+ action = CCD_UNCONFIG;
+ ++options;
+ break;
+
+ case 'U':
+ action = CCD_UNCONFIGALL;
+ ++options;
+ break;
+
+ case 'v':
+ verbose = 1;
+ break;
+
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (options > 1)
+ usage();
+
+ if (modfind("g_ccd") < 0) {
+ /* Not present in kernel, try loading it */
+ if (kldload("geom_ccd") < 0 || modfind("g_ccd") < 0)
+ warn("geom_ccd module not available!");
+ }
+
+ switch (action) {
+ case CCD_CONFIG:
+ case CCD_UNCONFIG:
+ exit(do_single(argc, argv, action));
+ /* NOTREACHED */
+
+ case CCD_CONFIGALL:
+ case CCD_UNCONFIGALL:
+ exit(do_all(action));
+ /* NOTREACHED */
+
+ case CCD_DUMP:
+ exit(dump_ccd(argc, argv));
+ /* NOTREACHED */
+ }
+ /* NOTREACHED */
+ return (0);
+}
+
+static int
+do_single(int argc, char **argv, int action)
+{
+ char *cp, *cp2;
+ int ccd, noflags = 0, i, ileave, flags = 0;
+ struct gctl_req *grq;
+ char const *errstr;
+ char buf1[BUFSIZ];
+ int ex;
+
+ /*
+ * If unconfiguring, all arguments are treated as ccds.
+ */
+ if (action == CCD_UNCONFIG || action == CCD_UNCONFIGALL) {
+ ex = 0;
+ for (; argc != 0;) {
+ cp = *argv++; --argc;
+ if ((ccd = resolve_ccdname(cp)) < 0) {
+ warnx("invalid ccd name: %s", cp);
+ continue;
+ }
+ grq = gctl_get_handle();
+ gctl_ro_param(grq, "verb", -1, "destroy geom");
+ gctl_ro_param(grq, "class", -1, "CCD");
+ sprintf(buf1, "ccd%d", ccd);
+ gctl_ro_param(grq, "geom", -1, buf1);
+ errstr = gctl_issue(grq);
+ if (errstr == NULL) {
+ if (verbose)
+ printf("%s unconfigured\n", cp);
+ gctl_free(grq);
+ continue;
+ }
+ warnx(
+ "%s\nor possibly kernel and ccdconfig out of sync",
+ errstr);
+ ex = 1;
+ }
+ return (ex);
+ }
+
+ /* Make sure there are enough arguments. */
+ if (argc < 4) {
+ if (argc == 3) {
+ /* Assume that no flags are specified. */
+ noflags = 1;
+ } else {
+ if (action == CCD_CONFIGALL) {
+ warnx("%s: bad line: %d", ccdconf, lineno);
+ return (1);
+ } else
+ usage();
+ }
+ }
+
+ /* First argument is the ccd to configure. */
+ cp = *argv++; --argc;
+ if ((ccd = resolve_ccdname(cp)) < 0) {
+ warnx("invalid ccd name: %s", cp);
+ return (1);
+ }
+
+ /* Next argument is the interleave factor. */
+ cp = *argv++; --argc;
+ errno = 0; /* to check for ERANGE */
+ ileave = (int)strtol(cp, &cp2, 10);
+ if ((errno == ERANGE) || (ileave < 0) || (*cp2 != '\0')) {
+ warnx("invalid interleave factor: %s", cp);
+ return (1);
+ }
+
+ if (noflags == 0) {
+ /* Next argument is the ccd configuration flags. */
+ cp = *argv++; --argc;
+ if ((flags = flags_to_val(cp)) < 0) {
+ warnx("invalid flags argument: %s", cp);
+ return (1);
+ }
+ }
+ grq = gctl_get_handle();
+ gctl_ro_param(grq, "verb", -1, "create geom");
+ gctl_ro_param(grq, "class", -1, "CCD");
+ gctl_ro_param(grq, "unit", sizeof(ccd), &ccd);
+ gctl_ro_param(grq, "ileave", sizeof(ileave), &ileave);
+ if (flags & CCDF_UNIFORM)
+ gctl_ro_param(grq, "uniform", -1, "");
+ if (flags & CCDF_MIRROR)
+ gctl_ro_param(grq, "mirror", -1, "");
+ if (flags & CCDF_NO_OFFSET)
+ gctl_ro_param(grq, "no_offset", -1, "");
+ if (flags & CCDF_LINUX)
+ gctl_ro_param(grq, "linux", -1, "");
+ gctl_ro_param(grq, "nprovider", sizeof(argc), &argc);
+ for (i = 0; i < argc; i++) {
+ sprintf(buf1, "provider%d", i);
+ cp = argv[i];
+ if (!strncmp(cp, _PATH_DEV, strlen(_PATH_DEV)))
+ cp += strlen(_PATH_DEV);
+ gctl_ro_param(grq, buf1, -1, cp);
+ }
+ gctl_rw_param(grq, "output", sizeof(buf1), buf1);
+ errstr = gctl_issue(grq);
+ if (errstr == NULL) {
+ if (verbose) {
+ printf("%s", buf1);
+ }
+ gctl_free(grq);
+ return (0);
+ }
+ warnx(
+ "%s\nor possibly kernel and ccdconfig out of sync",
+ errstr);
+ return (1);
+}
+
+static int
+do_all(int action)
+{
+ FILE *f;
+ char line[_POSIX2_LINE_MAX];
+ char *cp, **argv;
+ int argc, rval;
+ gid_t egid;
+
+ rval = 0;
+ egid = getegid();
+ if (setegid(getgid()) != 0)
+ err(1, "setegid failed");
+ if ((f = fopen(ccdconf, "r")) == NULL) {
+ if (setegid(egid) != 0)
+ err(1, "setegid failed");
+ warn("fopen: %s", ccdconf);
+ return (1);
+ }
+ if (setegid(egid) != 0)
+ err(1, "setegid failed");
+
+ while (fgets(line, sizeof(line), f) != NULL) {
+ argc = 0;
+ argv = NULL;
+ ++lineno;
+ if ((cp = strrchr(line, '\n')) != NULL)
+ *cp = '\0';
+
+ /* Break up the line and pass it's contents to do_single(). */
+ if (line[0] == '\0')
+ goto end_of_line;
+ for (cp = line; (cp = strtok(cp, " \t")) != NULL; cp = NULL) {
+ if (*cp == '#')
+ break;
+ if ((argv = realloc(argv,
+ sizeof(char *) * ++argc)) == NULL) {
+ warnx("no memory to configure ccds");
+ return (1);
+ }
+ argv[argc - 1] = cp;
+ /*
+ * If our action is to unconfigure all, then pass
+ * just the first token to do_single() and ignore
+ * the rest. Since this will be encountered on
+ * our first pass through the line, the Right
+ * Thing will happen.
+ */
+ if (action == CCD_UNCONFIGALL) {
+ if (do_single(argc, argv, action))
+ rval = 1;
+ goto end_of_line;
+ }
+ }
+ if (argc != 0)
+ if (do_single(argc, argv, action))
+ rval = 1;
+
+ end_of_line:
+ if (argv != NULL)
+ free(argv);
+ }
+
+ (void)fclose(f);
+ return (rval);
+}
+
+static int
+resolve_ccdname(char *name)
+{
+
+ if (!strncmp(name, _PATH_DEV, strlen(_PATH_DEV)))
+ name += strlen(_PATH_DEV);
+ if (strncmp(name, "ccd", 3))
+ return -1;
+ name += 3;
+ if (!isdigit(*name))
+ return -1;
+ return (strtoul(name, NULL, 10));
+}
+
+static int
+dumpout(int unit)
+{
+ static int v;
+ struct gctl_req *grq;
+ int ncp;
+ char *cp;
+ char const *errstr;
+
+ grq = gctl_get_handle();
+ ncp = 65536;
+ cp = malloc(ncp);
+ gctl_ro_param(grq, "verb", -1, "list");
+ gctl_ro_param(grq, "class", -1, "CCD");
+ gctl_ro_param(grq, "unit", sizeof(unit), &unit);
+ gctl_rw_param(grq, "output", ncp, cp);
+ errstr = gctl_issue(grq);
+ if (errstr != NULL)
+ errx(1, "%s\nor possibly kernel and ccdconfig out of sync",
+ errstr);
+ if (strlen(cp) == 0)
+ errx(1, "ccd%d not configured", unit);
+ if (verbose && !v) {
+ printf("# ccd\t\tileave\tflags\tcomponent devices\n");
+ v = 1;
+ }
+ printf("%s", cp);
+ free(cp);
+ return (0);
+}
+
+static int
+dump_ccd(int argc, char **argv)
+{
+ int i, error;
+
+ if (argc == 0) {
+ error = dumpout(-1);
+ } else {
+ error = 0;
+ for (i = 0; error == 0 && i < argc; i++)
+ error = dumpout(resolve_ccdname(argv[i]));
+ }
+ return (error);
+}
+
+static int
+flags_to_val(char *flags)
+{
+ char *cp, *tok;
+ int i, tmp, val;
+
+ errno = 0; /* to check for ERANGE */
+ val = (int)strtol(flags, &cp, 0);
+ if ((errno != ERANGE) && (*cp == '\0')) {
+ if (val & ~(CCDF_UNIFORM|CCDF_MIRROR))
+ return (-1);
+ return (val);
+ }
+
+ /* Check for values represented by strings. */
+ if ((cp = strdup(flags)) == NULL)
+ err(1, "no memory to parse flags");
+ tmp = 0;
+ for (tok = cp; (tok = strtok(tok, ",")) != NULL; tok = NULL) {
+ for (i = 0; flagvaltab[i].fv_flag != NULL; ++i)
+ if (strcmp(tok, flagvaltab[i].fv_flag) == 0)
+ break;
+ if (flagvaltab[i].fv_flag == NULL) {
+ free(cp);
+ return (-1);
+ }
+ tmp |= flagvaltab[i].fv_val;
+ }
+
+ /* If we get here, the string was ok. */
+ free(cp);
+ return (tmp);
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "%s\n%s\n%s\n%s\n%s\n",
+ "usage: ccdconfig [-cv] ccd ileave [flags] dev ...",
+ " ccdconfig -C [-v] [-f config_file]",
+ " ccdconfig -u [-v] ccd ...",
+ " ccdconfig -U [-v] [-f config_file]",
+ " ccdconfig -g [ccd ...]");
+ exit(1);
+}
diff --git a/sbin/ccdconfig/pathnames.h b/sbin/ccdconfig/pathnames.h
new file mode 100644
index 0000000..538cfed
--- /dev/null
+++ b/sbin/ccdconfig/pathnames.h
@@ -0,0 +1,38 @@
+/* $NetBSD: pathnames.h,v 1.1 1995/08/17 16:37:20 thorpej Exp $ */
+
+/*
+ * Copyright (c) 1995 Jason R. Thorpe.
+ * 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 for the NetBSD Project
+ * by Jason R. Thorpe.
+ * 4. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#define _PATH_CCDCONF "/etc/ccd.conf"
+#define _PATH_CCDCTL "ccd.ctl"
diff --git a/sbin/clri/Makefile b/sbin/clri/Makefile
new file mode 100644
index 0000000..1ae0cf4
--- /dev/null
+++ b/sbin/clri/Makefile
@@ -0,0 +1,8 @@
+# @(#)Makefile 8.1 (Berkeley) 6/5/93
+# $FreeBSD$
+
+PROG= clri
+MAN= clri.8
+WARNS?= 2
+
+.include <bsd.prog.mk>
diff --git a/sbin/clri/clri.8 b/sbin/clri/clri.8
new file mode 100644
index 0000000..c3c3796
--- /dev/null
+++ b/sbin/clri/clri.8
@@ -0,0 +1,76 @@
+.\" Copyright (c) 1980, 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.
+.\" 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.
+.\"
+.\" @(#)clri.8 8.2 (Berkeley) 4/19/94
+.\" $FreeBSD$
+.\"
+.Dd April 19, 1994
+.Dt CLRI 8
+.Os
+.Sh NAME
+.Nm clri
+.Nd clear an inode
+.Sh SYNOPSIS
+.Nm
+.Ar special_device inode_number ...
+.Sh DESCRIPTION
+.Bf -symbolic
+The
+.Nm
+utility is obsoleted for normal file system repair work by
+.Xr fsck 8 .
+.Ef
+.Pp
+The
+.Nm
+utility zeroes out the inodes with the specified inode number(s)
+on the file system residing on the given
+.Ar special_device .
+The
+.Xr fsck 8
+utility is usually run after
+.Nm
+to reclaim the zeroed inode(s) and the
+blocks previously claimed by those inode(s).
+Both read and write permission are required on the specified
+.Ar special_device .
+.Pp
+The primary purpose of this routine
+is to remove a file which
+for some reason is not being properly handled by
+.Xr fsck 8 .
+Once removed,
+it is anticipated that
+.Xr fsck 8
+will be able to clean up the resulting mess.
+.Sh SEE ALSO
+.Xr fsck 8 ,
+.Xr fsdb 8
+.Sh BUGS
+If the file is open, the work of
+.Nm
+will be lost when the inode is written back to disk from the inode cache.
diff --git a/sbin/clri/clri.c b/sbin/clri/clri.c
new file mode 100644
index 0000000..ff41d1a
--- /dev/null
+++ b/sbin/clri/clri.c
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Rich $alz of BBN Inc.
+ *
+ * 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.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if 0
+#ifndef lint
+static const char copyright[] =
+"@(#) Copyright (c) 1990, 1993\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static char sccsid[] = "@(#)clri.c 8.2 (Berkeley) 9/23/93";
+#endif /* not lint */
+#endif
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/disklabel.h>
+
+#include <ufs/ufs/dinode.h>
+#include <ufs/ffs/fs.h>
+
+#include <err.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+
+/*
+ * Possible superblock locations ordered from most to least likely.
+ */
+static int sblock_try[] = SBLOCKSEARCH;
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr, "usage: clri special_device inode_number ...\n");
+ exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct fs *sbp;
+ struct ufs1_dinode *dp1;
+ struct ufs2_dinode *dp2;
+ char *ibuf[MAXBSIZE];
+ long generation, bsize;
+ off_t offset;
+ int i, fd, inonum;
+ char *fs, sblock[SBLOCKSIZE];
+ void *v = ibuf;
+
+ if (argc < 3)
+ usage();
+
+ fs = *++argv;
+ sbp = NULL;
+
+ /* get the superblock. */
+ if ((fd = open(fs, O_RDWR, 0)) < 0)
+ err(1, "%s", fs);
+ for (i = 0; sblock_try[i] != -1; i++) {
+ if (lseek(fd, (off_t)(sblock_try[i]), SEEK_SET) < 0)
+ err(1, "%s", fs);
+ if (read(fd, sblock, sizeof(sblock)) != sizeof(sblock))
+ errx(1, "%s: can't read superblock", fs);
+ sbp = (struct fs *)sblock;
+ if ((sbp->fs_magic == FS_UFS1_MAGIC ||
+ (sbp->fs_magic == FS_UFS2_MAGIC &&
+ sbp->fs_sblockloc == sblock_try[i])) &&
+ sbp->fs_bsize <= MAXBSIZE &&
+ sbp->fs_bsize >= (int)sizeof(struct fs))
+ break;
+ }
+ if (sblock_try[i] == -1)
+ errx(2, "cannot find file system superblock");
+ bsize = sbp->fs_bsize;
+
+ /* remaining arguments are inode numbers. */
+ while (*++argv) {
+ /* get the inode number. */
+ if ((inonum = atoi(*argv)) <= 0)
+ errx(1, "%s is not a valid inode number", *argv);
+ (void)printf("clearing %d\n", inonum);
+
+ /* read in the appropriate block. */
+ offset = ino_to_fsba(sbp, inonum); /* inode to fs blk */
+ offset = fsbtodb(sbp, offset); /* fs blk disk blk */
+ offset *= DEV_BSIZE; /* disk blk to bytes */
+
+ /* seek and read the block */
+ if (lseek(fd, offset, SEEK_SET) < 0)
+ err(1, "%s", fs);
+ if (read(fd, ibuf, bsize) != bsize)
+ err(1, "%s", fs);
+
+ if (sbp->fs_magic == FS_UFS2_MAGIC) {
+ /* get the inode within the block. */
+ dp2 = &(((struct ufs2_dinode *)v)
+ [ino_to_fsbo(sbp, inonum)]);
+
+ /* clear the inode, and bump the generation count. */
+ generation = dp2->di_gen + 1;
+ memset(dp2, 0, sizeof(*dp2));
+ dp2->di_gen = generation;
+ } else {
+ /* get the inode within the block. */
+ dp1 = &(((struct ufs1_dinode *)v)
+ [ino_to_fsbo(sbp, inonum)]);
+
+ /* clear the inode, and bump the generation count. */
+ generation = dp1->di_gen + 1;
+ memset(dp1, 0, sizeof(*dp1));
+ dp1->di_gen = generation;
+ }
+
+ /* backup and write the block */
+ if (lseek(fd, (off_t)-bsize, SEEK_CUR) < 0)
+ err(1, "%s", fs);
+ if (write(fd, ibuf, bsize) != bsize)
+ err(1, "%s", fs);
+ (void)fsync(fd);
+ }
+ (void)close(fd);
+ exit(0);
+}
diff --git a/sbin/comcontrol/Makefile b/sbin/comcontrol/Makefile
new file mode 100644
index 0000000..d875756
--- /dev/null
+++ b/sbin/comcontrol/Makefile
@@ -0,0 +1,7 @@
+# @(#)Makefile 5.4 (Berkeley) 6/5/91
+# $FreeBSD$
+
+PROG= comcontrol
+MAN= comcontrol.8
+
+.include <bsd.prog.mk>
diff --git a/sbin/comcontrol/comcontrol.8 b/sbin/comcontrol/comcontrol.8
new file mode 100644
index 0000000..ec12dee
--- /dev/null
+++ b/sbin/comcontrol/comcontrol.8
@@ -0,0 +1,65 @@
+.\" $FreeBSD$
+.Dd May 15, 1994
+.Dt COMCONTROL 8
+.Os
+.Sh NAME
+.Nm comcontrol
+.Nd control a special tty device
+.Sh SYNOPSIS
+.Nm
+.Ar special_device
+.Op dtrwait Ar number
+.Op drainwait Ar number
+.Sh DESCRIPTION
+The
+.Nm
+utility is used to examine and modify some of the special characteristics
+of the specified tty device.
+If no arguments other than the device (or "-" for stdin)
+are specified,
+it prints the settings of all controllable characteristics.
+This usage requires only read access on the device.
+Only the superuser can change the settings.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Cm dtrwait Ar number
+Set the time to wait after dropping DTR
+to the given number.
+The units are hundredths of a second.
+The default is 300 hundredths, i.e., 3 seconds.
+This option needed mainly to set proper recover time after
+modem reset.
+.It Cm drainwait Ar number
+Set the time to wait for output drain
+to the given number.
+The units are seconds.
+The default is 5 minutes, 0 means
+waiting forever.
+This option needed mainly to specify upper limit of minutes
+to prevent modem hanging.
+.El
+.Pp
+The standard way to use
+.Nm
+is to put invocations of it in the
+.Pa /etc/rc.d/serial
+startup script.
+.Sh FILES
+.Bl -tag -width /dev/ttyd? -compact
+.It Pa /dev/ttyd?
+dialin devices, hardwired terminals
+.It Pa /dev/cuau?
+dialout devices
+.El
+.Sh SEE ALSO
+.Xr stty 1 ,
+.Xr sio 4
+.Sh HISTORY
+Originally part of cgd's com package patches, version 0.2.1, to
+.Bx 386 0.1 .
+Once controlled bidirectional capabilities.
+Little is left to control now
+that these capabilities are standard.
+.Sh AUTHORS
+.An Christopher G. Demetriou
diff --git a/sbin/comcontrol/comcontrol.c b/sbin/comcontrol/comcontrol.c
new file mode 100644
index 0000000..a7c696c
--- /dev/null
+++ b/sbin/comcontrol/comcontrol.c
@@ -0,0 +1,128 @@
+/*-
+ * Copyright (c) 1992 Christopher G. Demetriou
+ * 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 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 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+
+static void usage(void);
+
+static void
+usage(void)
+{
+ fprintf(stderr,
+ "usage: comcontrol <filename> [dtrwait <n>] [drainwait <n>]\n");
+ exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int fd;
+ int res = 0;
+ int print_dtrwait = 1, print_drainwait = 1;
+ int dtrwait = -1, drainwait = -1;
+
+ if (argc < 2)
+ usage();
+
+ if (strcmp(argv[1], "-") == 0)
+ fd = STDIN_FILENO;
+ else {
+ fd = open(argv[1], O_RDONLY|O_NONBLOCK, 0);
+ if (fd < 0) {
+ warn("couldn't open file %s", argv[1]);
+ return 1;
+ }
+ }
+ if (argc == 2) {
+ if (ioctl(fd, TIOCMGDTRWAIT, &dtrwait) < 0) {
+ print_dtrwait = 0;
+ if (errno != ENOTTY) {
+ res = 1;
+ warn("TIOCMGDTRWAIT");
+ }
+ }
+ if (ioctl(fd, TIOCGDRAINWAIT, &drainwait) < 0) {
+ print_drainwait = 0;
+ if (errno != ENOTTY) {
+ res = 1;
+ warn("TIOCGDRAINWAIT");
+ }
+ }
+ if (print_dtrwait)
+ printf("dtrwait %d ", dtrwait);
+ if (print_drainwait)
+ printf("drainwait %d ", drainwait);
+ printf("\n");
+ } else {
+ while (argv[2] != NULL) {
+ if (!strcmp(argv[2],"dtrwait")) {
+ if (dtrwait >= 0)
+ usage();
+ if (argv[3] == NULL || !isdigit(argv[3][0]))
+ usage();
+ dtrwait = atoi(argv[3]);
+ argv += 2;
+ } else if (!strcmp(argv[2],"drainwait")) {
+ if (drainwait >= 0)
+ usage();
+ if (argv[3] == NULL || !isdigit(argv[3][0]))
+ usage();
+ drainwait = atoi(argv[3]);
+ argv += 2;
+ } else
+ usage();
+ }
+ if (dtrwait >= 0) {
+ if (ioctl(fd, TIOCMSDTRWAIT, &dtrwait) < 0) {
+ res = 1;
+ warn("TIOCMSDTRWAIT");
+ }
+ }
+ if (drainwait >= 0) {
+ if (ioctl(fd, TIOCSDRAINWAIT, &drainwait) < 0) {
+ res = 1;
+ warn("TIOCSDRAINWAIT");
+ }
+ }
+ }
+
+ close(fd);
+ return res;
+}
diff --git a/sbin/conscontrol/Makefile b/sbin/conscontrol/Makefile
new file mode 100644
index 0000000..9014d80
--- /dev/null
+++ b/sbin/conscontrol/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+PROG= conscontrol
+MAN= conscontrol.8
+WARNS?= 2
+
+.include <bsd.prog.mk>
diff --git a/sbin/conscontrol/conscontrol.8 b/sbin/conscontrol/conscontrol.8
new file mode 100644
index 0000000..bbb4063
--- /dev/null
+++ b/sbin/conscontrol/conscontrol.8
@@ -0,0 +1,116 @@
+.\"
+.\" Copyright (c) 2001 Jonathan Lemon <jlemon@FreeBSD.org>
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd April 14, 2011
+.Dt CONSCONTROL 8
+.Os
+.Sh NAME
+.Nm conscontrol
+.Nd control physical console devices
+.Sh SYNOPSIS
+.Nm
+.Op Cm list
+.Nm
+.Cm mute on | off
+.Nm
+.Cm add | delete
+.Ar console
+.Nm
+.Cm set | unset Ar console
+.Sh DESCRIPTION
+The
+.Nm
+utility is used to examine and modify the physical devices which back
+the virtual console devices.
+If no arguments
+(or only the
+.Cm list
+command)
+are specified,
+the current console settings are shown.
+.Pp
+There are two types of logical consoles; a high level console which
+is represented by
+.Pa /dev/console ,
+and a low level console.
+The low level console is used for kernel
+.Xr printf 9
+and
+.Xr ddb 4
+debugger support,
+while the high level console is used by user programs like
+.Xr syslogd 8 .
+Multiple device support is implemented only for the low level console;
+the high level console is set to the first device in the console list.
+.Pp
+Multiple console support may be invoked by passing the kernel the
+.Fl D
+flag from the boot loader, or by using
+.Nm
+to change the list of console devices after the system has booted.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Cm add | delete Ar console
+Add or delete a physical device from the logical console.
+The device must support low-level console operations.
+Adding a device will place it at the front of the list of console
+devices; the first device is used for the high level console.
+.Pp
+The
+.Ar console
+argument
+is the name of a console device in
+.Pa /dev ;
+the name of the directory may be omitted.
+.It Cm mute on | off
+Change the state of console muting.
+All console output is suppressed when console muting is
+.Cm on .
+.It Cm set | unset Ar console
+Set or unset the virtual console.
+When unset, output from the system, such as the kernel
+.Xr printf 9 ,
+always goes out to the real main console.
+When set, it goes to another.
+This is an interface to the tty ioctl
+.Dv TIOCCONS .
+.El
+.Sh SEE ALSO
+.Xr sio 4 ,
+.Xr syscons 4 ,
+.Xr tty 4 ,
+.Xr vt 4 ,
+.Xr boot 8 ,
+.Xr loader 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 5.0 .
+.Sh AUTHORS
+.An Jonathan Lemon
diff --git a/sbin/conscontrol/conscontrol.c b/sbin/conscontrol/conscontrol.c
new file mode 100644
index 0000000..3d49709
--- /dev/null
+++ b/sbin/conscontrol/conscontrol.c
@@ -0,0 +1,196 @@
+/*-
+ * Copyright (c) 2001 Jonathan Lemon <jlemon@FreeBSD.org>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/sysctl.h>
+#include <sys/ioctl.h>
+#include <sys/ttycom.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define DEVDIR "/dev/"
+
+static void __dead2
+usage(void)
+{
+
+ (void)fprintf(stderr, "%s\n%s\n%s\n%s\n",
+ "usage: conscontrol [list]",
+ " conscontrol mute on | off",
+ " conscontrol add | delete console",
+ " conscontrol set | unset console");
+ exit(1);
+}
+
+static void
+consstatus(void)
+{
+ int mute;
+ size_t len;
+ char *buf, *p, *avail;
+
+ len = sizeof(mute);
+ if (sysctlbyname("kern.consmute", &mute, &len, NULL, 0) == -1)
+ err(1, "kern.consmute retrieval failed");
+ if (sysctlbyname("kern.console", NULL, &len, NULL, 0) == -1)
+ err(1, "kern.console estimate failed");
+ if ((buf = malloc(len)) == NULL)
+ errx(1, "kern.console malloc failed");
+ if (sysctlbyname("kern.console", buf, &len, NULL, 0) == -1)
+ err(1, "kern.console retrieval failed");
+ if ((avail = strchr(buf, '/')) == NULL)
+ errx(1, "kern.console format not understood");
+ p = avail;
+ *avail++ = '\0';
+ if (p != buf)
+ *--p = '\0'; /* remove trailing ',' */
+ p = avail + strlen(avail);
+ if (p != avail)
+ *--p = '\0'; /* remove trailing ',' */
+ printf("Configured: %s\n", buf);
+ printf(" Available: %s\n", avail);
+ printf(" Muting: %s\n", mute ? "on" : "off");
+ free(buf);
+}
+
+static void
+consmute(const char *onoff)
+{
+ int mute;
+ size_t len;
+
+ if (strcmp(onoff, "on") == 0)
+ mute = 1;
+ else if (strcmp(onoff, "off") == 0)
+ mute = 0;
+ else
+ usage();
+ len = sizeof(mute);
+ if (sysctlbyname("kern.consmute", NULL, NULL, &mute, len) == -1)
+ err(1, "could not change console muting");
+}
+
+/*
+ * The name we supply to the sysctls should be an entry in /dev. If
+ * the user has specified the full pathname in /dev, DTRT. If he
+ * specifies a name in some other directory, it's an error.
+ */
+
+static char*
+stripdev(char *devnam)
+{
+ if (memcmp (devnam, DEVDIR, strlen(DEVDIR)) == 0)
+ return (&devnam[strlen(DEVDIR)]); /* remove /dev */
+ else if (strchr (devnam, '/')) {
+ fprintf(stderr, "Not a device in /dev: %s\n", devnam);
+ return (NULL); /* end of string */
+ } else
+ return (devnam); /* passed */
+}
+
+static void
+consadd(char *devnam)
+{
+ size_t len;
+
+ devnam = stripdev(devnam);
+ if (devnam == NULL)
+ return;
+ len = strlen(devnam);
+ if (sysctlbyname("kern.console", NULL, NULL, devnam, len) == -1)
+ err(1, "could not add %s as a console", devnam);
+}
+
+static void
+consdel(char *devnam)
+{
+ char *buf;
+ size_t len;
+
+ devnam = stripdev(devnam);
+ if (devnam == NULL)
+ return;
+ len = strlen(devnam) + sizeof("-");
+ if ((buf = malloc(len)) == NULL)
+ errx(1, "malloc failed");
+ buf[0] = '-';
+ strcpy(buf + 1, devnam);
+ if (sysctlbyname("kern.console", NULL, NULL, buf, len) == -1)
+ err(1, "could not remove %s as a console", devnam);
+ free(buf);
+}
+
+static void
+consset(char *devnam, int flag)
+{
+ int ttyfd;
+
+ ttyfd = open(devnam, O_RDONLY);
+ if (ttyfd == -1)
+ err(1, "opening %s", devnam);
+ if (ioctl(ttyfd, TIOCCONS, &flag) == -1)
+ err(1, "could not %s %s as virtual console",
+ flag ? "set" : "unset", devnam);
+ close(ttyfd);
+}
+
+int
+main(int argc, char **argv)
+{
+
+ if (getopt(argc, argv, "") != -1)
+ usage();
+ argc -= optind;
+ argv += optind;
+
+ if (argc > 0 && strcmp(argv[0], "list") != 0) {
+ if (argc != 2)
+ usage();
+ else if (strcmp(argv[0], "mute") == 0)
+ consmute(argv[1]);
+ else if (strcmp(argv[0], "add") == 0)
+ consadd(argv[1]);
+ else if (strcmp(argv[0], "delete") == 0)
+ consdel(argv[1]);
+ else if (strcmp(argv[0], "set") == 0)
+ consset(argv[1], 1);
+ else if (strcmp(argv[0], "unset") == 0)
+ consset(argv[1], 0);
+ else
+ usage();
+ }
+ consstatus();
+ exit(0);
+}
diff --git a/sbin/ddb/Makefile b/sbin/ddb/Makefile
new file mode 100644
index 0000000..72abbed
--- /dev/null
+++ b/sbin/ddb/Makefile
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+PROG= ddb
+SRCS= ddb.c ddb_capture.c ddb_script.c
+MAN= ddb.8
+WARNS?= 3
+
+LIBADD= kvm
+
+.include <bsd.prog.mk>
diff --git a/sbin/ddb/ddb.8 b/sbin/ddb/ddb.8
new file mode 100644
index 0000000..0fb9687
--- /dev/null
+++ b/sbin/ddb/ddb.8
@@ -0,0 +1,168 @@
+.\"-
+.\" Copyright (c) 2007-2008 Robert N. M. Watson
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd December 24, 2008
+.Dt DDB 8
+.Os
+.Sh NAME
+.Nm ddb
+.Nd "configure DDB kernel debugger properties"
+.Sh SYNOPSIS
+.Nm
+.Cm capture
+.Op Fl M core
+.Op Fl N system
+.Cm print
+.Nm
+.Cm capture
+.Op Fl M core
+.Op Fl N system
+.Cm status
+.Nm
+.Cm script
+.Ar scriptname
+.Nm
+.Cm script
+.Ar scriptname Ns = Ns Ar script
+.Nm
+.Cm scripts
+.Nm
+.Cm unscript
+.Ar scriptname
+.Nm
+.Ar pathname
+.Sh DESCRIPTION
+The
+.Nm
+utility configures certain aspects of the
+.Xr ddb 4
+kernel debugger from user space that are not configured at compile-time or
+easily via
+.Xr sysctl 8
+MIB entries.
+.Pp
+To ease configuration, commands can be put in a file which is processed using
+.Nm
+as shown in the last synopsis line.
+An absolute
+.Ar pathname
+must be used.
+The file will be read line by line and applied as arguments to the
+.Nm
+utility.
+Whitespace at the beginning of lines will be ignored as will lines where the
+first non-whitespace character is
+.Ql # .
+.Sh OUTPUT CAPTURE
+The
+.Nm
+utility can be used to extract the contents of the
+.Xr ddb 4
+output capture buffer of the current live kernel, or from the crash dump of a
+kernel on disk.
+The following debugger commands are available from the command line:
+.Bl -tag -width indent
+.It Xo
+.Cm capture
+.Op Fl M Ar core
+.Op Fl N Ar system
+.Cm print
+.Xc
+Print the current contents of the
+.Xr ddb 4
+output capture buffer.
+.It Xo
+.Cm capture
+.Op Fl M Ar core
+.Op Fl N Ar system
+.Cm status
+.Xc
+Print the current status of the
+.Xr ddb 4
+output capture buffer.
+.El
+.Sh SCRIPTING
+The
+.Nm
+utility can be used to configure aspects of
+.Xr ddb 4
+scripting from user space; scripting support is described in more detail in
+.Xr ddb 4 .
+Each of the debugger commands is available from the command line:
+.Bl -tag -width indent
+.It Cm script Ar scriptname
+Print the script named
+.Ar scriptname .
+.It Cm script Ar scriptname Ns = Ns Ar script
+Define a script named
+.Ar scriptname .
+As many scripts contain characters interpreted in special ways by the shell,
+it is advisable to enclose
+.Ar script
+in quotes.
+.It Cm scripts
+List currently defined scripts.
+.It Cm unscript Ar scriptname
+Delete the script named
+.Ar scriptname .
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh EXAMPLES
+The following example defines a script that will execute when the kernel
+debugger is entered as a result of a break signal:
+.Bd -literal -offset indent
+ddb script kdb.enter.break="show pcpu; bt"
+.Ed
+.Pp
+The following example will delete the script:
+.Pp
+.Dl "ddb unscript kdb.enter.break"
+.Pp
+For further examples, see the
+.Xr ddb 4
+and
+.Xr textdump 4
+manual pages.
+.Sh SEE ALSO
+.Xr ddb 4 ,
+.Xr textdump 4 ,
+.Xr sysctl 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 7.1 .
+.Sh AUTHORS
+.An Robert N M Watson
+.Sh BUGS
+Ideally,
+.Nm
+would not exist, as all pertinent aspects of
+.Xr ddb 4
+could be configured directly via
+.Xr sysctl 8 .
diff --git a/sbin/ddb/ddb.c b/sbin/ddb/ddb.c
new file mode 100644
index 0000000..edba7f8
--- /dev/null
+++ b/sbin/ddb/ddb.c
@@ -0,0 +1,134 @@
+/*-
+ * Copyright (c) 2007 Robert N. M. Watson
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include "ddb.h"
+
+void ddb_readfile(char *file);
+void ddb_main(int argc, char *argv[]);
+
+void
+usage(void)
+{
+
+ fprintf(stderr, "usage: ddb capture [-M core] [-N system] print\n");
+ fprintf(stderr, " ddb capture [-M core] [-N system] status\n");
+ fprintf(stderr, " ddb script scriptname\n");
+ fprintf(stderr, " ddb script scriptname=script\n");
+ fprintf(stderr, " ddb scripts\n");
+ fprintf(stderr, " ddb unscript scriptname\n");
+ fprintf(stderr, " ddb pathname\n");
+ exit(EX_USAGE);
+}
+
+void
+ddb_readfile(char *filename)
+{
+ char buf[BUFSIZ];
+ FILE* f;
+
+ if ((f = fopen(filename, "r")) == NULL)
+ err(EX_UNAVAILABLE, "fopen: %s", filename);
+
+#define WHITESP " \t"
+#define MAXARG 2
+ while (fgets(buf, BUFSIZ, f)) {
+ int argc = 0;
+ char *argv[MAXARG];
+ size_t spn;
+
+ spn = strlen(buf);
+ if (buf[spn-1] == '\n')
+ buf[spn-1] = '\0';
+
+ spn = strspn(buf, WHITESP);
+ argv[0] = buf + spn;
+ if (*argv[0] == '#' || *argv[0] == '\0')
+ continue;
+ argc++;
+
+ spn = strcspn(argv[0], WHITESP);
+ argv[1] = argv[0] + spn + strspn(argv[0] + spn, WHITESP);
+ argv[0][spn] = '\0';
+ if (*argv[1] != '\0')
+ argc++;
+
+#ifdef DEBUG
+ {
+ int i;
+ printf("argc = %d\n", argc);
+ for (i = 0; i < argc; i++) {
+ printf("arg[%d] = %s\n", i, argv[i]);
+ }
+ }
+#endif
+ ddb_main(argc, argv);
+ }
+ fclose(f);
+}
+
+void
+ddb_main(int argc, char *argv[])
+{
+
+ if (argc < 1)
+ usage();
+
+ if (strcmp(argv[0], "capture") == 0)
+ ddb_capture(argc, argv);
+ else if (strcmp(argv[0], "script") == 0)
+ ddb_script(argc, argv);
+ else if (strcmp(argv[0], "scripts") == 0)
+ ddb_scripts(argc, argv);
+ else if (strcmp(argv[0], "unscript") == 0)
+ ddb_unscript(argc, argv);
+ else
+ usage();
+}
+
+int
+main(int argc, char *argv[])
+{
+
+ /*
+ * If we've only got one argument and it's an absolute path to a file,
+ * interpret as a file to be read in.
+ */
+ if (argc == 2 && argv[1][0] == '/' && access(argv[1], R_OK) == 0)
+ ddb_readfile(argv[1]);
+ else
+ ddb_main(argc-1, argv+1);
+ exit(EX_OK);
+}
diff --git a/sbin/ddb/ddb.h b/sbin/ddb/ddb.h
new file mode 100644
index 0000000..8363bcc
--- /dev/null
+++ b/sbin/ddb/ddb.h
@@ -0,0 +1,38 @@
+/*-
+ * Copyright (c) 2007 Robert N. M. Watson
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef DDB_H
+#define DDB_H
+
+void ddb_capture(int argc, char *argv[]);
+void ddb_script(int argc, char *argv[]);
+void ddb_scripts(int argc, char *argv[]);
+void ddb_unscript(int argc, char *argv[]);
+void usage(void);
+
+#endif /* DDB_H */
diff --git a/sbin/ddb/ddb_capture.c b/sbin/ddb/ddb_capture.c
new file mode 100644
index 0000000..370fc00
--- /dev/null
+++ b/sbin/ddb/ddb_capture.c
@@ -0,0 +1,248 @@
+/*-
+ * Copyright (c) 2008 Robert N. M. Watson
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/sysctl.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <kvm.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include "ddb.h"
+
+/*
+ * Interface with the ddb(4) capture buffer of a live kernel using sysctl, or
+ * for a crash dump using libkvm.
+ */
+#define SYSCTL_DDB_CAPTURE_BUFOFF "debug.ddb.capture.bufoff"
+#define SYSCTL_DDB_CAPTURE_BUFSIZE "debug.ddb.capture.bufsize"
+#define SYSCTL_DDB_CAPTURE_MAXBUFSIZE "debug.ddb.capture.maxbufsize"
+#define SYSCTL_DDB_CAPTURE_DATA "debug.ddb.capture.data"
+#define SYSCTL_DDB_CAPTURE_INPROGRESS "debug.ddb.capture.inprogress"
+
+static struct nlist namelist[] = {
+#define X_DB_CAPTURE_BUF 0
+ { .n_name = "_db_capture_buf" },
+#define X_DB_CAPTURE_BUFSIZE 1
+ { .n_name = "_db_capture_bufsize" },
+#define X_DB_CAPTURE_MAXBUFSIZE 2
+ { .n_name = "_db_capture_maxbufsize" },
+#define X_DB_CAPTURE_BUFOFF 3
+ { .n_name = "_db_capture_bufoff" },
+#define X_DB_CAPTURE_INPROGRESS 4
+ { .n_name = "_db_capture_inprogress" },
+ { .n_name = "" },
+};
+
+static int
+kread(kvm_t *kvm, void *kvm_pointer, void *address, size_t size,
+ size_t offset)
+{
+ ssize_t ret;
+
+ ret = kvm_read(kvm, (unsigned long)kvm_pointer + offset, address,
+ size);
+ if (ret < 0 || (size_t)ret != size)
+ return (-1);
+ return (0);
+}
+
+static int
+kread_symbol(kvm_t *kvm, int index, void *address, size_t size,
+ size_t offset)
+{
+ ssize_t ret;
+
+ ret = kvm_read(kvm, namelist[index].n_value + offset, address, size);
+ if (ret < 0 || (size_t)ret != size)
+ return (-1);
+ return (0);
+}
+
+static void
+ddb_capture_print_kvm(kvm_t *kvm)
+{
+ u_int db_capture_bufoff;
+ char *buffer, *db_capture_buf;
+
+ if (kread_symbol(kvm, X_DB_CAPTURE_BUF, &db_capture_buf,
+ sizeof(db_capture_buf), 0) < 0)
+ errx(-1, "kvm: unable to read db_capture_buf");
+
+ if (kread_symbol(kvm, X_DB_CAPTURE_BUFOFF, &db_capture_bufoff,
+ sizeof(db_capture_bufoff), 0) < 0)
+ errx(-1, "kvm: unable to read db_capture_bufoff");
+
+ buffer = malloc(db_capture_bufoff + 1);
+ if (buffer == NULL)
+ err(-1, "malloc: db_capture_bufoff (%u)",
+ db_capture_bufoff);
+ bzero(buffer, db_capture_bufoff + 1);
+
+ if (kread(kvm, db_capture_buf, buffer, db_capture_bufoff, 0) < 0)
+ errx(-1, "kvm: unable to read buffer");
+
+ printf("%s\n", buffer);
+ free(buffer);
+}
+
+static void
+ddb_capture_print_sysctl(void)
+{
+ size_t buflen, len;
+ char *buffer;
+ int ret;
+
+repeat:
+ if (sysctlbyname(SYSCTL_DDB_CAPTURE_DATA, NULL, &buflen, NULL, 0) < 0)
+ err(EX_OSERR, "sysctl: %s", SYSCTL_DDB_CAPTURE_DATA);
+ if (buflen == 0)
+ return;
+ buffer = malloc(buflen);
+ if (buffer == NULL)
+ err(EX_OSERR, "malloc");
+ bzero(buffer, buflen);
+ len = buflen;
+ ret = sysctlbyname(SYSCTL_DDB_CAPTURE_DATA, buffer, &len, NULL, 0);
+ if (ret < 0 && errno != ENOMEM)
+ err(EX_OSERR, "sysctl: %s", SYSCTL_DDB_CAPTURE_DATA);
+ if (ret < 0) {
+ free(buffer);
+ goto repeat;
+ }
+
+ printf("%s\n", buffer);
+ free(buffer);
+}
+
+static void
+ddb_capture_status_kvm(kvm_t *kvm)
+{
+ u_int db_capture_bufoff, db_capture_bufsize, db_capture_inprogress;
+
+ if (kread_symbol(kvm, X_DB_CAPTURE_BUFOFF, &db_capture_bufoff,
+ sizeof(db_capture_bufoff), 0) < 0)
+ errx(-1, "kvm: unable to read db_capture_bufoff");
+ if (kread_symbol(kvm, X_DB_CAPTURE_BUFSIZE, &db_capture_bufsize,
+ sizeof(db_capture_bufsize), 0) < 0)
+ errx(-1, "kvm: unable to read db_capture_bufsize");
+ if (kread_symbol(kvm, X_DB_CAPTURE_INPROGRESS,
+ &db_capture_inprogress, sizeof(db_capture_inprogress), 0) < 0)
+ err(-1, "kvm: unable to read db_capture_inprogress");
+ printf("%u/%u bytes used\n", db_capture_bufoff, db_capture_bufsize);
+ if (db_capture_inprogress)
+ printf("capture is on\n");
+ else
+ printf("capture is off\n");
+
+}
+
+static void
+ddb_capture_status_sysctl(void)
+{
+ u_int db_capture_bufoff, db_capture_bufsize, db_capture_inprogress;
+ size_t len;
+
+ len = sizeof(db_capture_bufoff);
+ if (sysctlbyname(SYSCTL_DDB_CAPTURE_BUFOFF, &db_capture_bufoff, &len,
+ NULL, 0) < 0)
+ err(EX_OSERR, "sysctl: %s", SYSCTL_DDB_CAPTURE_BUFOFF);
+ len = sizeof(db_capture_bufoff);
+ if (sysctlbyname(SYSCTL_DDB_CAPTURE_BUFSIZE, &db_capture_bufsize,
+ &len, NULL, 0) < 0)
+ err(EX_OSERR, "sysctl: %s", SYSCTL_DDB_CAPTURE_BUFSIZE);
+ len = sizeof(db_capture_inprogress);
+ if (sysctlbyname(SYSCTL_DDB_CAPTURE_INPROGRESS,
+ &db_capture_inprogress, &len, NULL, 0) < 0)
+ err(EX_OSERR, "sysctl: %s", SYSCTL_DDB_CAPTURE_INPROGRESS);
+ printf("%u/%u bytes used\n", db_capture_bufoff, db_capture_bufsize);
+ if (db_capture_inprogress)
+ printf("capture is on\n");
+ else
+ printf("capture is off\n");
+}
+
+void
+ddb_capture(int argc, char *argv[])
+{
+ char *mflag, *nflag, errbuf[_POSIX2_LINE_MAX];
+ kvm_t *kvm;
+ int ch;
+
+ mflag = NULL;
+ nflag = NULL;
+ kvm = NULL;
+ while ((ch = getopt(argc, argv, "M:N:")) != -1) {
+ switch (ch) {
+ case 'M':
+ mflag = optarg;
+ break;
+
+ case 'N':
+ nflag = optarg;
+ break;
+
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 1)
+ usage();
+
+ if (mflag != NULL) {
+ kvm = kvm_openfiles(nflag, mflag, NULL, O_RDONLY, errbuf);
+ if (kvm == NULL)
+ errx(-1, "ddb_capture: kvm_openfiles: %s", errbuf);
+ if (kvm_nlist(kvm, namelist) != 0)
+ errx(-1, "ddb_capture: kvm_nlist");
+ } else if (nflag != NULL)
+ usage();
+ if (strcmp(argv[0], "print") == 0) {
+ if (kvm != NULL)
+ ddb_capture_print_kvm(kvm);
+ else
+ ddb_capture_print_sysctl();
+ } else if (strcmp(argv[0], "status") == 0) {
+ if (kvm != NULL)
+ ddb_capture_status_kvm(kvm);
+ else
+ ddb_capture_status_sysctl();
+ } else
+ usage();
+}
diff --git a/sbin/ddb/ddb_script.c b/sbin/ddb/ddb_script.c
new file mode 100644
index 0000000..1382904
--- /dev/null
+++ b/sbin/ddb/ddb_script.c
@@ -0,0 +1,160 @@
+/*-
+ * Copyright (c) 2007 Robert N. M. Watson
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/sysctl.h>
+
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+
+#include "ddb.h"
+
+/*
+ * These commands manage DDB(4) scripts from user space. For better or worse,
+ * the setting and unsetting of scripts is only poorly represented using
+ * sysctl(8), and this interface provides a more user-friendly way to
+ * accomplish this management, wrapped around lower-level sysctls. For
+ * completeness, listing of scripts is also included.
+ */
+
+#define SYSCTL_SCRIPT "debug.ddb.scripting.script"
+#define SYSCTL_SCRIPTS "debug.ddb.scripting.scripts"
+#define SYSCTL_UNSCRIPT "debug.ddb.scripting.unscript"
+
+/*
+ * Print all scripts (scriptname==NULL) or a specific script.
+ */
+static void
+ddb_list_scripts(const char *scriptname)
+{
+ char *buffer, *line, *nextline;
+ char *line_script, *line_scriptname;
+ size_t buflen, len;
+ int ret;
+
+repeat:
+ if (sysctlbyname(SYSCTL_SCRIPTS, NULL, &buflen, NULL, 0) < 0)
+ err(EX_OSERR, "sysctl: %s", SYSCTL_SCRIPTS);
+ if (buflen == 0)
+ return;
+ buffer = malloc(buflen);
+ if (buffer == NULL)
+ err(EX_OSERR, "malloc");
+ bzero(buffer, buflen);
+ len = buflen;
+ ret = sysctlbyname(SYSCTL_SCRIPTS, buffer, &len, NULL, 0);
+ if (ret < 0 && errno != ENOMEM)
+ err(EX_OSERR, "sysctl: %s", SYSCTL_SCRIPTS);
+ if (ret < 0) {
+ free(buffer);
+ goto repeat;
+ }
+
+ /*
+ * We nul'd the buffer before calling sysctl(), so at worst empty.
+ *
+ * If a specific script hasn't been requested, print it all.
+ */
+ if (scriptname == NULL) {
+ printf("%s", buffer);
+ free(buffer);
+ return;
+ }
+
+ /*
+ * If a specific script has been requested, we have to parse the
+ * string to find it.
+ */
+ nextline = buffer;
+ while ((line = strsep(&nextline, "\n")) != NULL) {
+ line_script = line;
+ line_scriptname = strsep(&line_script, "=");
+ if (line_script == NULL)
+ continue;
+ if (strcmp(scriptname, line_scriptname) != 0)
+ continue;
+ printf("%s\n", line_script);
+ break;
+ }
+ if (line == NULL) {
+ errno = ENOENT;
+ err(EX_DATAERR, "%s", scriptname);
+ }
+ free(buffer);
+}
+
+/*
+ * "ddb script" can be used to either print or set a script.
+ */
+void
+ddb_script(int argc, char *argv[])
+{
+
+ if (argc != 2)
+ usage();
+ argv++;
+ argc--;
+ if (strchr(argv[0], '=') != 0) {
+ if (sysctlbyname(SYSCTL_SCRIPT, NULL, NULL, argv[0],
+ strlen(argv[0]) + 1) < 0)
+ err(EX_OSERR, "sysctl: %s", SYSCTL_SCRIPTS);
+ } else
+ ddb_list_scripts(argv[0]);
+}
+
+void
+ddb_scripts(int argc, char *argv[])
+{
+
+ if (argc != 1)
+ usage();
+ ddb_list_scripts(NULL);
+}
+
+void
+ddb_unscript(int argc, char *argv[])
+{
+ int ret;
+
+ if (argc != 2)
+ usage();
+ argv++;
+ argc--;
+ ret = sysctlbyname(SYSCTL_UNSCRIPT, NULL, NULL, argv[0],
+ strlen(argv[0]) + 1);
+ if (ret < 0 && errno == EINVAL) {
+ errno = ENOENT;
+ err(EX_DATAERR, "sysctl: %s", argv[0]);
+ } else if (ret < 0)
+ err(EX_OSERR, "sysctl: %s", SYSCTL_UNSCRIPT);
+}
diff --git a/sbin/devd/Makefile b/sbin/devd/Makefile
new file mode 100644
index 0000000..fc00216
--- /dev/null
+++ b/sbin/devd/Makefile
@@ -0,0 +1,24 @@
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+PROG_CXX=devd
+SRCS= devd.cc token.l parse.y y.tab.h
+MAN= devd.8 devd.conf.5
+
+WARNS?= 3
+
+NO_SHARED?=YES
+
+LIBADD= l util
+
+YFLAGS+=-v
+CFLAGS+=-I. -I${.CURDIR}
+
+CLEANFILES= y.output
+
+.if ${MK_TESTS} != "no"
+SUBDIR+= tests
+.endif
+
+.include <bsd.prog.mk>
diff --git a/sbin/devd/devd.8 b/sbin/devd/devd.8
new file mode 100644
index 0000000..12a92d9
--- /dev/null
+++ b/sbin/devd/devd.8
@@ -0,0 +1,157 @@
+.\"
+.\" Copyright (c) 2002 M. Warner Losh.
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd August 14, 2014
+.Dt DEVD 8
+.Os
+.Sh NAME
+.Nm devd
+.Nd "device state change daemon"
+.Sh SYNOPSIS
+.Nm
+.Op Fl dnq
+.Op Fl f Ar file
+.Op Fl l Ar num
+.Sh DESCRIPTION
+The
+.Nm
+daemon provides a way to have userland programs run when certain
+kernel events happen.
+.Pp
+The following options are accepted.
+.Bl -tag -width ".Fl f Ar file"
+.It Fl d
+Run in the foreground instead of becoming a daemon and log additional information for debugging.
+.It Fl f Ar file
+Use configuration file
+.Ar file
+instead of the default
+.Pa /etc/devd.conf .
+If option
+.Fl f
+is specified more than once, the last file specified is used.
+.It Fl l Ar num
+Limit concurrent socket connections to
+.Ar num .
+The default connection limit is 10.
+.It Fl n
+Do not process all pending events before becoming a daemon.
+Instead, call daemon right away.
+.It Fl q
+Quiet mode. Only log messages at priority LOG_WARNING or above.
+.El
+.Sh IMPLEMENTATION NOTES
+The
+.Nm
+utility
+is a system daemon that runs in the background all the time.
+Whenever a device is added to or removed from the device tree,
+.Nm
+will execute actions specified in
+.Xr devd.conf 5 .
+For example,
+.Nm
+might execute
+.Xr dhclient 8
+when an Ethernet adapter is added to the system, and kill the
+.Xr dhclient 8
+instance when the same adapter is removed.
+Another example would be for
+.Nm
+to use a table to locate and load via
+.Xr kldload 8
+the proper driver for an unrecognized device that is added to the system.
+.Pp
+The
+.Nm
+utility
+hooks into the
+.Xr devctl 4
+device driver.
+This device driver has hooks into the device configuration system.
+When nodes are added or deleted from the tree, this device will
+deliver information about the event to
+.Nm .
+Once
+.Nm
+has parsed the message, it will search its action list for that kind
+of event and perform the action with the highest matching value.
+For most mundane uses, the default handlers are adequate.
+However, for more advanced users, the power is present to tweak every
+aspect of what happens.
+.Pp
+The
+.Nm
+utility
+reads
+.Pa /etc/devd.conf
+or the alternate configuration file specified with a
+.Fl f
+option and uses that file to drive the rest of the process.
+While the format of this file is described in
+.Xr devd.conf 5 ,
+some basics are covered here.
+In the
+.Ic options
+section, one can define multiple directories to search
+for config files.
+All files in these directories whose names match the pattern
+.Pa *.conf
+are parsed.
+These files are intended to be installed by third party vendors that
+wish to hook into the
+.Nm
+system without modifying the user's other
+config files.
+.Pp
+Since
+.Xr devctl 4
+allows only one active reader,
+.Nm
+multiplexes it, forwarding all events to any number of connected clients.
+Clients connect by opening the SOCK_SEQPACKET
+.Ux
+domain socket at
+.Pa /var/run/devd.seqpacket.pipe .
+.Sh FILES
+.Bl -tag -width ".Pa /var/run/devd.seqpacket.pipe" -compact
+.It Pa /etc/devd.conf
+The default
+.Nm
+configuration file.
+.It Pa /var/run/devd.seqpacket.pipe
+The socket used by
+.Nm
+to communicate with its clients.
+.It Pa /var/run/devd.pipe
+A deprecated socket retained for use with old clients.
+.El
+.Sh SEE ALSO
+.Xr devctl 4 ,
+.Xr devd.conf 5
+.Sh AUTHORS
+.An M. Warner Losh
diff --git a/sbin/devd/devd.cc b/sbin/devd/devd.cc
new file mode 100644
index 0000000..c770204
--- /dev/null
+++ b/sbin/devd/devd.cc
@@ -0,0 +1,1243 @@
+/*-
+ * Copyright (c) 2002-2010 M. Warner Losh.
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * my_system is a variation on lib/libc/stdlib/system.c:
+ *
+ * Copyright (c) 1988, 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.
+ * 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.
+ */
+
+/*
+ * DEVD control daemon.
+ */
+
+// TODO list:
+// o devd.conf and devd man pages need a lot of help:
+// - devd needs to document the unix domain socket
+// - devd.conf needs more details on the supported statements.
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/sysctl.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/un.h>
+
+#include <cctype>
+#include <cerrno>
+#include <cstdlib>
+#include <cstdio>
+#include <csignal>
+#include <cstring>
+#include <cstdarg>
+
+#include <dirent.h>
+#include <err.h>
+#include <fcntl.h>
+#include <libutil.h>
+#include <paths.h>
+#include <poll.h>
+#include <regex.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <map>
+#include <string>
+#include <list>
+#include <vector>
+
+#include "devd.h" /* C compatible definitions */
+#include "devd.hh" /* C++ class definitions */
+
+#define STREAMPIPE "/var/run/devd.pipe"
+#define SEQPACKETPIPE "/var/run/devd.seqpacket.pipe"
+#define CF "/etc/devd.conf"
+#define SYSCTL "hw.bus.devctl_queue"
+
+/*
+ * Since the client socket is nonblocking, we must increase its send buffer to
+ * handle brief event storms. On FreeBSD, AF_UNIX sockets don't have a receive
+ * buffer, so the client can't increate the buffersize by itself.
+ *
+ * For example, when creating a ZFS pool, devd emits one 165 character
+ * resource.fs.zfs.statechange message for each vdev in the pool. A 64k
+ * buffer has enough space for almost 400 drives, which would be very large but
+ * not impossibly large pool. A 128k buffer has enough space for 794 drives,
+ * which is more than can fit in a rack with modern technology.
+ */
+#define CLIENT_BUFSIZE 131072
+
+using namespace std;
+
+typedef struct client {
+ int fd;
+ int socktype;
+} client_t;
+
+extern FILE *yyin;
+extern int lineno;
+
+static const char notify = '!';
+static const char nomatch = '?';
+static const char attach = '+';
+static const char detach = '-';
+
+static struct pidfh *pfh;
+
+static int no_daemon = 0;
+static int daemonize_quick = 0;
+static int quiet_mode = 0;
+static unsigned total_events = 0;
+static volatile sig_atomic_t got_siginfo = 0;
+static volatile sig_atomic_t romeo_must_die = 0;
+
+static const char *configfile = CF;
+
+static void devdlog(int priority, const char* message, ...)
+ __printflike(2, 3);
+static void event_loop(void);
+static void usage(void);
+
+template <class T> void
+delete_and_clear(vector<T *> &v)
+{
+ typename vector<T *>::const_iterator i;
+
+ for (i = v.begin(); i != v.end(); ++i)
+ delete *i;
+ v.clear();
+}
+
+config cfg;
+
+event_proc::event_proc() : _prio(-1)
+{
+ _epsvec.reserve(4);
+}
+
+event_proc::~event_proc()
+{
+ delete_and_clear(_epsvec);
+}
+
+void
+event_proc::add(eps *eps)
+{
+ _epsvec.push_back(eps);
+}
+
+bool
+event_proc::matches(config &c) const
+{
+ vector<eps *>::const_iterator i;
+
+ for (i = _epsvec.begin(); i != _epsvec.end(); ++i)
+ if (!(*i)->do_match(c))
+ return (false);
+ return (true);
+}
+
+bool
+event_proc::run(config &c) const
+{
+ vector<eps *>::const_iterator i;
+
+ for (i = _epsvec.begin(); i != _epsvec.end(); ++i)
+ if (!(*i)->do_action(c))
+ return (false);
+ return (true);
+}
+
+action::action(const char *cmd)
+ : _cmd(cmd)
+{
+ // nothing
+}
+
+action::~action()
+{
+ // nothing
+}
+
+static int
+my_system(const char *command)
+{
+ pid_t pid, savedpid;
+ int pstat;
+ struct sigaction ign, intact, quitact;
+ sigset_t newsigblock, oldsigblock;
+
+ if (!command) /* just checking... */
+ return (1);
+
+ /*
+ * Ignore SIGINT and SIGQUIT, block SIGCHLD. Remember to save
+ * existing signal dispositions.
+ */
+ ign.sa_handler = SIG_IGN;
+ ::sigemptyset(&ign.sa_mask);
+ ign.sa_flags = 0;
+ ::sigaction(SIGINT, &ign, &intact);
+ ::sigaction(SIGQUIT, &ign, &quitact);
+ ::sigemptyset(&newsigblock);
+ ::sigaddset(&newsigblock, SIGCHLD);
+ ::sigprocmask(SIG_BLOCK, &newsigblock, &oldsigblock);
+ switch (pid = ::fork()) {
+ case -1: /* error */
+ break;
+ case 0: /* child */
+ /*
+ * Restore original signal dispositions and exec the command.
+ */
+ ::sigaction(SIGINT, &intact, NULL);
+ ::sigaction(SIGQUIT, &quitact, NULL);
+ ::sigprocmask(SIG_SETMASK, &oldsigblock, NULL);
+ /*
+ * Close the PID file, and all other open descriptors.
+ * Inherit std{in,out,err} only.
+ */
+ cfg.close_pidfile();
+ ::closefrom(3);
+ ::execl(_PATH_BSHELL, "sh", "-c", command, (char *)NULL);
+ ::_exit(127);
+ default: /* parent */
+ savedpid = pid;
+ do {
+ pid = ::wait4(savedpid, &pstat, 0, (struct rusage *)0);
+ } while (pid == -1 && errno == EINTR);
+ break;
+ }
+ ::sigaction(SIGINT, &intact, NULL);
+ ::sigaction(SIGQUIT, &quitact, NULL);
+ ::sigprocmask(SIG_SETMASK, &oldsigblock, NULL);
+ return (pid == -1 ? -1 : pstat);
+}
+
+bool
+action::do_action(config &c)
+{
+ string s = c.expand_string(_cmd.c_str());
+ devdlog(LOG_INFO, "Executing '%s'\n", s.c_str());
+ my_system(s.c_str());
+ return (true);
+}
+
+match::match(config &c, const char *var, const char *re) :
+ _inv(re[0] == '!'),
+ _var(var),
+ _re(c.expand_string(_inv ? re + 1 : re, "^", "$"))
+{
+ regcomp(&_regex, _re.c_str(), REG_EXTENDED | REG_NOSUB | REG_ICASE);
+}
+
+match::~match()
+{
+ regfree(&_regex);
+}
+
+bool
+match::do_match(config &c)
+{
+ const string &value = c.get_variable(_var);
+ bool retval;
+
+ /*
+ * This function gets called WAY too often to justify calling syslog()
+ * each time, even at LOG_DEBUG. Because if syslogd isn't running, it
+ * can consume excessive amounts of systime inside of connect(). Only
+ * log when we're in -d mode.
+ */
+ if (no_daemon) {
+ devdlog(LOG_DEBUG, "Testing %s=%s against %s, invert=%d\n",
+ _var.c_str(), value.c_str(), _re.c_str(), _inv);
+ }
+
+ retval = (regexec(&_regex, value.c_str(), 0, NULL, 0) == 0);
+ if (_inv == 1)
+ retval = (retval == 0) ? 1 : 0;
+
+ return (retval);
+}
+
+#include <sys/sockio.h>
+#include <net/if.h>
+#include <net/if_media.h>
+
+media::media(config &, const char *var, const char *type)
+ : _var(var), _type(-1)
+{
+ static struct ifmedia_description media_types[] = {
+ { IFM_ETHER, "Ethernet" },
+ { IFM_TOKEN, "Tokenring" },
+ { IFM_FDDI, "FDDI" },
+ { IFM_IEEE80211, "802.11" },
+ { IFM_ATM, "ATM" },
+ { -1, "unknown" },
+ { 0, NULL },
+ };
+ for (int i = 0; media_types[i].ifmt_string != NULL; ++i)
+ if (strcasecmp(type, media_types[i].ifmt_string) == 0) {
+ _type = media_types[i].ifmt_word;
+ break;
+ }
+}
+
+media::~media()
+{
+}
+
+bool
+media::do_match(config &c)
+{
+ string value;
+ struct ifmediareq ifmr;
+ bool retval;
+ int s;
+
+ // Since we can be called from both a device attach/detach
+ // context where device-name is defined and what we want,
+ // as well as from a link status context, where subsystem is
+ // the name of interest, first try device-name and fall back
+ // to subsystem if none exists.
+ value = c.get_variable("device-name");
+ if (value.empty())
+ value = c.get_variable("subsystem");
+ devdlog(LOG_DEBUG, "Testing media type of %s against 0x%x\n",
+ value.c_str(), _type);
+
+ retval = false;
+
+ s = socket(PF_INET, SOCK_DGRAM, 0);
+ if (s >= 0) {
+ memset(&ifmr, 0, sizeof(ifmr));
+ strncpy(ifmr.ifm_name, value.c_str(), sizeof(ifmr.ifm_name));
+
+ if (ioctl(s, SIOCGIFMEDIA, (caddr_t)&ifmr) >= 0 &&
+ ifmr.ifm_status & IFM_AVALID) {
+ devdlog(LOG_DEBUG, "%s has media type 0x%x\n",
+ value.c_str(), IFM_TYPE(ifmr.ifm_active));
+ retval = (IFM_TYPE(ifmr.ifm_active) == _type);
+ } else if (_type == -1) {
+ devdlog(LOG_DEBUG, "%s has unknown media type\n",
+ value.c_str());
+ retval = true;
+ }
+ close(s);
+ }
+
+ return (retval);
+}
+
+const string var_list::bogus = "_$_$_$_$_B_O_G_U_S_$_$_$_$_";
+const string var_list::nothing = "";
+
+const string &
+var_list::get_variable(const string &var) const
+{
+ map<string, string>::const_iterator i;
+
+ i = _vars.find(var);
+ if (i == _vars.end())
+ return (var_list::bogus);
+ return (i->second);
+}
+
+bool
+var_list::is_set(const string &var) const
+{
+ return (_vars.find(var) != _vars.end());
+}
+
+void
+var_list::set_variable(const string &var, const string &val)
+{
+ /*
+ * This function gets called WAY too often to justify calling syslog()
+ * each time, even at LOG_DEBUG. Because if syslogd isn't running, it
+ * can consume excessive amounts of systime inside of connect(). Only
+ * log when we're in -d mode.
+ */
+ if (no_daemon)
+ devdlog(LOG_DEBUG, "setting %s=%s\n", var.c_str(), val.c_str());
+ _vars[var] = val;
+}
+
+void
+config::reset(void)
+{
+ _dir_list.clear();
+ delete_and_clear(_var_list_table);
+ delete_and_clear(_attach_list);
+ delete_and_clear(_detach_list);
+ delete_and_clear(_nomatch_list);
+ delete_and_clear(_notify_list);
+}
+
+void
+config::parse_one_file(const char *fn)
+{
+ devdlog(LOG_DEBUG, "Parsing %s\n", fn);
+ yyin = fopen(fn, "r");
+ if (yyin == NULL)
+ err(1, "Cannot open config file %s", fn);
+ lineno = 1;
+ if (yyparse() != 0)
+ errx(1, "Cannot parse %s at line %d", fn, lineno);
+ fclose(yyin);
+}
+
+void
+config::parse_files_in_dir(const char *dirname)
+{
+ DIR *dirp;
+ struct dirent *dp;
+ char path[PATH_MAX];
+
+ devdlog(LOG_DEBUG, "Parsing files in %s\n", dirname);
+ dirp = opendir(dirname);
+ if (dirp == NULL)
+ return;
+ readdir(dirp); /* Skip . */
+ readdir(dirp); /* Skip .. */
+ while ((dp = readdir(dirp)) != NULL) {
+ if (strcmp(dp->d_name + dp->d_namlen - 5, ".conf") == 0) {
+ snprintf(path, sizeof(path), "%s/%s",
+ dirname, dp->d_name);
+ parse_one_file(path);
+ }
+ }
+ closedir(dirp);
+}
+
+class epv_greater {
+public:
+ int operator()(event_proc *const&l1, event_proc *const&l2) const
+ {
+ return (l1->get_priority() > l2->get_priority());
+ }
+};
+
+void
+config::sort_vector(vector<event_proc *> &v)
+{
+ stable_sort(v.begin(), v.end(), epv_greater());
+}
+
+void
+config::parse(void)
+{
+ vector<string>::const_iterator i;
+
+ parse_one_file(configfile);
+ for (i = _dir_list.begin(); i != _dir_list.end(); ++i)
+ parse_files_in_dir((*i).c_str());
+ sort_vector(_attach_list);
+ sort_vector(_detach_list);
+ sort_vector(_nomatch_list);
+ sort_vector(_notify_list);
+}
+
+void
+config::open_pidfile()
+{
+ pid_t otherpid;
+
+ if (_pidfile.empty())
+ return;
+ pfh = pidfile_open(_pidfile.c_str(), 0600, &otherpid);
+ if (pfh == NULL) {
+ if (errno == EEXIST)
+ errx(1, "devd already running, pid: %d", (int)otherpid);
+ warn("cannot open pid file");
+ }
+}
+
+void
+config::write_pidfile()
+{
+
+ pidfile_write(pfh);
+}
+
+void
+config::close_pidfile()
+{
+
+ pidfile_close(pfh);
+}
+
+void
+config::remove_pidfile()
+{
+
+ pidfile_remove(pfh);
+}
+
+void
+config::add_attach(int prio, event_proc *p)
+{
+ p->set_priority(prio);
+ _attach_list.push_back(p);
+}
+
+void
+config::add_detach(int prio, event_proc *p)
+{
+ p->set_priority(prio);
+ _detach_list.push_back(p);
+}
+
+void
+config::add_directory(const char *dir)
+{
+ _dir_list.push_back(string(dir));
+}
+
+void
+config::add_nomatch(int prio, event_proc *p)
+{
+ p->set_priority(prio);
+ _nomatch_list.push_back(p);
+}
+
+void
+config::add_notify(int prio, event_proc *p)
+{
+ p->set_priority(prio);
+ _notify_list.push_back(p);
+}
+
+void
+config::set_pidfile(const char *fn)
+{
+ _pidfile = fn;
+}
+
+void
+config::push_var_table()
+{
+ var_list *vl;
+
+ vl = new var_list();
+ _var_list_table.push_back(vl);
+ devdlog(LOG_DEBUG, "Pushing table\n");
+}
+
+void
+config::pop_var_table()
+{
+ delete _var_list_table.back();
+ _var_list_table.pop_back();
+ devdlog(LOG_DEBUG, "Popping table\n");
+}
+
+void
+config::set_variable(const char *var, const char *val)
+{
+ _var_list_table.back()->set_variable(var, val);
+}
+
+const string &
+config::get_variable(const string &var)
+{
+ vector<var_list *>::reverse_iterator i;
+
+ for (i = _var_list_table.rbegin(); i != _var_list_table.rend(); ++i) {
+ if ((*i)->is_set(var))
+ return ((*i)->get_variable(var));
+ }
+ return (var_list::nothing);
+}
+
+bool
+config::is_id_char(char ch) const
+{
+ return (ch != '\0' && (isalpha(ch) || isdigit(ch) || ch == '_' ||
+ ch == '-'));
+}
+
+void
+config::expand_one(const char *&src, string &dst)
+{
+ int count;
+ string buffer;
+
+ src++;
+ // $$ -> $
+ if (*src == '$') {
+ dst += *src++;
+ return;
+ }
+
+ // $(foo) -> $(foo)
+ // Not sure if I want to support this or not, so for now we just pass
+ // it through.
+ if (*src == '(') {
+ dst += '$';
+ count = 1;
+ /* If the string ends before ) is matched , return. */
+ while (count > 0 && *src) {
+ if (*src == ')')
+ count--;
+ else if (*src == '(')
+ count++;
+ dst += *src++;
+ }
+ return;
+ }
+
+ // $[^A-Za-z] -> $\1
+ if (!isalpha(*src)) {
+ dst += '$';
+ dst += *src++;
+ return;
+ }
+
+ // $var -> replace with value
+ do {
+ buffer += *src++;
+ } while (is_id_char(*src));
+ dst.append(get_variable(buffer));
+}
+
+const string
+config::expand_string(const char *src, const char *prepend, const char *append)
+{
+ const char *var_at;
+ string dst;
+
+ /*
+ * 128 bytes is enough for 2427 of 2438 expansions that happen
+ * while parsing config files, as tested on 2013-01-30.
+ */
+ dst.reserve(128);
+
+ if (prepend != NULL)
+ dst = prepend;
+
+ for (;;) {
+ var_at = strchr(src, '$');
+ if (var_at == NULL) {
+ dst.append(src);
+ break;
+ }
+ dst.append(src, var_at - src);
+ src = var_at;
+ expand_one(src, dst);
+ }
+
+ if (append != NULL)
+ dst.append(append);
+
+ return (dst);
+}
+
+bool
+config::chop_var(char *&buffer, char *&lhs, char *&rhs) const
+{
+ char *walker;
+
+ if (*buffer == '\0')
+ return (false);
+ walker = lhs = buffer;
+ while (is_id_char(*walker))
+ walker++;
+ if (*walker != '=')
+ return (false);
+ walker++; // skip =
+ if (*walker == '"') {
+ walker++; // skip "
+ rhs = walker;
+ while (*walker && *walker != '"')
+ walker++;
+ if (*walker != '"')
+ return (false);
+ rhs[-2] = '\0';
+ *walker++ = '\0';
+ } else {
+ rhs = walker;
+ while (*walker && !isspace(*walker))
+ walker++;
+ if (*walker != '\0')
+ *walker++ = '\0';
+ rhs[-1] = '\0';
+ }
+ while (isspace(*walker))
+ walker++;
+ buffer = walker;
+ return (true);
+}
+
+
+char *
+config::set_vars(char *buffer)
+{
+ char *lhs;
+ char *rhs;
+
+ while (1) {
+ if (!chop_var(buffer, lhs, rhs))
+ break;
+ set_variable(lhs, rhs);
+ }
+ return (buffer);
+}
+
+void
+config::find_and_execute(char type)
+{
+ vector<event_proc *> *l;
+ vector<event_proc *>::const_iterator i;
+ const char *s;
+
+ switch (type) {
+ default:
+ return;
+ case notify:
+ l = &_notify_list;
+ s = "notify";
+ break;
+ case nomatch:
+ l = &_nomatch_list;
+ s = "nomatch";
+ break;
+ case attach:
+ l = &_attach_list;
+ s = "attach";
+ break;
+ case detach:
+ l = &_detach_list;
+ s = "detach";
+ break;
+ }
+ devdlog(LOG_DEBUG, "Processing %s event\n", s);
+ for (i = l->begin(); i != l->end(); ++i) {
+ if ((*i)->matches(*this)) {
+ (*i)->run(*this);
+ break;
+ }
+ }
+
+}
+
+
+static void
+process_event(char *buffer)
+{
+ char type;
+ char *sp;
+
+ sp = buffer + 1;
+ devdlog(LOG_INFO, "Processing event '%s'\n", buffer);
+ type = *buffer++;
+ cfg.push_var_table();
+ // No match doesn't have a device, and the format is a little
+ // different, so handle it separately.
+ switch (type) {
+ case notify:
+ sp = cfg.set_vars(sp);
+ break;
+ case nomatch:
+ //? at location pnp-info on bus
+ sp = strchr(sp, ' ');
+ if (sp == NULL)
+ return; /* Can't happen? */
+ *sp++ = '\0';
+ while (isspace(*sp))
+ sp++;
+ if (strncmp(sp, "at ", 3) == 0)
+ sp += 3;
+ sp = cfg.set_vars(sp);
+ while (isspace(*sp))
+ sp++;
+ if (strncmp(sp, "on ", 3) == 0)
+ cfg.set_variable("bus", sp + 3);
+ break;
+ case attach: /*FALLTHROUGH*/
+ case detach:
+ sp = strchr(sp, ' ');
+ if (sp == NULL)
+ return; /* Can't happen? */
+ *sp++ = '\0';
+ cfg.set_variable("device-name", buffer);
+ while (isspace(*sp))
+ sp++;
+ if (strncmp(sp, "at ", 3) == 0)
+ sp += 3;
+ sp = cfg.set_vars(sp);
+ while (isspace(*sp))
+ sp++;
+ if (strncmp(sp, "on ", 3) == 0)
+ cfg.set_variable("bus", sp + 3);
+ break;
+ }
+
+ cfg.find_and_execute(type);
+ cfg.pop_var_table();
+}
+
+int
+create_socket(const char *name, int socktype)
+{
+ int fd, slen;
+ struct sockaddr_un sun;
+
+ if ((fd = socket(PF_LOCAL, socktype, 0)) < 0)
+ err(1, "socket");
+ bzero(&sun, sizeof(sun));
+ sun.sun_family = AF_UNIX;
+ strlcpy(sun.sun_path, name, sizeof(sun.sun_path));
+ slen = SUN_LEN(&sun);
+ unlink(name);
+ if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0)
+ err(1, "fcntl");
+ if (::bind(fd, (struct sockaddr *) & sun, slen) < 0)
+ err(1, "bind");
+ listen(fd, 4);
+ chown(name, 0, 0); /* XXX - root.wheel */
+ chmod(name, 0666);
+ return (fd);
+}
+
+unsigned int max_clients = 10; /* Default, can be overriden on cmdline. */
+unsigned int num_clients;
+
+list<client_t> clients;
+
+void
+notify_clients(const char *data, int len)
+{
+ list<client_t>::iterator i;
+
+ /*
+ * Deliver the data to all clients. Throw clients overboard at the
+ * first sign of trouble. This reaps clients who've died or closed
+ * their sockets, and also clients who are alive but failing to keep up
+ * (or who are maliciously not reading, to consume buffer space in
+ * kernel memory or tie up the limited number of available connections).
+ */
+ for (i = clients.begin(); i != clients.end(); ) {
+ int flags;
+ if (i->socktype == SOCK_SEQPACKET)
+ flags = MSG_EOR;
+ else
+ flags = 0;
+
+ if (send(i->fd, data, len, flags) != len) {
+ --num_clients;
+ close(i->fd);
+ i = clients.erase(i);
+ devdlog(LOG_WARNING, "notify_clients: send() failed; "
+ "dropping unresponsive client\n");
+ } else
+ ++i;
+ }
+}
+
+void
+check_clients(void)
+{
+ int s;
+ struct pollfd pfd;
+ list<client_t>::iterator i;
+
+ /*
+ * Check all existing clients to see if any of them have disappeared.
+ * Normally we reap clients when we get an error trying to send them an
+ * event. This check eliminates the problem of an ever-growing list of
+ * zombie clients because we're never writing to them on a system
+ * without frequent device-change activity.
+ */
+ pfd.events = 0;
+ for (i = clients.begin(); i != clients.end(); ) {
+ pfd.fd = i->fd;
+ s = poll(&pfd, 1, 0);
+ if ((s < 0 && s != EINTR ) ||
+ (s > 0 && (pfd.revents & POLLHUP))) {
+ --num_clients;
+ close(i->fd);
+ i = clients.erase(i);
+ devdlog(LOG_NOTICE, "check_clients: "
+ "dropping disconnected client\n");
+ } else
+ ++i;
+ }
+}
+
+void
+new_client(int fd, int socktype)
+{
+ client_t s;
+ int sndbuf_size;
+
+ /*
+ * First go reap any zombie clients, then accept the connection, and
+ * shut down the read side to stop clients from consuming kernel memory
+ * by sending large buffers full of data we'll never read.
+ */
+ check_clients();
+ s.socktype = socktype;
+ s.fd = accept(fd, NULL, NULL);
+ if (s.fd != -1) {
+ sndbuf_size = CLIENT_BUFSIZE;
+ if (setsockopt(s.fd, SOL_SOCKET, SO_SNDBUF, &sndbuf_size,
+ sizeof(sndbuf_size)))
+ err(1, "setsockopt");
+ shutdown(s.fd, SHUT_RD);
+ clients.push_back(s);
+ ++num_clients;
+ } else
+ err(1, "accept");
+}
+
+static void
+event_loop(void)
+{
+ int rv;
+ int fd;
+ char buffer[DEVCTL_MAXBUF];
+ int once = 0;
+ int stream_fd, seqpacket_fd, max_fd;
+ int accepting;
+ timeval tv;
+ fd_set fds;
+
+ fd = open(PATH_DEVCTL, O_RDONLY | O_CLOEXEC);
+ if (fd == -1)
+ err(1, "Can't open devctl device %s", PATH_DEVCTL);
+ stream_fd = create_socket(STREAMPIPE, SOCK_STREAM);
+ seqpacket_fd = create_socket(SEQPACKETPIPE, SOCK_SEQPACKET);
+ accepting = 1;
+ max_fd = max(fd, max(stream_fd, seqpacket_fd)) + 1;
+ while (!romeo_must_die) {
+ if (!once && !no_daemon && !daemonize_quick) {
+ // Check to see if we have any events pending.
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+ FD_ZERO(&fds);
+ FD_SET(fd, &fds);
+ rv = select(fd + 1, &fds, &fds, &fds, &tv);
+ // No events -> we've processed all pending events
+ if (rv == 0) {
+ devdlog(LOG_DEBUG, "Calling daemon\n");
+ cfg.remove_pidfile();
+ cfg.open_pidfile();
+ daemon(0, 0);
+ cfg.write_pidfile();
+ once++;
+ }
+ }
+ /*
+ * When we've already got the max number of clients, stop
+ * accepting new connections (don't put the listening sockets in
+ * the set), shrink the accept() queue to reject connections
+ * quickly, and poll the existing clients more often, so that we
+ * notice more quickly when any of them disappear to free up
+ * client slots.
+ */
+ FD_ZERO(&fds);
+ FD_SET(fd, &fds);
+ if (num_clients < max_clients) {
+ if (!accepting) {
+ listen(stream_fd, max_clients);
+ listen(seqpacket_fd, max_clients);
+ accepting = 1;
+ }
+ FD_SET(stream_fd, &fds);
+ FD_SET(seqpacket_fd, &fds);
+ tv.tv_sec = 60;
+ tv.tv_usec = 0;
+ } else {
+ if (accepting) {
+ listen(stream_fd, 0);
+ listen(seqpacket_fd, 0);
+ accepting = 0;
+ }
+ tv.tv_sec = 2;
+ tv.tv_usec = 0;
+ }
+ rv = select(max_fd, &fds, NULL, NULL, &tv);
+ if (got_siginfo) {
+ devdlog(LOG_NOTICE, "Events received so far=%u\n",
+ total_events);
+ got_siginfo = 0;
+ }
+ if (rv == -1) {
+ if (errno == EINTR)
+ continue;
+ err(1, "select");
+ } else if (rv == 0)
+ check_clients();
+ if (FD_ISSET(fd, &fds)) {
+ rv = read(fd, buffer, sizeof(buffer) - 1);
+ if (rv > 0) {
+ total_events++;
+ if (rv == sizeof(buffer) - 1) {
+ devdlog(LOG_WARNING, "Warning: "
+ "available event data exceeded "
+ "buffer space\n");
+ }
+ notify_clients(buffer, rv);
+ buffer[rv] = '\0';
+ while (buffer[--rv] == '\n')
+ buffer[rv] = '\0';
+ process_event(buffer);
+ } else if (rv < 0) {
+ if (errno != EINTR)
+ break;
+ } else {
+ /* EOF */
+ break;
+ }
+ }
+ if (FD_ISSET(stream_fd, &fds))
+ new_client(stream_fd, SOCK_STREAM);
+ /*
+ * Aside from the socket type, both sockets use the same
+ * protocol, so we can process clients the same way.
+ */
+ if (FD_ISSET(seqpacket_fd, &fds))
+ new_client(seqpacket_fd, SOCK_SEQPACKET);
+ }
+ close(fd);
+}
+
+/*
+ * functions that the parser uses.
+ */
+void
+add_attach(int prio, event_proc *p)
+{
+ cfg.add_attach(prio, p);
+}
+
+void
+add_detach(int prio, event_proc *p)
+{
+ cfg.add_detach(prio, p);
+}
+
+void
+add_directory(const char *dir)
+{
+ cfg.add_directory(dir);
+ free(const_cast<char *>(dir));
+}
+
+void
+add_nomatch(int prio, event_proc *p)
+{
+ cfg.add_nomatch(prio, p);
+}
+
+void
+add_notify(int prio, event_proc *p)
+{
+ cfg.add_notify(prio, p);
+}
+
+event_proc *
+add_to_event_proc(event_proc *ep, eps *eps)
+{
+ if (ep == NULL)
+ ep = new event_proc();
+ ep->add(eps);
+ return (ep);
+}
+
+eps *
+new_action(const char *cmd)
+{
+ eps *e = new action(cmd);
+ free(const_cast<char *>(cmd));
+ return (e);
+}
+
+eps *
+new_match(const char *var, const char *re)
+{
+ eps *e = new match(cfg, var, re);
+ free(const_cast<char *>(var));
+ free(const_cast<char *>(re));
+ return (e);
+}
+
+eps *
+new_media(const char *var, const char *re)
+{
+ eps *e = new media(cfg, var, re);
+ free(const_cast<char *>(var));
+ free(const_cast<char *>(re));
+ return (e);
+}
+
+void
+set_pidfile(const char *name)
+{
+ cfg.set_pidfile(name);
+ free(const_cast<char *>(name));
+}
+
+void
+set_variable(const char *var, const char *val)
+{
+ cfg.set_variable(var, val);
+ free(const_cast<char *>(var));
+ free(const_cast<char *>(val));
+}
+
+
+
+static void
+gensighand(int)
+{
+ romeo_must_die = 1;
+}
+
+/*
+ * SIGINFO handler. Will print useful statistics to the syslog or stderr
+ * as appropriate
+ */
+static void
+siginfohand(int)
+{
+ got_siginfo = 1;
+}
+
+/*
+ * Local logging function. Prints to syslog if we're daemonized; stderr
+ * otherwise.
+ */
+static void
+devdlog(int priority, const char* fmt, ...)
+{
+ va_list argp;
+
+ va_start(argp, fmt);
+ if (no_daemon)
+ vfprintf(stderr, fmt, argp);
+ else if ((! quiet_mode) || (priority <= LOG_WARNING))
+ vsyslog(priority, fmt, argp);
+ va_end(argp);
+}
+
+static void
+usage()
+{
+ fprintf(stderr, "usage: %s [-dnq] [-l connlimit] [-f file]\n",
+ getprogname());
+ exit(1);
+}
+
+static void
+check_devd_enabled()
+{
+ int val = 0;
+ size_t len;
+
+ len = sizeof(val);
+ if (sysctlbyname(SYSCTL, &val, &len, NULL, 0) != 0)
+ errx(1, "devctl sysctl missing from kernel!");
+ if (val == 0) {
+ warnx("Setting " SYSCTL " to 1000");
+ val = 1000;
+ sysctlbyname(SYSCTL, NULL, NULL, &val, sizeof(val));
+ }
+}
+
+/*
+ * main
+ */
+int
+main(int argc, char **argv)
+{
+ int ch;
+
+ check_devd_enabled();
+ while ((ch = getopt(argc, argv, "df:l:nq")) != -1) {
+ switch (ch) {
+ case 'd':
+ no_daemon = 1;
+ break;
+ case 'f':
+ configfile = optarg;
+ break;
+ case 'l':
+ max_clients = MAX(1, strtoul(optarg, NULL, 0));
+ break;
+ case 'n':
+ daemonize_quick = 1;
+ break;
+ case 'q':
+ quiet_mode = 1;
+ break;
+ default:
+ usage();
+ }
+ }
+
+ cfg.parse();
+ if (!no_daemon && daemonize_quick) {
+ cfg.open_pidfile();
+ daemon(0, 0);
+ cfg.write_pidfile();
+ }
+ signal(SIGPIPE, SIG_IGN);
+ signal(SIGHUP, gensighand);
+ signal(SIGINT, gensighand);
+ signal(SIGTERM, gensighand);
+ signal(SIGINFO, siginfohand);
+ event_loop();
+ return (0);
+}
diff --git a/sbin/devd/devd.conf.5 b/sbin/devd/devd.conf.5
new file mode 100644
index 0000000..9749283
--- /dev/null
+++ b/sbin/devd/devd.conf.5
@@ -0,0 +1,578 @@
+.\"
+.\" Copyright (c) 2002 M. Warner Losh
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. The 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 AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.\" The section on comments was taken from named.conf.5, which has the
+.\" following copyright:
+.\" Copyright (c) 1999-2000 by Internet Software Consortium
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
+.\" ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+.\" OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
+.\" CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+.\" DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+.\" PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+.\" ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+.\" SOFTWARE.
+.\"
+.Dd January 14, 2015
+.Dt DEVD.CONF 5
+.Os
+.Sh NAME
+.Nm devd.conf
+.Nd configuration file for
+.Xr devd 8
+.Sh DESCRIPTION
+.Ss General Syntax
+A
+.Xr devd 8
+configuration consists of two general features, statements
+and comments.
+All statements end with a semicolon.
+Many statements can contain substatements, which are also
+terminated with a semicolon.
+.Pp
+The following statements are supported:
+.Bl -tag -width ".Ic options"
+.It Ic attach
+Specifies various matching criteria and actions to perform when
+a newly attached device matches said criteria.
+.It Ic detach
+Specifies various matching criteria and actions to perform when
+a newly detached device matches said criteria.
+.It Ic nomatch
+Specifies various matching criteria and actions to perform when
+no device driver currently loaded in the kernel claims a (new)
+device.
+.It Ic notify
+Specifies various matching criteria and actions to perform when the kernel
+sends an event notification to userland.
+.It Ic options
+Specifies various options and parameters for the operation of
+.Xr devd 8 .
+.El
+.Pp
+Statements may occur in any order in the configuration file, and may be
+repeated as often as required.
+Further details on the syntax and meaning of each statement and their
+substatements are explained below.
+.Pp
+Each statement, except
+.Ic options
+has a priority (an arbitrary number) associated with it, where
+.Ql 0
+is defined as the lowest priority.
+If two statements match the same event, only the action of the statement with
+highest priority will be executed.
+In this way generic statements can be overridden for devices or
+notifications that require special attention.
+.Pp
+The general syntax of a statement is:
+.Bd -literal -offset indent
+statement priority {
+ substatement "value";
+ ...
+ substatement "value";
+};
+.Ed
+.Ss Sub-statements
+The following sub-statements are supported within the
+.Ic options
+statement.
+.Bl -tag -width ".Ic directory"
+.It Ic directory Qq Ar /some/path ;
+Adds the given directory to the list of directories from which
+.Xr devd 8
+will read all files named "*.conf" as further
+configuration files.
+Any number of
+.Ic directory
+statements can be used.
+.It Ic pid-file Qq Pa /var/run/devd.pid ;
+Specifies PID file.
+.It Ic set Ar regexp-name Qq Ar (some|regexp) ;
+Creates a regular expression and assigns it to the variable
+.Ar regexp-name .
+The variable is available throughout the rest of
+the configuration file.
+If the string begins with
+.Ql \&! ,
+it matches if the regular expression formed by the rest of the string
+does not match.
+All regular expressions have an implicit
+.Ql ^$
+around them.
+.El
+.Pp
+The following sub-statements are supported within the
+.Ic attach
+and
+.Ic detach
+statements.
+.Bl -tag -width ".Ic directory"
+.It Ic action Qq Ar command ;
+Command to execute upon a successful match.
+Example
+.Dq Li "/etc/pccard_ether $device-name start" .
+.It Ic class Qq Ar string ;
+This is shorthand for
+.Dq Ic match Qo Li class Qc Qq Ar string .
+.It Ic device-name Qq string ;
+This is shorthand for
+.Dq Ic match Qo Li device-name Qc Qq Ar string .
+This matches a device named
+.Ar string ,
+which is allowed to be a regular expression or a variable previously created
+containing a regular expression.
+The
+.Dq Li device-name
+variable
+is available for later use with the
+.Ic action
+statement.
+.It Ic match Qo Ar variable Qc Qq Ar value ;
+Matches the content of
+.Ar value
+against
+.Ar variable ;
+the content of
+.Ar value
+may be a regular expression.
+Not required during
+.Ic attach
+nor
+.Ic detach
+events since the
+.Ic device-name
+statement takes care of all device matching.
+For a partial list of variables, see below.
+.It Ic media-type Qq Ar string ;
+For network devices,
+.Ic media-type
+will match devices that have the given media type.
+Valid media types are:
+.Dq Li Ethernet ,
+.Dq Li Tokenring ,
+.Dq Li FDDI ,
+.Dq Li 802.11 ,
+and
+.Dq Li ATM .
+.It Ic subdevice Qq Ar string ;
+This is shorthand for
+.Dq Ic match Qo Li subdevice Qc Qq Ar string .
+.El
+.Pp
+The following sub-statements are supported within the
+.Ic nomatch
+statement.
+.Bl -tag -width ".Ic directory"
+.It Ic action Qq Ar command ;
+Same as above.
+.It Ic match Qo Ar variable Qc Qq Ar value ;
+Matches the content of
+.Ar value
+against
+.Ar variable ;
+the content of
+.Ar value
+may be a regular expression.
+For a partial list of variables, see below.
+.El
+.Pp
+The following sub-statements are supported within the
+.Ic notify
+statement.
+The
+.Dq Li notify
+variable is available inside this statement and contains, a value, depending
+on which system and subsystem that delivered the event.
+.Bl -tag -width ".Ic directory"
+.It Ic action Qq Ar command ;
+Command to execute upon a successful match.
+Example
+.Dq Li "/etc/rc.d/power_profile $notify" .
+.It Ic match Qo Ar system | subsystem | type | notify Qc Qq Ar value ;
+Any number of
+.Ic match
+statements can exist within a
+.Ic notify
+statement;
+.Ar value
+can be either a fixed string or a regular expression.
+Below is a list of available systems, subsystems, and types.
+.It Ic media-type Qq Ar string ;
+See above.
+.El
+.Ss Variables that can be used with the match statement
+A partial list of variables and their possible values that can be used together
+with the
+.Ic match
+statement.
+.Pp
+.Bl -tag -width ".Li manufacturer" -compact
+.It Ic Variable
+.Ic Description
+.It Li bus
+Device name of parent bus.
+.It Li cdev
+Device node path if one is created by the
+.Xr devfs 5
+filesystem.
+.It Li cisproduct
+CIS-product.
+.It Li cisvendor
+CIS-vendor.
+.It Li class
+Device class.
+.It Li device
+Device ID.
+.It Li devclass
+Device Class (USB)
+.It Li devsubclass
+Device Sub-class (USB)
+.It Li device-name
+Name of attached/detached device.
+.It Li endpoints
+Endpoint count (USB)
+.It Li function
+Card functions.
+.It Li interface
+Interface ID (USB)
+.It Li intclass
+Interface Class (USB)
+.It Li intprotocol
+Interface Protocol (USB)
+.It Li intsubclass
+Interface Sub-class (USB)
+.It Li manufacturer
+Manufacturer ID (pccard).
+.It Li mode
+Peripheral mode (USB)
+.It Li notify
+Match the value of the
+.Dq Li notify
+variable.
+.It Li parent
+Parent device
+.It Li port
+Hub port number (USB)
+.It Li product
+Product ID (pccard/USB).
+.It Li release
+Hardware revision (USB)
+.It Li sernum
+Serial Number (USB).
+.It Li slot
+Card slot.
+.It Li subvendor
+Sub-vendor ID.
+.It Li subdevice
+Sub-device ID.
+.It Li subsystem
+Matches a subsystem of a system, see below.
+.It Li system
+Matches a system type, see below.
+.It Li type
+Type of notification, see below.
+.It Li vendor
+Vendor ID.
+.El
+.Ss Notify matching
+A partial list of systems, subsystems, and types used within the
+.Ic notify
+mechanism.
+.Pp
+.Bl -tag -width ".Li coretemp" -compact
+.It Sy System
+.It Li ACPI
+Events related to the ACPI subsystem.
+.Bl -tag -width ".Sy Subsystem" -compact
+.It Sy Subsystem
+.It Li ACAD
+AC line state ($notify=0x00 is offline, 0x01 is online).
+.It Li Button
+Button state ($notify=0x00 is power, 0x01 is sleep).
+.It Li CMBAT
+Battery events.
+.It Li Lid
+Lid state ($notify=0x00 is closed, 0x01 is open).
+.It Li PROCESSOR
+Processor state/configuration ($notify=0x81 is a change in available Cx states).
+.It Li Thermal
+Thermal zone events.
+.El
+.Pp
+.It Li IFNET
+Events related to the network subsystem.
+.Bl -tag -width ".Sy Subsystem" -compact
+.It Sy Subsystem
+.It Ar interface
+The
+.Dq subsystem
+is the actual name of the network interface on which the event
+took place.
+.Bl -tag -width ".Li LINK_DOWN" -compact
+.It Sy Type
+.It Li LINK_UP
+Carrier status changed to UP.
+.It Li LINK_DOWN
+Carrier status changed to DOWN.
+.It Li ATTACH
+The network interface is attached to the system.
+.It Li DETACH
+The network interface is detached from the system.
+.El
+.El
+.Pp
+.It Li DEVFS
+Events related to the
+.Xr devfs 5
+filesystem.
+.Bl -tag -width ".Sy Subsystem" -compact
+.It Sy Subsystem
+.It Li CDEV
+.Bl -tag -width ".Li DESTROY" -compact
+.It Sy Type
+.It Li CREATE
+The
+.Xr devfs 5
+node is created.
+.It Li DESTROY
+The
+.Xr devfs 5
+node is destroyed.
+.El
+.El
+.Pp
+.It Li GEOM
+Events related to the
+.Xr geom 4
+framework.
+The difference compared to
+.Li DEVFS
+is that
+.Li GEOM
+only includes disk-like devices.
+.Bl -tag -width ".Sy Subsystem" -compact
+.It Sy Subsystem
+.It Li DEV
+.Bl -tag -width ".Li MEDIACHANGE" -compact
+.It Sy Type
+.It Li CREATE
+A
+.Xr geom 4
+device node is created.
+.It Li DESTROY
+A
+.Xr geom 4
+device node is destroyed.
+.It Li MEDIACHANGE
+Physical media has changed.
+.El
+.El
+.Pp
+.It Li USB
+Events related to the USB subsystem.
+.Bl -tag -width ".Sy Subsystem" -compact
+.It Sy Subsystem
+.It Li DEVICE
+.Bl -tag -width ".Li DETACH" -compact
+.It Sy Type
+.It Li ATTACH
+USB device is attached to the system.
+.It Li DETACH
+USB device is detached from the system.
+.El
+.It Li INTERFACE
+.Bl -tag -width ".Li DETACH" -compact
+.It Sy Type
+.It Li ATTACH
+USB interface is attached to a device.
+.It Li DETACH
+USB interface is detached from a device.
+.El
+.El
+.Pp
+.It Li coretemp
+Events related to the
+.Xr coretemp 4
+device.
+.Bl -tag -width ".Sy Subsystem" -compact
+.It Sy Subsystem
+.It Li Thermal
+Notification that the CPU core has reached critical temperature.
+.Bl -tag -width ".Ar temperature" -compact
+.It Sy Type
+.It Ar temperature
+String containing the temperature of the core that has become too hot.
+.El
+.El
+.Pp
+.It Li kern
+Events related to the kernel.
+.Bl -tag -width ".Sy Subsystem" -compact
+.It Sy Subsystem
+.It Li power
+Information about the state of the system.
+.Bl -tag -width ".li resume" -compact
+.It Sy Type
+.It Li resume
+Notification that the system has woken from the suspended state.
+.El
+.El
+.El
+.Pp
+A link state change to UP on the interface
+.Dq Li fxp0
+would result in the following notify event:
+.Bd -literal -offset indent
+system=IFNET, subsystem=fxp0, type=LINK_UP
+.Ed
+.Pp
+An AC line state change to
+.Dq offline
+would result in the following event:
+.Bd -literal -offset indent
+system=ACPI, subsystem=ACAD, notify=0x00
+.Ed
+.Ss Comments
+Comments may appear anywhere that whitespace may appear in a
+configuration file.
+To appeal to programmers of all kinds, they can
+be written in C, C++, or shell/Perl constructs.
+.Pp
+C-style comments start with the two characters
+.Ql /*
+(slash, star) and end with
+.Ql */
+(star, slash).
+Because they are completely delimited with these characters,
+they can be used to comment only a portion of a line or to span
+multiple lines.
+.Pp
+C-style comments cannot be nested.
+For example, the following is
+not valid because the entire comment ends with the first
+.Ql */ :
+.Bd -literal -offset indent
+/* This is the start of a comment.
+ This is still part of the comment.
+/* This is an incorrect attempt at nesting a comment. */
+ This is no longer in any comment. */
+.Ed
+.Pp
+C++-style comments start with the two characters
+.Ql //
+(slash, slash) and continue to the end of the physical line.
+They cannot be continued across multiple physical lines; to have
+one logical comment span multiple lines, each line must use the
+.Ql //
+pair.
+For example:
+.Bd -literal -offset indent
+// This is the start of a comment. The next line
+// is a new comment, even though it is logically
+// part of the previous comment.
+.Ed
+.Sh FILES
+.Bl -tag -width ".Pa /etc/devd.conf" -compact
+.It Pa /etc/devd.conf
+The
+.Xr devd 8
+configuration file.
+.El
+.Sh EXAMPLES
+.Bd -literal
+#
+# This will catch link down events on the interfaces fxp0 and ath0
+#
+notify 0 {
+ match "system" "IFNET";
+ match "subsystem" "(fxp0|ath0)";
+ match "type" "LINK_DOWN";
+ action "logger $subsystem is DOWN";
+};
+
+#
+# Match lid open/close events
+# These can be combined to a single event, by passing the
+# value of $notify to the external script.
+#
+notify 0 {
+ match "system" "ACPI";
+ match "subsystem" "Lid";
+ match "notify" "0x00";
+ action "logger Lid closed, we can sleep now!";
+};
+
+notify 0 {
+ match "system" "ACPI";
+ match "subsystem" "Lid";
+ match "notify" "0x01";
+ action "logger Lid opened, the sleeper must awaken!";
+};
+
+#
+# Match a USB device type
+#
+notify 0 {
+ match "system" "USB";
+ match "subsystem" "INTERFACE";
+ match "type" "ATTACH";
+ match "intclass" "0x0e";
+ action "logger USB video device attached";
+};
+
+#
+# Try to configure ath and wi devices with pccard_ether
+# as they are attached.
+#
+attach 0 {
+ device-name "(ath|wi)[0-9]+";
+ action "/etc/pccard_ether $device-name start";
+};
+
+#
+# Stop ath and wi devices as they are detached from
+# the system.
+#
+detach 0 {
+ device-name "(ath|wi)[0-9]+";
+ action "/etc/pccard_ether $device-name stop";
+};
+.Ed
+.Pp
+The installed
+.Pa /etc/devd.conf
+has many additional examples.
+.Sh SEE ALSO
+.Xr coretemp 4 ,
+.Xr devfs 5 ,
+.Xr re_format 7 ,
+.Xr devd 8
diff --git a/sbin/devd/devd.h b/sbin/devd/devd.h
new file mode 100644
index 0000000..becfe82
--- /dev/null
+++ b/sbin/devd/devd.h
@@ -0,0 +1,58 @@
+/*-
+ * DEVD (Device action daemon)
+ *
+ * Copyright (c) 2002 M. Warner Losh <imp@freebsd.org>.
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef DEVD_H
+#define DEVD_H
+
+/** @warning This file needs to be purely 'C' compatible.
+ */
+struct event_proc;
+struct eps;
+__BEGIN_DECLS
+void add_attach(int, struct event_proc *);
+void add_detach(int, struct event_proc *);
+void add_directory(const char *);
+void add_nomatch(int, struct event_proc *);
+void add_notify(int, struct event_proc *);
+struct event_proc *add_to_event_proc(struct event_proc *, struct eps *);
+struct eps *new_match(const char *, const char *);
+struct eps *new_media(const char *, const char *);
+struct eps *new_action(const char *);
+void set_pidfile(const char *);
+void set_variable(const char *, const char *);
+void yyerror(const char *s);
+int yylex(void);
+int yyparse(void);
+__END_DECLS
+
+#define PATH_DEVCTL "/dev/devctl"
+#define DEVCTL_MAXBUF 8192
+
+#endif /* DEVD_H */
diff --git a/sbin/devd/devd.hh b/sbin/devd/devd.hh
new file mode 100644
index 0000000..c8a7610
--- /dev/null
+++ b/sbin/devd/devd.hh
@@ -0,0 +1,183 @@
+/*-
+ * Copyright (c) 2002-2003 M. Warner Losh.
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef DEVD_HH
+#define DEVD_HH
+
+class config;
+
+/**
+ * var_list is a collection of variables. These collections of variables
+ * are stacked up and popped down for each event that we have to process.
+ * We have multiple levels so that we can push variables that are unique
+ * to the event in question, in addition to having global variables. This
+ * allows for future flexibility.
+ */
+class var_list
+{
+public:
+ /** Set a variable in this var list.
+ */
+ void set_variable(const std::string &var, const std::string &val);
+ /** Get the variable out of this, and no other, var_list. If
+ * no variable of %var is set, then %bogus will be returned.
+ */
+ const std::string &get_variable(const std::string &var) const;
+ /** Is there a variable of %var set in thi stable?
+ */
+ bool is_set(const std::string &var) const;
+ /** A completely bogus string.
+ */
+ static const std::string bogus;
+ static const std::string nothing;
+private:
+ std::map<std::string, std::string> _vars;
+};
+
+/**
+ * eps is short for event_proc_single. It is a single entry in an
+ * event_proc. Each keyword needs its own subclass from eps.
+ */
+struct eps
+{
+public:
+ virtual ~eps() {}
+ /** Does this eps match the current config?
+ */
+ virtual bool do_match(config &) = 0;
+ /** Perform some action for this eps.
+ */
+ virtual bool do_action(config &) = 0;
+};
+
+/**
+ * match is the subclass used to match an individual variable. Its
+ * actions are nops.
+ */
+class match : public eps
+{
+public:
+ match(config &, const char *var, const char *re);
+ virtual ~match();
+ virtual bool do_match(config &);
+ virtual bool do_action(config &) { return true; }
+private:
+ bool _inv;
+ std::string _var;
+ std::string _re;
+ regex_t _regex;
+};
+
+/**
+ * media is the subclass used to match an individual variable. Its
+ * actions are nops.
+ */
+class media : public eps
+{
+public:
+ media(config &, const char *var, const char *type);
+ virtual ~media();
+ virtual bool do_match(config &);
+ virtual bool do_action(config &) { return true; }
+private:
+ std::string _var;
+ int _type;
+};
+
+/**
+ * action is used to fork a process. It matches everything.
+ */
+class action : public eps
+{
+public:
+ action(const char *cmd);
+ virtual ~action();
+ virtual bool do_match(config &) { return true; }
+ virtual bool do_action(config &);
+private:
+ std::string _cmd;
+};
+
+struct event_proc
+{
+public:
+ event_proc();
+ virtual ~event_proc();
+ int get_priority() const { return (_prio); }
+ void set_priority(int prio) { _prio = prio; }
+ void add(eps *);
+ bool matches(config &) const;
+ bool run(config &) const;
+private:
+ int _prio;
+ std::vector<eps *> _epsvec;
+};
+
+class config
+{
+public:
+ config() { push_var_table(); }
+ virtual ~config() { reset(); }
+ void add_attach(int, event_proc *);
+ void add_detach(int, event_proc *);
+ void add_directory(const char *);
+ void add_nomatch(int, event_proc *);
+ void add_notify(int, event_proc *);
+ void set_pidfile(const char *);
+ void reset();
+ void parse();
+ void close_pidfile();
+ void open_pidfile();
+ void write_pidfile();
+ void remove_pidfile();
+ void push_var_table();
+ void pop_var_table();
+ void set_variable(const char *var, const char *val);
+ const std::string &get_variable(const std::string &var);
+ const std::string expand_string(const char * var,
+ const char * prepend = NULL, const char * append = NULL);
+ char *set_vars(char *);
+ void find_and_execute(char);
+protected:
+ void sort_vector(std::vector<event_proc *> &);
+ void parse_one_file(const char *fn);
+ void parse_files_in_dir(const char *dirname);
+ void expand_one(const char *&src, std::string &dst);
+ bool is_id_char(char) const;
+ bool chop_var(char *&buffer, char *&lhs, char *&rhs) const;
+private:
+ std::vector<std::string> _dir_list;
+ std::string _pidfile;
+ std::vector<var_list *> _var_list_table;
+ std::vector<event_proc *> _attach_list;
+ std::vector<event_proc *> _detach_list;
+ std::vector<event_proc *> _nomatch_list;
+ std::vector<event_proc *> _notify_list;
+};
+
+#endif /* DEVD_HH */
diff --git a/sbin/devd/parse.y b/sbin/devd/parse.y
new file mode 100644
index 0000000..6334b4e
--- /dev/null
+++ b/sbin/devd/parse.y
@@ -0,0 +1,153 @@
+%{
+/*-
+ * DEVD (Device action daemon)
+ *
+ * Copyright (c) 2002 M. Warner Losh <imp@freebsd.org>.
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/cdefs.h>
+#include "devd.h"
+#include <stdio.h>
+#include <string.h>
+
+%}
+
+%union {
+ char *str;
+ int i;
+ struct eps *eps; /* EventProcStatement */
+ struct event_proc *eventproc;
+}
+
+%token SEMICOLON BEGINBLOCK ENDBLOCK COMMA
+%token <i> NUMBER
+%token <str> STRING
+%token <str> ID
+%token OPTIONS SET DIRECTORY PID_FILE DEVICE_NAME ACTION MATCH
+%token ATTACH DETACH NOMATCH NOTIFY MEDIA_TYPE CLASS SUBDEVICE
+
+%type <eventproc> match_or_action_list
+%type <eps> match_or_action match action
+
+%%
+
+config_file
+ : config_list
+ |
+ ;
+
+config_list
+ : config
+ | config_list config
+ ;
+
+config
+ : option_block
+ | attach_block
+ | detach_block
+ | nomatch_block
+ | notify_block
+ ;
+
+option_block
+ : OPTIONS BEGINBLOCK options ENDBLOCK SEMICOLON
+ ;
+
+options
+ : option
+ | options option
+
+option
+ : directory_option
+ | pid_file_option
+ | set_option
+ ;
+
+directory_option
+ : DIRECTORY STRING SEMICOLON { add_directory($2); }
+ ;
+
+pid_file_option
+ : PID_FILE STRING SEMICOLON { set_pidfile($2); }
+ ;
+
+set_option
+ : SET ID STRING SEMICOLON { set_variable($2, $3); }
+ ;
+
+attach_block
+ : ATTACH NUMBER BEGINBLOCK match_or_action_list ENDBLOCK SEMICOLON
+ { add_attach($2, $4); }
+ | ATTACH NUMBER BEGINBLOCK ENDBLOCK SEMICOLON
+ ;
+
+detach_block
+ : DETACH NUMBER BEGINBLOCK match_or_action_list ENDBLOCK SEMICOLON
+ { add_detach($2, $4); }
+ | DETACH NUMBER BEGINBLOCK ENDBLOCK SEMICOLON
+ ;
+
+nomatch_block
+ : NOMATCH NUMBER BEGINBLOCK match_or_action_list ENDBLOCK SEMICOLON
+ { add_nomatch($2, $4); }
+ | NOMATCH NUMBER BEGINBLOCK ENDBLOCK SEMICOLON
+ ;
+
+notify_block
+ : NOTIFY NUMBER BEGINBLOCK match_or_action_list ENDBLOCK SEMICOLON
+ { add_notify($2, $4); }
+ | NOTIFY NUMBER BEGINBLOCK ENDBLOCK SEMICOLON
+ ;
+
+match_or_action_list
+ : match_or_action { $$ = add_to_event_proc( NULL, $1); }
+ | match_or_action_list match_or_action
+ { $$ = add_to_event_proc($1, $2); }
+ ;
+
+match_or_action
+ : match
+ | action
+ ;
+
+match
+ : MATCH STRING STRING SEMICOLON { $$ = new_match($2, $3); }
+ | DEVICE_NAME STRING SEMICOLON
+ { $$ = new_match(strdup("device-name"), $2); }
+ | MEDIA_TYPE STRING SEMICOLON
+ { $$ = new_media(strdup("media-type"), $2); }
+ | CLASS STRING SEMICOLON
+ { $$ = new_match(strdup("class"), $2); }
+ | SUBDEVICE STRING SEMICOLON
+ { $$ = new_match(strdup("subdevice"), $2); }
+ ;
+
+action
+ : ACTION STRING SEMICOLON { $$ = new_action($2); }
+ ;
+
+%%
diff --git a/sbin/devd/tests/Makefile b/sbin/devd/tests/Makefile
new file mode 100644
index 0000000..ee679ce
--- /dev/null
+++ b/sbin/devd/tests/Makefile
@@ -0,0 +1,12 @@
+# $FreeBSD$
+
+TESTSDIR= ${TESTSBASE}/sbin/devd
+
+ATF_TESTS_C= client_test
+TEST_METADATA.client_test= required_programs="devd"
+TEST_METADATA.client_test+= required_user="root"
+TEST_METADATA.client_test+= timeout=15
+
+WARNS?= 5
+
+.include <bsd.test.mk>
diff --git a/sbin/devd/tests/client_test.c b/sbin/devd/tests/client_test.c
new file mode 100644
index 0000000..dda9a89
--- /dev/null
+++ b/sbin/devd/tests/client_test.c
@@ -0,0 +1,198 @@
+/*-
+ * Copyright (c) 2014 Spectra Logic Corporation. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <stdbool.h>
+#include <stdio.h>
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <atf-c.h>
+/* Helper functions*/
+
+/*
+ * Create two devd events. The easiest way I know of, that requires no special
+ * hardware, is to create md(4) devices.
+ */
+static void
+create_two_events(void)
+{
+ FILE *create_stdout;
+ FILE *destroy_stdout;
+ char mdname[80];
+ char destroy_cmd[80];
+ char *error;
+
+ create_stdout = popen("mdconfig -a -s 64 -t null", "r");
+ ATF_REQUIRE(create_stdout != NULL);
+ error = fgets(mdname, sizeof(mdname), create_stdout);
+ ATF_REQUIRE(error != NULL);
+ /* We only expect one line of output */
+ ATF_REQUIRE_EQ(0, pclose(create_stdout));
+
+ snprintf(destroy_cmd, nitems(destroy_cmd), "mdconfig -d -u %s", mdname);
+ destroy_stdout = popen(destroy_cmd, "r");
+ ATF_REQUIRE(destroy_stdout != NULL);
+ /* We expect no output */
+ ATF_REQUIRE_EQ(0, pclose(destroy_stdout));
+}
+
+/*
+ * Test Cases
+ */
+
+/*
+ * Open a client connection to devd, create some events, and test that they can
+ * be read _whole_ and _one_at_a_time_ from the socket
+ */
+ATF_TC_WITHOUT_HEAD(seqpacket);
+ATF_TC_BODY(seqpacket, tc)
+{
+ int s;
+ int error;
+ struct sockaddr_un devd_addr;
+ bool got_create_event = false;
+ bool got_destroy_event = false;
+ const char create_pat[] =
+ "!system=DEVFS subsystem=CDEV type=CREATE cdev=md";
+ const char destroy_pat[] =
+ "!system=DEVFS subsystem=CDEV type=DESTROY cdev=md";
+
+ memset(&devd_addr, 0, sizeof(devd_addr));
+ devd_addr.sun_family = PF_LOCAL;
+ strlcpy(devd_addr.sun_path, "/var/run/devd.seqpacket.pipe",
+ sizeof(devd_addr.sun_path));
+
+ s = socket(PF_LOCAL, SOCK_SEQPACKET, 0);
+ ATF_REQUIRE(s >= 0);
+ error = connect(s, (struct sockaddr*)&devd_addr, SUN_LEN(&devd_addr));
+ ATF_REQUIRE_EQ(0, error);
+
+ create_two_events();
+
+ /*
+ * Loop until both events are detected on _different_ reads
+ * There may be extra events due to unrelated system activity
+ * If we never get both events, then the test will timeout.
+ */
+ while (!(got_create_event && got_destroy_event)) {
+ int cmp;
+ ssize_t len;
+ char event[1024];
+
+ /* Read 1 less than sizeof(event) to allow space for NULL */
+ len = recv(s, event, sizeof(event) - 1, MSG_WAITALL);
+ ATF_REQUIRE(len != -1);
+ /* NULL terminate the result */
+ event[len] = '\0';
+ printf("%s", event);
+ cmp = strncmp(event, create_pat, sizeof(create_pat) - 1);
+ if (cmp == 0)
+ got_create_event = true;
+
+ cmp = strncmp(event, destroy_pat, sizeof(destroy_pat) - 1);
+ if (cmp == 0)
+ got_destroy_event = true;
+ }
+
+ close(s);
+}
+
+/*
+ * Open a client connection to devd using the stream socket, create some
+ * events, and test that they can be read in any number of reads.
+ */
+ATF_TC_WITHOUT_HEAD(stream);
+ATF_TC_BODY(stream, tc)
+{
+ int s;
+ int error;
+ struct sockaddr_un devd_addr;
+ bool got_create_event = false;
+ bool got_destroy_event = false;
+ const char create_pat[] =
+ "!system=DEVFS subsystem=CDEV type=CREATE cdev=md";
+ const char destroy_pat[] =
+ "!system=DEVFS subsystem=CDEV type=DESTROY cdev=md";
+ ssize_t len = 0;
+
+ memset(&devd_addr, 0, sizeof(devd_addr));
+ devd_addr.sun_family = PF_LOCAL;
+ strlcpy(devd_addr.sun_path, "/var/run/devd.pipe",
+ sizeof(devd_addr.sun_path));
+
+ s = socket(PF_LOCAL, SOCK_STREAM, 0);
+ ATF_REQUIRE(s >= 0);
+ error = connect(s, (struct sockaddr*)&devd_addr, SUN_LEN(&devd_addr));
+ ATF_REQUIRE_EQ(0, error);
+
+ create_two_events();
+
+ /*
+ * Loop until both events are detected on _different_ reads
+ * There may be extra events due to unrelated system activity
+ * If we never get both events, then the test will timeout.
+ */
+ while (!(got_create_event && got_destroy_event)) {
+ char event[1024];
+ ssize_t newlen;
+ char *create_pos, *destroy_pos;
+
+ /* Read 1 less than sizeof(event) to allow space for NULL */
+ newlen = read(s, &event[len], sizeof(event) - len - 1);
+ ATF_REQUIRE(newlen != -1);
+ len += newlen;
+ /* NULL terminate the result */
+ event[newlen] = '\0';
+ printf("%s", event);
+
+ create_pos = strstr(event, create_pat);
+ if (create_pos != NULL)
+ got_create_event = true;
+
+ destroy_pos = strstr(event, destroy_pat);
+ if (destroy_pos != NULL)
+ got_destroy_event = true;
+ }
+
+ close(s);
+}
+
+/*
+ * Main.
+ */
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, seqpacket);
+ ATF_TP_ADD_TC(tp, stream);
+
+ return (atf_no_error());
+}
+
diff --git a/sbin/devd/token.l b/sbin/devd/token.l
new file mode 100644
index 0000000..b3441c3
--- /dev/null
+++ b/sbin/devd/token.l
@@ -0,0 +1,112 @@
+%{
+/*-
+ * DEVD (Device action daemon)
+ *
+ * Copyright (c) 2002 M. Warner Losh <imp@freebsd.org>.
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include "devd.h"
+#include "y.tab.h"
+
+int lineno = 1;
+
+static void
+update_lineno(const char *cp)
+{
+ while (*cp)
+ if (*cp++ == '\n')
+ lineno++;
+}
+
+%}
+
+%option nounput
+%option noinput
+
+%%
+
+[ \t]+ ;
+\n lineno++;
+; { return SEMICOLON; }
+#.*$ ;
+\/\/.*$ ;
+\/\*([^*]|(\*+([^*\/])))*\*+\/ { update_lineno(yytext); }
+\{ { return BEGINBLOCK; }
+\} { return ENDBLOCK; }
+[0-9]+ { yylval.i = atoi(yytext); return NUMBER; }
+\"[^"]+\" {
+ int len = strlen(yytext) - 2;
+ char *walker;
+ int i;
+ update_lineno(yytext);
+ if ((yylval.str = (char *) malloc(len + 1)) == NULL)
+ goto out;
+ walker = yylval.str;
+ for (i = 1; i <= len; i++) {
+ if (yytext[i] == '\\' &&
+ yytext[i + 1] == '\n') {
+ i += 2;
+ while(isspace(yytext[i]))
+ i++;
+ }
+ *walker++ = yytext[i];
+ }
+ *walker++ = '\0';
+ out:;
+ return STRING;
+ }
+
+
+options { return OPTIONS; }
+set { return SET; }
+directory { return DIRECTORY; }
+pid-file { return PID_FILE; }
+attach { return ATTACH; }
+detach { return DETACH; }
+device-name { return DEVICE_NAME; }
+media-type { return MEDIA_TYPE; }
+class { return CLASS; }
+subdevice { return SUBDEVICE; }
+action { return ACTION; }
+match { return MATCH; }
+nomatch { return NOMATCH; }
+notify { return NOTIFY; }
+[A-Za-z][A-Za-z0-9_-]* {
+ yylval.str = strdup(yytext);
+ return ID;
+ }
+%%
+
+void
+yyerror(const char *s)
+{
+ syslog(LOG_ERR, "line %d: %s%s %s.\n", lineno, yytext, yytext?":":"", s);
+}
diff --git a/sbin/devfs/Makefile b/sbin/devfs/Makefile
new file mode 100644
index 0000000..29b0f54
--- /dev/null
+++ b/sbin/devfs/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+PROG= devfs
+SRCS= devfs.c rule.c
+MAN= devfs.8
+
+.include <bsd.prog.mk>
diff --git a/sbin/devfs/devfs.8 b/sbin/devfs/devfs.8
new file mode 100644
index 0000000..8bbdfcc
--- /dev/null
+++ b/sbin/devfs/devfs.8
@@ -0,0 +1,378 @@
+.\"
+.\" Copyright (c) 2002 Dima Dorfman.
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd July 12, 2013
+.Dt DEVFS 8
+.Os
+.Sh NAME
+.Nm devfs
+.Nd "DEVFS control"
+.Sh SYNOPSIS
+.Nm
+.Op Fl m Ar mount-point
+.Ar keyword
+.Ar argument ...
+.Sh DESCRIPTION
+The
+.Nm
+utility provides an interface to manipulate properties of
+.Xr devfs 5
+mounts.
+.Pp
+The
+.Ar keyword
+argument determines the context for
+the rest of the arguments.
+For example,
+most of the commands related to the rule subsystem must be preceded by the
+.Cm rule
+keyword.
+The following flags are common to all keywords:
+.Bl -tag -width 15n
+.It Fl m Ar mount-point
+Operate on
+.Ar mount-point ,
+which is expected to be a
+.Xr devfs 5
+mount.
+If this option is not specified,
+.Nm
+operates on
+.Pa /dev .
+.El
+.Ss Rule Subsystem
+The
+.Xr devfs 5
+rule subsystem provides a way for the administrator of a system to control
+the attributes of DEVFS nodes.
+.\" XXX devfs node? entry? what?
+Each DEVFS mount-point has a
+.Dq ruleset ,
+or a list of rules,
+associated with it.
+When a device driver creates a new node,
+all the rules in the ruleset associated with each mount-point are applied
+(see below) before the node becomes visible to the userland.
+This permits the administrator to change the properties,
+including the visibility,
+of certain nodes.
+For example, one might want to hide all disk nodes in a
+.Xr jail 2 Ns 's
+.Pa /dev .
+.Ss Rule Manipulation
+Rule manipulation commands follow the
+.Cm rule
+keyword.
+The following flags are common to all of the rule manipulation commands:
+.Bl -tag -width 15n
+.It Fl s Ar ruleset
+Operate on the ruleset with the number
+.Ar ruleset .
+If this is not specified,
+the commands operate on the ruleset currently associated with the
+specified mount-point.
+.El
+.Pp
+The following commands are recognized:
+.Bl -tag -width 15n
+.It Cm rule add Oo Ar rulenum Oc Ar rulespec
+Add the rule described by
+.Ar rulespec
+(defined below)
+to the ruleset.
+The rule has the number
+.Ar rulenum
+if it is explicitly specified;
+otherwise, the rule number is automatically determined by the kernel.
+.It Cm rule apply Ar rulenum | rulespec
+Apply rule number
+.Ar rulenum
+or the rule described by
+.Ar rulespec
+to the mount-point.
+Rules that are
+.Dq applied
+have their conditions checked against all nodes
+in the mount-point and the actions taken if they match.
+.It Cm rule applyset
+Apply all the rules in the ruleset to the mount-point
+(see above for the definition of
+.Dq apply ) .
+.It Cm rule del Ar rulenum
+Delete rule number
+.Ar rulenum
+from the ruleset.
+.It Cm rule delset
+Delete all rules from the ruleset.
+.It Cm rule show Op Ar rulenum
+Display the rule number
+.Ar rulenum ,
+or all the rules in the ruleset.
+The output lines (one line per rule) are expected to be valid
+.Ar rulespec Ns s .
+.It Cm rule showsets
+Report the numbers of existing rulesets.
+.It Cm ruleset Ar ruleset
+Set ruleset number
+.Ar ruleset
+as the current ruleset for the mount-point.
+.El
+.Ss Rule Specification
+Rules have two parts: the conditions and the actions.
+The conditions determine which DEVFS nodes the rule matches
+and the actions determine what should be done when a rule matches a node.
+For example, a rule can be written that sets the GID to
+.Dq Li operator
+for all devices of type tape.
+If the first token of a rule specification is a single dash
+.Pq Sq Fl ,
+rules are read from the standard input and the rest of the specification
+is ignored.
+.Pp
+The following conditions are recognized.
+Conditions are ANDed together when matching a device;
+if OR is desired, multiple rules can be written.
+.Bl -tag -width 15n
+.It Cm path Ar pattern
+Matches any node with a path that matches
+.Ar pattern ,
+which is interpreted as a
+.Xr glob 3 Ns -style
+pattern.
+.It Cm type Ar devtype
+Matches any node that is of type
+.Ar devtype .
+Valid types are
+.Cm disk , mem , tape
+and
+.Cm tty .
+.El
+.Pp
+The following actions are recognized.
+Although there is no explicit delimiter between conditions and actions,
+they may not be intermixed.
+.Bl -tag -width 15n
+.It Cm group Ar gid
+Set the GID of the node to
+.Ar gid ,
+which may be a group name
+(looked up in
+.Pa /etc/group )
+or number.
+.It Cm hide
+Hide the node.
+Nodes may later be revived manually with
+.Xr mknod 8
+or with the
+.Cm unhide
+action.
+Hiding a directory node effectively hides all of its child nodes.
+.It Cm include Ar ruleset
+Apply all the rules in ruleset number
+.Ar ruleset
+to the node.
+This does not necessarily result in any changes to the node
+(e.g., if none of the rules in the included ruleset match).
+Include commands in the referenced
+.Ar ruleset
+are not resolved.
+.It Cm mode Ar filemode
+Set the file mode to
+.Ar filemode ,
+which is interpreted as in
+.Xr chmod 1 .
+.It Cm user Ar uid
+Set the UID to
+.Ar uid ,
+which may be a user name
+(looked up in
+.Pa /etc/passwd )
+or number.
+.It Cm unhide
+Unhide the node.
+If the node resides in a subdirectory,
+all parent directory nodes must be visible to be able to access the node.
+.El
+.Sh IMPLEMENTATION NOTES
+Rulesets are created by the kernel at the first reference
+and destroyed when the last reference disappears.
+E.g., a ruleset is created when a rule is added to it or when it is set
+as the current ruleset for a mount-point, and
+a ruleset is destroyed when the last rule in it is deleted
+and no other references to it exist
+(i.e., it is not included by any rules and it is not the current ruleset
+for any mount-point).
+.Pp
+Ruleset number 0 is the default ruleset for all new mount-points.
+It is always empty, cannot be modified or deleted, and does not show up
+in the output of
+.Cm showsets .
+.Pp
+Rules and rulesets are unique to the entire system,
+not a particular mount-point.
+I.e., a
+.Cm showsets
+will return the same information regardless of the mount-point specified with
+.Fl m .
+The mount-point is only relevant when changing what its current ruleset is
+or when using one of the apply commands.
+.Sh FILES
+.Bl -tag -width "Pa /usr/share/examples/etc/devfs.conf" -compact
+.It Pa /etc/defaults/devfs.rules
+Default
+.Nm
+configuration file.
+.It Pa /etc/devfs.rules
+Local
+.Nm
+configuration file. Rulesets in here override those in
+.Pa /etc/defaults/devfs.rules
+with the same ruleset number, otherwise the two files are effectively merged.
+.It Pa /etc/devfs.conf
+Boot-time
+.Nm
+configuration file.
+.It Pa /usr/share/examples/etc/devfs.conf
+Example boot-time
+.Nm
+configuration file.
+.El
+.Sh EXAMPLES
+When the system boots,
+the only ruleset that exists is ruleset number 0;
+since the latter may not be modified, we have to create another ruleset
+before adding rules.
+Note that since most of the following examples do not specify
+.Fl m ,
+the operations are performed on
+.Pa /dev
+(this only matters for things that might change the properties of nodes).
+.Pp
+Specify that ruleset 10 should be the current ruleset for
+.Pa /dev
+(if it does not already exist, it is created):
+.Pp
+.Dl "devfs ruleset 10"
+.Pp
+Add a rule that causes all nodes that have a path that matches
+.Dq Li speaker
+(this is only
+.Pa /dev/speaker )
+to have the file mode 666 (read and write for all).
+Note that if any such nodes already exist, their mode will not be changed
+unless this rule (or ruleset) is explicitly applied (see below).
+The mode
+.Em will
+be changed if the node is created
+.Em after
+the rule is added
+(e.g., the
+.Pa atspeaker
+module is loaded after the above rule is added):
+.Pp
+.Dl "devfs rule add path speaker mode 666"
+.Pp
+Apply all the rules in the current ruleset to all the existing nodes.
+E.g., if the below rule was added after
+.Pa /dev/speaker
+was created,
+this command will cause its file mode to be changed to 666
+as prescribed by the rule:
+.Pp
+.Dl "devfs rule applyset"
+.Pp
+For all devices with a path that matches
+.Dq Li snp* ,
+set the file mode to 660 and the GID to
+.Dq Li snoopers .
+This permits users in the
+.Dq Li snoopers
+group to use the
+.Xr snp 4
+devices
+(quoting the argument to
+.Cm path
+is often necessary to disable the shell's globbing features):
+.Pp
+.Dl devfs rule add path "snp*" mode 660 group snoopers
+.Pp
+Add a rule to ruleset number 20.
+Since this ruleset is not the current ruleset for any mount-points,
+this rule is never applied automatically (unless ruleset 20 becomes
+a current ruleset for some mount-point at a later time):
+.Pp
+.Dl "devfs rule -s 20 add type disk group wheel"
+.Pp
+Explicitly apply all rules in ruleset number 20 to the DEVFS mount on
+.Pa /my/jail/dev .
+It does not matter that ruleset 20 is not the current ruleset for that
+mount-point; the rules are still applied:
+.Pp
+.Dl "devfs -m /my/jail/dev rule -s 20 applyset"
+.Pp
+Since the following rule has no conditions, the action
+.Pq Cm hide
+will be applied to all nodes:
+.Pp
+.Dl "devfs rule apply hide"
+.Pp
+Since hiding all nodes is not very useful, we can undo it.
+The following applies
+.Cm unhide
+to all the nodes,
+causing them to reappear:
+.Pp
+.Dl "devfs rule apply unhide"
+.Pp
+Add all the rules from the file
+.Pa my_rules
+to ruleset 10:
+.Pp
+.Dl "devfs rule -s 10 add - < my_rules"
+.Pp
+The below copies all the rules from ruleset 20 into ruleset 10.
+The rule numbers are preserved,
+but ruleset 10 may already have rules with non-conflicting numbers
+(these will be preserved).
+Since
+.Cm show
+outputs valid rules,
+this feature can be used to copy rulesets:
+.Pp
+.Dl "devfs rule -s 20 show | devfs rule -s 10 add -"
+.Sh SEE ALSO
+.Xr chmod 1 ,
+.Xr jail 2 ,
+.Xr glob 3 ,
+.Xr devfs 5 ,
+.Xr devfs.conf 5 ,
+.Xr devfs.rules 5 ,
+.Xr chown 8 ,
+.Xr jail 8 ,
+.Xr mknod 8
+.Sh AUTHORS
+.An Dima Dorfman
diff --git a/sbin/devfs/devfs.c b/sbin/devfs/devfs.c
new file mode 100644
index 0000000..0e7b662
--- /dev/null
+++ b/sbin/devfs/devfs.c
@@ -0,0 +1,230 @@
+/*-
+ * Copyright (c) 2001, 2002 Dima Dorfman.
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * DEVFS control.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/queue.h>
+
+#include <assert.h>
+#include <err.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "extern.h"
+
+int mpfd;
+
+static ctbl_t ctbl_main = {
+ { "rule", rule_main },
+ { "ruleset", ruleset_main },
+ { NULL, NULL }
+};
+
+int
+main(int ac, char **av)
+{
+ const char *mountpt;
+ struct cmd *c;
+ int ch;
+
+ mountpt = NULL;
+ while ((ch = getopt(ac, av, "m:")) != -1)
+ switch (ch) {
+ case 'm':
+ mountpt = optarg;
+ break;
+ default:
+ usage();
+ }
+ ac -= optind;
+ av += optind;
+ if (ac < 1)
+ usage();
+
+ if (mountpt == NULL)
+ mountpt = _PATH_DEV;
+ mpfd = open(mountpt, O_RDONLY);
+ if (mpfd == -1)
+ err(1, "open: %s", mountpt);
+
+ for (c = ctbl_main; c->name != NULL; ++c)
+ if (strcmp(c->name, av[0]) == 0)
+ exit((*c->handler)(ac, av));
+ errx(1, "unknown command: %s", av[0]);
+}
+
+/*
+ * Convert an integer to a "number" (ruleset numbers and rule numbers
+ * are 16-bit). If the conversion is successful, num contains the
+ * integer representation of s and 1 is returned; otherwise, 0 is
+ * returned and num is unchanged.
+ */
+int
+atonum(const char *s, uint16_t *num)
+{
+ unsigned long ul;
+ char *cp;
+
+ ul = strtoul(s, &cp, 10);
+ if (ul > UINT16_MAX || *cp != '\0')
+ return (0);
+ *num = (uint16_t)ul;
+ return (1);
+}
+
+/*
+ * Convert user input in ASCII to an integer.
+ */
+int
+eatoi(const char *s)
+{
+ char *cp;
+ long l;
+
+ l = strtol(s, &cp, 10);
+ if (l > INT_MAX || *cp != '\0')
+ errx(1, "error converting to integer: %s", s);
+ return ((int)l);
+}
+
+/*
+ * As atonum(), but the result of failure is death.
+ */
+uint16_t
+eatonum(const char *s)
+{
+ uint16_t num;
+
+ if (!atonum(s, &num))
+ errx(1, "error converting to number: %s", s); /* XXX clarify */
+ return (num);
+}
+
+/*
+ * Read a line from a /FILE/. If the return value isn't 0, it is the
+ * length of the line, a pointer to which exists in /line/. It is the
+ * caller's responsibility to free(3) it. If the return value is 0,
+ * there was an error or we reached EOF, and /line/ is undefined (so,
+ * obviously, the caller shouldn't try to free(3) it).
+ */
+size_t
+efgetln(FILE *fp, char **line)
+{
+ size_t rv;
+ char *cp;
+
+ cp = fgetln(fp, &rv);
+ if (cp == NULL) {
+ *line = NULL;
+ return (rv);
+ }
+ if (cp[rv - 1] == '\n') {
+ cp[rv - 1] = '\0';
+ *line = strdup(cp);
+ if (*line == NULL)
+ errx(1, "cannot allocate memory");
+ --rv;
+ } else {
+ *line = malloc(rv + 1);
+ if (*line == NULL)
+ errx(1, "cannot allocate memory");
+ memcpy(*line, cp, rv);
+ (*line)[rv] = '\0';
+ }
+ assert(rv == strlen(*line));
+ return (rv);
+}
+
+struct ptrstq {
+ STAILQ_ENTRY(ptrstq) tq;
+ void *ptr;
+};
+
+/*
+ * Create an argument vector from /line/. The caller must free(3)
+ * /avp/, and /avp[0]/ when the argument vector is no longer
+ * needed unless /acp/ is 0, in which case /avp/ is undefined.
+ * /avp/ is NULL-terminated, so it is actually one longer than /acp/.
+ */
+void
+tokenize(const char *line, int *acp, char ***avp)
+{
+ static const char *delims = " \t\n";
+ struct ptrstq *pt;
+ STAILQ_HEAD(, ptrstq) plist;
+ char **ap, *cp, *wline, *xcp;
+
+ line += strspn(line, delims);
+ wline = strdup(line);
+ if (wline == NULL)
+ errx(1, "cannot allocate memory");
+
+ STAILQ_INIT(&plist);
+ for (xcp = wline, *acp = 0;
+ (cp = strsep(&xcp, delims)) != NULL;)
+ if (*cp != '\0') {
+ pt = calloc(1, sizeof(*pt));
+ if (pt == NULL)
+ errx(1, "cannot allocate memory");
+ pt->ptr = cp;
+ STAILQ_INSERT_TAIL(&plist, pt, tq);
+ ++*acp;
+ }
+ if (*acp == 0)
+ return;
+ assert(STAILQ_FIRST(&plist)->ptr == wline);
+ *avp = malloc(sizeof(**avp) * (*acp + 1));
+ if (*avp == NULL)
+ errx(1, "cannot allocate memory");
+ for (ap = *avp; !STAILQ_EMPTY(&plist);) {
+ pt = STAILQ_FIRST(&plist);
+ *ap = pt->ptr;
+ ++ap;
+ assert(ap <= *avp + (*acp));
+ STAILQ_REMOVE_HEAD(&plist, tq);
+ free(pt);
+ }
+ *ap = NULL;
+}
+
+void
+usage(void)
+{
+
+ fprintf(stderr, "usage: devfs rule|ruleset arguments\n");
+ exit(1);
+}
diff --git a/sbin/devfs/extern.h b/sbin/devfs/extern.h
new file mode 100644
index 0000000..a9f8d22
--- /dev/null
+++ b/sbin/devfs/extern.h
@@ -0,0 +1,57 @@
+/*-
+ * Copyright (c) 2002 Dima Dorfman.
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef __DEVFS_H__
+#define __DEVFS_H__
+
+#include <fs/devfs/devfs.h>
+
+struct intstr {
+ const char *s;
+ int i;
+};
+
+typedef int (command_t)(int, char **);
+struct cmd {
+ const char *name;
+ command_t *handler;
+};
+typedef struct cmd ctbl_t[];
+
+command_t rule_main, ruleset_main;
+
+int atonum(const char *, uint16_t *);
+int eatoi(const char *);
+uint16_t eatonum(const char *);
+size_t efgetln(FILE *, char **);
+void tokenize(const char *, int *, char ***);
+void usage(void) __dead2;
+
+extern int mpfd; /* Mount-point file descriptor. */
+
+#endif /* !__DEVFS_H__ */
diff --git a/sbin/devfs/rule.c b/sbin/devfs/rule.c
new file mode 100644
index 0000000..bb3ebe1
--- /dev/null
+++ b/sbin/devfs/rule.c
@@ -0,0 +1,462 @@
+/*-
+ * Copyright (c) 2002 Dima Dorfman.
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Rule subsystem manipulation.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/conf.h>
+#include <sys/ioctl.h>
+
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <grp.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "extern.h"
+
+static void rulespec_infp(FILE *fp, unsigned long request, devfs_rsnum rsnum);
+static void rulespec_instr(struct devfs_rule *dr, const char *str,
+ devfs_rsnum rsnum);
+static void rulespec_intok(struct devfs_rule *dr, int ac, char **av,
+ devfs_rsnum rsnum);
+static void rulespec_outfp(FILE *fp, struct devfs_rule *dr);
+
+static command_t rule_add, rule_apply, rule_applyset;
+static command_t rule_del, rule_delset, rule_show, rule_showsets;
+
+static ctbl_t ctbl_rule = {
+ { "add", rule_add },
+ { "apply", rule_apply },
+ { "applyset", rule_applyset },
+ { "del", rule_del },
+ { "delset", rule_delset },
+ { "show", rule_show },
+ { "showsets", rule_showsets },
+ { NULL, NULL }
+};
+
+static struct intstr ist_type[] = {
+ { "disk", D_DISK },
+ { "mem", D_MEM },
+ { "tape", D_TAPE },
+ { "tty", D_TTY },
+ { NULL, -1 }
+};
+
+static devfs_rsnum in_rsnum;
+
+int
+rule_main(int ac, char **av)
+{
+ struct cmd *c;
+ int ch;
+
+ setprogname("devfs rule");
+ optreset = optind = 1;
+ while ((ch = getopt(ac, av, "s:")) != -1)
+ switch (ch) {
+ case 's':
+ in_rsnum = eatonum(optarg);
+ break;
+ default:
+ usage();
+ }
+ ac -= optind;
+ av += optind;
+ if (ac < 1)
+ usage();
+
+ for (c = ctbl_rule; c->name != NULL; ++c)
+ if (strcmp(c->name, av[0]) == 0)
+ exit((*c->handler)(ac, av));
+ errx(1, "unknown command: %s", av[0]);
+}
+
+static int
+rule_add(int ac, char **av)
+{
+ struct devfs_rule dr;
+ int rv;
+
+ if (ac < 2)
+ usage();
+ if (strcmp(av[1], "-") == 0)
+ rulespec_infp(stdin, DEVFSIO_RADD, in_rsnum);
+ else {
+ rulespec_intok(&dr, ac - 1, av + 1, in_rsnum);
+ rv = ioctl(mpfd, DEVFSIO_RADD, &dr);
+ if (rv == -1)
+ err(1, "ioctl DEVFSIO_RADD");
+ }
+ return (0);
+}
+
+static int
+rule_apply(int ac __unused, char **av __unused)
+{
+ struct devfs_rule dr;
+ devfs_rnum rnum;
+ devfs_rid rid;
+ int rv;
+
+ if (ac < 2)
+ usage();
+ if (!atonum(av[1], &rnum)) {
+ if (strcmp(av[1], "-") == 0)
+ rulespec_infp(stdin, DEVFSIO_RAPPLY, in_rsnum);
+ else {
+ rulespec_intok(&dr, ac - 1, av + 1, in_rsnum);
+ rv = ioctl(mpfd, DEVFSIO_RAPPLY, &dr);
+ if (rv == -1)
+ err(1, "ioctl DEVFSIO_RAPPLY");
+ }
+ } else {
+ rid = mkrid(in_rsnum, rnum);
+ rv = ioctl(mpfd, DEVFSIO_RAPPLYID, &rid);
+ if (rv == -1)
+ err(1, "ioctl DEVFSIO_RAPPLYID");
+ }
+ return (0);
+}
+
+static int
+rule_applyset(int ac, char **av __unused)
+{
+ int rv;
+
+ if (ac != 1)
+ usage();
+ rv = ioctl(mpfd, DEVFSIO_SAPPLY, &in_rsnum);
+ if (rv == -1)
+ err(1, "ioctl DEVFSIO_SAPPLY");
+ return (0);
+}
+
+static int
+rule_del(int ac __unused, char **av)
+{
+ devfs_rid rid;
+ int rv;
+
+ if (av[1] == NULL)
+ usage();
+ rid = mkrid(in_rsnum, eatoi(av[1]));
+ rv = ioctl(mpfd, DEVFSIO_RDEL, &rid);
+ if (rv == -1)
+ err(1, "ioctl DEVFSIO_RDEL");
+ return (0);
+}
+
+static int
+rule_delset(int ac, char **av __unused)
+{
+ struct devfs_rule dr;
+ int rv;
+
+ if (ac != 1)
+ usage();
+ memset(&dr, '\0', sizeof(dr));
+ dr.dr_magic = DEVFS_MAGIC;
+ dr.dr_id = mkrid(in_rsnum, 0);
+ while (ioctl(mpfd, DEVFSIO_RGETNEXT, &dr) != -1) {
+ rv = ioctl(mpfd, DEVFSIO_RDEL, &dr.dr_id);
+ if (rv == -1)
+ err(1, "ioctl DEVFSIO_RDEL");
+ }
+ if (errno != ENOENT)
+ err(1, "ioctl DEVFSIO_RGETNEXT");
+ return (0);
+}
+
+static int
+rule_show(int ac __unused, char **av)
+{
+ struct devfs_rule dr;
+ devfs_rnum rnum;
+ int rv;
+
+ memset(&dr, '\0', sizeof(dr));
+ dr.dr_magic = DEVFS_MAGIC;
+ if (av[1] != NULL) {
+ rnum = eatoi(av[1]);
+ dr.dr_id = mkrid(in_rsnum, rnum - 1);
+ rv = ioctl(mpfd, DEVFSIO_RGETNEXT, &dr);
+ if (rv == -1)
+ err(1, "ioctl DEVFSIO_RGETNEXT");
+ if (rid2rn(dr.dr_id) == rnum)
+ rulespec_outfp(stdout, &dr);
+ } else {
+ dr.dr_id = mkrid(in_rsnum, 0);
+ while (ioctl(mpfd, DEVFSIO_RGETNEXT, &dr) != -1)
+ rulespec_outfp(stdout, &dr);
+ if (errno != ENOENT)
+ err(1, "ioctl DEVFSIO_RGETNEXT");
+ }
+ return (0);
+}
+
+static int
+rule_showsets(int ac, char **av __unused)
+{
+ devfs_rsnum rsnum;
+
+ if (ac != 1)
+ usage();
+ rsnum = 0;
+ while (ioctl(mpfd, DEVFSIO_SGETNEXT, &rsnum) != -1)
+ printf("%d\n", rsnum);
+ if (errno != ENOENT)
+ err(1, "ioctl DEVFSIO_SGETNEXT");
+ return (0);
+}
+
+int
+ruleset_main(int ac, char **av)
+{
+ devfs_rsnum rsnum;
+ int rv;
+
+ setprogname("devfs ruleset");
+ if (ac < 2)
+ usage();
+ rsnum = eatonum(av[1]);
+ rv = ioctl(mpfd, DEVFSIO_SUSE, &rsnum);
+ if (rv == -1)
+ err(1, "ioctl DEVFSIO_SUSE");
+ return (0);
+}
+
+
+/*
+ * Input rules from a file (probably the standard input). This
+ * differs from the other rulespec_in*() routines in that it also
+ * calls ioctl() for the rules, since it is impractical (and not very
+ * useful) to return a list (or array) of rules, just so the caller
+ * can call call ioctl() for each of them.
+ */
+static void
+rulespec_infp(FILE *fp, unsigned long request, devfs_rsnum rsnum)
+{
+ struct devfs_rule dr;
+ char *line;
+ int rv;
+
+ assert(fp == stdin); /* XXX: De-hardcode "stdin" from error msg. */
+ while (efgetln(fp, &line)) {
+ rulespec_instr(&dr, line, rsnum);
+ rv = ioctl(mpfd, request, &dr);
+ if (rv == -1)
+ err(1, "ioctl");
+ free(line); /* efgetln() always malloc()s. */
+ }
+ if (ferror(stdin))
+ err(1, "stdin");
+}
+
+/*
+ * Construct a /struct devfs_rule/ from a string.
+ */
+static void
+rulespec_instr(struct devfs_rule *dr, const char *str, devfs_rsnum rsnum)
+{
+ char **av;
+ int ac;
+
+ tokenize(str, &ac, &av);
+ if (ac == 0)
+ errx(1, "unexpected end of rulespec");
+ rulespec_intok(dr, ac, av, rsnum);
+ free(av[0]);
+ free(av);
+}
+
+/*
+ * Construct a /struct devfs_rule/ from ac and av.
+ */
+static void
+rulespec_intok(struct devfs_rule *dr, int ac __unused, char **av,
+ devfs_rsnum rsnum)
+{
+ struct intstr *is;
+ struct passwd *pw;
+ struct group *gr;
+ devfs_rnum rnum;
+ void *set;
+
+ memset(dr, '\0', sizeof(*dr));
+
+ /*
+ * We don't maintain ac hereinafter.
+ */
+ if (av[0] == NULL)
+ errx(1, "unexpected end of rulespec");
+
+ /* If the first argument is an integer, treat it as a rule number. */
+ if (!atonum(av[0], &rnum))
+ rnum = 0; /* auto-number */
+ else
+ ++av;
+
+ /*
+ * These aren't table-driven since that would result in more
+ * tiny functions than I care to deal with.
+ */
+ for (;;) {
+ if (av[0] == NULL)
+ break;
+ else if (strcmp(av[0], "type") == 0) {
+ if (av[1] == NULL)
+ errx(1, "expecting argument for type");
+ for (is = ist_type; is->s != NULL; ++is)
+ if (strcmp(av[1], is->s) == 0) {
+ dr->dr_dswflags |= is->i;
+ break;
+ }
+ if (is->s == NULL)
+ errx(1, "unknown type: %s", av[1]);
+ dr->dr_icond |= DRC_DSWFLAGS;
+ av += 2;
+ } else if (strcmp(av[0], "path") == 0) {
+ if (av[1] == NULL)
+ errx(1, "expecting argument for path");
+ if (strlcpy(dr->dr_pathptrn, av[1], DEVFS_MAXPTRNLEN)
+ >= DEVFS_MAXPTRNLEN)
+ warnx("pattern specified too long; truncated");
+ dr->dr_icond |= DRC_PATHPTRN;
+ av += 2;
+ } else
+ break;
+ }
+ while (av[0] != NULL) {
+ if (strcmp(av[0], "hide") == 0) {
+ dr->dr_iacts |= DRA_BACTS;
+ dr->dr_bacts |= DRB_HIDE;
+ ++av;
+ } else if (strcmp(av[0], "unhide") == 0) {
+ dr->dr_iacts |= DRA_BACTS;
+ dr->dr_bacts |= DRB_UNHIDE;
+ ++av;
+ } else if (strcmp(av[0], "user") == 0) {
+ if (av[1] == NULL)
+ errx(1, "expecting argument for user");
+ dr->dr_iacts |= DRA_UID;
+ pw = getpwnam(av[1]);
+ if (pw != NULL)
+ dr->dr_uid = pw->pw_uid;
+ else
+ dr->dr_uid = eatoi(av[1]); /* XXX overflow */
+ av += 2;
+ } else if (strcmp(av[0], "group") == 0) {
+ if (av[1] == NULL)
+ errx(1, "expecting argument for group");
+ dr->dr_iacts |= DRA_GID;
+ gr = getgrnam(av[1]);
+ if (gr != NULL)
+ dr->dr_gid = gr->gr_gid;
+ else
+ dr->dr_gid = eatoi(av[1]); /* XXX overflow */
+ av += 2;
+ } else if (strcmp(av[0], "mode") == 0) {
+ if (av[1] == NULL)
+ errx(1, "expecting argument for mode");
+ dr->dr_iacts |= DRA_MODE;
+ set = setmode(av[1]);
+ if (set == NULL)
+ errx(1, "invalid mode: %s", av[1]);
+ dr->dr_mode = getmode(set, 0);
+ av += 2;
+ } else if (strcmp(av[0], "include") == 0) {
+ if (av[1] == NULL)
+ errx(1, "expecting argument for include");
+ dr->dr_iacts |= DRA_INCSET;
+ dr->dr_incset = eatonum(av[1]);
+ av += 2;
+ } else
+ errx(1, "unknown argument: %s", av[0]);
+ }
+
+ dr->dr_id = mkrid(rsnum, rnum);
+ dr->dr_magic = DEVFS_MAGIC;
+}
+
+/*
+ * Write a human-readable (and machine-parsable, by rulespec_in*())
+ * representation of dr to bufp. *bufp should be free(3)'d when the
+ * caller is finished with it.
+ */
+static void
+rulespec_outfp(FILE *fp, struct devfs_rule *dr)
+{
+ struct intstr *is;
+ struct passwd *pw;
+ struct group *gr;
+
+ fprintf(fp, "%d", rid2rn(dr->dr_id));
+
+ if (dr->dr_icond & DRC_DSWFLAGS)
+ for (is = ist_type; is->s != NULL; ++is)
+ if (dr->dr_dswflags & is->i)
+ fprintf(fp, " type %s", is->s);
+ if (dr->dr_icond & DRC_PATHPTRN)
+ fprintf(fp, " path %s", dr->dr_pathptrn);
+
+ if (dr->dr_iacts & DRA_BACTS) {
+ if (dr->dr_bacts & DRB_HIDE)
+ fprintf(fp, " hide");
+ if (dr->dr_bacts & DRB_UNHIDE)
+ fprintf(fp, " unhide");
+ }
+ if (dr->dr_iacts & DRA_UID) {
+ pw = getpwuid(dr->dr_uid);
+ if (pw == NULL)
+ fprintf(fp, " user %d", dr->dr_uid);
+ else
+ fprintf(fp, " user %s", pw->pw_name);
+ }
+ if (dr->dr_iacts & DRA_GID) {
+ gr = getgrgid(dr->dr_gid);
+ if (gr == NULL)
+ fprintf(fp, " group %d", dr->dr_gid);
+ else
+ fprintf(fp, " group %s", gr->gr_name);
+ }
+ if (dr->dr_iacts & DRA_MODE)
+ fprintf(fp, " mode %o", dr->dr_mode);
+ if (dr->dr_iacts & DRA_INCSET)
+ fprintf(fp, " include %d", dr->dr_incset);
+
+ fprintf(fp, "\n");
+}
diff --git a/sbin/dhclient/Makefile b/sbin/dhclient/Makefile
new file mode 100644
index 0000000..868df6d
--- /dev/null
+++ b/sbin/dhclient/Makefile
@@ -0,0 +1,52 @@
+# $OpenBSD: Makefile,v 1.9 2004/05/04 12:52:05 henning Exp $
+# $FreeBSD$
+#
+# Copyright (c) 1996, 1997 The Internet Software Consortium.
+# 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 Internet Software Consortium 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 INTERNET SOFTWARE CONSORTIUM 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 INTERNET SOFTWARE CONSORTIUM 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 <src.opts.mk>
+
+SRCS= dhclient.c clparse.c alloc.c dispatch.c hash.c bpf.c options.c \
+ tree.c conflex.c errwarn.c inet.c packet.c convert.c tables.c \
+ parse.c privsep.c
+
+PROG= dhclient
+SCRIPTS=dhclient-script
+MAN= dhclient.8 dhclient.conf.5 dhclient.leases.5 dhcp-options.5 \
+ dhclient-script.8
+LIBADD= util
+
+WARNS?= 2
+
+.if ${MK_TESTS} != "no"
+SUBDIR+= tests
+.endif
+
+.include <bsd.prog.mk>
diff --git a/sbin/dhclient/alloc.c b/sbin/dhclient/alloc.c
new file mode 100644
index 0000000..cd60cec
--- /dev/null
+++ b/sbin/dhclient/alloc.c
@@ -0,0 +1,79 @@
+/* $OpenBSD: alloc.c,v 1.9 2004/05/04 20:28:40 deraadt Exp $ */
+
+/* Memory allocation... */
+
+/*
+ * Copyright (c) 1995, 1996, 1998 The Internet Software Consortium.
+ * 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 Internet Software Consortium 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 INTERNET SOFTWARE CONSORTIUM 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 INTERNET SOFTWARE CONSORTIUM OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * This software has been written for the Internet Software Consortium
+ * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
+ * Enterprises. To learn more about the Internet Software Consortium,
+ * see ``http://www.vix.com/isc''. To learn more about Vixie
+ * Enterprises, see ``http://www.vix.com''.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include "dhcpd.h"
+
+struct string_list *
+new_string_list(size_t size)
+{
+ struct string_list *rval;
+
+ rval = calloc(1, sizeof(struct string_list) + size);
+ if (rval != NULL)
+ rval->string = ((char *)rval) + sizeof(struct string_list);
+ return (rval);
+}
+
+struct hash_table *
+new_hash_table(int count)
+{
+ struct hash_table *rval;
+
+ rval = calloc(1, sizeof(struct hash_table) -
+ (DEFAULT_HASH_SIZE * sizeof(struct hash_bucket *)) +
+ (count * sizeof(struct hash_bucket *)));
+ if (rval == NULL)
+ return (NULL);
+ rval->hash_count = count;
+ return (rval);
+}
+
+struct hash_bucket *
+new_hash_bucket(void)
+{
+ struct hash_bucket *rval = calloc(1, sizeof(struct hash_bucket));
+
+ return (rval);
+}
diff --git a/sbin/dhclient/bpf.c b/sbin/dhclient/bpf.c
new file mode 100644
index 0000000..78b9eb6
--- /dev/null
+++ b/sbin/dhclient/bpf.c
@@ -0,0 +1,485 @@
+/* $OpenBSD: bpf.c,v 1.13 2004/05/05 14:28:58 deraadt Exp $ */
+
+/* BPF socket interface code, originally contributed by Archie Cobbs. */
+
+/*
+ * Copyright (c) 1995, 1996, 1998, 1999
+ * The Internet Software Consortium. 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 Internet Software Consortium 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 INTERNET SOFTWARE CONSORTIUM 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 INTERNET SOFTWARE CONSORTIUM OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * This software has been written for the Internet Software Consortium
+ * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
+ * Enterprises. To learn more about the Internet Software Consortium,
+ * see ``http://www.vix.com/isc''. To learn more about Vixie
+ * Enterprises, see ``http://www.vix.com''.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include "dhcpd.h"
+#include "privsep.h"
+#include <sys/capsicum.h>
+#include <sys/ioctl.h>
+#include <sys/uio.h>
+
+#include <net/bpf.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <netinet/udp.h>
+#include <netinet/if_ether.h>
+
+#define BPF_FORMAT "/dev/bpf%d"
+
+/*
+ * Called by get_interface_list for each interface that's discovered.
+ * Opens a packet filter for each interface and adds it to the select
+ * mask.
+ */
+int
+if_register_bpf(struct interface_info *info, int flags)
+{
+ char filename[50];
+ int sock, b;
+
+ /* Open a BPF device */
+ for (b = 0;; b++) {
+ snprintf(filename, sizeof(filename), BPF_FORMAT, b);
+ sock = open(filename, flags);
+ if (sock < 0) {
+ if (errno == EBUSY)
+ continue;
+ else
+ error("Can't find free bpf: %m");
+ } else
+ break;
+ }
+
+ /* Set the BPF device to point at this interface. */
+ if (ioctl(sock, BIOCSETIF, info->ifp) < 0)
+ error("Can't attach interface %s to bpf device %s: %m",
+ info->name, filename);
+
+ return (sock);
+}
+
+/*
+ * Packet write filter program:
+ * 'ip and udp and src port bootps and dst port (bootps or bootpc)'
+ */
+struct bpf_insn dhcp_bpf_wfilter[] = {
+ BPF_STMT(BPF_LD + BPF_B + BPF_IND, 14),
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, (IPVERSION << 4) + 5, 0, 12),
+
+ /* Make sure this is an IP packet... */
+ BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 12),
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 0, 10),
+
+ /* Make sure it's a UDP packet... */
+ BPF_STMT(BPF_LD + BPF_B + BPF_ABS, 23),
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 0, 8),
+
+ /* Make sure this isn't a fragment... */
+ BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 20),
+ BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 6, 0), /* patched */
+
+ /* Get the IP header length... */
+ BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, 14),
+
+ /* Make sure it's from the right port... */
+ BPF_STMT(BPF_LD + BPF_H + BPF_IND, 14),
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 68, 0, 3),
+
+ /* Make sure it is to the right ports ... */
+ BPF_STMT(BPF_LD + BPF_H + BPF_IND, 16),
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 67, 0, 1),
+
+ /* If we passed all the tests, ask for the whole packet. */
+ BPF_STMT(BPF_RET+BPF_K, (u_int)-1),
+
+ /* Otherwise, drop it. */
+ BPF_STMT(BPF_RET+BPF_K, 0),
+};
+
+int dhcp_bpf_wfilter_len = sizeof(dhcp_bpf_wfilter) / sizeof(struct bpf_insn);
+
+void
+if_register_send(struct interface_info *info)
+{
+ cap_rights_t rights;
+ struct bpf_version v;
+ struct bpf_program p;
+ int sock, on = 1;
+
+ /* Open a BPF device and hang it on this interface... */
+ info->wfdesc = if_register_bpf(info, O_WRONLY);
+
+ /* Make sure the BPF version is in range... */
+ if (ioctl(info->wfdesc, BIOCVERSION, &v) < 0)
+ error("Can't get BPF version: %m");
+
+ if (v.bv_major != BPF_MAJOR_VERSION ||
+ v.bv_minor < BPF_MINOR_VERSION)
+ error("Kernel BPF version out of range - recompile dhcpd!");
+
+ /* Set up the bpf write filter program structure. */
+ p.bf_len = dhcp_bpf_wfilter_len;
+ p.bf_insns = dhcp_bpf_wfilter;
+
+ if (dhcp_bpf_wfilter[7].k == 0x1fff)
+ dhcp_bpf_wfilter[7].k = htons(IP_MF|IP_OFFMASK);
+
+ if (ioctl(info->wfdesc, BIOCSETWF, &p) < 0)
+ error("Can't install write filter program: %m");
+
+ if (ioctl(info->wfdesc, BIOCLOCK, NULL) < 0)
+ error("Cannot lock bpf");
+
+ cap_rights_init(&rights, CAP_WRITE);
+ if (cap_rights_limit(info->wfdesc, &rights) < 0 && errno != ENOSYS)
+ error("Can't limit bpf descriptor: %m");
+
+ /*
+ * Use raw socket for unicast send.
+ */
+ if ((sock = socket(AF_INET, SOCK_RAW, IPPROTO_UDP)) == -1)
+ error("socket(SOCK_RAW): %m");
+ if (setsockopt(sock, IPPROTO_IP, IP_HDRINCL, &on,
+ sizeof(on)) == -1)
+ error("setsockopt(IP_HDRINCL): %m");
+ info->ufdesc = sock;
+}
+
+/*
+ * Packet filter program...
+ *
+ * XXX: Changes to the filter program may require changes to the
+ * constant offsets used in if_register_send to patch the BPF program!
+ */
+struct bpf_insn dhcp_bpf_filter[] = {
+ /* Make sure this is an IP packet... */
+ BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 12),
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 0, 8),
+
+ /* Make sure it's a UDP packet... */
+ BPF_STMT(BPF_LD + BPF_B + BPF_ABS, 23),
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 0, 6),
+
+ /* Make sure this isn't a fragment... */
+ BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 20),
+ BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 4, 0),
+
+ /* Get the IP header length... */
+ BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, 14),
+
+ /* Make sure it's to the right port... */
+ BPF_STMT(BPF_LD + BPF_H + BPF_IND, 16),
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 67, 0, 1), /* patch */
+
+ /* If we passed all the tests, ask for the whole packet. */
+ BPF_STMT(BPF_RET+BPF_K, (u_int)-1),
+
+ /* Otherwise, drop it. */
+ BPF_STMT(BPF_RET+BPF_K, 0),
+};
+
+int dhcp_bpf_filter_len = sizeof(dhcp_bpf_filter) / sizeof(struct bpf_insn);
+
+void
+if_register_receive(struct interface_info *info)
+{
+ static const unsigned long cmds[2] = { SIOCGIFFLAGS, SIOCGIFMEDIA };
+ cap_rights_t rights;
+ struct bpf_version v;
+ struct bpf_program p;
+ int flag = 1, sz;
+
+ /* Open a BPF device and hang it on this interface... */
+ info->rfdesc = if_register_bpf(info, O_RDONLY);
+
+ /* Make sure the BPF version is in range... */
+ if (ioctl(info->rfdesc, BIOCVERSION, &v) < 0)
+ error("Can't get BPF version: %m");
+
+ if (v.bv_major != BPF_MAJOR_VERSION ||
+ v.bv_minor < BPF_MINOR_VERSION)
+ error("Kernel BPF version out of range - recompile dhcpd!");
+
+ /*
+ * Set immediate mode so that reads return as soon as a packet
+ * comes in, rather than waiting for the input buffer to fill
+ * with packets.
+ */
+ if (ioctl(info->rfdesc, BIOCIMMEDIATE, &flag) < 0)
+ error("Can't set immediate mode on bpf device: %m");
+
+ /* Get the required BPF buffer length from the kernel. */
+ if (ioctl(info->rfdesc, BIOCGBLEN, &sz) < 0)
+ error("Can't get bpf buffer length: %m");
+ info->rbuf_max = sz;
+ info->rbuf = malloc(info->rbuf_max);
+ if (!info->rbuf)
+ error("Can't allocate %lu bytes for bpf input buffer.",
+ (unsigned long)info->rbuf_max);
+ info->rbuf_offset = 0;
+ info->rbuf_len = 0;
+
+ /* Set up the bpf filter program structure. */
+ p.bf_len = dhcp_bpf_filter_len;
+ p.bf_insns = dhcp_bpf_filter;
+
+ /* Patch the server port into the BPF program...
+ *
+ * XXX: changes to filter program may require changes to the
+ * insn number(s) used below!
+ */
+ dhcp_bpf_filter[8].k = LOCAL_PORT;
+
+ if (ioctl(info->rfdesc, BIOCSETF, &p) < 0)
+ error("Can't install packet filter program: %m");
+
+ if (ioctl(info->rfdesc, BIOCLOCK, NULL) < 0)
+ error("Cannot lock bpf");
+
+ cap_rights_init(&rights, CAP_IOCTL, CAP_EVENT, CAP_READ);
+ if (cap_rights_limit(info->rfdesc, &rights) < 0 && errno != ENOSYS)
+ error("Can't limit bpf descriptor: %m");
+ if (cap_ioctls_limit(info->rfdesc, cmds, 2) < 0 && errno != ENOSYS)
+ error("Can't limit ioctls for bpf descriptor: %m");
+}
+
+void
+send_packet_unpriv(int privfd, struct dhcp_packet *raw, size_t len,
+ struct in_addr from, struct in_addr to)
+{
+ struct imsg_hdr hdr;
+ struct buf *buf;
+ int errs;
+
+ hdr.code = IMSG_SEND_PACKET;
+ hdr.len = sizeof(hdr) +
+ sizeof(size_t) + len +
+ sizeof(from) + sizeof(to);
+
+ if ((buf = buf_open(hdr.len)) == NULL)
+ error("buf_open: %m");
+
+ errs = 0;
+ errs += buf_add(buf, &hdr, sizeof(hdr));
+ errs += buf_add(buf, &len, sizeof(len));
+ errs += buf_add(buf, raw, len);
+ errs += buf_add(buf, &from, sizeof(from));
+ errs += buf_add(buf, &to, sizeof(to));
+ if (errs)
+ error("buf_add: %m");
+
+ if (buf_close(privfd, buf) == -1)
+ error("buf_close: %m");
+}
+
+void
+send_packet_priv(struct interface_info *interface, struct imsg_hdr *hdr, int fd)
+{
+ unsigned char buf[256];
+ struct iovec iov[2];
+ struct msghdr msg;
+ struct dhcp_packet raw;
+ size_t len;
+ struct in_addr from, to;
+ int result, bufp = 0;
+
+ if (hdr->len < sizeof(*hdr) + sizeof(size_t))
+ error("corrupted message received");
+ buf_read(fd, &len, sizeof(len));
+ if (hdr->len != sizeof(*hdr) + sizeof(size_t) + len +
+ sizeof(from) + sizeof(to)) {
+ error("corrupted message received");
+ }
+ if (len > sizeof(raw))
+ error("corrupted message received");
+ buf_read(fd, &raw, len);
+ buf_read(fd, &from, sizeof(from));
+ buf_read(fd, &to, sizeof(to));
+
+ /* Assemble the headers... */
+ if (to.s_addr == INADDR_BROADCAST)
+ assemble_hw_header(interface, buf, &bufp);
+ assemble_udp_ip_header(buf, &bufp, from.s_addr, to.s_addr,
+ htons(REMOTE_PORT), (unsigned char *)&raw, len);
+
+ iov[0].iov_base = buf;
+ iov[0].iov_len = bufp;
+ iov[1].iov_base = &raw;
+ iov[1].iov_len = len;
+
+ /* Fire it off */
+ if (to.s_addr == INADDR_BROADCAST)
+ result = writev(interface->wfdesc, iov, 2);
+ else {
+ struct sockaddr_in sato;
+
+ sato.sin_addr = to;
+ sato.sin_port = htons(REMOTE_PORT);
+ sato.sin_family = AF_INET;
+ sato.sin_len = sizeof(sato);
+
+ memset(&msg, 0, sizeof(msg));
+ msg.msg_name = (struct sockaddr *)&sato;
+ msg.msg_namelen = sizeof(sato);
+ msg.msg_iov = iov;
+ msg.msg_iovlen = 2;
+ result = sendmsg(interface->ufdesc, &msg, 0);
+ }
+
+ if (result < 0)
+ warning("send_packet: %m");
+}
+
+ssize_t
+receive_packet(struct interface_info *interface, unsigned char *buf,
+ size_t len, struct sockaddr_in *from, struct hardware *hfrom)
+{
+ int length = 0, offset = 0;
+ struct bpf_hdr hdr;
+
+ /*
+ * All this complexity is because BPF doesn't guarantee that
+ * only one packet will be returned at a time. We're getting
+ * what we deserve, though - this is a terrible abuse of the BPF
+ * interface. Sigh.
+ */
+
+ /* Process packets until we get one we can return or until we've
+ * done a read and gotten nothing we can return...
+ */
+ do {
+ /* If the buffer is empty, fill it. */
+ if (interface->rbuf_offset >= interface->rbuf_len) {
+ length = read(interface->rfdesc, interface->rbuf,
+ interface->rbuf_max);
+ if (length <= 0)
+ return (length);
+ interface->rbuf_offset = 0;
+ interface->rbuf_len = length;
+ }
+
+ /*
+ * If there isn't room for a whole bpf header, something
+ * went wrong, but we'll ignore it and hope it goes
+ * away... XXX
+ */
+ if (interface->rbuf_len - interface->rbuf_offset <
+ sizeof(hdr)) {
+ interface->rbuf_offset = interface->rbuf_len;
+ continue;
+ }
+
+ /* Copy out a bpf header... */
+ memcpy(&hdr, &interface->rbuf[interface->rbuf_offset],
+ sizeof(hdr));
+
+ /*
+ * If the bpf header plus data doesn't fit in what's
+ * left of the buffer, stick head in sand yet again...
+ */
+ if (interface->rbuf_offset + hdr.bh_hdrlen + hdr.bh_caplen >
+ interface->rbuf_len) {
+ interface->rbuf_offset = interface->rbuf_len;
+ continue;
+ }
+
+ /* Skip over the BPF header... */
+ interface->rbuf_offset += hdr.bh_hdrlen;
+
+ /*
+ * If the captured data wasn't the whole packet, or if
+ * the packet won't fit in the input buffer, all we can
+ * do is drop it.
+ */
+ if (hdr.bh_caplen != hdr.bh_datalen) {
+ interface->rbuf_offset =
+ BPF_WORDALIGN(interface->rbuf_offset +
+ hdr.bh_caplen);
+ continue;
+ }
+
+ /* Decode the physical header... */
+ offset = decode_hw_header(interface->rbuf,
+ interface->rbuf_offset, hfrom);
+
+ /*
+ * If a physical layer checksum failed (dunno of any
+ * physical layer that supports this, but WTH), skip
+ * this packet.
+ */
+ if (offset < 0) {
+ interface->rbuf_offset =
+ BPF_WORDALIGN(interface->rbuf_offset +
+ hdr.bh_caplen);
+ continue;
+ }
+ interface->rbuf_offset += offset;
+ hdr.bh_caplen -= offset;
+
+ /* Decode the IP and UDP headers... */
+ offset = decode_udp_ip_header(interface->rbuf,
+ interface->rbuf_offset, from, NULL, hdr.bh_caplen);
+
+ /* If the IP or UDP checksum was bad, skip the packet... */
+ if (offset < 0) {
+ interface->rbuf_offset =
+ BPF_WORDALIGN(interface->rbuf_offset +
+ hdr.bh_caplen);
+ continue;
+ }
+ interface->rbuf_offset += offset;
+ hdr.bh_caplen -= offset;
+
+ /*
+ * If there's not enough room to stash the packet data,
+ * we have to skip it (this shouldn't happen in real
+ * life, though).
+ */
+ if (hdr.bh_caplen > len) {
+ interface->rbuf_offset =
+ BPF_WORDALIGN(interface->rbuf_offset +
+ hdr.bh_caplen);
+ continue;
+ }
+
+ /* Copy out the data in the packet... */
+ memcpy(buf, interface->rbuf + interface->rbuf_offset,
+ hdr.bh_caplen);
+ interface->rbuf_offset =
+ BPF_WORDALIGN(interface->rbuf_offset +
+ hdr.bh_caplen);
+ return (hdr.bh_caplen);
+ } while (!length);
+ return (0);
+}
diff --git a/sbin/dhclient/clparse.c b/sbin/dhclient/clparse.c
new file mode 100644
index 0000000..4f234c7
--- /dev/null
+++ b/sbin/dhclient/clparse.c
@@ -0,0 +1,951 @@
+/* $OpenBSD: clparse.c,v 1.18 2004/09/15 18:15:18 henning Exp $ */
+
+/* Parser for dhclient config and lease files... */
+
+/*
+ * Copyright (c) 1997 The Internet Software Consortium.
+ * 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 Internet Software Consortium 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 INTERNET SOFTWARE CONSORTIUM 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 INTERNET SOFTWARE CONSORTIUM OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * This software has been written for the Internet Software Consortium
+ * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
+ * Enterprises. To learn more about the Internet Software Consortium,
+ * see ``http://www.vix.com/isc''. To learn more about Vixie
+ * Enterprises, see ``http://www.vix.com''.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include "dhcpd.h"
+#include "dhctoken.h"
+
+struct client_config top_level_config;
+struct interface_info *dummy_interfaces;
+extern struct interface_info *ifi;
+
+char client_script_name[] = "/sbin/dhclient-script";
+
+/*
+ * client-conf-file :== client-declarations EOF
+ * client-declarations :== <nil>
+ * | client-declaration
+ * | client-declarations client-declaration
+ */
+int
+read_client_conf(void)
+{
+ FILE *cfile;
+ char *val;
+ int token;
+ struct client_config *config;
+
+ new_parse(path_dhclient_conf);
+
+ /* Set up the initial dhcp option universe. */
+ initialize_universes();
+
+ /* Initialize the top level client configuration. */
+ memset(&top_level_config, 0, sizeof(top_level_config));
+
+ /* Set some defaults... */
+ top_level_config.timeout = 60;
+ top_level_config.select_interval = 0;
+ top_level_config.reboot_timeout = 10;
+ top_level_config.retry_interval = 300;
+ top_level_config.backoff_cutoff = 15;
+ top_level_config.initial_interval = 3;
+ top_level_config.bootp_policy = ACCEPT;
+ top_level_config.script_name = client_script_name;
+ top_level_config.requested_options
+ [top_level_config.requested_option_count++] = DHO_SUBNET_MASK;
+ top_level_config.requested_options
+ [top_level_config.requested_option_count++] = DHO_BROADCAST_ADDRESS;
+ top_level_config.requested_options
+ [top_level_config.requested_option_count++] = DHO_TIME_OFFSET;
+ top_level_config.requested_options
+ [top_level_config.requested_option_count++] = DHO_CLASSLESS_ROUTES;
+ top_level_config.requested_options
+ [top_level_config.requested_option_count++] = DHO_ROUTERS;
+ top_level_config.requested_options
+ [top_level_config.requested_option_count++] = DHO_DOMAIN_NAME;
+ top_level_config.requested_options
+ [top_level_config.requested_option_count++] =
+ DHO_DOMAIN_NAME_SERVERS;
+ top_level_config.requested_options
+ [top_level_config.requested_option_count++] = DHO_HOST_NAME;
+ top_level_config.requested_options
+ [top_level_config.requested_option_count++] = DHO_DOMAIN_SEARCH;
+
+ if ((cfile = fopen(path_dhclient_conf, "r")) != NULL) {
+ do {
+ token = peek_token(&val, cfile);
+ if (token == EOF)
+ break;
+ parse_client_statement(cfile, NULL, &top_level_config);
+ } while (1);
+ token = next_token(&val, cfile); /* Clear the peek buffer */
+ fclose(cfile);
+ }
+
+ /*
+ * Set up state and config structures for clients that don't
+ * have per-interface configuration declarations.
+ */
+ config = NULL;
+ if (!ifi->client) {
+ ifi->client = malloc(sizeof(struct client_state));
+ if (!ifi->client)
+ error("no memory for client state.");
+ memset(ifi->client, 0, sizeof(*(ifi->client)));
+ }
+ if (!ifi->client->config) {
+ if (!config) {
+ config = malloc(sizeof(struct client_config));
+ if (!config)
+ error("no memory for client config.");
+ memcpy(config, &top_level_config,
+ sizeof(top_level_config));
+ }
+ ifi->client->config = config;
+ }
+
+ return (!warnings_occurred);
+}
+
+/*
+ * lease-file :== client-lease-statements EOF
+ * client-lease-statements :== <nil>
+ * | client-lease-statements LEASE client-lease-statement
+ */
+void
+read_client_leases(void)
+{
+ FILE *cfile;
+ char *val;
+ int token;
+
+ new_parse(path_dhclient_db);
+
+ /* Open the lease file. If we can't open it, just return -
+ we can safely trust the server to remember our state. */
+ if ((cfile = fopen(path_dhclient_db, "r")) == NULL)
+ return;
+ do {
+ token = next_token(&val, cfile);
+ if (token == EOF)
+ break;
+ if (token != LEASE) {
+ warning("Corrupt lease file - possible data loss!");
+ skip_to_semi(cfile);
+ break;
+ } else
+ parse_client_lease_statement(cfile, 0);
+
+ } while (1);
+ fclose(cfile);
+}
+
+/*
+ * client-declaration :==
+ * SEND option-decl |
+ * DEFAULT option-decl |
+ * SUPERSEDE option-decl |
+ * PREPEND option-decl |
+ * APPEND option-decl |
+ * hardware-declaration |
+ * REQUEST option-list |
+ * REQUIRE option-list |
+ * TIMEOUT number |
+ * RETRY number |
+ * REBOOT number |
+ * SELECT_TIMEOUT number |
+ * SCRIPT string |
+ * interface-declaration |
+ * LEASE client-lease-statement |
+ * ALIAS client-lease-statement
+ */
+void
+parse_client_statement(FILE *cfile, struct interface_info *ip,
+ struct client_config *config)
+{
+ int token;
+ char *val;
+ struct option *option;
+
+ switch (next_token(&val, cfile)) {
+ case SEND:
+ parse_option_decl(cfile, &config->send_options[0]);
+ return;
+ case DEFAULT:
+ option = parse_option_decl(cfile, &config->defaults[0]);
+ if (option)
+ config->default_actions[option->code] = ACTION_DEFAULT;
+ return;
+ case SUPERSEDE:
+ option = parse_option_decl(cfile, &config->defaults[0]);
+ if (option)
+ config->default_actions[option->code] =
+ ACTION_SUPERSEDE;
+ return;
+ case APPEND:
+ option = parse_option_decl(cfile, &config->defaults[0]);
+ if (option)
+ config->default_actions[option->code] = ACTION_APPEND;
+ return;
+ case PREPEND:
+ option = parse_option_decl(cfile, &config->defaults[0]);
+ if (option)
+ config->default_actions[option->code] = ACTION_PREPEND;
+ return;
+ case MEDIA:
+ parse_string_list(cfile, &config->media, 1);
+ return;
+ case HARDWARE:
+ if (ip)
+ parse_hardware_param(cfile, &ip->hw_address);
+ else {
+ parse_warn("hardware address parameter %s",
+ "not allowed here.");
+ skip_to_semi(cfile);
+ }
+ return;
+ case REQUEST:
+ config->requested_option_count =
+ parse_option_list(cfile, config->requested_options);
+ return;
+ case REQUIRE:
+ memset(config->required_options, 0,
+ sizeof(config->required_options));
+ parse_option_list(cfile, config->required_options);
+ return;
+ case TIMEOUT:
+ parse_lease_time(cfile, &config->timeout);
+ return;
+ case RETRY:
+ parse_lease_time(cfile, &config->retry_interval);
+ return;
+ case SELECT_TIMEOUT:
+ parse_lease_time(cfile, &config->select_interval);
+ return;
+ case REBOOT:
+ parse_lease_time(cfile, &config->reboot_timeout);
+ return;
+ case BACKOFF_CUTOFF:
+ parse_lease_time(cfile, &config->backoff_cutoff);
+ return;
+ case INITIAL_INTERVAL:
+ parse_lease_time(cfile, &config->initial_interval);
+ return;
+ case SCRIPT:
+ config->script_name = parse_string(cfile);
+ return;
+ case INTERFACE:
+ if (ip)
+ parse_warn("nested interface declaration.");
+ parse_interface_declaration(cfile, config);
+ return;
+ case LEASE:
+ parse_client_lease_statement(cfile, 1);
+ return;
+ case ALIAS:
+ parse_client_lease_statement(cfile, 2);
+ return;
+ case REJECT:
+ parse_reject_statement(cfile, config);
+ return;
+ default:
+ parse_warn("expecting a statement.");
+ skip_to_semi(cfile);
+ break;
+ }
+ token = next_token(&val, cfile);
+ if (token != SEMI) {
+ parse_warn("semicolon expected.");
+ skip_to_semi(cfile);
+ }
+}
+
+int
+parse_X(FILE *cfile, u_int8_t *buf, int max)
+{
+ int token;
+ char *val;
+ int len;
+
+ token = peek_token(&val, cfile);
+ if (token == NUMBER_OR_NAME || token == NUMBER) {
+ len = 0;
+ do {
+ token = next_token(&val, cfile);
+ if (token != NUMBER && token != NUMBER_OR_NAME) {
+ parse_warn("expecting hexadecimal constant.");
+ skip_to_semi(cfile);
+ return (0);
+ }
+ convert_num(&buf[len], val, 16, 8);
+ if (len++ > max) {
+ parse_warn("hexadecimal constant too long.");
+ skip_to_semi(cfile);
+ return (0);
+ }
+ token = peek_token(&val, cfile);
+ if (token == COLON)
+ token = next_token(&val, cfile);
+ } while (token == COLON);
+ val = (char *)buf;
+ } else if (token == STRING) {
+ token = next_token(&val, cfile);
+ len = strlen(val);
+ if (len + 1 > max) {
+ parse_warn("string constant too long.");
+ skip_to_semi(cfile);
+ return (0);
+ }
+ memcpy(buf, val, len + 1);
+ } else {
+ parse_warn("expecting string or hexadecimal data");
+ skip_to_semi(cfile);
+ return (0);
+ }
+ return (len);
+}
+
+/*
+ * option-list :== option_name |
+ * option_list COMMA option_name
+ */
+int
+parse_option_list(FILE *cfile, u_int8_t *list)
+{
+ int ix, i;
+ int token;
+ char *val;
+
+ ix = 0;
+ do {
+ token = next_token(&val, cfile);
+ if (!is_identifier(token)) {
+ parse_warn("expected option name.");
+ skip_to_semi(cfile);
+ return (0);
+ }
+ for (i = 0; i < 256; i++)
+ if (!strcasecmp(dhcp_options[i].name, val))
+ break;
+
+ if (i == 256) {
+ parse_warn("%s: unexpected option name.", val);
+ skip_to_semi(cfile);
+ return (0);
+ }
+ list[ix++] = i;
+ if (ix == 256) {
+ parse_warn("%s: too many options.", val);
+ skip_to_semi(cfile);
+ return (0);
+ }
+ token = next_token(&val, cfile);
+ } while (token == COMMA);
+ if (token != SEMI) {
+ parse_warn("expecting semicolon.");
+ skip_to_semi(cfile);
+ return (0);
+ }
+ return (ix);
+}
+
+/*
+ * interface-declaration :==
+ * INTERFACE string LBRACE client-declarations RBRACE
+ */
+void
+parse_interface_declaration(FILE *cfile, struct client_config *outer_config)
+{
+ int token;
+ char *val;
+ struct interface_info *ip;
+
+ token = next_token(&val, cfile);
+ if (token != STRING) {
+ parse_warn("expecting interface name (in quotes).");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ ip = interface_or_dummy(val);
+
+ if (!ip->client)
+ make_client_state(ip);
+
+ if (!ip->client->config)
+ make_client_config(ip, outer_config);
+
+ token = next_token(&val, cfile);
+ if (token != LBRACE) {
+ parse_warn("expecting left brace.");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ do {
+ token = peek_token(&val, cfile);
+ if (token == EOF) {
+ parse_warn("unterminated interface declaration.");
+ return;
+ }
+ if (token == RBRACE)
+ break;
+ parse_client_statement(cfile, ip, ip->client->config);
+ } while (1);
+ token = next_token(&val, cfile);
+}
+
+struct interface_info *
+interface_or_dummy(char *name)
+{
+ struct interface_info *ip;
+
+ /* Find the interface (if any) that matches the name. */
+ if (!strcmp(ifi->name, name))
+ return (ifi);
+
+ /* If it's not a real interface, see if it's on the dummy list. */
+ for (ip = dummy_interfaces; ip; ip = ip->next)
+ if (!strcmp(ip->name, name))
+ return (ip);
+
+ /*
+ * If we didn't find an interface, make a dummy interface as a
+ * placeholder.
+ */
+ ip = malloc(sizeof(*ip));
+ if (!ip)
+ error("Insufficient memory to record interface %s", name);
+ memset(ip, 0, sizeof(*ip));
+ strlcpy(ip->name, name, IFNAMSIZ);
+ ip->next = dummy_interfaces;
+ dummy_interfaces = ip;
+ return (ip);
+}
+
+void
+make_client_state(struct interface_info *ip)
+{
+ ip->client = malloc(sizeof(*(ip->client)));
+ if (!ip->client)
+ error("no memory for state on %s", ip->name);
+ memset(ip->client, 0, sizeof(*(ip->client)));
+}
+
+void
+make_client_config(struct interface_info *ip, struct client_config *config)
+{
+ ip->client->config = malloc(sizeof(struct client_config));
+ if (!ip->client->config)
+ error("no memory for config for %s", ip->name);
+ memset(ip->client->config, 0, sizeof(*(ip->client->config)));
+ memcpy(ip->client->config, config, sizeof(*config));
+}
+
+/*
+ * client-lease-statement :==
+ * RBRACE client-lease-declarations LBRACE
+ *
+ * client-lease-declarations :==
+ * <nil> |
+ * client-lease-declaration |
+ * client-lease-declarations client-lease-declaration
+ */
+void
+parse_client_lease_statement(FILE *cfile, int is_static)
+{
+ struct client_lease *lease, *lp, *pl;
+ struct interface_info *ip;
+ int token;
+ char *val;
+
+ token = next_token(&val, cfile);
+ if (token != LBRACE) {
+ parse_warn("expecting left brace.");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ lease = malloc(sizeof(struct client_lease));
+ if (!lease)
+ error("no memory for lease.");
+ memset(lease, 0, sizeof(*lease));
+ lease->is_static = is_static;
+
+ ip = NULL;
+
+ do {
+ token = peek_token(&val, cfile);
+ if (token == EOF) {
+ parse_warn("unterminated lease declaration.");
+ return;
+ }
+ if (token == RBRACE)
+ break;
+ parse_client_lease_declaration(cfile, lease, &ip);
+ } while (1);
+ token = next_token(&val, cfile);
+
+ /* If the lease declaration didn't include an interface
+ * declaration that we recognized, it's of no use to us.
+ */
+ if (!ip) {
+ free_client_lease(lease);
+ return;
+ }
+
+ /* Make sure there's a client state structure... */
+ if (!ip->client)
+ make_client_state(ip);
+
+ /* If this is an alias lease, it doesn't need to be sorted in. */
+ if (is_static == 2) {
+ ip->client->alias = lease;
+ return;
+ }
+
+ /*
+ * The new lease may supersede a lease that's not the active
+ * lease but is still on the lease list, so scan the lease list
+ * looking for a lease with the same address, and if we find it,
+ * toss it.
+ */
+ pl = NULL;
+ for (lp = ip->client->leases; lp; lp = lp->next) {
+ if (lp->address.len == lease->address.len &&
+ !memcmp(lp->address.iabuf, lease->address.iabuf,
+ lease->address.len)) {
+ if (pl)
+ pl->next = lp->next;
+ else
+ ip->client->leases = lp->next;
+ free_client_lease(lp);
+ break;
+ }
+ }
+
+ /*
+ * If this is a preloaded lease, just put it on the list of
+ * recorded leases - don't make it the active lease.
+ */
+ if (is_static) {
+ lease->next = ip->client->leases;
+ ip->client->leases = lease;
+ return;
+ }
+
+ /*
+ * The last lease in the lease file on a particular interface is
+ * the active lease for that interface. Of course, we don't
+ * know what the last lease in the file is until we've parsed
+ * the whole file, so at this point, we assume that the lease we
+ * just parsed is the active lease for its interface. If
+ * there's already an active lease for the interface, and this
+ * lease is for the same ip address, then we just toss the old
+ * active lease and replace it with this one. If this lease is
+ * for a different address, then if the old active lease has
+ * expired, we dump it; if not, we put it on the list of leases
+ * for this interface which are still valid but no longer
+ * active.
+ */
+ if (ip->client->active) {
+ if (ip->client->active->expiry < cur_time)
+ free_client_lease(ip->client->active);
+ else if (ip->client->active->address.len ==
+ lease->address.len &&
+ !memcmp(ip->client->active->address.iabuf,
+ lease->address.iabuf, lease->address.len))
+ free_client_lease(ip->client->active);
+ else {
+ ip->client->active->next = ip->client->leases;
+ ip->client->leases = ip->client->active;
+ }
+ }
+ ip->client->active = lease;
+
+ /* Phew. */
+}
+
+/*
+ * client-lease-declaration :==
+ * BOOTP |
+ * INTERFACE string |
+ * FIXED_ADDR ip_address |
+ * FILENAME string |
+ * SERVER_NAME string |
+ * OPTION option-decl |
+ * RENEW time-decl |
+ * REBIND time-decl |
+ * EXPIRE time-decl
+ */
+void
+parse_client_lease_declaration(FILE *cfile, struct client_lease *lease,
+ struct interface_info **ipp)
+{
+ int token;
+ char *val;
+ struct interface_info *ip;
+
+ switch (next_token(&val, cfile)) {
+ case BOOTP:
+ lease->is_bootp = 1;
+ break;
+ case INTERFACE:
+ token = next_token(&val, cfile);
+ if (token != STRING) {
+ parse_warn("expecting interface name (in quotes).");
+ skip_to_semi(cfile);
+ break;
+ }
+ ip = interface_or_dummy(val);
+ *ipp = ip;
+ break;
+ case FIXED_ADDR:
+ if (!parse_ip_addr(cfile, &lease->address))
+ return;
+ break;
+ case MEDIUM:
+ parse_string_list(cfile, &lease->medium, 0);
+ return;
+ case FILENAME:
+ lease->filename = parse_string(cfile);
+ return;
+ case NEXT_SERVER:
+ if (!parse_ip_addr(cfile, &lease->nextserver))
+ return;
+ break;
+ case SERVER_NAME:
+ lease->server_name = parse_string(cfile);
+ return;
+ case RENEW:
+ lease->renewal = parse_date(cfile);
+ return;
+ case REBIND:
+ lease->rebind = parse_date(cfile);
+ return;
+ case EXPIRE:
+ lease->expiry = parse_date(cfile);
+ return;
+ case OPTION:
+ parse_option_decl(cfile, lease->options);
+ return;
+ default:
+ parse_warn("expecting lease declaration.");
+ skip_to_semi(cfile);
+ break;
+ }
+ token = next_token(&val, cfile);
+ if (token != SEMI) {
+ parse_warn("expecting semicolon.");
+ skip_to_semi(cfile);
+ }
+}
+
+struct option *
+parse_option_decl(FILE *cfile, struct option_data *options)
+{
+ char *val;
+ int token;
+ u_int8_t buf[4];
+ u_int8_t hunkbuf[1024];
+ int hunkix = 0;
+ char *vendor;
+ char *fmt;
+ struct universe *universe;
+ struct option *option;
+ struct iaddr ip_addr;
+ u_int8_t *dp;
+ int len;
+ int nul_term = 0;
+
+ token = next_token(&val, cfile);
+ if (!is_identifier(token)) {
+ parse_warn("expecting identifier after option keyword.");
+ if (token != SEMI)
+ skip_to_semi(cfile);
+ return (NULL);
+ }
+ if ((vendor = strdup(val)) == NULL)
+ error("no memory for vendor information.");
+
+ token = peek_token(&val, cfile);
+ if (token == DOT) {
+ /* Go ahead and take the DOT token... */
+ token = next_token(&val, cfile);
+
+ /* The next token should be an identifier... */
+ token = next_token(&val, cfile);
+ if (!is_identifier(token)) {
+ parse_warn("expecting identifier after '.'");
+ if (token != SEMI)
+ skip_to_semi(cfile);
+ return (NULL);
+ }
+
+ /* Look up the option name hash table for the specified
+ vendor. */
+ universe = ((struct universe *)hash_lookup(&universe_hash,
+ (unsigned char *)vendor, 0));
+ /* If it's not there, we can't parse the rest of the
+ declaration. */
+ if (!universe) {
+ parse_warn("no vendor named %s.", vendor);
+ skip_to_semi(cfile);
+ return (NULL);
+ }
+ } else {
+ /* Use the default hash table, which contains all the
+ standard dhcp option names. */
+ val = vendor;
+ universe = &dhcp_universe;
+ }
+
+ /* Look up the actual option info... */
+ option = (struct option *)hash_lookup(universe->hash,
+ (unsigned char *)val, 0);
+
+ /* If we didn't get an option structure, it's an undefined option. */
+ if (!option) {
+ if (val == vendor)
+ parse_warn("no option named %s", val);
+ else
+ parse_warn("no option named %s for vendor %s",
+ val, vendor);
+ skip_to_semi(cfile);
+ return (NULL);
+ }
+
+ /* Free the initial identifier token. */
+ free(vendor);
+
+ /* Parse the option data... */
+ do {
+ for (fmt = option->format; *fmt; fmt++) {
+ if (*fmt == 'A')
+ break;
+ switch (*fmt) {
+ case 'X':
+ len = parse_X(cfile, &hunkbuf[hunkix],
+ sizeof(hunkbuf) - hunkix);
+ hunkix += len;
+ break;
+ case 't': /* Text string... */
+ token = next_token(&val, cfile);
+ if (token != STRING) {
+ parse_warn("expecting string.");
+ skip_to_semi(cfile);
+ return (NULL);
+ }
+ len = strlen(val);
+ if (hunkix + len + 1 > sizeof(hunkbuf)) {
+ parse_warn("option data buffer %s",
+ "overflow");
+ skip_to_semi(cfile);
+ return (NULL);
+ }
+ memcpy(&hunkbuf[hunkix], val, len + 1);
+ nul_term = 1;
+ hunkix += len;
+ break;
+ case 'I': /* IP address. */
+ if (!parse_ip_addr(cfile, &ip_addr))
+ return (NULL);
+ len = ip_addr.len;
+ dp = ip_addr.iabuf;
+alloc:
+ if (hunkix + len > sizeof(hunkbuf)) {
+ parse_warn("option data buffer "
+ "overflow");
+ skip_to_semi(cfile);
+ return (NULL);
+ }
+ memcpy(&hunkbuf[hunkix], dp, len);
+ hunkix += len;
+ break;
+ case 'L': /* Unsigned 32-bit integer... */
+ case 'l': /* Signed 32-bit integer... */
+ token = next_token(&val, cfile);
+ if (token != NUMBER) {
+need_number:
+ parse_warn("expecting number.");
+ if (token != SEMI)
+ skip_to_semi(cfile);
+ return (NULL);
+ }
+ convert_num(buf, val, 0, 32);
+ len = 4;
+ dp = buf;
+ goto alloc;
+ case 's': /* Signed 16-bit integer. */
+ case 'S': /* Unsigned 16-bit integer. */
+ token = next_token(&val, cfile);
+ if (token != NUMBER)
+ goto need_number;
+ convert_num(buf, val, 0, 16);
+ len = 2;
+ dp = buf;
+ goto alloc;
+ case 'b': /* Signed 8-bit integer. */
+ case 'B': /* Unsigned 8-bit integer. */
+ token = next_token(&val, cfile);
+ if (token != NUMBER)
+ goto need_number;
+ convert_num(buf, val, 0, 8);
+ len = 1;
+ dp = buf;
+ goto alloc;
+ case 'f': /* Boolean flag. */
+ token = next_token(&val, cfile);
+ if (!is_identifier(token)) {
+ parse_warn("expecting identifier.");
+bad_flag:
+ if (token != SEMI)
+ skip_to_semi(cfile);
+ return (NULL);
+ }
+ if (!strcasecmp(val, "true") ||
+ !strcasecmp(val, "on"))
+ buf[0] = 1;
+ else if (!strcasecmp(val, "false") ||
+ !strcasecmp(val, "off"))
+ buf[0] = 0;
+ else {
+ parse_warn("expecting boolean.");
+ goto bad_flag;
+ }
+ len = 1;
+ dp = buf;
+ goto alloc;
+ default:
+ warning("Bad format %c in parse_option_param.",
+ *fmt);
+ skip_to_semi(cfile);
+ return (NULL);
+ }
+ }
+ token = next_token(&val, cfile);
+ } while (*fmt == 'A' && token == COMMA);
+
+ if (token != SEMI) {
+ parse_warn("semicolon expected.");
+ skip_to_semi(cfile);
+ return (NULL);
+ }
+
+ options[option->code].data = malloc(hunkix + nul_term);
+ if (!options[option->code].data)
+ error("out of memory allocating option data.");
+ memcpy(options[option->code].data, hunkbuf, hunkix + nul_term);
+ options[option->code].len = hunkix;
+ return (option);
+}
+
+void
+parse_string_list(FILE *cfile, struct string_list **lp, int multiple)
+{
+ int token;
+ char *val;
+ size_t valsize;
+ struct string_list *cur, *tmp;
+
+ /* Find the last medium in the media list. */
+ if (*lp)
+ for (cur = *lp; cur->next; cur = cur->next)
+ ; /* nothing */
+ else
+ cur = NULL;
+
+ do {
+ token = next_token(&val, cfile);
+ if (token != STRING) {
+ parse_warn("Expecting media options.");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ valsize = strlen(val) + 1;
+ tmp = new_string_list(valsize);
+ if (tmp == NULL)
+ error("no memory for string list entry.");
+ memcpy(tmp->string, val, valsize);
+ tmp->next = NULL;
+
+ /* Store this medium at the end of the media list. */
+ if (cur)
+ cur->next = tmp;
+ else
+ *lp = tmp;
+ cur = tmp;
+
+ token = next_token(&val, cfile);
+ } while (multiple && token == COMMA);
+
+ if (token != SEMI) {
+ parse_warn("expecting semicolon.");
+ skip_to_semi(cfile);
+ }
+}
+
+void
+parse_reject_statement(FILE *cfile, struct client_config *config)
+{
+ int token;
+ char *val;
+ struct iaddr addr;
+ struct iaddrlist *list;
+
+ do {
+ if (!parse_ip_addr(cfile, &addr)) {
+ parse_warn("expecting IP address.");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ list = malloc(sizeof(struct iaddrlist));
+ if (!list)
+ error("no memory for reject list!");
+
+ list->addr = addr;
+ list->next = config->reject_list;
+ config->reject_list = list;
+
+ token = next_token(&val, cfile);
+ } while (token == COMMA);
+
+ if (token != SEMI) {
+ parse_warn("expecting semicolon.");
+ skip_to_semi(cfile);
+ }
+}
diff --git a/sbin/dhclient/conflex.c b/sbin/dhclient/conflex.c
new file mode 100644
index 0000000..3c8932d
--- /dev/null
+++ b/sbin/dhclient/conflex.c
@@ -0,0 +1,529 @@
+/* $OpenBSD: conflex.c,v 1.7 2004/09/15 19:02:38 deraadt Exp $ */
+
+/* Lexical scanner for dhcpd config file... */
+
+/*
+ * Copyright (c) 1995, 1996, 1997 The Internet Software Consortium.
+ * 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 Internet Software Consortium 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 INTERNET SOFTWARE CONSORTIUM 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 INTERNET SOFTWARE CONSORTIUM OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * This software has been written for the Internet Software Consortium
+ * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
+ * Enterprises. To learn more about the Internet Software Consortium,
+ * see ``http://www.vix.com/isc''. To learn more about Vixie
+ * Enterprises, see ``http://www.vix.com''.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <ctype.h>
+
+#include "dhcpd.h"
+#include "dhctoken.h"
+
+int lexline;
+int lexchar;
+char *token_line;
+char *prev_line;
+char *cur_line;
+char *tlname;
+int eol_token;
+
+static char line1[81];
+static char line2[81];
+static int lpos;
+static int line;
+static int tlpos;
+static int tline;
+static int token;
+static int ugflag;
+static char *tval;
+static char tokbuf[1500];
+
+static int get_char(FILE *);
+static int get_token(FILE *);
+static void skip_to_eol(FILE *);
+static int read_string(FILE *);
+static int read_number(int, FILE *);
+static int read_num_or_name(int, FILE *);
+static int intern(char *, int);
+
+void
+new_parse(char *name)
+{
+ tlname = name;
+ lpos = line = 1;
+ cur_line = line1;
+ prev_line = line2;
+ token_line = cur_line;
+ cur_line[0] = prev_line[0] = 0;
+ warnings_occurred = 0;
+}
+
+static int
+get_char(FILE *cfile)
+{
+ int c = getc(cfile);
+ if (!ugflag) {
+ if (c == '\n') {
+ if (cur_line == line1) {
+ cur_line = line2;
+ prev_line = line1;
+ } else {
+ cur_line = line2;
+ prev_line = line1;
+ }
+ line++;
+ lpos = 1;
+ cur_line[0] = 0;
+ } else if (c != EOF) {
+ if (lpos < sizeof(line1)) {
+ cur_line[lpos - 1] = c;
+ cur_line[lpos] = 0;
+ }
+ lpos++;
+ }
+ } else
+ ugflag = 0;
+ return (c);
+}
+
+static int
+get_token(FILE *cfile)
+{
+ int c, ttok;
+ static char tb[2];
+ int l, p;
+
+ do {
+ l = line;
+ p = lpos;
+
+ c = get_char(cfile);
+
+ if (!(c == '\n' && eol_token) && isascii(c) && isspace(c))
+ continue;
+ if (c == '#') {
+ skip_to_eol(cfile);
+ continue;
+ }
+ if (c == '"') {
+ lexline = l;
+ lexchar = p;
+ ttok = read_string(cfile);
+ break;
+ }
+ if ((isascii(c) && isdigit(c)) || c == '-') {
+ lexline = l;
+ lexchar = p;
+ ttok = read_number(c, cfile);
+ break;
+ } else if (isascii(c) && isalpha(c)) {
+ lexline = l;
+ lexchar = p;
+ ttok = read_num_or_name(c, cfile);
+ break;
+ } else {
+ lexline = l;
+ lexchar = p;
+ tb[0] = c;
+ tb[1] = 0;
+ tval = tb;
+ ttok = c;
+ break;
+ }
+ } while (1);
+ return (ttok);
+}
+
+int
+next_token(char **rval, FILE *cfile)
+{
+ int rv;
+
+ if (token) {
+ if (lexline != tline)
+ token_line = cur_line;
+ lexchar = tlpos;
+ lexline = tline;
+ rv = token;
+ token = 0;
+ } else {
+ rv = get_token(cfile);
+ token_line = cur_line;
+ }
+ if (rval)
+ *rval = tval;
+
+ return (rv);
+}
+
+int
+peek_token(char **rval, FILE *cfile)
+{
+ int x;
+
+ if (!token) {
+ tlpos = lexchar;
+ tline = lexline;
+ token = get_token(cfile);
+ if (lexline != tline)
+ token_line = prev_line;
+ x = lexchar;
+ lexchar = tlpos;
+ tlpos = x;
+ x = lexline;
+ lexline = tline;
+ tline = x;
+ }
+ if (rval)
+ *rval = tval;
+
+ return (token);
+}
+
+static void
+skip_to_eol(FILE *cfile)
+{
+ int c;
+
+ do {
+ c = get_char(cfile);
+ if (c == EOF)
+ return;
+ if (c == '\n')
+ return;
+ } while (1);
+}
+
+static int
+read_string(FILE *cfile)
+{
+ int i, c, bs = 0;
+
+ for (i = 0; i < sizeof(tokbuf); i++) {
+ c = get_char(cfile);
+ if (c == EOF) {
+ parse_warn("eof in string constant");
+ break;
+ }
+ if (bs) {
+ bs = 0;
+ i--;
+ tokbuf[i] = c;
+ } else if (c == '\\')
+ bs = 1;
+ else if (c == '"')
+ break;
+ else
+ tokbuf[i] = c;
+ }
+ /*
+ * Normally, I'd feel guilty about this, but we're talking about
+ * strings that'll fit in a DHCP packet here...
+ */
+ if (i == sizeof(tokbuf)) {
+ parse_warn("string constant larger than internal buffer");
+ i--;
+ }
+ tokbuf[i] = 0;
+ tval = tokbuf;
+ return (STRING);
+}
+
+static int
+read_number(int c, FILE *cfile)
+{
+ int seenx = 0, i = 0, token = NUMBER;
+
+ tokbuf[i++] = c;
+ for (; i < sizeof(tokbuf); i++) {
+ c = get_char(cfile);
+ if (!seenx && c == 'x')
+ seenx = 1;
+ else if (!isascii(c) || !isxdigit(c)) {
+ ungetc(c, cfile);
+ ugflag = 1;
+ break;
+ }
+ tokbuf[i] = c;
+ }
+ if (i == sizeof(tokbuf)) {
+ parse_warn("numeric token larger than internal buffer");
+ i--;
+ }
+ tokbuf[i] = 0;
+ tval = tokbuf;
+
+ return (token);
+}
+
+static int
+read_num_or_name(int c, FILE *cfile)
+{
+ int i = 0;
+ int rv = NUMBER_OR_NAME;
+
+ tokbuf[i++] = c;
+ for (; i < sizeof(tokbuf); i++) {
+ c = get_char(cfile);
+ if (!isascii(c) || (c != '-' && c != '_' && !isalnum(c))) {
+ ungetc(c, cfile);
+ ugflag = 1;
+ break;
+ }
+ if (!isxdigit(c))
+ rv = NAME;
+ tokbuf[i] = c;
+ }
+ if (i == sizeof(tokbuf)) {
+ parse_warn("token larger than internal buffer");
+ i--;
+ }
+ tokbuf[i] = 0;
+ tval = tokbuf;
+
+ return (intern(tval, rv));
+}
+
+static int
+intern(char *atom, int dfv)
+{
+ if (!isascii(atom[0]))
+ return (dfv);
+
+ switch (tolower(atom[0])) {
+ case 'a':
+ if (!strcasecmp(atom + 1, "lways-reply-rfc1048"))
+ return (ALWAYS_REPLY_RFC1048);
+ if (!strcasecmp(atom + 1, "ppend"))
+ return (APPEND);
+ if (!strcasecmp(atom + 1, "llow"))
+ return (ALLOW);
+ if (!strcasecmp(atom + 1, "lias"))
+ return (ALIAS);
+ if (!strcasecmp(atom + 1, "bandoned"))
+ return (ABANDONED);
+ if (!strcasecmp(atom + 1, "uthoritative"))
+ return (AUTHORITATIVE);
+ break;
+ case 'b':
+ if (!strcasecmp(atom + 1, "ackoff-cutoff"))
+ return (BACKOFF_CUTOFF);
+ if (!strcasecmp(atom + 1, "ootp"))
+ return (BOOTP);
+ if (!strcasecmp(atom + 1, "ooting"))
+ return (BOOTING);
+ if (!strcasecmp(atom + 1, "oot-unknown-clients"))
+ return (BOOT_UNKNOWN_CLIENTS);
+ case 'c':
+ if (!strcasecmp(atom + 1, "lass"))
+ return (CLASS);
+ if (!strcasecmp(atom + 1, "iaddr"))
+ return (CIADDR);
+ if (!strcasecmp(atom + 1, "lient-identifier"))
+ return (CLIENT_IDENTIFIER);
+ if (!strcasecmp(atom + 1, "lient-hostname"))
+ return (CLIENT_HOSTNAME);
+ break;
+ case 'd':
+ if (!strcasecmp(atom + 1, "omain"))
+ return (DOMAIN);
+ if (!strcasecmp(atom + 1, "eny"))
+ return (DENY);
+ if (!strncasecmp(atom + 1, "efault", 6)) {
+ if (!atom[7])
+ return (DEFAULT);
+ if (!strcasecmp(atom + 7, "-lease-time"))
+ return (DEFAULT_LEASE_TIME);
+ break;
+ }
+ if (!strncasecmp(atom + 1, "ynamic-bootp", 12)) {
+ if (!atom[13])
+ return (DYNAMIC_BOOTP);
+ if (!strcasecmp(atom + 13, "-lease-cutoff"))
+ return (DYNAMIC_BOOTP_LEASE_CUTOFF);
+ if (!strcasecmp(atom + 13, "-lease-length"))
+ return (DYNAMIC_BOOTP_LEASE_LENGTH);
+ break;
+ }
+ break;
+ case 'e':
+ if (!strcasecmp(atom + 1, "thernet"))
+ return (ETHERNET);
+ if (!strcasecmp(atom + 1, "nds"))
+ return (ENDS);
+ if (!strcasecmp(atom + 1, "xpire"))
+ return (EXPIRE);
+ break;
+ case 'f':
+ if (!strcasecmp(atom + 1, "ilename"))
+ return (FILENAME);
+ if (!strcasecmp(atom + 1, "ixed-address"))
+ return (FIXED_ADDR);
+ if (!strcasecmp(atom + 1, "ddi"))
+ return (FDDI);
+ break;
+ case 'g':
+ if (!strcasecmp(atom + 1, "iaddr"))
+ return (GIADDR);
+ if (!strcasecmp(atom + 1, "roup"))
+ return (GROUP);
+ if (!strcasecmp(atom + 1, "et-lease-hostnames"))
+ return (GET_LEASE_HOSTNAMES);
+ break;
+ case 'h':
+ if (!strcasecmp(atom + 1, "ost"))
+ return (HOST);
+ if (!strcasecmp(atom + 1, "ardware"))
+ return (HARDWARE);
+ if (!strcasecmp(atom + 1, "ostname"))
+ return (HOSTNAME);
+ break;
+ case 'i':
+ if (!strcasecmp(atom + 1, "nitial-interval"))
+ return (INITIAL_INTERVAL);
+ if (!strcasecmp(atom + 1, "nterface"))
+ return (INTERFACE);
+ break;
+ case 'l':
+ if (!strcasecmp(atom + 1, "ease"))
+ return (LEASE);
+ break;
+ case 'm':
+ if (!strcasecmp(atom + 1, "ax-lease-time"))
+ return (MAX_LEASE_TIME);
+ if (!strncasecmp(atom + 1, "edi", 3)) {
+ if (!strcasecmp(atom + 4, "a"))
+ return (MEDIA);
+ if (!strcasecmp(atom + 4, "um"))
+ return (MEDIUM);
+ break;
+ }
+ break;
+ case 'n':
+ if (!strcasecmp(atom + 1, "ameserver"))
+ return (NAMESERVER);
+ if (!strcasecmp(atom + 1, "etmask"))
+ return (NETMASK);
+ if (!strcasecmp(atom + 1, "ext-server"))
+ return (NEXT_SERVER);
+ if (!strcasecmp(atom + 1, "ot"))
+ return (TOKEN_NOT);
+ break;
+ case 'o':
+ if (!strcasecmp(atom + 1, "ption"))
+ return (OPTION);
+ if (!strcasecmp(atom + 1, "ne-lease-per-client"))
+ return (ONE_LEASE_PER_CLIENT);
+ break;
+ case 'p':
+ if (!strcasecmp(atom + 1, "repend"))
+ return (PREPEND);
+ if (!strcasecmp(atom + 1, "acket"))
+ return (PACKET);
+ break;
+ case 'r':
+ if (!strcasecmp(atom + 1, "ange"))
+ return (RANGE);
+ if (!strcasecmp(atom + 1, "equest"))
+ return (REQUEST);
+ if (!strcasecmp(atom + 1, "equire"))
+ return (REQUIRE);
+ if (!strcasecmp(atom + 1, "etry"))
+ return (RETRY);
+ if (!strcasecmp(atom + 1, "enew"))
+ return (RENEW);
+ if (!strcasecmp(atom + 1, "ebind"))
+ return (REBIND);
+ if (!strcasecmp(atom + 1, "eboot"))
+ return (REBOOT);
+ if (!strcasecmp(atom + 1, "eject"))
+ return (REJECT);
+ break;
+ case 's':
+ if (!strcasecmp(atom + 1, "earch"))
+ return (SEARCH);
+ if (!strcasecmp(atom + 1, "tarts"))
+ return (STARTS);
+ if (!strcasecmp(atom + 1, "iaddr"))
+ return (SIADDR);
+ if (!strcasecmp(atom + 1, "ubnet"))
+ return (SUBNET);
+ if (!strcasecmp(atom + 1, "hared-network"))
+ return (SHARED_NETWORK);
+ if (!strcasecmp(atom + 1, "erver-name"))
+ return (SERVER_NAME);
+ if (!strcasecmp(atom + 1, "erver-identifier"))
+ return (SERVER_IDENTIFIER);
+ if (!strcasecmp(atom + 1, "elect-timeout"))
+ return (SELECT_TIMEOUT);
+ if (!strcasecmp(atom + 1, "end"))
+ return (SEND);
+ if (!strcasecmp(atom + 1, "cript"))
+ return (SCRIPT);
+ if (!strcasecmp(atom + 1, "upersede"))
+ return (SUPERSEDE);
+ break;
+ case 't':
+ if (!strcasecmp(atom + 1, "imestamp"))
+ return (TIMESTAMP);
+ if (!strcasecmp(atom + 1, "imeout"))
+ return (TIMEOUT);
+ if (!strcasecmp(atom + 1, "oken-ring"))
+ return (TOKEN_RING);
+ break;
+ case 'u':
+ if (!strncasecmp(atom + 1, "se", 2)) {
+ if (!strcasecmp(atom + 3, "r-class"))
+ return (USER_CLASS);
+ if (!strcasecmp(atom + 3, "-host-decl-names"))
+ return (USE_HOST_DECL_NAMES);
+ if (!strcasecmp(atom + 3,
+ "-lease-addr-for-default-route"))
+ return (USE_LEASE_ADDR_FOR_DEFAULT_ROUTE);
+ break;
+ }
+ if (!strcasecmp(atom + 1, "id"))
+ return (UID);
+ if (!strcasecmp(atom + 1, "nknown-clients"))
+ return (UNKNOWN_CLIENTS);
+ break;
+ case 'v':
+ if (!strcasecmp(atom + 1, "endor-class"))
+ return (VENDOR_CLASS);
+ break;
+ case 'y':
+ if (!strcasecmp(atom + 1, "iaddr"))
+ return (YIADDR);
+ break;
+ }
+ return (dfv);
+}
diff --git a/sbin/dhclient/convert.c b/sbin/dhclient/convert.c
new file mode 100644
index 0000000..adca092
--- /dev/null
+++ b/sbin/dhclient/convert.c
@@ -0,0 +1,117 @@
+/* $OpenBSD: convert.c,v 1.5 2004/02/07 11:35:59 henning Exp $ */
+
+/*
+ * Safe copying of option values into and out of the option buffer,
+ * which can't be assumed to be aligned.
+ */
+
+/*
+ * Copyright (c) 1995, 1996 The Internet Software Consortium.
+ * 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 Internet Software Consortium 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 INTERNET SOFTWARE CONSORTIUM 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 INTERNET SOFTWARE CONSORTIUM OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * This software has been written for the Internet Software Consortium
+ * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
+ * Enterprises. To learn more about the Internet Software Consortium,
+ * see ``http://www.vix.com/isc''. To learn more about Vixie
+ * Enterprises, see ``http://www.vix.com''.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include "dhcpd.h"
+
+u_int32_t
+getULong(unsigned char *buf)
+{
+ u_int32_t ibuf;
+
+ memcpy(&ibuf, buf, sizeof(ibuf));
+ return (ntohl(ibuf));
+}
+
+int32_t
+getLong(unsigned char *(buf))
+{
+ int32_t ibuf;
+
+ memcpy(&ibuf, buf, sizeof(ibuf));
+ return (ntohl(ibuf));
+}
+
+u_int16_t
+getUShort(unsigned char *buf)
+{
+ u_int16_t ibuf;
+
+ memcpy(&ibuf, buf, sizeof(ibuf));
+ return (ntohs(ibuf));
+}
+
+int16_t
+getShort(unsigned char *buf)
+{
+ int16_t ibuf;
+
+ memcpy(&ibuf, buf, sizeof(ibuf));
+ return (ntohs(ibuf));
+}
+
+void
+putULong(unsigned char *obuf, u_int32_t val)
+{
+ u_int32_t tmp = htonl(val);
+
+ memcpy(obuf, &tmp, sizeof(tmp));
+}
+
+void
+putLong(unsigned char *obuf, int32_t val)
+{
+ int32_t tmp = htonl(val);
+
+ memcpy(obuf, &tmp, sizeof(tmp));
+}
+
+void
+putUShort(unsigned char *obuf, unsigned int val)
+{
+ u_int16_t tmp = htons(val);
+
+ memcpy(obuf, &tmp, sizeof(tmp));
+}
+
+void
+putShort(unsigned char *obuf, int val)
+{
+ int16_t tmp = htons(val);
+
+ memcpy(obuf, &tmp, sizeof(tmp));
+}
diff --git a/sbin/dhclient/dhclient-script b/sbin/dhclient/dhclient-script
new file mode 100755
index 0000000..3439fd9
--- /dev/null
+++ b/sbin/dhclient/dhclient-script
@@ -0,0 +1,407 @@
+#!/bin/sh
+#
+# $OpenBSD: dhclient-script,v 1.6 2004/05/06 18:22:41 claudio Exp $
+# $FreeBSD$
+#
+# Copyright (c) 2003 Kenneth R Westerback <krw@openbsd.org>
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+#
+
+ARP=/usr/sbin/arp
+HOSTNAME=/bin/hostname
+IFCONFIG='/sbin/ifconfig -n'
+
+LOCALHOST=127.0.0.1
+
+if [ -x /usr/bin/logger ]; then
+ LOGGER="/usr/bin/logger -s -p user.notice -t dhclient"
+else
+ LOGGER=echo
+fi
+
+#
+# Helper functions that implement common actions.
+#
+
+check_hostname() {
+ current_hostname=`$HOSTNAME`
+ if [ -z "$current_hostname" ]; then
+ $LOGGER "New Hostname ($interface): $new_host_name"
+ $HOSTNAME $new_host_name
+ elif [ "$current_hostname" = "$old_host_name" -a \
+ "$new_host_name" != "$old_host_name" ]; then
+ $LOGGER "New Hostname ($interface): $new_host_name"
+ $HOSTNAME $new_host_name
+ fi
+}
+
+arp_flush() {
+ arp -an -i $interface | \
+ sed -n -e 's/^.*(\(.*\)) at .*$/arp -d \1/p' | \
+ sh >/dev/null 2>&1
+}
+
+delete_old_address() {
+ eval "$IFCONFIG $interface inet -alias $old_ip_address $medium"
+}
+
+add_new_address() {
+ eval "$IFCONFIG $interface \
+ inet $new_ip_address \
+ netmask $new_subnet_mask \
+ broadcast $new_broadcast_address \
+ $medium"
+
+ $LOGGER "New IP Address ($interface): $new_ip_address"
+ $LOGGER "New Subnet Mask ($interface): $new_subnet_mask"
+ $LOGGER "New Broadcast Address ($interface): $new_broadcast_address"
+ $LOGGER "New Routers ($interface): $new_routers"
+}
+
+delete_old_alias() {
+ if [ -n "$alias_ip_address" ]; then
+ $IFCONFIG $interface inet -alias $alias_ip_address > /dev/null 2>&1
+ #route delete $alias_ip_address $LOCALHOST > /dev/null 2>&1
+ fi
+}
+
+add_new_alias() {
+ if [ -n "$alias_ip_address" ]; then
+ $IFCONFIG $interface inet alias $alias_ip_address netmask \
+ $alias_subnet_mask
+ #route add $alias_ip_address $LOCALHOST
+ fi
+}
+
+fill_classless_routes() {
+ set $1
+ while [ $# -ge 5 ]; do
+ if [ $1 -eq 0 ]; then
+ route="default"
+ elif [ $1 -le 8 ]; then
+ route="$2.0.0.0/$1"
+ shift
+ elif [ $1 -le 16 ]; then
+ route="$2.$3.0.0/$1"
+ shift; shift
+ elif [ $1 -le 24 ]; then
+ route="$2.$3.$4.0/$1"
+ shift; shift; shift
+ else
+ route="$2.$3.$4.$5/$1"
+ shift; shift; shift; shift
+ fi
+ shift
+ router="$1.$2.$3.$4"
+ classless_routes="$classless_routes $route $router"
+ shift; shift; shift; shift
+ done
+}
+
+delete_old_routes() {
+ #route delete "$old_ip_address" $LOCALHOST >/dev/null 2>&1
+ if [ -n "$old_classless_routes" ]; then
+ fill_classless_routes "$old_classless_routes"
+ set $classless_routes
+ while [ $# -gt 1 ]; do
+ route delete "$1" "$2"
+ shift; shift
+ done
+ return 0;
+ fi
+
+ # If we supported multiple default routes, we'd be removing each
+ # one here. We don't so just delete the default route if it's
+ # through our interface.
+ if is_default_interface; then
+ route delete default >/dev/null 2>&1
+ fi
+
+ if [ -n "$old_static_routes" ]; then
+ set $old_static_routes
+ while [ $# -gt 1 ]; do
+ route delete "$1" "$2"
+ shift; shift
+ done
+ fi
+
+ arp_flush
+}
+
+add_new_routes() {
+ #route add $new_ip_address $LOCALHOST >/dev/null 2>&1
+
+ # RFC 3442: If the DHCP server returns both a Classless Static
+ # Routes option and a Router option, the DHCP client MUST ignore
+ # the Router option.
+ #
+ # DHCP clients that support this option (Classless Static Routes)
+ # MUST NOT install the routes specified in the Static Routes
+ # option (option code 33) if both a Static Routes option and the
+ # Classless Static Routes option are provided.
+
+ if [ -n "$new_classless_routes" ]; then
+ fill_classless_routes "$new_classless_routes"
+ $LOGGER "New Classless Static Routes ($interface): $classless_routes"
+ set $classless_routes
+ while [ $# -gt 1 ]; do
+ if [ "0.0.0.0" = "$2" ]; then
+ route add "$1" -iface "$interface"
+ else
+ route add "$1" "$2"
+ fi
+ shift; shift
+ done
+ return
+ fi
+
+ for router in $new_routers; do
+ if is_default_interface; then
+
+ if [ "$new_ip_address" = "$router" ]; then
+ route add default -iface $router >/dev/null 2>&1
+ else
+ route add default $router >/dev/null 2>&1
+ fi
+ fi
+ # 2nd and subsequent default routers error out, so explicitly
+ # stop processing the list after the first one.
+ break
+ done
+
+ if [ -n "$new_static_routes" ]; then
+ $LOGGER "New Static Routes ($interface): $new_static_routes"
+ set $new_static_routes
+ while [ $# -gt 1 ]; do
+ route add $1 $2
+ shift; shift
+ done
+ fi
+}
+
+add_new_resolv_conf() {
+ # XXX Old code did not create/update resolv.conf unless both
+ # $new_domain_name and $new_domain_name_servers were provided. PR
+ # #3135 reported some ISP's only provide $new_domain_name_servers and
+ # thus broke the script. This code creates the resolv.conf if either
+ # are provided.
+
+ local tmpres=/var/run/resolv.conf.${interface}
+ rm -f $tmpres
+
+ if [ -n "$new_domain_search" ]; then
+ echo "search $new_domain_search" >>$tmpres
+ elif [ -n "$new_domain_name" ]; then
+ echo "search $new_domain_name" >>$tmpres
+ fi
+
+ if [ -n "$new_domain_name_servers" ]; then
+ for nameserver in $new_domain_name_servers; do
+ echo "nameserver $nameserver" >>$tmpres
+ done
+ fi
+
+ if [ -f $tmpres ]; then
+ if [ -f /etc/resolv.conf.tail ]; then
+ cat /etc/resolv.conf.tail >>$tmpres
+ fi
+
+ case $resolvconf_enable in
+ # "no", "false", "off", or "0"
+ [Nn][Oo]|[Ff][Aa][Ll][Ss][Ee]|[Oo][Ff][Ff]|0)
+ # When resolv.conf is not changed actually, we don't
+ # need to update it.
+ # If /usr is not mounted yet, we cannot use cmp, then
+ # the following test fails. In such case, we simply
+ # ignore an error and do update resolv.conf.
+ if cmp -s $tmpres /etc/resolv.conf; then
+ rm -f $tmpres
+ return 0
+ fi 2>/dev/null
+
+ # In case (e.g. during OpenBSD installs)
+ # /etc/resolv.conf is a symbolic link, take
+ # care to preserve the link and write the new
+ # data in the correct location.
+
+ if [ -f /etc/resolv.conf ]; then
+ cat /etc/resolv.conf > /etc/resolv.conf.save
+ fi
+ cat $tmpres > /etc/resolv.conf
+
+ # Try to ensure correct ownership and permissions.
+ chown -RL root:wheel /etc/resolv.conf
+ chmod -RL 644 /etc/resolv.conf
+ ;;
+
+ *)
+ /sbin/resolvconf -a ${interface} < $tmpres
+ ;;
+ esac
+
+ rm -f $tmpres
+
+ return 0
+ fi
+
+ return 1
+}
+
+# Must be used on exit. Invokes the local dhcp client exit hooks, if any.
+exit_with_hooks() {
+ exit_status=$1
+ if [ -f /etc/dhclient-exit-hooks ]; then
+ . /etc/dhclient-exit-hooks
+ fi
+ # probably should do something with exit status of the local script
+ exit $exit_status
+}
+
+# Get the interface with the current ipv4 default route on it using only
+# commands that are available prior to /usr being mounted.
+is_default_interface()
+{
+ routeget="`route -n get -inet default`"
+ oldifs="$IFS"
+ IFS="
+"
+ defif=
+ for line in $routeget ; do
+ case $line in
+ *interface:*)
+ defif=${line##*: }
+ ;;
+ esac
+ done
+ IFS=${oldifs}
+
+ if [ -z "$defif" -o "$defif" = "$interface" ]; then
+ return 0
+ else
+ return 1
+ fi
+}
+
+#
+# Start of active code.
+#
+
+# Invoke the local dhcp client enter hooks, if they exist.
+if [ -f /etc/dhclient-enter-hooks ]; then
+ exit_status=0
+ . /etc/dhclient-enter-hooks
+ # allow the local script to abort processing of this state
+ # local script must set exit_status variable to nonzero.
+ if [ $exit_status -ne 0 ]; then
+ exit $exit_status
+ fi
+fi
+
+: ${resolvconf_enable="YES"}
+
+case $reason in
+MEDIUM)
+ eval "$IFCONFIG $interface $medium"
+ eval "$IFCONFIG $interface inet -alias 0.0.0.0 $medium" >/dev/null 2>&1
+ sleep 1
+ ;;
+
+PREINIT)
+ delete_old_alias
+ $IFCONFIG $interface inet alias 0.0.0.0 netmask 255.0.0.0 broadcast 255.255.255.255 up
+ ;;
+
+ARPCHECK|ARPSEND)
+ ;;
+
+BOUND|RENEW|REBIND|REBOOT)
+ check_hostname
+ if [ -n "$old_ip_address" ]; then
+ if [ "$old_ip_address" != "$alias_ip_address" ]; then
+ delete_old_alias
+ fi
+ if [ "$old_ip_address" != "$new_ip_address" ]; then
+ delete_old_address
+ delete_old_routes
+ fi
+ fi
+ if [ "$reason" = BOUND ] || \
+ [ "$reason" = REBOOT ] || \
+ [ -z "$old_ip_address" ] || \
+ [ "$old_ip_address" != "$new_ip_address" ]; then
+ add_new_address
+ add_new_routes
+ fi
+ if [ "$new_ip_address" != "$alias_ip_address" ]; then
+ add_new_alias
+ fi
+ if is_default_interface; then
+ add_new_resolv_conf
+ fi
+ ;;
+
+EXPIRE|FAIL)
+ delete_old_alias
+ if [ -n "$old_ip_address" ]; then
+ delete_old_address
+ delete_old_routes
+ fi
+ if [ -x $ARP ]; then
+ $ARP -d -a -i $interface
+ fi
+ # XXX Why add alias we just deleted above?
+ add_new_alias
+ if is_default_interface; then
+ case $resolvconf_enable in
+ # "no", "false", "off", or "0"
+ [Nn][Oo]|[Ff][Aa][Ll][Ss][Ee]|[Oo][Ff][Ff]|0)
+ if [ -f /etc/resolv.conf.save ]; then
+ cat /etc/resolv.conf.save > /etc/resolv.conf
+ fi
+ ;;
+ *)
+ /sbin/resolvconf -d ${interface}
+ ;;
+ esac
+ fi
+ ;;
+
+TIMEOUT)
+ delete_old_alias
+ add_new_address
+ sleep 1
+ if [ -n "$new_routers" ]; then
+ $LOGGER "New Routers ($interface): $new_routers"
+ set "$new_routers"
+ if ping -q -c 1 -t 1 "$1"; then
+ if [ "$new_ip_address" != "$alias_ip_address" ]; then
+ add_new_alias
+ fi
+ add_new_routes
+ if ! is_default_interface; then
+ exit_with_hooks 0
+ fi
+ if add_new_resolv_conf; then
+ exit_with_hooks 0
+ fi
+ fi
+ fi
+ eval "$IFCONFIG $interface inet -alias $new_ip_address $medium"
+ delete_old_routes
+ exit_with_hooks 1
+ ;;
+esac
+
+exit_with_hooks 0
diff --git a/sbin/dhclient/dhclient-script.8 b/sbin/dhclient/dhclient-script.8
new file mode 100644
index 0000000..2fae9f6
--- /dev/null
+++ b/sbin/dhclient/dhclient-script.8
@@ -0,0 +1,297 @@
+.\" $OpenBSD: dhclient-script.8,v 1.2 2004/04/09 18:30:15 jmc Exp $
+.\"
+.\" Copyright (c) 1997 The Internet Software Consortium.
+.\" 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 Internet Software Consortium 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 INTERNET SOFTWARE CONSORTIUM 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 INTERNET SOFTWARE CONSORTIUM OR
+.\" CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+.\" SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+.\" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+.\" USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+.\" ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+.\" OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+.\" OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" This software has been written for the Internet Software Consortium
+.\" by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
+.\" Enterprises. To learn more about the Internet Software Consortium,
+.\" see ``http://www.isc.org/isc''. To learn more about Vixie
+.\" Enterprises, see ``http://www.vix.com''.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd September 6, 2010
+.Dt DHCLIENT-SCRIPT 8
+.Os
+.Sh NAME
+.Nm dhclient-script
+.Nd DHCP client network configuration script
+.Sh DESCRIPTION
+The DHCP client network configuration script is invoked from time to
+time by
+.Xr dhclient 8 .
+This script is used by the DHCP client to set each interface's initial
+configuration prior to requesting an address, to test the address once it
+has been offered, and to set the interface's final configuration once a
+lease has been acquired.
+If no lease is acquired, the script is used to test predefined leases, if
+any, and also called once if no valid lease can be identified.
+.Pp
+.\" No standard client script exists for some operating systems, even though
+.\" the actual client may work, so a pioneering user may well need to create
+.\" a new script or modify an existing one.
+In general, customizations specific to a particular computer should be done
+in the
+.Pa /etc/dhclient.conf
+file.
+.Sh OPERATION
+When
+.Xr dhclient 8
+needs to invoke the client configuration script, it sets up a number of
+environment variables and runs
+.Nm .
+In all cases,
+.Va $reason
+is set to the name of the reason why the script has been invoked.
+The following reasons are currently defined:
+.Li MEDIUM , PREINIT , ARPCHECK , ARPSEND , BOUND , RENEW , REBIND , REBOOT ,
+.Li EXPIRE , FAIL
+and
+.Li TIMEOUT .
+.Bl -tag -width ".Li ARPCHECK"
+.It Li MEDIUM
+The DHCP client is requesting that an interface's media type be set.
+The interface name is passed in
+.Va $interface ,
+and the media type is passed in
+.Va $medium .
+.It Li PREINIT
+The DHCP client is requesting that an interface be configured as
+required in order to send packets prior to receiving an actual address.
+.\" For clients which use the BSD socket library,
+This means configuring the interface with an IP address of 0.0.0.0
+and a broadcast address of 255.255.255.255.
+.\" For other clients, it may be possible to simply configure the interface up
+.\" without actually giving it an IP address at all.
+The interface name is passed in
+.Va $interface ,
+and the media type in
+.Va $medium .
+.Pp
+If an IP alias has been declared in
+.Xr dhclient.conf 5 ,
+its address will be passed in
+.Va $alias_ip_address ,
+and that IP alias should be deleted from the interface,
+along with any routes to it.
+.It Li ARPSEND
+The DHCP client is requesting that an address that has been offered to
+it be checked to see if somebody else is using it, by sending an ARP
+request for that address.
+It is not clear how to implement this, so no examples exist yet.
+The IP address to check is passed in
+.Va $new_ip_address ,
+and the interface name is passed in
+.Va $interface .
+.It Li ARPCHECK
+The DHCP client wants to know if a response to the ARP request sent
+using
+.Li ARPSEND
+has been received.
+If one has, the script should exit with a nonzero status, indicating that
+the offered address has already been requested and should be declined.
+The
+.Va $new_ip_address
+and
+.Va $interface
+variables are set as with
+.Li ARPSEND .
+.It Li BOUND
+The DHCP client has done an initial binding to a new address.
+The new IP address is passed in
+.Va $new_ip_address ,
+and the interface name is passed in
+.Va $interface .
+The media type is passed in
+.Va $medium .
+Any options acquired from the server are passed using the option name
+described in
+.Xr dhcp-options 5 ,
+except that dashes
+.Pq Ql -
+are replaced by underscores
+.Pq Ql _
+in order to make valid shell variables, and the variable names start with
+.Dq Li new_ .
+So for example, the new subnet mask would be passed in
+.Va $new_subnet_mask .
+.Pp
+When a binding has been completed, a lot of network parameters are
+likely to need to be set up.
+A new
+.Pa /etc/resolv.conf
+needs to be created, using the values of
+.Va $new_domain_name
+and
+.Va $new_domain_name_servers
+(which may list more than one server, separated by spaces).
+A default route should be set using
+.Va $new_routers ,
+and static routes may need to be set up using
+.Va $new_static_routes .
+.Pp
+If an IP alias has been declared, it must be set up here.
+The alias IP address will be written as
+.Va $alias_ip_address ,
+and other DHCP options that are set for the alias (e.g., subnet mask)
+will be passed in variables named as described previously except starting with
+.Dq Li $alias_
+instead of
+.Dq Li $new_ .
+Care should be taken that the alias IP address not be used if it is identical
+to the bound IP address
+.Pq Va $new_ip_address ,
+since the other alias parameters may be incorrect in this case.
+.It Li RENEW
+When a binding has been renewed, the script is called as in
+.Li BOUND ,
+except that in addition to all the variables starting with
+.Dq Li $new_ ,
+there is another set of variables starting with
+.Dq Li $old_ .
+Persistent settings that may have changed need to be deleted - for example,
+if a local route to the bound address is being configured, the old local
+route should be deleted.
+If the default route has changed, the old default route should be deleted.
+If the static routes have changed, the old ones should be deleted.
+Otherwise, processing can be done as with
+.Li BOUND .
+.It Li REBIND
+The DHCP client has rebound to a new DHCP server.
+This can be handled as with
+.Li RENEW ,
+except that if the IP address has changed,
+the ARP table should be cleared.
+.It Li REBOOT
+The DHCP client has successfully reacquired its old address after a reboot.
+This can be processed as with
+.Li BOUND .
+.It Li EXPIRE
+The DHCP client has failed to renew its lease or acquire a new one,
+and the lease has expired.
+The IP address must be relinquished, and all related parameters should be
+deleted, as in
+.Li RENEW
+and
+.Li REBIND .
+.It Li FAIL
+The DHCP client has been unable to contact any DHCP servers, and any
+leases that have been tested have not proved to be valid.
+The parameters from the last lease tested should be deconfigured.
+This can be handled in the same way as
+.Li EXPIRE .
+.It Li TIMEOUT
+The DHCP client has been unable to contact any DHCP servers.
+However, an old lease has been identified, and its parameters have
+been passed in as with
+.Li BOUND .
+The client configuration script should test these parameters and,
+if it has reason to believe they are valid, should exit with a value of zero.
+If not, it should exit with a nonzero value.
+.El
+.Pp
+Before taking action according to
+.Va $reason ,
+.Nm
+will check for the existence of
+.Pa /etc/dhclient-enter-hooks .
+If found, it will be sourced
+.Pq see Xr sh 1 .
+After taking action according to
+.Va $reason ,
+.Nm
+will check for the existence of
+.Pa /etc/dhclient-exit-hooks .
+If found, it will be sourced
+.Pq see Xr sh 1 .
+These hooks scripts can be used to dynamically modify the environment at
+appropriate times during the DHCP negotiations.
+For example, if the administrator wishes to disable alias IP numbers on
+the DHCP interface, they might want to put the following in
+.Pa /etc/dhclient-enter-hooks :
+.Bd -literal -offset indent
+[ ."$reason" = .PREINIT ] && ifconfig $interface 0.0.0.0
+.Ed
+.Pp
+The usual way to test a lease is to set up the network as with
+.Li REBIND
+(since this may be called to test more than one lease) and then ping
+the first router defined in
+.Va $routers .
+If a response is received, the lease must be valid for the network to
+which the interface is currently connected.
+It would be more complete to try to ping all of the routers listed in
+.Va $new_routers ,
+as well as those listed in
+.Va $new_static_routes ,
+but current scripts do not do this.
+.\" .Sh FILES
+.\" Each operating system should generally have its own script file,
+.\" although the script files for similar operating systems may be similar
+.\" or even identical.
+.\" The script files included in the Internet Software Consortium DHCP
+.\" distribution appear in the distribution tree under client/scripts,
+.\" and bear the names of the operating systems on which they are intended
+.\" to work.
+.Sh SEE ALSO
+.Xr sh 1 ,
+.Xr dhclient.conf 5 ,
+.Xr dhclient.leases 5 ,
+.Xr dhclient 8 ,
+.Xr dhcpd 8 ,
+.Xr dhcrelay 8
+.Sh AUTHORS
+.An -nosplit
+The original version of
+.Nm
+was written for the Internet Software Consortium by
+.An Ted Lemon Aq Mt mellon@fugue.com
+in cooperation with Vixie Enterprises.
+.Pp
+The
+.Ox
+implementation of
+.Nm
+was written by
+.An Kenneth R. Westerback Aq Mt krw@openbsd.org .
+.Sh BUGS
+If more than one interface is being used, there is no obvious way to
+avoid clashes between server-supplied configuration parameters - for
+example, the stock
+.Nm
+rewrites
+.Pa /etc/resolv.conf .
+If more than one interface is being configured,
+.Pa /etc/resolv.conf
+will be repeatedly initialized to the values provided by one server, and then
+the other.
+Assuming the information provided by both servers is valid, this should not
+cause any real problems, but it could be confusing.
diff --git a/sbin/dhclient/dhclient.8 b/sbin/dhclient/dhclient.8
new file mode 100644
index 0000000..c6940da
--- /dev/null
+++ b/sbin/dhclient/dhclient.8
@@ -0,0 +1,196 @@
+.\" $OpenBSD: dhclient.8,v 1.3 2004/04/09 18:30:15 jmc Exp $
+.\"
+.\" Copyright (c) 1997 The Internet Software Consortium.
+.\" 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 Internet Software Consortium 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 INTERNET SOFTWARE CONSORTIUM 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 INTERNET SOFTWARE CONSORTIUM OR
+.\" CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+.\" SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+.\" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+.\" USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+.\" ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+.\" OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+.\" OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" This software has been written for the Internet Software Consortium
+.\" by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
+.\" Enterprises. To learn more about the Internet Software Consortium,
+.\" see ``http://www.isc.org/isc''. To learn more about Vixie
+.\" Enterprises, see ``http://www.vix.com''.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd October 13, 2011
+.Dt DHCLIENT 8
+.Os
+.Sh NAME
+.Nm dhclient
+.Nd "Dynamic Host Configuration Protocol (DHCP) client"
+.Sh SYNOPSIS
+.Nm
+.Op Fl bdqu
+.Op Fl c Ar file
+.Op Fl l Ar file
+.Op Fl p Ar file
+.Ar interface
+.Sh DESCRIPTION
+The
+.Nm
+utility provides a means for configuring network interfaces using DHCP, BOOTP,
+or if these protocols fail, by statically assigning an address.
+.Pp
+The name of the network interface that
+.Nm
+should attempt to
+configure must be specified on the command line.
+.Pp
+The options are as follows:
+.Bl -tag -width ".Fl c Ar file"
+.It Fl b
+Forces
+.Nm
+to immediately move to the background.
+.It Fl c Ar file
+Specify an alternate location,
+.Ar file ,
+for the configuration file.
+.It Fl d
+Forces
+.Nm
+to always run as a foreground process.
+By default,
+.Nm
+runs in the foreground until it has configured the interface, and then
+will revert to running in the background.
+.It Fl l Ar file
+Specify an alternate location,
+.Ar file ,
+for the leases file.
+.It Fl p Ar file
+Specify an alternate location for the PID file.
+The default is
+.Pa /var/run/dhclient. Ns Ar interface Ns Pa .pid .
+.It Fl q
+Forces
+.Nm
+to be less verbose on startup.
+.It Fl u
+Forces
+.Nm
+to reject leases with unknown options in them.
+The default behaviour is to accept such lease offers.
+.El
+.Pp
+The DHCP protocol allows a host to contact a central server which
+maintains a list of IP addresses which may be assigned on one or more
+subnets.
+A DHCP client may request an address from this pool, and
+then use it on a temporary basis for communication on the network.
+The DHCP protocol also provides a mechanism whereby a client can learn
+important details about the network to which it is attached, such as
+the location of a default router, the location of a name server, and
+so on.
+.Pp
+On startup,
+.Nm
+reads
+.Pa /etc/dhclient.conf
+for configuration instructions.
+It then gets a list of all the
+network interfaces that are configured in the current system.
+It then attempts to configure each interface with DHCP.
+.Pp
+In order to keep track of leases across system reboots and server
+restarts,
+.Nm
+keeps a list of leases it has been assigned in the
+.Pa /var/db/dhclient.leases. Ns Ar IFNAME
+file.
+.Ar IFNAME
+represents the network interface of the DHCP client
+(e.g.,
+.Li em0 ) ,
+one for each interface.
+On startup, after reading the
+.Xr dhclient.conf 5
+file,
+.Nm
+reads the leases file to refresh its memory about what leases it has been
+assigned.
+.Pp
+Old leases are kept around in case the DHCP server is unavailable when
+.Nm
+is first invoked (generally during the initial system boot
+process).
+In that event, old leases from the
+.Pa dhclient.leases. Ns Ar IFNAME
+file which have not yet expired are tested, and if they are determined to
+be valid, they are used until either they expire or the DHCP server
+becomes available.
+.Pp
+A mobile host which may sometimes need to access a network on which no
+DHCP server exists may be preloaded with a lease for a fixed
+address on that network.
+When all attempts to contact a DHCP server have failed,
+.Nm
+will try to validate the static lease, and if it
+succeeds, it will use that lease until it is restarted.
+.Pp
+A mobile host may also travel to some networks on which DHCP is not
+available but BOOTP is.
+In that case, it may be advantageous to
+arrange with the network administrator for an entry on the BOOTP
+database, so that the host can boot quickly on that network rather
+than cycling through the list of old leases.
+.Sh NOTES
+You must have the Berkeley Packet Filter (BPF) configured in your kernel.
+The
+.Nm
+utility
+requires at least one
+.Pa /dev/bpf*
+device for each broadcast network interface that is attached to your system.
+See
+.Xr bpf 4
+for more information.
+.Sh FILES
+.Bl -tag -width ".Pa /var/db/dhclient.leases. Ns Ar IFNAME" -compact
+.It Pa /etc/dhclient.conf
+DHCP client configuration file
+.It Pa /var/db/dhclient.leases. Ns Ar IFNAME
+database of acquired leases
+.El
+.Sh SEE ALSO
+.Xr dhclient.conf 5 ,
+.Xr dhclient.leases 5 ,
+.Xr dhclient-script 8
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+utility
+was written by
+.An Ted Lemon Aq Mt mellon@fugue.com
+and
+.An Elliot Poger Aq Mt elliot@poger.com .
+.Pp
+The current implementation was reworked by
+.An Henning Brauer Aq Mt henning@openbsd.org .
diff --git a/sbin/dhclient/dhclient.c b/sbin/dhclient/dhclient.c
new file mode 100644
index 0000000..22b21f1
--- /dev/null
+++ b/sbin/dhclient/dhclient.c
@@ -0,0 +1,2756 @@
+/* $OpenBSD: dhclient.c,v 1.63 2005/02/06 17:10:13 krw Exp $ */
+
+/*
+ * Copyright 2004 Henning Brauer <henning@openbsd.org>
+ * Copyright (c) 1995, 1996, 1997, 1998, 1999
+ * The Internet Software Consortium. 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 Internet Software Consortium 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 INTERNET SOFTWARE CONSORTIUM 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 INTERNET SOFTWARE CONSORTIUM OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * This software has been written for the Internet Software Consortium
+ * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
+ * Enterprises. To learn more about the Internet Software Consortium,
+ * see ``http://www.vix.com/isc''. To learn more about Vixie
+ * Enterprises, see ``http://www.vix.com''.
+ *
+ * This client was substantially modified and enhanced by Elliot Poger
+ * for use on Linux while he was working on the MosquitoNet project at
+ * Stanford.
+ *
+ * The current version owes much to Elliot's Linux enhancements, but
+ * was substantially reorganized and partially rewritten by Ted Lemon
+ * so as to use the same networking framework that the Internet Software
+ * Consortium DHCP server uses. Much system-specific configuration code
+ * was moved into a shell script so that as support for more operating
+ * systems is added, it will not be necessary to port and maintain
+ * system-specific configuration code to these operating systems - instead,
+ * the shell script can invoke the native tools to accomplish the same
+ * purpose.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include "dhcpd.h"
+#include "privsep.h"
+
+#include <sys/capsicum.h>
+
+#include <net80211/ieee80211_freebsd.h>
+
+#ifndef _PATH_VAREMPTY
+#define _PATH_VAREMPTY "/var/empty"
+#endif
+
+#define PERIOD 0x2e
+#define hyphenchar(c) ((c) == 0x2d)
+#define bslashchar(c) ((c) == 0x5c)
+#define periodchar(c) ((c) == PERIOD)
+#define asterchar(c) ((c) == 0x2a)
+#define alphachar(c) (((c) >= 0x41 && (c) <= 0x5a) || \
+ ((c) >= 0x61 && (c) <= 0x7a))
+#define digitchar(c) ((c) >= 0x30 && (c) <= 0x39)
+#define whitechar(c) ((c) == ' ' || (c) == '\t')
+
+#define borderchar(c) (alphachar(c) || digitchar(c))
+#define middlechar(c) (borderchar(c) || hyphenchar(c))
+#define domainchar(c) ((c) > 0x20 && (c) < 0x7f)
+
+#define CLIENT_PATH "PATH=/usr/bin:/usr/sbin:/bin:/sbin"
+
+time_t cur_time;
+time_t default_lease_time = 43200; /* 12 hours... */
+
+char *path_dhclient_conf = _PATH_DHCLIENT_CONF;
+char *path_dhclient_db = NULL;
+
+int log_perror = 1;
+int privfd;
+int nullfd = -1;
+
+char hostname[_POSIX_HOST_NAME_MAX + 1];
+
+struct iaddr iaddr_broadcast = { 4, { 255, 255, 255, 255 } };
+struct in_addr inaddr_any, inaddr_broadcast;
+
+char *path_dhclient_pidfile;
+struct pidfh *pidfile;
+
+/*
+ * ASSERT_STATE() does nothing now; it used to be
+ * assert (state_is == state_shouldbe).
+ */
+#define ASSERT_STATE(state_is, state_shouldbe) {}
+
+#define TIME_MAX 2147483647
+
+int log_priority;
+int no_daemon;
+int unknown_ok = 1;
+int routefd;
+
+struct interface_info *ifi;
+
+int findproto(char *, int);
+struct sockaddr *get_ifa(char *, int);
+void routehandler(struct protocol *);
+void usage(void);
+int check_option(struct client_lease *l, int option);
+int check_classless_option(unsigned char *data, int len);
+int ipv4addrs(char * buf);
+int res_hnok(const char *dn);
+int check_search(const char *srch);
+char *option_as_string(unsigned int code, unsigned char *data, int len);
+int fork_privchld(int, int);
+
+#define ROUNDUP(a) \
+ ((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long))
+#define ADVANCE(x, n) (x += ROUNDUP((n)->sa_len))
+
+static time_t scripttime;
+
+int
+findproto(char *cp, int n)
+{
+ struct sockaddr *sa;
+ int i;
+
+ if (n == 0)
+ return -1;
+ for (i = 1; i; i <<= 1) {
+ if (i & n) {
+ sa = (struct sockaddr *)cp;
+ switch (i) {
+ case RTA_IFA:
+ case RTA_DST:
+ case RTA_GATEWAY:
+ case RTA_NETMASK:
+ if (sa->sa_family == AF_INET)
+ return AF_INET;
+ if (sa->sa_family == AF_INET6)
+ return AF_INET6;
+ break;
+ case RTA_IFP:
+ break;
+ }
+ ADVANCE(cp, sa);
+ }
+ }
+ return (-1);
+}
+
+struct sockaddr *
+get_ifa(char *cp, int n)
+{
+ struct sockaddr *sa;
+ int i;
+
+ if (n == 0)
+ return (NULL);
+ for (i = 1; i; i <<= 1)
+ if (i & n) {
+ sa = (struct sockaddr *)cp;
+ if (i == RTA_IFA)
+ return (sa);
+ ADVANCE(cp, sa);
+ }
+
+ return (NULL);
+}
+
+struct iaddr defaddr = { 4 };
+uint8_t curbssid[6];
+
+static void
+disassoc(void *arg)
+{
+ struct interface_info *ifi = arg;
+
+ /*
+ * Clear existing state.
+ */
+ if (ifi->client->active != NULL) {
+ script_init("EXPIRE", NULL);
+ script_write_params("old_",
+ ifi->client->active);
+ if (ifi->client->alias)
+ script_write_params("alias_",
+ ifi->client->alias);
+ script_go();
+ }
+ ifi->client->state = S_INIT;
+}
+
+/* ARGSUSED */
+void
+routehandler(struct protocol *p)
+{
+ char msg[2048], *addr;
+ struct rt_msghdr *rtm;
+ struct if_msghdr *ifm;
+ struct ifa_msghdr *ifam;
+ struct if_announcemsghdr *ifan;
+ struct ieee80211_join_event *jev;
+ struct client_lease *l;
+ time_t t = time(NULL);
+ struct sockaddr *sa;
+ struct iaddr a;
+ ssize_t n;
+ int linkstat;
+
+ n = read(routefd, &msg, sizeof(msg));
+ rtm = (struct rt_msghdr *)msg;
+ if (n < sizeof(rtm->rtm_msglen) || n < rtm->rtm_msglen ||
+ rtm->rtm_version != RTM_VERSION)
+ return;
+
+ switch (rtm->rtm_type) {
+ case RTM_NEWADDR:
+ case RTM_DELADDR:
+ ifam = (struct ifa_msghdr *)rtm;
+
+ if (ifam->ifam_index != ifi->index)
+ break;
+ if (findproto((char *)(ifam + 1), ifam->ifam_addrs) != AF_INET)
+ break;
+ if (scripttime == 0 || t < scripttime + 10)
+ break;
+
+ sa = get_ifa((char *)(ifam + 1), ifam->ifam_addrs);
+ if (sa == NULL)
+ break;
+
+ if ((a.len = sizeof(struct in_addr)) > sizeof(a.iabuf))
+ error("king bula sez: len mismatch");
+ memcpy(a.iabuf, &((struct sockaddr_in *)sa)->sin_addr, a.len);
+ if (addr_eq(a, defaddr))
+ break;
+
+ for (l = ifi->client->active; l != NULL; l = l->next)
+ if (addr_eq(a, l->address))
+ break;
+
+ if (l == NULL) /* added/deleted addr is not the one we set */
+ break;
+
+ addr = inet_ntoa(((struct sockaddr_in *)sa)->sin_addr);
+ if (rtm->rtm_type == RTM_NEWADDR) {
+ /*
+ * XXX: If someone other than us adds our address,
+ * should we assume they are taking over from us,
+ * delete the lease record, and exit without modifying
+ * the interface?
+ */
+ warning("My address (%s) was re-added", addr);
+ } else {
+ warning("My address (%s) was deleted, dhclient exiting",
+ addr);
+ goto die;
+ }
+ break;
+ case RTM_IFINFO:
+ ifm = (struct if_msghdr *)rtm;
+ if (ifm->ifm_index != ifi->index)
+ break;
+ if ((rtm->rtm_flags & RTF_UP) == 0) {
+ warning("Interface %s is down, dhclient exiting",
+ ifi->name);
+ goto die;
+ }
+ linkstat = interface_link_status(ifi->name);
+ if (linkstat != ifi->linkstat) {
+ debug("%s link state %s -> %s", ifi->name,
+ ifi->linkstat ? "up" : "down",
+ linkstat ? "up" : "down");
+ ifi->linkstat = linkstat;
+ if (linkstat)
+ state_reboot(ifi);
+ }
+ break;
+ case RTM_IFANNOUNCE:
+ ifan = (struct if_announcemsghdr *)rtm;
+ if (ifan->ifan_what == IFAN_DEPARTURE &&
+ ifan->ifan_index == ifi->index) {
+ warning("Interface %s is gone, dhclient exiting",
+ ifi->name);
+ goto die;
+ }
+ break;
+ case RTM_IEEE80211:
+ ifan = (struct if_announcemsghdr *)rtm;
+ if (ifan->ifan_index != ifi->index)
+ break;
+ switch (ifan->ifan_what) {
+ case RTM_IEEE80211_ASSOC:
+ case RTM_IEEE80211_REASSOC:
+ /*
+ * Use assoc/reassoc event to kick state machine
+ * in case we roam. Otherwise fall back to the
+ * normal state machine just like a wired network.
+ */
+ jev = (struct ieee80211_join_event *) &ifan[1];
+ if (memcmp(curbssid, jev->iev_addr, 6)) {
+ disassoc(ifi);
+ state_reboot(ifi);
+ }
+ memcpy(curbssid, jev->iev_addr, 6);
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ return;
+
+die:
+ script_init("FAIL", NULL);
+ if (ifi->client->alias)
+ script_write_params("alias_", ifi->client->alias);
+ script_go();
+ if (pidfile != NULL)
+ pidfile_remove(pidfile);
+ exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+ extern char *__progname;
+ int ch, fd, quiet = 0, i = 0;
+ int pipe_fd[2];
+ int immediate_daemon = 0;
+ struct passwd *pw;
+ pid_t otherpid;
+ cap_rights_t rights;
+
+ /* Initially, log errors to stderr as well as to syslogd. */
+ openlog(__progname, LOG_PID | LOG_NDELAY, DHCPD_LOG_FACILITY);
+ setlogmask(LOG_UPTO(LOG_DEBUG));
+
+ while ((ch = getopt(argc, argv, "bc:dl:p:qu")) != -1)
+ switch (ch) {
+ case 'b':
+ immediate_daemon = 1;
+ break;
+ case 'c':
+ path_dhclient_conf = optarg;
+ break;
+ case 'd':
+ no_daemon = 1;
+ break;
+ case 'l':
+ path_dhclient_db = optarg;
+ break;
+ case 'p':
+ path_dhclient_pidfile = optarg;
+ break;
+ case 'q':
+ quiet = 1;
+ break;
+ case 'u':
+ unknown_ok = 0;
+ break;
+ default:
+ usage();
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 1)
+ usage();
+
+ if (path_dhclient_pidfile == NULL) {
+ asprintf(&path_dhclient_pidfile,
+ "%sdhclient.%s.pid", _PATH_VARRUN, *argv);
+ if (path_dhclient_pidfile == NULL)
+ error("asprintf");
+ }
+ pidfile = pidfile_open(path_dhclient_pidfile, 0644, &otherpid);
+ if (pidfile == NULL) {
+ if (errno == EEXIST)
+ error("dhclient already running, pid: %d.", otherpid);
+ if (errno == EAGAIN)
+ error("dhclient already running.");
+ warning("Cannot open or create pidfile: %m");
+ }
+
+ if ((ifi = calloc(1, sizeof(struct interface_info))) == NULL)
+ error("calloc");
+ if (strlcpy(ifi->name, argv[0], IFNAMSIZ) >= IFNAMSIZ)
+ error("Interface name too long");
+ if (path_dhclient_db == NULL && asprintf(&path_dhclient_db, "%s.%s",
+ _PATH_DHCLIENT_DB, ifi->name) == -1)
+ error("asprintf");
+
+ if (quiet)
+ log_perror = 0;
+
+ tzset();
+ time(&cur_time);
+
+ inaddr_broadcast.s_addr = INADDR_BROADCAST;
+ inaddr_any.s_addr = INADDR_ANY;
+
+ read_client_conf();
+
+ /* The next bit is potentially very time-consuming, so write out
+ the pidfile right away. We will write it out again with the
+ correct pid after daemonizing. */
+ if (pidfile != NULL)
+ pidfile_write(pidfile);
+
+ if (!interface_link_status(ifi->name)) {
+ fprintf(stderr, "%s: no link ...", ifi->name);
+ fflush(stderr);
+ sleep(1);
+ while (!interface_link_status(ifi->name)) {
+ fprintf(stderr, ".");
+ fflush(stderr);
+ if (++i > 10) {
+ fprintf(stderr, " giving up\n");
+ exit(1);
+ }
+ sleep(1);
+ }
+ fprintf(stderr, " got link\n");
+ }
+ ifi->linkstat = 1;
+
+ if ((nullfd = open(_PATH_DEVNULL, O_RDWR, 0)) == -1)
+ error("cannot open %s: %m", _PATH_DEVNULL);
+
+ if ((pw = getpwnam("_dhcp")) == NULL) {
+ warning("no such user: _dhcp, falling back to \"nobody\"");
+ if ((pw = getpwnam("nobody")) == NULL)
+ error("no such user: nobody");
+ }
+
+ /*
+ * Obtain hostname before entering capability mode - it won't be
+ * possible then, as reading kern.hostname is not permitted.
+ */
+ if (gethostname(hostname, sizeof(hostname)) < 0)
+ hostname[0] = '\0';
+
+ priv_script_init("PREINIT", NULL);
+ if (ifi->client->alias)
+ priv_script_write_params("alias_", ifi->client->alias);
+ priv_script_go();
+
+ /* set up the interface */
+ discover_interfaces(ifi);
+
+ if (pipe(pipe_fd) == -1)
+ error("pipe");
+
+ fork_privchld(pipe_fd[0], pipe_fd[1]);
+
+ close(ifi->ufdesc);
+ ifi->ufdesc = -1;
+ close(ifi->wfdesc);
+ ifi->wfdesc = -1;
+
+ close(pipe_fd[0]);
+ privfd = pipe_fd[1];
+ cap_rights_init(&rights, CAP_READ, CAP_WRITE);
+ if (cap_rights_limit(privfd, &rights) < 0 && errno != ENOSYS)
+ error("can't limit private descriptor: %m");
+
+ if ((fd = open(path_dhclient_db, O_RDONLY|O_EXLOCK|O_CREAT, 0)) == -1)
+ error("can't open and lock %s: %m", path_dhclient_db);
+ read_client_leases();
+ rewrite_client_leases();
+ close(fd);
+
+ if ((routefd = socket(PF_ROUTE, SOCK_RAW, 0)) != -1)
+ add_protocol("AF_ROUTE", routefd, routehandler, ifi);
+ if (shutdown(routefd, SHUT_WR) < 0)
+ error("can't shutdown route socket: %m");
+ cap_rights_init(&rights, CAP_EVENT, CAP_READ);
+ if (cap_rights_limit(routefd, &rights) < 0 && errno != ENOSYS)
+ error("can't limit route socket: %m");
+
+ if (chroot(_PATH_VAREMPTY) == -1)
+ error("chroot");
+ if (chdir("/") == -1)
+ error("chdir(\"/\")");
+
+ if (setgroups(1, &pw->pw_gid) ||
+ setegid(pw->pw_gid) || setgid(pw->pw_gid) ||
+ seteuid(pw->pw_uid) || setuid(pw->pw_uid))
+ error("can't drop privileges: %m");
+
+ endpwent();
+
+ setproctitle("%s", ifi->name);
+
+ if (cap_enter() < 0 && errno != ENOSYS)
+ error("can't enter capability mode: %m");
+
+ if (immediate_daemon)
+ go_daemon();
+
+ ifi->client->state = S_INIT;
+ state_reboot(ifi);
+
+ bootp_packet_handler = do_packet;
+
+ dispatch();
+
+ /* not reached */
+ return (0);
+}
+
+void
+usage(void)
+{
+ extern char *__progname;
+
+ fprintf(stderr, "usage: %s [-bdqu] ", __progname);
+ fprintf(stderr, "[-c conffile] [-l leasefile] interface\n");
+ exit(1);
+}
+
+/*
+ * Individual States:
+ *
+ * Each routine is called from the dhclient_state_machine() in one of
+ * these conditions:
+ * -> entering INIT state
+ * -> recvpacket_flag == 0: timeout in this state
+ * -> otherwise: received a packet in this state
+ *
+ * Return conditions as handled by dhclient_state_machine():
+ * Returns 1, sendpacket_flag = 1: send packet, reset timer.
+ * Returns 1, sendpacket_flag = 0: just reset the timer (wait for a milestone).
+ * Returns 0: finish the nap which was interrupted for no good reason.
+ *
+ * Several per-interface variables are used to keep track of the process:
+ * active_lease: the lease that is being used on the interface
+ * (null pointer if not configured yet).
+ * offered_leases: leases corresponding to DHCPOFFER messages that have
+ * been sent to us by DHCP servers.
+ * acked_leases: leases corresponding to DHCPACK messages that have been
+ * sent to us by DHCP servers.
+ * sendpacket: DHCP packet we're trying to send.
+ * destination: IP address to send sendpacket to
+ * In addition, there are several relevant per-lease variables.
+ * T1_expiry, T2_expiry, lease_expiry: lease milestones
+ * In the active lease, these control the process of renewing the lease;
+ * In leases on the acked_leases list, this simply determines when we
+ * can no longer legitimately use the lease.
+ */
+
+void
+state_reboot(void *ipp)
+{
+ struct interface_info *ip = ipp;
+
+ /* If we don't remember an active lease, go straight to INIT. */
+ if (!ip->client->active || ip->client->active->is_bootp) {
+ state_init(ip);
+ return;
+ }
+
+ /* We are in the rebooting state. */
+ ip->client->state = S_REBOOTING;
+
+ /* make_request doesn't initialize xid because it normally comes
+ from the DHCPDISCOVER, but we haven't sent a DHCPDISCOVER,
+ so pick an xid now. */
+ ip->client->xid = arc4random();
+
+ /* Make a DHCPREQUEST packet, and set appropriate per-interface
+ flags. */
+ make_request(ip, ip->client->active);
+ ip->client->destination = iaddr_broadcast;
+ ip->client->first_sending = cur_time;
+ ip->client->interval = ip->client->config->initial_interval;
+
+ /* Zap the medium list... */
+ ip->client->medium = NULL;
+
+ /* Send out the first DHCPREQUEST packet. */
+ send_request(ip);
+}
+
+/*
+ * Called when a lease has completely expired and we've
+ * been unable to renew it.
+ */
+void
+state_init(void *ipp)
+{
+ struct interface_info *ip = ipp;
+
+ ASSERT_STATE(state, S_INIT);
+
+ /* Make a DHCPDISCOVER packet, and set appropriate per-interface
+ flags. */
+ make_discover(ip, ip->client->active);
+ ip->client->xid = ip->client->packet.xid;
+ ip->client->destination = iaddr_broadcast;
+ ip->client->state = S_SELECTING;
+ ip->client->first_sending = cur_time;
+ ip->client->interval = ip->client->config->initial_interval;
+
+ /* Add an immediate timeout to cause the first DHCPDISCOVER packet
+ to go out. */
+ send_discover(ip);
+}
+
+/*
+ * state_selecting is called when one or more DHCPOFFER packets
+ * have been received and a configurable period of time has passed.
+ */
+void
+state_selecting(void *ipp)
+{
+ struct interface_info *ip = ipp;
+ struct client_lease *lp, *next, *picked;
+
+ ASSERT_STATE(state, S_SELECTING);
+
+ /* Cancel state_selecting and send_discover timeouts, since either
+ one could have got us here. */
+ cancel_timeout(state_selecting, ip);
+ cancel_timeout(send_discover, ip);
+
+ /* We have received one or more DHCPOFFER packets. Currently,
+ the only criterion by which we judge leases is whether or
+ not we get a response when we arp for them. */
+ picked = NULL;
+ for (lp = ip->client->offered_leases; lp; lp = next) {
+ next = lp->next;
+
+ /* Check to see if we got an ARPREPLY for the address
+ in this particular lease. */
+ if (!picked) {
+ script_init("ARPCHECK", lp->medium);
+ script_write_params("check_", lp);
+
+ /* If the ARPCHECK code detects another
+ machine using the offered address, it exits
+ nonzero. We need to send a DHCPDECLINE and
+ toss the lease. */
+ if (script_go()) {
+ make_decline(ip, lp);
+ send_decline(ip);
+ goto freeit;
+ }
+ picked = lp;
+ picked->next = NULL;
+ } else {
+freeit:
+ free_client_lease(lp);
+ }
+ }
+ ip->client->offered_leases = NULL;
+
+ /* If we just tossed all the leases we were offered, go back
+ to square one. */
+ if (!picked) {
+ ip->client->state = S_INIT;
+ state_init(ip);
+ return;
+ }
+
+ /* If it was a BOOTREPLY, we can just take the address right now. */
+ if (!picked->options[DHO_DHCP_MESSAGE_TYPE].len) {
+ ip->client->new = picked;
+
+ /* Make up some lease expiry times
+ XXX these should be configurable. */
+ ip->client->new->expiry = cur_time + 12000;
+ ip->client->new->renewal += cur_time + 8000;
+ ip->client->new->rebind += cur_time + 10000;
+
+ ip->client->state = S_REQUESTING;
+
+ /* Bind to the address we received. */
+ bind_lease(ip);
+ return;
+ }
+
+ /* Go to the REQUESTING state. */
+ ip->client->destination = iaddr_broadcast;
+ ip->client->state = S_REQUESTING;
+ ip->client->first_sending = cur_time;
+ ip->client->interval = ip->client->config->initial_interval;
+
+ /* Make a DHCPREQUEST packet from the lease we picked. */
+ make_request(ip, picked);
+ ip->client->xid = ip->client->packet.xid;
+
+ /* Toss the lease we picked - we'll get it back in a DHCPACK. */
+ free_client_lease(picked);
+
+ /* Add an immediate timeout to send the first DHCPREQUEST packet. */
+ send_request(ip);
+}
+
+/* state_requesting is called when we receive a DHCPACK message after
+ having sent out one or more DHCPREQUEST packets. */
+
+void
+dhcpack(struct packet *packet)
+{
+ struct interface_info *ip = packet->interface;
+ struct client_lease *lease;
+
+ /* If we're not receptive to an offer right now, or if the offer
+ has an unrecognizable transaction id, then just drop it. */
+ if (packet->interface->client->xid != packet->raw->xid ||
+ (packet->interface->hw_address.hlen != packet->raw->hlen) ||
+ (memcmp(packet->interface->hw_address.haddr,
+ packet->raw->chaddr, packet->raw->hlen)))
+ return;
+
+ if (ip->client->state != S_REBOOTING &&
+ ip->client->state != S_REQUESTING &&
+ ip->client->state != S_RENEWING &&
+ ip->client->state != S_REBINDING)
+ return;
+
+ note("DHCPACK from %s", piaddr(packet->client_addr));
+
+ lease = packet_to_lease(packet);
+ if (!lease) {
+ note("packet_to_lease failed.");
+ return;
+ }
+
+ ip->client->new = lease;
+
+ /* Stop resending DHCPREQUEST. */
+ cancel_timeout(send_request, ip);
+
+ /* Figure out the lease time. */
+ if (ip->client->new->options[DHO_DHCP_LEASE_TIME].data)
+ ip->client->new->expiry = getULong(
+ ip->client->new->options[DHO_DHCP_LEASE_TIME].data);
+ else
+ ip->client->new->expiry = default_lease_time;
+ /* A number that looks negative here is really just very large,
+ because the lease expiry offset is unsigned. */
+ if (ip->client->new->expiry < 0)
+ ip->client->new->expiry = TIME_MAX;
+ /* XXX should be fixed by resetting the client state */
+ if (ip->client->new->expiry < 60)
+ ip->client->new->expiry = 60;
+
+ /* Take the server-provided renewal time if there is one;
+ otherwise figure it out according to the spec. */
+ if (ip->client->new->options[DHO_DHCP_RENEWAL_TIME].len)
+ ip->client->new->renewal = getULong(
+ ip->client->new->options[DHO_DHCP_RENEWAL_TIME].data);
+ else
+ ip->client->new->renewal = ip->client->new->expiry / 2;
+
+ /* Same deal with the rebind time. */
+ if (ip->client->new->options[DHO_DHCP_REBINDING_TIME].len)
+ ip->client->new->rebind = getULong(
+ ip->client->new->options[DHO_DHCP_REBINDING_TIME].data);
+ else
+ ip->client->new->rebind = ip->client->new->renewal +
+ ip->client->new->renewal / 2 + ip->client->new->renewal / 4;
+
+ ip->client->new->expiry += cur_time;
+ /* Lease lengths can never be negative. */
+ if (ip->client->new->expiry < cur_time)
+ ip->client->new->expiry = TIME_MAX;
+ ip->client->new->renewal += cur_time;
+ if (ip->client->new->renewal < cur_time)
+ ip->client->new->renewal = TIME_MAX;
+ ip->client->new->rebind += cur_time;
+ if (ip->client->new->rebind < cur_time)
+ ip->client->new->rebind = TIME_MAX;
+
+ bind_lease(ip);
+}
+
+void
+bind_lease(struct interface_info *ip)
+{
+ /* Remember the medium. */
+ ip->client->new->medium = ip->client->medium;
+
+ /* Write out the new lease. */
+ write_client_lease(ip, ip->client->new, 0);
+
+ /* Run the client script with the new parameters. */
+ script_init((ip->client->state == S_REQUESTING ? "BOUND" :
+ (ip->client->state == S_RENEWING ? "RENEW" :
+ (ip->client->state == S_REBOOTING ? "REBOOT" : "REBIND"))),
+ ip->client->new->medium);
+ if (ip->client->active && ip->client->state != S_REBOOTING)
+ script_write_params("old_", ip->client->active);
+ script_write_params("new_", ip->client->new);
+ if (ip->client->alias)
+ script_write_params("alias_", ip->client->alias);
+ script_go();
+
+ /* Replace the old active lease with the new one. */
+ if (ip->client->active)
+ free_client_lease(ip->client->active);
+ ip->client->active = ip->client->new;
+ ip->client->new = NULL;
+
+ /* Set up a timeout to start the renewal process. */
+ add_timeout(ip->client->active->renewal, state_bound, ip);
+
+ note("bound to %s -- renewal in %d seconds.",
+ piaddr(ip->client->active->address),
+ (int)(ip->client->active->renewal - cur_time));
+ ip->client->state = S_BOUND;
+ reinitialize_interfaces();
+ go_daemon();
+}
+
+/*
+ * state_bound is called when we've successfully bound to a particular
+ * lease, but the renewal time on that lease has expired. We are
+ * expected to unicast a DHCPREQUEST to the server that gave us our
+ * original lease.
+ */
+void
+state_bound(void *ipp)
+{
+ struct interface_info *ip = ipp;
+
+ ASSERT_STATE(state, S_BOUND);
+
+ /* T1 has expired. */
+ make_request(ip, ip->client->active);
+ ip->client->xid = ip->client->packet.xid;
+
+ if (ip->client->active->options[DHO_DHCP_SERVER_IDENTIFIER].len == 4) {
+ memcpy(ip->client->destination.iabuf, ip->client->active->
+ options[DHO_DHCP_SERVER_IDENTIFIER].data, 4);
+ ip->client->destination.len = 4;
+ } else
+ ip->client->destination = iaddr_broadcast;
+
+ ip->client->first_sending = cur_time;
+ ip->client->interval = ip->client->config->initial_interval;
+ ip->client->state = S_RENEWING;
+
+ /* Send the first packet immediately. */
+ send_request(ip);
+}
+
+void
+bootp(struct packet *packet)
+{
+ struct iaddrlist *ap;
+
+ if (packet->raw->op != BOOTREPLY)
+ return;
+
+ /* If there's a reject list, make sure this packet's sender isn't
+ on it. */
+ for (ap = packet->interface->client->config->reject_list;
+ ap; ap = ap->next) {
+ if (addr_eq(packet->client_addr, ap->addr)) {
+ note("BOOTREPLY from %s rejected.", piaddr(ap->addr));
+ return;
+ }
+ }
+ dhcpoffer(packet);
+}
+
+void
+dhcp(struct packet *packet)
+{
+ struct iaddrlist *ap;
+ void (*handler)(struct packet *);
+ char *type;
+
+ switch (packet->packet_type) {
+ case DHCPOFFER:
+ handler = dhcpoffer;
+ type = "DHCPOFFER";
+ break;
+ case DHCPNAK:
+ handler = dhcpnak;
+ type = "DHCPNACK";
+ break;
+ case DHCPACK:
+ handler = dhcpack;
+ type = "DHCPACK";
+ break;
+ default:
+ return;
+ }
+
+ /* If there's a reject list, make sure this packet's sender isn't
+ on it. */
+ for (ap = packet->interface->client->config->reject_list;
+ ap; ap = ap->next) {
+ if (addr_eq(packet->client_addr, ap->addr)) {
+ note("%s from %s rejected.", type, piaddr(ap->addr));
+ return;
+ }
+ }
+ (*handler)(packet);
+}
+
+void
+dhcpoffer(struct packet *packet)
+{
+ struct interface_info *ip = packet->interface;
+ struct client_lease *lease, *lp;
+ int i;
+ int arp_timeout_needed, stop_selecting;
+ char *name = packet->options[DHO_DHCP_MESSAGE_TYPE].len ?
+ "DHCPOFFER" : "BOOTREPLY";
+
+ /* If we're not receptive to an offer right now, or if the offer
+ has an unrecognizable transaction id, then just drop it. */
+ if (ip->client->state != S_SELECTING ||
+ packet->interface->client->xid != packet->raw->xid ||
+ (packet->interface->hw_address.hlen != packet->raw->hlen) ||
+ (memcmp(packet->interface->hw_address.haddr,
+ packet->raw->chaddr, packet->raw->hlen)))
+ return;
+
+ note("%s from %s", name, piaddr(packet->client_addr));
+
+
+ /* If this lease doesn't supply the minimum required parameters,
+ blow it off. */
+ for (i = 0; ip->client->config->required_options[i]; i++) {
+ if (!packet->options[ip->client->config->
+ required_options[i]].len) {
+ note("%s isn't satisfactory.", name);
+ return;
+ }
+ }
+
+ /* If we've already seen this lease, don't record it again. */
+ for (lease = ip->client->offered_leases;
+ lease; lease = lease->next) {
+ if (lease->address.len == sizeof(packet->raw->yiaddr) &&
+ !memcmp(lease->address.iabuf,
+ &packet->raw->yiaddr, lease->address.len)) {
+ debug("%s already seen.", name);
+ return;
+ }
+ }
+
+ lease = packet_to_lease(packet);
+ if (!lease) {
+ note("packet_to_lease failed.");
+ return;
+ }
+
+ /* If this lease was acquired through a BOOTREPLY, record that
+ fact. */
+ if (!packet->options[DHO_DHCP_MESSAGE_TYPE].len)
+ lease->is_bootp = 1;
+
+ /* Record the medium under which this lease was offered. */
+ lease->medium = ip->client->medium;
+
+ /* Send out an ARP Request for the offered IP address. */
+ script_init("ARPSEND", lease->medium);
+ script_write_params("check_", lease);
+ /* If the script can't send an ARP request without waiting,
+ we'll be waiting when we do the ARPCHECK, so don't wait now. */
+ if (script_go())
+ arp_timeout_needed = 0;
+ else
+ arp_timeout_needed = 2;
+
+ /* Figure out when we're supposed to stop selecting. */
+ stop_selecting =
+ ip->client->first_sending + ip->client->config->select_interval;
+
+ /* If this is the lease we asked for, put it at the head of the
+ list, and don't mess with the arp request timeout. */
+ if (lease->address.len == ip->client->requested_address.len &&
+ !memcmp(lease->address.iabuf,
+ ip->client->requested_address.iabuf,
+ ip->client->requested_address.len)) {
+ lease->next = ip->client->offered_leases;
+ ip->client->offered_leases = lease;
+ } else {
+ /* If we already have an offer, and arping for this
+ offer would take us past the selection timeout,
+ then don't extend the timeout - just hope for the
+ best. */
+ if (ip->client->offered_leases &&
+ (cur_time + arp_timeout_needed) > stop_selecting)
+ arp_timeout_needed = 0;
+
+ /* Put the lease at the end of the list. */
+ lease->next = NULL;
+ if (!ip->client->offered_leases)
+ ip->client->offered_leases = lease;
+ else {
+ for (lp = ip->client->offered_leases; lp->next;
+ lp = lp->next)
+ ; /* nothing */
+ lp->next = lease;
+ }
+ }
+
+ /* If we're supposed to stop selecting before we've had time
+ to wait for the ARPREPLY, add some delay to wait for
+ the ARPREPLY. */
+ if (stop_selecting - cur_time < arp_timeout_needed)
+ stop_selecting = cur_time + arp_timeout_needed;
+
+ /* If the selecting interval has expired, go immediately to
+ state_selecting(). Otherwise, time out into
+ state_selecting at the select interval. */
+ if (stop_selecting <= 0)
+ state_selecting(ip);
+ else {
+ add_timeout(stop_selecting, state_selecting, ip);
+ cancel_timeout(send_discover, ip);
+ }
+}
+
+/* Allocate a client_lease structure and initialize it from the parameters
+ in the specified packet. */
+
+struct client_lease *
+packet_to_lease(struct packet *packet)
+{
+ struct client_lease *lease;
+ int i;
+
+ lease = malloc(sizeof(struct client_lease));
+
+ if (!lease) {
+ warning("dhcpoffer: no memory to record lease.");
+ return (NULL);
+ }
+
+ memset(lease, 0, sizeof(*lease));
+
+ /* Copy the lease options. */
+ for (i = 0; i < 256; i++) {
+ if (packet->options[i].len) {
+ lease->options[i].data =
+ malloc(packet->options[i].len + 1);
+ if (!lease->options[i].data) {
+ warning("dhcpoffer: no memory for option %d", i);
+ free_client_lease(lease);
+ return (NULL);
+ } else {
+ memcpy(lease->options[i].data,
+ packet->options[i].data,
+ packet->options[i].len);
+ lease->options[i].len =
+ packet->options[i].len;
+ lease->options[i].data[lease->options[i].len] =
+ 0;
+ }
+ if (!check_option(lease,i)) {
+ /* ignore a bogus lease offer */
+ warning("Invalid lease option - ignoring offer");
+ free_client_lease(lease);
+ return (NULL);
+ }
+ }
+ }
+
+ lease->address.len = sizeof(packet->raw->yiaddr);
+ memcpy(lease->address.iabuf, &packet->raw->yiaddr, lease->address.len);
+
+ lease->nextserver.len = sizeof(packet->raw->siaddr);
+ memcpy(lease->nextserver.iabuf, &packet->raw->siaddr, lease->nextserver.len);
+
+ /* If the server name was filled out, copy it.
+ Do not attempt to validate the server name as a host name.
+ RFC 2131 merely states that sname is NUL-terminated (which do
+ do not assume) and that it is the server's host name. Since
+ the ISC client and server allow arbitrary characters, we do
+ as well. */
+ if ((!packet->options[DHO_DHCP_OPTION_OVERLOAD].len ||
+ !(packet->options[DHO_DHCP_OPTION_OVERLOAD].data[0] & 2)) &&
+ packet->raw->sname[0]) {
+ lease->server_name = malloc(DHCP_SNAME_LEN + 1);
+ if (!lease->server_name) {
+ warning("dhcpoffer: no memory for server name.");
+ free_client_lease(lease);
+ return (NULL);
+ }
+ memcpy(lease->server_name, packet->raw->sname, DHCP_SNAME_LEN);
+ lease->server_name[DHCP_SNAME_LEN]='\0';
+ }
+
+ /* Ditto for the filename. */
+ if ((!packet->options[DHO_DHCP_OPTION_OVERLOAD].len ||
+ !(packet->options[DHO_DHCP_OPTION_OVERLOAD].data[0] & 1)) &&
+ packet->raw->file[0]) {
+ /* Don't count on the NUL terminator. */
+ lease->filename = malloc(DHCP_FILE_LEN + 1);
+ if (!lease->filename) {
+ warning("dhcpoffer: no memory for filename.");
+ free_client_lease(lease);
+ return (NULL);
+ }
+ memcpy(lease->filename, packet->raw->file, DHCP_FILE_LEN);
+ lease->filename[DHCP_FILE_LEN]='\0';
+ }
+ return lease;
+}
+
+void
+dhcpnak(struct packet *packet)
+{
+ struct interface_info *ip = packet->interface;
+
+ /* If we're not receptive to an offer right now, or if the offer
+ has an unrecognizable transaction id, then just drop it. */
+ if (packet->interface->client->xid != packet->raw->xid ||
+ (packet->interface->hw_address.hlen != packet->raw->hlen) ||
+ (memcmp(packet->interface->hw_address.haddr,
+ packet->raw->chaddr, packet->raw->hlen)))
+ return;
+
+ if (ip->client->state != S_REBOOTING &&
+ ip->client->state != S_REQUESTING &&
+ ip->client->state != S_RENEWING &&
+ ip->client->state != S_REBINDING)
+ return;
+
+ note("DHCPNAK from %s", piaddr(packet->client_addr));
+
+ if (!ip->client->active) {
+ note("DHCPNAK with no active lease.\n");
+ return;
+ }
+
+ free_client_lease(ip->client->active);
+ ip->client->active = NULL;
+
+ /* Stop sending DHCPREQUEST packets... */
+ cancel_timeout(send_request, ip);
+
+ ip->client->state = S_INIT;
+ state_init(ip);
+}
+
+/* Send out a DHCPDISCOVER packet, and set a timeout to send out another
+ one after the right interval has expired. If we don't get an offer by
+ the time we reach the panic interval, call the panic function. */
+
+void
+send_discover(void *ipp)
+{
+ struct interface_info *ip = ipp;
+ int interval, increase = 1;
+
+ /* Figure out how long it's been since we started transmitting. */
+ interval = cur_time - ip->client->first_sending;
+
+ /* If we're past the panic timeout, call the script and tell it
+ we haven't found anything for this interface yet. */
+ if (interval > ip->client->config->timeout) {
+ state_panic(ip);
+ return;
+ }
+
+ /* If we're selecting media, try the whole list before doing
+ the exponential backoff, but if we've already received an
+ offer, stop looping, because we obviously have it right. */
+ if (!ip->client->offered_leases &&
+ ip->client->config->media) {
+ int fail = 0;
+again:
+ if (ip->client->medium) {
+ ip->client->medium = ip->client->medium->next;
+ increase = 0;
+ }
+ if (!ip->client->medium) {
+ if (fail)
+ error("No valid media types for %s!", ip->name);
+ ip->client->medium = ip->client->config->media;
+ increase = 1;
+ }
+
+ note("Trying medium \"%s\" %d", ip->client->medium->string,
+ increase);
+ script_init("MEDIUM", ip->client->medium);
+ if (script_go())
+ goto again;
+ }
+
+ /*
+ * If we're supposed to increase the interval, do so. If it's
+ * currently zero (i.e., we haven't sent any packets yet), set
+ * it to one; otherwise, add to it a random number between zero
+ * and two times itself. On average, this means that it will
+ * double with every transmission.
+ */
+ if (increase) {
+ if (!ip->client->interval)
+ ip->client->interval =
+ ip->client->config->initial_interval;
+ else {
+ ip->client->interval += (arc4random() >> 2) %
+ (2 * ip->client->interval);
+ }
+
+ /* Don't backoff past cutoff. */
+ if (ip->client->interval >
+ ip->client->config->backoff_cutoff)
+ ip->client->interval =
+ ((ip->client->config->backoff_cutoff / 2)
+ + ((arc4random() >> 2) %
+ ip->client->config->backoff_cutoff));
+ } else if (!ip->client->interval)
+ ip->client->interval =
+ ip->client->config->initial_interval;
+
+ /* If the backoff would take us to the panic timeout, just use that
+ as the interval. */
+ if (cur_time + ip->client->interval >
+ ip->client->first_sending + ip->client->config->timeout)
+ ip->client->interval =
+ (ip->client->first_sending +
+ ip->client->config->timeout) - cur_time + 1;
+
+ /* Record the number of seconds since we started sending. */
+ if (interval < 65536)
+ ip->client->packet.secs = htons(interval);
+ else
+ ip->client->packet.secs = htons(65535);
+ ip->client->secs = ip->client->packet.secs;
+
+ note("DHCPDISCOVER on %s to %s port %d interval %d",
+ ip->name, inet_ntoa(inaddr_broadcast), REMOTE_PORT,
+ (int)ip->client->interval);
+
+ /* Send out a packet. */
+ send_packet_unpriv(privfd, &ip->client->packet,
+ ip->client->packet_length, inaddr_any, inaddr_broadcast);
+
+ add_timeout(cur_time + ip->client->interval, send_discover, ip);
+}
+
+/*
+ * state_panic gets called if we haven't received any offers in a preset
+ * amount of time. When this happens, we try to use existing leases
+ * that haven't yet expired, and failing that, we call the client script
+ * and hope it can do something.
+ */
+void
+state_panic(void *ipp)
+{
+ struct interface_info *ip = ipp;
+ struct client_lease *loop = ip->client->active;
+ struct client_lease *lp;
+
+ note("No DHCPOFFERS received.");
+
+ /* We may not have an active lease, but we may have some
+ predefined leases that we can try. */
+ if (!ip->client->active && ip->client->leases)
+ goto activate_next;
+
+ /* Run through the list of leases and see if one can be used. */
+ while (ip->client->active) {
+ if (ip->client->active->expiry > cur_time) {
+ note("Trying recorded lease %s",
+ piaddr(ip->client->active->address));
+ /* Run the client script with the existing
+ parameters. */
+ script_init("TIMEOUT",
+ ip->client->active->medium);
+ script_write_params("new_", ip->client->active);
+ if (ip->client->alias)
+ script_write_params("alias_",
+ ip->client->alias);
+
+ /* If the old lease is still good and doesn't
+ yet need renewal, go into BOUND state and
+ timeout at the renewal time. */
+ if (!script_go()) {
+ if (cur_time <
+ ip->client->active->renewal) {
+ ip->client->state = S_BOUND;
+ note("bound: renewal in %d seconds.",
+ (int)(ip->client->active->renewal -
+ cur_time));
+ add_timeout(
+ ip->client->active->renewal,
+ state_bound, ip);
+ } else {
+ ip->client->state = S_BOUND;
+ note("bound: immediate renewal.");
+ state_bound(ip);
+ }
+ reinitialize_interfaces();
+ go_daemon();
+ return;
+ }
+ }
+
+ /* If there are no other leases, give up. */
+ if (!ip->client->leases) {
+ ip->client->leases = ip->client->active;
+ ip->client->active = NULL;
+ break;
+ }
+
+activate_next:
+ /* Otherwise, put the active lease at the end of the
+ lease list, and try another lease.. */
+ for (lp = ip->client->leases; lp->next; lp = lp->next)
+ ;
+ lp->next = ip->client->active;
+ if (lp->next)
+ lp->next->next = NULL;
+ ip->client->active = ip->client->leases;
+ ip->client->leases = ip->client->leases->next;
+
+ /* If we already tried this lease, we've exhausted the
+ set of leases, so we might as well give up for
+ now. */
+ if (ip->client->active == loop)
+ break;
+ else if (!loop)
+ loop = ip->client->active;
+ }
+
+ /* No leases were available, or what was available didn't work, so
+ tell the shell script that we failed to allocate an address,
+ and try again later. */
+ note("No working leases in persistent database - sleeping.\n");
+ script_init("FAIL", NULL);
+ if (ip->client->alias)
+ script_write_params("alias_", ip->client->alias);
+ script_go();
+ ip->client->state = S_INIT;
+ add_timeout(cur_time + ip->client->config->retry_interval, state_init,
+ ip);
+ go_daemon();
+}
+
+void
+send_request(void *ipp)
+{
+ struct interface_info *ip = ipp;
+ struct in_addr from, to;
+ int interval;
+
+ /* Figure out how long it's been since we started transmitting. */
+ interval = cur_time - ip->client->first_sending;
+
+ /* If we're in the INIT-REBOOT or REQUESTING state and we're
+ past the reboot timeout, go to INIT and see if we can
+ DISCOVER an address... */
+ /* XXX In the INIT-REBOOT state, if we don't get an ACK, it
+ means either that we're on a network with no DHCP server,
+ or that our server is down. In the latter case, assuming
+ that there is a backup DHCP server, DHCPDISCOVER will get
+ us a new address, but we could also have successfully
+ reused our old address. In the former case, we're hosed
+ anyway. This is not a win-prone situation. */
+ if ((ip->client->state == S_REBOOTING ||
+ ip->client->state == S_REQUESTING) &&
+ interval > ip->client->config->reboot_timeout) {
+cancel:
+ ip->client->state = S_INIT;
+ cancel_timeout(send_request, ip);
+ state_init(ip);
+ return;
+ }
+
+ /* If we're in the reboot state, make sure the media is set up
+ correctly. */
+ if (ip->client->state == S_REBOOTING &&
+ !ip->client->medium &&
+ ip->client->active->medium ) {
+ script_init("MEDIUM", ip->client->active->medium);
+
+ /* If the medium we chose won't fly, go to INIT state. */
+ if (script_go())
+ goto cancel;
+
+ /* Record the medium. */
+ ip->client->medium = ip->client->active->medium;
+ }
+
+ /* If the lease has expired, relinquish the address and go back
+ to the INIT state. */
+ if (ip->client->state != S_REQUESTING &&
+ cur_time > ip->client->active->expiry) {
+ /* Run the client script with the new parameters. */
+ script_init("EXPIRE", NULL);
+ script_write_params("old_", ip->client->active);
+ if (ip->client->alias)
+ script_write_params("alias_", ip->client->alias);
+ script_go();
+
+ /* Now do a preinit on the interface so that we can
+ discover a new address. */
+ script_init("PREINIT", NULL);
+ if (ip->client->alias)
+ script_write_params("alias_", ip->client->alias);
+ script_go();
+
+ ip->client->state = S_INIT;
+ state_init(ip);
+ return;
+ }
+
+ /* Do the exponential backoff... */
+ if (!ip->client->interval)
+ ip->client->interval = ip->client->config->initial_interval;
+ else
+ ip->client->interval += ((arc4random() >> 2) %
+ (2 * ip->client->interval));
+
+ /* Don't backoff past cutoff. */
+ if (ip->client->interval >
+ ip->client->config->backoff_cutoff)
+ ip->client->interval =
+ ((ip->client->config->backoff_cutoff / 2) +
+ ((arc4random() >> 2) % ip->client->interval));
+
+ /* If the backoff would take us to the expiry time, just set the
+ timeout to the expiry time. */
+ if (ip->client->state != S_REQUESTING &&
+ cur_time + ip->client->interval >
+ ip->client->active->expiry)
+ ip->client->interval =
+ ip->client->active->expiry - cur_time + 1;
+
+ /* If the lease T2 time has elapsed, or if we're not yet bound,
+ broadcast the DHCPREQUEST rather than unicasting. */
+ if (ip->client->state == S_REQUESTING ||
+ ip->client->state == S_REBOOTING ||
+ cur_time > ip->client->active->rebind)
+ to.s_addr = INADDR_BROADCAST;
+ else
+ memcpy(&to.s_addr, ip->client->destination.iabuf,
+ sizeof(to.s_addr));
+
+ if (ip->client->state != S_REQUESTING)
+ memcpy(&from, ip->client->active->address.iabuf,
+ sizeof(from));
+ else
+ from.s_addr = INADDR_ANY;
+
+ /* Record the number of seconds since we started sending. */
+ if (ip->client->state == S_REQUESTING)
+ ip->client->packet.secs = ip->client->secs;
+ else {
+ if (interval < 65536)
+ ip->client->packet.secs = htons(interval);
+ else
+ ip->client->packet.secs = htons(65535);
+ }
+
+ note("DHCPREQUEST on %s to %s port %d", ip->name, inet_ntoa(to),
+ REMOTE_PORT);
+
+ /* Send out a packet. */
+ send_packet_unpriv(privfd, &ip->client->packet,
+ ip->client->packet_length, from, to);
+
+ add_timeout(cur_time + ip->client->interval, send_request, ip);
+}
+
+void
+send_decline(void *ipp)
+{
+ struct interface_info *ip = ipp;
+
+ note("DHCPDECLINE on %s to %s port %d", ip->name,
+ inet_ntoa(inaddr_broadcast), REMOTE_PORT);
+
+ /* Send out a packet. */
+ send_packet_unpriv(privfd, &ip->client->packet,
+ ip->client->packet_length, inaddr_any, inaddr_broadcast);
+}
+
+void
+make_discover(struct interface_info *ip, struct client_lease *lease)
+{
+ unsigned char discover = DHCPDISCOVER;
+ struct tree_cache *options[256];
+ struct tree_cache option_elements[256];
+ int i;
+
+ memset(option_elements, 0, sizeof(option_elements));
+ memset(options, 0, sizeof(options));
+ memset(&ip->client->packet, 0, sizeof(ip->client->packet));
+
+ /* Set DHCP_MESSAGE_TYPE to DHCPDISCOVER */
+ i = DHO_DHCP_MESSAGE_TYPE;
+ options[i] = &option_elements[i];
+ options[i]->value = &discover;
+ options[i]->len = sizeof(discover);
+ options[i]->buf_size = sizeof(discover);
+ options[i]->timeout = 0xFFFFFFFF;
+
+ /* Request the options we want */
+ i = DHO_DHCP_PARAMETER_REQUEST_LIST;
+ options[i] = &option_elements[i];
+ options[i]->value = ip->client->config->requested_options;
+ options[i]->len = ip->client->config->requested_option_count;
+ options[i]->buf_size =
+ ip->client->config->requested_option_count;
+ options[i]->timeout = 0xFFFFFFFF;
+
+ /* If we had an address, try to get it again. */
+ if (lease) {
+ ip->client->requested_address = lease->address;
+ i = DHO_DHCP_REQUESTED_ADDRESS;
+ options[i] = &option_elements[i];
+ options[i]->value = lease->address.iabuf;
+ options[i]->len = lease->address.len;
+ options[i]->buf_size = lease->address.len;
+ options[i]->timeout = 0xFFFFFFFF;
+ } else
+ ip->client->requested_address.len = 0;
+
+ /* Send any options requested in the config file. */
+ for (i = 0; i < 256; i++)
+ if (!options[i] &&
+ ip->client->config->send_options[i].data) {
+ options[i] = &option_elements[i];
+ options[i]->value =
+ ip->client->config->send_options[i].data;
+ options[i]->len =
+ ip->client->config->send_options[i].len;
+ options[i]->buf_size =
+ ip->client->config->send_options[i].len;
+ options[i]->timeout = 0xFFFFFFFF;
+ }
+
+ /* send host name if not set via config file. */
+ if (!options[DHO_HOST_NAME]) {
+ if (hostname[0] != '\0') {
+ size_t len;
+ char* posDot = strchr(hostname, '.');
+ if (posDot != NULL)
+ len = posDot - hostname;
+ else
+ len = strlen(hostname);
+ options[DHO_HOST_NAME] = &option_elements[DHO_HOST_NAME];
+ options[DHO_HOST_NAME]->value = hostname;
+ options[DHO_HOST_NAME]->len = len;
+ options[DHO_HOST_NAME]->buf_size = len;
+ options[DHO_HOST_NAME]->timeout = 0xFFFFFFFF;
+ }
+ }
+
+ /* set unique client identifier */
+ char client_ident[sizeof(struct hardware)];
+ if (!options[DHO_DHCP_CLIENT_IDENTIFIER]) {
+ int hwlen = (ip->hw_address.hlen < sizeof(client_ident)-1) ?
+ ip->hw_address.hlen : sizeof(client_ident)-1;
+ client_ident[0] = ip->hw_address.htype;
+ memcpy(&client_ident[1], ip->hw_address.haddr, hwlen);
+ options[DHO_DHCP_CLIENT_IDENTIFIER] = &option_elements[DHO_DHCP_CLIENT_IDENTIFIER];
+ options[DHO_DHCP_CLIENT_IDENTIFIER]->value = client_ident;
+ options[DHO_DHCP_CLIENT_IDENTIFIER]->len = hwlen+1;
+ options[DHO_DHCP_CLIENT_IDENTIFIER]->buf_size = hwlen+1;
+ options[DHO_DHCP_CLIENT_IDENTIFIER]->timeout = 0xFFFFFFFF;
+ }
+
+ /* Set up the option buffer... */
+ ip->client->packet_length = cons_options(NULL, &ip->client->packet, 0,
+ options, 0, 0, 0, NULL, 0);
+ if (ip->client->packet_length < BOOTP_MIN_LEN)
+ ip->client->packet_length = BOOTP_MIN_LEN;
+
+ ip->client->packet.op = BOOTREQUEST;
+ ip->client->packet.htype = ip->hw_address.htype;
+ ip->client->packet.hlen = ip->hw_address.hlen;
+ ip->client->packet.hops = 0;
+ ip->client->packet.xid = arc4random();
+ ip->client->packet.secs = 0; /* filled in by send_discover. */
+ ip->client->packet.flags = 0;
+
+ memset(&(ip->client->packet.ciaddr),
+ 0, sizeof(ip->client->packet.ciaddr));
+ memset(&(ip->client->packet.yiaddr),
+ 0, sizeof(ip->client->packet.yiaddr));
+ memset(&(ip->client->packet.siaddr),
+ 0, sizeof(ip->client->packet.siaddr));
+ memset(&(ip->client->packet.giaddr),
+ 0, sizeof(ip->client->packet.giaddr));
+ memcpy(ip->client->packet.chaddr,
+ ip->hw_address.haddr, ip->hw_address.hlen);
+}
+
+
+void
+make_request(struct interface_info *ip, struct client_lease * lease)
+{
+ unsigned char request = DHCPREQUEST;
+ struct tree_cache *options[256];
+ struct tree_cache option_elements[256];
+ int i;
+
+ memset(options, 0, sizeof(options));
+ memset(&ip->client->packet, 0, sizeof(ip->client->packet));
+
+ /* Set DHCP_MESSAGE_TYPE to DHCPREQUEST */
+ i = DHO_DHCP_MESSAGE_TYPE;
+ options[i] = &option_elements[i];
+ options[i]->value = &request;
+ options[i]->len = sizeof(request);
+ options[i]->buf_size = sizeof(request);
+ options[i]->timeout = 0xFFFFFFFF;
+
+ /* Request the options we want */
+ i = DHO_DHCP_PARAMETER_REQUEST_LIST;
+ options[i] = &option_elements[i];
+ options[i]->value = ip->client->config->requested_options;
+ options[i]->len = ip->client->config->requested_option_count;
+ options[i]->buf_size =
+ ip->client->config->requested_option_count;
+ options[i]->timeout = 0xFFFFFFFF;
+
+ /* If we are requesting an address that hasn't yet been assigned
+ to us, use the DHCP Requested Address option. */
+ if (ip->client->state == S_REQUESTING) {
+ /* Send back the server identifier... */
+ i = DHO_DHCP_SERVER_IDENTIFIER;
+ options[i] = &option_elements[i];
+ options[i]->value = lease->options[i].data;
+ options[i]->len = lease->options[i].len;
+ options[i]->buf_size = lease->options[i].len;
+ options[i]->timeout = 0xFFFFFFFF;
+ }
+ if (ip->client->state == S_REQUESTING ||
+ ip->client->state == S_REBOOTING) {
+ ip->client->requested_address = lease->address;
+ i = DHO_DHCP_REQUESTED_ADDRESS;
+ options[i] = &option_elements[i];
+ options[i]->value = lease->address.iabuf;
+ options[i]->len = lease->address.len;
+ options[i]->buf_size = lease->address.len;
+ options[i]->timeout = 0xFFFFFFFF;
+ } else
+ ip->client->requested_address.len = 0;
+
+ /* Send any options requested in the config file. */
+ for (i = 0; i < 256; i++)
+ if (!options[i] &&
+ ip->client->config->send_options[i].data) {
+ options[i] = &option_elements[i];
+ options[i]->value =
+ ip->client->config->send_options[i].data;
+ options[i]->len =
+ ip->client->config->send_options[i].len;
+ options[i]->buf_size =
+ ip->client->config->send_options[i].len;
+ options[i]->timeout = 0xFFFFFFFF;
+ }
+
+ /* send host name if not set via config file. */
+ if (!options[DHO_HOST_NAME]) {
+ if (hostname[0] != '\0') {
+ size_t len;
+ char* posDot = strchr(hostname, '.');
+ if (posDot != NULL)
+ len = posDot - hostname;
+ else
+ len = strlen(hostname);
+ options[DHO_HOST_NAME] = &option_elements[DHO_HOST_NAME];
+ options[DHO_HOST_NAME]->value = hostname;
+ options[DHO_HOST_NAME]->len = len;
+ options[DHO_HOST_NAME]->buf_size = len;
+ options[DHO_HOST_NAME]->timeout = 0xFFFFFFFF;
+ }
+ }
+
+ /* set unique client identifier */
+ char client_ident[sizeof(struct hardware)];
+ if (!options[DHO_DHCP_CLIENT_IDENTIFIER]) {
+ int hwlen = (ip->hw_address.hlen < sizeof(client_ident)-1) ?
+ ip->hw_address.hlen : sizeof(client_ident)-1;
+ client_ident[0] = ip->hw_address.htype;
+ memcpy(&client_ident[1], ip->hw_address.haddr, hwlen);
+ options[DHO_DHCP_CLIENT_IDENTIFIER] = &option_elements[DHO_DHCP_CLIENT_IDENTIFIER];
+ options[DHO_DHCP_CLIENT_IDENTIFIER]->value = client_ident;
+ options[DHO_DHCP_CLIENT_IDENTIFIER]->len = hwlen+1;
+ options[DHO_DHCP_CLIENT_IDENTIFIER]->buf_size = hwlen+1;
+ options[DHO_DHCP_CLIENT_IDENTIFIER]->timeout = 0xFFFFFFFF;
+ }
+
+ /* Set up the option buffer... */
+ ip->client->packet_length = cons_options(NULL, &ip->client->packet, 0,
+ options, 0, 0, 0, NULL, 0);
+ if (ip->client->packet_length < BOOTP_MIN_LEN)
+ ip->client->packet_length = BOOTP_MIN_LEN;
+
+ ip->client->packet.op = BOOTREQUEST;
+ ip->client->packet.htype = ip->hw_address.htype;
+ ip->client->packet.hlen = ip->hw_address.hlen;
+ ip->client->packet.hops = 0;
+ ip->client->packet.xid = ip->client->xid;
+ ip->client->packet.secs = 0; /* Filled in by send_request. */
+
+ /* If we own the address we're requesting, put it in ciaddr;
+ otherwise set ciaddr to zero. */
+ if (ip->client->state == S_BOUND ||
+ ip->client->state == S_RENEWING ||
+ ip->client->state == S_REBINDING) {
+ memcpy(&ip->client->packet.ciaddr,
+ lease->address.iabuf, lease->address.len);
+ ip->client->packet.flags = 0;
+ } else {
+ memset(&ip->client->packet.ciaddr, 0,
+ sizeof(ip->client->packet.ciaddr));
+ ip->client->packet.flags = 0;
+ }
+
+ memset(&ip->client->packet.yiaddr, 0,
+ sizeof(ip->client->packet.yiaddr));
+ memset(&ip->client->packet.siaddr, 0,
+ sizeof(ip->client->packet.siaddr));
+ memset(&ip->client->packet.giaddr, 0,
+ sizeof(ip->client->packet.giaddr));
+ memcpy(ip->client->packet.chaddr,
+ ip->hw_address.haddr, ip->hw_address.hlen);
+}
+
+void
+make_decline(struct interface_info *ip, struct client_lease *lease)
+{
+ struct tree_cache *options[256], message_type_tree;
+ struct tree_cache requested_address_tree;
+ struct tree_cache server_id_tree, client_id_tree;
+ unsigned char decline = DHCPDECLINE;
+ int i;
+
+ memset(options, 0, sizeof(options));
+ memset(&ip->client->packet, 0, sizeof(ip->client->packet));
+
+ /* Set DHCP_MESSAGE_TYPE to DHCPDECLINE */
+ i = DHO_DHCP_MESSAGE_TYPE;
+ options[i] = &message_type_tree;
+ options[i]->value = &decline;
+ options[i]->len = sizeof(decline);
+ options[i]->buf_size = sizeof(decline);
+ options[i]->timeout = 0xFFFFFFFF;
+
+ /* Send back the server identifier... */
+ i = DHO_DHCP_SERVER_IDENTIFIER;
+ options[i] = &server_id_tree;
+ options[i]->value = lease->options[i].data;
+ options[i]->len = lease->options[i].len;
+ options[i]->buf_size = lease->options[i].len;
+ options[i]->timeout = 0xFFFFFFFF;
+
+ /* Send back the address we're declining. */
+ i = DHO_DHCP_REQUESTED_ADDRESS;
+ options[i] = &requested_address_tree;
+ options[i]->value = lease->address.iabuf;
+ options[i]->len = lease->address.len;
+ options[i]->buf_size = lease->address.len;
+ options[i]->timeout = 0xFFFFFFFF;
+
+ /* Send the uid if the user supplied one. */
+ i = DHO_DHCP_CLIENT_IDENTIFIER;
+ if (ip->client->config->send_options[i].len) {
+ options[i] = &client_id_tree;
+ options[i]->value = ip->client->config->send_options[i].data;
+ options[i]->len = ip->client->config->send_options[i].len;
+ options[i]->buf_size = ip->client->config->send_options[i].len;
+ options[i]->timeout = 0xFFFFFFFF;
+ }
+
+
+ /* Set up the option buffer... */
+ ip->client->packet_length = cons_options(NULL, &ip->client->packet, 0,
+ options, 0, 0, 0, NULL, 0);
+ if (ip->client->packet_length < BOOTP_MIN_LEN)
+ ip->client->packet_length = BOOTP_MIN_LEN;
+
+ ip->client->packet.op = BOOTREQUEST;
+ ip->client->packet.htype = ip->hw_address.htype;
+ ip->client->packet.hlen = ip->hw_address.hlen;
+ ip->client->packet.hops = 0;
+ ip->client->packet.xid = ip->client->xid;
+ ip->client->packet.secs = 0; /* Filled in by send_request. */
+ ip->client->packet.flags = 0;
+
+ /* ciaddr must always be zero. */
+ memset(&ip->client->packet.ciaddr, 0,
+ sizeof(ip->client->packet.ciaddr));
+ memset(&ip->client->packet.yiaddr, 0,
+ sizeof(ip->client->packet.yiaddr));
+ memset(&ip->client->packet.siaddr, 0,
+ sizeof(ip->client->packet.siaddr));
+ memset(&ip->client->packet.giaddr, 0,
+ sizeof(ip->client->packet.giaddr));
+ memcpy(ip->client->packet.chaddr,
+ ip->hw_address.haddr, ip->hw_address.hlen);
+}
+
+void
+free_client_lease(struct client_lease *lease)
+{
+ int i;
+
+ if (lease->server_name)
+ free(lease->server_name);
+ if (lease->filename)
+ free(lease->filename);
+ for (i = 0; i < 256; i++) {
+ if (lease->options[i].len)
+ free(lease->options[i].data);
+ }
+ free(lease);
+}
+
+FILE *leaseFile;
+
+void
+rewrite_client_leases(void)
+{
+ struct client_lease *lp;
+ cap_rights_t rights;
+
+ if (!leaseFile) {
+ leaseFile = fopen(path_dhclient_db, "w");
+ if (!leaseFile)
+ error("can't create %s: %m", path_dhclient_db);
+ cap_rights_init(&rights, CAP_FSTAT, CAP_FSYNC, CAP_FTRUNCATE,
+ CAP_SEEK, CAP_WRITE);
+ if (cap_rights_limit(fileno(leaseFile), &rights) < 0 &&
+ errno != ENOSYS) {
+ error("can't limit lease descriptor: %m");
+ }
+ } else {
+ fflush(leaseFile);
+ rewind(leaseFile);
+ }
+
+ for (lp = ifi->client->leases; lp; lp = lp->next)
+ write_client_lease(ifi, lp, 1);
+ if (ifi->client->active)
+ write_client_lease(ifi, ifi->client->active, 1);
+
+ fflush(leaseFile);
+ ftruncate(fileno(leaseFile), ftello(leaseFile));
+ fsync(fileno(leaseFile));
+}
+
+void
+write_client_lease(struct interface_info *ip, struct client_lease *lease,
+ int rewrite)
+{
+ static int leases_written;
+ struct tm *t;
+ int i;
+
+ if (!rewrite) {
+ if (leases_written++ > 20) {
+ rewrite_client_leases();
+ leases_written = 0;
+ }
+ }
+
+ /* If the lease came from the config file, we don't need to stash
+ a copy in the lease database. */
+ if (lease->is_static)
+ return;
+
+ if (!leaseFile) { /* XXX */
+ leaseFile = fopen(path_dhclient_db, "w");
+ if (!leaseFile)
+ error("can't create %s: %m", path_dhclient_db);
+ }
+
+ fprintf(leaseFile, "lease {\n");
+ if (lease->is_bootp)
+ fprintf(leaseFile, " bootp;\n");
+ fprintf(leaseFile, " interface \"%s\";\n", ip->name);
+ fprintf(leaseFile, " fixed-address %s;\n", piaddr(lease->address));
+ if (lease->nextserver.len == sizeof(inaddr_any) &&
+ 0 != memcmp(lease->nextserver.iabuf, &inaddr_any,
+ sizeof(inaddr_any)))
+ fprintf(leaseFile, " next-server %s;\n",
+ piaddr(lease->nextserver));
+ if (lease->filename)
+ fprintf(leaseFile, " filename \"%s\";\n", lease->filename);
+ if (lease->server_name)
+ fprintf(leaseFile, " server-name \"%s\";\n",
+ lease->server_name);
+ if (lease->medium)
+ fprintf(leaseFile, " medium \"%s\";\n", lease->medium->string);
+ for (i = 0; i < 256; i++)
+ if (lease->options[i].len)
+ fprintf(leaseFile, " option %s %s;\n",
+ dhcp_options[i].name,
+ pretty_print_option(i, lease->options[i].data,
+ lease->options[i].len, 1, 1));
+
+ t = gmtime(&lease->renewal);
+ fprintf(leaseFile, " renew %d %d/%d/%d %02d:%02d:%02d;\n",
+ t->tm_wday, t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
+ t->tm_hour, t->tm_min, t->tm_sec);
+ t = gmtime(&lease->rebind);
+ fprintf(leaseFile, " rebind %d %d/%d/%d %02d:%02d:%02d;\n",
+ t->tm_wday, t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
+ t->tm_hour, t->tm_min, t->tm_sec);
+ t = gmtime(&lease->expiry);
+ fprintf(leaseFile, " expire %d %d/%d/%d %02d:%02d:%02d;\n",
+ t->tm_wday, t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
+ t->tm_hour, t->tm_min, t->tm_sec);
+ fprintf(leaseFile, "}\n");
+ fflush(leaseFile);
+}
+
+void
+script_init(char *reason, struct string_list *medium)
+{
+ size_t len, mediumlen = 0;
+ struct imsg_hdr hdr;
+ struct buf *buf;
+ int errs;
+
+ if (medium != NULL && medium->string != NULL)
+ mediumlen = strlen(medium->string);
+
+ hdr.code = IMSG_SCRIPT_INIT;
+ hdr.len = sizeof(struct imsg_hdr) +
+ sizeof(size_t) + mediumlen +
+ sizeof(size_t) + strlen(reason);
+
+ if ((buf = buf_open(hdr.len)) == NULL)
+ error("buf_open: %m");
+
+ errs = 0;
+ errs += buf_add(buf, &hdr, sizeof(hdr));
+ errs += buf_add(buf, &mediumlen, sizeof(mediumlen));
+ if (mediumlen > 0)
+ errs += buf_add(buf, medium->string, mediumlen);
+ len = strlen(reason);
+ errs += buf_add(buf, &len, sizeof(len));
+ errs += buf_add(buf, reason, len);
+
+ if (errs)
+ error("buf_add: %m");
+
+ if (buf_close(privfd, buf) == -1)
+ error("buf_close: %m");
+}
+
+void
+priv_script_init(char *reason, char *medium)
+{
+ struct interface_info *ip = ifi;
+
+ if (ip) {
+ ip->client->scriptEnvsize = 100;
+ if (ip->client->scriptEnv == NULL)
+ ip->client->scriptEnv =
+ malloc(ip->client->scriptEnvsize * sizeof(char *));
+ if (ip->client->scriptEnv == NULL)
+ error("script_init: no memory for environment");
+
+ ip->client->scriptEnv[0] = strdup(CLIENT_PATH);
+ if (ip->client->scriptEnv[0] == NULL)
+ error("script_init: no memory for environment");
+
+ ip->client->scriptEnv[1] = NULL;
+
+ script_set_env(ip->client, "", "interface", ip->name);
+
+ if (medium)
+ script_set_env(ip->client, "", "medium", medium);
+
+ script_set_env(ip->client, "", "reason", reason);
+ }
+}
+
+void
+priv_script_write_params(char *prefix, struct client_lease *lease)
+{
+ struct interface_info *ip = ifi;
+ u_int8_t dbuf[1500], *dp = NULL;
+ int i, len;
+ char tbuf[128];
+
+ script_set_env(ip->client, prefix, "ip_address",
+ piaddr(lease->address));
+
+ if (ip->client->config->default_actions[DHO_SUBNET_MASK] ==
+ ACTION_SUPERSEDE) {
+ dp = ip->client->config->defaults[DHO_SUBNET_MASK].data;
+ len = ip->client->config->defaults[DHO_SUBNET_MASK].len;
+ } else {
+ dp = lease->options[DHO_SUBNET_MASK].data;
+ len = lease->options[DHO_SUBNET_MASK].len;
+ }
+ if (len && (len < sizeof(lease->address.iabuf))) {
+ struct iaddr netmask, subnet, broadcast;
+
+ memcpy(netmask.iabuf, dp, len);
+ netmask.len = len;
+ subnet = subnet_number(lease->address, netmask);
+ if (subnet.len) {
+ script_set_env(ip->client, prefix, "network_number",
+ piaddr(subnet));
+ if (!lease->options[DHO_BROADCAST_ADDRESS].len) {
+ broadcast = broadcast_addr(subnet, netmask);
+ if (broadcast.len)
+ script_set_env(ip->client, prefix,
+ "broadcast_address",
+ piaddr(broadcast));
+ }
+ }
+ }
+
+ if (lease->filename)
+ script_set_env(ip->client, prefix, "filename", lease->filename);
+ if (lease->server_name)
+ script_set_env(ip->client, prefix, "server_name",
+ lease->server_name);
+ for (i = 0; i < 256; i++) {
+ len = 0;
+
+ if (ip->client->config->defaults[i].len) {
+ if (lease->options[i].len) {
+ switch (
+ ip->client->config->default_actions[i]) {
+ case ACTION_DEFAULT:
+ dp = lease->options[i].data;
+ len = lease->options[i].len;
+ break;
+ case ACTION_SUPERSEDE:
+supersede:
+ dp = ip->client->
+ config->defaults[i].data;
+ len = ip->client->
+ config->defaults[i].len;
+ break;
+ case ACTION_PREPEND:
+ len = ip->client->
+ config->defaults[i].len +
+ lease->options[i].len;
+ if (len >= sizeof(dbuf)) {
+ warning("no space to %s %s",
+ "prepend option",
+ dhcp_options[i].name);
+ goto supersede;
+ }
+ dp = dbuf;
+ memcpy(dp,
+ ip->client->
+ config->defaults[i].data,
+ ip->client->
+ config->defaults[i].len);
+ memcpy(dp + ip->client->
+ config->defaults[i].len,
+ lease->options[i].data,
+ lease->options[i].len);
+ dp[len] = '\0';
+ break;
+ case ACTION_APPEND:
+ /*
+ * When we append, we assume that we're
+ * appending to text. Some MS servers
+ * include a NUL byte at the end of
+ * the search string provided.
+ */
+ len = ip->client->
+ config->defaults[i].len +
+ lease->options[i].len;
+ if (len >= sizeof(dbuf)) {
+ warning("no space to %s %s",
+ "append option",
+ dhcp_options[i].name);
+ goto supersede;
+ }
+ memcpy(dbuf,
+ lease->options[i].data,
+ lease->options[i].len);
+ for (dp = dbuf + lease->options[i].len;
+ dp > dbuf; dp--, len--)
+ if (dp[-1] != '\0')
+ break;
+ memcpy(dp,
+ ip->client->
+ config->defaults[i].data,
+ ip->client->
+ config->defaults[i].len);
+ dp = dbuf;
+ dp[len] = '\0';
+ }
+ } else {
+ dp = ip->client->
+ config->defaults[i].data;
+ len = ip->client->
+ config->defaults[i].len;
+ }
+ } else if (lease->options[i].len) {
+ len = lease->options[i].len;
+ dp = lease->options[i].data;
+ } else {
+ len = 0;
+ }
+ if (len) {
+ char name[256];
+
+ if (dhcp_option_ev_name(name, sizeof(name),
+ &dhcp_options[i]))
+ script_set_env(ip->client, prefix, name,
+ pretty_print_option(i, dp, len, 0, 0));
+ }
+ }
+ snprintf(tbuf, sizeof(tbuf), "%d", (int)lease->expiry);
+ script_set_env(ip->client, prefix, "expiry", tbuf);
+}
+
+void
+script_write_params(char *prefix, struct client_lease *lease)
+{
+ size_t fn_len = 0, sn_len = 0, pr_len = 0;
+ struct imsg_hdr hdr;
+ struct buf *buf;
+ int errs, i;
+
+ if (lease->filename != NULL)
+ fn_len = strlen(lease->filename);
+ if (lease->server_name != NULL)
+ sn_len = strlen(lease->server_name);
+ if (prefix != NULL)
+ pr_len = strlen(prefix);
+
+ hdr.code = IMSG_SCRIPT_WRITE_PARAMS;
+ hdr.len = sizeof(hdr) + sizeof(struct client_lease) +
+ sizeof(size_t) + fn_len + sizeof(size_t) + sn_len +
+ sizeof(size_t) + pr_len;
+
+ for (i = 0; i < 256; i++)
+ hdr.len += sizeof(int) + lease->options[i].len;
+
+ scripttime = time(NULL);
+
+ if ((buf = buf_open(hdr.len)) == NULL)
+ error("buf_open: %m");
+
+ errs = 0;
+ errs += buf_add(buf, &hdr, sizeof(hdr));
+ errs += buf_add(buf, lease, sizeof(struct client_lease));
+ errs += buf_add(buf, &fn_len, sizeof(fn_len));
+ errs += buf_add(buf, lease->filename, fn_len);
+ errs += buf_add(buf, &sn_len, sizeof(sn_len));
+ errs += buf_add(buf, lease->server_name, sn_len);
+ errs += buf_add(buf, &pr_len, sizeof(pr_len));
+ errs += buf_add(buf, prefix, pr_len);
+
+ for (i = 0; i < 256; i++) {
+ errs += buf_add(buf, &lease->options[i].len,
+ sizeof(lease->options[i].len));
+ errs += buf_add(buf, lease->options[i].data,
+ lease->options[i].len);
+ }
+
+ if (errs)
+ error("buf_add: %m");
+
+ if (buf_close(privfd, buf) == -1)
+ error("buf_close: %m");
+}
+
+int
+script_go(void)
+{
+ struct imsg_hdr hdr;
+ struct buf *buf;
+ int ret;
+
+ hdr.code = IMSG_SCRIPT_GO;
+ hdr.len = sizeof(struct imsg_hdr);
+
+ if ((buf = buf_open(hdr.len)) == NULL)
+ error("buf_open: %m");
+
+ if (buf_add(buf, &hdr, sizeof(hdr)))
+ error("buf_add: %m");
+
+ if (buf_close(privfd, buf) == -1)
+ error("buf_close: %m");
+
+ bzero(&hdr, sizeof(hdr));
+ buf_read(privfd, &hdr, sizeof(hdr));
+ if (hdr.code != IMSG_SCRIPT_GO_RET)
+ error("unexpected msg type %u", hdr.code);
+ if (hdr.len != sizeof(hdr) + sizeof(int))
+ error("received corrupted message");
+ buf_read(privfd, &ret, sizeof(ret));
+
+ scripttime = time(NULL);
+
+ return (ret);
+}
+
+int
+priv_script_go(void)
+{
+ char *scriptName, *argv[2], **envp, *epp[3], reason[] = "REASON=NBI";
+ static char client_path[] = CLIENT_PATH;
+ struct interface_info *ip = ifi;
+ int pid, wpid, wstatus;
+
+ scripttime = time(NULL);
+
+ if (ip) {
+ scriptName = ip->client->config->script_name;
+ envp = ip->client->scriptEnv;
+ } else {
+ scriptName = top_level_config.script_name;
+ epp[0] = reason;
+ epp[1] = client_path;
+ epp[2] = NULL;
+ envp = epp;
+ }
+
+ argv[0] = scriptName;
+ argv[1] = NULL;
+
+ pid = fork();
+ if (pid < 0) {
+ error("fork: %m");
+ wstatus = 0;
+ } else if (pid) {
+ do {
+ wpid = wait(&wstatus);
+ } while (wpid != pid && wpid > 0);
+ if (wpid < 0) {
+ error("wait: %m");
+ wstatus = 0;
+ }
+ } else {
+ execve(scriptName, argv, envp);
+ error("execve (%s, ...): %m", scriptName);
+ }
+
+ if (ip)
+ script_flush_env(ip->client);
+
+ return (wstatus & 0xff);
+}
+
+void
+script_set_env(struct client_state *client, const char *prefix,
+ const char *name, const char *value)
+{
+ int i, j, namelen;
+
+ namelen = strlen(name);
+
+ for (i = 0; client->scriptEnv[i]; i++)
+ if (strncmp(client->scriptEnv[i], name, namelen) == 0 &&
+ client->scriptEnv[i][namelen] == '=')
+ break;
+
+ if (client->scriptEnv[i])
+ /* Reuse the slot. */
+ free(client->scriptEnv[i]);
+ else {
+ /* New variable. Expand if necessary. */
+ if (i >= client->scriptEnvsize - 1) {
+ char **newscriptEnv;
+ int newscriptEnvsize = client->scriptEnvsize + 50;
+
+ newscriptEnv = realloc(client->scriptEnv,
+ newscriptEnvsize);
+ if (newscriptEnv == NULL) {
+ free(client->scriptEnv);
+ client->scriptEnv = NULL;
+ client->scriptEnvsize = 0;
+ error("script_set_env: no memory for variable");
+ }
+ client->scriptEnv = newscriptEnv;
+ client->scriptEnvsize = newscriptEnvsize;
+ }
+ /* need to set the NULL pointer at end of array beyond
+ the new slot. */
+ client->scriptEnv[i + 1] = NULL;
+ }
+ /* Allocate space and format the variable in the appropriate slot. */
+ client->scriptEnv[i] = malloc(strlen(prefix) + strlen(name) + 1 +
+ strlen(value) + 1);
+ if (client->scriptEnv[i] == NULL)
+ error("script_set_env: no memory for variable assignment");
+
+ /* No `` or $() command substitution allowed in environment values! */
+ for (j=0; j < strlen(value); j++)
+ switch (value[j]) {
+ case '`':
+ case '$':
+ error("illegal character (%c) in value '%s'", value[j],
+ value);
+ /* not reached */
+ }
+ snprintf(client->scriptEnv[i], strlen(prefix) + strlen(name) +
+ 1 + strlen(value) + 1, "%s%s=%s", prefix, name, value);
+}
+
+void
+script_flush_env(struct client_state *client)
+{
+ int i;
+
+ for (i = 0; client->scriptEnv[i]; i++) {
+ free(client->scriptEnv[i]);
+ client->scriptEnv[i] = NULL;
+ }
+ client->scriptEnvsize = 0;
+}
+
+int
+dhcp_option_ev_name(char *buf, size_t buflen, struct option *option)
+{
+ int i;
+
+ for (i = 0; option->name[i]; i++) {
+ if (i + 1 == buflen)
+ return 0;
+ if (option->name[i] == '-')
+ buf[i] = '_';
+ else
+ buf[i] = option->name[i];
+ }
+
+ buf[i] = 0;
+ return 1;
+}
+
+void
+go_daemon(void)
+{
+ static int state = 0;
+ cap_rights_t rights;
+
+ if (no_daemon || state)
+ return;
+
+ state = 1;
+
+ /* Stop logging to stderr... */
+ log_perror = 0;
+
+ if (daemon(1, 0) == -1)
+ error("daemon");
+
+ cap_rights_init(&rights);
+
+ if (pidfile != NULL) {
+ pidfile_write(pidfile);
+ if (cap_rights_limit(pidfile_fileno(pidfile), &rights) < 0 &&
+ errno != ENOSYS) {
+ error("can't limit pidfile descriptor: %m");
+ }
+ }
+
+ /* we are chrooted, daemon(3) fails to open /dev/null */
+ if (nullfd != -1) {
+ dup2(nullfd, STDIN_FILENO);
+ dup2(nullfd, STDOUT_FILENO);
+ dup2(nullfd, STDERR_FILENO);
+ close(nullfd);
+ nullfd = -1;
+ }
+
+ if (cap_rights_limit(STDIN_FILENO, &rights) < 0 && errno != ENOSYS)
+ error("can't limit stdin: %m");
+ cap_rights_init(&rights, CAP_WRITE);
+ if (cap_rights_limit(STDOUT_FILENO, &rights) < 0 && errno != ENOSYS)
+ error("can't limit stdout: %m");
+ if (cap_rights_limit(STDERR_FILENO, &rights) < 0 && errno != ENOSYS)
+ error("can't limit stderr: %m");
+}
+
+int
+check_option(struct client_lease *l, int option)
+{
+ char *opbuf;
+ char *sbuf;
+
+ /* we use this, since this is what gets passed to dhclient-script */
+
+ opbuf = pretty_print_option(option, l->options[option].data,
+ l->options[option].len, 0, 0);
+
+ sbuf = option_as_string(option, l->options[option].data,
+ l->options[option].len);
+
+ switch (option) {
+ case DHO_SUBNET_MASK:
+ case DHO_TIME_SERVERS:
+ case DHO_NAME_SERVERS:
+ case DHO_ROUTERS:
+ case DHO_DOMAIN_NAME_SERVERS:
+ case DHO_LOG_SERVERS:
+ case DHO_COOKIE_SERVERS:
+ case DHO_LPR_SERVERS:
+ case DHO_IMPRESS_SERVERS:
+ case DHO_RESOURCE_LOCATION_SERVERS:
+ case DHO_SWAP_SERVER:
+ case DHO_BROADCAST_ADDRESS:
+ case DHO_NIS_SERVERS:
+ case DHO_NTP_SERVERS:
+ case DHO_NETBIOS_NAME_SERVERS:
+ case DHO_NETBIOS_DD_SERVER:
+ case DHO_FONT_SERVERS:
+ case DHO_DHCP_SERVER_IDENTIFIER:
+ case DHO_NISPLUS_SERVERS:
+ case DHO_MOBILE_IP_HOME_AGENT:
+ case DHO_SMTP_SERVER:
+ case DHO_POP_SERVER:
+ case DHO_NNTP_SERVER:
+ case DHO_WWW_SERVER:
+ case DHO_FINGER_SERVER:
+ case DHO_IRC_SERVER:
+ case DHO_STREETTALK_SERVER:
+ case DHO_STREETTALK_DA_SERVER:
+ if (!ipv4addrs(opbuf)) {
+ warning("Invalid IP address in option: %s", opbuf);
+ return (0);
+ }
+ return (1) ;
+ case DHO_HOST_NAME:
+ case DHO_NIS_DOMAIN:
+ case DHO_NISPLUS_DOMAIN:
+ case DHO_TFTP_SERVER_NAME:
+ if (!res_hnok(sbuf)) {
+ warning("Bogus Host Name option %d: %s (%s)", option,
+ sbuf, opbuf);
+ l->options[option].len = 0;
+ free(l->options[option].data);
+ }
+ return (1);
+ case DHO_DOMAIN_NAME:
+ case DHO_DOMAIN_SEARCH:
+ if (!res_hnok(sbuf)) {
+ if (!check_search(sbuf)) {
+ warning("Bogus domain search list %d: %s (%s)",
+ option, sbuf, opbuf);
+ l->options[option].len = 0;
+ free(l->options[option].data);
+ }
+ }
+ return (1);
+ case DHO_PAD:
+ case DHO_TIME_OFFSET:
+ case DHO_BOOT_SIZE:
+ case DHO_MERIT_DUMP:
+ case DHO_ROOT_PATH:
+ case DHO_EXTENSIONS_PATH:
+ case DHO_IP_FORWARDING:
+ case DHO_NON_LOCAL_SOURCE_ROUTING:
+ case DHO_POLICY_FILTER:
+ case DHO_MAX_DGRAM_REASSEMBLY:
+ case DHO_DEFAULT_IP_TTL:
+ case DHO_PATH_MTU_AGING_TIMEOUT:
+ case DHO_PATH_MTU_PLATEAU_TABLE:
+ case DHO_INTERFACE_MTU:
+ case DHO_ALL_SUBNETS_LOCAL:
+ case DHO_PERFORM_MASK_DISCOVERY:
+ case DHO_MASK_SUPPLIER:
+ case DHO_ROUTER_DISCOVERY:
+ case DHO_ROUTER_SOLICITATION_ADDRESS:
+ case DHO_STATIC_ROUTES:
+ case DHO_TRAILER_ENCAPSULATION:
+ case DHO_ARP_CACHE_TIMEOUT:
+ case DHO_IEEE802_3_ENCAPSULATION:
+ case DHO_DEFAULT_TCP_TTL:
+ case DHO_TCP_KEEPALIVE_INTERVAL:
+ case DHO_TCP_KEEPALIVE_GARBAGE:
+ case DHO_VENDOR_ENCAPSULATED_OPTIONS:
+ case DHO_NETBIOS_NODE_TYPE:
+ case DHO_NETBIOS_SCOPE:
+ case DHO_X_DISPLAY_MANAGER:
+ case DHO_DHCP_REQUESTED_ADDRESS:
+ case DHO_DHCP_LEASE_TIME:
+ case DHO_DHCP_OPTION_OVERLOAD:
+ case DHO_DHCP_MESSAGE_TYPE:
+ case DHO_DHCP_PARAMETER_REQUEST_LIST:
+ case DHO_DHCP_MESSAGE:
+ case DHO_DHCP_MAX_MESSAGE_SIZE:
+ case DHO_DHCP_RENEWAL_TIME:
+ case DHO_DHCP_REBINDING_TIME:
+ case DHO_DHCP_CLASS_IDENTIFIER:
+ case DHO_DHCP_CLIENT_IDENTIFIER:
+ case DHO_BOOTFILE_NAME:
+ case DHO_DHCP_USER_CLASS_ID:
+ case DHO_END:
+ return (1);
+ case DHO_CLASSLESS_ROUTES:
+ return (check_classless_option(l->options[option].data,
+ l->options[option].len));
+ default:
+ warning("unknown dhcp option value 0x%x", option);
+ return (unknown_ok);
+ }
+}
+
+/* RFC 3442 The Classless Static Routes option checks */
+int
+check_classless_option(unsigned char *data, int len)
+{
+ int i = 0;
+ unsigned char width;
+ in_addr_t addr, mask;
+
+ if (len < 5) {
+ warning("Too small length: %d", len);
+ return (0);
+ }
+ while(i < len) {
+ width = data[i++];
+ if (width == 0) {
+ i += 4;
+ continue;
+ } else if (width < 9) {
+ addr = (in_addr_t)(data[i] << 24);
+ i += 1;
+ } else if (width < 17) {
+ addr = (in_addr_t)(data[i] << 24) +
+ (in_addr_t)(data[i + 1] << 16);
+ i += 2;
+ } else if (width < 25) {
+ addr = (in_addr_t)(data[i] << 24) +
+ (in_addr_t)(data[i + 1] << 16) +
+ (in_addr_t)(data[i + 2] << 8);
+ i += 3;
+ } else if (width < 33) {
+ addr = (in_addr_t)(data[i] << 24) +
+ (in_addr_t)(data[i + 1] << 16) +
+ (in_addr_t)(data[i + 2] << 8) +
+ data[i + 3];
+ i += 4;
+ } else {
+ warning("Incorrect subnet width: %d", width);
+ return (0);
+ }
+ mask = (in_addr_t)(~0) << (32 - width);
+ addr = ntohl(addr);
+ mask = ntohl(mask);
+
+ /*
+ * From RFC 3442:
+ * ... After deriving a subnet number and subnet mask
+ * from each destination descriptor, the DHCP client
+ * MUST zero any bits in the subnet number where the
+ * corresponding bit in the mask is zero...
+ */
+ if ((addr & mask) != addr) {
+ addr &= mask;
+ data[i - 1] = (unsigned char)(
+ (addr >> (((32 - width)/8)*8)) & 0xFF);
+ }
+ i += 4;
+ }
+ if (i > len) {
+ warning("Incorrect data length: %d (must be %d)", len, i);
+ return (0);
+ }
+ return (1);
+}
+
+int
+res_hnok(const char *dn)
+{
+ int pch = PERIOD, ch = *dn++;
+
+ while (ch != '\0') {
+ int nch = *dn++;
+
+ if (periodchar(ch)) {
+ ;
+ } else if (periodchar(pch)) {
+ if (!borderchar(ch))
+ return (0);
+ } else if (periodchar(nch) || nch == '\0') {
+ if (!borderchar(ch))
+ return (0);
+ } else {
+ if (!middlechar(ch))
+ return (0);
+ }
+ pch = ch, ch = nch;
+ }
+ return (1);
+}
+
+int
+check_search(const char *srch)
+{
+ int pch = PERIOD, ch = *srch++;
+ int domains = 1;
+
+ /* 256 char limit re resolv.conf(5) */
+ if (strlen(srch) > 256)
+ return (0);
+
+ while (whitechar(ch))
+ ch = *srch++;
+
+ while (ch != '\0') {
+ int nch = *srch++;
+
+ if (periodchar(ch) || whitechar(ch)) {
+ ;
+ } else if (periodchar(pch)) {
+ if (!borderchar(ch))
+ return (0);
+ } else if (periodchar(nch) || nch == '\0') {
+ if (!borderchar(ch))
+ return (0);
+ } else {
+ if (!middlechar(ch))
+ return (0);
+ }
+ if (!whitechar(ch)) {
+ pch = ch;
+ } else {
+ while (whitechar(nch)) {
+ nch = *srch++;
+ }
+ if (nch != '\0')
+ domains++;
+ pch = PERIOD;
+ }
+ ch = nch;
+ }
+ /* 6 domain limit re resolv.conf(5) */
+ if (domains > 6)
+ return (0);
+ return (1);
+}
+
+/* Does buf consist only of dotted decimal ipv4 addrs?
+ * return how many if so,
+ * otherwise, return 0
+ */
+int
+ipv4addrs(char * buf)
+{
+ struct in_addr jnk;
+ int count = 0;
+
+ while (inet_aton(buf, &jnk) == 1){
+ count++;
+ while (periodchar(*buf) || digitchar(*buf))
+ buf++;
+ if (*buf == '\0')
+ return (count);
+ while (*buf == ' ')
+ buf++;
+ }
+ return (0);
+}
+
+
+char *
+option_as_string(unsigned int code, unsigned char *data, int len)
+{
+ static char optbuf[32768]; /* XXX */
+ char *op = optbuf;
+ int opleft = sizeof(optbuf);
+ unsigned char *dp = data;
+
+ if (code > 255)
+ error("option_as_string: bad code %d", code);
+
+ for (; dp < data + len; dp++) {
+ if (!isascii(*dp) || !isprint(*dp)) {
+ if (dp + 1 != data + len || *dp != 0) {
+ snprintf(op, opleft, "\\%03o", *dp);
+ op += 4;
+ opleft -= 4;
+ }
+ } else if (*dp == '"' || *dp == '\'' || *dp == '$' ||
+ *dp == '`' || *dp == '\\') {
+ *op++ = '\\';
+ *op++ = *dp;
+ opleft -= 2;
+ } else {
+ *op++ = *dp;
+ opleft--;
+ }
+ }
+ if (opleft < 1)
+ goto toobig;
+ *op = 0;
+ return optbuf;
+toobig:
+ warning("dhcp option too large");
+ return "<error>";
+}
+
+int
+fork_privchld(int fd, int fd2)
+{
+ struct pollfd pfd[1];
+ int nfds;
+
+ switch (fork()) {
+ case -1:
+ error("cannot fork");
+ case 0:
+ break;
+ default:
+ return (0);
+ }
+
+ setproctitle("%s [priv]", ifi->name);
+
+ setsid();
+ dup2(nullfd, STDIN_FILENO);
+ dup2(nullfd, STDOUT_FILENO);
+ dup2(nullfd, STDERR_FILENO);
+ close(nullfd);
+ close(fd2);
+ close(ifi->rfdesc);
+ ifi->rfdesc = -1;
+
+ for (;;) {
+ pfd[0].fd = fd;
+ pfd[0].events = POLLIN;
+ if ((nfds = poll(pfd, 1, INFTIM)) == -1)
+ if (errno != EINTR)
+ error("poll error");
+
+ if (nfds == 0 || !(pfd[0].revents & POLLIN))
+ continue;
+
+ dispatch_imsg(ifi, fd);
+ }
+}
diff --git a/sbin/dhclient/dhclient.conf b/sbin/dhclient/dhclient.conf
new file mode 100644
index 0000000..7eaeeb8
--- /dev/null
+++ b/sbin/dhclient/dhclient.conf
@@ -0,0 +1,39 @@
+# $FreeBSD$
+
+send host-name "andare.fugue.com";
+send dhcp-client-identifier 1:0:a0:24:ab:fb:9c;
+send dhcp-lease-time 3600;
+supersede domain-name "fugue.com home.vix.com";
+prepend domain-name-servers 127.0.0.1;
+request subnet-mask, broadcast-address, time-offset,
+ classless-routes, routers, domain-name,
+ domain-name-servers, host-name;
+require subnet-mask, domain-name-servers;
+timeout 60;
+retry 60;
+reboot 10;
+select-timeout 5;
+initial-interval 2;
+script "/etc/dhclient-script";
+media "-link0 -link1 -link2", "link0 link1";
+reject 192.33.137.209;
+
+alias {
+ interface "ep0";
+ fixed-address 192.5.5.213;
+ option subnet-mask 255.255.255.255;
+}
+
+lease {
+ interface "ep0";
+ fixed-address 192.33.137.200;
+ medium "link0 link1";
+ option host-name "andare.swiftmedia.com";
+ option subnet-mask 255.255.255.0;
+ option broadcast-address 192.33.137.255;
+ option routers 192.33.137.250;
+ option domain-name-servers 127.0.0.1;
+ renew 2 2000/1/12 00:00:01;
+ rebind 2 2000/1/12 00:00:01;
+ expire 2 2000/1/12 00:00:01;
+}
diff --git a/sbin/dhclient/dhclient.conf.5 b/sbin/dhclient/dhclient.conf.5
new file mode 100644
index 0000000..fb9d9f1
--- /dev/null
+++ b/sbin/dhclient/dhclient.conf.5
@@ -0,0 +1,544 @@
+.\" $OpenBSD: dhclient.conf.5,v 1.5 2004/11/01 23:10:18 henning Exp $
+.\"
+.\" Copyright (c) 1997 The Internet Software Consortium.
+.\" 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 Internet Software Consortium 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 INTERNET SOFTWARE CONSORTIUM 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 INTERNET SOFTWARE CONSORTIUM OR
+.\" CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+.\" SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+.\" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+.\" USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+.\" ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+.\" OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+.\" OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" This software has been written for the Internet Software Consortium
+.\" by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
+.\" Enterprises. To learn more about the Internet Software Consortium,
+.\" see ``http://www.isc.org/isc''. To learn more about Vixie
+.\" Enterprises, see ``http://www.vix.com''.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd January 1, 1997
+.Dt DHCLIENT.CONF 5
+.Os
+.Sh NAME
+.Nm dhclient.conf
+.Nd DHCP client configuration file
+.Sh DESCRIPTION
+The
+.Nm
+file contains configuration information for
+.Xr dhclient 8 ,
+the Internet Software Consortium DHCP Client.
+.Pp
+The
+.Nm
+file is a free-form ASCII text file.
+It is parsed by the recursive-descent parser built into
+.Xr dhclient 8 .
+The file may contain extra tabs and newlines for formatting purposes.
+Keywords in the file are case-insensitive.
+Comments may be placed anywhere within the file (except within quotes).
+Comments begin with the
+.Ql #
+character and end at the end of the line.
+.Pp
+The
+.Nm
+file can be used to configure the behaviour of the client in a wide variety
+of ways: protocol timing, information requested from the server, information
+required of the server, defaults to use if the server does not provide
+certain information, values with which to override information provided by
+the server, or values to prepend or append to information provided by the
+server.
+The configuration file can also be preinitialized with addresses to
+use on networks that do not have DHCP servers.
+.Sh PROTOCOL TIMING
+The timing behaviour of the client need not be configured by the user.
+If no timing configuration is provided by the user, a fairly
+reasonable timing behaviour will be used by default - one which
+results in fairly timely updates without placing an inordinate load on
+the server.
+.Pp
+The following statements can be used to adjust the timing behaviour of
+the DHCP client if required, however:
+.Bl -tag -width indent
+.It Ic timeout Ar time ;
+The
+.Ic timeout
+statement determines the amount of time that must pass between the
+time that the client begins to try to determine its address and the
+time that it decides that it is not going to be able to contact a server.
+By default, this timeout is sixty seconds.
+After the timeout has passed, if there are any static leases defined in the
+configuration file, or any leases remaining in the lease database that
+have not yet expired, the client will loop through these leases
+attempting to validate them, and if it finds one that appears to be
+valid, it will use that lease's address.
+If there are no valid static leases or unexpired leases in the lease database,
+the client will restart the protocol after the defined retry interval.
+.It Ic retry Ar time ;
+The
+.Ic retry
+statement determines the time that must pass after the client has
+determined that there is no DHCP server present before it tries again
+to contact a DHCP server.
+By default, this is five minutes.
+.It Ic select-timeout Ar time ;
+It is possible (some might say desirable) for there to be more than
+one DHCP server serving any given network.
+In this case, it is possible that a client may be sent more than one offer
+in response to its initial lease discovery message.
+It may be that one of these offers is preferable to the other
+(e.g., one offer may have the address the client previously used,
+and the other may not).
+.Pp
+The
+.Ic select-timeout
+is the time after the client sends its first lease discovery request
+at which it stops waiting for offers from servers, assuming that it
+has received at least one such offer.
+If no offers have been received by the time the
+.Ic select-timeout
+has expired, the client will accept the first offer that arrives.
+.Pp
+By default, the
+.Ic select-timeout
+is zero seconds - that is, the client will take the first offer it sees.
+.It Ic reboot Ar time ;
+When the client is restarted, it first tries to reacquire the last
+address it had.
+This is called the INIT-REBOOT state.
+If it is still attached to the same network it was attached to when it last
+ran, this is the quickest way to get started.
+The
+.Ic reboot
+statement sets the time that must elapse after the client first tries
+to reacquire its old address before it gives up and tries to discover
+a new address.
+By default, the reboot timeout is ten seconds.
+.It Ic backoff-cutoff Ar time ;
+The client uses an exponential backoff algorithm with some randomness,
+so that if many clients try to configure themselves at the same time,
+they will not make their requests in lockstep.
+The
+.Ic backoff-cutoff
+statement determines the maximum amount of time that the client is
+allowed to back off.
+It defaults to two minutes.
+.It Ic initial-interval Ar time ;
+The
+.Ic initial-interval
+statement sets the amount of time between the first attempt to reach a
+server and the second attempt to reach a server.
+Each time a message is sent, the interval between messages is incremented by
+twice the current interval multiplied by a random number between zero and one.
+If it is greater than the
+.Ic backoff-cutoff
+amount, it is set to that
+amount.
+It defaults to ten seconds.
+.El
+.Sh LEASE REQUIREMENTS AND REQUESTS
+The DHCP protocol allows the client to request that the server send it
+specific information, and not send it other information that it is not
+prepared to accept.
+The protocol also allows the client to reject offers from servers if they
+do not contain information the client needs, or if the information provided
+is not satisfactory.
+.Pp
+There is a variety of data contained in offers that DHCP servers send
+to DHCP clients.
+The data that can be specifically requested is what are called
+.Em DHCP Options .
+DHCP Options are defined in
+.Xr dhcp-options 5 .
+.Bl -tag -width indent
+.It Ic request Oo Ar option Oc Oo , Ar ... option Oc ;
+The
+.Ic request
+statement causes the client to request that any server responding to the
+client send the client its values for the specified options.
+Only the option names should be specified in the request statement - not
+option parameters.
+.It Ic require Oo Ar option Oc Oo , Ar ... option Oc ;
+The
+.Ic require
+statement lists options that must be sent in order for an offer to be accepted.
+Offers that do not contain all the listed options will be ignored.
+.It Ic send No { Oo Ar option declaration Oc Oo , Ar ... option declaration Oc }
+The
+.Ic send
+statement causes the client to send the specified options to the server with
+the specified values.
+These are full option declarations as described in
+.Xr dhcp-options 5 .
+Options that are always sent in the DHCP protocol should not be specified
+here, except that the client can specify a
+.Ar dhcp-lease-time
+option other than the default requested lease time, which is two hours.
+The other obvious use for this statement is to send information to the server
+that will allow it to differentiate between this client and other
+clients or kinds of clients.
+.El
+.Sh OPTION MODIFIERS
+In some cases, a client may receive option data from the server which
+is not really appropriate for that client, or may not receive
+information that it needs, and for which a useful default value exists.
+It may also receive information which is useful, but which needs to be
+supplemented with local information.
+To handle these needs, several option modifiers are available.
+.Bl -tag -width indent
+.It Xo
+.Ic default No { Op Ar option declaration
+.Oo , Ar ... option declaration Oc }
+.Xc
+If for some set of options the client should use the value supplied by
+the server, but needs to use some default value if no value was supplied
+by the server, these values can be defined in the
+.Ic default
+statement.
+.It Xo
+.Ic supersede No { Op Ar option declaration
+.Oo , Ar ... option declaration Oc }
+.Xc
+If for some set of options the client should always use its own value
+rather than any value supplied by the server, these values can be defined
+in the
+.Ic supersede
+statement.
+.It Xo
+.Ic prepend No { Op Ar option declaration
+.Oo , Ar ... option declaration Oc }
+.Xc
+If for some set of options the client should use a value you supply,
+and then use the values supplied by the server, if any,
+these values can be defined in the
+.Ic prepend
+statement.
+The
+.Ic prepend
+statement can only be used for options which allow more than one value to
+be given.
+This restriction is not enforced - if violated, the results are unpredictable.
+.It Xo
+.Ic append No { Op Ar option declaration
+.Oo , Ar ... option declaration Oc }
+.Xc
+If for some set of options the client should first use the values
+supplied by the server, if any, and then use values you supply, these
+values can be defined in the
+.Ic append
+statement.
+The
+.Ic append
+statement can only be used for options which allow more than one value to
+be given.
+This restriction is not enforced - if you ignore it,
+the behaviour will be unpredictable.
+.El
+.Sh LEASE DECLARATIONS
+The lease declaration:
+.Pp
+.D1 Ic lease No { Ar lease-declaration Oo Ar ... lease-declaration Oc }
+.Pp
+The DHCP client may decide after some period of time (see
+.Sx PROTOCOL TIMING )
+that it is not going to succeed in contacting a server.
+At that time, it consults its own database of old leases and tests each one
+that has not yet timed out by pinging the listed router for that lease to
+see if that lease could work.
+It is possible to define one or more
+.Em fixed
+leases in the client configuration file for networks where there is no DHCP
+or BOOTP service, so that the client can still automatically configure its
+address.
+This is done with the
+.Ic lease
+statement.
+.Pp
+NOTE: the lease statement is also used in the
+.Pa dhclient.leases
+file in order to record leases that have been received from DHCP servers.
+Some of the syntax for leases as described below is only needed in the
+.Pa dhclient.leases
+file.
+Such syntax is documented here for completeness.
+.Pp
+A lease statement consists of the
+.Ic lease
+keyword, followed by a left
+curly brace, followed by one or more lease declaration statements,
+followed by a right curly brace.
+The following lease declarations are possible:
+.Bl -tag -width indent
+.It Ic bootp ;
+The
+.Ic bootp
+statement is used to indicate that the lease was acquired using the
+BOOTP protocol rather than the DHCP protocol.
+It is never necessary to specify this in the client configuration file.
+The client uses this syntax in its lease database file.
+.It Ic interface Qq Ar string ;
+The
+.Ic interface
+lease statement is used to indicate the interface on which the lease is valid.
+If set, this lease will only be tried on a particular interface.
+When the client receives a lease from a server, it always records the
+interface number on which it received that lease.
+If predefined leases are specified in the
+.Nm
+file, the interface should also be specified, although this is not required.
+.It Ic fixed-address Ar ip-address ;
+The
+.Ic fixed-address
+statement is used to set the IP address of a particular lease.
+This is required for all lease statements.
+The IP address must be specified as a dotted quad (e.g.,
+.Li 12.34.56.78 ) .
+.It Ic filename Qq Ar string ;
+The
+.Ic filename
+statement specifies the name of the boot filename to use.
+This is not used by the standard client configuration script, but is
+included for completeness.
+.It Ic server-name Qq Ar string ;
+The
+.Ic server-name
+statement specifies the name of the boot server name to use.
+This is also not used by the standard client configuration script.
+.It Ic option Ar option-declaration ;
+The
+.Ic option
+statement is used to specify the value of an option supplied by the server,
+or, in the case of predefined leases declared in
+.Nm ,
+the value that the user wishes the client configuration script to use if the
+predefined lease is used.
+.It Ic script Qq Ar script-name ;
+The
+.Ic script
+statement is used to specify the pathname of the DHCP client configuration
+script.
+This script is used by the DHCP client to set each interface's initial
+configuration prior to requesting an address, to test the address once it
+has been offered, and to set the interface's final configuration once a
+lease has been acquired.
+If no lease is acquired, the script is used to test predefined leases, if
+any, and also called once if no valid lease can be identified.
+For more information, see
+.Xr dhclient.leases 5 .
+.It Ic medium Qq Ar "media setup" ;
+The
+.Ic medium
+statement can be used on systems where network interfaces cannot
+automatically determine the type of network to which they are connected.
+The
+.Ar "media setup"
+string is a system-dependent parameter which is passed
+to the DHCP client configuration script when initializing the interface.
+On
+.Ux
+and
+.Ux Ns -like
+systems, the argument is passed on the
+.Xr ifconfig 8
+command line
+when configuring the interface.
+.Pp
+The DHCP client automatically declares this parameter if it used a
+media type (see the
+.Ic media
+statement) when configuring the interface in order to obtain a lease.
+This statement should be used in predefined leases only if the network
+interface requires media type configuration.
+.It Ic renew Ar date ;
+.It Ic rebind Ar date ;
+.It Ic expire Ar date ;
+The
+.Ic renew
+statement defines the time at which the DHCP client should begin trying to
+contact its server to renew a lease that it is using.
+The
+.Ic rebind
+statement defines the time at which the DHCP client should begin to try to
+contact
+.Em any
+DHCP server in order to renew its lease.
+The
+.Ic expire
+statement defines the time at which the DHCP client must stop using a lease
+if it has not been able to contact a server in order to renew it.
+.El
+.Pp
+These declarations are automatically set in leases acquired by the
+DHCP client, but must also be configured in predefined leases - a
+predefined lease whose expiry time has passed will not be used by the
+DHCP client.
+.Pp
+Dates are specified as follows:
+.Bd -ragged -offset indent
+.Ar <weekday>
+.Sm off
+.Ar <year> No / Ar <month> No / Ar <day>
+.Ar <hour> : <minute> : <second>
+.Sm on
+.Ed
+.Pp
+The weekday is present to make it easy for a human to tell when a
+lease expires - it is specified as a number from zero to six, with zero
+being Sunday.
+When declaring a predefined lease, it can always be specified as zero.
+The year is specified with the century, so it should generally be four
+digits except for really long leases.
+The month is specified as a number starting with 1 for January.
+The day of the month is likewise specified starting with 1.
+The hour is a number between 0 and 23,
+the minute a number between 0 and 59,
+and the second also a number between 0 and 59.
+.Sh ALIAS DECLARATIONS
+.Ic alias No { Ar declarations ... No }
+.Pp
+Some DHCP clients running TCP/IP roaming protocols may require that in
+addition to the lease they may acquire via DHCP, their interface also
+be configured with a predefined IP alias so that they can have a
+permanent IP address even while roaming.
+The Internet Software Consortium DHCP client does not support roaming with
+fixed addresses directly, but in order to facilitate such experimentation,
+the DHCP client can be set up to configure an IP alias using the
+.Ic alias
+declaration.
+.Pp
+The
+.Ic alias
+declaration resembles a lease declaration, except that options other than
+the subnet-mask option are ignored by the standard client configuration
+script, and expiry times are ignored.
+A typical alias declaration includes an interface declaration, a fixed-address
+declaration for the IP alias address, and a subnet-mask option declaration.
+A medium statement should never be included in an alias declaration.
+.Sh OTHER DECLARATIONS
+.Bl -tag -width indent
+.It Ic reject Ar ip-address ;
+The
+.Ic reject
+statement causes the DHCP client to reject offers from servers who use
+the specified address as a server identifier.
+This can be used to avoid being configured by rogue or misconfigured DHCP
+servers, although it should be a last resort - better to track down
+the bad DHCP server and fix it.
+.It Ic interface Qo Ar name Qc { Ar declarations ... No }
+A client with more than one network interface may require different
+behaviour depending on which interface is being configured.
+All timing parameters and declarations other than lease and alias
+declarations can be enclosed in an interface declaration, and those
+parameters will then be used only for the interface that matches the
+specified name.
+Interfaces for which there is no interface declaration will use the
+parameters declared outside of any interface declaration,
+or the default settings.
+.It Ic media Qo Ar "media setup" Qc Oo , Qo Ar "media setup" Qc , Ar ... Oc ;
+The
+.Ic media
+statement defines one or more media configuration parameters which may
+be tried while attempting to acquire an IP address.
+The DHCP client will cycle through each media setup string on the list,
+configuring the interface using that setup and attempting to boot,
+and then trying the next one.
+This can be used for network interfaces which are not capable of sensing
+the media type unaided - whichever media type succeeds in getting a request
+to the server and hearing the reply is probably right (no guarantees).
+.Pp
+The media setup is only used for the initial phase of address
+acquisition (the DHCPDISCOVER and DHCPOFFER packets).
+Once an address has been acquired, the DHCP client will record it in its
+lease database and will record the media type used to acquire the address.
+Whenever the client tries to renew the lease, it will use that same media type.
+The lease must expire before the client will go back to cycling through media
+types.
+.El
+.Sh EXAMPLES
+The following configuration file is used on a laptop
+which has an IP alias of
+.Li 192.5.5.213 ,
+and has one interface,
+.Li ep0
+(a 3Com 3C589C).
+Booting intervals have been shortened somewhat from the default, because
+the client is known to spend most of its time on networks with little DHCP
+activity.
+The laptop does roam to multiple networks.
+.Bd -literal -offset indent
+timeout 60;
+retry 60;
+reboot 10;
+select-timeout 5;
+initial-interval 2;
+reject 192.33.137.209;
+
+interface "ep0" {
+ send host-name "andare.fugue.com";
+ send dhcp-client-identifier 1:0:a0:24:ab:fb:9c;
+ send dhcp-lease-time 3600;
+ supersede domain-name "fugue.com rc.vix.com home.vix.com";
+ prepend domain-name-servers 127.0.0.1;
+ request subnet-mask, broadcast-address, time-offset, routers,
+ domain-name, domain-name-servers, host-name;
+ require subnet-mask, domain-name-servers;
+ script "/etc/dhclient-script";
+ media "media 10baseT/UTP", "media 10base2/BNC";
+}
+
+alias {
+ interface "ep0";
+ fixed-address 192.5.5.213;
+ option subnet-mask 255.255.255.255;
+}
+.Ed
+.Pp
+This is a very complicated
+.Nm
+file - in general, yours should be much simpler.
+In many cases, it is sufficient to just create an empty
+.Nm
+file - the defaults are usually fine.
+.Sh SEE ALSO
+.Xr dhclient.leases 5 ,
+.Xr dhcp-options 5 ,
+.Xr dhcpd.conf 5 ,
+.Xr dhclient 8 ,
+.Xr dhcpd 8
+.Rs
+.%R "RFC 2132, RFC 2131"
+.Re
+.Sh AUTHORS
+.An -nosplit
+The
+.Xr dhclient 8
+utility
+was written by
+.An Ted Lemon Aq Mt mellon@vix.com
+under a contract with Vixie Labs.
+.Pp
+The current implementation was reworked by
+.An Henning Brauer Aq Mt henning@openbsd.org .
diff --git a/sbin/dhclient/dhclient.leases.5 b/sbin/dhclient/dhclient.leases.5
new file mode 100644
index 0000000..ebef819
--- /dev/null
+++ b/sbin/dhclient/dhclient.leases.5
@@ -0,0 +1,95 @@
+.\" $OpenBSD: dhclient.leases.5,v 1.4 2004/04/15 08:59:47 jmc Exp $
+.\"
+.\" Copyright (c) 1997 The Internet Software Consortium.
+.\" 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 Internet Software Consortium 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 INTERNET SOFTWARE CONSORTIUM 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 INTERNET SOFTWARE CONSORTIUM OR
+.\" CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+.\" SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+.\" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+.\" USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+.\" ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+.\" OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+.\" OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" This software has been written for the Internet Software Consortium
+.\" by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
+.\" Enterprises. To learn more about the Internet Software Consortium,
+.\" see ``http://www.isc.org/isc''. To learn more about Vixie
+.\" Enterprises, see ``http://www.vix.com''.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd January 1, 1997
+.Dt DHCLIENT.LEASES 5
+.Os
+.Sh NAME
+.Nm dhclient.leases
+.Nd DHCP client lease database
+.Sh DESCRIPTION
+The Internet Software Consortium DHCP client keeps a persistent
+database of leases that it has acquired that are still valid.
+The database is a free-form ASCII file containing one valid declaration
+per lease.
+If more than one declaration appears for a given lease,
+the last one in the file is used.
+The file is written as a log, so this is not an unusual occurrence.
+.Pp
+The lease file is named
+.Pa dhclient.leases. Ns Ar IFNAME ,
+where
+.Ar IFNAME
+represents the network interface the DHCP client acquired the lease on.
+For example, if
+.Xr dhclient 8
+is configured for the
+.Li em0
+network device,
+the lease file will be named
+.Pa dhclient.leases.em0 .
+.Pp
+The format of the lease declarations is described in
+.Xr dhclient.conf 5 .
+.Sh FILES
+.Bl -tag -width ".Pa /var/db/dhclient.leases. Ns Ar IFNAME"
+.It Pa /var/db/dhclient.leases. Ns Ar IFNAME
+Current lease file.
+.El
+.Sh SEE ALSO
+.Xr dhclient.conf 5 ,
+.Xr dhcp-options 5 ,
+.Xr dhcpd.conf 5 ,
+.Xr dhclient 8 ,
+.Xr dhcpd 8
+.Rs
+.%R "RFC 2132, RFC 2131"
+.Re
+.Sh AUTHORS
+.An -nosplit
+The
+.Xr dhclient 8
+utility
+was written by
+.An Ted Lemon Aq Mt mellon@vix.com
+under a contract with Vixie Labs.
+.Pp
+The current implementation was reworked by
+.An Henning Brauer Aq Mt henning@openbsd.org .
diff --git a/sbin/dhclient/dhcp-options.5 b/sbin/dhclient/dhcp-options.5
new file mode 100644
index 0000000..4b65fa7
--- /dev/null
+++ b/sbin/dhclient/dhcp-options.5
@@ -0,0 +1,610 @@
+.\" $OpenBSD: dhcp-options.5,v 1.5 2005/03/02 15:30:42 jmc Exp $
+.\"
+.\" Copyright (c) 1995, 1996, 1997, 1998 The Internet Software Consortium.
+.\" 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 Internet Software Consortium 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 INTERNET SOFTWARE CONSORTIUM 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 INTERNET SOFTWARE CONSORTIUM OR
+.\" CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+.\" SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+.\" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+.\" USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+.\" ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+.\" OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+.\" OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" This software has been written for the Internet Software Consortium
+.\" by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
+.\" Enterprises. To learn more about the Internet Software Consortium,
+.\" see ``http://www.isc.org/isc''. To learn more about Vixie
+.\" Enterprises, see ``http://www.vix.com''.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd January 1, 1995
+.Dt DHCP-OPTIONS 5
+.Os
+.Sh NAME
+.Nm dhcp-options
+.Nd Dynamic Host Configuration Protocol options
+.Sh DESCRIPTION
+The Dynamic Host Configuration protocol allows the client to receive
+.Ic options
+from the DHCP server describing the network configuration and various
+services that are available on the network.
+When configuring
+.Xr dhcpd 8
+or
+.Xr dhclient 8 ,
+options must often be declared.
+The syntax for declaring options, and the names and formats of the options
+that can be declared, are documented here.
+.Sh REFERENCE: OPTION STATEMENTS
+DHCP
+.Ic option
+statements always start with the
+.Ic option
+keyword, followed by an option name, followed by option data.
+The option names and data formats are described below.
+It is not necessary to exhaustively specify all DHCP options -
+only those options which are needed by clients must be specified.
+.Pp
+Option data comes in a variety of formats, as defined below:
+.Pp
+The
+.Ar ip-address
+data type can be entered either as an explicit IP address
+(e.g.,
+.Li 239.254.197.10 )
+or as a domain name (e.g.,
+.Li haagen.isc.org ) .
+A domain name must resolve to a single IP address.
+.Pp
+The
+.Ar int32
+data type specifies a signed 32-bit integer.
+The
+.Ar uint32
+data type specifies an unsigned 32-bit integer.
+The
+.Ar int16
+and
+.Ar uint16
+data types specify signed and unsigned 16-bit integers.
+The
+.Ar int8
+and
+.Ar uint8
+data types specify signed and unsigned 8-bit integers.
+Unsigned 8-bit integers are also sometimes referred to as octets.
+.Pp
+The
+.Ar string
+data type specifies an
+.Tn NVT
+.Pq Network Virtual Terminal
+.Tn ASCII
+string, which must be enclosed in double quotes - for example,
+to specify a domain-name option, the syntax would be
+.Pp
+.Dl option domain-name \&"isc.org";
+.Pp
+The
+.Ar flag
+data type specifies a boolean value.
+Booleans can be either
+.Li true
+or
+.Li false
+(or
+.Li on
+or
+.Li off ,
+if that makes more sense to you).
+.Pp
+The
+.Ar data-string
+data type specifies either an
+.Tn NVT ASCII
+string enclosed in double quotes, or a series of octets specified in
+hexadecimal, separated by colons.
+For example:
+.Pp
+.Dl option dhcp-client-identifier \&"CLIENT-FOO";
+or
+.Dl option dhcp-client-identifier 43:4c:49:45:54:2d:46:4f:4f;
+.Pp
+The documentation for the various options mentioned below is taken
+from the IETF draft document on DHCP options, RFC 2132.
+Options which are not listed by name may be defined by the name
+.Li option- Ns Ar nnn ,
+where
+.Ar nnn
+is the decimal number of the option code.
+These options may be followed either by a string, enclosed in quotes, or by
+a series of octets, expressed as two-digit hexadecimal numbers separated
+by colons.
+For example:
+.Bd -literal -offset indent
+option option-133 "my-option-133-text";
+option option-129 1:54:c9:2b:47;
+.Ed
+.Pp
+Because
+.Xr dhcpd 8
+does not know the format of these undefined option codes,
+no checking is done to ensure the correctness of the entered data.
+.Pp
+The standard options are:
+.Ss RFC 1497 Vendor Extensions
+.Bl -tag -width indent
+.It Ic option subnet-mask Ar ip-address ;
+The
+.Ic subnet-mask
+option specifies the client's subnet mask as per RFC 950.
+If no subnet-mask option is provided anywhere in scope, as a last resort
+.Xr dhcpd 8
+will use the subnet mask from the subnet declaration for the network on
+which an address is being assigned.
+However,
+.Em any
+subnet-mask option declaration that is in scope for the address being
+assigned will override the subnet mask specified in the subnet declaration.
+.It Ic option time-offset Ar int32 ;
+The
+.Ic time-offset
+option specifies the offset of the client's subnet in seconds from
+Coordinated Universal Time (UTC).
+.It Xo
+.Ic option routers Ar ip-address
+.Oo , Ar ip-address ... Oc ;
+.Xc
+The
+.Ic routers
+option specifies a list of IP addresses for routers on the client's subnet.
+Routers should be listed in order of preference.
+.It Xo
+.Ic option time-servers Ar ip-address
+.Oo , Ar ip-address ... Oc ;
+.Xc
+The
+.Ic time-server
+option specifies a list of RFC 868 time servers available to the client.
+Servers should be listed in order of preference.
+.It Xo
+.Ic option ien116-name-servers Ar ip-address
+.Oo , Ar ip-address ... Oc ;
+.Xc
+The
+.Ic ien116-name-servers
+option specifies a list of IEN 116 name servers available to the client.
+Servers should be listed in order of preference.
+.It Xo
+.Ic option domain-name-servers Ar ip-address
+.Oo , Ar ip-address ... Oc ;
+.Xc
+The
+.Ic domain-name-servers
+option specifies a list of Domain Name System (STD 13, RFC 1035) name servers
+available to the client.
+Servers should be listed in order of preference.
+.It Xo
+.Ic option log-servers Ar ip-address
+.Oo , Ar ip-address ... Oc ;
+.Xc
+The
+.Ic log-servers
+option specifies a list of MIT-LCS UDP log servers available to the client.
+Servers should be listed in order of preference.
+.It Xo
+.Ic option cookie-servers Ar ip-address
+.Oo , Ar ip-address ... Oc ;
+.Xc
+The
+.Ic cookie-servers
+option specifies a list of RFC 865 cookie servers available to the client.
+Servers should be listed in order of preference.
+.It Xo
+.Ic option lpr-servers Ar ip-address
+.Oo , Ar ip-address ... Oc ;
+.Xc
+The
+.Ic lpr-servers
+option specifies a list of RFC 1179 line printer servers available to the
+client.
+Servers should be listed in order of preference.
+.It Xo
+.Ic option impress-servers Ar ip-address
+.Oo , Ar ip-address ... Oc ;
+.Xc
+The
+.Ic impress-servers
+option specifies a list of Imagen Impress servers available to the client.
+Servers should be listed in order of preference.
+.It Xo
+.Ic option resource-location-servers Ar ip-address
+.Oo , Ar ip-address ... Oc ;
+.Xc
+This option specifies a list of RFC 887 Resource Location servers available
+to the client.
+Servers should be listed in order of preference.
+.It Ic option host-name Ar string ;
+This option specifies the name of the client.
+The name may or may not be qualified with the local domain name
+(it is preferable to use the
+.Ic domain-name
+option to specify the domain name).
+See RFC 1035 for character set restrictions.
+.It Ic option boot-size Ar uint16 ;
+This option specifies the length in 512-octet blocks of the default
+boot image for the client.
+.It Ic option merit-dump Ar string ;
+This option specifies the pathname of a file to which the client's
+core image should be dumped in the event the client crashes.
+The path is formatted as a character string consisting of characters from
+the
+.Tn NVT ASCII
+character set.
+.It Ic option domain-name Ar string ;
+This option specifies the domain name that the client should use when
+resolving hostnames via the Domain Name System.
+.It Ic option domain-search Ar string ;
+This option specifies a list of domain names that the client should use
+when resolving hostnames via the Domain Name System. This option is
+defined in RFC 3397.
+.It Ic option swap-server Ar ip-address ;
+This specifies the IP address of the client's swap server.
+.It Ic option root-path Ar string ;
+This option specifies the pathname that contains the client's root disk.
+The path is formatted as a character string consisting of characters from
+the
+.Tn NVT ASCII
+character set.
+.El
+.Ss IP Layer Parameters per Host
+.Bl -tag -width indent
+.It Ic option ip-forwarding Ar flag ;
+This option specifies whether the client should configure its IP layer
+for packet forwarding.
+A value of 0 means disable IP forwarding, and a value of 1 means enable
+IP forwarding.
+.It Ic option non-local-source-routing Ar flag ;
+This option specifies whether the client should configure its IP
+layer to allow forwarding of datagrams with non-local source routes
+(see Section 3.3.5 of [4] for a discussion of this topic).
+A value of 0 means disallow forwarding of such datagrams, and a value of 1
+means allow forwarding.
+.It Xo
+.Ic option policy-filter Ar ip-address ip-address
+.Oo , Ar ip-address ip-address ... Oc ;
+.Xc
+This option specifies policy filters for non-local source routing.
+The filters consist of a list of IP addresses and masks which specify
+destination/mask pairs with which to filter incoming source routes.
+.Pp
+Any source-routed datagram whose next-hop address does not match one
+of the filters should be discarded by the client.
+.Pp
+See STD 3 (RFC 1122) for further information.
+.It Ic option max-dgram-reassembly Ar uint16 ;
+This option specifies the maximum size datagram that the client should be
+prepared to reassemble.
+The minimum legal value is 576.
+.It Ic option default-ip-ttl Ar uint8 ;
+This option specifies the default time-to-live that the client should
+use on outgoing datagrams.
+.It Ic option path-mtu-aging-timeout Ar uint32 ;
+This option specifies the timeout (in seconds) to use when aging Path
+MTU values discovered by the mechanism defined in RFC 1191.
+.It Xo
+.Ic option path-mtu-plateau-table Ar uint16
+.Oo , Ar uint16 ... Oc ;
+.Xc
+This option specifies a table of MTU sizes to use when performing
+Path MTU Discovery as defined in RFC 1191.
+The table is formatted as a list of 16-bit unsigned integers,
+ordered from smallest to largest.
+The minimum MTU value cannot be smaller than 68.
+.El
+.Ss IP Layer Parameters per Interface
+.Bl -tag -width indent
+.It Ic option interface-mtu Ar uint16 ;
+This option specifies the MTU to use on this interface.
+The minimum legal value for the MTU is 68.
+.It Ic option all-subnets-local Ar flag ;
+This option specifies whether or not the client may assume that all subnets
+of the IP network to which the client is connected use the same MTU as the
+subnet of that network to which the client is directly connected.
+A value of 1 indicates that all subnets share the same MTU.
+A value of 0 means that the client should assume that some subnets of the
+directly connected network may have smaller MTUs.
+.It Ic option broadcast-address Ar ip-address ;
+This option specifies the broadcast address in use on the client's subnet.
+Legal values for broadcast addresses are specified in section 3.2.1.3 of
+STD 3 (RFC 1122).
+.It Ic option perform-mask-discovery Ar flag ;
+This option specifies whether or not the client should perform subnet mask
+discovery using ICMP.
+A value of 0 indicates that the client should not perform mask discovery.
+A value of 1 means that the client should perform mask discovery.
+.It Ic option mask-supplier Ar flag ;
+This option specifies whether or not the client should respond to subnet mask
+requests using ICMP.
+A value of 0 indicates that the client should not respond.
+A value of 1 means that the client should respond.
+.It Ic option router-discovery Ar flag ;
+This option specifies whether or not the client should solicit routers using
+the Router Discovery mechanism defined in RFC 1256.
+A value of 0 indicates that the client should not perform router discovery.
+A value of 1 means that the client should perform router discovery.
+.It Ic option router-solicitation-address Ar ip-address ;
+This option specifies the address to which the client should transmit
+router solicitation requests.
+.It Xo
+.Ic option static-routes Ar ip-address ip-address
+.Oo , Ar ip-address ip-address ... Oc ;
+.Xc
+This option specifies a list of static routes that the client should
+install in its routing cache.
+If multiple routes to the same destination are specified, they are listed
+in descending order of priority.
+.Pp
+The routes consist of a list of IP address pairs.
+The first address is the destination address,
+and the second address is the router for the destination.
+.Pp
+The default route (0.0.0.0) is an illegal destination for a static route.
+To specify the default route, use the
+.Ic routers
+option.
+.El
+.Ss Link Layer Parameters per Interface
+.Bl -tag -width indent
+.It Ic option trailer-encapsulation Ar flag ;
+This option specifies whether or not the client should negotiate the
+use of trailers (RFC 893 [14]) when using the ARP protocol.
+A value of 0 indicates that the client should not attempt to use trailers.
+A value of 1 means that the client should attempt to use trailers.
+.It Ic option arp-cache-timeout Ar uint32 ;
+This option specifies the timeout in seconds for ARP cache entries.
+.It Ic option ieee802-3-encapsulation Ar flag ;
+This option specifies whether or not the client should use Ethernet
+Version 2 (RFC 894) or IEEE 802.3 (RFC 1042) encapsulation if the
+interface is an Ethernet.
+A value of 0 indicates that the client should use RFC 894 encapsulation.
+A value of 1 means that the client should use RFC 1042 encapsulation.
+.El
+.Ss TCP Parameters
+.Bl -tag -width indent
+.It Ic option default-tcp-ttl Ar uint8 ;
+This option specifies the default TTL that the client should use when
+sending TCP segments.
+The minimum value is 1.
+.It Ic option tcp-keepalive-interval Ar uint32 ;
+This option specifies the interval (in seconds) that the client TCP
+should wait before sending a keepalive message on a TCP connection.
+The time is specified as a 32-bit unsigned integer.
+A value of zero indicates that the client should not generate keepalive
+messages on connections unless specifically requested by an application.
+.It Ic option tcp-keepalive-garbage Ar flag ;
+This option specifies whether or not the client should send TCP keepalive
+messages with an octet of garbage for compatibility with older implementations.
+A value of 0 indicates that a garbage octet should not be sent.
+A value of 1 indicates that a garbage octet should be sent.
+.El
+.Ss Application and Service Parameters
+.Bl -tag -width indent
+.It Ic option nis-domain Ar string ;
+This option specifies the name of the client's NIS (Sun Network Information
+Services) domain.
+The domain is formatted as a character string consisting of characters
+from the
+.Tn NVT ASCII
+character set.
+.It Xo
+.Ic option nis-servers Ar ip-address
+.Oo , Ar ip-address ... Oc ;
+.Xc
+This option specifies a list of IP addresses indicating NIS servers
+available to the client.
+Servers should be listed in order of preference.
+.It Xo
+.Ic option ntp-servers Ar ip-address
+.Oo , Ar ip-address ... Oc ;
+.Xc
+This option specifies a list of IP addresses indicating NTP (RFC 1305)
+servers available to the client.
+Servers should be listed in order of preference.
+.It Xo
+.Ic option netbios-name-servers Ar ip-address
+.Oo , Ar ip-address ... Oc ;
+.Xc
+The NetBIOS name server (NBNS) option specifies a list of RFC 1001/1002
+NBNS name servers listed in order of preference.
+NetBIOS Name Service is currently more commonly referred to as WINS.
+WINS servers can be specified using the
+.Ic netbios-name-servers
+option.
+.It Xo
+.Ic option netbios-dd-server Ar ip-address
+.Oo , Ar ip-address ... Oc ;
+.Xc
+The NetBIOS datagram distribution server (NBDD) option specifies a
+list of RFC 1001/1002 NBDD servers listed in order of preference.
+.It Ic option netbios-node-type Ar uint8 ;
+The NetBIOS node type option allows NetBIOS over TCP/IP clients which
+are configurable to be configured as described in RFC 1001/1002.
+The value is specified as a single octet which identifies the client type.
+.Pp
+Possible node types are:
+.Bl -tag -width indent
+.It 1
+B-node: Broadcast - no WINS
+.It 2
+P-node: Peer - WINS only
+.It 4
+M-node: Mixed - broadcast, then WINS
+.It 8
+H-node: Hybrid - WINS, then broadcast
+.El
+.It Ic option netbios-scope Ar string ;
+The NetBIOS scope option specifies the NetBIOS over TCP/IP scope
+parameter for the client as specified in RFC 1001/1002.
+See RFC 1001, RFC 1002, and RFC 1035 for character-set restrictions.
+.It Xo
+.Ic option font-servers Ar ip-address
+.Oo , Ar ip-address ... Oc ;
+.Xc
+This option specifies a list of X Window System Font servers available
+to the client.
+Servers should be listed in order of preference.
+.It Xo
+.Ic option x-display-manager Ar ip-address
+.Oo , Ar ip-address ... Oc ;
+.Xc
+This option specifies a list of systems that are running the X Window
+System Display Manager and are available to the client.
+Addresses should be listed in order of preference.
+.It Ic option dhcp-client-identifier Ar data-string ;
+This option can be used to specify a DHCP client identifier in a
+host declaration, so that
+.Xr dhcpd 8
+can find the host record by matching against the client identifier.
+.It Ic option nisplus-domain Ar string ;
+This option specifies the name of the client's NIS+ domain.
+The domain is formatted as a character string consisting of characters
+from the
+.Tn NVT ASCII
+character set.
+.It Xo
+.Ic option nisplus-servers Ar ip-address
+.Oo , Ar ip-address ... Oc ;
+.Xc
+This option specifies a list of IP addresses indicating NIS+ servers
+available to the client.
+Servers should be listed in order of preference.
+.It Ic option tftp-server-name Ar string ;
+This option is used to identify a TFTP server and, if supported by the
+client, should have the same effect as the
+.Ic server-name
+declaration.
+BOOTP clients are unlikely to support this option.
+Some DHCP clients will support it, and others actually require it.
+.It Ic option bootfile-name Ar string ;
+This option is used to identify a bootstrap file.
+If supported by the client, it should have the same effect as the
+.Ic filename
+declaration.
+BOOTP clients are unlikely to support this option.
+Some DHCP clients will support it, and others actually require it.
+.It Xo
+.Ic option mobile-ip-home-agent Ar ip-address
+.Oo , Ar ip-address ... Oc ;
+.Xc
+This option specifies a list of IP addresses indicating mobile IP
+home agents available to the client.
+Agents should be listed in order of preference, although normally there
+will be only one such agent.
+.It Xo
+.Ic option smtp-server Ar ip-address
+.Oo , Ar ip-address ... Oc ;
+.Xc
+The
+.Ic smtp-server
+option specifies a list of SMTP servers available to the client.
+Servers should be listed in order of preference.
+.It Xo
+.Ic option pop-server Ar ip-address
+.Oo , Ar ip-address ... Oc ;
+.Xc
+The
+.Ic pop-server
+option specifies a list of POP3 servers available to the client.
+Servers should be listed in order of preference.
+.It Xo
+.Ic option nntp-server Ar ip-address
+.Oo , Ar ip-address ... Oc ;
+.Xc
+The
+.Ic nntp-server
+option specifies a list of NNTP servers available to the client.
+Servers should be listed in order of preference.
+.It Xo
+.Ic option www-server Ar ip-address
+.Oo , Ar ip-address ... Oc ;
+.Xc
+The
+.Ic www-server
+option specifies a list of WWW servers available to the client.
+Servers should be listed in order of preference.
+.It Xo
+.Ic option finger-server Ar ip-address
+.Oo , Ar ip-address ... Oc ;
+.Xc
+The
+.Ic finger-server
+option specifies a list of
+.Xr finger 1
+servers available to the client.
+Servers should be listed in order of preference.
+.It Xo
+.Ic option irc-server Ar ip-address
+.Oo , Ar ip-address ... Oc ;
+.Xc
+The
+.Ic irc-server
+option specifies a list of IRC servers available to the client.
+Servers should be listed in order of preference.
+.It Xo
+.Ic option streettalk-server Ar ip-address
+.Oo , Ar ip-address ... Oc ;
+.Xc
+The
+.Ic streettalk-server
+option specifies a list of StreetTalk servers available to the client.
+Servers should be listed in order of preference.
+.It Xo
+.Ic option streettalk-directory-assistance-server Ar ip-address
+.Oo , Ar ip-address ... Oc ;
+.Xc
+The StreetTalk Directory Assistance (STDA) server option specifies a
+list of STDA servers available to the client.
+Servers should be listed in order of preference.
+.El
+.Sh SEE ALSO
+.Xr dhclient.conf 5 ,
+.Xr dhcpd.conf 5 ,
+.Xr dhcpd.leases 5 ,
+.Xr dhclient 8 ,
+.Xr dhcpd 8
+.Rs
+.%R "RFC 2131, RFC 2132"
+.Re
+.Sh AUTHORS
+.An -nosplit
+The
+.Xr dhcpd 8
+utility
+was written by
+.An Ted Lemon Aq Mt mellon@vix.com
+under a contract with Vixie Labs.
+.Pp
+The current implementation was reworked by
+.An Henning Brauer Aq Mt henning@openbsd.org .
diff --git a/sbin/dhclient/dhcp.h b/sbin/dhclient/dhcp.h
new file mode 100644
index 0000000..6e7d1a7
--- /dev/null
+++ b/sbin/dhclient/dhcp.h
@@ -0,0 +1,184 @@
+/* $OpenBSD: dhcp.h,v 1.5 2004/05/04 15:49:49 deraadt Exp $ */
+/* $FreeBSD$ */
+
+/* Protocol structures... */
+
+/*
+ * Copyright (c) 1995, 1996 The Internet Software Consortium.
+ * 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 Internet Software Consortium 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 INTERNET SOFTWARE CONSORTIUM 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 INTERNET SOFTWARE CONSORTIUM OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * This software has been written for the Internet Software Consortium
+ * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
+ * Enterprises. To learn more about the Internet Software Consortium,
+ * see ``http://www.vix.com/isc''. To learn more about Vixie
+ * Enterprises, see ``http://www.vix.com''.
+ */
+
+#define DHCP_UDP_OVERHEAD (14 + /* Ethernet header */ \
+ 20 + /* IP header */ \
+ 8) /* UDP header */
+#define DHCP_SNAME_LEN 64
+#define DHCP_FILE_LEN 128
+#define DHCP_FIXED_NON_UDP 236
+#define DHCP_FIXED_LEN (DHCP_FIXED_NON_UDP + DHCP_UDP_OVERHEAD)
+ /* Everything but options. */
+#define DHCP_MTU_MAX 1500
+#define DHCP_OPTION_LEN (DHCP_MTU_MAX - DHCP_FIXED_LEN)
+
+#define BOOTP_MIN_LEN 300
+#define DHCP_MIN_LEN 548
+
+struct dhcp_packet {
+ u_int8_t op; /* Message opcode/type */
+ u_int8_t htype; /* Hardware addr type (see net/if_types.h) */
+ u_int8_t hlen; /* Hardware addr length */
+ u_int8_t hops; /* Number of relay agent hops from client */
+ u_int32_t xid; /* Transaction ID */
+ u_int16_t secs; /* Seconds since client started looking */
+ u_int16_t flags; /* Flag bits */
+ struct in_addr ciaddr; /* Client IP address (if already in use) */
+ struct in_addr yiaddr; /* Client IP address */
+ struct in_addr siaddr; /* IP address of next server to talk to */
+ struct in_addr giaddr; /* DHCP relay agent IP address */
+ unsigned char chaddr[16]; /* Client hardware address */
+ char sname[DHCP_SNAME_LEN]; /* Server name */
+ char file[DHCP_FILE_LEN]; /* Boot filename */
+ unsigned char options[DHCP_OPTION_LEN];
+ /* Optional parameters
+ (actual length dependent on MTU). */
+};
+
+/* BOOTP (rfc951) message types */
+#define BOOTREQUEST 1
+#define BOOTREPLY 2
+
+/* Possible values for flags field... */
+#define BOOTP_BROADCAST 32768L
+
+/* Possible values for hardware type (htype) field... */
+#define HTYPE_ETHER 1 /* Ethernet */
+#define HTYPE_IEEE802 6 /* IEEE 802.2 Token Ring... */
+#define HTYPE_FDDI 8 /* FDDI... */
+
+/* Magic cookie validating dhcp options field (and bootp vendor
+ extensions field). */
+#define DHCP_OPTIONS_COOKIE "\143\202\123\143"
+
+/* DHCP Option codes: */
+
+#define DHO_PAD 0
+#define DHO_SUBNET_MASK 1
+#define DHO_TIME_OFFSET 2
+#define DHO_ROUTERS 3
+#define DHO_TIME_SERVERS 4
+#define DHO_NAME_SERVERS 5
+#define DHO_DOMAIN_NAME_SERVERS 6
+#define DHO_LOG_SERVERS 7
+#define DHO_COOKIE_SERVERS 8
+#define DHO_LPR_SERVERS 9
+#define DHO_IMPRESS_SERVERS 10
+#define DHO_RESOURCE_LOCATION_SERVERS 11
+#define DHO_HOST_NAME 12
+#define DHO_BOOT_SIZE 13
+#define DHO_MERIT_DUMP 14
+#define DHO_DOMAIN_NAME 15
+#define DHO_SWAP_SERVER 16
+#define DHO_ROOT_PATH 17
+#define DHO_EXTENSIONS_PATH 18
+#define DHO_IP_FORWARDING 19
+#define DHO_NON_LOCAL_SOURCE_ROUTING 20
+#define DHO_POLICY_FILTER 21
+#define DHO_MAX_DGRAM_REASSEMBLY 22
+#define DHO_DEFAULT_IP_TTL 23
+#define DHO_PATH_MTU_AGING_TIMEOUT 24
+#define DHO_PATH_MTU_PLATEAU_TABLE 25
+#define DHO_INTERFACE_MTU 26
+#define DHO_ALL_SUBNETS_LOCAL 27
+#define DHO_BROADCAST_ADDRESS 28
+#define DHO_PERFORM_MASK_DISCOVERY 29
+#define DHO_MASK_SUPPLIER 30
+#define DHO_ROUTER_DISCOVERY 31
+#define DHO_ROUTER_SOLICITATION_ADDRESS 32
+#define DHO_STATIC_ROUTES 33
+#define DHO_TRAILER_ENCAPSULATION 34
+#define DHO_ARP_CACHE_TIMEOUT 35
+#define DHO_IEEE802_3_ENCAPSULATION 36
+#define DHO_DEFAULT_TCP_TTL 37
+#define DHO_TCP_KEEPALIVE_INTERVAL 38
+#define DHO_TCP_KEEPALIVE_GARBAGE 39
+#define DHO_NIS_DOMAIN 40
+#define DHO_NIS_SERVERS 41
+#define DHO_NTP_SERVERS 42
+#define DHO_VENDOR_ENCAPSULATED_OPTIONS 43
+#define DHO_NETBIOS_NAME_SERVERS 44
+#define DHO_NETBIOS_DD_SERVER 45
+#define DHO_NETBIOS_NODE_TYPE 46
+#define DHO_NETBIOS_SCOPE 47
+#define DHO_FONT_SERVERS 48
+#define DHO_X_DISPLAY_MANAGER 49
+#define DHO_DHCP_REQUESTED_ADDRESS 50
+#define DHO_DHCP_LEASE_TIME 51
+#define DHO_DHCP_OPTION_OVERLOAD 52
+#define DHO_DHCP_MESSAGE_TYPE 53
+#define DHO_DHCP_SERVER_IDENTIFIER 54
+#define DHO_DHCP_PARAMETER_REQUEST_LIST 55
+#define DHO_DHCP_MESSAGE 56
+#define DHO_DHCP_MAX_MESSAGE_SIZE 57
+#define DHO_DHCP_RENEWAL_TIME 58
+#define DHO_DHCP_REBINDING_TIME 59
+#define DHO_DHCP_CLASS_IDENTIFIER 60
+#define DHO_DHCP_CLIENT_IDENTIFIER 61
+#define DHO_NISPLUS_DOMAIN 64
+#define DHO_NISPLUS_SERVERS 65
+#define DHO_TFTP_SERVER_NAME 66
+#define DHO_BOOTFILE_NAME 67
+#define DHO_MOBILE_IP_HOME_AGENT 68
+#define DHO_SMTP_SERVER 69
+#define DHO_POP_SERVER 70
+#define DHO_NNTP_SERVER 71
+#define DHO_WWW_SERVER 72
+#define DHO_FINGER_SERVER 73
+#define DHO_IRC_SERVER 74
+#define DHO_STREETTALK_SERVER 75
+#define DHO_STREETTALK_DA_SERVER 76
+#define DHO_DHCP_USER_CLASS_ID 77
+#define DHO_DOMAIN_SEARCH 119
+#define DHO_CLASSLESS_ROUTES 121
+#define DHO_END 255
+
+/* DHCP message types. */
+#define DHCPDISCOVER 1
+#define DHCPOFFER 2
+#define DHCPREQUEST 3
+#define DHCPDECLINE 4
+#define DHCPACK 5
+#define DHCPNAK 6
+#define DHCPRELEASE 7
+#define DHCPINFORM 8
diff --git a/sbin/dhclient/dhcpd.h b/sbin/dhclient/dhcpd.h
new file mode 100644
index 0000000..479753e
--- /dev/null
+++ b/sbin/dhclient/dhcpd.h
@@ -0,0 +1,440 @@
+/* $OpenBSD: dhcpd.h,v 1.33 2004/05/06 22:29:15 deraadt Exp $ */
+
+/*
+ * Copyright (c) 2004 Henning Brauer <henning@openbsd.org>
+ * Copyright (c) 1995, 1996, 1997, 1998, 1999
+ * The Internet Software Consortium. 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 Internet Software Consortium 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 INTERNET SOFTWARE CONSORTIUM 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 INTERNET SOFTWARE CONSORTIUM OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * This software has been written for the Internet Software Consortium
+ * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
+ * Enterprises. To learn more about the Internet Software Consortium,
+ * see ``http://www.vix.com/isc''. To learn more about Vixie
+ * Enterprises, see ``http://www.vix.com''.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/param.h>
+
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <net/route.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libutil.h>
+#include <limits.h>
+#include <netdb.h>
+#include <paths.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "dhcp.h"
+#include "tree.h"
+
+#define LOCAL_PORT 68
+#define REMOTE_PORT 67
+
+struct option_data {
+ int len;
+ u_int8_t *data;
+};
+
+struct string_list {
+ struct string_list *next;
+ char *string;
+};
+
+struct iaddr {
+ int len;
+ unsigned char iabuf[16];
+};
+
+struct iaddrlist {
+ struct iaddrlist *next;
+ struct iaddr addr;
+};
+
+struct packet {
+ struct dhcp_packet *raw;
+ int packet_length;
+ int packet_type;
+ int options_valid;
+ int client_port;
+ struct iaddr client_addr;
+ struct interface_info *interface;
+ struct hardware *haddr;
+ struct option_data options[256];
+};
+
+struct hardware {
+ u_int8_t htype;
+ u_int8_t hlen;
+ u_int8_t haddr[16];
+};
+
+struct client_lease {
+ struct client_lease *next;
+ time_t expiry, renewal, rebind;
+ struct iaddr address;
+ struct iaddr nextserver;
+ char *server_name;
+ char *filename;
+ struct string_list *medium;
+ unsigned int is_static : 1;
+ unsigned int is_bootp : 1;
+ struct option_data options[256];
+};
+
+/* Possible states in which the client can be. */
+enum dhcp_state {
+ S_REBOOTING,
+ S_INIT,
+ S_SELECTING,
+ S_REQUESTING,
+ S_BOUND,
+ S_RENEWING,
+ S_REBINDING
+};
+
+struct client_config {
+ struct option_data defaults[256];
+ enum {
+ ACTION_DEFAULT,
+ ACTION_SUPERSEDE,
+ ACTION_PREPEND,
+ ACTION_APPEND
+ } default_actions[256];
+
+ struct option_data send_options[256];
+ u_int8_t required_options[256];
+ u_int8_t requested_options[256];
+ int requested_option_count;
+ time_t timeout;
+ time_t initial_interval;
+ time_t retry_interval;
+ time_t select_interval;
+ time_t reboot_timeout;
+ time_t backoff_cutoff;
+ struct string_list *media;
+ char *script_name;
+ enum { IGNORE, ACCEPT, PREFER }
+ bootp_policy;
+ struct string_list *medium;
+ struct iaddrlist *reject_list;
+};
+
+struct client_state {
+ struct client_lease *active;
+ struct client_lease *new;
+ struct client_lease *offered_leases;
+ struct client_lease *leases;
+ struct client_lease *alias;
+ enum dhcp_state state;
+ struct iaddr destination;
+ u_int32_t xid;
+ u_int16_t secs;
+ time_t first_sending;
+ time_t interval;
+ struct string_list *medium;
+ struct dhcp_packet packet;
+ int packet_length;
+ struct iaddr requested_address;
+ struct client_config *config;
+ char **scriptEnv;
+ int scriptEnvsize;
+ struct string_list *env;
+ int envc;
+};
+
+struct interface_info {
+ struct interface_info *next;
+ struct hardware hw_address;
+ struct in_addr primary_address;
+ char name[IFNAMSIZ];
+ int rfdesc;
+ int wfdesc;
+ int ufdesc;
+ unsigned char *rbuf;
+ size_t rbuf_max;
+ size_t rbuf_offset;
+ size_t rbuf_len;
+ struct ifreq *ifp;
+ struct client_state *client;
+ int noifmedia;
+ int errors;
+ int dead;
+ u_int16_t index;
+ int linkstat;
+};
+
+struct timeout {
+ struct timeout *next;
+ time_t when;
+ void (*func)(void *);
+ void *what;
+};
+
+struct protocol {
+ struct protocol *next;
+ int fd;
+ void (*handler)(struct protocol *);
+ void *local;
+};
+
+#define DEFAULT_HASH_SIZE 97
+
+struct hash_bucket {
+ struct hash_bucket *next;
+ unsigned char *name;
+ int len;
+ unsigned char *value;
+};
+
+struct hash_table {
+ int hash_count;
+ struct hash_bucket *buckets[DEFAULT_HASH_SIZE];
+};
+
+/* Default path to dhcpd config file. */
+#define _PATH_DHCLIENT_CONF "/etc/dhclient.conf"
+#define _PATH_DHCLIENT_DB "/var/db/dhclient.leases"
+#define DHCPD_LOG_FACILITY LOG_DAEMON
+
+#define MAX_TIME 0x7fffffff
+#define MIN_TIME 0
+
+/* External definitions... */
+
+/* options.c */
+int cons_options(struct packet *, struct dhcp_packet *, int,
+ struct tree_cache **, int, int, int, u_int8_t *, int);
+char *pretty_print_option(unsigned int,
+ unsigned char *, int, int, int);
+void do_packet(struct interface_info *, struct dhcp_packet *,
+ int, unsigned int, struct iaddr, struct hardware *);
+
+/* errwarn.c */
+extern int warnings_occurred;
+void error(char *, ...) __attribute__ ((__format__ (__printf__, 1, 2)));
+int warning(char *, ...) __attribute__ ((__format__ (__printf__, 1, 2)));
+int note(char *, ...) __attribute__ ((__format__ (__printf__, 1, 2)));
+int debug(char *, ...) __attribute__ ((__format__ (__printf__, 1, 2)));
+int parse_warn(char *, ...) __attribute__ ((__format__ (__printf__, 1, 2)));
+
+/* conflex.c */
+extern int lexline, lexchar;
+extern char *token_line, *tlname;
+extern char comments[4096];
+extern int comment_index;
+extern int eol_token;
+void new_parse(char *);
+int next_token(char **, FILE *);
+int peek_token(char **, FILE *);
+
+/* parse.c */
+void skip_to_semi(FILE *);
+int parse_semi(FILE *);
+char *parse_string(FILE *);
+int parse_ip_addr(FILE *, struct iaddr *);
+void parse_hardware_param(FILE *, struct hardware *);
+void parse_lease_time(FILE *, time_t *);
+unsigned char *parse_numeric_aggregate(FILE *, unsigned char *, int *,
+ int, int, int);
+void convert_num(unsigned char *, char *, int, int);
+time_t parse_date(FILE *);
+
+/* tree.c */
+pair cons(caddr_t, pair);
+
+/* alloc.c */
+struct string_list *new_string_list(size_t size);
+struct hash_table *new_hash_table(int);
+struct hash_bucket *new_hash_bucket(void);
+
+/* bpf.c */
+int if_register_bpf(struct interface_info *, int);
+void if_register_send(struct interface_info *);
+void if_register_receive(struct interface_info *);
+void send_packet_unpriv(int, struct dhcp_packet *, size_t, struct in_addr,
+ struct in_addr);
+struct imsg_hdr;
+void send_packet_priv(struct interface_info *, struct imsg_hdr *, int);
+ssize_t receive_packet(struct interface_info *, unsigned char *, size_t,
+ struct sockaddr_in *, struct hardware *);
+
+/* dispatch.c */
+extern void (*bootp_packet_handler)(struct interface_info *,
+ struct dhcp_packet *, int, unsigned int, struct iaddr, struct hardware *);
+void discover_interfaces(struct interface_info *);
+void reinitialize_interfaces(void);
+void dispatch(void);
+void got_one(struct protocol *);
+void add_timeout(time_t, void (*)(void *), void *);
+void cancel_timeout(void (*)(void *), void *);
+void add_protocol(char *, int, void (*)(struct protocol *), void *);
+void remove_protocol(struct protocol *);
+int interface_link_status(char *);
+
+/* hash.c */
+struct hash_table *new_hash(void);
+void add_hash(struct hash_table *, unsigned char *, int, unsigned char *);
+unsigned char *hash_lookup(struct hash_table *, unsigned char *, int);
+
+/* tables.c */
+extern struct option dhcp_options[256];
+extern unsigned char dhcp_option_default_priority_list[];
+extern int sizeof_dhcp_option_default_priority_list;
+extern struct hash_table universe_hash;
+extern struct universe dhcp_universe;
+void initialize_universes(void);
+
+/* convert.c */
+u_int32_t getULong(unsigned char *);
+int32_t getLong(unsigned char *);
+u_int16_t getUShort(unsigned char *);
+int16_t getShort(unsigned char *);
+void putULong(unsigned char *, u_int32_t);
+void putLong(unsigned char *, int32_t);
+void putUShort(unsigned char *, unsigned int);
+void putShort(unsigned char *, int);
+
+/* inet.c */
+struct iaddr subnet_number(struct iaddr, struct iaddr);
+struct iaddr broadcast_addr(struct iaddr, struct iaddr);
+int addr_eq(struct iaddr, struct iaddr);
+char *piaddr(struct iaddr);
+
+/* dhclient.c */
+extern char *path_dhclient_conf;
+extern char *path_dhclient_db;
+extern time_t cur_time;
+extern int log_priority;
+extern int log_perror;
+
+extern struct client_config top_level_config;
+
+extern struct pidfh *pidfile;
+
+void dhcpoffer(struct packet *);
+void dhcpack(struct packet *);
+void dhcpnak(struct packet *);
+
+void send_discover(void *);
+void send_request(void *);
+void send_decline(void *);
+
+void state_reboot(void *);
+void state_init(void *);
+void state_selecting(void *);
+void state_requesting(void *);
+void state_bound(void *);
+void state_panic(void *);
+
+void bind_lease(struct interface_info *);
+
+void make_discover(struct interface_info *, struct client_lease *);
+void make_request(struct interface_info *, struct client_lease *);
+void make_decline(struct interface_info *, struct client_lease *);
+
+void free_client_lease(struct client_lease *);
+void rewrite_client_leases(void);
+void write_client_lease(struct interface_info *, struct client_lease *, int);
+
+void priv_script_init(char *, char *);
+void priv_script_write_params(char *, struct client_lease *);
+int priv_script_go(void);
+
+void script_init(char *, struct string_list *);
+void script_write_params(char *, struct client_lease *);
+int script_go(void);
+void client_envadd(struct client_state *,
+ const char *, const char *, const char *, ...);
+void script_set_env(struct client_state *, const char *, const char *,
+ const char *);
+void script_flush_env(struct client_state *);
+int dhcp_option_ev_name(char *, size_t, struct option *);
+
+struct client_lease *packet_to_lease(struct packet *);
+void go_daemon(void);
+void client_location_changed(void);
+
+void bootp(struct packet *);
+void dhcp(struct packet *);
+
+/* packet.c */
+void assemble_hw_header(struct interface_info *, unsigned char *, int *);
+void assemble_udp_ip_header(unsigned char *, int *, u_int32_t, u_int32_t,
+ unsigned int, unsigned char *, int);
+ssize_t decode_hw_header(unsigned char *, int, struct hardware *);
+ssize_t decode_udp_ip_header(unsigned char *, int, struct sockaddr_in *,
+ unsigned char *, int);
+
+/* clparse.c */
+int read_client_conf(void);
+void read_client_leases(void);
+void parse_client_statement(FILE *, struct interface_info *,
+ struct client_config *);
+int parse_X(FILE *, u_int8_t *, int);
+int parse_option_list(FILE *, u_int8_t *);
+void parse_interface_declaration(FILE *, struct client_config *);
+struct interface_info *interface_or_dummy(char *);
+void make_client_state(struct interface_info *);
+void make_client_config(struct interface_info *, struct client_config *);
+void parse_client_lease_statement(FILE *, int);
+void parse_client_lease_declaration(FILE *, struct client_lease *,
+ struct interface_info **);
+struct option *parse_option_decl(FILE *, struct option_data *);
+void parse_string_list(FILE *, struct string_list **, int);
+void parse_reject_statement(FILE *, struct client_config *);
+
+/* privsep.c */
+struct buf *buf_open(size_t);
+int buf_add(struct buf *, void *, size_t);
+int buf_close(int, struct buf *);
+ssize_t buf_read(int, void *, size_t);
+void dispatch_imsg(struct interface_info *, int);
diff --git a/sbin/dhclient/dhctoken.h b/sbin/dhclient/dhctoken.h
new file mode 100644
index 0000000..7b23242
--- /dev/null
+++ b/sbin/dhclient/dhctoken.h
@@ -0,0 +1,136 @@
+/* $OpenBSD: dhctoken.h,v 1.2 2004/02/04 12:16:56 henning Exp $ */
+
+/* Tokens for config file lexer and parser. */
+
+/*
+ * Copyright (c) 1995, 1996, 1997, 1998, 1999
+ * The Internet Software Consortium. 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 Internet Software Consortium 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 INTERNET SOFTWARE CONSORTIUM 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 INTERNET SOFTWARE CONSORTIUM OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * This software has been written for the Internet Software Consortium
+ * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
+ * Enterprises. To learn more about the Internet Software Consortium,
+ * see ``http://www.vix.com/isc''. To learn more about Vixie
+ * Enterprises, see ``http://www.vix.com''.
+ */
+
+#define SEMI ';'
+#define DOT '.'
+#define COLON ':'
+#define COMMA ','
+#define SLASH '/'
+#define LBRACE '{'
+#define RBRACE '}'
+
+#define FIRST_TOKEN HOST
+#define HOST 256
+#define HARDWARE 257
+#define FILENAME 258
+#define FIXED_ADDR 259
+#define OPTION 260
+#define ETHERNET 261
+#define STRING 262
+#define NUMBER 263
+#define NUMBER_OR_NAME 264
+#define NAME 265
+#define TIMESTAMP 266
+#define STARTS 267
+#define ENDS 268
+#define UID 269
+#define CLASS 270
+#define LEASE 271
+#define RANGE 272
+#define PACKET 273
+#define CIADDR 274
+#define YIADDR 275
+#define SIADDR 276
+#define GIADDR 277
+#define SUBNET 278
+#define NETMASK 279
+#define DEFAULT_LEASE_TIME 280
+#define MAX_LEASE_TIME 281
+#define VENDOR_CLASS 282
+#define USER_CLASS 283
+#define SHARED_NETWORK 284
+#define SERVER_NAME 285
+#define DYNAMIC_BOOTP 286
+#define SERVER_IDENTIFIER 287
+#define DYNAMIC_BOOTP_LEASE_CUTOFF 288
+#define DYNAMIC_BOOTP_LEASE_LENGTH 289
+#define BOOT_UNKNOWN_CLIENTS 290
+#define NEXT_SERVER 291
+#define TOKEN_RING 292
+#define GROUP 293
+#define ONE_LEASE_PER_CLIENT 294
+#define GET_LEASE_HOSTNAMES 295
+#define USE_HOST_DECL_NAMES 296
+#define SEND 297
+#define CLIENT_IDENTIFIER 298
+#define REQUEST 299
+#define REQUIRE 300
+#define TIMEOUT 301
+#define RETRY 302
+#define SELECT_TIMEOUT 303
+#define SCRIPT 304
+#define INTERFACE 305
+#define RENEW 306
+#define REBIND 307
+#define EXPIRE 308
+#define UNKNOWN_CLIENTS 309
+#define ALLOW 310
+#define BOOTP 311
+#define DENY 312
+#define BOOTING 313
+#define DEFAULT 314
+#define MEDIA 315
+#define MEDIUM 316
+#define ALIAS 317
+#define REBOOT 318
+#define ABANDONED 319
+#define BACKOFF_CUTOFF 320
+#define INITIAL_INTERVAL 321
+#define NAMESERVER 322
+#define DOMAIN 323
+#define SEARCH 324
+#define SUPERSEDE 325
+#define APPEND 326
+#define PREPEND 327
+#define HOSTNAME 328
+#define CLIENT_HOSTNAME 329
+#define REJECT 330
+#define FDDI 331
+#define USE_LEASE_ADDR_FOR_DEFAULT_ROUTE 332
+#define AUTHORITATIVE 333
+#define TOKEN_NOT 334
+#define ALWAYS_REPLY_RFC1048 335
+
+#define is_identifier(x) ((x) >= FIRST_TOKEN && \
+ (x) != STRING && \
+ (x) != NUMBER && \
+ (x) != EOF)
diff --git a/sbin/dhclient/dispatch.c b/sbin/dhclient/dispatch.c
new file mode 100644
index 0000000..3ee0cf6
--- /dev/null
+++ b/sbin/dhclient/dispatch.c
@@ -0,0 +1,503 @@
+/* $OpenBSD: dispatch.c,v 1.31 2004/09/21 04:07:03 david Exp $ */
+
+/*
+ * Copyright 2004 Henning Brauer <henning@openbsd.org>
+ * Copyright (c) 1995, 1996, 1997, 1998, 1999
+ * The Internet Software Consortium. 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 Internet Software Consortium 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 INTERNET SOFTWARE CONSORTIUM 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 INTERNET SOFTWARE CONSORTIUM OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * This software has been written for the Internet Software Consortium
+ * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
+ * Enterprises. To learn more about the Internet Software Consortium,
+ * see ``http://www.vix.com/isc''. To learn more about Vixie
+ * Enterprises, see ``http://www.vix.com''.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include "dhcpd.h"
+
+#include <sys/ioctl.h>
+
+#include <net/if_media.h>
+#include <ifaddrs.h>
+#include <poll.h>
+
+struct protocol *protocols;
+struct timeout *timeouts;
+static struct timeout *free_timeouts;
+static int interfaces_invalidated;
+void (*bootp_packet_handler)(struct interface_info *,
+ struct dhcp_packet *, int, unsigned int,
+ struct iaddr, struct hardware *);
+
+static int interface_status(struct interface_info *ifinfo);
+
+/*
+ * Use getifaddrs() to get a list of all the attached interfaces. For
+ * each interface that's of type INET and not the loopback interface,
+ * register that interface with the network I/O software, figure out
+ * what subnet it's on, and add it to the list of interfaces.
+ */
+void
+discover_interfaces(struct interface_info *iface)
+{
+ struct ifaddrs *ifap, *ifa;
+ struct sockaddr_in foo;
+ struct ifreq *tif;
+
+ if (getifaddrs(&ifap) != 0)
+ error("getifaddrs failed");
+
+ for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) {
+ if ((ifa->ifa_flags & IFF_LOOPBACK) ||
+ (ifa->ifa_flags & IFF_POINTOPOINT) ||
+ (!(ifa->ifa_flags & IFF_UP)))
+ continue;
+
+ if (strcmp(iface->name, ifa->ifa_name))
+ continue;
+
+ /*
+ * If we have the capability, extract link information
+ * and record it in a linked list.
+ */
+ if (ifa->ifa_addr->sa_family == AF_LINK) {
+ struct sockaddr_dl *foo =
+ (struct sockaddr_dl *)ifa->ifa_addr;
+
+ iface->index = foo->sdl_index;
+ iface->hw_address.hlen = foo->sdl_alen;
+ iface->hw_address.htype = HTYPE_ETHER; /* XXX */
+ memcpy(iface->hw_address.haddr,
+ LLADDR(foo), foo->sdl_alen);
+ } else if (ifa->ifa_addr->sa_family == AF_INET) {
+ struct iaddr addr;
+
+ memcpy(&foo, ifa->ifa_addr, sizeof(foo));
+ if (foo.sin_addr.s_addr == htonl(INADDR_LOOPBACK))
+ continue;
+ if (!iface->ifp) {
+ int len = IFNAMSIZ + ifa->ifa_addr->sa_len;
+ if ((tif = malloc(len)) == NULL)
+ error("no space to remember ifp");
+ strlcpy(tif->ifr_name, ifa->ifa_name, IFNAMSIZ);
+ memcpy(&tif->ifr_addr, ifa->ifa_addr,
+ ifa->ifa_addr->sa_len);
+ iface->ifp = tif;
+ iface->primary_address = foo.sin_addr;
+ }
+ addr.len = 4;
+ memcpy(addr.iabuf, &foo.sin_addr.s_addr, addr.len);
+ }
+ }
+
+ if (!iface->ifp)
+ error("%s: not found", iface->name);
+
+ /* Register the interface... */
+ if_register_receive(iface);
+ if_register_send(iface);
+ add_protocol(iface->name, iface->rfdesc, got_one, iface);
+ freeifaddrs(ifap);
+}
+
+void
+reinitialize_interfaces(void)
+{
+ interfaces_invalidated = 1;
+}
+
+/*
+ * Wait for packets to come in using poll(). When a packet comes in,
+ * call receive_packet to receive the packet and possibly strip hardware
+ * addressing information from it, and then call through the
+ * bootp_packet_handler hook to try to do something with it.
+ */
+void
+dispatch(void)
+{
+ int count, live_interfaces, i, to_msec, nfds = 0;
+ struct protocol *l;
+ struct pollfd *fds;
+ time_t howlong;
+
+ for (l = protocols; l; l = l->next)
+ nfds++;
+
+ fds = malloc(nfds * sizeof(struct pollfd));
+ if (fds == NULL)
+ error("Can't allocate poll structures.");
+
+ do {
+ /*
+ * Call any expired timeouts, and then if there's still
+ * a timeout registered, time out the select call then.
+ */
+another:
+ if (timeouts) {
+ struct timeout *t;
+
+ if (timeouts->when <= cur_time) {
+ t = timeouts;
+ timeouts = timeouts->next;
+ (*(t->func))(t->what);
+ t->next = free_timeouts;
+ free_timeouts = t;
+ goto another;
+ }
+
+ /*
+ * Figure timeout in milliseconds, and check for
+ * potential overflow, so we can cram into an
+ * int for poll, while not polling with a
+ * negative timeout and blocking indefinitely.
+ */
+ howlong = timeouts->when - cur_time;
+ if (howlong > INT_MAX / 1000)
+ howlong = INT_MAX / 1000;
+ to_msec = howlong * 1000;
+ } else
+ to_msec = -1;
+
+ /* Set up the descriptors to be polled. */
+ live_interfaces = 0;
+ for (i = 0, l = protocols; l; l = l->next) {
+ struct interface_info *ip = l->local;
+
+ if (ip == NULL || ip->dead)
+ continue;
+ fds[i].fd = l->fd;
+ fds[i].events = POLLIN;
+ fds[i].revents = 0;
+ i++;
+ if (l->handler == got_one)
+ live_interfaces++;
+ }
+ if (live_interfaces == 0)
+ error("No live interfaces to poll on - exiting.");
+
+ /* Wait for a packet or a timeout... XXX */
+ count = poll(fds, nfds, to_msec);
+
+ /* Not likely to be transitory... */
+ if (count == -1) {
+ if (errno == EAGAIN || errno == EINTR) {
+ time(&cur_time);
+ continue;
+ } else
+ error("poll: %m");
+ }
+
+ /* Get the current time... */
+ time(&cur_time);
+
+ i = 0;
+ for (l = protocols; l; l = l->next) {
+ struct interface_info *ip;
+ ip = l->local;
+ if ((fds[i].revents & (POLLIN | POLLHUP))) {
+ fds[i].revents = 0;
+ if (ip && (l->handler != got_one ||
+ !ip->dead))
+ (*(l->handler))(l);
+ if (interfaces_invalidated)
+ break;
+ }
+ i++;
+ }
+ interfaces_invalidated = 0;
+ } while (1);
+}
+
+
+void
+got_one(struct protocol *l)
+{
+ struct sockaddr_in from;
+ struct hardware hfrom;
+ struct iaddr ifrom;
+ ssize_t result;
+ union {
+ /*
+ * Packet input buffer. Must be as large as largest
+ * possible MTU.
+ */
+ unsigned char packbuf[4095];
+ struct dhcp_packet packet;
+ } u;
+ struct interface_info *ip = l->local;
+
+ if ((result = receive_packet(ip, u.packbuf, sizeof(u), &from,
+ &hfrom)) == -1) {
+ warning("receive_packet failed on %s: %s", ip->name,
+ strerror(errno));
+ ip->errors++;
+ if ((!interface_status(ip)) ||
+ (ip->noifmedia && ip->errors > 20)) {
+ /* our interface has gone away. */
+ warning("Interface %s no longer appears valid.",
+ ip->name);
+ ip->dead = 1;
+ interfaces_invalidated = 1;
+ close(l->fd);
+ remove_protocol(l);
+ free(ip);
+ }
+ return;
+ }
+ if (result == 0)
+ return;
+
+ if (bootp_packet_handler) {
+ ifrom.len = 4;
+ memcpy(ifrom.iabuf, &from.sin_addr, ifrom.len);
+
+ (*bootp_packet_handler)(ip, &u.packet, result,
+ from.sin_port, ifrom, &hfrom);
+ }
+}
+
+int
+interface_status(struct interface_info *ifinfo)
+{
+ char *ifname = ifinfo->name;
+ int ifsock = ifinfo->rfdesc;
+ struct ifreq ifr;
+ struct ifmediareq ifmr;
+
+ /* get interface flags */
+ memset(&ifr, 0, sizeof(ifr));
+ strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+ if (ioctl(ifsock, SIOCGIFFLAGS, &ifr) < 0) {
+ syslog(LOG_ERR, "ioctl(SIOCGIFFLAGS) on %s: %m", ifname);
+ goto inactive;
+ }
+
+ /*
+ * if one of UP and RUNNING flags is dropped,
+ * the interface is not active.
+ */
+ if ((ifr.ifr_flags & (IFF_UP|IFF_RUNNING)) != (IFF_UP|IFF_RUNNING))
+ goto inactive;
+
+ /* Next, check carrier on the interface, if possible */
+ if (ifinfo->noifmedia)
+ goto active;
+ memset(&ifmr, 0, sizeof(ifmr));
+ strlcpy(ifmr.ifm_name, ifname, sizeof(ifmr.ifm_name));
+ if (ioctl(ifsock, SIOCGIFMEDIA, (caddr_t)&ifmr) < 0) {
+ if (errno != EINVAL) {
+ syslog(LOG_DEBUG, "ioctl(SIOCGIFMEDIA) on %s: %m",
+ ifname);
+
+ ifinfo->noifmedia = 1;
+ goto active;
+ }
+ /*
+ * EINVAL (or ENOTTY) simply means that the interface
+ * does not support the SIOCGIFMEDIA ioctl. We regard it alive.
+ */
+ ifinfo->noifmedia = 1;
+ goto active;
+ }
+ if (ifmr.ifm_status & IFM_AVALID) {
+ switch (ifmr.ifm_active & IFM_NMASK) {
+ case IFM_ETHER:
+ case IFM_IEEE80211:
+ if (ifmr.ifm_status & IFM_ACTIVE)
+ goto active;
+ else
+ goto inactive;
+ break;
+ default:
+ goto inactive;
+ }
+ }
+inactive:
+ return (0);
+active:
+ return (1);
+}
+
+void
+add_timeout(time_t when, void (*where)(void *), void *what)
+{
+ struct timeout *t, *q;
+
+ /* See if this timeout supersedes an existing timeout. */
+ t = NULL;
+ for (q = timeouts; q; q = q->next) {
+ if (q->func == where && q->what == what) {
+ if (t)
+ t->next = q->next;
+ else
+ timeouts = q->next;
+ break;
+ }
+ t = q;
+ }
+
+ /* If we didn't supersede a timeout, allocate a timeout
+ structure now. */
+ if (!q) {
+ if (free_timeouts) {
+ q = free_timeouts;
+ free_timeouts = q->next;
+ q->func = where;
+ q->what = what;
+ } else {
+ q = malloc(sizeof(struct timeout));
+ if (!q)
+ error("Can't allocate timeout structure!");
+ q->func = where;
+ q->what = what;
+ }
+ }
+
+ q->when = when;
+
+ /* Now sort this timeout into the timeout list. */
+
+ /* Beginning of list? */
+ if (!timeouts || timeouts->when > q->when) {
+ q->next = timeouts;
+ timeouts = q;
+ return;
+ }
+
+ /* Middle of list? */
+ for (t = timeouts; t->next; t = t->next) {
+ if (t->next->when > q->when) {
+ q->next = t->next;
+ t->next = q;
+ return;
+ }
+ }
+
+ /* End of list. */
+ t->next = q;
+ q->next = NULL;
+}
+
+void
+cancel_timeout(void (*where)(void *), void *what)
+{
+ struct timeout *t, *q;
+
+ /* Look for this timeout on the list, and unlink it if we find it. */
+ t = NULL;
+ for (q = timeouts; q; q = q->next) {
+ if (q->func == where && q->what == what) {
+ if (t)
+ t->next = q->next;
+ else
+ timeouts = q->next;
+ break;
+ }
+ t = q;
+ }
+
+ /* If we found the timeout, put it on the free list. */
+ if (q) {
+ q->next = free_timeouts;
+ free_timeouts = q;
+ }
+}
+
+/* Add a protocol to the list of protocols... */
+void
+add_protocol(char *name, int fd, void (*handler)(struct protocol *),
+ void *local)
+{
+ struct protocol *p;
+
+ p = malloc(sizeof(*p));
+ if (!p)
+ error("can't allocate protocol struct for %s", name);
+
+ p->fd = fd;
+ p->handler = handler;
+ p->local = local;
+ p->next = protocols;
+ protocols = p;
+}
+
+void
+remove_protocol(struct protocol *proto)
+{
+ struct protocol *p, *next, *prev;
+
+ prev = NULL;
+ for (p = protocols; p; p = next) {
+ next = p->next;
+ if (p == proto) {
+ if (prev)
+ prev->next = p->next;
+ else
+ protocols = p->next;
+ free(p);
+ }
+ }
+}
+
+int
+interface_link_status(char *ifname)
+{
+ struct ifmediareq ifmr;
+ int sock;
+
+ if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
+ error("Can't create socket");
+
+ memset(&ifmr, 0, sizeof(ifmr));
+ strlcpy(ifmr.ifm_name, ifname, sizeof(ifmr.ifm_name));
+ if (ioctl(sock, SIOCGIFMEDIA, (caddr_t)&ifmr) == -1) {
+ /* EINVAL -> link state unknown. treat as active */
+ if (errno != EINVAL)
+ syslog(LOG_DEBUG, "ioctl(SIOCGIFMEDIA) on %s: %m",
+ ifname);
+ close(sock);
+ return (1);
+ }
+ close(sock);
+
+ if (ifmr.ifm_status & IFM_AVALID) {
+ switch (ifmr.ifm_active & IFM_NMASK) {
+ case IFM_ETHER:
+ case IFM_IEEE80211:
+ if (ifmr.ifm_status & IFM_ACTIVE)
+ return (1);
+ else
+ return (0);
+ }
+ }
+ return (1);
+}
diff --git a/sbin/dhclient/errwarn.c b/sbin/dhclient/errwarn.c
new file mode 100644
index 0000000..5720780
--- /dev/null
+++ b/sbin/dhclient/errwarn.c
@@ -0,0 +1,239 @@
+/* $OpenBSD: errwarn.c,v 1.7 2004/05/04 22:23:01 mickey Exp $ */
+
+/* Errors and warnings... */
+
+/*
+ * Copyright (c) 1996 The Internet Software Consortium.
+ * All Rights Reserved.
+ * Copyright (c) 1995 RadioMail Corporation. 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 RadioMail Corporation, the Internet Software
+ * Consortium 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 RADIOMAIL CORPORATION, THE INTERNET
+ * SOFTWARE CONSORTIUM 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 RADIOMAIL CORPORATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software was written for RadioMail Corporation by Ted Lemon
+ * under a contract with Vixie Enterprises. Further modifications have
+ * been made for the Internet Software Consortium under a contract
+ * with Vixie Laboratories.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <errno.h>
+
+#include "dhcpd.h"
+
+static void do_percentm(char *obuf, size_t size, char *ibuf);
+
+static char mbuf[1024];
+static char fbuf[1024];
+
+int warnings_occurred;
+
+/*
+ * Log an error message, then exit.
+ */
+void
+error(char *fmt, ...)
+{
+ va_list list;
+
+ do_percentm(fbuf, sizeof(fbuf), fmt);
+
+ va_start(list, fmt);
+ vsnprintf(mbuf, sizeof(mbuf), fbuf, list);
+ va_end(list);
+
+#ifndef DEBUG
+ syslog(log_priority | LOG_ERR, "%s", mbuf);
+#endif
+
+ /* Also log it to stderr? */
+ if (log_perror) {
+ write(2, mbuf, strlen(mbuf));
+ write(2, "\n", 1);
+ }
+
+ syslog(LOG_CRIT, "exiting.");
+ if (log_perror) {
+ fprintf(stderr, "exiting.\n");
+ fflush(stderr);
+ }
+ if (pidfile != NULL)
+ pidfile_remove(pidfile);
+ exit(1);
+}
+
+/*
+ * Log a warning message...
+ */
+int
+warning(char *fmt, ...)
+{
+ va_list list;
+
+ do_percentm(fbuf, sizeof(fbuf), fmt);
+
+ va_start(list, fmt);
+ vsnprintf(mbuf, sizeof(mbuf), fbuf, list);
+ va_end(list);
+
+#ifndef DEBUG
+ syslog(log_priority | LOG_ERR, "%s", mbuf);
+#endif
+
+ if (log_perror) {
+ write(2, mbuf, strlen(mbuf));
+ write(2, "\n", 1);
+ }
+
+ return (0);
+}
+
+/*
+ * Log a note...
+ */
+int
+note(char *fmt, ...)
+{
+ va_list list;
+
+ do_percentm(fbuf, sizeof(fbuf), fmt);
+
+ va_start(list, fmt);
+ vsnprintf(mbuf, sizeof(mbuf), fbuf, list);
+ va_end(list);
+
+#ifndef DEBUG
+ syslog(log_priority | LOG_INFO, "%s", mbuf);
+#endif
+
+ if (log_perror) {
+ write(2, mbuf, strlen(mbuf));
+ write(2, "\n", 1);
+ }
+
+ return (0);
+}
+
+/*
+ * Log a debug message...
+ */
+int
+debug(char *fmt, ...)
+{
+ va_list list;
+
+ do_percentm(fbuf, sizeof(fbuf), fmt);
+
+ va_start(list, fmt);
+ vsnprintf(mbuf, sizeof(mbuf), fbuf, list);
+ va_end(list);
+
+#ifndef DEBUG
+ syslog(log_priority | LOG_DEBUG, "%s", mbuf);
+#endif
+
+ if (log_perror) {
+ write(2, mbuf, strlen(mbuf));
+ write(2, "\n", 1);
+ }
+
+ return (0);
+}
+
+/*
+ * Find %m in the input string and substitute an error message string.
+ */
+static void
+do_percentm(char *obuf, size_t size, char *ibuf)
+{
+ char ch;
+ char *s = ibuf;
+ char *t = obuf;
+ size_t prlen;
+ size_t fmt_left;
+ int saved_errno = errno;
+
+ /*
+ * We wouldn't need this mess if printf handled %m, or if
+ * strerror() had been invented before syslog().
+ */
+ for (fmt_left = size; (ch = *s); ++s) {
+ if (ch == '%' && s[1] == 'm') {
+ ++s;
+ prlen = snprintf(t, fmt_left, "%s",
+ strerror(saved_errno));
+ if (prlen >= fmt_left)
+ prlen = fmt_left - 1;
+ t += prlen;
+ fmt_left -= prlen;
+ } else {
+ if (fmt_left > 1) {
+ *t++ = ch;
+ fmt_left--;
+ }
+ }
+ }
+ *t = '\0';
+}
+
+int
+parse_warn(char *fmt, ...)
+{
+ va_list list;
+ static char spaces[] =
+ " "
+ " "; /* 80 spaces */
+
+ do_percentm(mbuf, sizeof(mbuf), fmt);
+ snprintf(fbuf, sizeof(fbuf), "%s line %d: %s", tlname, lexline, mbuf);
+ va_start(list, fmt);
+ vsnprintf(mbuf, sizeof(mbuf), fbuf, list);
+ va_end(list);
+
+#ifndef DEBUG
+ syslog(log_priority | LOG_ERR, "%s", mbuf);
+ syslog(log_priority | LOG_ERR, "%s", token_line);
+ if (lexline < 81)
+ syslog(log_priority | LOG_ERR,
+ "%s^", &spaces[sizeof(spaces) - lexchar]);
+#endif
+
+ if (log_perror) {
+ write(2, mbuf, strlen(mbuf));
+ write(2, "\n", 1);
+ write(2, token_line, strlen(token_line));
+ write(2, "\n", 1);
+ write(2, spaces, lexchar - 1);
+ write(2, "^\n", 2);
+ }
+
+ warnings_occurred = 1;
+
+ return (0);
+}
diff --git a/sbin/dhclient/hash.c b/sbin/dhclient/hash.c
new file mode 100644
index 0000000..040236a
--- /dev/null
+++ b/sbin/dhclient/hash.c
@@ -0,0 +1,122 @@
+/* $OpenBSD: hash.c,v 1.9 2004/05/10 15:30:47 deraadt Exp $ */
+
+/* Routines for manipulating hash tables... */
+
+/*
+ * Copyright (c) 1995, 1996, 1997, 1998 The Internet Software Consortium.
+ * 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 Internet Software Consortium 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 INTERNET SOFTWARE CONSORTIUM 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 INTERNET SOFTWARE CONSORTIUM OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * This software has been written for the Internet Software Consortium
+ * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
+ * Enterprises. To learn more about the Internet Software Consortium,
+ * see ``http://www.vix.com/isc''. To learn more about Vixie
+ * Enterprises, see ``http://www.vix.com''.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include "dhcpd.h"
+
+static int do_hash(unsigned char *, int, int);
+
+struct hash_table *
+new_hash(void)
+{
+ struct hash_table *rv = new_hash_table(DEFAULT_HASH_SIZE);
+
+ if (!rv)
+ return (rv);
+ memset(&rv->buckets[0], 0,
+ DEFAULT_HASH_SIZE * sizeof(struct hash_bucket *));
+ return (rv);
+}
+
+static int
+do_hash(unsigned char *name, int len, int size)
+{
+ unsigned char *s = name;
+ int accum = 0, i = len;
+
+ while (i--) {
+ /* Add the character in... */
+ accum += *s++;
+ /* Add carry back in... */
+ while (accum > 255)
+ accum = (accum & 255) + (accum >> 8);
+ }
+ return (accum % size);
+}
+
+void add_hash(struct hash_table *table, unsigned char *name, int len,
+ unsigned char *pointer)
+{
+ struct hash_bucket *bp;
+ int hashno;
+
+ if (!table)
+ return;
+ if (!len)
+ len = strlen((char *)name);
+
+ hashno = do_hash(name, len, table->hash_count);
+ bp = new_hash_bucket();
+
+ if (!bp) {
+ warning("Can't add %s to hash table.", name);
+ return;
+ }
+ bp->name = name;
+ bp->value = pointer;
+ bp->next = table->buckets[hashno];
+ bp->len = len;
+ table->buckets[hashno] = bp;
+}
+
+unsigned char *
+hash_lookup(struct hash_table *table, unsigned char *name, int len)
+{
+ struct hash_bucket *bp;
+ int hashno;
+
+ if (!table)
+ return (NULL);
+
+ if (!len)
+ len = strlen((char *)name);
+
+ hashno = do_hash(name, len, table->hash_count);
+
+ for (bp = table->buckets[hashno]; bp; bp = bp->next)
+ if (len == bp->len && !memcmp(bp->name, name, len))
+ return (bp->value);
+
+ return (NULL);
+}
diff --git a/sbin/dhclient/inet.c b/sbin/dhclient/inet.c
new file mode 100644
index 0000000..4b7b1ce
--- /dev/null
+++ b/sbin/dhclient/inet.c
@@ -0,0 +1,121 @@
+/* $OpenBSD: inet.c,v 1.7 2004/05/04 21:48:16 deraadt Exp $ */
+
+/*
+ * Subroutines to manipulate internet addresses in a safely portable
+ * way...
+ */
+
+/*
+ * Copyright (c) 1996 The Internet Software Consortium. 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 Internet Software Consortium 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 INTERNET SOFTWARE CONSORTIUM 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 INTERNET SOFTWARE CONSORTIUM OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * This software has been written for the Internet Software Consortium
+ * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
+ * Enterprises. To learn more about the Internet Software Consortium,
+ * see ``http://www.vix.com/isc''. To learn more about Vixie
+ * Enterprises, see ``http://www.vix.com''.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include "dhcpd.h"
+
+/*
+ * Return just the network number of an internet address...
+ */
+struct iaddr
+subnet_number(struct iaddr addr, struct iaddr mask)
+{
+ struct iaddr rv;
+ int i;
+
+ rv.len = 0;
+
+ /* Both addresses must have the same length... */
+ if (addr.len != mask.len)
+ return (rv);
+
+ rv.len = addr.len;
+ for (i = 0; i < rv.len; i++)
+ rv.iabuf[i] = addr.iabuf[i] & mask.iabuf[i];
+ return (rv);
+}
+
+/*
+ * Given a subnet number and netmask, return the address on that subnet
+ * for which the host portion of the address is all ones (the standard
+ * broadcast address).
+ */
+struct iaddr
+broadcast_addr(struct iaddr subnet, struct iaddr mask)
+{
+ struct iaddr rv;
+ int i;
+
+ if (subnet.len != mask.len) {
+ rv.len = 0;
+ return (rv);
+ }
+
+ for (i = 0; i < subnet.len; i++)
+ rv.iabuf[i] = subnet.iabuf[i] | (~mask.iabuf[i] & 255);
+ rv.len = subnet.len;
+
+ return (rv);
+}
+
+int
+addr_eq(struct iaddr addr1, struct iaddr addr2)
+{
+ if (addr1.len != addr2.len)
+ return (0);
+ return (memcmp(addr1.iabuf, addr2.iabuf, addr1.len) == 0);
+}
+
+char *
+piaddr(struct iaddr addr)
+{
+ static char pbuf[32];
+ struct in_addr a;
+ char *s;
+
+ memcpy(&a, &(addr.iabuf), sizeof(struct in_addr));
+
+ if (addr.len == 0)
+ strlcpy(pbuf, "<null address>", sizeof(pbuf));
+ else {
+ s = inet_ntoa(a);
+ if (s != NULL)
+ strlcpy(pbuf, s, sizeof(pbuf));
+ else
+ strlcpy(pbuf, "<invalid address>", sizeof(pbuf));
+ }
+ return (pbuf);
+}
diff --git a/sbin/dhclient/options.c b/sbin/dhclient/options.c
new file mode 100644
index 0000000..be073da
--- /dev/null
+++ b/sbin/dhclient/options.c
@@ -0,0 +1,894 @@
+/* $OpenBSD: options.c,v 1.15 2004/12/26 03:17:07 deraadt Exp $ */
+
+/* DHCP options parsing and reassembly. */
+
+/*
+ * Copyright (c) 1995, 1996, 1997, 1998 The Internet Software Consortium.
+ * 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 Internet Software Consortium 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 INTERNET SOFTWARE CONSORTIUM 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 INTERNET SOFTWARE CONSORTIUM OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * This software has been written for the Internet Software Consortium
+ * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
+ * Enterprises. To learn more about the Internet Software Consortium,
+ * see ``http://www.vix.com/isc''. To learn more about Vixie
+ * Enterprises, see ``http://www.vix.com''.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <ctype.h>
+
+#define DHCP_OPTION_DATA
+#include "dhcpd.h"
+
+int bad_options = 0;
+int bad_options_max = 5;
+
+void parse_options(struct packet *);
+void parse_option_buffer(struct packet *, unsigned char *, int);
+int store_options(unsigned char *, int, struct tree_cache **,
+ unsigned char *, int, int, int, int);
+void expand_domain_search(struct packet *packet);
+int find_search_domain_name_len(struct option_data *option, int *offset);
+void expand_search_domain_name(struct option_data *option, int *offset,
+ unsigned char **domain_search);
+
+
+/*
+ * Parse all available options out of the specified packet.
+ */
+void
+parse_options(struct packet *packet)
+{
+ /* Initially, zero all option pointers. */
+ memset(packet->options, 0, sizeof(packet->options));
+
+ /* If we don't see the magic cookie, there's nothing to parse. */
+ if (memcmp(packet->raw->options, DHCP_OPTIONS_COOKIE, 4)) {
+ packet->options_valid = 0;
+ return;
+ }
+
+ /*
+ * Go through the options field, up to the end of the packet or
+ * the End field.
+ */
+ parse_option_buffer(packet, &packet->raw->options[4],
+ packet->packet_length - DHCP_FIXED_NON_UDP - 4);
+
+ /*
+ * If we parsed a DHCP Option Overload option, parse more
+ * options out of the buffer(s) containing them.
+ */
+ if (packet->options_valid &&
+ packet->options[DHO_DHCP_OPTION_OVERLOAD].data) {
+ if (packet->options[DHO_DHCP_OPTION_OVERLOAD].data[0] & 1)
+ parse_option_buffer(packet,
+ (unsigned char *)packet->raw->file,
+ sizeof(packet->raw->file));
+ if (packet->options[DHO_DHCP_OPTION_OVERLOAD].data[0] & 2)
+ parse_option_buffer(packet,
+ (unsigned char *)packet->raw->sname,
+ sizeof(packet->raw->sname));
+ }
+
+ /* Expand DHCP Domain Search option. */
+ if (packet->options_valid) {
+ expand_domain_search(packet);
+ }
+}
+
+/*
+ * Parse options out of the specified buffer, storing addresses of
+ * option values in packet->options and setting packet->options_valid if
+ * no errors are encountered.
+ */
+void
+parse_option_buffer(struct packet *packet,
+ unsigned char *buffer, int length)
+{
+ unsigned char *s, *t, *end = buffer + length;
+ int len, code;
+
+ for (s = buffer; *s != DHO_END && s < end; ) {
+ code = s[0];
+
+ /* Pad options don't have a length - just skip them. */
+ if (code == DHO_PAD) {
+ s++;
+ continue;
+ }
+ if (s + 2 > end) {
+ len = 65536;
+ goto bogus;
+ }
+
+ /*
+ * All other fields (except end, see above) have a
+ * one-byte length.
+ */
+ len = s[1];
+
+ /*
+ * If the length is outrageous, silently skip the rest,
+ * and mark the packet bad. Unfortunately some crappy
+ * dhcp servers always seem to give us garbage on the
+ * end of a packet. so rather than keep refusing, give
+ * up and try to take one after seeing a few without
+ * anything good.
+ */
+ if (s + len + 2 > end) {
+ bogus:
+ bad_options++;
+ warning("option %s (%d) %s.",
+ dhcp_options[code].name, len,
+ "larger than buffer");
+ if (bad_options == bad_options_max) {
+ packet->options_valid = 1;
+ bad_options = 0;
+ warning("Many bogus options seen in offers. "
+ "Taking this offer in spite of bogus "
+ "options - hope for the best!");
+ } else {
+ warning("rejecting bogus offer.");
+ packet->options_valid = 0;
+ }
+ return;
+ }
+ /*
+ * If we haven't seen this option before, just make
+ * space for it and copy it there.
+ */
+ if (!packet->options[code].data) {
+ if (!(t = calloc(1, len + 1)))
+ error("Can't allocate storage for option %s.",
+ dhcp_options[code].name);
+ /*
+ * Copy and NUL-terminate the option (in case
+ * it's an ASCII string.
+ */
+ memcpy(t, &s[2], len);
+ t[len] = 0;
+ packet->options[code].len = len;
+ packet->options[code].data = t;
+ } else {
+ /*
+ * If it's a repeat, concatenate it to whatever
+ * we last saw. This is really only required
+ * for clients, but what the heck...
+ */
+ t = calloc(1, len + packet->options[code].len + 1);
+ if (!t)
+ error("Can't expand storage for option %s.",
+ dhcp_options[code].name);
+ memcpy(t, packet->options[code].data,
+ packet->options[code].len);
+ memcpy(t + packet->options[code].len,
+ &s[2], len);
+ packet->options[code].len += len;
+ t[packet->options[code].len] = 0;
+ free(packet->options[code].data);
+ packet->options[code].data = t;
+ }
+ s += len + 2;
+ }
+ packet->options_valid = 1;
+}
+
+/*
+ * Expand DHCP Domain Search option. The value of this option is
+ * encoded like DNS' list of labels. See:
+ * RFC 3397
+ * RFC 1035
+ */
+void
+expand_domain_search(struct packet *packet)
+{
+ int offset, expanded_len, next_domain_len;
+ struct option_data *option;
+ unsigned char *domain_search, *cursor;
+
+ if (packet->options[DHO_DOMAIN_SEARCH].data == NULL)
+ return;
+
+ option = &packet->options[DHO_DOMAIN_SEARCH];
+
+ /* Compute final expanded length. */
+ expanded_len = 0;
+ offset = 0;
+ while (offset < option->len) {
+ next_domain_len = find_search_domain_name_len(option, &offset);
+ if (next_domain_len < 0)
+ /* The Domain Search option value is invalid. */
+ return;
+
+ /* We add 1 for the space between domain names. */
+ expanded_len += next_domain_len + 1;
+ }
+ if (expanded_len > 0)
+ /* Remove 1 for the superfluous trailing space. */
+ --expanded_len;
+
+ domain_search = malloc(expanded_len + 1);
+ if (domain_search == NULL)
+ error("Can't allocate storage for expanded domain-search\n");
+
+ offset = 0;
+ cursor = domain_search;
+ while (offset < option->len) {
+ expand_search_domain_name(option, &offset, &cursor);
+ cursor[0] = ' ';
+ cursor++;
+ }
+ domain_search[expanded_len] = '\0';
+
+ free(option->data);
+ option->len = expanded_len;
+ option->data = domain_search;
+}
+
+int
+find_search_domain_name_len(struct option_data *option, int *offset)
+{
+ int domain_name_len, i, label_len, pointer, pointed_len;
+
+ domain_name_len = 0;
+
+ i = *offset;
+ while (i < option->len) {
+ label_len = option->data[i];
+ if (label_len == 0) {
+ /*
+ * A zero-length label marks the end of this
+ * domain name.
+ */
+ *offset = i + 1;
+ return (domain_name_len);
+ } else if (label_len & 0xC0) {
+ /* This is a pointer to another list of labels. */
+ if (i + 1 >= option->len) {
+ /* The pointer is truncated. */
+ warning("Truncated pointer in DHCP Domain "
+ "Search option.");
+ return (-1);
+ }
+
+ pointer = ((label_len & ~(0xC0)) << 8) +
+ option->data[i + 1];
+ if (pointer >= *offset) {
+ /*
+ * The pointer must indicate a prior
+ * occurrence.
+ */
+ warning("Invalid forward pointer in DHCP "
+ "Domain Search option compression.");
+ return (-1);
+ }
+
+ pointed_len = find_search_domain_name_len(option,
+ &pointer);
+ domain_name_len += pointed_len;
+
+ *offset = i + 2;
+ return (domain_name_len);
+ }
+
+ if (i + label_len >= option->len) {
+ warning("Truncated label in DHCP Domain Search "
+ "option.");
+ return (-1);
+ }
+
+ /*
+ * Update the domain name length with the length of the
+ * current label, plus a trailing dot ('.').
+ */
+ domain_name_len += label_len + 1;
+
+ /* Move cursor. */
+ i += label_len + 1;
+ }
+
+ warning("Truncated DHCP Domain Search option.");
+
+ return (-1);
+}
+
+void
+expand_search_domain_name(struct option_data *option, int *offset,
+ unsigned char **domain_search)
+{
+ int i, label_len, pointer;
+ unsigned char *cursor;
+
+ /*
+ * This is the same loop than the function above
+ * (find_search_domain_name_len). Therefore, we remove checks,
+ * they're already done. Here, we just make the copy.
+ */
+ i = *offset;
+ cursor = *domain_search;
+ while (i < option->len) {
+ label_len = option->data[i];
+ if (label_len == 0) {
+ /*
+ * A zero-length label marks the end of this
+ * domain name.
+ */
+ *offset = i + 1;
+ *domain_search = cursor;
+ return;
+ } else if (label_len & 0xC0) {
+ /* This is a pointer to another list of labels. */
+ pointer = ((label_len & ~(0xC0)) << 8) +
+ option->data[i + 1];
+
+ expand_search_domain_name(option, &pointer, &cursor);
+
+ *offset = i + 2;
+ *domain_search = cursor;
+ return;
+ }
+
+ /* Copy the label found. */
+ memcpy(cursor, option->data + i + 1, label_len);
+ cursor[label_len] = '.';
+
+ /* Move cursor. */
+ i += label_len + 1;
+ cursor += label_len + 1;
+ }
+}
+
+/*
+ * cons options into a big buffer, and then split them out into the
+ * three separate buffers if needed. This allows us to cons up a set of
+ * vendor options using the same routine.
+ */
+int
+cons_options(struct packet *inpacket, struct dhcp_packet *outpacket,
+ int mms, struct tree_cache **options,
+ int overload, /* Overload flags that may be set. */
+ int terminate, int bootpp, u_int8_t *prl, int prl_len)
+{
+ unsigned char priority_list[300], buffer[4096];
+ int priority_len, main_buffer_size, mainbufix, bufix;
+ int option_size, length;
+
+ /*
+ * If the client has provided a maximum DHCP message size, use
+ * that; otherwise, if it's BOOTP, only 64 bytes; otherwise use
+ * up to the minimum IP MTU size (576 bytes).
+ *
+ * XXX if a BOOTP client specifies a max message size, we will
+ * honor it.
+ */
+ if (!mms &&
+ inpacket &&
+ inpacket->options[DHO_DHCP_MAX_MESSAGE_SIZE].data &&
+ (inpacket->options[DHO_DHCP_MAX_MESSAGE_SIZE].len >=
+ sizeof(u_int16_t)))
+ mms = getUShort(
+ inpacket->options[DHO_DHCP_MAX_MESSAGE_SIZE].data);
+
+ if (mms)
+ main_buffer_size = mms - DHCP_FIXED_LEN;
+ else if (bootpp)
+ main_buffer_size = 64;
+ else
+ main_buffer_size = 576 - DHCP_FIXED_LEN;
+
+ if (main_buffer_size > sizeof(buffer))
+ main_buffer_size = sizeof(buffer);
+
+ /* Preload the option priority list with mandatory options. */
+ priority_len = 0;
+ priority_list[priority_len++] = DHO_DHCP_MESSAGE_TYPE;
+ priority_list[priority_len++] = DHO_DHCP_SERVER_IDENTIFIER;
+ priority_list[priority_len++] = DHO_DHCP_LEASE_TIME;
+ priority_list[priority_len++] = DHO_DHCP_MESSAGE;
+
+ /*
+ * If the client has provided a list of options that it wishes
+ * returned, use it to prioritize. Otherwise, prioritize based
+ * on the default priority list.
+ */
+ if (inpacket &&
+ inpacket->options[DHO_DHCP_PARAMETER_REQUEST_LIST].data) {
+ int prlen =
+ inpacket->options[DHO_DHCP_PARAMETER_REQUEST_LIST].len;
+ if (prlen + priority_len > sizeof(priority_list))
+ prlen = sizeof(priority_list) - priority_len;
+
+ memcpy(&priority_list[priority_len],
+ inpacket->options[DHO_DHCP_PARAMETER_REQUEST_LIST].data,
+ prlen);
+ priority_len += prlen;
+ prl = priority_list;
+ } else if (prl) {
+ if (prl_len + priority_len > sizeof(priority_list))
+ prl_len = sizeof(priority_list) - priority_len;
+
+ memcpy(&priority_list[priority_len], prl, prl_len);
+ priority_len += prl_len;
+ prl = priority_list;
+ } else {
+ memcpy(&priority_list[priority_len],
+ dhcp_option_default_priority_list,
+ sizeof_dhcp_option_default_priority_list);
+ priority_len += sizeof_dhcp_option_default_priority_list;
+ }
+
+ /* Copy the options into the big buffer... */
+ option_size = store_options(
+ buffer,
+ (main_buffer_size - 7 + ((overload & 1) ? DHCP_FILE_LEN : 0) +
+ ((overload & 2) ? DHCP_SNAME_LEN : 0)),
+ options, priority_list, priority_len, main_buffer_size,
+ (main_buffer_size + ((overload & 1) ? DHCP_FILE_LEN : 0)),
+ terminate);
+
+ /* Put the cookie up front... */
+ memcpy(outpacket->options, DHCP_OPTIONS_COOKIE, 4);
+ mainbufix = 4;
+
+ /*
+ * If we're going to have to overload, store the overload option
+ * at the beginning. If we can, though, just store the whole
+ * thing in the packet's option buffer and leave it at that.
+ */
+ if (option_size <= main_buffer_size - mainbufix) {
+ memcpy(&outpacket->options[mainbufix],
+ buffer, option_size);
+ mainbufix += option_size;
+ if (mainbufix < main_buffer_size)
+ outpacket->options[mainbufix++] = DHO_END;
+ length = DHCP_FIXED_NON_UDP + mainbufix;
+ } else {
+ outpacket->options[mainbufix++] = DHO_DHCP_OPTION_OVERLOAD;
+ outpacket->options[mainbufix++] = 1;
+ if (option_size >
+ main_buffer_size - mainbufix + DHCP_FILE_LEN)
+ outpacket->options[mainbufix++] = 3;
+ else
+ outpacket->options[mainbufix++] = 1;
+
+ memcpy(&outpacket->options[mainbufix],
+ buffer, main_buffer_size - mainbufix);
+ bufix = main_buffer_size - mainbufix;
+ length = DHCP_FIXED_NON_UDP + mainbufix;
+ if (overload & 1) {
+ if (option_size - bufix <= DHCP_FILE_LEN) {
+ memcpy(outpacket->file,
+ &buffer[bufix], option_size - bufix);
+ mainbufix = option_size - bufix;
+ if (mainbufix < DHCP_FILE_LEN)
+ outpacket->file[mainbufix++] = (char)DHO_END;
+ while (mainbufix < DHCP_FILE_LEN)
+ outpacket->file[mainbufix++] = (char)DHO_PAD;
+ } else {
+ memcpy(outpacket->file,
+ &buffer[bufix], DHCP_FILE_LEN);
+ bufix += DHCP_FILE_LEN;
+ }
+ }
+ if ((overload & 2) && option_size < bufix) {
+ memcpy(outpacket->sname,
+ &buffer[bufix], option_size - bufix);
+
+ mainbufix = option_size - bufix;
+ if (mainbufix < DHCP_SNAME_LEN)
+ outpacket->file[mainbufix++] = (char)DHO_END;
+ while (mainbufix < DHCP_SNAME_LEN)
+ outpacket->file[mainbufix++] = (char)DHO_PAD;
+ }
+ }
+ return (length);
+}
+
+/*
+ * Store all the requested options into the requested buffer.
+ */
+int
+store_options(unsigned char *buffer, int buflen, struct tree_cache **options,
+ unsigned char *priority_list, int priority_len, int first_cutoff,
+ int second_cutoff, int terminate)
+{
+ int bufix = 0, option_stored[256], i, ix, tto;
+
+ /* Zero out the stored-lengths array. */
+ memset(option_stored, 0, sizeof(option_stored));
+
+ /*
+ * Copy out the options in the order that they appear in the
+ * priority list...
+ */
+ for (i = 0; i < priority_len; i++) {
+ /* Code for next option to try to store. */
+ int code = priority_list[i];
+ int optstart;
+
+ /*
+ * Number of bytes left to store (some may already have
+ * been stored by a previous pass).
+ */
+ int length;
+
+ /* If no data is available for this option, skip it. */
+ if (!options[code]) {
+ continue;
+ }
+
+ /*
+ * The client could ask for things that are mandatory,
+ * in which case we should avoid storing them twice...
+ */
+ if (option_stored[code])
+ continue;
+ option_stored[code] = 1;
+
+ /* We should now have a constant length for the option. */
+ length = options[code]->len;
+
+ /* Do we add a NUL? */
+ if (terminate && dhcp_options[code].format[0] == 't') {
+ length++;
+ tto = 1;
+ } else
+ tto = 0;
+
+ /* Try to store the option. */
+
+ /*
+ * If the option's length is more than 255, we must
+ * store it in multiple hunks. Store 255-byte hunks
+ * first. However, in any case, if the option data will
+ * cross a buffer boundary, split it across that
+ * boundary.
+ */
+ ix = 0;
+
+ optstart = bufix;
+ while (length) {
+ unsigned char incr = length > 255 ? 255 : length;
+
+ /*
+ * If this hunk of the buffer will cross a
+ * boundary, only go up to the boundary in this
+ * pass.
+ */
+ if (bufix < first_cutoff &&
+ bufix + incr > first_cutoff)
+ incr = first_cutoff - bufix;
+ else if (bufix < second_cutoff &&
+ bufix + incr > second_cutoff)
+ incr = second_cutoff - bufix;
+
+ /*
+ * If this option is going to overflow the
+ * buffer, skip it.
+ */
+ if (bufix + 2 + incr > buflen) {
+ bufix = optstart;
+ break;
+ }
+
+ /* Everything looks good - copy it in! */
+ buffer[bufix] = code;
+ buffer[bufix + 1] = incr;
+ if (tto && incr == length) {
+ memcpy(buffer + bufix + 2,
+ options[code]->value + ix, incr - 1);
+ buffer[bufix + 2 + incr - 1] = 0;
+ } else
+ memcpy(buffer + bufix + 2,
+ options[code]->value + ix, incr);
+ length -= incr;
+ ix += incr;
+ bufix += 2 + incr;
+ }
+ }
+ return (bufix);
+}
+
+/*
+ * Format the specified option so that a human can easily read it.
+ */
+char *
+pretty_print_option(unsigned int code, unsigned char *data, int len,
+ int emit_commas, int emit_quotes)
+{
+ static char optbuf[32768]; /* XXX */
+ int hunksize = 0, numhunk = -1, numelem = 0;
+ char fmtbuf[32], *op = optbuf;
+ int i, j, k, opleft = sizeof(optbuf);
+ unsigned char *dp = data;
+ struct in_addr foo;
+ char comma;
+
+ /* Code should be between 0 and 255. */
+ if (code > 255)
+ error("pretty_print_option: bad code %d", code);
+
+ if (emit_commas)
+ comma = ',';
+ else
+ comma = ' ';
+
+ /* Figure out the size of the data. */
+ for (i = 0; dhcp_options[code].format[i]; i++) {
+ if (!numhunk) {
+ warning("%s: Excess information in format string: %s",
+ dhcp_options[code].name,
+ &(dhcp_options[code].format[i]));
+ break;
+ }
+ numelem++;
+ fmtbuf[i] = dhcp_options[code].format[i];
+ switch (dhcp_options[code].format[i]) {
+ case 'A':
+ --numelem;
+ fmtbuf[i] = 0;
+ numhunk = 0;
+ break;
+ case 'X':
+ for (k = 0; k < len; k++)
+ if (!isascii(data[k]) ||
+ !isprint(data[k]))
+ break;
+ if (k == len) {
+ fmtbuf[i] = 't';
+ numhunk = -2;
+ } else {
+ fmtbuf[i] = 'x';
+ hunksize++;
+ comma = ':';
+ numhunk = 0;
+ }
+ fmtbuf[i + 1] = 0;
+ break;
+ case 't':
+ fmtbuf[i] = 't';
+ fmtbuf[i + 1] = 0;
+ numhunk = -2;
+ break;
+ case 'I':
+ case 'l':
+ case 'L':
+ hunksize += 4;
+ break;
+ case 's':
+ case 'S':
+ hunksize += 2;
+ break;
+ case 'b':
+ case 'B':
+ case 'f':
+ hunksize++;
+ break;
+ case 'e':
+ break;
+ default:
+ warning("%s: garbage in format string: %s",
+ dhcp_options[code].name,
+ &(dhcp_options[code].format[i]));
+ break;
+ }
+ }
+
+ /* Check for too few bytes... */
+ if (hunksize > len) {
+ warning("%s: expecting at least %d bytes; got %d",
+ dhcp_options[code].name, hunksize, len);
+ return ("<error>");
+ }
+ /* Check for too many bytes... */
+ if (numhunk == -1 && hunksize < len)
+ warning("%s: %d extra bytes",
+ dhcp_options[code].name, len - hunksize);
+
+ /* If this is an array, compute its size. */
+ if (!numhunk)
+ numhunk = len / hunksize;
+ /* See if we got an exact number of hunks. */
+ if (numhunk > 0 && numhunk * hunksize < len)
+ warning("%s: %d extra bytes at end of array",
+ dhcp_options[code].name, len - numhunk * hunksize);
+
+ /* A one-hunk array prints the same as a single hunk. */
+ if (numhunk < 0)
+ numhunk = 1;
+
+ /* Cycle through the array (or hunk) printing the data. */
+ for (i = 0; i < numhunk; i++) {
+ for (j = 0; j < numelem; j++) {
+ int opcount;
+ switch (fmtbuf[j]) {
+ case 't':
+ if (emit_quotes) {
+ *op++ = '"';
+ opleft--;
+ }
+ for (; dp < data + len; dp++) {
+ if (!isascii(*dp) ||
+ !isprint(*dp)) {
+ if (dp + 1 != data + len ||
+ *dp != 0) {
+ snprintf(op, opleft,
+ "\\%03o", *dp);
+ op += 4;
+ opleft -= 4;
+ }
+ } else if (*dp == '"' ||
+ *dp == '\'' ||
+ *dp == '$' ||
+ *dp == '`' ||
+ *dp == '\\') {
+ *op++ = '\\';
+ *op++ = *dp;
+ opleft -= 2;
+ } else {
+ *op++ = *dp;
+ opleft--;
+ }
+ }
+ if (emit_quotes) {
+ *op++ = '"';
+ opleft--;
+ }
+
+ *op = 0;
+ break;
+ case 'I':
+ foo.s_addr = htonl(getULong(dp));
+ opcount = strlcpy(op, inet_ntoa(foo), opleft);
+ if (opcount >= opleft)
+ goto toobig;
+ opleft -= opcount;
+ dp += 4;
+ break;
+ case 'l':
+ opcount = snprintf(op, opleft, "%ld",
+ (long)getLong(dp));
+ if (opcount >= opleft || opcount == -1)
+ goto toobig;
+ opleft -= opcount;
+ dp += 4;
+ break;
+ case 'L':
+ opcount = snprintf(op, opleft, "%ld",
+ (unsigned long)getULong(dp));
+ if (opcount >= opleft || opcount == -1)
+ goto toobig;
+ opleft -= opcount;
+ dp += 4;
+ break;
+ case 's':
+ opcount = snprintf(op, opleft, "%d",
+ getShort(dp));
+ if (opcount >= opleft || opcount == -1)
+ goto toobig;
+ opleft -= opcount;
+ dp += 2;
+ break;
+ case 'S':
+ opcount = snprintf(op, opleft, "%d",
+ getUShort(dp));
+ if (opcount >= opleft || opcount == -1)
+ goto toobig;
+ opleft -= opcount;
+ dp += 2;
+ break;
+ case 'b':
+ opcount = snprintf(op, opleft, "%d",
+ *(char *)dp++);
+ if (opcount >= opleft || opcount == -1)
+ goto toobig;
+ opleft -= opcount;
+ break;
+ case 'B':
+ opcount = snprintf(op, opleft, "%d", *dp++);
+ if (opcount >= opleft || opcount == -1)
+ goto toobig;
+ opleft -= opcount;
+ break;
+ case 'x':
+ opcount = snprintf(op, opleft, "%x", *dp++);
+ if (opcount >= opleft || opcount == -1)
+ goto toobig;
+ opleft -= opcount;
+ break;
+ case 'f':
+ opcount = strlcpy(op,
+ *dp++ ? "true" : "false", opleft);
+ if (opcount >= opleft)
+ goto toobig;
+ opleft -= opcount;
+ break;
+ default:
+ warning("Unexpected format code %c", fmtbuf[j]);
+ }
+ op += strlen(op);
+ opleft -= strlen(op);
+ if (opleft < 1)
+ goto toobig;
+ if (j + 1 < numelem && comma != ':') {
+ *op++ = ' ';
+ opleft--;
+ }
+ }
+ if (i + 1 < numhunk) {
+ *op++ = comma;
+ opleft--;
+ }
+ if (opleft < 1)
+ goto toobig;
+
+ }
+ return (optbuf);
+ toobig:
+ warning("dhcp option too large");
+ return ("<error>");
+}
+
+void
+do_packet(struct interface_info *interface, struct dhcp_packet *packet,
+ int len, unsigned int from_port, struct iaddr from, struct hardware *hfrom)
+{
+ struct packet tp;
+ int i;
+
+ if (packet->hlen > sizeof(packet->chaddr)) {
+ note("Discarding packet with invalid hlen.");
+ return;
+ }
+
+ memset(&tp, 0, sizeof(tp));
+ tp.raw = packet;
+ tp.packet_length = len;
+ tp.client_port = from_port;
+ tp.client_addr = from;
+ tp.interface = interface;
+ tp.haddr = hfrom;
+
+ parse_options(&tp);
+ if (tp.options_valid &&
+ tp.options[DHO_DHCP_MESSAGE_TYPE].data)
+ tp.packet_type = tp.options[DHO_DHCP_MESSAGE_TYPE].data[0];
+ if (tp.packet_type)
+ dhcp(&tp);
+ else
+ bootp(&tp);
+
+ /* Free the data associated with the options. */
+ for (i = 0; i < 256; i++)
+ if (tp.options[i].len && tp.options[i].data)
+ free(tp.options[i].data);
+}
diff --git a/sbin/dhclient/packet.c b/sbin/dhclient/packet.c
new file mode 100644
index 0000000..859f48b
--- /dev/null
+++ b/sbin/dhclient/packet.c
@@ -0,0 +1,248 @@
+/* $OpenBSD: packet.c,v 1.9 2004/05/04 18:58:50 deraadt Exp $ */
+
+/* Packet assembly code, originally contributed by Archie Cobbs. */
+
+/*
+ * Copyright (c) 1995, 1996, 1999 The Internet Software Consortium.
+ * 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 Internet Software Consortium 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 INTERNET SOFTWARE CONSORTIUM 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 INTERNET SOFTWARE CONSORTIUM OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * This software has been written for the Internet Software Consortium
+ * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
+ * Enterprises. To learn more about the Internet Software Consortium,
+ * see ``http://www.vix.com/isc''. To learn more about Vixie
+ * Enterprises, see ``http://www.vix.com''.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include "dhcpd.h"
+
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <netinet/udp.h>
+#include <netinet/if_ether.h>
+
+#define ETHER_HEADER_SIZE (ETHER_ADDR_LEN * 2 + sizeof(u_int16_t))
+
+u_int32_t checksum(unsigned char *, unsigned, u_int32_t);
+u_int32_t wrapsum(u_int32_t);
+
+u_int32_t
+checksum(unsigned char *buf, unsigned nbytes, u_int32_t sum)
+{
+ int i;
+
+ /* Checksum all the pairs of bytes first... */
+ for (i = 0; i < (nbytes & ~1U); i += 2) {
+ sum += (u_int16_t)ntohs(*((u_int16_t *)(buf + i)));
+ if (sum > 0xFFFF)
+ sum -= 0xFFFF;
+ }
+
+ /*
+ * If there's a single byte left over, checksum it, too.
+ * Network byte order is big-endian, so the remaining byte is
+ * the high byte.
+ */
+ if (i < nbytes) {
+ sum += buf[i] << 8;
+ if (sum > 0xFFFF)
+ sum -= 0xFFFF;
+ }
+
+ return (sum);
+}
+
+u_int32_t
+wrapsum(u_int32_t sum)
+{
+ sum = ~sum & 0xFFFF;
+ return (htons(sum));
+}
+
+void
+assemble_hw_header(struct interface_info *interface, unsigned char *buf,
+ int *bufix)
+{
+ struct ether_header eh;
+
+ memset(eh.ether_dhost, 0xff, sizeof(eh.ether_dhost));
+ if (interface->hw_address.hlen == sizeof(eh.ether_shost))
+ memcpy(eh.ether_shost, interface->hw_address.haddr,
+ sizeof(eh.ether_shost));
+ else
+ memset(eh.ether_shost, 0x00, sizeof(eh.ether_shost));
+
+ eh.ether_type = htons(ETHERTYPE_IP);
+
+ memcpy(&buf[*bufix], &eh, ETHER_HEADER_SIZE);
+ *bufix += ETHER_HEADER_SIZE;
+}
+
+void
+assemble_udp_ip_header(unsigned char *buf, int *bufix, u_int32_t from,
+ u_int32_t to, unsigned int port, unsigned char *data, int len)
+{
+ struct ip ip;
+ struct udphdr udp;
+
+ ip.ip_v = 4;
+ ip.ip_hl = 5;
+ ip.ip_tos = IPTOS_LOWDELAY;
+ ip.ip_len = htons(sizeof(ip) + sizeof(udp) + len);
+ ip.ip_id = 0;
+ ip.ip_off = 0;
+ ip.ip_ttl = 128;
+ ip.ip_p = IPPROTO_UDP;
+ ip.ip_sum = 0;
+ ip.ip_src.s_addr = from;
+ ip.ip_dst.s_addr = to;
+
+ ip.ip_sum = wrapsum(checksum((unsigned char *)&ip, sizeof(ip), 0));
+ memcpy(&buf[*bufix], &ip, sizeof(ip));
+ *bufix += sizeof(ip);
+
+ udp.uh_sport = htons(LOCAL_PORT); /* XXX */
+ udp.uh_dport = port; /* XXX */
+ udp.uh_ulen = htons(sizeof(udp) + len);
+ memset(&udp.uh_sum, 0, sizeof(udp.uh_sum));
+
+ udp.uh_sum = wrapsum(checksum((unsigned char *)&udp, sizeof(udp),
+ checksum(data, len, checksum((unsigned char *)&ip.ip_src,
+ 2 * sizeof(ip.ip_src),
+ IPPROTO_UDP + (u_int32_t)ntohs(udp.uh_ulen)))));
+
+ memcpy(&buf[*bufix], &udp, sizeof(udp));
+ *bufix += sizeof(udp);
+}
+
+ssize_t
+decode_hw_header(unsigned char *buf, int bufix, struct hardware *from)
+{
+ struct ether_header eh;
+
+ memcpy(&eh, buf + bufix, ETHER_HEADER_SIZE);
+
+ memcpy(from->haddr, eh.ether_shost, sizeof(eh.ether_shost));
+ from->htype = ARPHRD_ETHER;
+ from->hlen = sizeof(eh.ether_shost);
+
+ return (sizeof(eh));
+}
+
+ssize_t
+decode_udp_ip_header(unsigned char *buf, int bufix, struct sockaddr_in *from,
+ unsigned char *data, int buflen)
+{
+ struct ip *ip;
+ struct udphdr *udp;
+ u_int32_t ip_len = (buf[bufix] & 0xf) << 2;
+ u_int32_t sum, usum;
+ static int ip_packets_seen;
+ static int ip_packets_bad_checksum;
+ static int udp_packets_seen;
+ static int udp_packets_bad_checksum;
+ static int udp_packets_length_checked;
+ static int udp_packets_length_overflow;
+ int len = 0;
+
+ ip = (struct ip *)(buf + bufix);
+ udp = (struct udphdr *)(buf + bufix + ip_len);
+
+ /* Check the IP header checksum - it should be zero. */
+ ip_packets_seen++;
+ if (wrapsum(checksum(buf + bufix, ip_len, 0)) != 0) {
+ ip_packets_bad_checksum++;
+ if (ip_packets_seen > 4 &&
+ (ip_packets_seen / ip_packets_bad_checksum) < 2) {
+ note("%d bad IP checksums seen in %d packets",
+ ip_packets_bad_checksum, ip_packets_seen);
+ ip_packets_seen = ip_packets_bad_checksum = 0;
+ }
+ return (-1);
+ }
+
+ if (ntohs(ip->ip_len) != buflen)
+ debug("ip length %d disagrees with bytes received %d.",
+ ntohs(ip->ip_len), buflen);
+
+ memcpy(&from->sin_addr, &ip->ip_src, 4);
+
+ /*
+ * Compute UDP checksums, including the ``pseudo-header'', the
+ * UDP header and the data. If the UDP checksum field is zero,
+ * we're not supposed to do a checksum.
+ */
+ if (!data) {
+ data = buf + bufix + ip_len + sizeof(*udp);
+ len = ntohs(udp->uh_ulen) - sizeof(*udp);
+ udp_packets_length_checked++;
+ if (len + data > buf + bufix + buflen) {
+ udp_packets_length_overflow++;
+ if (udp_packets_length_checked > 4 &&
+ (udp_packets_length_checked /
+ udp_packets_length_overflow) < 2) {
+ note("%d udp packets in %d too long - dropped",
+ udp_packets_length_overflow,
+ udp_packets_length_checked);
+ udp_packets_length_overflow =
+ udp_packets_length_checked = 0;
+ }
+ return (-1);
+ }
+ if (len + data != buf + bufix + buflen)
+ debug("accepting packet with data after udp payload.");
+ }
+
+ usum = udp->uh_sum;
+ udp->uh_sum = 0;
+
+ sum = wrapsum(checksum((unsigned char *)udp, sizeof(*udp),
+ checksum(data, len, checksum((unsigned char *)&ip->ip_src,
+ 2 * sizeof(ip->ip_src),
+ IPPROTO_UDP + (u_int32_t)ntohs(udp->uh_ulen)))));
+
+ udp_packets_seen++;
+ if (usum && usum != sum) {
+ udp_packets_bad_checksum++;
+ if (udp_packets_seen > 4 &&
+ (udp_packets_seen / udp_packets_bad_checksum) < 2) {
+ note("%d bad udp checksums in %d packets",
+ udp_packets_bad_checksum, udp_packets_seen);
+ udp_packets_seen = udp_packets_bad_checksum = 0;
+ }
+ return (-1);
+ }
+
+ memcpy(&from->sin_port, &udp->uh_sport, sizeof(udp->uh_sport));
+
+ return (ip_len + sizeof(*udp));
+}
diff --git a/sbin/dhclient/parse.c b/sbin/dhclient/parse.c
new file mode 100644
index 0000000..e548bd3
--- /dev/null
+++ b/sbin/dhclient/parse.c
@@ -0,0 +1,584 @@
+/* $OpenBSD: parse.c,v 1.11 2004/05/05 23:07:47 deraadt Exp $ */
+
+/* Common parser code for dhcpd and dhclient. */
+
+/*
+ * Copyright (c) 1995, 1996, 1997, 1998 The Internet Software Consortium.
+ * 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 Internet Software Consortium 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 INTERNET SOFTWARE CONSORTIUM 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 INTERNET SOFTWARE CONSORTIUM OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * This software has been written for the Internet Software Consortium
+ * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
+ * Enterprises. To learn more about the Internet Software Consortium,
+ * see ``http://www.vix.com/isc''. To learn more about Vixie
+ * Enterprises, see ``http://www.vix.com''.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include "dhcpd.h"
+#include "dhctoken.h"
+
+/* Skip to the semicolon ending the current statement. If we encounter
+ * braces, the matching closing brace terminates the statement. If we
+ * encounter a right brace but haven't encountered a left brace, return
+ * leaving the brace in the token buffer for the caller. If we see a
+ * semicolon and haven't seen a left brace, return. This lets us skip
+ * over:
+ *
+ * statement;
+ * statement foo bar { }
+ * statement foo bar { statement { } }
+ * statement}
+ *
+ * ...et cetera.
+ */
+void
+skip_to_semi(FILE *cfile)
+{
+ int brace_count = 0, token;
+ char *val;
+
+ do {
+ token = peek_token(&val, cfile);
+ if (token == RBRACE) {
+ if (brace_count) {
+ token = next_token(&val, cfile);
+ if (!--brace_count)
+ return;
+ } else
+ return;
+ } else if (token == LBRACE) {
+ brace_count++;
+ } else if (token == SEMI && !brace_count) {
+ token = next_token(&val, cfile);
+ return;
+ } else if (token == '\n') {
+ /*
+ * EOL only happens when parsing
+ * /etc/resolv.conf, and we treat it like a
+ * semicolon because the resolv.conf file is
+ * line-oriented.
+ */
+ token = next_token(&val, cfile);
+ return;
+ }
+ token = next_token(&val, cfile);
+ } while (token != EOF);
+}
+
+int
+parse_semi(FILE *cfile)
+{
+ int token;
+ char *val;
+
+ token = next_token(&val, cfile);
+ if (token != SEMI) {
+ parse_warn("semicolon expected.");
+ skip_to_semi(cfile);
+ return (0);
+ }
+ return (1);
+}
+
+/*
+ * string-parameter :== STRING SEMI
+ */
+char *
+parse_string(FILE *cfile)
+{
+ char *val, *s;
+ size_t valsize;
+ int token;
+
+ token = next_token(&val, cfile);
+ if (token != STRING) {
+ parse_warn("filename must be a string");
+ skip_to_semi(cfile);
+ return (NULL);
+ }
+ valsize = strlen(val) + 1;
+ s = malloc(valsize);
+ if (!s)
+ error("no memory for string %s.", val);
+ memcpy(s, val, valsize);
+
+ if (!parse_semi(cfile))
+ return (NULL);
+ return (s);
+}
+
+int
+parse_ip_addr(FILE *cfile, struct iaddr *addr)
+{
+ addr->len = 4;
+ if (parse_numeric_aggregate(cfile, addr->iabuf,
+ &addr->len, DOT, 10, 8))
+ return (1);
+ return (0);
+}
+
+/*
+ * hardware-parameter :== HARDWARE ETHERNET csns SEMI
+ * csns :== NUMBER | csns COLON NUMBER
+ */
+void
+parse_hardware_param(FILE *cfile, struct hardware *hardware)
+{
+ unsigned char *t;
+ int token, hlen;
+ char *val;
+
+ token = next_token(&val, cfile);
+ switch (token) {
+ case ETHERNET:
+ hardware->htype = HTYPE_ETHER;
+ break;
+ case TOKEN_RING:
+ hardware->htype = HTYPE_IEEE802;
+ break;
+ case FDDI:
+ hardware->htype = HTYPE_FDDI;
+ break;
+ default:
+ parse_warn("expecting a network hardware type");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ /*
+ * Parse the hardware address information. Technically, it
+ * would make a lot of sense to restrict the length of the data
+ * we'll accept here to the length of a particular hardware
+ * address type. Unfortunately, there are some broken clients
+ * out there that put bogus data in the chaddr buffer, and we
+ * accept that data in the lease file rather than simply failing
+ * on such clients. Yuck.
+ */
+ hlen = 0;
+ t = parse_numeric_aggregate(cfile, NULL, &hlen, COLON, 16, 8);
+ if (!t)
+ return;
+ if (hlen > sizeof(hardware->haddr)) {
+ free(t);
+ parse_warn("hardware address too long");
+ } else {
+ hardware->hlen = hlen;
+ memcpy((unsigned char *)&hardware->haddr[0], t,
+ hardware->hlen);
+ if (hlen < sizeof(hardware->haddr))
+ memset(&hardware->haddr[hlen], 0,
+ sizeof(hardware->haddr) - hlen);
+ free(t);
+ }
+
+ token = next_token(&val, cfile);
+ if (token != SEMI) {
+ parse_warn("expecting semicolon.");
+ skip_to_semi(cfile);
+ }
+}
+
+/*
+ * lease-time :== NUMBER SEMI
+ */
+void
+parse_lease_time(FILE *cfile, time_t *timep)
+{
+ char *val;
+ int token;
+
+ token = next_token(&val, cfile);
+ if (token != NUMBER) {
+ parse_warn("Expecting numeric lease time");
+ skip_to_semi(cfile);
+ return;
+ }
+ convert_num((unsigned char *)timep, val, 10, 32);
+ /* Unswap the number - convert_num returns stuff in NBO. */
+ *timep = ntohl(*timep); /* XXX */
+
+ parse_semi(cfile);
+}
+
+/*
+ * No BNF for numeric aggregates - that's defined by the caller. What
+ * this function does is to parse a sequence of numbers separated by the
+ * token specified in separator. If max is zero, any number of numbers
+ * will be parsed; otherwise, exactly max numbers are expected. Base
+ * and size tell us how to internalize the numbers once they've been
+ * tokenized.
+ */
+unsigned char *
+parse_numeric_aggregate(FILE *cfile, unsigned char *buf, int *max,
+ int separator, int base, int size)
+{
+ unsigned char *bufp = buf, *s = NULL;
+ int token, count = 0;
+ char *val, *t;
+ size_t valsize;
+ pair c = NULL;
+
+ if (!bufp && *max) {
+ bufp = malloc(*max * size / 8);
+ if (!bufp)
+ error("can't allocate space for numeric aggregate");
+ } else
+ s = bufp;
+
+ do {
+ if (count) {
+ token = peek_token(&val, cfile);
+ if (token != separator) {
+ if (!*max)
+ break;
+ if (token != RBRACE && token != LBRACE)
+ token = next_token(&val, cfile);
+ parse_warn("too few numbers.");
+ if (token != SEMI)
+ skip_to_semi(cfile);
+ return (NULL);
+ }
+ token = next_token(&val, cfile);
+ }
+ token = next_token(&val, cfile);
+
+ if (token == EOF) {
+ parse_warn("unexpected end of file");
+ break;
+ }
+
+ /* Allow NUMBER_OR_NAME if base is 16. */
+ if (token != NUMBER &&
+ (base != 16 || token != NUMBER_OR_NAME)) {
+ parse_warn("expecting numeric value.");
+ skip_to_semi(cfile);
+ return (NULL);
+ }
+ /*
+ * If we can, convert the number now; otherwise, build a
+ * linked list of all the numbers.
+ */
+ if (s) {
+ convert_num(s, val, base, size);
+ s += size / 8;
+ } else {
+ valsize = strlen(val) + 1;
+ t = malloc(valsize);
+ if (!t)
+ error("no temp space for number.");
+ memcpy(t, val, valsize);
+ c = cons(t, c);
+ }
+ } while (++count != *max);
+
+ /* If we had to cons up a list, convert it now. */
+ if (c) {
+ bufp = malloc(count * size / 8);
+ if (!bufp)
+ error("can't allocate space for numeric aggregate.");
+ s = bufp + count - size / 8;
+ *max = count;
+ }
+ while (c) {
+ pair cdr = c->cdr;
+ convert_num(s, (char *)c->car, base, size);
+ s -= size / 8;
+ /* Free up temp space. */
+ free(c->car);
+ free(c);
+ c = cdr;
+ }
+ return (bufp);
+}
+
+void
+convert_num(unsigned char *buf, char *str, int base, int size)
+{
+ int negative = 0, tval, max;
+ u_int32_t val = 0;
+ char *ptr = str;
+
+ if (*ptr == '-') {
+ negative = 1;
+ ptr++;
+ }
+
+ /* If base wasn't specified, figure it out from the data. */
+ if (!base) {
+ if (ptr[0] == '0') {
+ if (ptr[1] == 'x') {
+ base = 16;
+ ptr += 2;
+ } else if (isascii(ptr[1]) && isdigit(ptr[1])) {
+ base = 8;
+ ptr += 1;
+ } else
+ base = 10;
+ } else
+ base = 10;
+ }
+
+ do {
+ tval = *ptr++;
+ /* XXX assumes ASCII... */
+ if (tval >= 'a')
+ tval = tval - 'a' + 10;
+ else if (tval >= 'A')
+ tval = tval - 'A' + 10;
+ else if (tval >= '0')
+ tval -= '0';
+ else {
+ warning("Bogus number: %s.", str);
+ break;
+ }
+ if (tval >= base) {
+ warning("Bogus number: %s: digit %d not in base %d",
+ str, tval, base);
+ break;
+ }
+ val = val * base + tval;
+ } while (*ptr);
+
+ if (negative)
+ max = (1 << (size - 1));
+ else
+ max = (1 << (size - 1)) + ((1 << (size - 1)) - 1);
+ if (val > max) {
+ switch (base) {
+ case 8:
+ warning("value %s%o exceeds max (%d) for precision.",
+ negative ? "-" : "", val, max);
+ break;
+ case 16:
+ warning("value %s%x exceeds max (%d) for precision.",
+ negative ? "-" : "", val, max);
+ break;
+ default:
+ warning("value %s%u exceeds max (%d) for precision.",
+ negative ? "-" : "", val, max);
+ break;
+ }
+ }
+
+ if (negative)
+ switch (size) {
+ case 8:
+ *buf = -(unsigned long)val;
+ break;
+ case 16:
+ putShort(buf, -(unsigned long)val);
+ break;
+ case 32:
+ putLong(buf, -(unsigned long)val);
+ break;
+ default:
+ warning("Unexpected integer size: %d", size);
+ break;
+ }
+ else
+ switch (size) {
+ case 8:
+ *buf = (u_int8_t)val;
+ break;
+ case 16:
+ putUShort(buf, (u_int16_t)val);
+ break;
+ case 32:
+ putULong(buf, val);
+ break;
+ default:
+ warning("Unexpected integer size: %d", size);
+ break;
+ }
+}
+
+/*
+ * date :== NUMBER NUMBER SLASH NUMBER SLASH NUMBER
+ * NUMBER COLON NUMBER COLON NUMBER SEMI
+ *
+ * Dates are always in GMT; first number is day of week; next is
+ * year/month/day; next is hours:minutes:seconds on a 24-hour
+ * clock.
+ */
+time_t
+parse_date(FILE *cfile)
+{
+ static int months[11] = { 31, 59, 90, 120, 151, 181,
+ 212, 243, 273, 304, 334 };
+ int guess, token;
+ struct tm tm;
+ char *val;
+
+ /* Day of week... */
+ token = next_token(&val, cfile);
+ if (token != NUMBER) {
+ parse_warn("numeric day of week expected.");
+ if (token != SEMI)
+ skip_to_semi(cfile);
+ return (0);
+ }
+ tm.tm_wday = atoi(val);
+
+ /* Year... */
+ token = next_token(&val, cfile);
+ if (token != NUMBER) {
+ parse_warn("numeric year expected.");
+ if (token != SEMI)
+ skip_to_semi(cfile);
+ return (0);
+ }
+ tm.tm_year = atoi(val);
+ if (tm.tm_year > 1900)
+ tm.tm_year -= 1900;
+
+ /* Slash separating year from month... */
+ token = next_token(&val, cfile);
+ if (token != SLASH) {
+ parse_warn("expected slash separating year from month.");
+ if (token != SEMI)
+ skip_to_semi(cfile);
+ return (0);
+ }
+
+ /* Month... */
+ token = next_token(&val, cfile);
+ if (token != NUMBER) {
+ parse_warn("numeric month expected.");
+ if (token != SEMI)
+ skip_to_semi(cfile);
+ return (0);
+ }
+ tm.tm_mon = atoi(val) - 1;
+
+ /* Slash separating month from day... */
+ token = next_token(&val, cfile);
+ if (token != SLASH) {
+ parse_warn("expected slash separating month from day.");
+ if (token != SEMI)
+ skip_to_semi(cfile);
+ return (0);
+ }
+
+ /* Month... */
+ token = next_token(&val, cfile);
+ if (token != NUMBER) {
+ parse_warn("numeric day of month expected.");
+ if (token != SEMI)
+ skip_to_semi(cfile);
+ return (0);
+ }
+ tm.tm_mday = atoi(val);
+
+ /* Hour... */
+ token = next_token(&val, cfile);
+ if (token != NUMBER) {
+ parse_warn("numeric hour expected.");
+ if (token != SEMI)
+ skip_to_semi(cfile);
+ return (0);
+ }
+ tm.tm_hour = atoi(val);
+
+ /* Colon separating hour from minute... */
+ token = next_token(&val, cfile);
+ if (token != COLON) {
+ parse_warn("expected colon separating hour from minute.");
+ if (token != SEMI)
+ skip_to_semi(cfile);
+ return (0);
+ }
+
+ /* Minute... */
+ token = next_token(&val, cfile);
+ if (token != NUMBER) {
+ parse_warn("numeric minute expected.");
+ if (token != SEMI)
+ skip_to_semi(cfile);
+ return (0);
+ }
+ tm.tm_min = atoi(val);
+
+ /* Colon separating minute from second... */
+ token = next_token(&val, cfile);
+ if (token != COLON) {
+ parse_warn("expected colon separating hour from minute.");
+ if (token != SEMI)
+ skip_to_semi(cfile);
+ return (0);
+ }
+
+ /* Minute... */
+ token = next_token(&val, cfile);
+ if (token != NUMBER) {
+ parse_warn("numeric minute expected.");
+ if (token != SEMI)
+ skip_to_semi(cfile);
+ return (0);
+ }
+ tm.tm_sec = atoi(val);
+ tm.tm_isdst = 0;
+
+ /* XXX: We assume that mktime does not use tm_yday. */
+ tm.tm_yday = 0;
+
+ /* Make sure the date ends in a semicolon... */
+ token = next_token(&val, cfile);
+ if (token != SEMI) {
+ parse_warn("semicolon expected.");
+ skip_to_semi(cfile);
+ return (0);
+ }
+
+ /* Guess the time value... */
+ guess = ((((((365 * (tm.tm_year - 70) + /* Days in years since '70 */
+ (tm.tm_year - 69) / 4 + /* Leap days since '70 */
+ (tm.tm_mon /* Days in months this year */
+ ? months[tm.tm_mon - 1]
+ : 0) +
+ (tm.tm_mon > 1 && /* Leap day this year */
+ !((tm.tm_year - 72) & 3)) +
+ tm.tm_mday - 1) * 24) + /* Day of month */
+ tm.tm_hour) * 60) +
+ tm.tm_min) * 60) + tm.tm_sec;
+
+ /*
+ * This guess could be wrong because of leap seconds or other
+ * weirdness we don't know about that the system does. For
+ * now, we're just going to accept the guess, but at some point
+ * it might be nice to do a successive approximation here to get
+ * an exact value. Even if the error is small, if the server
+ * is restarted frequently (and thus the lease database is
+ * reread), the error could accumulate into something
+ * significant.
+ */
+ return (guess);
+}
diff --git a/sbin/dhclient/privsep.c b/sbin/dhclient/privsep.c
new file mode 100644
index 0000000..a0521a6
--- /dev/null
+++ b/sbin/dhclient/privsep.c
@@ -0,0 +1,241 @@
+/* $OpenBSD: privsep.c,v 1.7 2004/05/10 18:34:42 deraadt Exp $ */
+
+/*
+ * Copyright (c) 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER IN
+ * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE, ABUSE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include "dhcpd.h"
+#include "privsep.h"
+
+struct buf *
+buf_open(size_t len)
+{
+ struct buf *buf;
+
+ if ((buf = calloc(1, sizeof(struct buf))) == NULL)
+ return (NULL);
+ if ((buf->buf = malloc(len)) == NULL) {
+ free(buf);
+ return (NULL);
+ }
+ buf->size = len;
+
+ return (buf);
+}
+
+int
+buf_add(struct buf *buf, void *data, size_t len)
+{
+ if (buf->wpos + len > buf->size)
+ return (-1);
+
+ memcpy(buf->buf + buf->wpos, data, len);
+ buf->wpos += len;
+ return (0);
+}
+
+int
+buf_close(int sock, struct buf *buf)
+{
+ ssize_t n;
+
+ do {
+ n = write(sock, buf->buf + buf->rpos, buf->size - buf->rpos);
+ if (n != -1)
+ buf->rpos += n;
+ if (n == 0) { /* connection closed */
+ errno = 0;
+ return (-1);
+ }
+ } while (n == -1 && (errno == EAGAIN || errno == EINTR));
+
+ if (buf->rpos < buf->size)
+ error("short write: wanted %lu got %ld bytes",
+ (unsigned long)buf->size, (long)buf->rpos);
+
+ free(buf->buf);
+ free(buf);
+ return (n);
+}
+
+ssize_t
+buf_read(int sock, void *buf, size_t nbytes)
+{
+ ssize_t n, r = 0;
+ char *p = buf;
+
+ do {
+ n = read(sock, p, nbytes);
+ if (n == 0)
+ error("connection closed");
+ if (n != -1) {
+ r += n;
+ p += n;
+ nbytes -= n;
+ }
+ } while (n == -1 && (errno == EINTR || errno == EAGAIN));
+
+ if (n == -1)
+ error("buf_read: %m");
+
+ if (r < nbytes)
+ error("short read: wanted %lu got %ld bytes",
+ (unsigned long)nbytes, (long)r);
+
+ return (r);
+}
+
+void
+dispatch_imsg(struct interface_info *ifi, int fd)
+{
+ struct imsg_hdr hdr;
+ char *medium, *reason, *filename,
+ *servername, *prefix;
+ size_t medium_len, reason_len, filename_len,
+ servername_len, prefix_len, totlen;
+ struct client_lease lease;
+ int ret, i, optlen;
+ struct buf *buf;
+
+ buf_read(fd, &hdr, sizeof(hdr));
+
+ switch (hdr.code) {
+ case IMSG_SCRIPT_INIT:
+ if (hdr.len < sizeof(hdr) + sizeof(size_t))
+ error("corrupted message received");
+ buf_read(fd, &medium_len, sizeof(medium_len));
+ if (hdr.len < medium_len + sizeof(size_t) + sizeof(hdr)
+ + sizeof(size_t) || medium_len == SIZE_T_MAX)
+ error("corrupted message received");
+ if (medium_len > 0) {
+ if ((medium = calloc(1, medium_len + 1)) == NULL)
+ error("%m");
+ buf_read(fd, medium, medium_len);
+ } else
+ medium = NULL;
+
+ buf_read(fd, &reason_len, sizeof(reason_len));
+ if (hdr.len < medium_len + reason_len + sizeof(hdr) ||
+ reason_len == SIZE_T_MAX)
+ error("corrupted message received");
+ if (reason_len > 0) {
+ if ((reason = calloc(1, reason_len + 1)) == NULL)
+ error("%m");
+ buf_read(fd, reason, reason_len);
+ } else
+ reason = NULL;
+
+ priv_script_init(reason, medium);
+ free(reason);
+ free(medium);
+ break;
+ case IMSG_SCRIPT_WRITE_PARAMS:
+ bzero(&lease, sizeof lease);
+ totlen = sizeof(hdr) + sizeof(lease) + sizeof(size_t);
+ if (hdr.len < totlen)
+ error("corrupted message received");
+ buf_read(fd, &lease, sizeof(lease));
+
+ buf_read(fd, &filename_len, sizeof(filename_len));
+ totlen += filename_len + sizeof(size_t);
+ if (hdr.len < totlen || filename_len == SIZE_T_MAX)
+ error("corrupted message received");
+ if (filename_len > 0) {
+ if ((filename = calloc(1, filename_len + 1)) == NULL)
+ error("%m");
+ buf_read(fd, filename, filename_len);
+ } else
+ filename = NULL;
+
+ buf_read(fd, &servername_len, sizeof(servername_len));
+ totlen += servername_len + sizeof(size_t);
+ if (hdr.len < totlen || servername_len == SIZE_T_MAX)
+ error("corrupted message received");
+ if (servername_len > 0) {
+ if ((servername =
+ calloc(1, servername_len + 1)) == NULL)
+ error("%m");
+ buf_read(fd, servername, servername_len);
+ } else
+ servername = NULL;
+
+ buf_read(fd, &prefix_len, sizeof(prefix_len));
+ totlen += prefix_len;
+ if (hdr.len < totlen || prefix_len == SIZE_T_MAX)
+ error("corrupted message received");
+ if (prefix_len > 0) {
+ if ((prefix = calloc(1, prefix_len + 1)) == NULL)
+ error("%m");
+ buf_read(fd, prefix, prefix_len);
+ } else
+ prefix = NULL;
+
+ for (i = 0; i < 256; i++) {
+ totlen += sizeof(optlen);
+ if (hdr.len < totlen)
+ error("corrupted message received");
+ buf_read(fd, &optlen, sizeof(optlen));
+ lease.options[i].data = NULL;
+ lease.options[i].len = optlen;
+ if (optlen > 0) {
+ totlen += optlen;
+ if (hdr.len < totlen || optlen == SIZE_T_MAX)
+ error("corrupted message received");
+ lease.options[i].data =
+ calloc(1, optlen + 1);
+ if (lease.options[i].data == NULL)
+ error("%m");
+ buf_read(fd, lease.options[i].data, optlen);
+ }
+ }
+ lease.server_name = servername;
+ lease.filename = filename;
+
+ priv_script_write_params(prefix, &lease);
+
+ free(servername);
+ free(filename);
+ free(prefix);
+ for (i = 0; i < 256; i++)
+ if (lease.options[i].len > 0)
+ free(lease.options[i].data);
+ break;
+ case IMSG_SCRIPT_GO:
+ if (hdr.len != sizeof(hdr))
+ error("corrupted message received");
+
+ ret = priv_script_go();
+
+ hdr.code = IMSG_SCRIPT_GO_RET;
+ hdr.len = sizeof(struct imsg_hdr) + sizeof(int);
+ if ((buf = buf_open(hdr.len)) == NULL)
+ error("buf_open: %m");
+ if (buf_add(buf, &hdr, sizeof(hdr)))
+ error("buf_add: %m");
+ if (buf_add(buf, &ret, sizeof(ret)))
+ error("buf_add: %m");
+ if (buf_close(fd, buf) == -1)
+ error("buf_close: %m");
+ break;
+ case IMSG_SEND_PACKET:
+ send_packet_priv(ifi, &hdr, fd);
+ break;
+ default:
+ error("received unknown message, code %d", hdr.code);
+ }
+}
diff --git a/sbin/dhclient/privsep.h b/sbin/dhclient/privsep.h
new file mode 100644
index 0000000..d464da4
--- /dev/null
+++ b/sbin/dhclient/privsep.h
@@ -0,0 +1,50 @@
+/* $OpenBSD: privsep.h,v 1.2 2004/05/04 18:51:18 henning Exp $ */
+
+/*
+ * Copyright (c) 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER IN
+ * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE, ABUSE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/types.h>
+
+#include <poll.h>
+#include <pwd.h>
+
+struct buf {
+ u_char *buf;
+ size_t size;
+ size_t wpos;
+ size_t rpos;
+};
+
+enum imsg_code {
+ IMSG_NONE,
+ IMSG_SCRIPT_INIT,
+ IMSG_SCRIPT_WRITE_PARAMS,
+ IMSG_SCRIPT_GO,
+ IMSG_SCRIPT_GO_RET,
+ IMSG_SEND_PACKET
+};
+
+struct imsg_hdr {
+ enum imsg_code code;
+ size_t len;
+};
+
+struct buf *buf_open(size_t);
+int buf_add(struct buf *, void *, size_t);
+int buf_close(int, struct buf *);
+ssize_t buf_read(int sock, void *, size_t);
diff --git a/sbin/dhclient/tables.c b/sbin/dhclient/tables.c
new file mode 100644
index 0000000..c7bac57
--- /dev/null
+++ b/sbin/dhclient/tables.c
@@ -0,0 +1,447 @@
+/* $OpenBSD: tables.c,v 1.4 2004/05/04 20:28:40 deraadt Exp $ */
+
+/* Tables of information... */
+
+/*
+ * Copyright (c) 1995, 1996 The Internet Software Consortium.
+ * 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 Internet Software Consortium 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 INTERNET SOFTWARE CONSORTIUM 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 INTERNET SOFTWARE CONSORTIUM OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * This software has been written for the Internet Software Consortium
+ * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
+ * Enterprises. To learn more about the Internet Software Consortium,
+ * see ``http://www.vix.com/isc''. To learn more about Vixie
+ * Enterprises, see ``http://www.vix.com''.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include "dhcpd.h"
+
+/*
+ * DHCP Option names, formats and codes, from RFC1533.
+ *
+ * Format codes:
+ *
+ * e - end of data
+ * I - IP address
+ * l - 32-bit signed integer
+ * L - 32-bit unsigned integer
+ * s - 16-bit signed integer
+ * S - 16-bit unsigned integer
+ * b - 8-bit signed integer
+ * B - 8-bit unsigned integer
+ * t - ASCII text
+ * f - flag (true or false)
+ * A - array of whatever precedes (e.g., IA means array of IP addresses)
+ */
+
+struct universe dhcp_universe;
+struct option dhcp_options[256] = {
+ { "pad", "", &dhcp_universe, 0 },
+ { "subnet-mask", "I", &dhcp_universe, 1 },
+ { "time-offset", "l", &dhcp_universe, 2 },
+ { "routers", "IA", &dhcp_universe, 3 },
+ { "time-servers", "IA", &dhcp_universe, 4 },
+ { "ien116-name-servers", "IA", &dhcp_universe, 5 },
+ { "domain-name-servers", "IA", &dhcp_universe, 6 },
+ { "log-servers", "IA", &dhcp_universe, 7 },
+ { "cookie-servers", "IA", &dhcp_universe, 8 },
+ { "lpr-servers", "IA", &dhcp_universe, 9 },
+ { "impress-servers", "IA", &dhcp_universe, 10 },
+ { "resource-location-servers", "IA", &dhcp_universe, 11 },
+ { "host-name", "t", &dhcp_universe, 12 },
+ { "boot-size", "S", &dhcp_universe, 13 },
+ { "merit-dump", "t", &dhcp_universe, 14 },
+ { "domain-name", "t", &dhcp_universe, 15 },
+ { "swap-server", "I", &dhcp_universe, 16 },
+ { "root-path", "t", &dhcp_universe, 17 },
+ { "extensions-path", "t", &dhcp_universe, 18 },
+ { "ip-forwarding", "f", &dhcp_universe, 19 },
+ { "non-local-source-routing", "f", &dhcp_universe, 20 },
+ { "policy-filter", "IIA", &dhcp_universe, 21 },
+ { "max-dgram-reassembly", "S", &dhcp_universe, 22 },
+ { "default-ip-ttl", "B", &dhcp_universe, 23 },
+ { "path-mtu-aging-timeout", "L", &dhcp_universe, 24 },
+ { "path-mtu-plateau-table", "SA", &dhcp_universe, 25 },
+ { "interface-mtu", "S", &dhcp_universe, 26 },
+ { "all-subnets-local", "f", &dhcp_universe, 27 },
+ { "broadcast-address", "I", &dhcp_universe, 28 },
+ { "perform-mask-discovery", "f", &dhcp_universe, 29 },
+ { "mask-supplier", "f", &dhcp_universe, 30 },
+ { "router-discovery", "f", &dhcp_universe, 31 },
+ { "router-solicitation-address", "I", &dhcp_universe, 32 },
+ { "static-routes", "IIA", &dhcp_universe, 33 },
+ { "trailer-encapsulation", "f", &dhcp_universe, 34 },
+ { "arp-cache-timeout", "L", &dhcp_universe, 35 },
+ { "ieee802-3-encapsulation", "f", &dhcp_universe, 36 },
+ { "default-tcp-ttl", "B", &dhcp_universe, 37 },
+ { "tcp-keepalive-interval", "L", &dhcp_universe, 38 },
+ { "tcp-keepalive-garbage", "f", &dhcp_universe, 39 },
+ { "nis-domain", "t", &dhcp_universe, 40 },
+ { "nis-servers", "IA", &dhcp_universe, 41 },
+ { "ntp-servers", "IA", &dhcp_universe, 42 },
+ { "vendor-encapsulated-options", "X", &dhcp_universe, 43 },
+ { "netbios-name-servers", "IA", &dhcp_universe, 44 },
+ { "netbios-dd-server", "IA", &dhcp_universe, 45 },
+ { "netbios-node-type", "B", &dhcp_universe, 46 },
+ { "netbios-scope", "t", &dhcp_universe, 47 },
+ { "font-servers", "IA", &dhcp_universe, 48 },
+ { "x-display-manager", "IA", &dhcp_universe, 49 },
+ { "dhcp-requested-address", "I", &dhcp_universe, 50 },
+ { "dhcp-lease-time", "L", &dhcp_universe, 51 },
+ { "dhcp-option-overload", "B", &dhcp_universe, 52 },
+ { "dhcp-message-type", "B", &dhcp_universe, 53 },
+ { "dhcp-server-identifier", "I", &dhcp_universe, 54 },
+ { "dhcp-parameter-request-list", "BA", &dhcp_universe, 55 },
+ { "dhcp-message", "t", &dhcp_universe, 56 },
+ { "dhcp-max-message-size", "S", &dhcp_universe, 57 },
+ { "dhcp-renewal-time", "L", &dhcp_universe, 58 },
+ { "dhcp-rebinding-time", "L", &dhcp_universe, 59 },
+ { "dhcp-class-identifier", "t", &dhcp_universe, 60 },
+ { "dhcp-client-identifier", "X", &dhcp_universe, 61 },
+ { "option-62", "X", &dhcp_universe, 62 },
+ { "option-63", "X", &dhcp_universe, 63 },
+ { "nisplus-domain", "t", &dhcp_universe, 64 },
+ { "nisplus-servers", "IA", &dhcp_universe, 65 },
+ { "tftp-server-name", "t", &dhcp_universe, 66 },
+ { "bootfile-name", "t", &dhcp_universe, 67 },
+ { "mobile-ip-home-agent", "IA", &dhcp_universe, 68 },
+ { "smtp-server", "IA", &dhcp_universe, 69 },
+ { "pop-server", "IA", &dhcp_universe, 70 },
+ { "nntp-server", "IA", &dhcp_universe, 71 },
+ { "www-server", "IA", &dhcp_universe, 72 },
+ { "finger-server", "IA", &dhcp_universe, 73 },
+ { "irc-server", "IA", &dhcp_universe, 74 },
+ { "streettalk-server", "IA", &dhcp_universe, 75 },
+ { "streettalk-directory-assistance-server", "IA", &dhcp_universe, 76 },
+ { "user-class", "t", &dhcp_universe, 77 },
+ { "option-78", "X", &dhcp_universe, 78 },
+ { "option-79", "X", &dhcp_universe, 79 },
+ { "option-80", "X", &dhcp_universe, 80 },
+ { "option-81", "X", &dhcp_universe, 81 },
+ { "option-82", "X", &dhcp_universe, 82 },
+ { "option-83", "X", &dhcp_universe, 83 },
+ { "option-84", "X", &dhcp_universe, 84 },
+ { "nds-servers", "IA", &dhcp_universe, 85 },
+ { "nds-tree-name", "X", &dhcp_universe, 86 },
+ { "nds-context", "X", &dhcp_universe, 87 },
+ { "option-88", "X", &dhcp_universe, 88 },
+ { "option-89", "X", &dhcp_universe, 89 },
+ { "option-90", "X", &dhcp_universe, 90 },
+ { "option-91", "X", &dhcp_universe, 91 },
+ { "option-92", "X", &dhcp_universe, 92 },
+ { "option-93", "X", &dhcp_universe, 93 },
+ { "option-94", "X", &dhcp_universe, 94 },
+ { "option-95", "X", &dhcp_universe, 95 },
+ { "option-96", "X", &dhcp_universe, 96 },
+ { "option-97", "X", &dhcp_universe, 97 },
+ { "option-98", "X", &dhcp_universe, 98 },
+ { "option-99", "X", &dhcp_universe, 99 },
+ { "option-100", "X", &dhcp_universe, 100 },
+ { "option-101", "X", &dhcp_universe, 101 },
+ { "option-102", "X", &dhcp_universe, 102 },
+ { "option-103", "X", &dhcp_universe, 103 },
+ { "option-104", "X", &dhcp_universe, 104 },
+ { "option-105", "X", &dhcp_universe, 105 },
+ { "option-106", "X", &dhcp_universe, 106 },
+ { "option-107", "X", &dhcp_universe, 107 },
+ { "option-108", "X", &dhcp_universe, 108 },
+ { "option-109", "X", &dhcp_universe, 109 },
+ { "option-110", "X", &dhcp_universe, 110 },
+ { "option-111", "X", &dhcp_universe, 111 },
+ { "option-112", "X", &dhcp_universe, 112 },
+ { "option-113", "X", &dhcp_universe, 113 },
+ { "option-114", "X", &dhcp_universe, 114 },
+ { "option-115", "X", &dhcp_universe, 115 },
+ { "option-116", "X", &dhcp_universe, 116 },
+ { "option-117", "X", &dhcp_universe, 117 },
+ { "option-118", "X", &dhcp_universe, 118 },
+ { "domain-search", "t", &dhcp_universe, 119 },
+ { "option-120", "X", &dhcp_universe, 120 },
+ { "classless-routes", "BA", &dhcp_universe, 121 },
+ { "option-122", "X", &dhcp_universe, 122 },
+ { "option-123", "X", &dhcp_universe, 123 },
+ { "option-124", "X", &dhcp_universe, 124 },
+ { "option-125", "X", &dhcp_universe, 125 },
+ { "option-126", "X", &dhcp_universe, 126 },
+ { "option-127", "X", &dhcp_universe, 127 },
+ { "option-128", "X", &dhcp_universe, 128 },
+ { "option-129", "X", &dhcp_universe, 129 },
+ { "option-130", "X", &dhcp_universe, 130 },
+ { "option-131", "X", &dhcp_universe, 131 },
+ { "option-132", "X", &dhcp_universe, 132 },
+ { "option-133", "X", &dhcp_universe, 133 },
+ { "option-134", "X", &dhcp_universe, 134 },
+ { "option-135", "X", &dhcp_universe, 135 },
+ { "option-136", "X", &dhcp_universe, 136 },
+ { "option-137", "X", &dhcp_universe, 137 },
+ { "option-138", "X", &dhcp_universe, 138 },
+ { "option-139", "X", &dhcp_universe, 139 },
+ { "option-140", "X", &dhcp_universe, 140 },
+ { "option-141", "X", &dhcp_universe, 141 },
+ { "option-142", "X", &dhcp_universe, 142 },
+ { "option-143", "X", &dhcp_universe, 143 },
+ { "option-144", "X", &dhcp_universe, 144 },
+ { "option-145", "X", &dhcp_universe, 145 },
+ { "option-146", "X", &dhcp_universe, 146 },
+ { "option-147", "X", &dhcp_universe, 147 },
+ { "option-148", "X", &dhcp_universe, 148 },
+ { "option-149", "X", &dhcp_universe, 149 },
+ { "option-150", "X", &dhcp_universe, 150 },
+ { "option-151", "X", &dhcp_universe, 151 },
+ { "option-152", "X", &dhcp_universe, 152 },
+ { "option-153", "X", &dhcp_universe, 153 },
+ { "option-154", "X", &dhcp_universe, 154 },
+ { "option-155", "X", &dhcp_universe, 155 },
+ { "option-156", "X", &dhcp_universe, 156 },
+ { "option-157", "X", &dhcp_universe, 157 },
+ { "option-158", "X", &dhcp_universe, 158 },
+ { "option-159", "X", &dhcp_universe, 159 },
+ { "option-160", "X", &dhcp_universe, 160 },
+ { "option-161", "X", &dhcp_universe, 161 },
+ { "option-162", "X", &dhcp_universe, 162 },
+ { "option-163", "X", &dhcp_universe, 163 },
+ { "option-164", "X", &dhcp_universe, 164 },
+ { "option-165", "X", &dhcp_universe, 165 },
+ { "option-166", "X", &dhcp_universe, 166 },
+ { "option-167", "X", &dhcp_universe, 167 },
+ { "option-168", "X", &dhcp_universe, 168 },
+ { "option-169", "X", &dhcp_universe, 169 },
+ { "option-170", "X", &dhcp_universe, 170 },
+ { "option-171", "X", &dhcp_universe, 171 },
+ { "option-172", "X", &dhcp_universe, 172 },
+ { "option-173", "X", &dhcp_universe, 173 },
+ { "option-174", "X", &dhcp_universe, 174 },
+ { "option-175", "X", &dhcp_universe, 175 },
+ { "option-176", "X", &dhcp_universe, 176 },
+ { "option-177", "X", &dhcp_universe, 177 },
+ { "option-178", "X", &dhcp_universe, 178 },
+ { "option-179", "X", &dhcp_universe, 179 },
+ { "option-180", "X", &dhcp_universe, 180 },
+ { "option-181", "X", &dhcp_universe, 181 },
+ { "option-182", "X", &dhcp_universe, 182 },
+ { "option-183", "X", &dhcp_universe, 183 },
+ { "option-184", "X", &dhcp_universe, 184 },
+ { "option-185", "X", &dhcp_universe, 185 },
+ { "option-186", "X", &dhcp_universe, 186 },
+ { "option-187", "X", &dhcp_universe, 187 },
+ { "option-188", "X", &dhcp_universe, 188 },
+ { "option-189", "X", &dhcp_universe, 189 },
+ { "option-190", "X", &dhcp_universe, 190 },
+ { "option-191", "X", &dhcp_universe, 191 },
+ { "option-192", "X", &dhcp_universe, 192 },
+ { "option-193", "X", &dhcp_universe, 193 },
+ { "option-194", "X", &dhcp_universe, 194 },
+ { "option-195", "X", &dhcp_universe, 195 },
+ { "option-196", "X", &dhcp_universe, 196 },
+ { "option-197", "X", &dhcp_universe, 197 },
+ { "option-198", "X", &dhcp_universe, 198 },
+ { "option-199", "X", &dhcp_universe, 199 },
+ { "option-200", "X", &dhcp_universe, 200 },
+ { "option-201", "X", &dhcp_universe, 201 },
+ { "option-202", "X", &dhcp_universe, 202 },
+ { "option-203", "X", &dhcp_universe, 203 },
+ { "option-204", "X", &dhcp_universe, 204 },
+ { "option-205", "X", &dhcp_universe, 205 },
+ { "option-206", "X", &dhcp_universe, 206 },
+ { "option-207", "X", &dhcp_universe, 207 },
+ { "option-208", "X", &dhcp_universe, 208 },
+ { "option-209", "X", &dhcp_universe, 209 },
+ { "option-210", "X", &dhcp_universe, 210 },
+ { "option-211", "X", &dhcp_universe, 211 },
+ { "option-212", "X", &dhcp_universe, 212 },
+ { "option-213", "X", &dhcp_universe, 213 },
+ { "option-214", "X", &dhcp_universe, 214 },
+ { "option-215", "X", &dhcp_universe, 215 },
+ { "option-216", "X", &dhcp_universe, 216 },
+ { "option-217", "X", &dhcp_universe, 217 },
+ { "option-218", "X", &dhcp_universe, 218 },
+ { "option-219", "X", &dhcp_universe, 219 },
+ { "option-220", "X", &dhcp_universe, 220 },
+ { "option-221", "X", &dhcp_universe, 221 },
+ { "option-222", "X", &dhcp_universe, 222 },
+ { "option-223", "X", &dhcp_universe, 223 },
+ { "option-224", "X", &dhcp_universe, 224 },
+ { "option-225", "X", &dhcp_universe, 225 },
+ { "option-226", "X", &dhcp_universe, 226 },
+ { "option-227", "X", &dhcp_universe, 227 },
+ { "option-228", "X", &dhcp_universe, 228 },
+ { "option-229", "X", &dhcp_universe, 229 },
+ { "option-230", "X", &dhcp_universe, 230 },
+ { "option-231", "X", &dhcp_universe, 231 },
+ { "option-232", "X", &dhcp_universe, 232 },
+ { "option-233", "X", &dhcp_universe, 233 },
+ { "option-234", "X", &dhcp_universe, 234 },
+ { "option-235", "X", &dhcp_universe, 235 },
+ { "option-236", "X", &dhcp_universe, 236 },
+ { "option-237", "X", &dhcp_universe, 237 },
+ { "option-238", "X", &dhcp_universe, 238 },
+ { "option-239", "X", &dhcp_universe, 239 },
+ { "option-240", "X", &dhcp_universe, 240 },
+ { "option-241", "X", &dhcp_universe, 241 },
+ { "option-242", "X", &dhcp_universe, 242 },
+ { "option-243", "X", &dhcp_universe, 243 },
+ { "option-244", "X", &dhcp_universe, 244 },
+ { "option-245", "X", &dhcp_universe, 245 },
+ { "option-246", "X", &dhcp_universe, 246 },
+ { "option-247", "X", &dhcp_universe, 247 },
+ { "option-248", "X", &dhcp_universe, 248 },
+ { "option-249", "X", &dhcp_universe, 249 },
+ { "option-250", "X", &dhcp_universe, 250 },
+ { "option-251", "X", &dhcp_universe, 251 },
+ { "option-252", "X", &dhcp_universe, 252 },
+ { "option-253", "X", &dhcp_universe, 253 },
+ { "option-254", "X", &dhcp_universe, 254 },
+ { "option-end", "e", &dhcp_universe, 255 },
+};
+
+/*
+ * Default dhcp option priority list (this is ad hoc and should not be
+ * mistaken for a carefully crafted and optimized list).
+ */
+unsigned char dhcp_option_default_priority_list[] = {
+ DHO_DHCP_REQUESTED_ADDRESS,
+ DHO_DHCP_OPTION_OVERLOAD,
+ DHO_DHCP_MAX_MESSAGE_SIZE,
+ DHO_DHCP_RENEWAL_TIME,
+ DHO_DHCP_REBINDING_TIME,
+ DHO_DHCP_CLASS_IDENTIFIER,
+ DHO_DHCP_CLIENT_IDENTIFIER,
+ DHO_SUBNET_MASK,
+ DHO_TIME_OFFSET,
+ DHO_CLASSLESS_ROUTES,
+ DHO_ROUTERS,
+ DHO_TIME_SERVERS,
+ DHO_NAME_SERVERS,
+ DHO_DOMAIN_NAME_SERVERS,
+ DHO_HOST_NAME,
+ DHO_LOG_SERVERS,
+ DHO_COOKIE_SERVERS,
+ DHO_LPR_SERVERS,
+ DHO_IMPRESS_SERVERS,
+ DHO_RESOURCE_LOCATION_SERVERS,
+ DHO_HOST_NAME,
+ DHO_BOOT_SIZE,
+ DHO_MERIT_DUMP,
+ DHO_DOMAIN_NAME,
+ DHO_SWAP_SERVER,
+ DHO_ROOT_PATH,
+ DHO_EXTENSIONS_PATH,
+ DHO_IP_FORWARDING,
+ DHO_NON_LOCAL_SOURCE_ROUTING,
+ DHO_POLICY_FILTER,
+ DHO_MAX_DGRAM_REASSEMBLY,
+ DHO_DEFAULT_IP_TTL,
+ DHO_PATH_MTU_AGING_TIMEOUT,
+ DHO_PATH_MTU_PLATEAU_TABLE,
+ DHO_INTERFACE_MTU,
+ DHO_ALL_SUBNETS_LOCAL,
+ DHO_BROADCAST_ADDRESS,
+ DHO_PERFORM_MASK_DISCOVERY,
+ DHO_MASK_SUPPLIER,
+ DHO_ROUTER_DISCOVERY,
+ DHO_ROUTER_SOLICITATION_ADDRESS,
+ DHO_STATIC_ROUTES,
+ DHO_TRAILER_ENCAPSULATION,
+ DHO_ARP_CACHE_TIMEOUT,
+ DHO_IEEE802_3_ENCAPSULATION,
+ DHO_DEFAULT_TCP_TTL,
+ DHO_TCP_KEEPALIVE_INTERVAL,
+ DHO_TCP_KEEPALIVE_GARBAGE,
+ DHO_NIS_DOMAIN,
+ DHO_NIS_SERVERS,
+ DHO_NTP_SERVERS,
+ DHO_VENDOR_ENCAPSULATED_OPTIONS,
+ DHO_NETBIOS_NAME_SERVERS,
+ DHO_NETBIOS_DD_SERVER,
+ DHO_NETBIOS_NODE_TYPE,
+ DHO_NETBIOS_SCOPE,
+ DHO_FONT_SERVERS,
+ DHO_X_DISPLAY_MANAGER,
+ DHO_DHCP_PARAMETER_REQUEST_LIST,
+ DHO_NISPLUS_DOMAIN,
+ DHO_NISPLUS_SERVERS,
+ DHO_TFTP_SERVER_NAME,
+ DHO_BOOTFILE_NAME,
+ DHO_MOBILE_IP_HOME_AGENT,
+ DHO_SMTP_SERVER,
+ DHO_POP_SERVER,
+ DHO_NNTP_SERVER,
+ DHO_WWW_SERVER,
+ DHO_FINGER_SERVER,
+ DHO_IRC_SERVER,
+ DHO_STREETTALK_SERVER,
+ DHO_STREETTALK_DA_SERVER,
+ DHO_DOMAIN_SEARCH,
+
+ /* Presently-undefined options... */
+ 62, 63, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91,
+ 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105,
+ 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117,
+ 118, 120, 122, 123, 124, 125, 126, 127, 128, 129, 130,
+ 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142,
+ 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154,
+ 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166,
+ 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178,
+ 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190,
+ 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202,
+ 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214,
+ 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226,
+ 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238,
+ 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250,
+ 251, 252, 253, 254,
+};
+
+int sizeof_dhcp_option_default_priority_list =
+ sizeof(dhcp_option_default_priority_list);
+
+struct hash_table universe_hash;
+
+void
+initialize_universes(void)
+{
+ int i;
+
+ dhcp_universe.name = "dhcp";
+ dhcp_universe.hash = new_hash();
+ if (!dhcp_universe.hash)
+ error("Can't allocate dhcp option hash table.");
+ for (i = 0; i < 256; i++) {
+ dhcp_universe.options[i] = &dhcp_options[i];
+ add_hash(dhcp_universe.hash,
+ (unsigned char *)dhcp_options[i].name, 0,
+ (unsigned char *)&dhcp_options[i]);
+ }
+ universe_hash.hash_count = DEFAULT_HASH_SIZE;
+ add_hash(&universe_hash,
+ (unsigned char *)dhcp_universe.name, 0,
+ (unsigned char *)&dhcp_universe);
+}
diff --git a/sbin/dhclient/tests/Makefile b/sbin/dhclient/tests/Makefile
new file mode 100644
index 0000000..a460f7f
--- /dev/null
+++ b/sbin/dhclient/tests/Makefile
@@ -0,0 +1,16 @@
+# $FreeBSD$
+
+TESTSDIR= ${TESTSBASE}/sbin/dhclient
+
+.PATH: ${.CURDIR}/..
+
+PLAIN_TESTS_C= option-domain-search_test
+SRCS.option-domain-search_test= alloc.c convert.c hash.c options.c \
+ tables.c fake.c option-domain-search.c
+CFLAGS.option-domain-search_test+= -I${.CURDIR}/..
+DPADD.option-domain-search_test= ${LIBUTIL}
+LDADD.option-domain-search_test= -lutil
+
+WARNS?= 2
+
+.include <bsd.test.mk>
diff --git a/sbin/dhclient/tests/fake.c b/sbin/dhclient/tests/fake.c
new file mode 100644
index 0000000..c204d49
--- /dev/null
+++ b/sbin/dhclient/tests/fake.c
@@ -0,0 +1,64 @@
+/* $FreeBSD$ */
+
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stdio.h>
+
+#include "dhcpd.h"
+
+extern jmp_buf env;
+
+void
+error(char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ fprintf(stderr, "\n");
+
+ longjmp(env, 1);
+}
+
+int
+warning(char *fmt, ...)
+{
+ int ret;
+ va_list ap;
+
+ va_start(ap, fmt);
+ ret = vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ fprintf(stderr, "\n");
+
+ /*
+ * The original warning() would return "ret" here. We do this to
+ * check warnings explicitely.
+ */
+ longjmp(env, 1);
+}
+
+int
+note(char *fmt, ...)
+{
+ int ret;
+ va_list ap;
+
+ va_start(ap, fmt);
+ ret = vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ fprintf(stderr, "\n");
+
+ return ret;
+}
+
+void
+bootp(struct packet *packet)
+{
+}
+
+void
+dhcp(struct packet *packet)
+{
+}
diff --git a/sbin/dhclient/tests/option-domain-search.c b/sbin/dhclient/tests/option-domain-search.c
new file mode 100644
index 0000000..b79f9a5
--- /dev/null
+++ b/sbin/dhclient/tests/option-domain-search.c
@@ -0,0 +1,328 @@
+/* $FreeBSD$ */
+
+#include <setjmp.h>
+#include <stdlib.h>
+
+#include "dhcpd.h"
+
+jmp_buf env;
+
+void expand_domain_search(struct packet *packet);
+
+void
+no_option_present()
+{
+ int ret;
+ struct option_data option;
+ struct packet p;
+
+ option.data = NULL;
+ option.len = 0;
+ p.options[DHO_DOMAIN_SEARCH] = option;
+
+ ret = setjmp(env);
+ if (ret == 0)
+ expand_domain_search(&p);
+
+ if (p.options[DHO_DOMAIN_SEARCH].len != 0 ||
+ p.options[DHO_DOMAIN_SEARCH].data != NULL)
+ abort();
+}
+
+void
+one_domain_valid()
+{
+ int ret;
+ struct packet p;
+ struct option_data *option;
+
+ char *data = "\007example\003org\0";
+ char *expected = "example.org.";
+
+ option = &p.options[DHO_DOMAIN_SEARCH];
+ option->len = 13;
+ option->data = malloc(option->len);
+ memcpy(option->data, data, option->len);
+
+ ret = setjmp(env);
+ if (ret == 0)
+ expand_domain_search(&p);
+
+ if (option->len != strlen(expected) ||
+ strcmp(option->data, expected) != 0)
+ abort();
+
+ free(option->data);
+}
+
+void
+one_domain_truncated1()
+{
+ int ret;
+ struct option_data *option;
+ struct packet p;
+
+ char *data = "\007example\003org";
+
+ option = &p.options[DHO_DOMAIN_SEARCH];
+ option->len = 12;
+ option->data = malloc(option->len);
+ memcpy(option->data, data, option->len);
+
+ ret = setjmp(env);
+ if (ret == 0)
+ expand_domain_search(&p);
+
+ if (ret != 1)
+ abort();
+
+ free(option->data);
+}
+
+void
+one_domain_truncated2()
+{
+ int ret;
+ struct option_data *option;
+ struct packet p;
+
+ char *data = "\007ex";
+
+ option = &p.options[DHO_DOMAIN_SEARCH];
+ option->len = 3;
+ option->data = malloc(option->len);
+ memcpy(option->data, data, option->len);
+
+ ret = setjmp(env);
+ if (ret == 0)
+ expand_domain_search(&p);
+
+ if (ret != 1)
+ abort();
+
+ free(option->data);
+}
+
+void
+two_domains_valid()
+{
+ int ret;
+ struct packet p;
+ struct option_data *option;
+
+ char *data = "\007example\003org\0\007example\003com\0";
+ char *expected = "example.org. example.com.";
+
+ option = &p.options[DHO_DOMAIN_SEARCH];
+ option->len = 26;
+ option->data = malloc(option->len);
+ memcpy(option->data, data, option->len);
+
+ ret = setjmp(env);
+ if (ret == 0)
+ expand_domain_search(&p);
+
+ if (option->len != strlen(expected) ||
+ strcmp(option->data, expected) != 0)
+ abort();
+
+ free(option->data);
+}
+
+void
+two_domains_truncated1()
+{
+ int ret;
+ struct option_data *option;
+ struct packet p;
+
+ char *data = "\007example\003org\0\007example\003com";
+
+ option = &p.options[DHO_DOMAIN_SEARCH];
+ option->len = 25;
+ option->data = malloc(option->len);
+ memcpy(option->data, data, option->len);
+
+ ret = setjmp(env);
+ if (ret == 0)
+ expand_domain_search(&p);
+
+ if (ret != 1)
+ abort();
+
+ free(option->data);
+}
+
+void
+two_domains_truncated2()
+{
+ int ret;
+ struct option_data *option;
+ struct packet p;
+
+ char *data = "\007example\003org\0\007ex";
+
+ option = &p.options[DHO_DOMAIN_SEARCH];
+ option->len = 16;
+ option->data = malloc(option->len);
+ memcpy(option->data, data, option->len);
+
+ ret = setjmp(env);
+ if (ret == 0)
+ expand_domain_search(&p);
+
+ if (ret != 1)
+ abort();
+
+ free(option->data);
+}
+
+void
+two_domains_compressed()
+{
+ int ret;
+ struct packet p;
+ struct option_data *option;
+
+ char *data = "\007example\003org\0\006foobar\xc0\x08";
+ char *expected = "example.org. foobar.org.";
+
+ option = &p.options[DHO_DOMAIN_SEARCH];
+ option->len = 22;
+ option->data = malloc(option->len);
+ memcpy(option->data, data, option->len);
+
+ ret = setjmp(env);
+ if (ret == 0)
+ expand_domain_search(&p);
+
+ if (option->len != strlen(expected) ||
+ strcmp(option->data, expected) != 0)
+ abort();
+
+ free(option->data);
+}
+
+void
+two_domains_infloop()
+{
+ int ret;
+ struct packet p;
+ struct option_data *option;
+
+ char *data = "\007example\003org\0\006foobar\xc0\x0d";
+
+ option = &p.options[DHO_DOMAIN_SEARCH];
+ option->len = 22;
+ option->data = malloc(option->len);
+ memcpy(option->data, data, option->len);
+
+ ret = setjmp(env);
+ if (ret == 0)
+ expand_domain_search(&p);
+
+ if (ret != 1)
+ abort();
+
+ free(option->data);
+}
+
+void
+two_domains_forwardptr()
+{
+ int ret;
+ struct packet p;
+ struct option_data *option;
+
+ char *data = "\007example\003org\xc0\x0d\006foobar\0";
+
+ option = &p.options[DHO_DOMAIN_SEARCH];
+ option->len = 22;
+ option->data = malloc(option->len);
+ memcpy(option->data, data, option->len);
+
+ ret = setjmp(env);
+ if (ret == 0)
+ expand_domain_search(&p);
+
+ if (ret != 1)
+ abort();
+
+ free(option->data);
+}
+
+void
+two_domains_truncatedptr()
+{
+ int ret;
+ struct packet p;
+ struct option_data *option;
+
+ char *data = "\007example\003org\0\006foobar\xc0";
+
+ option = &p.options[DHO_DOMAIN_SEARCH];
+ option->len = 21;
+ option->data = malloc(option->len);
+ memcpy(option->data, data, option->len);
+
+ ret = setjmp(env);
+ if (ret == 0)
+ expand_domain_search(&p);
+
+ if (ret != 1)
+ abort();
+
+ free(option->data);
+}
+
+void
+multiple_domains_valid()
+{
+ int ret;
+ struct packet p;
+ struct option_data *option;
+
+ char *data =
+ "\007example\003org\0\002cl\006foobar\003com\0\002fr\xc0\x10";
+
+ char *expected = "example.org. cl.foobar.com. fr.foobar.com.";
+
+ option = &p.options[DHO_DOMAIN_SEARCH];
+ option->len = 33;
+ option->data = malloc(option->len);
+ memcpy(option->data, data, option->len);
+
+ ret = setjmp(env);
+ if (ret == 0)
+ expand_domain_search(&p);
+
+ if (option->len != strlen(expected) ||
+ strcmp(option->data, expected) != 0)
+ abort();
+
+ free(option->data);
+}
+
+int
+main(int argc, char *argv[])
+{
+
+ no_option_present();
+
+ one_domain_valid();
+ one_domain_truncated1();
+ one_domain_truncated2();
+
+ two_domains_valid();
+ two_domains_truncated1();
+ two_domains_truncated2();
+
+ two_domains_compressed();
+ two_domains_infloop();
+ two_domains_forwardptr();
+ two_domains_truncatedptr();
+
+ multiple_domains_valid();
+
+ return (0);
+}
diff --git a/sbin/dhclient/tree.c b/sbin/dhclient/tree.c
new file mode 100644
index 0000000..0ed2919
--- /dev/null
+++ b/sbin/dhclient/tree.c
@@ -0,0 +1,59 @@
+/* $OpenBSD: tree.c,v 1.13 2004/05/06 22:29:15 deraadt Exp $ */
+
+/* Routines for manipulating parse trees... */
+
+/*
+ * Copyright (c) 1995, 1996, 1997 The Internet Software Consortium.
+ * 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 Internet Software Consortium 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 INTERNET SOFTWARE CONSORTIUM 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 INTERNET SOFTWARE CONSORTIUM OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * This software has been written for the Internet Software Consortium
+ * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
+ * Enterprises. To learn more about the Internet Software Consortium,
+ * see ``http://www.vix.com/isc''. To learn more about Vixie
+ * Enterprises, see ``http://www.vix.com''.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include "dhcpd.h"
+
+extern int h_errno;
+
+pair
+cons(caddr_t car, pair cdr)
+{
+ pair foo = calloc(1, sizeof(*foo));
+ if (!foo)
+ error("no memory for cons.");
+ foo->car = car;
+ foo->cdr = cdr;
+ return (foo);
+}
diff --git a/sbin/dhclient/tree.h b/sbin/dhclient/tree.h
new file mode 100644
index 0000000..04e08e7
--- /dev/null
+++ b/sbin/dhclient/tree.h
@@ -0,0 +1,66 @@
+/* $OpenBSD: tree.h,v 1.5 2004/05/06 22:29:15 deraadt Exp $ */
+
+/* Definitions for address trees... */
+
+/*
+ * Copyright (c) 1995 The Internet Software Consortium. 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 Internet Software Consortium 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 INTERNET SOFTWARE CONSORTIUM 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 INTERNET SOFTWARE CONSORTIUM OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * This software has been written for the Internet Software Consortium
+ * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
+ * Enterprises. To learn more about the Internet Software Consortium,
+ * see ``http://www.vix.com/isc''. To learn more about Vixie
+ * Enterprises, see ``http://www.vix.com''.
+ */
+
+/* A pair of pointers, suitable for making a linked list. */
+typedef struct _pair {
+ caddr_t car;
+ struct _pair *cdr;
+} *pair;
+
+struct tree_cache {
+ unsigned char *value;
+ int len;
+ int buf_size;
+ time_t timeout;
+};
+
+struct universe {
+ char *name;
+ struct hash_table *hash;
+ struct option *options[256];
+};
+
+struct option {
+ char *name;
+ char *format;
+ struct universe *universe;
+ unsigned char code;
+};
diff --git a/sbin/dmesg/Makefile b/sbin/dmesg/Makefile
new file mode 100644
index 0000000..64a0155
--- /dev/null
+++ b/sbin/dmesg/Makefile
@@ -0,0 +1,9 @@
+# @(#)Makefile 8.1 (Berkeley) 6/5/93
+# $FreeBSD$
+
+PROG= dmesg
+MAN= dmesg.8
+
+LIBADD= kvm
+
+.include <bsd.prog.mk>
diff --git a/sbin/dmesg/dmesg.8 b/sbin/dmesg/dmesg.8
new file mode 100644
index 0000000..35446fb
--- /dev/null
+++ b/sbin/dmesg/dmesg.8
@@ -0,0 +1,87 @@
+.\" Copyright (c) 1980, 1991, 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.
+.\" 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.
+.\"
+.\" @(#)dmesg.8 8.1 (Berkeley) 6/5/93
+.\" $FreeBSD$
+.\"
+.Dd May 9, 2013
+.Dt DMESG 8
+.Os
+.Sh NAME
+.Nm dmesg
+.Nd "display the system message buffer"
+.Sh SYNOPSIS
+.Nm
+.Op Fl ac
+.Op Fl M Ar core Op Fl N Ar system
+.Sh DESCRIPTION
+The
+.Nm
+utility displays the contents of the system message buffer.
+If the
+.Fl M
+option is not specified, the buffer is read from the currently running kernel
+via the
+.Xr sysctl 3
+interface.
+Otherwise, the buffer is read from the specified core file,
+using the name list from the specified kernel image (or from
+the default image).
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl a
+Show all data in the message buffer.
+This includes any syslog records and
+.Pa /dev/console
+output.
+.It Fl c
+Clear the kernel buffer after printing.
+.It Fl M
+Extract values associated with the name list from the specified core.
+.It Fl N
+If
+.Fl M
+is also specified,
+extract the name list from the specified system instead of the default,
+which is the kernel image the system has booted from.
+.El
+.Sh FILES
+.Bl -tag -width ".Pa /var/run/dmesg.boot" -compact
+.It Pa /var/run/dmesg.boot
+usually a snapshot of the buffer contents
+taken soon after file systems are mounted
+at startup time
+.El
+.Sh SEE ALSO
+.Xr sysctl 3 ,
+.Xr syslogd 8
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Bx 4.0 .
diff --git a/sbin/dmesg/dmesg.c b/sbin/dmesg/dmesg.c
new file mode 100644
index 0000000..827ed8e
--- /dev/null
+++ b/sbin/dmesg/dmesg.c
@@ -0,0 +1,217 @@
+/*-
+ * Copyright (c) 1991, 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.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if 0
+#ifndef lint
+static const char copyright[] =
+"@(#) Copyright (c) 1991, 1993\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static const char sccsid[] = "@(#)dmesg.c 8.1 (Berkeley) 6/5/93";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/msgbuf.h>
+#include <sys/sysctl.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <kvm.h>
+#include <limits.h>
+#include <locale.h>
+#include <nlist.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <vis.h>
+#include <sys/syslog.h>
+
+static struct nlist nl[] = {
+#define X_MSGBUF 0
+ { "_msgbufp", 0, 0, 0, 0 },
+ { NULL, 0, 0, 0, 0 },
+};
+
+void usage(void) __dead2;
+
+#define KREAD(addr, var) \
+ kvm_read(kd, addr, &var, sizeof(var)) != sizeof(var)
+
+int
+main(int argc, char *argv[])
+{
+ struct msgbuf *bufp, cur;
+ char *bp, *ep, *memf, *nextp, *nlistf, *p, *q, *visbp;
+ kvm_t *kd;
+ size_t buflen, bufpos;
+ long pri;
+ int ch, clear;
+ bool all;
+
+ all = false;
+ clear = false;
+ (void) setlocale(LC_CTYPE, "");
+ memf = nlistf = NULL;
+ while ((ch = getopt(argc, argv, "acM:N:")) != -1)
+ switch(ch) {
+ case 'a':
+ all = true;
+ break;
+ case 'c':
+ clear = true;
+ break;
+ case 'M':
+ memf = optarg;
+ break;
+ case 'N':
+ nlistf = optarg;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ if (argc != 0)
+ usage();
+
+ if (memf == NULL) {
+ /*
+ * Running kernel. Use sysctl. This gives an unwrapped buffer
+ * as a side effect. Remove nulterm (if present) so the value
+ * returned by sysctl is formatted as the rest of the code
+ * expects (the same as the value read from a core file below).
+ */
+ if (sysctlbyname("kern.msgbuf", NULL, &buflen, NULL, 0) == -1)
+ err(1, "sysctl kern.msgbuf");
+ /* Allocate extra room for growth between the sysctl calls. */
+ buflen += buflen/8;
+ /* Allocate more than sysctl sees, for room to append \n\0. */
+ if ((bp = malloc(buflen + 2)) == NULL)
+ errx(1, "malloc failed");
+ if (sysctlbyname("kern.msgbuf", bp, &buflen, NULL, 0) == -1)
+ err(1, "sysctl kern.msgbuf");
+ if (buflen > 0 && bp[buflen - 1] == '\0')
+ buflen--;
+ if (clear)
+ if (sysctlbyname("kern.msgbuf_clear", NULL, NULL, &clear, sizeof(int)))
+ err(1, "sysctl kern.msgbuf_clear");
+ } else {
+ /* Read in kernel message buffer and do sanity checks. */
+ kd = kvm_open(nlistf, memf, NULL, O_RDONLY, "dmesg");
+ if (kd == NULL)
+ exit (1);
+ if (kvm_nlist(kd, nl) == -1)
+ errx(1, "kvm_nlist: %s", kvm_geterr(kd));
+ if (nl[X_MSGBUF].n_type == 0)
+ errx(1, "%s: msgbufp not found",
+ nlistf ? nlistf : "namelist");
+ if (KREAD(nl[X_MSGBUF].n_value, bufp) || KREAD((long)bufp, cur))
+ errx(1, "kvm_read: %s", kvm_geterr(kd));
+ if (cur.msg_magic != MSG_MAGIC)
+ errx(1, "kernel message buffer has different magic "
+ "number");
+ if ((bp = malloc(cur.msg_size + 2)) == NULL)
+ errx(1, "malloc failed");
+
+ /* Unwrap the circular buffer to start from the oldest data. */
+ bufpos = MSGBUF_SEQ_TO_POS(&cur, cur.msg_wseq);
+ if (kvm_read(kd, (long)&cur.msg_ptr[bufpos], bp,
+ cur.msg_size - bufpos) != (ssize_t)(cur.msg_size - bufpos))
+ errx(1, "kvm_read: %s", kvm_geterr(kd));
+ if (bufpos != 0 && kvm_read(kd, (long)cur.msg_ptr,
+ &bp[cur.msg_size - bufpos], bufpos) != (ssize_t)bufpos)
+ errx(1, "kvm_read: %s", kvm_geterr(kd));
+ kvm_close(kd);
+ buflen = cur.msg_size;
+ }
+
+ /*
+ * Ensure that the buffer ends with a newline and a \0 to avoid
+ * complications below. We left space above.
+ */
+ if (buflen == 0 || bp[buflen - 1] != '\n')
+ bp[buflen++] = '\n';
+ bp[buflen] = '\0';
+
+ if ((visbp = malloc(4 * buflen + 1)) == NULL)
+ errx(1, "malloc failed");
+
+ /*
+ * The message buffer is circular, but has been unwrapped so that
+ * the oldest data comes first. The data will be preceded by \0's
+ * if the message buffer was not full.
+ */
+ p = bp;
+ ep = &bp[buflen];
+ if (*p == '\0') {
+ /* Strip leading \0's */
+ while (*p == '\0')
+ p++;
+ } else if (!all) {
+ /* Skip the first line, since it is probably incomplete. */
+ p = memchr(p, '\n', ep - p);
+ p++;
+ }
+ for (; p < ep; p = nextp) {
+ nextp = memchr(p, '\n', ep - p);
+ nextp++;
+
+ /* Skip ^<[0-9]+> syslog sequences. */
+ if (*p == '<' && isdigit(*(p+1))) {
+ errno = 0;
+ pri = strtol(p + 1, &q, 10);
+ if (*q == '>' && pri >= 0 && pri < INT_MAX &&
+ errno == 0) {
+ if (LOG_FAC(pri) != LOG_KERN && !all)
+ continue;
+ p = q + 1;
+ }
+ }
+
+ (void)strvisx(visbp, p, nextp - p, 0);
+ (void)printf("%s", visbp);
+ }
+ exit(0);
+}
+
+void
+usage(void)
+{
+ fprintf(stderr, "usage: dmesg [-ac] [-M core [-N system]]\n");
+ exit(1);
+}
diff --git a/sbin/dump/Makefile b/sbin/dump/Makefile
new file mode 100644
index 0000000..e3d9aef
--- /dev/null
+++ b/sbin/dump/Makefile
@@ -0,0 +1,24 @@
+# @(#)Makefile 8.1 (Berkeley) 6/5/93
+# $FreeBSD$
+
+# dump.h header file
+# itime.c reads /etc/dumpdates
+# main.c driver
+# optr.c operator interface
+# dumprmt.c handles remote tape via rmt(8)
+# tape.c handles the mag tape and opening/closing
+# traverse.c traverses the file system
+# unctime.c undo ctime
+#
+# DEBUG use local directory to find ddate and dumpdates
+# TDEBUG trace out the process forking
+
+PROG= dump
+LINKS= ${BINDIR}/dump ${BINDIR}/rdump
+CFLAGS+=-DRDUMP
+SRCS= itime.c main.c optr.c dumprmt.c tape.c traverse.c unctime.c cache.c
+MAN= dump.8
+MLINKS= dump.8 rdump.8
+WARNS?= 2
+
+.include <bsd.prog.mk>
diff --git a/sbin/dump/cache.c b/sbin/dump/cache.c
new file mode 100644
index 0000000..906ac27
--- /dev/null
+++ b/sbin/dump/cache.c
@@ -0,0 +1,146 @@
+/*
+ * CACHE.C
+ *
+ * Block cache for dump
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+
+#ifdef sunos
+#include <sys/vnode.h>
+
+#include <ufs/fs.h>
+#include <ufs/fsdir.h>
+#include <ufs/inode.h>
+#else
+#include <ufs/ufs/dir.h>
+#include <ufs/ufs/dinode.h>
+#include <ufs/ffs/fs.h>
+#endif
+
+#include <protocols/dumprestore.h>
+
+#include <ctype.h>
+#include <stdio.h>
+#ifdef __STDC__
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#endif
+#include "dump.h"
+
+typedef struct Block {
+ struct Block *b_HNext; /* must be first field */
+ off_t b_Offset;
+ char *b_Data;
+} Block;
+
+#define HFACTOR 4
+#define BLKFACTOR 4
+
+static char *DataBase;
+static Block **BlockHash;
+static int BlockSize;
+static int HSize;
+static int NBlocks;
+
+static void
+cinit(void)
+{
+ int i;
+ int hi;
+ Block *base;
+
+ if ((BlockSize = sblock->fs_bsize * BLKFACTOR) > MAXBSIZE)
+ BlockSize = MAXBSIZE;
+ NBlocks = cachesize / BlockSize;
+ HSize = NBlocks / HFACTOR;
+
+ msg("Cache %d MB, blocksize = %d\n",
+ NBlocks * BlockSize / (1024 * 1024), BlockSize);
+
+ base = calloc(sizeof(Block), NBlocks);
+ BlockHash = calloc(sizeof(Block *), HSize);
+ DataBase = mmap(NULL, NBlocks * BlockSize,
+ PROT_READ|PROT_WRITE, MAP_ANON, -1, 0);
+ for (i = 0; i < NBlocks; ++i) {
+ base[i].b_Data = DataBase + i * BlockSize;
+ base[i].b_Offset = (off_t)-1;
+ hi = i / HFACTOR;
+ base[i].b_HNext = BlockHash[hi];
+ BlockHash[hi] = &base[i];
+ }
+}
+
+ssize_t
+cread(int fd, void *buf, size_t nbytes, off_t offset)
+{
+ Block *blk;
+ Block **pblk;
+ Block **ppblk;
+ int hi;
+ int n;
+ off_t mask;
+
+ /*
+ * If the cache is disabled, or we do not yet know the filesystem
+ * block size, then revert to pread. Otherwise initialize the
+ * cache as necessary and continue.
+ */
+ if (cachesize <= 0 || sblock->fs_bsize == 0)
+ return(pread(fd, buf, nbytes, offset));
+ if (DataBase == NULL)
+ cinit();
+
+ /*
+ * If the request crosses a cache block boundary, or the
+ * request is larger or equal to the cache block size,
+ * revert to pread(). Full-block-reads are typically
+ * one-time calls and caching would be detrimental.
+ */
+ mask = ~(off_t)(BlockSize - 1);
+ if (nbytes >= BlockSize ||
+ ((offset ^ (offset + nbytes - 1)) & mask) != 0) {
+ return(pread(fd, buf, nbytes, offset));
+ }
+
+ /*
+ * Obtain and access the cache block. Cache a successful
+ * result. If an error occurs, revert to pread() (this might
+ * occur near the end of the media).
+ */
+ hi = (offset / BlockSize) % HSize;
+ pblk = &BlockHash[hi];
+ ppblk = NULL;
+ while ((blk = *pblk) != NULL) {
+ if (((blk->b_Offset ^ offset) & mask) == 0)
+ break;
+ ppblk = pblk;
+ pblk = &blk->b_HNext;
+ }
+ if (blk == NULL) {
+ blk = *ppblk;
+ pblk = ppblk;
+ blk->b_Offset = offset & mask;
+ n = pread(fd, blk->b_Data, BlockSize, blk->b_Offset);
+ if (n != BlockSize) {
+ blk->b_Offset = (off_t)-1;
+ blk = NULL;
+ }
+ }
+ if (blk) {
+ bcopy(blk->b_Data + (offset - blk->b_Offset), buf, nbytes);
+ *pblk = blk->b_HNext;
+ blk->b_HNext = BlockHash[hi];
+ BlockHash[hi] = blk;
+ return(nbytes);
+ } else {
+ return(pread(fd, buf, nbytes, offset));
+ }
+}
+
diff --git a/sbin/dump/dump.8 b/sbin/dump/dump.8
new file mode 100644
index 0000000..c5726f2
--- /dev/null
+++ b/sbin/dump/dump.8
@@ -0,0 +1,568 @@
+.\" Copyright (c) 1980, 1991, 1993
+.\" 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.
+.\" 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.
+.\"
+.\" @(#)dump.8 8.3 (Berkeley) 5/1/95
+.\" $FreeBSD$
+.\"
+.Dd February 24, 2006
+.Dt DUMP 8
+.Os
+.Sh NAME
+.Nm dump ,
+.Nm rdump
+.Nd file system backup
+.Sh SYNOPSIS
+.Nm
+.Op Fl 0123456789acLnrRSu
+.Op Fl B Ar records
+.Op Fl b Ar blocksize
+.Op Fl C Ar cachesize
+.Op Fl D Ar dumpdates
+.Op Fl d Ar density
+.Op Fl f Ar file | Fl P Ar pipecommand
+.Op Fl h Ar level
+.Op Fl s Ar feet
+.Op Fl T Ar date
+.Ar filesystem
+.Nm
+.Fl W | Fl w
+.Sh DESCRIPTION
+The
+.Nm
+utility examines files
+on a file system
+and determines which files
+need to be backed up.
+These files
+are copied to the given disk, tape or other
+storage medium for safe keeping (see the
+.Fl f
+option below for doing remote backups).
+A dump that is larger than the output medium is broken into
+multiple volumes.
+On most media the size is determined by writing until an
+end-of-media indication is returned.
+This can be enforced
+by using the
+.Fl a
+option.
+.Pp
+On media that cannot reliably return an end-of-media indication
+(such as some cartridge tape drives)
+each volume is of a fixed size;
+the actual size is determined by the tape size and density and/or
+.Fl B
+options.
+By default, the same output file name is used for each volume
+after prompting the operator to change media.
+.Pp
+The file system to be dumped is specified by the argument
+.Ar filesystem
+as either its device-special file or its mount point
+(if that is in a standard entry in
+.Pa /etc/fstab ) .
+.Pp
+.Nm
+may also be invoked as
+.Nm rdump .
+The
+.Bx 4.3
+option syntax is implemented for backward compatibility, but
+is not documented here.
+.Pp
+The following options are supported by
+.Nm :
+.Bl -tag -width Ds
+.It Fl 0-9
+Dump levels.
+A level 0, full backup,
+guarantees the entire file system is copied
+(but see also the
+.Fl h
+option below).
+A level number above 0,
+incremental backup,
+tells dump to
+copy all files new or modified since the
+last dump of any lower level.
+The default level is 0.
+.It Fl a
+.Dq auto-size .
+Bypass all tape length considerations, and enforce writing
+until an end-of-media indication is returned.
+This fits best for most modern tape drives.
+Use of this option is particularly
+recommended when appending to an existing tape, or using a tape
+drive with hardware compression (where you can never be sure about
+the compression ratio).
+.It Fl B Ar records
+The number of kilobytes per output volume, except that if it is
+not an integer multiple of the output block size,
+the command uses the next smaller such multiple.
+This option overrides the calculation of tape size
+based on length and density.
+.It Fl b Ar blocksize
+The number of kilobytes per output block.
+The default block size is 10.
+.It Fl C Ar cachesize
+Specify the cache size in megabytes.
+This will greatly improve performance
+at the cost of
+.Nm
+possibly not noticing changes in the file system between passes.
+It is
+recommended that you always use this option when dumping a snapshot.
+Beware that
+.Nm
+forks, and the actual memory use may be larger than the specified cache
+size.
+The recommended cache size is between 8 and 32 (megabytes).
+.It Fl c
+Change the defaults for use with a cartridge tape drive, with a density
+of 8000 bpi, and a length of 1700 feet.
+.It Fl D Ar dumpdates
+Specify an alternate path to the
+.Pa dumpdates
+file.
+The default is
+.Pa /etc/dumpdates .
+.It Fl d Ar density
+Set tape density to
+.Ar density .
+The default is 1600BPI.
+.It Fl f Ar file
+Write the backup to
+.Ar file ;
+.Ar file
+may be a special device file
+like
+.Pa /dev/sa0
+(a tape drive),
+.Pa /dev/fd1
+(a floppy disk drive),
+an ordinary file,
+or
+.Sq Fl
+(the standard output).
+Multiple file names may be given as a single argument separated by commas.
+Each file will be used for one dump volume in the order listed;
+if the dump requires more volumes than the number of names given,
+the last file name will used for all remaining volumes after prompting
+for media changes.
+If the name of the file is of the form
+.Dq host:file ,
+or
+.Dq user@host:file ,
+.Nm
+writes to the named file on the remote host using
+.Xr rmt 8 .
+The default path name of the remote
+.Xr rmt 8
+program is
+.\" rmt path, is the path on the remote host
+.Pa /etc/rmt ;
+this can be overridden by the environment variable
+.Ev RMT .
+.It Fl P Ar pipecommand
+Use
+.Xr popen 3
+to execute the
+.Xr sh 1
+script string defined by
+.Ar pipecommand
+for the output device of each volume.
+This child pipeline's
+.Dv stdin
+.Pq Pa /dev/fd/0
+is redirected from the
+.Nm
+output stream, and the environment variable
+.Ev DUMP_VOLUME
+is set to the current volume number being written.
+After every volume, the writer side of the pipe is closed and
+.Ar pipecommand
+is executed again.
+Subject to the media size specified by
+.Fl B ,
+each volume is written in this manner as if the output were a tape drive.
+.It Fl h Ar level
+Honor the user
+.Dq nodump
+flag
+.Pq Dv UF_NODUMP
+only for dumps at or above the given
+.Ar level .
+The default honor level is 1,
+so that incremental backups omit such files
+but full backups retain them.
+.It Fl L
+This option is to notify
+.Nm
+that it is dumping a live file system.
+To obtain a consistent dump image,
+.Nm
+takes a snapshot of the file system in the
+.Pa .snap
+directory in the root of the file system being dumped and
+then does a dump of the snapshot.
+The snapshot is unlinked as soon as the dump starts, and
+is thus removed when the dump is complete.
+This option is ignored for unmounted or read-only file systems.
+If the
+.Pa .snap
+directory does not exist in the root of the file system being dumped,
+a warning will be issued and the
+.Nm
+will revert to the standard behavior.
+This problem can be corrected by creating a
+.Pa .snap
+directory in the root of the file system to be dumped;
+its owner should be
+.Dq Li root ,
+its group should be
+.Dq Li operator ,
+and its mode should be
+.Dq Li 0770 .
+.It Fl n
+Whenever
+.Nm
+requires operator attention,
+notify all operators in the group
+.Dq operator
+by means similar to a
+.Xr wall 1 .
+.It Fl r
+Be rsync-friendly.
+Normally dump stores the date of the current
+and prior dump in numerous places throughout the dump.
+These scattered changes significantly slow down rsync or
+another incremental file transfer program when they are
+used to update a remote copy of a level 0 dump,
+since the date changes for each dump.
+This option sets both dates to the epoch, permitting
+rsync to be much more efficient when transferring a dump file.
+.It Fl R
+Be even more rsync-friendly.
+This option disables the storage of the actual inode access time
+(storing it instead as the inode's modified time).
+This option permits rsync to be even more efficient
+when transferring dumps generated from filesystems with numerous files
+which are not changing other than their access times.
+The
+.Fl R
+option also sets
+.Fl r .
+.It Fl S
+Display an estimate of the backup size and the number of
+tapes required, and exit without actually performing the dump.
+.It Fl s Ar feet
+Attempt to calculate the amount of tape needed
+at a particular density.
+If this amount is exceeded,
+.Nm
+prompts for a new tape.
+It is recommended to be a bit conservative on this option.
+The default tape length is 2300 feet.
+.It Fl T Ar date
+Use the specified date as the starting time for the dump
+instead of the time determined from looking in
+the
+.Pa dumpdates
+file.
+The format of date is the same as that of
+.Xr ctime 3 .
+This option is useful for automated dump scripts that wish to
+dump over a specific period of time.
+The
+.Fl T
+option is mutually exclusive from the
+.Fl u
+option.
+.It Fl u
+Update the
+.Pa dumpdates
+file
+after a successful dump.
+The format of
+the
+.Pa dumpdates
+file
+is readable by people, consisting of one
+free format record per line:
+file system name,
+increment level
+and
+.Xr ctime 3
+format dump date.
+There may be only one entry per file system at each level.
+The
+.Pa dumpdates
+file
+may be edited to change any of the fields,
+if necessary.
+The default path for the
+.Pa dumpdates
+file is
+.Pa /etc/dumpdates ,
+but the
+.Fl D
+option may be used to change it.
+.It Fl W
+Tell the operator what file systems need to be dumped.
+This information is gleaned from the files
+.Pa dumpdates
+and
+.Pa /etc/fstab .
+The
+.Fl W
+option causes
+.Nm
+to print out, for each file system in
+the
+.Pa dumpdates
+file
+the most recent dump date and level,
+and highlights those file systems that should be dumped.
+If the
+.Fl W
+option is set, all other options are ignored, and
+.Nm
+exits immediately.
+.It Fl w
+Is like
+.Fl W ,
+but prints only those file systems which need to be dumped.
+.El
+.Pp
+Directories and regular files which have their
+.Dq nodump
+flag
+.Pq Dv UF_NODUMP
+set will be omitted along with everything under such directories,
+subject to the
+.Fl h
+option.
+.Pp
+The
+.Nm
+utility requires operator intervention on these conditions:
+end of tape,
+end of dump,
+tape write error,
+tape open error or
+disk read error (if there are more than a threshold of 32).
+In addition to alerting all operators implied by the
+.Fl n
+key,
+.Nm
+interacts with the operator on
+.Em dump's
+control terminal at times when
+.Nm
+can no longer proceed,
+or if something is grossly wrong.
+All questions
+.Nm
+poses
+.Em must
+be answered by typing
+.Dq yes
+or
+.Dq no ,
+appropriately.
+.Pp
+Since making a dump involves a lot of time and effort for full dumps,
+.Nm
+checkpoints itself at the start of each tape volume.
+If writing that volume fails for some reason,
+.Nm
+will,
+with operator permission,
+restart itself from the checkpoint
+after the old tape has been rewound and removed,
+and a new tape has been mounted.
+.Pp
+The
+.Nm
+utility tells the operator what is going on at periodic intervals
+(every 5 minutes, or promptly after receiving
+.Dv SIGINFO ) ,
+including usually low estimates of the number of blocks to write,
+the number of tapes it will take, the time to completion, and
+the time to the tape change.
+The output is verbose,
+so that others know that the terminal
+controlling
+.Nm
+is busy,
+and will be for some time.
+.Pp
+In the event of a catastrophic disk event, the time required
+to restore all the necessary backup tapes or files to disk
+can be kept to a minimum by staggering the incremental dumps.
+An efficient method of staggering incremental dumps
+to minimize the number of tapes follows:
+.Bl -bullet -offset indent
+.It
+Always start with a level 0 backup, for example:
+.Bd -literal -offset indent
+/sbin/dump -0u -f /dev/nsa0 /usr/src
+.Ed
+.Pp
+This should be done at set intervals, say once a month or once every two months,
+and on a set of fresh tapes that is saved forever.
+.It
+After a level 0, dumps of active file systems (file systems with files
+that change, depending on your partition layout some file systems may
+contain only data that does not change) are taken on a daily basis,
+using a modified Tower of Hanoi algorithm,
+with this sequence of dump levels:
+.Bd -literal -offset indent
+3 2 5 4 7 6 9 8 9 9 ...
+.Ed
+.Pp
+For the daily dumps, it should be possible to use a fixed number of tapes
+for each day, used on a weekly basis.
+Each week, a level 1 dump is taken, and
+the daily Hanoi sequence repeats beginning with 3.
+For weekly dumps, another fixed set of tapes per dumped file system is
+used, also on a cyclical basis.
+.El
+.Pp
+After several months or so, the daily and weekly tapes should get
+rotated out of the dump cycle and fresh tapes brought in.
+.Sh ENVIRONMENT
+.Bl -tag -width ".Ev TAPE"
+.It Ev TAPE
+The
+.Ar file
+or device to dump to if the
+.Fl f
+option is not used.
+.It Ev RMT
+Pathname of the remote
+.Xr rmt 8
+program.
+.It Ev RSH
+Pathname of a remote shell program, if not
+.Xr rsh 1 .
+.El
+.Sh FILES
+.Bl -tag -width /etc/dumpdates -compact
+.It Pa /dev/sa0
+default tape unit to dump to
+.It Pa /etc/dumpdates
+dump date records
+(this can be changed;
+see the
+.Fl D
+option)
+.It Pa /etc/fstab
+dump table: file systems and frequency
+.It Pa /etc/group
+to find group
+.Em operator
+.El
+.Sh EXIT STATUS
+Dump exits with zero status on success.
+Startup errors are indicated with an exit code of 1;
+abnormal termination is indicated with an exit code of 3.
+.Sh EXAMPLES
+Dumps the
+.Pa /u
+file system to DVDs using
+.Nm growisofs .
+Uses a 16MB cache, creates a snapshot of the dump, and records the
+.Pa dumpdates
+file.
+.Bd -literal
+/sbin/dump -0u -L -C16 -B4589840 -P 'growisofs -Z /dev/cd0=/dev/fd/0' /u
+.Ed
+.Sh DIAGNOSTICS
+Many, and verbose.
+.Sh SEE ALSO
+.Xr chflags 1 ,
+.Xr fstab 5 ,
+.Xr restore 8 ,
+.Xr rmt 8
+.Sh HISTORY
+A
+.Nm
+utility appeared in
+.At v6 .
+.Sh BUGS
+Fewer than 32 read errors on the file system are ignored, though all
+errors will generate a warning message.
+This is a bit of a compromise.
+In practice, it is possible to generate read errors when doing dumps
+on mounted partitions if the file system is being modified while the
+.Nm
+is running.
+Since dumps are often done in an unattended fashion using
+.Xr cron 8
+jobs asking for Operator intervention would result in the
+.Nm
+dying.
+However, there is nothing wrong with a dump tape written when this sort
+of read error occurs, and there is no reason to terminate the
+.Nm .
+.Pp
+Each reel requires a new process, so parent processes for
+reels already written just hang around until the entire tape
+is written.
+.Pp
+The
+.Nm
+utility with the
+.Fl W
+or
+.Fl w
+options does not report file systems that have never been recorded
+in the
+.Pa dumpdates
+file,
+even if listed in
+.Pa /etc/fstab .
+.Pp
+It would be nice if
+.Nm
+knew about the dump sequence,
+kept track of the tapes scribbled on,
+told the operator which tape to mount when,
+and provided more assistance
+for the operator running
+.Xr restore 8 .
+.Pp
+The
+.Nm
+utility cannot do remote backups without being run as root, due to its
+security history.
+This will be fixed in a later version of
+.Fx .
+Presently, it works if you set it setuid (like it used to be), but this
+might constitute a security risk.
diff --git a/sbin/dump/dump.h b/sbin/dump/dump.h
new file mode 100644
index 0000000..4a8af10
--- /dev/null
+++ b/sbin/dump/dump.h
@@ -0,0 +1,183 @@
+/*-
+ * Copyright (c) 1980, 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.
+ * 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.
+ *
+ * @(#)dump.h 8.2 (Berkeley) 4/28/95
+ *
+ * $FreeBSD$
+ */
+
+/*
+ * Dump maps used to describe what is to be dumped.
+ */
+int mapsize; /* size of the state maps */
+char *usedinomap; /* map of allocated inodes */
+char *dumpdirmap; /* map of directories to be dumped */
+char *dumpinomap; /* map of files to be dumped */
+/*
+ * Map manipulation macros.
+ */
+#define SETINO(ino, map) \
+ map[(u_int)((ino) - 1) / CHAR_BIT] |= \
+ 1 << ((u_int)((ino) - 1) % CHAR_BIT)
+#define CLRINO(ino, map) \
+ map[(u_int)((ino) - 1) / CHAR_BIT] &= \
+ ~(1 << ((u_int)((ino) - 1) % CHAR_BIT))
+#define TSTINO(ino, map) \
+ (map[(u_int)((ino) - 1) / CHAR_BIT] & \
+ (1 << ((u_int)((ino) - 1) % CHAR_BIT)))
+
+/*
+ * All calculations done in 0.1" units!
+ */
+char *disk; /* name of the disk file */
+char *tape; /* name of the tape file */
+char *popenout; /* popen(3) per-"tape" command */
+char *dumpdates; /* name of the file containing dump date information*/
+char *temp; /* name of the file for doing rewrite of dumpdates */
+int lastlevel; /* dump level of previous dump */
+int level; /* dump level of this dump */
+int uflag; /* update flag */
+int diskfd; /* disk file descriptor */
+int tapefd; /* tape file descriptor */
+int pipeout; /* true => output to standard output */
+ino_t curino; /* current inumber; used globally */
+int newtape; /* new tape flag */
+int density; /* density in 0.1" units */
+long tapesize; /* estimated tape size, blocks */
+long tsize; /* tape size in 0.1" units */
+long asize; /* number of 0.1" units written on current tape */
+int etapes; /* estimated number of tapes */
+int nonodump; /* if set, do not honor UF_NODUMP user flags */
+int unlimited; /* if set, write to end of medium */
+int cachesize; /* size of block cache in bytes */
+int rsync_friendly; /* be friendly with rsync */
+
+int notify; /* notify operator flag */
+int blockswritten; /* number of blocks written on current tape */
+int tapeno; /* current tape number */
+time_t tstart_writing; /* when started writing the first tape block */
+time_t tend_writing; /* after writing the last tape block */
+int passno; /* current dump pass number */
+struct fs *sblock; /* the file system super block */
+char sblock_buf[MAXBSIZE];
+long dev_bsize; /* block size of underlying disk device */
+int dev_bshift; /* log2(dev_bsize) */
+int tp_bshift; /* log2(TP_BSIZE) */
+
+/* operator interface functions */
+void broadcast(const char *message);
+void infosch(int);
+void lastdump(int arg); /* int should be char */
+void msg(const char *fmt, ...) __printflike(1, 2);
+void msgtail(const char *fmt, ...) __printflike(1, 2);
+int query(const char *question);
+void quit(const char *fmt, ...) __printflike(1, 2);
+void timeest(void);
+time_t unctime(char *str);
+
+/* mapping rouintes */
+union dinode;
+int mapfiles(ino_t maxino, long *tapesize);
+int mapdirs(ino_t maxino, long *tapesize);
+
+/* file dumping routines */
+void bread(ufs2_daddr_t blkno, char *buf, int size);
+ssize_t cread(int fd, void *buf, size_t nbytes, off_t offset);
+void dumpino(union dinode *dp, ino_t ino);
+void dumpmap(char *map, int type, ino_t ino);
+void writeheader(ino_t ino);
+
+/* tape writing routines */
+int alloctape(void);
+void close_rewind(void);
+void dumpblock(ufs2_daddr_t blkno, int size);
+void startnewtape(int top);
+void trewind(void);
+void writerec(char *dp, int isspcl);
+
+void Exit(int status) __dead2;
+void dumpabort(int signo) __dead2;
+void dump_getfstab(void);
+
+char *rawname(char *cp);
+union dinode *getino(ino_t inum, int *mode);
+
+/* rdump routines */
+#ifdef RDUMP
+void rmtclose(void);
+int rmthost(const char *host);
+int rmtopen(const char *tape, int mode);
+int rmtwrite(const char *buf, int count);
+#endif /* RDUMP */
+
+void interrupt(int signo); /* in case operator bangs on console */
+
+/*
+ * Exit status codes
+ */
+#define X_FINOK 0 /* normal exit */
+#define X_STARTUP 1 /* startup error */
+#define X_REWRITE 2 /* restart writing from the check point */
+#define X_ABORT 3 /* abort dump; don't attempt checkpointing */
+
+#define OPGRENT "operator" /* group entry to notify */
+
+struct fstab *fstabsearch(const char *key); /* search fs_file and fs_spec */
+
+#ifndef NAME_MAX
+#define NAME_MAX 255
+#endif
+
+/*
+ * The contents of the file _PATH_DUMPDATES is maintained both on
+ * a linked list, and then (eventually) arrayified.
+ */
+struct dumpdates {
+ char dd_name[NAME_MAX+3];
+ int dd_level;
+ time_t dd_ddate;
+};
+int nddates; /* number of records (might be zero) */
+struct dumpdates **ddatev; /* the arrayfied version */
+void initdumptimes(void);
+void getdumptime(void);
+void putdumptime(void);
+#define ITITERATE(i, ddp) \
+ if (ddatev != NULL) \
+ for (ddp = ddatev[i = 0]; i < nddates; ddp = ddatev[++i])
+
+#define DUMPFMTLEN 53 /* max device pathname length */
+#define DUMPOUTFMT "%-*s %d %s" /* for printf */
+ /* name, level, ctime(date) */
+#define DUMPINFMT "%s %d %[^\n]\n" /* inverse for scanf */
+
+void sig(int signo);
+
+#ifndef _PATH_FSTAB
+#define _PATH_FSTAB "/etc/fstab"
+#endif
diff --git a/sbin/dump/dumprmt.c b/sbin/dump/dumprmt.c
new file mode 100644
index 0000000..dd02642
--- /dev/null
+++ b/sbin/dump/dumprmt.c
@@ -0,0 +1,375 @@
+/*-
+ * Copyright (c) 1980, 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.
+ * 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 lint
+#if 0
+static char sccsid[] = "@(#)dumprmt.c 8.3 (Berkeley) 4/28/95";
+#endif
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/mtio.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+
+#include <ufs/ufs/dinode.h>
+
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <netinet/tcp.h>
+
+#include <protocols/dumprestore.h>
+
+#include <ctype.h>
+#include <netdb.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <limits.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "pathnames.h"
+#include "dump.h"
+
+#define TS_CLOSED 0
+#define TS_OPEN 1
+
+static int rmtstate = TS_CLOSED;
+static int rmtape;
+static char *rmtpeer;
+
+static int okname(const char *);
+static int rmtcall(const char *, const char *);
+static void rmtconnaborted(int);
+static int rmtgetb(void);
+static void rmtgetconn(void);
+static void rmtgets(char *, int);
+static int rmtreply(const char *);
+
+static int errfd = -1;
+extern int ntrec; /* blocking factor on tape */
+
+int
+rmthost(const char *host)
+{
+
+ rmtpeer = strdup(host);
+ if (rmtpeer == NULL)
+ return (0);
+ signal(SIGPIPE, rmtconnaborted);
+ rmtgetconn();
+ if (rmtape < 0)
+ return (0);
+ return (1);
+}
+
+static void
+rmtconnaborted(int sig __unused)
+{
+ msg("Lost connection to remote host.\n");
+ if (errfd != -1) {
+ fd_set r;
+ struct timeval t;
+
+ FD_ZERO(&r);
+ FD_SET(errfd, &r);
+ t.tv_sec = 0;
+ t.tv_usec = 0;
+ if (select(errfd + 1, &r, NULL, NULL, &t)) {
+ int i;
+ char buf[2048];
+
+ if ((i = read(errfd, buf, sizeof(buf) - 1)) > 0) {
+ buf[i] = '\0';
+ msg("on %s: %s%s", rmtpeer, buf,
+ buf[i - 1] == '\n' ? "" : "\n");
+ }
+ }
+ }
+
+ exit(X_ABORT);
+}
+
+void
+rmtgetconn(void)
+{
+ char *cp;
+ const char *rmt;
+ static struct servent *sp = NULL;
+ static struct passwd *pwd = NULL;
+ char *tuser;
+ int size;
+ int throughput;
+ int on;
+
+ if (sp == NULL) {
+ sp = getservbyname("shell", "tcp");
+ if (sp == NULL) {
+ msg("shell/tcp: unknown service\n");
+ exit(X_STARTUP);
+ }
+ pwd = getpwuid(getuid());
+ if (pwd == NULL) {
+ msg("who are you?\n");
+ exit(X_STARTUP);
+ }
+ }
+ if ((cp = strchr(rmtpeer, '@')) != NULL) {
+ tuser = rmtpeer;
+ *cp = '\0';
+ if (!okname(tuser))
+ exit(X_STARTUP);
+ rmtpeer = ++cp;
+ } else
+ tuser = pwd->pw_name;
+ if ((rmt = getenv("RMT")) == NULL)
+ rmt = _PATH_RMT;
+ msg("%s", "");
+ rmtape = rcmd(&rmtpeer, (u_short)sp->s_port, pwd->pw_name,
+ tuser, rmt, &errfd);
+ if (rmtape < 0) {
+ msg("login to %s as %s failed.\n", rmtpeer, tuser);
+ return;
+ }
+ (void)fprintf(stderr, "Connection to %s established.\n", rmtpeer);
+ size = ntrec * TP_BSIZE;
+ if (size > 60 * 1024) /* XXX */
+ size = 60 * 1024;
+ /* Leave some space for rmt request/response protocol */
+ size += 2 * 1024;
+ while (size > TP_BSIZE &&
+ setsockopt(rmtape, SOL_SOCKET, SO_SNDBUF, &size, sizeof (size)) < 0)
+ size -= TP_BSIZE;
+ (void)setsockopt(rmtape, SOL_SOCKET, SO_RCVBUF, &size, sizeof (size));
+ throughput = IPTOS_THROUGHPUT;
+ if (setsockopt(rmtape, IPPROTO_IP, IP_TOS,
+ &throughput, sizeof(throughput)) < 0)
+ perror("IP_TOS:IPTOS_THROUGHPUT setsockopt");
+ on = 1;
+ if (setsockopt(rmtape, IPPROTO_TCP, TCP_NODELAY, &on, sizeof (on)) < 0)
+ perror("TCP_NODELAY setsockopt");
+}
+
+static int
+okname(const char *cp0)
+{
+ const char *cp;
+ int c;
+
+ for (cp = cp0; *cp; cp++) {
+ c = *cp;
+ if (!isascii(c) || !(isalnum(c) || c == '_' || c == '-')) {
+ msg("invalid user name %s\n", cp0);
+ return (0);
+ }
+ }
+ return (1);
+}
+
+int
+rmtopen(const char *tape, int mode)
+{
+ char buf[256];
+
+ (void)snprintf(buf, sizeof (buf), "O%.226s\n%d\n", tape, mode);
+ rmtstate = TS_OPEN;
+ return (rmtcall(tape, buf));
+}
+
+void
+rmtclose(void)
+{
+
+ if (rmtstate != TS_OPEN)
+ return;
+ rmtcall("close", "C\n");
+ rmtstate = TS_CLOSED;
+}
+
+int
+rmtread(char *buf, int count)
+{
+ char line[30];
+ int n, i, cc;
+
+ (void)snprintf(line, sizeof (line), "R%d\n", count);
+ n = rmtcall("read", line);
+ if (n < 0)
+ /* rmtcall() properly sets errno for us on errors. */
+ return (n);
+ for (i = 0; i < n; i += cc) {
+ cc = read(rmtape, buf+i, n - i);
+ if (cc <= 0)
+ rmtconnaborted(0);
+ }
+ return (n);
+}
+
+int
+rmtwrite(const char *buf, int count)
+{
+ char line[30];
+
+ (void)snprintf(line, sizeof (line), "W%d\n", count);
+ write(rmtape, line, strlen(line));
+ write(rmtape, buf, count);
+ return (rmtreply("write"));
+}
+
+void
+rmtwrite0(int count)
+{
+ char line[30];
+
+ (void)snprintf(line, sizeof (line), "W%d\n", count);
+ write(rmtape, line, strlen(line));
+}
+
+void
+rmtwrite1(const char *buf, int count)
+{
+
+ write(rmtape, buf, count);
+}
+
+int
+rmtwrite2(void)
+{
+
+ return (rmtreply("write"));
+}
+
+int
+rmtseek(int offset, int pos) /* XXX off_t ? */
+{
+ char line[80];
+
+ (void)snprintf(line, sizeof (line), "L%d\n%d\n", offset, pos);
+ return (rmtcall("seek", line));
+}
+
+struct mtget mts;
+
+struct mtget *
+rmtstatus(void)
+{
+ int i;
+ char *cp;
+
+ if (rmtstate != TS_OPEN)
+ return (NULL);
+ rmtcall("status", "S\n");
+ for (i = 0, cp = (char *)&mts; i < sizeof(mts); i++)
+ *cp++ = rmtgetb();
+ return (&mts);
+}
+
+int
+rmtioctl(int cmd, int count)
+{
+ char buf[256];
+
+ if (count < 0)
+ return (-1);
+ (void)snprintf(buf, sizeof (buf), "I%d\n%d\n", cmd, count);
+ return (rmtcall("ioctl", buf));
+}
+
+static int
+rmtcall(const char *cmd, const char *buf)
+{
+
+ if (write(rmtape, buf, strlen(buf)) != strlen(buf))
+ rmtconnaborted(0);
+ return (rmtreply(cmd));
+}
+
+static int
+rmtreply(const char *cmd)
+{
+ char *cp;
+ char code[30], emsg[BUFSIZ];
+
+ rmtgets(code, sizeof (code));
+ if (*code == 'E' || *code == 'F') {
+ rmtgets(emsg, sizeof (emsg));
+ msg("%s: %s", cmd, emsg);
+ errno = atoi(code + 1);
+ if (*code == 'F')
+ rmtstate = TS_CLOSED;
+ return (-1);
+ }
+ if (*code != 'A') {
+ /* Kill trailing newline */
+ cp = code + strlen(code);
+ if (cp > code && *--cp == '\n')
+ *cp = '\0';
+
+ msg("Protocol to remote tape server botched (code \"%s\").\n",
+ code);
+ rmtconnaborted(0);
+ }
+ return (atoi(code + 1));
+}
+
+int
+rmtgetb(void)
+{
+ char c;
+
+ if (read(rmtape, &c, 1) != 1)
+ rmtconnaborted(0);
+ return (c);
+}
+
+/* Get a line (guaranteed to have a trailing newline). */
+void
+rmtgets(char *line, int len)
+{
+ char *cp = line;
+
+ while (len > 1) {
+ *cp = rmtgetb();
+ if (*cp == '\n') {
+ cp[1] = '\0';
+ return;
+ }
+ cp++;
+ len--;
+ }
+ *cp = '\0';
+ msg("Protocol to remote tape server botched.\n");
+ msg("(rmtgets got \"%s\").\n", line);
+ rmtconnaborted(0);
+}
diff --git a/sbin/dump/itime.c b/sbin/dump/itime.c
new file mode 100644
index 0000000..909b8ab
--- /dev/null
+++ b/sbin/dump/itime.c
@@ -0,0 +1,265 @@
+/*-
+ * Copyright (c) 1980, 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.
+ * 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 lint
+#if 0
+static char sccsid[] = "@(#)itime.c 8.1 (Berkeley) 6/5/93";
+#endif
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/queue.h>
+
+#include <ufs/ufs/dinode.h>
+
+#include <protocols/dumprestore.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <timeconv.h>
+
+#include "dump.h"
+
+struct dumptime {
+ struct dumpdates dt_value;
+ SLIST_ENTRY(dumptime) dt_list;
+};
+SLIST_HEAD(dthead, dumptime) dthead = SLIST_HEAD_INITIALIZER(dthead);
+struct dumpdates **ddatev = 0;
+int nddates = 0;
+
+static void dumprecout(FILE *, const struct dumpdates *);
+static int getrecord(FILE *, struct dumpdates *);
+static int makedumpdate(struct dumpdates *, const char *);
+static void readdumptimes(FILE *);
+
+void
+initdumptimes(void)
+{
+ FILE *df;
+
+ if ((df = fopen(dumpdates, "r")) == NULL) {
+ if (errno != ENOENT) {
+ msg("WARNING: cannot read %s: %s\n", dumpdates,
+ strerror(errno));
+ return;
+ }
+ /*
+ * Dumpdates does not exist, make an empty one.
+ */
+ msg("WARNING: no file `%s', making an empty one\n", dumpdates);
+ if ((df = fopen(dumpdates, "w")) == NULL) {
+ msg("WARNING: cannot create %s: %s\n", dumpdates,
+ strerror(errno));
+ return;
+ }
+ (void) fclose(df);
+ if ((df = fopen(dumpdates, "r")) == NULL) {
+ quit("cannot read %s even after creating it: %s\n",
+ dumpdates, strerror(errno));
+ /* NOTREACHED */
+ }
+ }
+ (void) flock(fileno(df), LOCK_SH);
+ readdumptimes(df);
+ (void) fclose(df);
+}
+
+static void
+readdumptimes(FILE *df)
+{
+ int i;
+ struct dumptime *dtwalk;
+
+ for (;;) {
+ dtwalk = (struct dumptime *)calloc(1, sizeof (struct dumptime));
+ if (getrecord(df, &(dtwalk->dt_value)) < 0) {
+ free(dtwalk);
+ break;
+ }
+ nddates++;
+ SLIST_INSERT_HEAD(&dthead, dtwalk, dt_list);
+ }
+
+ /*
+ * arrayify the list, leaving enough room for the additional
+ * record that we may have to add to the ddate structure
+ */
+ ddatev = (struct dumpdates **)
+ calloc((unsigned) (nddates + 1), sizeof (struct dumpdates *));
+ dtwalk = SLIST_FIRST(&dthead);
+ for (i = nddates - 1; i >= 0; i--, dtwalk = SLIST_NEXT(dtwalk, dt_list))
+ ddatev[i] = &dtwalk->dt_value;
+}
+
+void
+getdumptime(void)
+{
+ struct dumpdates *ddp;
+ int i;
+ char *fname;
+
+ fname = disk;
+#ifdef FDEBUG
+ msg("Looking for name %s in dumpdates = %s for level = %d\n",
+ fname, dumpdates, level);
+#endif
+ spcl.c_ddate = 0;
+ lastlevel = 0;
+
+ initdumptimes();
+ /*
+ * Go find the entry with the same name for a lower increment
+ * and older date
+ */
+ ITITERATE(i, ddp) {
+ if (strncmp(fname, ddp->dd_name, sizeof (ddp->dd_name)) != 0)
+ continue;
+ if (ddp->dd_level >= level)
+ continue;
+ if (ddp->dd_ddate <= _time64_to_time(spcl.c_ddate))
+ continue;
+ spcl.c_ddate = _time_to_time64(ddp->dd_ddate);
+ lastlevel = ddp->dd_level;
+ }
+}
+
+void
+putdumptime(void)
+{
+ FILE *df;
+ struct dumpdates *dtwalk;
+ int i;
+ int fd;
+ char *fname;
+ char *tmsg;
+
+ if(uflag == 0)
+ return;
+ if ((df = fopen(dumpdates, "r+")) == NULL)
+ quit("cannot rewrite %s: %s\n", dumpdates, strerror(errno));
+ fd = fileno(df);
+ (void) flock(fd, LOCK_EX);
+ fname = disk;
+ free((char *)ddatev);
+ ddatev = 0;
+ nddates = 0;
+ readdumptimes(df);
+ if (fseek(df, 0L, 0) < 0)
+ quit("fseek: %s\n", strerror(errno));
+ spcl.c_ddate = 0;
+ ITITERATE(i, dtwalk) {
+ if (strncmp(fname, dtwalk->dd_name,
+ sizeof (dtwalk->dd_name)) != 0)
+ continue;
+ if (dtwalk->dd_level != level)
+ continue;
+ goto found;
+ }
+ /*
+ * construct the new upper bound;
+ * Enough room has been allocated.
+ */
+ dtwalk = ddatev[nddates] =
+ (struct dumpdates *)calloc(1, sizeof (struct dumpdates));
+ nddates += 1;
+ found:
+ (void) strncpy(dtwalk->dd_name, fname, sizeof (dtwalk->dd_name));
+ dtwalk->dd_level = level;
+ dtwalk->dd_ddate = _time64_to_time(spcl.c_date);
+
+ ITITERATE(i, dtwalk) {
+ dumprecout(df, dtwalk);
+ }
+ if (fflush(df))
+ quit("%s: %s\n", dumpdates, strerror(errno));
+ if (ftruncate(fd, ftell(df)))
+ quit("ftruncate (%s): %s\n", dumpdates, strerror(errno));
+ (void) fclose(df);
+ if (spcl.c_date == 0) {
+ tmsg = "the epoch\n";
+ } else {
+ time_t t = _time64_to_time(spcl.c_date);
+ tmsg = ctime(&t);
+ }
+ msg("level %d dump on %s", level, tmsg);
+}
+
+static void
+dumprecout(FILE *file, const struct dumpdates *what)
+{
+
+ if (strlen(what->dd_name) > DUMPFMTLEN)
+ quit("Name '%s' exceeds DUMPFMTLEN (%d) bytes\n",
+ what->dd_name, DUMPFMTLEN);
+ if (fprintf(file, DUMPOUTFMT, DUMPFMTLEN, what->dd_name,
+ what->dd_level, ctime(&what->dd_ddate)) < 0)
+ quit("%s: %s\n", dumpdates, strerror(errno));
+}
+
+int recno;
+
+static int
+getrecord(FILE *df, struct dumpdates *ddatep)
+{
+ char tbuf[BUFSIZ];
+
+ recno = 0;
+ if ( (fgets(tbuf, sizeof (tbuf), df)) != tbuf)
+ return(-1);
+ recno++;
+ if (makedumpdate(ddatep, tbuf) < 0)
+ msg("Unknown intermediate format in %s, line %d\n",
+ dumpdates, recno);
+
+#ifdef FDEBUG
+ msg("getrecord: %s %d %s", ddatep->dd_name, ddatep->dd_level,
+ ddatep->dd_ddate == 0 ? "the epoch\n" : ctime(&ddatep->dd_ddate));
+#endif
+ return(0);
+}
+
+static int
+makedumpdate(struct dumpdates *ddp, const char *tbuf)
+{
+ char un_buf[128];
+
+ (void) sscanf(tbuf, DUMPINFMT, ddp->dd_name, &ddp->dd_level, un_buf);
+ ddp->dd_ddate = unctime(un_buf);
+ if (ddp->dd_ddate < 0)
+ return(-1);
+ return(0);
+}
diff --git a/sbin/dump/main.c b/sbin/dump/main.c
new file mode 100644
index 0000000..3ec78fd1
--- /dev/null
+++ b/sbin/dump/main.c
@@ -0,0 +1,778 @@
+/*-
+ * Copyright (c) 1980, 1991, 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.
+ * 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 lint
+static const char copyright[] =
+"@(#) Copyright (c) 1980, 1991, 1993, 1994\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)main.c 8.6 (Berkeley) 5/1/95";
+#endif
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/mount.h>
+#include <sys/disklabel.h>
+
+#include <ufs/ufs/dinode.h>
+#include <ufs/ufs/ufsmount.h>
+#include <ufs/ffs/fs.h>
+
+#include <protocols/dumprestore.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <fstab.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <timeconv.h>
+#include <unistd.h>
+
+#include "dump.h"
+#include "pathnames.h"
+
+int notify = 0; /* notify operator flag */
+int snapdump = 0; /* dumping live filesystem, so use snapshot */
+int blockswritten = 0; /* number of blocks written on current tape */
+int tapeno = 0; /* current tape number */
+int density = 0; /* density in bytes/0.1" " <- this is for hilit19 */
+int ntrec = NTREC; /* # tape blocks in each tape record */
+int cartridge = 0; /* Assume non-cartridge tape */
+int cachesize = 0; /* block cache size (in bytes), defaults to 0 */
+long dev_bsize = 1; /* recalculated below */
+long blocksperfile; /* output blocks per file */
+char *host = NULL; /* remote host (if any) */
+
+/*
+ * Possible superblock locations ordered from most to least likely.
+ */
+static int sblock_try[] = SBLOCKSEARCH;
+
+static char *getmntpt(char *, int *);
+static long numarg(const char *, long, long);
+static void obsolete(int *, char **[]);
+static void usage(void) __dead2;
+
+int
+main(int argc, char *argv[])
+{
+ struct stat sb;
+ ino_t ino;
+ int dirty;
+ union dinode *dp;
+ struct fstab *dt;
+ char *map, *mntpt;
+ int ch, mode, mntflags;
+ int i, anydirskipped, bflag = 0, Tflag = 0, honorlevel = 1;
+ int just_estimate = 0;
+ ino_t maxino;
+ char *tmsg;
+
+ spcl.c_date = _time_to_time64(time(NULL));
+
+ tsize = 0; /* Default later, based on 'c' option for cart tapes */
+ dumpdates = _PATH_DUMPDATES;
+ popenout = NULL;
+ tape = NULL;
+ temp = _PATH_DTMP;
+ if (TP_BSIZE / DEV_BSIZE == 0 || TP_BSIZE % DEV_BSIZE != 0)
+ quit("TP_BSIZE must be a multiple of DEV_BSIZE\n");
+ level = 0;
+ rsync_friendly = 0;
+
+ if (argc < 2)
+ usage();
+
+ obsolete(&argc, &argv);
+ while ((ch = getopt(argc, argv,
+ "0123456789aB:b:C:cD:d:f:h:LnP:RrSs:T:uWw")) != -1)
+ switch (ch) {
+ /* dump level */
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ level = 10 * level + ch - '0';
+ break;
+
+ case 'a': /* `auto-size', Write to EOM. */
+ unlimited = 1;
+ break;
+
+ case 'B': /* blocks per output file */
+ blocksperfile = numarg("number of blocks per file",
+ 1L, 0L);
+ break;
+
+ case 'b': /* blocks per tape write */
+ ntrec = numarg("number of blocks per write",
+ 1L, 1000L);
+ break;
+
+ case 'C':
+ cachesize = numarg("cachesize", 0, 0) * 1024 * 1024;
+ break;
+
+ case 'c': /* Tape is cart. not 9-track */
+ cartridge = 1;
+ break;
+
+ case 'D':
+ dumpdates = optarg;
+ break;
+
+ case 'd': /* density, in bits per inch */
+ density = numarg("density", 10L, 327670L) / 10;
+ if (density >= 625 && !bflag)
+ ntrec = HIGHDENSITYTREC;
+ break;
+
+ case 'f': /* output file */
+ if (popenout != NULL)
+ errx(X_STARTUP, "You cannot use the P and f "
+ "flags together.\n");
+ tape = optarg;
+ break;
+
+ case 'h':
+ honorlevel = numarg("honor level", 0L, 10L);
+ break;
+
+ case 'L':
+ snapdump = 1;
+ break;
+
+ case 'n': /* notify operators */
+ notify = 1;
+ break;
+
+ case 'P':
+ if (tape != NULL)
+ errx(X_STARTUP, "You cannot use the P and f "
+ "flags together.\n");
+ popenout = optarg;
+ break;
+
+ case 'r': /* store slightly less data to be friendly to rsync */
+ if (rsync_friendly < 1)
+ rsync_friendly = 1;
+ break;
+
+ case 'R': /* store even less data to be friendlier to rsync */
+ if (rsync_friendly < 2)
+ rsync_friendly = 2;
+ break;
+
+ case 'S': /* exit after estimating # of tapes */
+ just_estimate = 1;
+ break;
+
+ case 's': /* tape size, feet */
+ tsize = numarg("tape size", 1L, 0L) * 12 * 10;
+ break;
+
+ case 'T': /* time of last dump */
+ spcl.c_ddate = unctime(optarg);
+ if (spcl.c_ddate < 0) {
+ (void)fprintf(stderr, "bad time \"%s\"\n",
+ optarg);
+ exit(X_STARTUP);
+ }
+ Tflag = 1;
+ lastlevel = -1;
+ break;
+
+ case 'u': /* update /etc/dumpdates */
+ uflag = 1;
+ break;
+
+ case 'W': /* what to do */
+ case 'w':
+ lastdump(ch);
+ exit(X_FINOK); /* do nothing else */
+
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 1) {
+ (void)fprintf(stderr, "Must specify disk or file system\n");
+ exit(X_STARTUP);
+ }
+ disk = *argv++;
+ argc--;
+ if (argc >= 1) {
+ (void)fprintf(stderr, "Unknown arguments to dump:");
+ while (argc--)
+ (void)fprintf(stderr, " %s", *argv++);
+ (void)fprintf(stderr, "\n");
+ exit(X_STARTUP);
+ }
+ if (rsync_friendly && (level > 0)) {
+ (void)fprintf(stderr, "%s %s\n", "rsync friendly options",
+ "can be used only with level 0 dumps.");
+ exit(X_STARTUP);
+ }
+ if (Tflag && uflag) {
+ (void)fprintf(stderr,
+ "You cannot use the T and u flags together.\n");
+ exit(X_STARTUP);
+ }
+ if (popenout) {
+ tape = "child pipeline process";
+ } else if (tape == NULL && (tape = getenv("TAPE")) == NULL)
+ tape = _PATH_DEFTAPE;
+ if (strcmp(tape, "-") == 0) {
+ pipeout++;
+ tape = "standard output";
+ }
+
+ if (blocksperfile)
+ blocksperfile = blocksperfile / ntrec * ntrec; /* round down */
+ else if (!unlimited) {
+ /*
+ * Determine how to default tape size and density
+ *
+ * density tape size
+ * 9-track 1600 bpi (160 bytes/.1") 2300 ft.
+ * 9-track 6250 bpi (625 bytes/.1") 2300 ft.
+ * cartridge 8000 bpi (100 bytes/.1") 1700 ft.
+ * (450*4 - slop)
+ * hilit19 hits again: "
+ */
+ if (density == 0)
+ density = cartridge ? 100 : 160;
+ if (tsize == 0)
+ tsize = cartridge ? 1700L*120L : 2300L*120L;
+ }
+
+ if (strchr(tape, ':')) {
+ host = tape;
+ tape = strchr(host, ':');
+ *tape++ = '\0';
+#ifdef RDUMP
+ if (strchr(tape, '\n')) {
+ (void)fprintf(stderr, "invalid characters in tape\n");
+ exit(X_STARTUP);
+ }
+ if (rmthost(host) == 0)
+ exit(X_STARTUP);
+#else
+ (void)fprintf(stderr, "remote dump not enabled\n");
+ exit(X_STARTUP);
+#endif
+ }
+ (void)setuid(getuid()); /* rmthost() is the only reason to be setuid */
+
+ if (signal(SIGHUP, SIG_IGN) != SIG_IGN)
+ signal(SIGHUP, sig);
+ if (signal(SIGTRAP, SIG_IGN) != SIG_IGN)
+ signal(SIGTRAP, sig);
+ if (signal(SIGFPE, SIG_IGN) != SIG_IGN)
+ signal(SIGFPE, sig);
+ if (signal(SIGBUS, SIG_IGN) != SIG_IGN)
+ signal(SIGBUS, sig);
+ if (signal(SIGSEGV, SIG_IGN) != SIG_IGN)
+ signal(SIGSEGV, sig);
+ if (signal(SIGTERM, SIG_IGN) != SIG_IGN)
+ signal(SIGTERM, sig);
+ if (signal(SIGINT, interrupt) == SIG_IGN)
+ signal(SIGINT, SIG_IGN);
+
+ dump_getfstab(); /* /etc/fstab snarfed */
+ /*
+ * disk can be either the full special file name,
+ * the suffix of the special file name,
+ * the special name missing the leading '/',
+ * the file system name with or without the leading '/'.
+ */
+ dt = fstabsearch(disk);
+ if (dt != NULL) {
+ disk = rawname(dt->fs_spec);
+ if (disk == NULL)
+ errx(X_STARTUP, "%s: unknown file system", dt->fs_spec);
+ (void)strncpy(spcl.c_dev, dt->fs_spec, NAMELEN);
+ (void)strncpy(spcl.c_filesys, dt->fs_file, NAMELEN);
+ } else {
+ (void)strncpy(spcl.c_dev, disk, NAMELEN);
+ (void)strncpy(spcl.c_filesys, "an unlisted file system",
+ NAMELEN);
+ }
+ spcl.c_dev[NAMELEN-1]='\0';
+ spcl.c_filesys[NAMELEN-1]='\0';
+
+ if ((mntpt = getmntpt(disk, &mntflags)) != 0) {
+ if (mntflags & MNT_RDONLY) {
+ if (snapdump != 0) {
+ msg("WARNING: %s\n",
+ "-L ignored for read-only filesystem.");
+ snapdump = 0;
+ }
+ } else if (snapdump == 0) {
+ msg("WARNING: %s\n",
+ "should use -L when dumping live read-write "
+ "filesystems!");
+ } else {
+ char snapname[BUFSIZ], snapcmd[BUFSIZ];
+
+ snprintf(snapname, sizeof snapname, "%s/.snap", mntpt);
+ if ((stat(snapname, &sb) < 0) || !S_ISDIR(sb.st_mode)) {
+ msg("WARNING: %s %s\n",
+ "-L requested but snapshot location",
+ snapname);
+ msg(" %s: %s\n",
+ "is not a directory",
+ "dump downgraded, -L ignored");
+ snapdump = 0;
+ } else {
+ snprintf(snapname, sizeof snapname,
+ "%s/.snap/dump_snapshot", mntpt);
+ snprintf(snapcmd, sizeof snapcmd, "%s %s %s",
+ _PATH_MKSNAP_FFS, mntpt, snapname);
+ unlink(snapname);
+ if (system(snapcmd) != 0)
+ errx(X_STARTUP, "Cannot create %s: %s\n",
+ snapname, strerror(errno));
+ if ((diskfd = open(snapname, O_RDONLY)) < 0) {
+ unlink(snapname);
+ errx(X_STARTUP, "Cannot open %s: %s\n",
+ snapname, strerror(errno));
+ }
+ unlink(snapname);
+ if (fstat(diskfd, &sb) != 0)
+ err(X_STARTUP, "%s: stat", snapname);
+ spcl.c_date = _time_to_time64(sb.st_mtime);
+ }
+ }
+ } else if (snapdump != 0) {
+ msg("WARNING: Cannot use -L on an unmounted filesystem.\n");
+ snapdump = 0;
+ }
+ if (snapdump == 0) {
+ if ((diskfd = open(disk, O_RDONLY)) < 0)
+ err(X_STARTUP, "Cannot open %s", disk);
+ if (fstat(diskfd, &sb) != 0)
+ err(X_STARTUP, "%s: stat", disk);
+ if (S_ISDIR(sb.st_mode))
+ errx(X_STARTUP, "%s: unknown file system", disk);
+ }
+
+ (void)strcpy(spcl.c_label, "none");
+ (void)gethostname(spcl.c_host, NAMELEN);
+ spcl.c_level = level;
+ spcl.c_type = TS_TAPE;
+ if (rsync_friendly) {
+ /* don't store real dump times */
+ spcl.c_date = 0;
+ spcl.c_ddate = 0;
+ }
+ if (spcl.c_date == 0) {
+ tmsg = "the epoch\n";
+ } else {
+ time_t t = _time64_to_time(spcl.c_date);
+ tmsg = ctime(&t);
+ }
+ msg("Date of this level %d dump: %s", level, tmsg);
+
+ if (!Tflag && (!rsync_friendly))
+ getdumptime(); /* /etc/dumpdates snarfed */
+ if (spcl.c_ddate == 0) {
+ tmsg = "the epoch\n";
+ } else {
+ time_t t = _time64_to_time(spcl.c_ddate);
+ tmsg = ctime(&t);
+ }
+ if (lastlevel < 0)
+ msg("Date of last (level unknown) dump: %s", tmsg);
+ else
+ msg("Date of last level %d dump: %s", lastlevel, tmsg);
+
+ msg("Dumping %s%s ", snapdump ? "snapshot of ": "", disk);
+ if (dt != NULL)
+ msgtail("(%s) ", dt->fs_file);
+ if (host)
+ msgtail("to %s on host %s\n", tape, host);
+ else
+ msgtail("to %s\n", tape);
+
+ sync();
+ sblock = (struct fs *)sblock_buf;
+ for (i = 0; sblock_try[i] != -1; i++) {
+ sblock->fs_fsize = SBLOCKSIZE; /* needed in bread */
+ bread(sblock_try[i] >> dev_bshift, (char *) sblock, SBLOCKSIZE);
+ if ((sblock->fs_magic == FS_UFS1_MAGIC ||
+ (sblock->fs_magic == FS_UFS2_MAGIC &&
+ sblock->fs_sblockloc == sblock_try[i])) &&
+ sblock->fs_bsize <= MAXBSIZE &&
+ sblock->fs_bsize >= sizeof(struct fs))
+ break;
+ }
+ if (sblock_try[i] == -1)
+ quit("Cannot find file system superblock\n");
+ dev_bsize = sblock->fs_fsize / fsbtodb(sblock, 1);
+ dev_bshift = ffs(dev_bsize) - 1;
+ if (dev_bsize != (1 << dev_bshift))
+ quit("dev_bsize (%ld) is not a power of 2", dev_bsize);
+ tp_bshift = ffs(TP_BSIZE) - 1;
+ if (TP_BSIZE != (1 << tp_bshift))
+ quit("TP_BSIZE (%d) is not a power of 2", TP_BSIZE);
+ maxino = sblock->fs_ipg * sblock->fs_ncg;
+ mapsize = roundup(howmany(maxino, CHAR_BIT), TP_BSIZE);
+ usedinomap = (char *)calloc((unsigned) mapsize, sizeof(char));
+ dumpdirmap = (char *)calloc((unsigned) mapsize, sizeof(char));
+ dumpinomap = (char *)calloc((unsigned) mapsize, sizeof(char));
+ tapesize = 3 * (howmany(mapsize * sizeof(char), TP_BSIZE) + 1);
+
+ nonodump = spcl.c_level < honorlevel;
+
+ passno = 1;
+ setproctitle("%s: pass 1: regular files", disk);
+ msg("mapping (Pass I) [regular files]\n");
+ anydirskipped = mapfiles(maxino, &tapesize);
+
+ passno = 2;
+ setproctitle("%s: pass 2: directories", disk);
+ msg("mapping (Pass II) [directories]\n");
+ while (anydirskipped) {
+ anydirskipped = mapdirs(maxino, &tapesize);
+ }
+
+ if (pipeout || unlimited) {
+ tapesize += 10; /* 10 trailer blocks */
+ msg("estimated %ld tape blocks.\n", tapesize);
+ } else {
+ double fetapes;
+
+ if (blocksperfile)
+ fetapes = (double) tapesize / blocksperfile;
+ else if (cartridge) {
+ /* Estimate number of tapes, assuming streaming stops at
+ the end of each block written, and not in mid-block.
+ Assume no erroneous blocks; this can be compensated
+ for with an artificially low tape size. */
+ fetapes =
+ ( (double) tapesize /* blocks */
+ * TP_BSIZE /* bytes/block */
+ * (1.0/density) /* 0.1" / byte " */
+ +
+ (double) tapesize /* blocks */
+ * (1.0/ntrec) /* streaming-stops per block */
+ * 15.48 /* 0.1" / streaming-stop " */
+ ) * (1.0 / tsize ); /* tape / 0.1" " */
+ } else {
+ /* Estimate number of tapes, for old fashioned 9-track
+ tape */
+ int tenthsperirg = (density == 625) ? 3 : 7;
+ fetapes =
+ ( (double) tapesize /* blocks */
+ * TP_BSIZE /* bytes / block */
+ * (1.0/density) /* 0.1" / byte " */
+ +
+ (double) tapesize /* blocks */
+ * (1.0/ntrec) /* IRG's / block */
+ * tenthsperirg /* 0.1" / IRG " */
+ ) * (1.0 / tsize ); /* tape / 0.1" " */
+ }
+ etapes = fetapes; /* truncating assignment */
+ etapes++;
+ /* count the dumped inodes map on each additional tape */
+ tapesize += (etapes - 1) *
+ (howmany(mapsize * sizeof(char), TP_BSIZE) + 1);
+ tapesize += etapes + 10; /* headers + 10 trailer blks */
+ msg("estimated %ld tape blocks on %3.2f tape(s).\n",
+ tapesize, fetapes);
+ }
+
+ /*
+ * If the user only wants an estimate of the number of
+ * tapes, exit now.
+ */
+ if (just_estimate)
+ exit(0);
+
+ /*
+ * Allocate tape buffer.
+ */
+ if (!alloctape())
+ quit(
+ "can't allocate tape buffers - try a smaller blocking factor.\n");
+
+ startnewtape(1);
+ (void)time((time_t *)&(tstart_writing));
+ dumpmap(usedinomap, TS_CLRI, maxino - 1);
+
+ passno = 3;
+ setproctitle("%s: pass 3: directories", disk);
+ msg("dumping (Pass III) [directories]\n");
+ dirty = 0; /* XXX just to get gcc to shut up */
+ for (map = dumpdirmap, ino = 1; ino < maxino; ino++) {
+ if (((ino - 1) % CHAR_BIT) == 0) /* map is offset by 1 */
+ dirty = *map++;
+ else
+ dirty >>= 1;
+ if ((dirty & 1) == 0)
+ continue;
+ /*
+ * Skip directory inodes deleted and maybe reallocated
+ */
+ dp = getino(ino, &mode);
+ if (mode != IFDIR)
+ continue;
+ (void)dumpino(dp, ino);
+ }
+
+ passno = 4;
+ setproctitle("%s: pass 4: regular files", disk);
+ msg("dumping (Pass IV) [regular files]\n");
+ for (map = dumpinomap, ino = 1; ino < maxino; ino++) {
+ if (((ino - 1) % CHAR_BIT) == 0) /* map is offset by 1 */
+ dirty = *map++;
+ else
+ dirty >>= 1;
+ if ((dirty & 1) == 0)
+ continue;
+ /*
+ * Skip inodes deleted and reallocated as directories.
+ */
+ dp = getino(ino, &mode);
+ if (mode == IFDIR)
+ continue;
+ (void)dumpino(dp, ino);
+ }
+
+ (void)time((time_t *)&(tend_writing));
+ spcl.c_type = TS_END;
+ for (i = 0; i < ntrec; i++)
+ writeheader(maxino - 1);
+ if (pipeout)
+ msg("DUMP: %jd tape blocks\n", (intmax_t)spcl.c_tapea);
+ else
+ msg("DUMP: %jd tape blocks on %d volume%s\n",
+ (intmax_t)spcl.c_tapea, spcl.c_volume,
+ (spcl.c_volume == 1) ? "" : "s");
+
+ /* report dump performance, avoid division through zero */
+ if (tend_writing - tstart_writing == 0)
+ msg("finished in less than a second\n");
+ else
+ msg("finished in %jd seconds, throughput %jd KBytes/sec\n",
+ (intmax_t)tend_writing - tstart_writing,
+ (intmax_t)(spcl.c_tapea /
+ (tend_writing - tstart_writing)));
+
+ putdumptime();
+ trewind();
+ broadcast("DUMP IS DONE!\a\a\n");
+ msg("DUMP IS DONE\n");
+ Exit(X_FINOK);
+ /* NOTREACHED */
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr,
+ "usage: dump [-0123456789acLnSu] [-B records] [-b blocksize] [-C cachesize]\n"
+ " [-D dumpdates] [-d density] [-f file | -P pipecommand] [-h level]\n"
+ " [-s feet] [-T date] filesystem\n"
+ " dump -W | -w\n");
+ exit(X_STARTUP);
+}
+
+/*
+ * Check to see if a disk is currently mounted.
+ */
+static char *
+getmntpt(char *name, int *mntflagsp)
+{
+ long mntsize, i;
+ struct statfs *mntbuf;
+
+ mntsize = getmntinfo(&mntbuf, MNT_NOWAIT);
+ for (i = 0; i < mntsize; i++) {
+ if (!strcmp(mntbuf[i].f_mntfromname, name)) {
+ *mntflagsp = mntbuf[i].f_flags;
+ return (mntbuf[i].f_mntonname);
+ }
+ }
+ return (0);
+}
+
+/*
+ * Pick up a numeric argument. It must be nonnegative and in the given
+ * range (except that a vmax of 0 means unlimited).
+ */
+static long
+numarg(const char *meaning, long vmin, long vmax)
+{
+ char *p;
+ long val;
+
+ val = strtol(optarg, &p, 10);
+ if (*p)
+ errx(1, "illegal %s -- %s", meaning, optarg);
+ if (val < vmin || (vmax && val > vmax))
+ errx(1, "%s must be between %ld and %ld", meaning, vmin, vmax);
+ return (val);
+}
+
+void
+sig(int signo)
+{
+ switch(signo) {
+ case SIGALRM:
+ case SIGBUS:
+ case SIGFPE:
+ case SIGHUP:
+ case SIGTERM:
+ case SIGTRAP:
+ if (pipeout)
+ quit("Signal on pipe: cannot recover\n");
+ msg("Rewriting attempted as response to unknown signal.\n");
+ (void)fflush(stderr);
+ (void)fflush(stdout);
+ close_rewind();
+ exit(X_REWRITE);
+ /* NOTREACHED */
+ case SIGSEGV:
+ msg("SIGSEGV: ABORTING!\n");
+ (void)signal(SIGSEGV, SIG_DFL);
+ (void)kill(0, SIGSEGV);
+ /* NOTREACHED */
+ }
+}
+
+char *
+rawname(char *cp)
+{
+ struct stat sb;
+
+ /*
+ * Ensure that the device passed in is a raw device.
+ */
+ if (stat(cp, &sb) == 0 && (sb.st_mode & S_IFMT) == S_IFCHR)
+ return (cp);
+
+ /*
+ * Since there's only one device type now, we can't construct any
+ * better name, so we have to return NULL.
+ */
+ return (NULL);
+}
+
+/*
+ * obsolete --
+ * Change set of key letters and ordered arguments into something
+ * getopt(3) will like.
+ */
+static void
+obsolete(int *argcp, char **argvp[])
+{
+ int argc, flags;
+ char *ap, **argv, *flagsp, **nargv, *p;
+
+ /* Setup. */
+ argv = *argvp;
+ argc = *argcp;
+
+ /*
+ * Return if no arguments or first argument has leading
+ * dash or slash.
+ */
+ ap = argv[1];
+ if (argc == 1 || *ap == '-' || *ap == '/')
+ return;
+
+ /* Allocate space for new arguments. */
+ if ((*argvp = nargv = malloc((argc + 1) * sizeof(char *))) == NULL ||
+ (p = flagsp = malloc(strlen(ap) + 2)) == NULL)
+ err(1, NULL);
+
+ *nargv++ = *argv;
+ argv += 2;
+
+ for (flags = 0; *ap; ++ap) {
+ switch (*ap) {
+ case 'B':
+ case 'b':
+ case 'd':
+ case 'f':
+ case 'D':
+ case 'C':
+ case 'h':
+ case 's':
+ case 'T':
+ if (*argv == NULL) {
+ warnx("option requires an argument -- %c", *ap);
+ usage();
+ }
+ if ((nargv[0] = malloc(strlen(*argv) + 2 + 1)) == NULL)
+ err(1, NULL);
+ nargv[0][0] = '-';
+ nargv[0][1] = *ap;
+ (void)strcpy(&nargv[0][2], *argv);
+ ++argv;
+ ++nargv;
+ break;
+ default:
+ if (!flags) {
+ *p++ = '-';
+ flags = 1;
+ }
+ *p++ = *ap;
+ break;
+ }
+ }
+
+ /* Terminate flags. */
+ if (flags) {
+ *p = '\0';
+ *nargv++ = flagsp;
+ } else
+ free(flagsp);
+
+ /* Copy remaining arguments. */
+ while ((*nargv++ = *argv++));
+
+ /* Update argument count. */
+ *argcp = nargv - *argvp - 1;
+}
diff --git a/sbin/dump/optr.c b/sbin/dump/optr.c
new file mode 100644
index 0000000..311e255
--- /dev/null
+++ b/sbin/dump/optr.c
@@ -0,0 +1,429 @@
+/*-
+ * Copyright (c) 1980, 1988, 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.
+ * 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 lint
+#if 0
+static char sccsid[] = "@(#)optr.c 8.2 (Berkeley) 1/6/94";
+#endif
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/wait.h>
+
+#include <ufs/ufs/dinode.h>
+
+#include <errno.h>
+#include <fstab.h>
+#include <grp.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <signal.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "dump.h"
+#include "pathnames.h"
+
+void alarmcatch(int);
+int datesort(const void *, const void *);
+
+/*
+ * Query the operator; This previously-fascist piece of code
+ * no longer requires an exact response.
+ * It is intended to protect dump aborting by inquisitive
+ * people banging on the console terminal to see what is
+ * happening which might cause dump to croak, destroying
+ * a large number of hours of work.
+ *
+ * Every 2 minutes we reprint the message, alerting others
+ * that dump needs attention.
+ */
+static int timeout;
+static const char *attnmessage; /* attention message */
+
+int
+query(const char *question)
+{
+ char replybuffer[64];
+ int back, errcount;
+ FILE *mytty;
+
+ if ((mytty = fopen(_PATH_TTY, "r")) == NULL)
+ quit("fopen on %s fails: %s\n", _PATH_TTY, strerror(errno));
+ attnmessage = question;
+ timeout = 0;
+ alarmcatch(0);
+ back = -1;
+ errcount = 0;
+ do {
+ if (fgets(replybuffer, 63, mytty) == NULL) {
+ clearerr(mytty);
+ if (++errcount > 30) /* XXX ugly */
+ quit("excessive operator query failures\n");
+ } else if (replybuffer[0] == 'y' || replybuffer[0] == 'Y') {
+ back = 1;
+ } else if (replybuffer[0] == 'n' || replybuffer[0] == 'N') {
+ back = 0;
+ } else {
+ (void) fprintf(stderr,
+ " DUMP: \"Yes\" or \"No\"?\n");
+ (void) fprintf(stderr,
+ " DUMP: %s: (\"yes\" or \"no\") ", question);
+ }
+ } while (back < 0);
+
+ /*
+ * Turn off the alarm, and reset the signal to trap out..
+ */
+ (void) alarm(0);
+ if (signal(SIGALRM, sig) == SIG_IGN)
+ signal(SIGALRM, SIG_IGN);
+ (void) fclose(mytty);
+ return(back);
+}
+
+char lastmsg[BUFSIZ];
+
+/*
+ * Alert the console operator, and enable the alarm clock to
+ * sleep for 2 minutes in case nobody comes to satisfy dump
+ */
+void
+alarmcatch(int sig __unused)
+{
+ if (notify == 0) {
+ if (timeout == 0)
+ (void) fprintf(stderr,
+ " DUMP: %s: (\"yes\" or \"no\") ",
+ attnmessage);
+ else
+ msgtail("\a\a");
+ } else {
+ if (timeout) {
+ msgtail("\n");
+ broadcast(""); /* just print last msg */
+ }
+ (void) fprintf(stderr," DUMP: %s: (\"yes\" or \"no\") ",
+ attnmessage);
+ }
+ signal(SIGALRM, alarmcatch);
+ (void) alarm(120);
+ timeout = 1;
+}
+
+/*
+ * Here if an inquisitive operator interrupts the dump program
+ */
+void
+interrupt(int signo __unused)
+{
+ msg("Interrupt received.\n");
+ if (query("Do you want to abort dump?"))
+ dumpabort(0);
+}
+
+/*
+ * We now use wall(1) to do the actual broadcasting.
+ */
+void
+broadcast(const char *message)
+{
+ FILE *fp;
+ char buf[sizeof(_PATH_WALL) + sizeof(OPGRENT) + 3];
+
+ if (!notify)
+ return;
+
+ snprintf(buf, sizeof(buf), "%s -g %s", _PATH_WALL, OPGRENT);
+ if ((fp = popen(buf, "w")) == NULL)
+ return;
+
+ (void) fputs("\a\a\aMessage from the dump program to all operators\n\nDUMP: NEEDS ATTENTION: ", fp);
+ if (lastmsg[0])
+ (void) fputs(lastmsg, fp);
+ if (message[0])
+ (void) fputs(message, fp);
+
+ (void) pclose(fp);
+}
+
+/*
+ * Print out an estimate of the amount of time left to do the dump
+ */
+
+time_t tschedule = 0;
+
+void
+timeest(void)
+{
+ double percent;
+ time_t tnow, tdone;
+ char *tdone_str;
+ int deltat, hours, mins;
+
+ (void)time(&tnow);
+ if (blockswritten > tapesize) {
+ setproctitle("%s: 99.99%% done, finished soon", disk);
+ if (tnow >= tschedule) {
+ tschedule = tnow + 300;
+ msg("99.99%% done, finished soon\n");
+ }
+ } else {
+ deltat = (blockswritten == 0) ? 0 : tstart_writing - tnow +
+ (double)(tnow - tstart_writing) / blockswritten * tapesize;
+ tdone = tnow + deltat;
+ percent = (blockswritten * 100.0) / tapesize;
+ hours = deltat / 3600;
+ mins = (deltat % 3600) / 60;
+
+ tdone_str = ctime(&tdone);
+ tdone_str[strlen(tdone_str) - 1] = '\0';
+ setproctitle(
+ "%s: pass %d: %3.2f%% done, finished in %d:%02d at %s",
+ disk, passno, percent, hours, mins, tdone_str);
+ if (tnow >= tschedule) {
+ tschedule = tnow + 300;
+ if (blockswritten < 500)
+ return;
+ msg("%3.2f%% done, finished in %d:%02d at %s\n", percent,
+ hours, mins, tdone_str);
+ }
+ }
+}
+
+/*
+ * Schedule a printout of the estimate in the next call to timeest().
+ */
+void
+infosch(int signal __unused)
+{
+ tschedule = 0;
+}
+
+void
+msg(const char *fmt, ...)
+{
+ va_list ap;
+
+ (void) fprintf(stderr," DUMP: ");
+#ifdef TDEBUG
+ (void) fprintf(stderr, "pid=%d ", getpid());
+#endif
+ va_start(ap, fmt);
+ (void) vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ (void) fflush(stdout);
+ (void) fflush(stderr);
+ va_start(ap, fmt);
+ (void) vsnprintf(lastmsg, sizeof(lastmsg), fmt, ap);
+ va_end(ap);
+}
+
+void
+msgtail(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ (void) vfprintf(stderr, fmt, ap);
+ va_end(ap);
+}
+
+void
+quit(const char *fmt, ...)
+{
+ va_list ap;
+
+ (void) fprintf(stderr," DUMP: ");
+#ifdef TDEBUG
+ (void) fprintf(stderr, "pid=%d ", getpid());
+#endif
+ va_start(ap, fmt);
+ (void) vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ (void) fflush(stdout);
+ (void) fflush(stderr);
+ dumpabort(0);
+}
+
+/*
+ * Tell the operator what has to be done;
+ * we don't actually do it
+ */
+
+struct fstab *
+allocfsent(const struct fstab *fs)
+{
+ struct fstab *new;
+
+ new = (struct fstab *)malloc(sizeof (*fs));
+ if (new == NULL ||
+ (new->fs_file = strdup(fs->fs_file)) == NULL ||
+ (new->fs_type = strdup(fs->fs_type)) == NULL ||
+ (new->fs_spec = strdup(fs->fs_spec)) == NULL)
+ quit("%s\n", strerror(errno));
+ new->fs_passno = fs->fs_passno;
+ new->fs_freq = fs->fs_freq;
+ return (new);
+}
+
+struct pfstab {
+ SLIST_ENTRY(pfstab) pf_list;
+ struct fstab *pf_fstab;
+};
+
+static SLIST_HEAD(, pfstab) table;
+
+void
+dump_getfstab(void)
+{
+ struct fstab *fs;
+ struct pfstab *pf;
+
+ if (setfsent() == 0) {
+ msg("Can't open %s for dump table information: %s\n",
+ _PATH_FSTAB, strerror(errno));
+ return;
+ }
+ while ((fs = getfsent()) != NULL) {
+ if ((strcmp(fs->fs_type, FSTAB_RW) &&
+ strcmp(fs->fs_type, FSTAB_RO) &&
+ strcmp(fs->fs_type, FSTAB_RQ)) ||
+ strcmp(fs->fs_vfstype, "ufs"))
+ continue;
+ fs = allocfsent(fs);
+ if ((pf = (struct pfstab *)malloc(sizeof (*pf))) == NULL)
+ quit("%s\n", strerror(errno));
+ pf->pf_fstab = fs;
+ SLIST_INSERT_HEAD(&table, pf, pf_list);
+ }
+ (void) endfsent();
+}
+
+/*
+ * Search in the fstab for a file name.
+ * This file name can be either the special or the path file name.
+ *
+ * The file name can omit the leading '/'.
+ */
+struct fstab *
+fstabsearch(const char *key)
+{
+ struct pfstab *pf;
+ struct fstab *fs;
+ char *rn;
+
+ SLIST_FOREACH(pf, &table, pf_list) {
+ fs = pf->pf_fstab;
+ if (strcmp(fs->fs_file, key) == 0 ||
+ strcmp(fs->fs_spec, key) == 0)
+ return (fs);
+ rn = rawname(fs->fs_spec);
+ if (rn != NULL && strcmp(rn, key) == 0)
+ return (fs);
+ if (key[0] != '/') {
+ if (*fs->fs_spec == '/' &&
+ strcmp(fs->fs_spec + 1, key) == 0)
+ return (fs);
+ if (*fs->fs_file == '/' &&
+ strcmp(fs->fs_file + 1, key) == 0)
+ return (fs);
+ }
+ }
+ return (NULL);
+}
+
+/*
+ * Tell the operator what to do
+ */
+void
+lastdump(int arg) /* w ==> just what to do; W ==> most recent dumps */
+{
+ int i;
+ struct fstab *dt;
+ struct dumpdates *dtwalk;
+ char *lastname, *date;
+ int dumpme;
+ time_t tnow;
+ struct tm *tlast;
+
+ (void) time(&tnow);
+ dump_getfstab(); /* /etc/fstab input */
+ initdumptimes(); /* /etc/dumpdates input */
+ qsort((char *) ddatev, nddates, sizeof(struct dumpdates *), datesort);
+
+ if (arg == 'w')
+ (void) printf("Dump these file systems:\n");
+ else
+ (void) printf("Last dump(s) done (Dump '>' file systems):\n");
+ lastname = "??";
+ ITITERATE(i, dtwalk) {
+ if (strncmp(lastname, dtwalk->dd_name,
+ sizeof(dtwalk->dd_name)) == 0)
+ continue;
+ date = (char *)ctime(&dtwalk->dd_ddate);
+ date[16] = '\0'; /* blast away seconds and year */
+ lastname = dtwalk->dd_name;
+ dt = fstabsearch(dtwalk->dd_name);
+ dumpme = (dt != NULL && dt->fs_freq != 0);
+ if (dumpme) {
+ tlast = localtime(&dtwalk->dd_ddate);
+ dumpme = tnow > (dtwalk->dd_ddate - (tlast->tm_hour * 3600)
+ - (tlast->tm_min * 60) - tlast->tm_sec
+ + (dt->fs_freq * 86400));
+ };
+ if (arg != 'w' || dumpme)
+ (void) printf(
+ "%c %8s\t(%6s) Last dump: Level %d, Date %s\n",
+ dumpme && (arg != 'w') ? '>' : ' ',
+ dtwalk->dd_name,
+ dt ? dt->fs_file : "",
+ dtwalk->dd_level,
+ date);
+ }
+}
+
+int
+datesort(const void *a1, const void *a2)
+{
+ struct dumpdates *d1 = *(struct dumpdates **)a1;
+ struct dumpdates *d2 = *(struct dumpdates **)a2;
+ int diff;
+
+ diff = strncmp(d1->dd_name, d2->dd_name, sizeof(d1->dd_name));
+ if (diff == 0)
+ return (d2->dd_ddate - d1->dd_ddate);
+ return (diff);
+}
diff --git a/sbin/dump/pathnames.h b/sbin/dump/pathnames.h
new file mode 100644
index 0000000..eb3ce39
--- /dev/null
+++ b/sbin/dump/pathnames.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 1989, 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.
+ * 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.
+ *
+ * @(#)pathnames.h 8.1 (Berkeley) 6/5/93
+ * $FreeBSD$
+ */
+
+#include <paths.h>
+
+#define _PATH_DEFTAPE "/dev/sa0"
+#define _PATH_DTMP "/etc/dtmp"
+#define _PATH_DUMPDATES "/etc/dumpdates"
+#define _PATH_LOCK "/tmp/dumplockXXXXXX"
+#define _PATH_RMT "/etc/rmt" /* path on remote host */
diff --git a/sbin/dump/tape.c b/sbin/dump/tape.c
new file mode 100644
index 0000000..ff4e397
--- /dev/null
+++ b/sbin/dump/tape.c
@@ -0,0 +1,880 @@
+/*-
+ * Copyright (c) 1980, 1991, 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.
+ * 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 lint
+#if 0
+static char sccsid[] = "@(#)tape.c 8.4 (Berkeley) 5/1/95";
+#endif
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+
+#include <ufs/ufs/dinode.h>
+#include <ufs/ffs/fs.h>
+
+#include <protocols/dumprestore.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "dump.h"
+
+int writesize; /* size of malloc()ed buffer for tape */
+int64_t lastspclrec = -1; /* tape block number of last written header */
+int trecno = 0; /* next record to write in current block */
+extern long blocksperfile; /* number of blocks per output file */
+long blocksthisvol; /* number of blocks on current output file */
+extern int ntrec; /* blocking factor on tape */
+extern int cartridge;
+extern char *host;
+char *nexttape;
+FILE *popenfp = NULL;
+
+static int atomic(ssize_t (*)(), int, char *, int);
+static void doslave(int, int);
+static void enslave(void);
+static void flushtape(void);
+static void killall(void);
+static void rollforward(void);
+
+/*
+ * Concurrent dump mods (Caltech) - disk block reading and tape writing
+ * are exported to several slave processes. While one slave writes the
+ * tape, the others read disk blocks; they pass control of the tape in
+ * a ring via signals. The parent process traverses the file system and
+ * sends writeheader()'s and lists of daddr's to the slaves via pipes.
+ * The following structure defines the instruction packets sent to slaves.
+ */
+struct req {
+ ufs2_daddr_t dblk;
+ int count;
+};
+int reqsiz;
+
+#define SLAVES 3 /* 1 slave writing, 1 reading, 1 for slack */
+struct slave {
+ int64_t tapea; /* header number at start of this chunk */
+ int64_t firstrec; /* record number of this block */
+ int count; /* count to next header (used for TS_TAPE */
+ /* after EOT) */
+ int inode; /* inode that we are currently dealing with */
+ int fd; /* FD for this slave */
+ int pid; /* PID for this slave */
+ int sent; /* 1 == we've sent this slave requests */
+ char (*tblock)[TP_BSIZE]; /* buffer for data blocks */
+ struct req *req; /* buffer for requests */
+} slaves[SLAVES+1];
+struct slave *slp;
+
+char (*nextblock)[TP_BSIZE];
+
+int master; /* pid of master, for sending error signals */
+int tenths; /* length of tape used per block written */
+static volatile sig_atomic_t caught; /* have we caught the signal to proceed? */
+static volatile sig_atomic_t ready; /* reached the lock point without having */
+ /* received the SIGUSR2 signal from the prev slave? */
+static jmp_buf jmpbuf; /* where to jump to if we are ready when the */
+ /* SIGUSR2 arrives from the previous slave */
+
+int
+alloctape(void)
+{
+ int pgoff = getpagesize() - 1;
+ char *buf;
+ int i;
+
+ writesize = ntrec * TP_BSIZE;
+ reqsiz = (ntrec + 1) * sizeof(struct req);
+ /*
+ * CDC 92181's and 92185's make 0.8" gaps in 1600-bpi start/stop mode
+ * (see DEC TU80 User's Guide). The shorter gaps of 6250-bpi require
+ * repositioning after stopping, i.e, streaming mode, where the gap is
+ * variable, 0.30" to 0.45". The gap is maximal when the tape stops.
+ */
+ if (blocksperfile == 0 && !unlimited)
+ tenths = writesize / density +
+ (cartridge ? 16 : density == 625 ? 5 : 8);
+ /*
+ * Allocate tape buffer contiguous with the array of instruction
+ * packets, so flushtape() can write them together with one write().
+ * Align tape buffer on page boundary to speed up tape write().
+ */
+ for (i = 0; i <= SLAVES; i++) {
+ buf = (char *)
+ malloc((unsigned)(reqsiz + writesize + pgoff + TP_BSIZE));
+ if (buf == NULL)
+ return(0);
+ slaves[i].tblock = (char (*)[TP_BSIZE])
+ (((long)&buf[ntrec + 1] + pgoff) &~ pgoff);
+ slaves[i].req = (struct req *)slaves[i].tblock - ntrec - 1;
+ }
+ slp = &slaves[0];
+ slp->count = 1;
+ slp->tapea = 0;
+ slp->firstrec = 0;
+ nextblock = slp->tblock;
+ return(1);
+}
+
+void
+writerec(char *dp, int isspcl)
+{
+
+ slp->req[trecno].dblk = (ufs2_daddr_t)0;
+ slp->req[trecno].count = 1;
+ /* Can't do a structure assignment due to alignment problems */
+ bcopy(dp, *(nextblock)++, sizeof (union u_spcl));
+ if (isspcl)
+ lastspclrec = spcl.c_tapea;
+ trecno++;
+ spcl.c_tapea++;
+ if (trecno >= ntrec)
+ flushtape();
+}
+
+void
+dumpblock(ufs2_daddr_t blkno, int size)
+{
+ int avail, tpblks;
+ ufs2_daddr_t dblkno;
+
+ dblkno = fsbtodb(sblock, blkno);
+ tpblks = size >> tp_bshift;
+ while ((avail = MIN(tpblks, ntrec - trecno)) > 0) {
+ slp->req[trecno].dblk = dblkno;
+ slp->req[trecno].count = avail;
+ trecno += avail;
+ spcl.c_tapea += avail;
+ if (trecno >= ntrec)
+ flushtape();
+ dblkno += avail << (tp_bshift - dev_bshift);
+ tpblks -= avail;
+ }
+}
+
+int nogripe = 0;
+
+void
+tperror(int signo __unused)
+{
+
+ if (pipeout) {
+ msg("write error on %s\n", tape);
+ quit("Cannot recover\n");
+ /* NOTREACHED */
+ }
+ msg("write error %ld blocks into volume %d\n", blocksthisvol, tapeno);
+ broadcast("DUMP WRITE ERROR!\n");
+ if (!query("Do you want to restart?"))
+ dumpabort(0);
+ msg("Closing this volume. Prepare to restart with new media;\n");
+ msg("this dump volume will be rewritten.\n");
+ killall();
+ nogripe = 1;
+ close_rewind();
+ Exit(X_REWRITE);
+}
+
+void
+sigpipe(int signo __unused)
+{
+
+ quit("Broken pipe\n");
+}
+
+static void
+flushtape(void)
+{
+ int i, blks, got;
+ int64_t lastfirstrec;
+
+ int siz = (char *)nextblock - (char *)slp->req;
+
+ slp->req[trecno].count = 0; /* Sentinel */
+
+ if (atomic(write, slp->fd, (char *)slp->req, siz) != siz)
+ quit("error writing command pipe: %s\n", strerror(errno));
+ slp->sent = 1; /* we sent a request, read the response later */
+
+ lastfirstrec = slp->firstrec;
+
+ if (++slp >= &slaves[SLAVES])
+ slp = &slaves[0];
+
+ /* Read results back from next slave */
+ if (slp->sent) {
+ if (atomic(read, slp->fd, (char *)&got, sizeof got)
+ != sizeof got) {
+ perror(" DUMP: error reading command pipe in master");
+ dumpabort(0);
+ }
+ slp->sent = 0;
+
+ /* Check for end of tape */
+ if (got < writesize) {
+ msg("End of tape detected\n");
+
+ /*
+ * Drain the results, don't care what the values were.
+ * If we read them here then trewind won't...
+ */
+ for (i = 0; i < SLAVES; i++) {
+ if (slaves[i].sent) {
+ if (atomic(read, slaves[i].fd,
+ (char *)&got, sizeof got)
+ != sizeof got) {
+ perror(" DUMP: error reading command pipe in master");
+ dumpabort(0);
+ }
+ slaves[i].sent = 0;
+ }
+ }
+
+ close_rewind();
+ rollforward();
+ return;
+ }
+ }
+
+ blks = 0;
+ if (spcl.c_type != TS_END) {
+ for (i = 0; i < spcl.c_count; i++)
+ if (spcl.c_addr[i] != 0)
+ blks++;
+ }
+ slp->count = lastspclrec + blks + 1 - spcl.c_tapea;
+ slp->tapea = spcl.c_tapea;
+ slp->firstrec = lastfirstrec + ntrec;
+ slp->inode = curino;
+ nextblock = slp->tblock;
+ trecno = 0;
+ asize += tenths;
+ blockswritten += ntrec;
+ blocksthisvol += ntrec;
+ if (!pipeout && !unlimited && (blocksperfile ?
+ (blocksthisvol >= blocksperfile) : (asize > tsize))) {
+ close_rewind();
+ startnewtape(0);
+ }
+ timeest();
+}
+
+void
+trewind(void)
+{
+ struct stat sb;
+ int f;
+ int got;
+
+ for (f = 0; f < SLAVES; f++) {
+ /*
+ * Drain the results, but unlike EOT we DO (or should) care
+ * what the return values were, since if we detect EOT after
+ * we think we've written the last blocks to the tape anyway,
+ * we have to replay those blocks with rollforward.
+ *
+ * fixme: punt for now.
+ */
+ if (slaves[f].sent) {
+ if (atomic(read, slaves[f].fd, (char *)&got, sizeof got)
+ != sizeof got) {
+ perror(" DUMP: error reading command pipe in master");
+ dumpabort(0);
+ }
+ slaves[f].sent = 0;
+ if (got != writesize) {
+ msg("EOT detected in last 2 tape records!\n");
+ msg("Use a longer tape, decrease the size estimate\n");
+ quit("or use no size estimate at all.\n");
+ }
+ }
+ (void) close(slaves[f].fd);
+ }
+ while (wait((int *)NULL) >= 0) /* wait for any signals from slaves */
+ /* void */;
+
+ if (pipeout)
+ return;
+
+ msg("Closing %s\n", tape);
+
+ if (popenout) {
+ tapefd = -1;
+ (void)pclose(popenfp);
+ popenfp = NULL;
+ return;
+ }
+#ifdef RDUMP
+ if (host) {
+ rmtclose();
+ while (rmtopen(tape, 0) < 0)
+ sleep(10);
+ rmtclose();
+ return;
+ }
+#endif
+ if (fstat(tapefd, &sb) == 0 && S_ISFIFO(sb.st_mode)) {
+ (void)close(tapefd);
+ return;
+ }
+ (void) close(tapefd);
+ while ((f = open(tape, 0)) < 0)
+ sleep (10);
+ (void) close(f);
+}
+
+void
+close_rewind()
+{
+ time_t tstart_changevol, tend_changevol;
+
+ trewind();
+ if (nexttape)
+ return;
+ (void)time((time_t *)&(tstart_changevol));
+ if (!nogripe) {
+ msg("Change Volumes: Mount volume #%d\n", tapeno+1);
+ broadcast("CHANGE DUMP VOLUMES!\a\a\n");
+ }
+ while (!query("Is the new volume mounted and ready to go?"))
+ if (query("Do you want to abort?")) {
+ dumpabort(0);
+ /*NOTREACHED*/
+ }
+ (void)time((time_t *)&(tend_changevol));
+ if ((tstart_changevol != (time_t)-1) && (tend_changevol != (time_t)-1))
+ tstart_writing += (tend_changevol - tstart_changevol);
+}
+
+void
+rollforward(void)
+{
+ struct req *p, *q, *prev;
+ struct slave *tslp;
+ int i, size, got;
+ int64_t savedtapea;
+ union u_spcl *ntb, *otb;
+ tslp = &slaves[SLAVES];
+ ntb = (union u_spcl *)tslp->tblock[1];
+
+ /*
+ * Each of the N slaves should have requests that need to
+ * be replayed on the next tape. Use the extra slave buffers
+ * (slaves[SLAVES]) to construct request lists to be sent to
+ * each slave in turn.
+ */
+ for (i = 0; i < SLAVES; i++) {
+ q = &tslp->req[1];
+ otb = (union u_spcl *)slp->tblock;
+
+ /*
+ * For each request in the current slave, copy it to tslp.
+ */
+
+ prev = NULL;
+ for (p = slp->req; p->count > 0; p += p->count) {
+ *q = *p;
+ if (p->dblk == 0)
+ *ntb++ = *otb++; /* copy the datablock also */
+ prev = q;
+ q += q->count;
+ }
+ if (prev == NULL)
+ quit("rollforward: protocol botch");
+ if (prev->dblk != 0)
+ prev->count -= 1;
+ else
+ ntb--;
+ q -= 1;
+ q->count = 0;
+ q = &tslp->req[0];
+ if (i == 0) {
+ q->dblk = 0;
+ q->count = 1;
+ trecno = 0;
+ nextblock = tslp->tblock;
+ savedtapea = spcl.c_tapea;
+ spcl.c_tapea = slp->tapea;
+ startnewtape(0);
+ spcl.c_tapea = savedtapea;
+ lastspclrec = savedtapea - 1;
+ }
+ size = (char *)ntb - (char *)q;
+ if (atomic(write, slp->fd, (char *)q, size) != size) {
+ perror(" DUMP: error writing command pipe");
+ dumpabort(0);
+ }
+ slp->sent = 1;
+ if (++slp >= &slaves[SLAVES])
+ slp = &slaves[0];
+
+ q->count = 1;
+
+ if (prev->dblk != 0) {
+ /*
+ * If the last one was a disk block, make the
+ * first of this one be the last bit of that disk
+ * block...
+ */
+ q->dblk = prev->dblk +
+ prev->count * (TP_BSIZE / DEV_BSIZE);
+ ntb = (union u_spcl *)tslp->tblock;
+ } else {
+ /*
+ * It wasn't a disk block. Copy the data to its
+ * new location in the buffer.
+ */
+ q->dblk = 0;
+ *((union u_spcl *)tslp->tblock) = *ntb;
+ ntb = (union u_spcl *)tslp->tblock[1];
+ }
+ }
+ slp->req[0] = *q;
+ nextblock = slp->tblock;
+ if (q->dblk == 0)
+ nextblock++;
+ trecno = 1;
+
+ /*
+ * Clear the first slaves' response. One hopes that it
+ * worked ok, otherwise the tape is much too short!
+ */
+ if (slp->sent) {
+ if (atomic(read, slp->fd, (char *)&got, sizeof got)
+ != sizeof got) {
+ perror(" DUMP: error reading command pipe in master");
+ dumpabort(0);
+ }
+ slp->sent = 0;
+
+ if (got != writesize) {
+ quit("EOT detected at start of the tape!\n");
+ }
+ }
+}
+
+/*
+ * We implement taking and restoring checkpoints on the tape level.
+ * When each tape is opened, a new process is created by forking; this
+ * saves all of the necessary context in the parent. The child
+ * continues the dump; the parent waits around, saving the context.
+ * If the child returns X_REWRITE, then it had problems writing that tape;
+ * this causes the parent to fork again, duplicating the context, and
+ * everything continues as if nothing had happened.
+ */
+void
+startnewtape(int top)
+{
+ int parentpid;
+ int childpid;
+ int status;
+ char *p;
+ sig_t interrupt_save;
+
+ interrupt_save = signal(SIGINT, SIG_IGN);
+ parentpid = getpid();
+
+restore_check_point:
+ (void)signal(SIGINT, interrupt_save);
+ /*
+ * All signals are inherited...
+ */
+ setproctitle(NULL); /* Restore the proctitle. */
+ childpid = fork();
+ if (childpid < 0) {
+ msg("Context save fork fails in parent %d\n", parentpid);
+ Exit(X_ABORT);
+ }
+ if (childpid != 0) {
+ /*
+ * PARENT:
+ * save the context by waiting
+ * until the child doing all of the work returns.
+ * don't catch the interrupt
+ */
+ signal(SIGINT, SIG_IGN);
+#ifdef TDEBUG
+ msg("Tape: %d; parent process: %d child process %d\n",
+ tapeno+1, parentpid, childpid);
+#endif /* TDEBUG */
+ if (waitpid(childpid, &status, 0) == -1)
+ msg("Waiting for child %d: %s\n", childpid,
+ strerror(errno));
+ if (status & 0xFF) {
+ msg("Child %d returns LOB status %o\n",
+ childpid, status&0xFF);
+ }
+ status = (status >> 8) & 0xFF;
+#ifdef TDEBUG
+ switch(status) {
+ case X_FINOK:
+ msg("Child %d finishes X_FINOK\n", childpid);
+ break;
+ case X_ABORT:
+ msg("Child %d finishes X_ABORT\n", childpid);
+ break;
+ case X_REWRITE:
+ msg("Child %d finishes X_REWRITE\n", childpid);
+ break;
+ default:
+ msg("Child %d finishes unknown %d\n",
+ childpid, status);
+ break;
+ }
+#endif /* TDEBUG */
+ switch(status) {
+ case X_FINOK:
+ Exit(X_FINOK);
+ case X_ABORT:
+ Exit(X_ABORT);
+ case X_REWRITE:
+ goto restore_check_point;
+ default:
+ msg("Bad return code from dump: %d\n", status);
+ Exit(X_ABORT);
+ }
+ /*NOTREACHED*/
+ } else { /* we are the child; just continue */
+#ifdef TDEBUG
+ sleep(4); /* allow time for parent's message to get out */
+ msg("Child on Tape %d has parent %d, my pid = %d\n",
+ tapeno+1, parentpid, getpid());
+#endif /* TDEBUG */
+ /*
+ * If we have a name like "/dev/rmt0,/dev/rmt1",
+ * use the name before the comma first, and save
+ * the remaining names for subsequent volumes.
+ */
+ tapeno++; /* current tape sequence */
+ if (nexttape || strchr(tape, ',')) {
+ if (nexttape && *nexttape)
+ tape = nexttape;
+ if ((p = strchr(tape, ',')) != NULL) {
+ *p = '\0';
+ nexttape = p + 1;
+ } else
+ nexttape = NULL;
+ msg("Dumping volume %d on %s\n", tapeno, tape);
+ }
+ if (pipeout) {
+ tapefd = STDOUT_FILENO;
+ } else if (popenout) {
+ char volno[sizeof("2147483647")];
+
+ (void)sprintf(volno, "%d", spcl.c_volume + 1);
+ if (setenv("DUMP_VOLUME", volno, 1) == -1) {
+ msg("Cannot set $DUMP_VOLUME.\n");
+ dumpabort(0);
+ }
+ popenfp = popen(popenout, "w");
+ if (popenfp == NULL) {
+ msg("Cannot open output pipeline \"%s\".\n",
+ popenout);
+ dumpabort(0);
+ }
+ tapefd = fileno(popenfp);
+ } else {
+#ifdef RDUMP
+ while ((tapefd = (host ? rmtopen(tape, 2) :
+ open(tape, O_WRONLY|O_CREAT, 0666))) < 0)
+#else
+ while ((tapefd =
+ open(tape, O_WRONLY|O_CREAT, 0666)) < 0)
+#endif
+ {
+ msg("Cannot open output \"%s\".\n", tape);
+ if (!query("Do you want to retry the open?"))
+ dumpabort(0);
+ }
+ }
+
+ enslave(); /* Share open tape file descriptor with slaves */
+ if (popenout)
+ close(tapefd); /* Give up our copy of it. */
+ signal(SIGINFO, infosch);
+
+ asize = 0;
+ blocksthisvol = 0;
+ if (top)
+ newtape++; /* new tape signal */
+ spcl.c_count = slp->count;
+ /*
+ * measure firstrec in TP_BSIZE units since restore doesn't
+ * know the correct ntrec value...
+ */
+ spcl.c_firstrec = slp->firstrec;
+ spcl.c_volume++;
+ spcl.c_type = TS_TAPE;
+ writeheader((ino_t)slp->inode);
+ if (tapeno > 1)
+ msg("Volume %d begins with blocks from inode %d\n",
+ tapeno, slp->inode);
+ }
+}
+
+void
+dumpabort(int signo __unused)
+{
+
+ if (master != 0 && master != getpid())
+ /* Signals master to call dumpabort */
+ (void) kill(master, SIGTERM);
+ else {
+ killall();
+ msg("The ENTIRE dump is aborted.\n");
+ }
+#ifdef RDUMP
+ rmtclose();
+#endif
+ Exit(X_ABORT);
+}
+
+void
+Exit(status)
+ int status;
+{
+
+#ifdef TDEBUG
+ msg("pid = %d exits with status %d\n", getpid(), status);
+#endif /* TDEBUG */
+ exit(status);
+}
+
+/*
+ * proceed - handler for SIGUSR2, used to synchronize IO between the slaves.
+ */
+void
+proceed(int signo __unused)
+{
+
+ if (ready)
+ longjmp(jmpbuf, 1);
+ caught++;
+}
+
+void
+enslave(void)
+{
+ int cmd[2];
+ int i, j;
+
+ master = getpid();
+
+ signal(SIGTERM, dumpabort); /* Slave sends SIGTERM on dumpabort() */
+ signal(SIGPIPE, sigpipe);
+ signal(SIGUSR1, tperror); /* Slave sends SIGUSR1 on tape errors */
+ signal(SIGUSR2, proceed); /* Slave sends SIGUSR2 to next slave */
+
+ for (i = 0; i < SLAVES; i++) {
+ if (i == slp - &slaves[0]) {
+ caught = 1;
+ } else {
+ caught = 0;
+ }
+
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, cmd) < 0 ||
+ (slaves[i].pid = fork()) < 0)
+ quit("too many slaves, %d (recompile smaller): %s\n",
+ i, strerror(errno));
+
+ slaves[i].fd = cmd[1];
+ slaves[i].sent = 0;
+ if (slaves[i].pid == 0) { /* Slave starts up here */
+ for (j = 0; j <= i; j++)
+ (void) close(slaves[j].fd);
+ signal(SIGINT, SIG_IGN); /* Master handles this */
+ doslave(cmd[0], i);
+ Exit(X_FINOK);
+ }
+ }
+
+ for (i = 0; i < SLAVES; i++)
+ (void) atomic(write, slaves[i].fd,
+ (char *) &slaves[(i + 1) % SLAVES].pid,
+ sizeof slaves[0].pid);
+
+ master = 0;
+}
+
+void
+killall(void)
+{
+ int i;
+
+ for (i = 0; i < SLAVES; i++)
+ if (slaves[i].pid > 0) {
+ (void) kill(slaves[i].pid, SIGKILL);
+ slaves[i].sent = 0;
+ }
+}
+
+/*
+ * Synchronization - each process has a lockfile, and shares file
+ * descriptors to the following process's lockfile. When our write
+ * completes, we release our lock on the following process's lock-
+ * file, allowing the following process to lock it and proceed. We
+ * get the lock back for the next cycle by swapping descriptors.
+ */
+static void
+doslave(int cmd, int slave_number)
+{
+ int nread;
+ int nextslave, size, wrote, eot_count;
+
+ /*
+ * Need our own seek pointer.
+ */
+ (void) close(diskfd);
+ if ((diskfd = open(disk, O_RDONLY)) < 0)
+ quit("slave couldn't reopen disk: %s\n", strerror(errno));
+
+ /*
+ * Need the pid of the next slave in the loop...
+ */
+ if ((nread = atomic(read, cmd, (char *)&nextslave, sizeof nextslave))
+ != sizeof nextslave) {
+ quit("master/slave protocol botched - didn't get pid of next slave.\n");
+ }
+
+ /*
+ * Get list of blocks to dump, read the blocks into tape buffer
+ */
+ while ((nread = atomic(read, cmd, (char *)slp->req, reqsiz)) == reqsiz) {
+ struct req *p = slp->req;
+
+ for (trecno = 0; trecno < ntrec;
+ trecno += p->count, p += p->count) {
+ if (p->dblk) {
+ bread(p->dblk, slp->tblock[trecno],
+ p->count * TP_BSIZE);
+ } else {
+ if (p->count != 1 || atomic(read, cmd,
+ (char *)slp->tblock[trecno],
+ TP_BSIZE) != TP_BSIZE)
+ quit("master/slave protocol botched.\n");
+ }
+ }
+ if (setjmp(jmpbuf) == 0) {
+ ready = 1;
+ if (!caught)
+ (void) pause();
+ }
+ ready = 0;
+ caught = 0;
+
+ /* Try to write the data... */
+ eot_count = 0;
+ size = 0;
+
+ wrote = 0;
+ while (eot_count < 10 && size < writesize) {
+#ifdef RDUMP
+ if (host)
+ wrote = rmtwrite(slp->tblock[0]+size,
+ writesize-size);
+ else
+#endif
+ wrote = write(tapefd, slp->tblock[0]+size,
+ writesize-size);
+#ifdef WRITEDEBUG
+ printf("slave %d wrote %d\n", slave_number, wrote);
+#endif
+ if (wrote < 0)
+ break;
+ if (wrote == 0)
+ eot_count++;
+ size += wrote;
+ }
+
+#ifdef WRITEDEBUG
+ if (size != writesize)
+ printf("slave %d only wrote %d out of %d bytes and gave up.\n",
+ slave_number, size, writesize);
+#endif
+
+ /*
+ * Handle ENOSPC as an EOT condition.
+ */
+ if (wrote < 0 && errno == ENOSPC) {
+ wrote = 0;
+ eot_count++;
+ }
+
+ if (eot_count > 0)
+ size = 0;
+
+ if (wrote < 0) {
+ (void) kill(master, SIGUSR1);
+ for (;;)
+ (void) sigpause(0);
+ } else {
+ /*
+ * pass size of write back to master
+ * (for EOT handling)
+ */
+ (void) atomic(write, cmd, (char *)&size, sizeof size);
+ }
+
+ /*
+ * If partial write, don't want next slave to go.
+ * Also jolts him awake.
+ */
+ (void) kill(nextslave, SIGUSR2);
+ }
+ if (nread != 0)
+ quit("error reading command pipe: %s\n", strerror(errno));
+}
+
+/*
+ * Since a read from a pipe may not return all we asked for,
+ * or a write may not write all we ask if we get a signal,
+ * loop until the count is satisfied (or error).
+ */
+static int
+atomic(ssize_t (*func)(), int fd, char *buf, int count)
+{
+ int got, need = count;
+
+ while ((got = (*func)(fd, buf, need)) > 0 && (need -= got) > 0)
+ buf += got;
+ return (got < 0 ? got : count - need);
+}
diff --git a/sbin/dump/traverse.c b/sbin/dump/traverse.c
new file mode 100644
index 0000000..8a9a378
--- /dev/null
+++ b/sbin/dump/traverse.c
@@ -0,0 +1,1009 @@
+/*-
+ * Copyright (c) 1980, 1988, 1991, 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.
+ * 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 lint
+#if 0
+static char sccsid[] = "@(#)traverse.c 8.7 (Berkeley) 6/15/95";
+#endif
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include <ufs/ufs/dir.h>
+#include <ufs/ufs/dinode.h>
+#include <ufs/ffs/fs.h>
+
+#include <protocols/dumprestore.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <timeconv.h>
+#include <unistd.h>
+
+#include "dump.h"
+
+union dinode {
+ struct ufs1_dinode dp1;
+ struct ufs2_dinode dp2;
+};
+#define DIP(dp, field) \
+ ((sblock->fs_magic == FS_UFS1_MAGIC) ? \
+ (dp)->dp1.field : (dp)->dp2.field)
+#define DIP_SET(dp, field, val) do {\
+ if (sblock->fs_magic == FS_UFS1_MAGIC) \
+ (dp)->dp1.field = (val); \
+ else \
+ (dp)->dp2.field = (val); \
+ } while (0)
+
+#define HASDUMPEDFILE 0x1
+#define HASSUBDIRS 0x2
+
+static int dirindir(ino_t ino, ufs2_daddr_t blkno, int level, long *size,
+ long *tapesize, int nodump, ino_t maxino);
+static void dmpindir(union dinode *dp, ino_t ino, ufs2_daddr_t blk, int level,
+ off_t *size);
+static void ufs1_blksout(ufs1_daddr_t *blkp, int frags, ino_t ino);
+static void ufs2_blksout(union dinode *dp, ufs2_daddr_t *blkp, int frags,
+ ino_t ino, int last);
+static int appendextdata(union dinode *dp);
+static void writeextdata(union dinode *dp, ino_t ino, int added);
+static int searchdir(ino_t ino, ufs2_daddr_t blkno, long size, long filesize,
+ long *tapesize, int nodump, ino_t maxino);
+static long blockest(union dinode *dp);
+
+/*
+ * This is an estimation of the number of TP_BSIZE blocks in the file.
+ * It estimates the number of blocks in files with holes by assuming
+ * that all of the blocks accounted for by di_blocks are data blocks
+ * (when some of the blocks are usually used for indirect pointers);
+ * hence the estimate may be high.
+ */
+static long
+blockest(union dinode *dp)
+{
+ long blkest, sizeest;
+
+ /*
+ * dp->di_size is the size of the file in bytes.
+ * dp->di_blocks stores the number of sectors actually in the file.
+ * If there are more sectors than the size would indicate, this just
+ * means that there are indirect blocks in the file or unused
+ * sectors in the last file block; we can safely ignore these
+ * (blkest = sizeest below).
+ * If the file is bigger than the number of sectors would indicate,
+ * then the file has holes in it. In this case we must use the
+ * block count to estimate the number of data blocks used, but
+ * we use the actual size for estimating the number of indirect
+ * dump blocks (sizeest vs. blkest in the indirect block
+ * calculation).
+ */
+ if ((DIP(dp, di_flags) & SF_SNAPSHOT) != 0)
+ return (1);
+ blkest = howmany(dbtob(DIP(dp, di_blocks)), TP_BSIZE);
+ sizeest = howmany(DIP(dp, di_size), TP_BSIZE);
+ if (blkest > sizeest)
+ blkest = sizeest;
+ if (DIP(dp, di_size) > sblock->fs_bsize * NDADDR) {
+ /* calculate the number of indirect blocks on the dump tape */
+ blkest +=
+ howmany(sizeest - NDADDR * sblock->fs_bsize / TP_BSIZE,
+ TP_NINDIR);
+ }
+ return (blkest + 1);
+}
+
+/* Auxiliary macro to pick up files changed since previous dump. */
+#define CHANGEDSINCE(dp, t) \
+ (DIP(dp, di_mtime) >= (t) || DIP(dp, di_ctime) >= (t))
+
+/* The WANTTODUMP macro decides whether a file should be dumped. */
+#ifdef UF_NODUMP
+#define WANTTODUMP(dp) \
+ (CHANGEDSINCE(dp, spcl.c_ddate) && \
+ (nonodump || (DIP(dp, di_flags) & UF_NODUMP) != UF_NODUMP))
+#else
+#define WANTTODUMP(dp) CHANGEDSINCE(dp, spcl.c_ddate)
+#endif
+
+/*
+ * Dump pass 1.
+ *
+ * Walk the inode list for a file system to find all allocated inodes
+ * that have been modified since the previous dump time. Also, find all
+ * the directories in the file system.
+ */
+int
+mapfiles(ino_t maxino, long *tapesize)
+{
+ int i, cg, mode, inosused;
+ int anydirskipped = 0;
+ union dinode *dp;
+ struct cg *cgp;
+ ino_t ino;
+ u_char *cp;
+
+ if ((cgp = malloc(sblock->fs_cgsize)) == NULL)
+ quit("mapfiles: cannot allocate memory.\n");
+ for (cg = 0; cg < sblock->fs_ncg; cg++) {
+ ino = cg * sblock->fs_ipg;
+ bread(fsbtodb(sblock, cgtod(sblock, cg)), (char *)cgp,
+ sblock->fs_cgsize);
+ if (sblock->fs_magic == FS_UFS2_MAGIC)
+ inosused = cgp->cg_initediblk;
+ else
+ inosused = sblock->fs_ipg;
+ /*
+ * If we are using soft updates, then we can trust the
+ * cylinder group inode allocation maps to tell us which
+ * inodes are allocated. We will scan the used inode map
+ * to find the inodes that are really in use, and then
+ * read only those inodes in from disk.
+ */
+ if (sblock->fs_flags & FS_DOSOFTDEP) {
+ if (!cg_chkmagic(cgp))
+ quit("mapfiles: cg %d: bad magic number\n", cg);
+ cp = &cg_inosused(cgp)[(inosused - 1) / CHAR_BIT];
+ for ( ; inosused > 0; inosused -= CHAR_BIT, cp--) {
+ if (*cp == 0)
+ continue;
+ for (i = 1 << (CHAR_BIT - 1); i > 0; i >>= 1) {
+ if (*cp & i)
+ break;
+ inosused--;
+ }
+ break;
+ }
+ if (inosused <= 0)
+ continue;
+ }
+ for (i = 0; i < inosused; i++, ino++) {
+ if (ino < ROOTINO ||
+ (dp = getino(ino, &mode)) == NULL ||
+ (mode & IFMT) == 0)
+ continue;
+ if (ino >= maxino) {
+ msg("Skipping inode %ju >= maxino %ju\n",
+ (uintmax_t)ino, (uintmax_t)maxino);
+ continue;
+ }
+ /*
+ * Everything must go in usedinomap so that a check
+ * for "in dumpdirmap but not in usedinomap" to detect
+ * dirs with nodump set has a chance of succeeding
+ * (this is used in mapdirs()).
+ */
+ SETINO(ino, usedinomap);
+ if (mode == IFDIR)
+ SETINO(ino, dumpdirmap);
+ if (WANTTODUMP(dp)) {
+ SETINO(ino, dumpinomap);
+ if (mode != IFREG &&
+ mode != IFDIR &&
+ mode != IFLNK)
+ *tapesize += 1;
+ else
+ *tapesize += blockest(dp);
+ continue;
+ }
+ if (mode == IFDIR) {
+ if (!nonodump &&
+ (DIP(dp, di_flags) & UF_NODUMP))
+ CLRINO(ino, usedinomap);
+ anydirskipped = 1;
+ }
+ }
+ }
+ /*
+ * Restore gets very upset if the root is not dumped,
+ * so ensure that it always is dumped.
+ */
+ SETINO(ROOTINO, dumpinomap);
+ return (anydirskipped);
+}
+
+/*
+ * Dump pass 2.
+ *
+ * Scan each directory on the file system to see if it has any modified
+ * files in it. If it does, and has not already been added to the dump
+ * list (because it was itself modified), then add it. If a directory
+ * has not been modified itself, contains no modified files and has no
+ * subdirectories, then it can be deleted from the dump list and from
+ * the list of directories. By deleting it from the list of directories,
+ * its parent may now qualify for the same treatment on this or a later
+ * pass using this algorithm.
+ */
+int
+mapdirs(ino_t maxino, long *tapesize)
+{
+ union dinode *dp;
+ int i, isdir, nodump;
+ char *map;
+ ino_t ino;
+ union dinode di;
+ long filesize;
+ int ret, change = 0;
+
+ isdir = 0; /* XXX just to get gcc to shut up */
+ for (map = dumpdirmap, ino = 1; ino < maxino; ino++) {
+ if (((ino - 1) % CHAR_BIT) == 0) /* map is offset by 1 */
+ isdir = *map++;
+ else
+ isdir >>= 1;
+ /*
+ * If a directory has been removed from usedinomap, it
+ * either has the nodump flag set, or has inherited
+ * it. Although a directory can't be in dumpinomap if
+ * it isn't in usedinomap, we have to go through it to
+ * propagate the nodump flag.
+ */
+ nodump = !nonodump && (TSTINO(ino, usedinomap) == 0);
+ if ((isdir & 1) == 0 || (TSTINO(ino, dumpinomap) && !nodump))
+ continue;
+ dp = getino(ino, &i);
+ /*
+ * inode buf may change in searchdir().
+ */
+ if (sblock->fs_magic == FS_UFS1_MAGIC)
+ di.dp1 = dp->dp1;
+ else
+ di.dp2 = dp->dp2;
+ filesize = DIP(&di, di_size);
+ for (ret = 0, i = 0; filesize > 0 && i < NDADDR; i++) {
+ if (DIP(&di, di_db[i]) != 0)
+ ret |= searchdir(ino, DIP(&di, di_db[i]),
+ (long)sblksize(sblock, DIP(&di, di_size),
+ i), filesize, tapesize, nodump, maxino);
+ if (ret & HASDUMPEDFILE)
+ filesize = 0;
+ else
+ filesize -= sblock->fs_bsize;
+ }
+ for (i = 0; filesize > 0 && i < NIADDR; i++) {
+ if (DIP(&di, di_ib[i]) == 0)
+ continue;
+ ret |= dirindir(ino, DIP(&di, di_ib[i]), i, &filesize,
+ tapesize, nodump, maxino);
+ }
+ if (ret & HASDUMPEDFILE) {
+ SETINO(ino, dumpinomap);
+ *tapesize += blockest(&di);
+ change = 1;
+ continue;
+ }
+ if (nodump) {
+ if (ret & HASSUBDIRS)
+ change = 1; /* subdirs inherit nodump */
+ CLRINO(ino, dumpdirmap);
+ } else if ((ret & HASSUBDIRS) == 0)
+ if (!TSTINO(ino, dumpinomap)) {
+ CLRINO(ino, dumpdirmap);
+ change = 1;
+ }
+ }
+ return (change);
+}
+
+/*
+ * Read indirect blocks, and pass the data blocks to be searched
+ * as directories. Quit as soon as any entry is found that will
+ * require the directory to be dumped.
+ */
+static int
+dirindir(
+ ino_t ino,
+ ufs2_daddr_t blkno,
+ int ind_level,
+ long *filesize,
+ long *tapesize,
+ int nodump,
+ ino_t maxino)
+{
+ union {
+ ufs1_daddr_t ufs1[MAXBSIZE / sizeof(ufs1_daddr_t)];
+ ufs2_daddr_t ufs2[MAXBSIZE / sizeof(ufs2_daddr_t)];
+ } idblk;
+ int ret = 0;
+ int i;
+
+ bread(fsbtodb(sblock, blkno), (char *)&idblk, (int)sblock->fs_bsize);
+ if (ind_level <= 0) {
+ for (i = 0; *filesize > 0 && i < NINDIR(sblock); i++) {
+ if (sblock->fs_magic == FS_UFS1_MAGIC)
+ blkno = idblk.ufs1[i];
+ else
+ blkno = idblk.ufs2[i];
+ if (blkno != 0)
+ ret |= searchdir(ino, blkno, sblock->fs_bsize,
+ *filesize, tapesize, nodump, maxino);
+ if (ret & HASDUMPEDFILE)
+ *filesize = 0;
+ else
+ *filesize -= sblock->fs_bsize;
+ }
+ return (ret);
+ }
+ ind_level--;
+ for (i = 0; *filesize > 0 && i < NINDIR(sblock); i++) {
+ if (sblock->fs_magic == FS_UFS1_MAGIC)
+ blkno = idblk.ufs1[i];
+ else
+ blkno = idblk.ufs2[i];
+ if (blkno != 0)
+ ret |= dirindir(ino, blkno, ind_level, filesize,
+ tapesize, nodump, maxino);
+ }
+ return (ret);
+}
+
+/*
+ * Scan a disk block containing directory information looking to see if
+ * any of the entries are on the dump list and to see if the directory
+ * contains any subdirectories.
+ */
+static int
+searchdir(
+ ino_t ino,
+ ufs2_daddr_t blkno,
+ long size,
+ long filesize,
+ long *tapesize,
+ int nodump,
+ ino_t maxino)
+{
+ int mode;
+ struct direct *dp;
+ union dinode *ip;
+ long loc, ret = 0;
+ static caddr_t dblk;
+
+ if (dblk == NULL && (dblk = malloc(sblock->fs_bsize)) == NULL)
+ quit("searchdir: cannot allocate indirect memory.\n");
+ bread(fsbtodb(sblock, blkno), dblk, (int)size);
+ if (filesize < size)
+ size = filesize;
+ for (loc = 0; loc < size; ) {
+ dp = (struct direct *)(dblk + loc);
+ if (dp->d_reclen == 0) {
+ msg("corrupted directory, inumber %ju\n",
+ (uintmax_t)ino);
+ break;
+ }
+ loc += dp->d_reclen;
+ if (dp->d_ino == 0)
+ continue;
+ if (dp->d_ino >= maxino) {
+ msg("corrupted directory entry, d_ino %ju >= %ju\n",
+ (uintmax_t)dp->d_ino, (uintmax_t)maxino);
+ break;
+ }
+ if (dp->d_name[0] == '.') {
+ if (dp->d_name[1] == '\0')
+ continue;
+ if (dp->d_name[1] == '.' && dp->d_name[2] == '\0')
+ continue;
+ }
+ if (nodump) {
+ ip = getino(dp->d_ino, &mode);
+ if (TSTINO(dp->d_ino, dumpinomap)) {
+ CLRINO(dp->d_ino, dumpinomap);
+ *tapesize -= blockest(ip);
+ }
+ /*
+ * Add back to dumpdirmap and remove from usedinomap
+ * to propagate nodump.
+ */
+ if (mode == IFDIR) {
+ SETINO(dp->d_ino, dumpdirmap);
+ CLRINO(dp->d_ino, usedinomap);
+ ret |= HASSUBDIRS;
+ }
+ } else {
+ if (TSTINO(dp->d_ino, dumpinomap)) {
+ ret |= HASDUMPEDFILE;
+ if (ret & HASSUBDIRS)
+ break;
+ }
+ if (TSTINO(dp->d_ino, dumpdirmap)) {
+ ret |= HASSUBDIRS;
+ if (ret & HASDUMPEDFILE)
+ break;
+ }
+ }
+ }
+ return (ret);
+}
+
+/*
+ * Dump passes 3 and 4.
+ *
+ * Dump the contents of an inode to tape.
+ */
+void
+dumpino(union dinode *dp, ino_t ino)
+{
+ int ind_level, cnt, last, added;
+ off_t size;
+ char buf[TP_BSIZE];
+
+ if (newtape) {
+ newtape = 0;
+ dumpmap(dumpinomap, TS_BITS, ino);
+ }
+ CLRINO(ino, dumpinomap);
+ /*
+ * Zero out the size of a snapshot so that it will be dumped
+ * as a zero length file.
+ */
+ if ((DIP(dp, di_flags) & SF_SNAPSHOT) != 0) {
+ DIP_SET(dp, di_size, 0);
+ DIP_SET(dp, di_flags, DIP(dp, di_flags) & ~SF_SNAPSHOT);
+ }
+ if (sblock->fs_magic == FS_UFS1_MAGIC) {
+ spcl.c_mode = dp->dp1.di_mode;
+ spcl.c_size = dp->dp1.di_size;
+ spcl.c_extsize = 0;
+ spcl.c_atime = _time32_to_time(dp->dp1.di_atime);
+ spcl.c_atimensec = dp->dp1.di_atimensec;
+ spcl.c_mtime = _time32_to_time(dp->dp1.di_mtime);
+ spcl.c_mtimensec = dp->dp1.di_mtimensec;
+ spcl.c_birthtime = 0;
+ spcl.c_birthtimensec = 0;
+ spcl.c_rdev = dp->dp1.di_rdev;
+ spcl.c_file_flags = dp->dp1.di_flags;
+ spcl.c_uid = dp->dp1.di_uid;
+ spcl.c_gid = dp->dp1.di_gid;
+ } else {
+ spcl.c_mode = dp->dp2.di_mode;
+ spcl.c_size = dp->dp2.di_size;
+ spcl.c_extsize = dp->dp2.di_extsize;
+ spcl.c_atime = _time64_to_time(dp->dp2.di_atime);
+ spcl.c_atimensec = dp->dp2.di_atimensec;
+ spcl.c_mtime = _time64_to_time(dp->dp2.di_mtime);
+ spcl.c_mtimensec = dp->dp2.di_mtimensec;
+ spcl.c_birthtime = _time64_to_time(dp->dp2.di_birthtime);
+ spcl.c_birthtimensec = dp->dp2.di_birthnsec;
+ spcl.c_rdev = dp->dp2.di_rdev;
+ spcl.c_file_flags = dp->dp2.di_flags;
+ spcl.c_uid = dp->dp2.di_uid;
+ spcl.c_gid = dp->dp2.di_gid;
+ }
+ spcl.c_type = TS_INODE;
+ spcl.c_count = 0;
+ switch (DIP(dp, di_mode) & S_IFMT) {
+
+ case 0:
+ /*
+ * Freed inode.
+ */
+ return;
+
+ case S_IFLNK:
+ /*
+ * Check for short symbolic link.
+ */
+ if (DIP(dp, di_size) > 0 &&
+ DIP(dp, di_size) < sblock->fs_maxsymlinklen) {
+ spcl.c_addr[0] = 1;
+ spcl.c_count = 1;
+ added = appendextdata(dp);
+ writeheader(ino);
+ if (sblock->fs_magic == FS_UFS1_MAGIC)
+ memmove(buf, (caddr_t)dp->dp1.di_db,
+ (u_long)DIP(dp, di_size));
+ else
+ memmove(buf, (caddr_t)dp->dp2.di_db,
+ (u_long)DIP(dp, di_size));
+ buf[DIP(dp, di_size)] = '\0';
+ writerec(buf, 0);
+ writeextdata(dp, ino, added);
+ return;
+ }
+ /* FALLTHROUGH */
+
+ case S_IFDIR:
+ case S_IFREG:
+ if (DIP(dp, di_size) > 0)
+ break;
+ /* FALLTHROUGH */
+
+ case S_IFIFO:
+ case S_IFSOCK:
+ case S_IFCHR:
+ case S_IFBLK:
+ added = appendextdata(dp);
+ writeheader(ino);
+ writeextdata(dp, ino, added);
+ return;
+
+ default:
+ msg("Warning: undefined file type 0%o\n",
+ DIP(dp, di_mode) & IFMT);
+ return;
+ }
+ if (DIP(dp, di_size) > NDADDR * sblock->fs_bsize) {
+ cnt = NDADDR * sblock->fs_frag;
+ last = 0;
+ } else {
+ cnt = howmany(DIP(dp, di_size), sblock->fs_fsize);
+ last = 1;
+ }
+ if (sblock->fs_magic == FS_UFS1_MAGIC)
+ ufs1_blksout(&dp->dp1.di_db[0], cnt, ino);
+ else
+ ufs2_blksout(dp, &dp->dp2.di_db[0], cnt, ino, last);
+ if ((size = DIP(dp, di_size) - NDADDR * sblock->fs_bsize) <= 0)
+ return;
+ for (ind_level = 0; ind_level < NIADDR; ind_level++) {
+ dmpindir(dp, ino, DIP(dp, di_ib[ind_level]), ind_level, &size);
+ if (size <= 0)
+ return;
+ }
+}
+
+/*
+ * Read indirect blocks, and pass the data blocks to be dumped.
+ */
+static void
+dmpindir(union dinode *dp, ino_t ino, ufs2_daddr_t blk, int ind_level,
+ off_t *size)
+{
+ union {
+ ufs1_daddr_t ufs1[MAXBSIZE / sizeof(ufs1_daddr_t)];
+ ufs2_daddr_t ufs2[MAXBSIZE / sizeof(ufs2_daddr_t)];
+ } idblk;
+ int i, cnt, last;
+
+ if (blk != 0)
+ bread(fsbtodb(sblock, blk), (char *)&idblk,
+ (int)sblock->fs_bsize);
+ else
+ memset(&idblk, 0, sblock->fs_bsize);
+ if (ind_level <= 0) {
+ if (*size > NINDIR(sblock) * sblock->fs_bsize) {
+ cnt = NINDIR(sblock) * sblock->fs_frag;
+ last = 0;
+ } else {
+ cnt = howmany(*size, sblock->fs_fsize);
+ last = 1;
+ }
+ *size -= NINDIR(sblock) * sblock->fs_bsize;
+ if (sblock->fs_magic == FS_UFS1_MAGIC)
+ ufs1_blksout(idblk.ufs1, cnt, ino);
+ else
+ ufs2_blksout(dp, idblk.ufs2, cnt, ino, last);
+ return;
+ }
+ ind_level--;
+ for (i = 0; i < NINDIR(sblock); i++) {
+ if (sblock->fs_magic == FS_UFS1_MAGIC)
+ dmpindir(dp, ino, idblk.ufs1[i], ind_level, size);
+ else
+ dmpindir(dp, ino, idblk.ufs2[i], ind_level, size);
+ if (*size <= 0)
+ return;
+ }
+}
+
+/*
+ * Collect up the data into tape record sized buffers and output them.
+ */
+static void
+ufs1_blksout(ufs1_daddr_t *blkp, int frags, ino_t ino)
+{
+ ufs1_daddr_t *bp;
+ int i, j, count, blks, tbperdb;
+
+ blks = howmany(frags * sblock->fs_fsize, TP_BSIZE);
+ tbperdb = sblock->fs_bsize >> tp_bshift;
+ for (i = 0; i < blks; i += TP_NINDIR) {
+ if (i + TP_NINDIR > blks)
+ count = blks;
+ else
+ count = i + TP_NINDIR;
+ for (j = i; j < count; j++)
+ if (blkp[j / tbperdb] != 0)
+ spcl.c_addr[j - i] = 1;
+ else
+ spcl.c_addr[j - i] = 0;
+ spcl.c_count = count - i;
+ writeheader(ino);
+ bp = &blkp[i / tbperdb];
+ for (j = i; j < count; j += tbperdb, bp++)
+ if (*bp != 0) {
+ if (j + tbperdb <= count)
+ dumpblock(*bp, (int)sblock->fs_bsize);
+ else
+ dumpblock(*bp, (count - j) * TP_BSIZE);
+ }
+ spcl.c_type = TS_ADDR;
+ }
+}
+
+/*
+ * Collect up the data into tape record sized buffers and output them.
+ */
+static void
+ufs2_blksout(union dinode *dp, ufs2_daddr_t *blkp, int frags, ino_t ino,
+ int last)
+{
+ ufs2_daddr_t *bp;
+ int i, j, count, resid, blks, tbperdb, added;
+ static int writingextdata = 0;
+
+ /*
+ * Calculate the number of TP_BSIZE blocks to be dumped.
+ * For filesystems with a fragment size bigger than TP_BSIZE,
+ * only part of the final fragment may need to be dumped.
+ */
+ blks = howmany(frags * sblock->fs_fsize, TP_BSIZE);
+ if (last) {
+ if (writingextdata)
+ resid = howmany(fragoff(sblock, spcl.c_extsize),
+ TP_BSIZE);
+ else
+ resid = howmany(fragoff(sblock, dp->dp2.di_size),
+ TP_BSIZE);
+ if (resid > 0)
+ blks -= howmany(sblock->fs_fsize, TP_BSIZE) - resid;
+ }
+ tbperdb = sblock->fs_bsize >> tp_bshift;
+ for (i = 0; i < blks; i += TP_NINDIR) {
+ if (i + TP_NINDIR > blks)
+ count = blks;
+ else
+ count = i + TP_NINDIR;
+ for (j = i; j < count; j++)
+ if (blkp[j / tbperdb] != 0)
+ spcl.c_addr[j - i] = 1;
+ else
+ spcl.c_addr[j - i] = 0;
+ spcl.c_count = count - i;
+ if (last && count == blks && !writingextdata)
+ added = appendextdata(dp);
+ writeheader(ino);
+ bp = &blkp[i / tbperdb];
+ for (j = i; j < count; j += tbperdb, bp++)
+ if (*bp != 0) {
+ if (j + tbperdb <= count)
+ dumpblock(*bp, (int)sblock->fs_bsize);
+ else
+ dumpblock(*bp, (count - j) * TP_BSIZE);
+ }
+ spcl.c_type = TS_ADDR;
+ spcl.c_count = 0;
+ if (last && count == blks && !writingextdata) {
+ writingextdata = 1;
+ writeextdata(dp, ino, added);
+ writingextdata = 0;
+ }
+ }
+}
+
+/*
+ * If there is room in the current block for the extended attributes
+ * as well as the file data, update the header to reflect the added
+ * attribute data at the end. Attributes are placed at the end so that
+ * old versions of restore will correctly restore the file and simply
+ * discard the extra data at the end that it does not understand.
+ * The attribute data is dumped following the file data by the
+ * writeextdata() function (below).
+ */
+static int
+appendextdata(union dinode *dp)
+{
+ int i, blks, tbperdb;
+
+ /*
+ * If no extended attributes, there is nothing to do.
+ */
+ if (spcl.c_extsize == 0)
+ return (0);
+ /*
+ * If there is not enough room at the end of this block
+ * to add the extended attributes, then rather than putting
+ * part of them here, we simply push them entirely into a
+ * new block rather than putting some here and some later.
+ */
+ if (spcl.c_extsize > NXADDR * sblock->fs_bsize)
+ blks = howmany(NXADDR * sblock->fs_bsize, TP_BSIZE);
+ else
+ blks = howmany(spcl.c_extsize, TP_BSIZE);
+ if (spcl.c_count + blks > TP_NINDIR)
+ return (0);
+ /*
+ * Update the block map in the header to indicate the added
+ * extended attribute. They will be appended after the file
+ * data by the writeextdata() routine.
+ */
+ tbperdb = sblock->fs_bsize >> tp_bshift;
+ for (i = 0; i < blks; i++)
+ if (&dp->dp2.di_extb[i / tbperdb] != 0)
+ spcl.c_addr[spcl.c_count + i] = 1;
+ else
+ spcl.c_addr[spcl.c_count + i] = 0;
+ spcl.c_count += blks;
+ return (blks);
+}
+
+/*
+ * Dump the extended attribute data. If there was room in the file
+ * header, then all we need to do is output the data blocks. If there
+ * was not room in the file header, then an additional TS_ADDR header
+ * is created to hold the attribute data.
+ */
+static void
+writeextdata(union dinode *dp, ino_t ino, int added)
+{
+ int i, frags, blks, tbperdb, last;
+ ufs2_daddr_t *bp;
+ off_t size;
+
+ /*
+ * If no extended attributes, there is nothing to do.
+ */
+ if (spcl.c_extsize == 0)
+ return;
+ /*
+ * If there was no room in the file block for the attributes,
+ * dump them out in a new block, otherwise just dump the data.
+ */
+ if (added == 0) {
+ if (spcl.c_extsize > NXADDR * sblock->fs_bsize) {
+ frags = NXADDR * sblock->fs_frag;
+ last = 0;
+ } else {
+ frags = howmany(spcl.c_extsize, sblock->fs_fsize);
+ last = 1;
+ }
+ ufs2_blksout(dp, &dp->dp2.di_extb[0], frags, ino, last);
+ } else {
+ if (spcl.c_extsize > NXADDR * sblock->fs_bsize)
+ blks = howmany(NXADDR * sblock->fs_bsize, TP_BSIZE);
+ else
+ blks = howmany(spcl.c_extsize, TP_BSIZE);
+ tbperdb = sblock->fs_bsize >> tp_bshift;
+ for (i = 0; i < blks; i += tbperdb) {
+ bp = &dp->dp2.di_extb[i / tbperdb];
+ if (*bp != 0) {
+ if (i + tbperdb <= blks)
+ dumpblock(*bp, (int)sblock->fs_bsize);
+ else
+ dumpblock(*bp, (blks - i) * TP_BSIZE);
+ }
+ }
+
+ }
+ /*
+ * If an indirect block is added for extended attributes, then
+ * di_exti below should be changed to the structure element
+ * that references the extended attribute indirect block. This
+ * definition is here only to make it compile without complaint.
+ */
+#define di_exti di_spare[0]
+ /*
+ * If the extended attributes fall into an indirect block,
+ * dump it as well.
+ */
+ if ((size = spcl.c_extsize - NXADDR * sblock->fs_bsize) > 0)
+ dmpindir(dp, ino, dp->dp2.di_exti, 0, &size);
+}
+
+/*
+ * Dump a map to the tape.
+ */
+void
+dumpmap(char *map, int type, ino_t ino)
+{
+ int i;
+ char *cp;
+
+ spcl.c_type = type;
+ spcl.c_count = howmany(mapsize * sizeof(char), TP_BSIZE);
+ writeheader(ino);
+ for (i = 0, cp = map; i < spcl.c_count; i++, cp += TP_BSIZE)
+ writerec(cp, 0);
+}
+
+/*
+ * Write a header record to the dump tape.
+ */
+void
+writeheader(ino_t ino)
+{
+ int32_t sum, cnt, *lp;
+
+ if (rsync_friendly >= 2) {
+ /* don't track changes to access time */
+ spcl.c_atime = spcl.c_mtime;
+ spcl.c_atimensec = spcl.c_mtimensec;
+ }
+ spcl.c_inumber = ino;
+ spcl.c_magic = FS_UFS2_MAGIC;
+ spcl.c_checksum = 0;
+ lp = (int32_t *)&spcl;
+ sum = 0;
+ cnt = sizeof(union u_spcl) / (4 * sizeof(int32_t));
+ while (--cnt >= 0) {
+ sum += *lp++;
+ sum += *lp++;
+ sum += *lp++;
+ sum += *lp++;
+ }
+ spcl.c_checksum = CHECKSUM - sum;
+ writerec((char *)&spcl, 1);
+}
+
+union dinode *
+getino(ino_t inum, int *modep)
+{
+ static ino_t minino, maxino;
+ static caddr_t inoblock;
+ struct ufs1_dinode *dp1;
+ struct ufs2_dinode *dp2;
+
+ if (inoblock == NULL && (inoblock = malloc(sblock->fs_bsize)) == NULL)
+ quit("cannot allocate inode memory.\n");
+ curino = inum;
+ if (inum >= minino && inum < maxino)
+ goto gotit;
+ bread(fsbtodb(sblock, ino_to_fsba(sblock, inum)), inoblock,
+ (int)sblock->fs_bsize);
+ minino = inum - (inum % INOPB(sblock));
+ maxino = minino + INOPB(sblock);
+gotit:
+ if (sblock->fs_magic == FS_UFS1_MAGIC) {
+ dp1 = &((struct ufs1_dinode *)inoblock)[inum - minino];
+ *modep = (dp1->di_mode & IFMT);
+ return ((union dinode *)dp1);
+ }
+ dp2 = &((struct ufs2_dinode *)inoblock)[inum - minino];
+ *modep = (dp2->di_mode & IFMT);
+ return ((union dinode *)dp2);
+}
+
+/*
+ * Read a chunk of data from the disk.
+ * Try to recover from hard errors by reading in sector sized pieces.
+ * Error recovery is attempted at most BREADEMAX times before seeking
+ * consent from the operator to continue.
+ */
+int breaderrors = 0;
+#define BREADEMAX 32
+
+void
+bread(ufs2_daddr_t blkno, char *buf, int size)
+{
+ int secsize, bytes, resid, xfer, base, cnt, i;
+ static char *tmpbuf;
+ off_t offset;
+
+loop:
+ offset = blkno << dev_bshift;
+ secsize = sblock->fs_fsize;
+ base = offset % secsize;
+ resid = size % secsize;
+ /*
+ * If the transfer request starts or ends on a non-sector
+ * boundary, we must read the entire sector and copy out
+ * just the part that we need.
+ */
+ if (base == 0 && resid == 0) {
+ cnt = cread(diskfd, buf, size, offset);
+ if (cnt == size)
+ return;
+ } else {
+ if (tmpbuf == NULL && (tmpbuf = malloc(secsize)) == 0)
+ quit("buffer malloc failed\n");
+ xfer = 0;
+ bytes = size;
+ if (base != 0) {
+ cnt = cread(diskfd, tmpbuf, secsize, offset - base);
+ if (cnt != secsize)
+ goto bad;
+ xfer = MIN(secsize - base, size);
+ offset += xfer;
+ bytes -= xfer;
+ resid = bytes % secsize;
+ memcpy(buf, &tmpbuf[base], xfer);
+ }
+ if (bytes >= secsize) {
+ cnt = cread(diskfd, &buf[xfer], bytes - resid, offset);
+ if (cnt != bytes - resid)
+ goto bad;
+ xfer += cnt;
+ offset += cnt;
+ }
+ if (resid == 0)
+ return;
+ cnt = cread(diskfd, tmpbuf, secsize, offset);
+ if (cnt == secsize) {
+ memcpy(&buf[xfer], tmpbuf, resid);
+ return;
+ }
+ }
+bad:
+ if (blkno + (size / dev_bsize) > fsbtodb(sblock, sblock->fs_size)) {
+ /*
+ * Trying to read the final fragment.
+ *
+ * NB - dump only works in TP_BSIZE blocks, hence
+ * rounds `dev_bsize' fragments up to TP_BSIZE pieces.
+ * It should be smarter about not actually trying to
+ * read more than it can get, but for the time being
+ * we punt and scale back the read only when it gets
+ * us into trouble. (mkm 9/25/83)
+ */
+ size -= dev_bsize;
+ goto loop;
+ }
+ if (cnt == -1)
+ msg("read error from %s: %s: [block %jd]: count=%d\n",
+ disk, strerror(errno), (intmax_t)blkno, size);
+ else
+ msg("short read error from %s: [block %jd]: count=%d, got=%d\n",
+ disk, (intmax_t)blkno, size, cnt);
+ if (++breaderrors > BREADEMAX) {
+ msg("More than %d block read errors from %s\n",
+ BREADEMAX, disk);
+ broadcast("DUMP IS AILING!\n");
+ msg("This is an unrecoverable error.\n");
+ if (!query("Do you want to attempt to continue?")){
+ dumpabort(0);
+ /*NOTREACHED*/
+ } else
+ breaderrors = 0;
+ }
+ /*
+ * Zero buffer, then try to read each sector of buffer separately,
+ * and bypass the cache.
+ */
+ memset(buf, 0, size);
+ for (i = 0; i < size; i += dev_bsize, buf += dev_bsize, blkno++) {
+ if ((cnt = pread(diskfd, buf, (int)dev_bsize,
+ ((off_t)blkno << dev_bshift))) == dev_bsize)
+ continue;
+ if (cnt == -1) {
+ msg("read error from %s: %s: [sector %jd]: count=%ld\n",
+ disk, strerror(errno), (intmax_t)blkno, dev_bsize);
+ continue;
+ }
+ msg("short read from %s: [sector %jd]: count=%ld, got=%d\n",
+ disk, (intmax_t)blkno, dev_bsize, cnt);
+ }
+}
diff --git a/sbin/dump/unctime.c b/sbin/dump/unctime.c
new file mode 100644
index 0000000..3720ebf
--- /dev/null
+++ b/sbin/dump/unctime.c
@@ -0,0 +1,56 @@
+/*-
+ * Copyright (c) 1980, 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.
+ * 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 lint
+#if 0
+static char sccsid[] = "@(#)unctime.c 8.2 (Berkeley) 6/14/94";
+#endif
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <time.h>
+
+/*
+ * Convert a ctime(3) format string into a system format date.
+ * Return the date thus calculated.
+ *
+ * Return -1 if the string is not in ctime format.
+ */
+time_t
+unctime(char *str)
+{
+ struct tm then;
+
+ str = strptime(str, "%a %b %e %T %Y", &then);
+ if (str == NULL || (*str != '\n' && *str != '\0'))
+ return ((time_t)-1);
+ then.tm_isdst = -1;
+ return (mktime(&then));
+}
diff --git a/sbin/dumpfs/Makefile b/sbin/dumpfs/Makefile
new file mode 100644
index 0000000..87eb89b
--- /dev/null
+++ b/sbin/dumpfs/Makefile
@@ -0,0 +1,9 @@
+# @(#)Makefile 8.1 (Berkeley) 6/5/93
+# $FreeBSD$
+
+PROG= dumpfs
+WARNS?= 2
+LIBADD= ufs
+MAN= dumpfs.8
+
+.include <bsd.prog.mk>
diff --git a/sbin/dumpfs/dumpfs.8 b/sbin/dumpfs/dumpfs.8
new file mode 100644
index 0000000..feb8758
--- /dev/null
+++ b/sbin/dumpfs/dumpfs.8
@@ -0,0 +1,111 @@
+.\" Copyright (c) 1983, 1991, 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.
+.\" 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.
+.\"
+.\" @(#)dumpfs.8 8.1 (Berkeley) 6/5/93
+.\" $FreeBSD$
+.\"
+.Dd May 16, 2013
+.Dt DUMPFS 8
+.Os
+.Sh NAME
+.Nm dumpfs
+.Nd dump UFS file system information
+.Sh SYNOPSIS
+.Nm
+.Op Fl f
+.Op Fl l
+.Op Fl m
+.Ar filesys | device
+.Sh DESCRIPTION
+The
+.Nm
+utility prints out the UFS super block and cylinder group information
+for the file system or special device specified, unless the
+.Fl f ,
+.Fl l
+or
+.Fl m
+flag is specified.
+The listing is very long and detailed.
+This
+command is useful mostly for finding out certain file system
+information such as the file system block size and minimum
+free space percentage.
+.Pp
+If
+.Fl f
+is specified, a sorted list of all free fragments and free fragment ranges,
+as represented in cylinder group block free lists, is printed.
+If the flag is specified twice, contiguous free fragments are not collapsed
+into ranges and instead printed in a simple list.
+Fragment numbers may be converted to raw byte offsets by multiplying by the
+fragment size, which may be useful when recovering deleted data.
+.Pp
+If
+.Fl l
+is specified, the pathname to the file system's container derived from
+its unique identifier is printed.
+.Pp
+If
+.Fl m
+is specified, a
+.Xr newfs 8
+command is printed that can be used to generate a new file system
+with equivalent settings.
+Please note that
+.Xr newfs 8
+options
+.Fl E ,
+.Fl R ,
+.Fl S ,
+and
+.Fl T
+are not handled and
+.Fl p
+is not useful in this case so is omitted.
+.Xr Newfs 8
+options
+.Fl n
+and
+.Fl r
+are neither checked for nor output but should be.
+The
+.Fl r
+flag is needed if the filesystem uses
+.Xr gjournal 8 .
+.Sh SEE ALSO
+.Xr disktab 5 ,
+.Xr fs 5 ,
+.Xr disklabel 8 ,
+.Xr fsck 8 ,
+.Xr newfs 8 ,
+.Xr tunefs 8
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Bx 4.2 .
diff --git a/sbin/dumpfs/dumpfs.c b/sbin/dumpfs/dumpfs.c
new file mode 100644
index 0000000..baf3d99
--- /dev/null
+++ b/sbin/dumpfs/dumpfs.c
@@ -0,0 +1,510 @@
+/*
+ * Copyright (c) 2009 Robert N. M. Watson
+ * All rights reserved.
+ *
+ * This software was developed at the University of Cambridge Computer
+ * Laboratory with support from a grant from Google, Inc.
+ *
+ * Copyright (c) 2002 Networks Associates Technology, Inc.
+ * All rights reserved.
+ *
+ * This software was developed for the FreeBSD Project by Marshall
+ * Kirk McKusick and Network Associates Laboratories, the Security
+ * Research Division of Network Associates, Inc. under DARPA/SPAWAR
+ * contract N66001-01-C-8035 ("CBOSS"), as part of the DARPA CHATS
+ * research program.
+ *
+ * Copyright (c) 1983, 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.
+ * 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 lint
+static const char copyright[] =
+"@(#) Copyright (c) 1983, 1992, 1993\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)dumpfs.c 8.5 (Berkeley) 4/29/95";
+#endif
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/time.h>
+#include <sys/disklabel.h>
+
+#include <ufs/ufs/dinode.h>
+#include <ufs/ffs/fs.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <fstab.h>
+#include <libufs.h>
+#include <paths.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#define afs disk.d_fs
+#define acg disk.d_cg
+
+static struct uufsd disk;
+
+static int dumpfs(const char *);
+static int dumpfsid(void);
+static int dumpcg(void);
+static int dumpfreespace(const char *, int);
+static void dumpfreespacecg(int);
+static int marshal(const char *);
+static void pbits(void *, int);
+static void pblklist(void *, int, off_t, int);
+static void ufserr(const char *);
+static void usage(void) __dead2;
+
+int
+main(int argc, char *argv[])
+{
+ const char *name;
+ int ch, dofreespace, domarshal, dolabel, eval;
+
+ dofreespace = domarshal = dolabel = eval = 0;
+
+ while ((ch = getopt(argc, argv, "lfm")) != -1) {
+ switch (ch) {
+ case 'f':
+ dofreespace++;
+ break;
+ case 'm':
+ domarshal = 1;
+ break;
+ case 'l':
+ dolabel = 1;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 1)
+ usage();
+ if (dofreespace && domarshal)
+ usage();
+ if (dofreespace > 2)
+ usage();
+
+ while ((name = *argv++) != NULL) {
+ if (ufs_disk_fillout(&disk, name) == -1) {
+ ufserr(name);
+ eval |= 1;
+ continue;
+ }
+ if (dofreespace)
+ eval |= dumpfreespace(name, dofreespace);
+ else if (domarshal)
+ eval |= marshal(name);
+ else if (dolabel)
+ eval |= dumpfsid();
+ else
+ eval |= dumpfs(name);
+ ufs_disk_close(&disk);
+ }
+ exit(eval);
+}
+
+static int
+dumpfsid(void)
+{
+
+ printf("%sufsid/%08x%08x\n", _PATH_DEV, afs.fs_id[0], afs.fs_id[1]);
+ return 0;
+}
+
+static int
+dumpfs(const char *name)
+{
+ time_t fstime;
+ int64_t fssize;
+ int32_t fsflags;
+ int i;
+
+ switch (disk.d_ufs) {
+ case 2:
+ fssize = afs.fs_size;
+ fstime = afs.fs_time;
+ printf("magic\t%x (UFS2)\ttime\t%s",
+ afs.fs_magic, ctime(&fstime));
+ printf("superblock location\t%jd\tid\t[ %x %x ]\n",
+ (intmax_t)afs.fs_sblockloc, afs.fs_id[0], afs.fs_id[1]);
+ printf("ncg\t%d\tsize\t%jd\tblocks\t%jd\n",
+ afs.fs_ncg, (intmax_t)fssize, (intmax_t)afs.fs_dsize);
+ break;
+ case 1:
+ fssize = afs.fs_old_size;
+ fstime = afs.fs_old_time;
+ printf("magic\t%x (UFS1)\ttime\t%s",
+ afs.fs_magic, ctime(&fstime));
+ printf("id\t[ %08x %08x ]\n", afs.fs_id[0], afs.fs_id[1]);
+ printf("ncg\t%d\tsize\t%jd\tblocks\t%jd\n",
+ afs.fs_ncg, (intmax_t)fssize, (intmax_t)afs.fs_dsize);
+ break;
+ default:
+ goto err;
+ }
+ printf("bsize\t%d\tshift\t%d\tmask\t0x%08x\n",
+ afs.fs_bsize, afs.fs_bshift, afs.fs_bmask);
+ printf("fsize\t%d\tshift\t%d\tmask\t0x%08x\n",
+ afs.fs_fsize, afs.fs_fshift, afs.fs_fmask);
+ printf("frag\t%d\tshift\t%d\tfsbtodb\t%d\n",
+ afs.fs_frag, afs.fs_fragshift, afs.fs_fsbtodb);
+ printf("minfree\t%d%%\toptim\t%s\tsymlinklen %d\n",
+ afs.fs_minfree, afs.fs_optim == FS_OPTSPACE ? "space" : "time",
+ afs.fs_maxsymlinklen);
+ switch (disk.d_ufs) {
+ case 2:
+ printf("%s %d\tmaxbpg\t%d\tmaxcontig %d\tcontigsumsize %d\n",
+ "maxbsize", afs.fs_maxbsize, afs.fs_maxbpg,
+ afs.fs_maxcontig, afs.fs_contigsumsize);
+ printf("nbfree\t%jd\tndir\t%jd\tnifree\t%jd\tnffree\t%jd\n",
+ (intmax_t)afs.fs_cstotal.cs_nbfree,
+ (intmax_t)afs.fs_cstotal.cs_ndir,
+ (intmax_t)afs.fs_cstotal.cs_nifree,
+ (intmax_t)afs.fs_cstotal.cs_nffree);
+ printf("bpg\t%d\tfpg\t%d\tipg\t%d\tunrefs\t%jd\n",
+ afs.fs_fpg / afs.fs_frag, afs.fs_fpg, afs.fs_ipg,
+ (intmax_t)afs.fs_unrefs);
+ printf("nindir\t%d\tinopb\t%d\tmaxfilesize\t%ju\n",
+ afs.fs_nindir, afs.fs_inopb,
+ (uintmax_t)afs.fs_maxfilesize);
+ printf("sbsize\t%d\tcgsize\t%d\tcsaddr\t%jd\tcssize\t%d\n",
+ afs.fs_sbsize, afs.fs_cgsize, (intmax_t)afs.fs_csaddr,
+ afs.fs_cssize);
+ break;
+ case 1:
+ printf("maxbpg\t%d\tmaxcontig %d\tcontigsumsize %d\n",
+ afs.fs_maxbpg, afs.fs_maxcontig, afs.fs_contigsumsize);
+ printf("nbfree\t%d\tndir\t%d\tnifree\t%d\tnffree\t%d\n",
+ afs.fs_old_cstotal.cs_nbfree, afs.fs_old_cstotal.cs_ndir,
+ afs.fs_old_cstotal.cs_nifree, afs.fs_old_cstotal.cs_nffree);
+ printf("cpg\t%d\tbpg\t%d\tfpg\t%d\tipg\t%d\n",
+ afs.fs_old_cpg, afs.fs_fpg / afs.fs_frag, afs.fs_fpg,
+ afs.fs_ipg);
+ printf("nindir\t%d\tinopb\t%d\tnspf\t%d\tmaxfilesize\t%ju\n",
+ afs.fs_nindir, afs.fs_inopb, afs.fs_old_nspf,
+ (uintmax_t)afs.fs_maxfilesize);
+ printf("sbsize\t%d\tcgsize\t%d\tcgoffset %d\tcgmask\t0x%08x\n",
+ afs.fs_sbsize, afs.fs_cgsize, afs.fs_old_cgoffset,
+ afs.fs_old_cgmask);
+ printf("csaddr\t%d\tcssize\t%d\n",
+ afs.fs_old_csaddr, afs.fs_cssize);
+ printf("rotdelay %dms\trps\t%d\ttrackskew %d\tinterleave %d\n",
+ afs.fs_old_rotdelay, afs.fs_old_rps, afs.fs_old_trackskew,
+ afs.fs_old_interleave);
+ printf("nsect\t%d\tnpsect\t%d\tspc\t%d\n",
+ afs.fs_old_nsect, afs.fs_old_npsect, afs.fs_old_spc);
+ break;
+ default:
+ goto err;
+ }
+ printf("sblkno\t%d\tcblkno\t%d\tiblkno\t%d\tdblkno\t%d\n",
+ afs.fs_sblkno, afs.fs_cblkno, afs.fs_iblkno, afs.fs_dblkno);
+ printf("cgrotor\t%d\tfmod\t%d\tronly\t%d\tclean\t%d\n",
+ afs.fs_cgrotor, afs.fs_fmod, afs.fs_ronly, afs.fs_clean);
+ printf("metaspace %jd\tavgfpdir %d\tavgfilesize %d\n",
+ afs.fs_metaspace, afs.fs_avgfpdir, afs.fs_avgfilesize);
+ printf("flags\t");
+ if (afs.fs_old_flags & FS_FLAGS_UPDATED)
+ fsflags = afs.fs_flags;
+ else
+ fsflags = afs.fs_old_flags;
+ if (fsflags == 0)
+ printf("none");
+ if (fsflags & FS_UNCLEAN)
+ printf("unclean ");
+ if (fsflags & FS_DOSOFTDEP)
+ printf("soft-updates%s ", (fsflags & FS_SUJ) ? "+journal" : "");
+ if (fsflags & FS_NEEDSFSCK)
+ printf("needs fsck run ");
+ if (fsflags & FS_INDEXDIRS)
+ printf("indexed directories ");
+ if (fsflags & FS_ACLS)
+ printf("acls ");
+ if (fsflags & FS_MULTILABEL)
+ printf("multilabel ");
+ if (fsflags & FS_GJOURNAL)
+ printf("gjournal ");
+ if (fsflags & FS_FLAGS_UPDATED)
+ printf("fs_flags expanded ");
+ if (fsflags & FS_NFS4ACLS)
+ printf("nfsv4acls ");
+ if (fsflags & FS_TRIM)
+ printf("trim ");
+ fsflags &= ~(FS_UNCLEAN | FS_DOSOFTDEP | FS_NEEDSFSCK | FS_INDEXDIRS |
+ FS_ACLS | FS_MULTILABEL | FS_GJOURNAL | FS_FLAGS_UPDATED |
+ FS_NFS4ACLS | FS_SUJ | FS_TRIM);
+ if (fsflags != 0)
+ printf("unknown flags (%#x)", fsflags);
+ putchar('\n');
+ printf("fsmnt\t%s\n", afs.fs_fsmnt);
+ printf("volname\t%s\tswuid\t%ju\tprovidersize\t%ju\n",
+ afs.fs_volname, (uintmax_t)afs.fs_swuid,
+ (uintmax_t)afs.fs_providersize);
+ printf("\ncs[].cs_(nbfree,ndir,nifree,nffree):\n\t");
+ afs.fs_csp = calloc(1, afs.fs_cssize);
+ if (bread(&disk, fsbtodb(&afs, afs.fs_csaddr), afs.fs_csp, afs.fs_cssize) == -1)
+ goto err;
+ for (i = 0; i < afs.fs_ncg; i++) {
+ struct csum *cs = &afs.fs_cs(&afs, i);
+ if (i && i % 4 == 0)
+ printf("\n\t");
+ printf("(%d,%d,%d,%d) ",
+ cs->cs_nbfree, cs->cs_ndir, cs->cs_nifree, cs->cs_nffree);
+ }
+ printf("\n");
+ if (fssize % afs.fs_fpg) {
+ if (disk.d_ufs == 1)
+ printf("cylinders in last group %d\n",
+ howmany(afs.fs_old_size % afs.fs_fpg,
+ afs.fs_old_spc / afs.fs_old_nspf));
+ printf("blocks in last group %ld\n\n",
+ (long)((fssize % afs.fs_fpg) / afs.fs_frag));
+ }
+ while ((i = cgread(&disk)) != 0) {
+ if (i == -1 || dumpcg())
+ goto err;
+ }
+ return (0);
+
+err: ufserr(name);
+ return (1);
+}
+
+static int
+dumpcg(void)
+{
+ time_t cgtime;
+ off_t cur;
+ int i, j;
+
+ printf("\ncg %d:\n", disk.d_lcg);
+ cur = fsbtodb(&afs, cgtod(&afs, disk.d_lcg)) * disk.d_bsize;
+ switch (disk.d_ufs) {
+ case 2:
+ cgtime = acg.cg_time;
+ printf("magic\t%x\ttell\t%jx\ttime\t%s",
+ acg.cg_magic, (intmax_t)cur, ctime(&cgtime));
+ printf("cgx\t%d\tndblk\t%d\tniblk\t%d\tinitiblk %d\tunrefs %d\n",
+ acg.cg_cgx, acg.cg_ndblk, acg.cg_niblk, acg.cg_initediblk,
+ acg.cg_unrefs);
+ break;
+ case 1:
+ cgtime = acg.cg_old_time;
+ printf("magic\t%x\ttell\t%jx\ttime\t%s",
+ acg.cg_magic, (intmax_t)cur, ctime(&cgtime));
+ printf("cgx\t%d\tncyl\t%d\tniblk\t%d\tndblk\t%d\n",
+ acg.cg_cgx, acg.cg_old_ncyl, acg.cg_old_niblk,
+ acg.cg_ndblk);
+ break;
+ default:
+ break;
+ }
+ printf("nbfree\t%d\tndir\t%d\tnifree\t%d\tnffree\t%d\n",
+ acg.cg_cs.cs_nbfree, acg.cg_cs.cs_ndir,
+ acg.cg_cs.cs_nifree, acg.cg_cs.cs_nffree);
+ printf("rotor\t%d\tirotor\t%d\tfrotor\t%d\nfrsum",
+ acg.cg_rotor, acg.cg_irotor, acg.cg_frotor);
+ for (i = 1, j = 0; i < afs.fs_frag; i++) {
+ printf("\t%d", acg.cg_frsum[i]);
+ j += i * acg.cg_frsum[i];
+ }
+ printf("\nsum of frsum: %d", j);
+ if (afs.fs_contigsumsize > 0) {
+ for (i = 1; i < afs.fs_contigsumsize; i++) {
+ if ((i - 1) % 8 == 0)
+ printf("\nclusters %d-%d:", i,
+ afs.fs_contigsumsize - 1 < i + 7 ?
+ afs.fs_contigsumsize - 1 : i + 7);
+ printf("\t%d", cg_clustersum(&acg)[i]);
+ }
+ printf("\nclusters size %d and over: %d\n",
+ afs.fs_contigsumsize,
+ cg_clustersum(&acg)[afs.fs_contigsumsize]);
+ printf("clusters free:\t");
+ pbits(cg_clustersfree(&acg), acg.cg_nclusterblks);
+ } else
+ printf("\n");
+ printf("inodes used:\t");
+ pbits(cg_inosused(&acg), afs.fs_ipg);
+ printf("blks free:\t");
+ pbits(cg_blksfree(&acg), afs.fs_fpg);
+ return (0);
+}
+
+static int
+dumpfreespace(const char *name, int fflag)
+{
+ int i;
+
+ while ((i = cgread(&disk)) != 0) {
+ if (i == -1)
+ goto err;
+ dumpfreespacecg(fflag);
+ }
+ return (0);
+err:
+ ufserr(name);
+ return (1);
+}
+
+static void
+dumpfreespacecg(int fflag)
+{
+
+ pblklist(cg_blksfree(&acg), afs.fs_fpg, disk.d_lcg * afs.fs_fpg,
+ fflag);
+}
+
+static int
+marshal(const char *name)
+{
+ struct fs *fs;
+
+ fs = &disk.d_fs;
+
+ printf("# newfs command for %s (%s)\n", name, disk.d_name);
+ printf("newfs ");
+ if (fs->fs_volname[0] != '\0')
+ printf("-L %s ", fs->fs_volname);
+ printf("-O %d ", disk.d_ufs);
+ if (fs->fs_flags & FS_DOSOFTDEP)
+ printf("-U ");
+ printf("-a %d ", fs->fs_maxcontig);
+ printf("-b %d ", fs->fs_bsize);
+ /* -c is dumb */
+ printf("-d %d ", fs->fs_maxbsize);
+ printf("-e %d ", fs->fs_maxbpg);
+ printf("-f %d ", fs->fs_fsize);
+ printf("-g %d ", fs->fs_avgfilesize);
+ printf("-h %d ", fs->fs_avgfpdir);
+ printf("-i %jd ", fragroundup(fs, lblktosize(fs, fragstoblks(fs,
+ fs->fs_fpg)) / fs->fs_ipg));
+ if (fs->fs_flags & FS_SUJ)
+ printf("-j ");
+ if (fs->fs_flags & FS_GJOURNAL)
+ printf("-J ");
+ printf("-k %jd ", fs->fs_metaspace);
+ if (fs->fs_flags & FS_MULTILABEL)
+ printf("-l ");
+ printf("-m %d ", fs->fs_minfree);
+ /* -n unimplemented */
+ printf("-o ");
+ switch (fs->fs_optim) {
+ case FS_OPTSPACE:
+ printf("space ");
+ break;
+ case FS_OPTTIME:
+ printf("time ");
+ break;
+ default:
+ printf("unknown ");
+ break;
+ }
+ /* -p..r unimplemented */
+ printf("-s %jd ", (intmax_t)fsbtodb(fs, fs->fs_size));
+ if (fs->fs_flags & FS_TRIM)
+ printf("-t ");
+ printf("%s ", disk.d_name);
+ printf("\n");
+
+ return 0;
+}
+
+static void
+pbits(void *vp, int max)
+{
+ int i;
+ char *p;
+ int count, j;
+
+ for (count = i = 0, p = vp; i < max; i++)
+ if (isset(p, i)) {
+ if (count)
+ printf(",%s", count % 6 ? " " : "\n\t");
+ count++;
+ printf("%d", i);
+ j = i;
+ while ((i+1)<max && isset(p, i+1))
+ i++;
+ if (i != j)
+ printf("-%d", i);
+ }
+ printf("\n");
+}
+
+static void
+pblklist(void *vp, int max, off_t offset, int fflag)
+{
+ int i, j;
+ char *p;
+
+ for (i = 0, p = vp; i < max; i++) {
+ if (isset(p, i)) {
+ printf("%jd", (intmax_t)(i + offset));
+ if (fflag < 2) {
+ j = i;
+ while ((i+1)<max && isset(p, i+1))
+ i++;
+ if (i != j)
+ printf("-%jd", (intmax_t)(i + offset));
+ }
+ printf("\n");
+ }
+ }
+}
+
+static void
+ufserr(const char *name)
+{
+ if (disk.d_error != NULL)
+ warnx("%s: %s", name, disk.d_error);
+ else if (errno)
+ warn("%s", name);
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr, "usage: dumpfs [-flm] filesys | device\n");
+ exit(1);
+}
diff --git a/sbin/dumpon/Makefile b/sbin/dumpon/Makefile
new file mode 100644
index 0000000..d11ccc7
--- /dev/null
+++ b/sbin/dumpon/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+PROG= dumpon
+MAN= dumpon.8
+
+.include <bsd.prog.mk>
diff --git a/sbin/dumpon/dumpon.8 b/sbin/dumpon/dumpon.8
new file mode 100644
index 0000000..c2601b2
--- /dev/null
+++ b/sbin/dumpon/dumpon.8
@@ -0,0 +1,160 @@
+.\" Copyright (c) 1980, 1991, 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.
+.\" 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.
+.\"
+.\" From: @(#)swapon.8 8.1 (Berkeley) 6/5/93
+.\" $FreeBSD$
+.\"
+.Dd October 8, 2014
+.Dt DUMPON 8
+.Os
+.Sh NAME
+.Nm dumpon
+.Nd "specify a device for crash dumps"
+.Sh SYNOPSIS
+.Nm
+.Op Fl v
+.Ar special_file
+.Nm
+.Op Fl v
+.Cm off
+.Nm
+.Op Fl v
+.Fl l
+.Sh DESCRIPTION
+The
+.Nm
+utility is used to specify a device where the kernel can save a crash
+dump in the case of a panic.
+.Pp
+Calls to
+.Nm
+normally occur from the system multi-user initialization file
+.Pa /etc/rc ,
+controlled by the
+.Dq dumpdev
+variable in the boot time configuration file
+.Pa /etc/rc.conf .
+.Pp
+The default type of kernel crash dump is the mini crash dump.
+Mini crash dumps hold only memory pages in use by the kernel.
+Alternatively, full memory dumps can be enabled by setting the
+.Va debug.minidump
+.Xr sysctl 8
+variable to 0.
+.Pp
+For systems using full memory dumps, the size of the specified dump
+device must be at
+least the size of physical memory.
+Even though an additional 64 kB header is added to the dump, the BIOS for a
+platform typically holds back some memory, so it is not usually
+necessary to size the dump device larger than the actual amount of RAM
+available in the machine.
+Also, when using full memory dumps, the
+.Nm
+utility will refuse to enable a dump device which is smaller than the
+total amount of physical memory as reported by the
+.Va hw.physmem
+.Xr sysctl 8
+variable.
+.Pp
+The
+.Fl l
+flag causes
+.Nm
+to print the current dump device or _PATH_DEVNULL ("/dev/null") if no device is
+configured.
+.Pp
+The
+.Fl v
+flag causes
+.Nm
+to be verbose about its activity.
+.Sh IMPLEMENTATION NOTES
+Since a
+.Xr panic 9
+condition may occur in a situation
+where the kernel cannot trust its internal representation
+of the state of any given file system,
+one of the system swap devices,
+and
+.Em not
+a device containing a file system,
+should be used as the dump device.
+.Pp
+The
+.Nm
+utility operates by opening
+.Ar special_file
+and making a
+.Dv DIOCSKERNELDUMP
+.Xr ioctl 2
+request on it to save kernel crash dumps.
+If
+.Ar special_file
+is the text string:
+.Dq Li off ,
+.Nm
+performs a
+.Dv DIOCSKERNELDUMP
+.Xr ioctl 2
+on
+.Pa /dev/null
+and thus instructs the kernel not to save crash dumps.
+.Pp
+Since
+.Nm
+cannot be used during kernel initialization, the
+.Va dumpdev
+variable of
+.Xr loader 8
+must be used to enable dumps for system panics which occur
+during kernel initialization.
+.Sh FILES
+.Bl -tag -width "/dev/{ada,da}?s?b" -compact
+.It Pa /dev/{ada,da}?s?b
+standard swap areas
+.It Pa /etc/rc.conf
+boot-time system configuration
+.El
+.Sh SEE ALSO
+.Xr fstab 5 ,
+.Xr rc.conf 5 ,
+.Xr config 8 ,
+.Xr init 8 ,
+.Xr loader 8 ,
+.Xr rc 8 ,
+.Xr savecore 8 ,
+.Xr swapon 8 ,
+.Xr panic 9
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Fx 2.1 .
+.Sh BUGS
+Because the file system layer is already dead by the time a crash dump
+is taken, it is not possible to send crash dumps directly to a file.
diff --git a/sbin/dumpon/dumpon.c b/sbin/dumpon/dumpon.c
new file mode 100644
index 0000000..eebcc69
--- /dev/null
+++ b/sbin/dumpon/dumpon.c
@@ -0,0 +1,178 @@
+/*
+ * Copyright (c) 1980, 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.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if 0
+#ifndef lint
+static const char copyright[] =
+"@(#) Copyright (c) 1980, 1993\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static char sccsid[] = "From: @(#)swapon.c 8.1 (Berkeley) 6/5/93";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/disk.h>
+#include <sys/sysctl.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+static int verbose;
+
+static void
+usage(void)
+{
+ fprintf(stderr, "%s\n%s\n%s\n",
+ "usage: dumpon [-v] special_file",
+ " dumpon [-v] off",
+ " dumpon [-v] -l");
+ exit(EX_USAGE);
+}
+
+static void
+check_size(int fd, const char *fn)
+{
+ int name[] = { CTL_HW, HW_PHYSMEM };
+ size_t namelen = sizeof(name) / sizeof(*name);
+ unsigned long physmem;
+ size_t len;
+ off_t mediasize;
+ int minidump;
+
+ len = sizeof(minidump);
+ if (sysctlbyname("debug.minidump", &minidump, &len, NULL, 0) == 0 &&
+ minidump == 1)
+ return;
+ len = sizeof(physmem);
+ if (sysctl(name, namelen, &physmem, &len, NULL, 0) != 0)
+ err(EX_OSERR, "can't get memory size");
+ if (ioctl(fd, DIOCGMEDIASIZE, &mediasize) != 0)
+ err(EX_OSERR, "%s: can't get size", fn);
+ if ((uintmax_t)mediasize < (uintmax_t)physmem) {
+ if (verbose)
+ printf("%s is smaller than physical memory\n", fn);
+ exit(EX_IOERR);
+ }
+}
+
+static void
+listdumpdev(void)
+{
+ char dumpdev[PATH_MAX];
+ size_t len;
+ const char *sysctlname = "kern.shutdown.dumpdevname";
+
+ len = sizeof(dumpdev);
+ if (sysctlbyname(sysctlname, &dumpdev, &len, NULL, 0) != 0) {
+ if (errno == ENOMEM) {
+ err(EX_OSERR, "Kernel returned too large of a buffer for '%s'\n",
+ sysctlname);
+ } else {
+ err(EX_OSERR, "Sysctl get '%s'\n", sysctlname);
+ }
+ }
+ if (verbose) {
+ printf("kernel dumps on ");
+ }
+ if (strlen(dumpdev) == 0) {
+ printf("%s\n", _PATH_DEVNULL);
+ } else {
+ printf("%s\n", dumpdev);
+ }
+}
+
+int
+main(int argc, char *argv[])
+{
+ int ch;
+ int i, fd;
+ u_int u;
+ int do_listdumpdev = 0;
+
+ while ((ch = getopt(argc, argv, "lv")) != -1)
+ switch((char)ch) {
+ case 'l':
+ do_listdumpdev = 1;
+ break;
+ case 'v':
+ verbose = 1;
+ break;
+ default:
+ usage();
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (do_listdumpdev) {
+ listdumpdev();
+ exit(EX_OK);
+ }
+
+ if (argc != 1)
+ usage();
+
+ if (strcmp(argv[0], "off") != 0) {
+ fd = open(argv[0], O_RDONLY);
+ if (fd < 0)
+ err(EX_OSFILE, "%s", argv[0]);
+ check_size(fd, argv[0]);
+ u = 0;
+ i = ioctl(fd, DIOCSKERNELDUMP, &u);
+ u = 1;
+ i = ioctl(fd, DIOCSKERNELDUMP, &u);
+ if (i == 0 && verbose)
+ printf("kernel dumps on %s\n", argv[0]);
+ } else {
+ fd = open(_PATH_DEVNULL, O_RDONLY);
+ if (fd < 0)
+ err(EX_OSFILE, "%s", _PATH_DEVNULL);
+ u = 0;
+ i = ioctl(fd, DIOCSKERNELDUMP, &u);
+ if (i == 0 && verbose)
+ printf("kernel dumps disabled\n");
+ }
+ if (i < 0)
+ err(EX_OSERR, "ioctl(DIOCSKERNELDUMP)");
+
+ exit (0);
+}
diff --git a/sbin/etherswitchcfg/Makefile b/sbin/etherswitchcfg/Makefile
new file mode 100644
index 0000000..b5cf39a
--- /dev/null
+++ b/sbin/etherswitchcfg/Makefile
@@ -0,0 +1,9 @@
+# @(#)Makefile 5.4 (Berkeley) 6/5/91
+# $FreeBSD$
+
+PROG= etherswitchcfg
+MAN= etherswitchcfg.8
+SRCS= etherswitchcfg.c ifmedia.c
+CFLAGS+= -I${.CURDIR}/../../sys
+
+.include <bsd.prog.mk>
diff --git a/sbin/etherswitchcfg/etherswitchcfg.8 b/sbin/etherswitchcfg/etherswitchcfg.8
new file mode 100644
index 0000000..c5b93e9
--- /dev/null
+++ b/sbin/etherswitchcfg/etherswitchcfg.8
@@ -0,0 +1,192 @@
+.\" Copyright (c) 2011-2012 Stefan Bethke.
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd September 20, 2013
+.Dt ETHERSWITCHCFG 8
+.Os
+.Sh NAME
+.Nm etherswitchcfg
+.Nd configure a built-in Ethernet switch
+.Sh SYNOPSIS
+.Nm
+.Op Fl "f control file"
+.Ar info
+.Nm
+.Op Fl "f control file"
+.Ar config
+.Ar command parameter
+.Nm
+.Op Fl "f control file"
+.Ar phy
+.Ar phy.register[=value]
+.Nm
+.Op Fl "f control file"
+.Ar port%d
+.Ar [flags] command parameter
+.Nm
+.Op Fl "f control file"
+.Ar reg
+.Ar register[=value]
+.Nm
+.Op Fl "f control file"
+.Ar vlangroup%d
+.Ar command parameter
+.Sh DESCRIPTION
+The
+.Nm
+utility is used to configure an Ethernet switch built into the system.
+.Nm
+accepts a number of options:
+.Pp
+.Bl -tag -width ".Fl f" -compact
+.It Fl "f control file"
+Specifies the
+.Xr etherswitch 4
+control file that represents the switch to be configured.
+It defaults to
+.Pa /dev/etherswitch0 .
+.It Fl m
+When reporting port information, also list available media options for
+that port.
+.It Fl v
+Produce more verbose output.
+Without this flag, lines that represent inactive or empty configuration
+options are omitted.
+.El
+.Ss config
+The config command provides access to global switch configuration
+parameters.
+It support the following commands:
+.Pp
+.Bl -tag -width ".Ar vlan_mode mode" -compact
+.It Ar vlan_mode mode
+Sets the switch VLAN mode (depends on the hardware).
+.El
+.Ss phy
+The phy command provides access to the registers of the PHYs attached
+to or integrated into the switch controller.
+PHY registers are specified as phy.register,
+where
+.Ar phy
+is usually the port number, and
+.Ar register
+is the register number.
+Both can be provided as decimal, octal or hexadecimal numbers in any of the formats
+understood by
+.Xr strtol 3 .
+To set the register value, use the form instance.register=value.
+.Ss port
+The port command selects one of the ports of the switch.
+It supports the following commands:
+.Pp
+.Bl -tag -width ".Ar pvid number" -compact
+.It Ar pvid number
+Sets the default port VID that is used to process incoming frames that are not tagged.
+.It Ar media mediaspec
+Specifies the physical media configuration to be configured for a port.
+.It Ar mediaopt mediaoption
+Specifies a list of media options for a port.
+See
+.Xr ifconfig 8
+for details on
+.Ar media
+and
+.Ar mediaopt .
+.El
+.Pp
+And the following flags (please note that not all flags
+are supported by all switch drivers):
+.Pp
+.Bl -tag -width ".Ar addtag" -compact
+.It Ar addtag
+Add VLAN tag to each packet sent by the port.
+.It Ar -addtag
+Disable the add VLAN tag option.
+.It Ar striptag
+Strip the VLAN tags from the packets sent by the port.
+.It Ar -striptag
+Disable the strip VLAN tag option.
+.It Ar firstlock
+This options makes the switch port lock on the first MAC address it sees.
+After that, usually you need to reset the switch to learn different
+MAC addresses.
+.It Ar -firstlock
+Disable the first lock option.
+Note that sometimes you need to reset the
+switch to really disable this option.
+.It Ar dropuntagged
+Drop packets without a VLAN tag.
+.It Ar -dropuntagged
+Disable the drop untagged packets option.
+.It Ar doubletag
+Enable QinQ for the port.
+.It Ar -doubletag
+Disable QinQ for the port.
+.It Ar ingress
+Enable the ingress filter on the port.
+.It Ar -ingress
+Disable the ingress filter.
+.El
+.Ss reg
+The reg command provides access to the registers of the switch controller.
+.Ss vlangroup
+The vlangroup command selects one of the VLAN groups for configuration.
+It supports the following commands:
+.Pp
+.Bl -tag -width ".Ar vlangroup" -compact
+.It Ar vlan VID
+Sets the VLAN ID (802.1q VID) for this VLAN group.
+Frames transmitted on tagged member ports of this group will be tagged
+with this VID.
+Incoming frames carrying this tag will be forwarded according to the
+configuration of this VLAN group.
+.It Ar members port,...
+Configures which ports are to be a member of this VLAN group.
+The port numbers are given as a comma-separated list.
+Each port can optionally be followed by
+.Dq t
+to indicate that frames on this port are tagged.
+.El
+.Sh FILES
+.Bl -tag -width /dev/etherswitch? -compact
+.It Pa /dev/etherswitch?
+Control file for the Ethernet switch driver.
+.El
+.Sh EXAMPLES
+Configure VLAN group 1 with a VID of 2 and make ports 0 and 5 its members
+while excluding all other ports.
+Port 5 will send and receive tagged frames while port 0 will be untagged.
+Incoming untagged frames on port 0 are assigned to vlangroup1.
+.Pp
+.Dl # etherswitchcfg vlangroup1 vlan 2 members 0,5t port0 pvid 2
+.Sh SEE ALSO
+.Xr etherswitch 4
+.Sh HISTORY
+.Nm
+first appeared in
+.Fx 10.0 .
+.Sh AUTHORS
+.An Stefan Bethke
diff --git a/sbin/etherswitchcfg/etherswitchcfg.c b/sbin/etherswitchcfg/etherswitchcfg.c
new file mode 100644
index 0000000..f7f117a
--- /dev/null
+++ b/sbin/etherswitchcfg/etherswitchcfg.c
@@ -0,0 +1,710 @@
+/*-
+ * Copyright (c) 2011-2012 Stefan Bethke.
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <net/if.h>
+#include <net/if_media.h>
+#include <dev/etherswitch/etherswitch.h>
+
+int get_media_subtype(int, const char *);
+int get_media_mode(int, const char *);
+int get_media_options(int, const char *);
+int lookup_media_word(struct ifmedia_description *, const char *);
+void print_media_word(int, int);
+void print_media_word_ifconfig(int);
+
+/* some constants */
+#define IEEE802DOT1Q_VID_MAX 4094
+#define IFMEDIAREQ_NULISTENTRIES 256
+
+enum cmdmode {
+ MODE_NONE = 0,
+ MODE_PORT,
+ MODE_CONFIG,
+ MODE_VLANGROUP,
+ MODE_REGISTER,
+ MODE_PHYREG
+};
+
+struct cfg {
+ int fd;
+ int verbose;
+ int mediatypes;
+ const char *controlfile;
+ etherswitch_conf_t conf;
+ etherswitch_info_t info;
+ enum cmdmode mode;
+ int unit;
+};
+
+struct cmds {
+ enum cmdmode mode;
+ const char *name;
+ int args;
+ void (*f)(struct cfg *, char *argv[]);
+};
+static struct cmds cmds[];
+
+
+/*
+ * Print a value a la the %b format of the kernel's printf.
+ * Stolen from ifconfig.c.
+ */
+static void
+printb(const char *s, unsigned v, const char *bits)
+{
+ int i, any = 0;
+ char c;
+
+ if (bits && *bits == 8)
+ printf("%s=%o", s, v);
+ else
+ printf("%s=%x", s, v);
+ bits++;
+ if (bits) {
+ putchar('<');
+ while ((i = *bits++) != '\0') {
+ if (v & (1 << (i-1))) {
+ if (any)
+ putchar(',');
+ any = 1;
+ for (; (c = *bits) > 32; bits++)
+ putchar(c);
+ } else
+ for (; *bits > 32; bits++)
+ ;
+ }
+ putchar('>');
+ }
+}
+
+static int
+read_register(struct cfg *cfg, int r)
+{
+ struct etherswitch_reg er;
+
+ er.reg = r;
+ if (ioctl(cfg->fd, IOETHERSWITCHGETREG, &er) != 0)
+ err(EX_OSERR, "ioctl(IOETHERSWITCHGETREG)");
+ return (er.val);
+}
+
+static void
+write_register(struct cfg *cfg, int r, int v)
+{
+ struct etherswitch_reg er;
+
+ er.reg = r;
+ er.val = v;
+ if (ioctl(cfg->fd, IOETHERSWITCHSETREG, &er) != 0)
+ err(EX_OSERR, "ioctl(IOETHERSWITCHSETREG)");
+}
+
+static int
+read_phyregister(struct cfg *cfg, int phy, int reg)
+{
+ struct etherswitch_phyreg er;
+
+ er.phy = phy;
+ er.reg = reg;
+ if (ioctl(cfg->fd, IOETHERSWITCHGETPHYREG, &er) != 0)
+ err(EX_OSERR, "ioctl(IOETHERSWITCHGETPHYREG)");
+ return (er.val);
+}
+
+static void
+write_phyregister(struct cfg *cfg, int phy, int reg, int val)
+{
+ struct etherswitch_phyreg er;
+
+ er.phy = phy;
+ er.reg = reg;
+ er.val = val;
+ if (ioctl(cfg->fd, IOETHERSWITCHSETPHYREG, &er) != 0)
+ err(EX_OSERR, "ioctl(IOETHERSWITCHSETPHYREG)");
+}
+
+static void
+set_port_vid(struct cfg *cfg, char *argv[])
+{
+ int v;
+ etherswitch_port_t p;
+
+ v = strtol(argv[1], NULL, 0);
+ if (v < 0 || v > IEEE802DOT1Q_VID_MAX)
+ errx(EX_USAGE, "pvid must be between 0 and %d",
+ IEEE802DOT1Q_VID_MAX);
+ bzero(&p, sizeof(p));
+ p.es_port = cfg->unit;
+ if (ioctl(cfg->fd, IOETHERSWITCHGETPORT, &p) != 0)
+ err(EX_OSERR, "ioctl(IOETHERSWITCHGETPORT)");
+ p.es_pvid = v;
+ if (ioctl(cfg->fd, IOETHERSWITCHSETPORT, &p) != 0)
+ err(EX_OSERR, "ioctl(IOETHERSWITCHSETPORT)");
+}
+
+static void
+set_port_flag(struct cfg *cfg, char *argv[])
+{
+ char *flag;
+ int n;
+ uint32_t f;
+ etherswitch_port_t p;
+
+ n = 0;
+ f = 0;
+ flag = argv[0];
+ if (strcmp(flag, "none") != 0) {
+ if (*flag == '-') {
+ n++;
+ flag++;
+ }
+ if (strcasecmp(flag, "striptag") == 0)
+ f = ETHERSWITCH_PORT_STRIPTAG;
+ else if (strcasecmp(flag, "addtag") == 0)
+ f = ETHERSWITCH_PORT_ADDTAG;
+ else if (strcasecmp(flag, "firstlock") == 0)
+ f = ETHERSWITCH_PORT_FIRSTLOCK;
+ else if (strcasecmp(flag, "dropuntagged") == 0)
+ f = ETHERSWITCH_PORT_DROPUNTAGGED;
+ else if (strcasecmp(flag, "doubletag") == 0)
+ f = ETHERSWITCH_PORT_DOUBLE_TAG;
+ else if (strcasecmp(flag, "ingress") == 0)
+ f = ETHERSWITCH_PORT_INGRESS;
+ }
+ bzero(&p, sizeof(p));
+ p.es_port = cfg->unit;
+ if (ioctl(cfg->fd, IOETHERSWITCHGETPORT, &p) != 0)
+ err(EX_OSERR, "ioctl(IOETHERSWITCHGETPORT)");
+ if (n)
+ p.es_flags &= ~f;
+ else
+ p.es_flags |= f;
+ if (ioctl(cfg->fd, IOETHERSWITCHSETPORT, &p) != 0)
+ err(EX_OSERR, "ioctl(IOETHERSWITCHSETPORT)");
+}
+
+static void
+set_port_media(struct cfg *cfg, char *argv[])
+{
+ etherswitch_port_t p;
+ int ifm_ulist[IFMEDIAREQ_NULISTENTRIES];
+ int subtype;
+
+ bzero(&p, sizeof(p));
+ p.es_port = cfg->unit;
+ p.es_ifmr.ifm_ulist = ifm_ulist;
+ p.es_ifmr.ifm_count = IFMEDIAREQ_NULISTENTRIES;
+ if (ioctl(cfg->fd, IOETHERSWITCHGETPORT, &p) != 0)
+ err(EX_OSERR, "ioctl(IOETHERSWITCHGETPORT)");
+ if (p.es_ifmr.ifm_count == 0)
+ return;
+ subtype = get_media_subtype(IFM_TYPE(ifm_ulist[0]), argv[1]);
+ p.es_ifr.ifr_media = (p.es_ifmr.ifm_current & IFM_IMASK) |
+ IFM_TYPE(ifm_ulist[0]) | subtype;
+ if (ioctl(cfg->fd, IOETHERSWITCHSETPORT, &p) != 0)
+ err(EX_OSERR, "ioctl(IOETHERSWITCHSETPORT)");
+}
+
+static void
+set_port_mediaopt(struct cfg *cfg, char *argv[])
+{
+ etherswitch_port_t p;
+ int ifm_ulist[IFMEDIAREQ_NULISTENTRIES];
+ int options;
+
+ bzero(&p, sizeof(p));
+ p.es_port = cfg->unit;
+ p.es_ifmr.ifm_ulist = ifm_ulist;
+ p.es_ifmr.ifm_count = IFMEDIAREQ_NULISTENTRIES;
+ if (ioctl(cfg->fd, IOETHERSWITCHGETPORT, &p) != 0)
+ err(EX_OSERR, "ioctl(IOETHERSWITCHGETPORT)");
+ options = get_media_options(IFM_TYPE(ifm_ulist[0]), argv[1]);
+ if (options == -1)
+ errx(EX_USAGE, "invalid media options \"%s\"", argv[1]);
+ if (options & IFM_HDX) {
+ p.es_ifr.ifr_media &= ~IFM_FDX;
+ options &= ~IFM_HDX;
+ }
+ p.es_ifr.ifr_media |= options;
+ if (ioctl(cfg->fd, IOETHERSWITCHSETPORT, &p) != 0)
+ err(EX_OSERR, "ioctl(IOETHERSWITCHSETPORT)");
+}
+
+static void
+set_vlangroup_vid(struct cfg *cfg, char *argv[])
+{
+ int v;
+ etherswitch_vlangroup_t vg;
+
+ v = strtol(argv[1], NULL, 0);
+ if (v < 0 || v > IEEE802DOT1Q_VID_MAX)
+ errx(EX_USAGE, "vlan must be between 0 and %d", IEEE802DOT1Q_VID_MAX);
+ vg.es_vlangroup = cfg->unit;
+ if (ioctl(cfg->fd, IOETHERSWITCHGETVLANGROUP, &vg) != 0)
+ err(EX_OSERR, "ioctl(IOETHERSWITCHGETVLANGROUP)");
+ vg.es_vid = v;
+ if (ioctl(cfg->fd, IOETHERSWITCHSETVLANGROUP, &vg) != 0)
+ err(EX_OSERR, "ioctl(IOETHERSWITCHSETVLANGROUP)");
+}
+
+static void
+set_vlangroup_members(struct cfg *cfg, char *argv[])
+{
+ etherswitch_vlangroup_t vg;
+ int member, untagged;
+ char *c, *d;
+ int v;
+
+ member = untagged = 0;
+ if (strcmp(argv[1], "none") != 0) {
+ for (c=argv[1]; *c; c=d) {
+ v = strtol(c, &d, 0);
+ if (d == c)
+ break;
+ if (v < 0 || v >= cfg->info.es_nports)
+ errx(EX_USAGE, "Member port must be between 0 and %d", cfg->info.es_nports-1);
+ if (d[0] == ',' || d[0] == '\0' ||
+ ((d[0] == 't' || d[0] == 'T') && (d[1] == ',' || d[1] == '\0'))) {
+ if (d[0] == 't' || d[0] == 'T') {
+ untagged &= ~ETHERSWITCH_PORTMASK(v);
+ d++;
+ } else
+ untagged |= ETHERSWITCH_PORTMASK(v);
+ member |= ETHERSWITCH_PORTMASK(v);
+ d++;
+ } else
+ errx(EX_USAGE, "Invalid members specification \"%s\"", d);
+ }
+ }
+ vg.es_vlangroup = cfg->unit;
+ if (ioctl(cfg->fd, IOETHERSWITCHGETVLANGROUP, &vg) != 0)
+ err(EX_OSERR, "ioctl(IOETHERSWITCHGETVLANGROUP)");
+ vg.es_member_ports = member;
+ vg.es_untagged_ports = untagged;
+ if (ioctl(cfg->fd, IOETHERSWITCHSETVLANGROUP, &vg) != 0)
+ err(EX_OSERR, "ioctl(IOETHERSWITCHSETVLANGROUP)");
+}
+
+static int
+set_register(struct cfg *cfg, char *arg)
+{
+ int a, v;
+ char *c;
+
+ a = strtol(arg, &c, 0);
+ if (c==arg)
+ return (1);
+ if (*c == '=') {
+ v = strtol(c+1, NULL, 0);
+ write_register(cfg, a, v);
+ }
+ printf("\treg 0x%04x=0x%04x\n", a, read_register(cfg, a));
+ return (0);
+}
+
+static int
+set_phyregister(struct cfg *cfg, char *arg)
+{
+ int phy, reg, val;
+ char *c, *d;
+
+ phy = strtol(arg, &c, 0);
+ if (c==arg)
+ return (1);
+ if (*c != '.')
+ return (1);
+ d = c+1;
+ reg = strtol(d, &c, 0);
+ if (d == c)
+ return (1);
+ if (*c == '=') {
+ val = strtol(c+1, NULL, 0);
+ write_phyregister(cfg, phy, reg, val);
+ }
+ printf("\treg %d.0x%02x=0x%04x\n", phy, reg, read_phyregister(cfg, phy, reg));
+ return (0);
+}
+
+static void
+set_vlan_mode(struct cfg *cfg, char *argv[])
+{
+ etherswitch_conf_t conf;
+
+ bzero(&conf, sizeof(conf));
+ conf.cmd = ETHERSWITCH_CONF_VLAN_MODE;
+ if (strcasecmp(argv[1], "isl") == 0)
+ conf.vlan_mode = ETHERSWITCH_VLAN_ISL;
+ else if (strcasecmp(argv[1], "port") == 0)
+ conf.vlan_mode = ETHERSWITCH_VLAN_PORT;
+ else if (strcasecmp(argv[1], "dot1q") == 0)
+ conf.vlan_mode = ETHERSWITCH_VLAN_DOT1Q;
+ else if (strcasecmp(argv[1], "dot1q4k") == 0)
+ conf.vlan_mode = ETHERSWITCH_VLAN_DOT1Q_4K;
+ else if (strcasecmp(argv[1], "qinq") == 0)
+ conf.vlan_mode = ETHERSWITCH_VLAN_DOUBLE_TAG;
+ else
+ conf.vlan_mode = 0;
+ if (ioctl(cfg->fd, IOETHERSWITCHSETCONF, &conf) != 0)
+ err(EX_OSERR, "ioctl(IOETHERSWITCHSETCONF)");
+}
+
+static void
+print_config(struct cfg *cfg)
+{
+ const char *c;
+
+ /* Get the device name. */
+ c = strrchr(cfg->controlfile, '/');
+ if (c != NULL)
+ c = c + 1;
+ else
+ c = cfg->controlfile;
+
+ /* Print VLAN mode. */
+ if (cfg->conf.cmd & ETHERSWITCH_CONF_VLAN_MODE) {
+ printf("%s: VLAN mode: ", c);
+ switch (cfg->conf.vlan_mode) {
+ case ETHERSWITCH_VLAN_ISL:
+ printf("ISL\n");
+ break;
+ case ETHERSWITCH_VLAN_PORT:
+ printf("PORT\n");
+ break;
+ case ETHERSWITCH_VLAN_DOT1Q:
+ printf("DOT1Q\n");
+ break;
+ case ETHERSWITCH_VLAN_DOT1Q_4K:
+ printf("DOT1Q4K\n");
+ break;
+ case ETHERSWITCH_VLAN_DOUBLE_TAG:
+ printf("QinQ\n");
+ break;
+ default:
+ printf("none\n");
+ }
+ }
+}
+
+static void
+print_port(struct cfg *cfg, int port)
+{
+ etherswitch_port_t p;
+ int ifm_ulist[IFMEDIAREQ_NULISTENTRIES];
+ int i;
+
+ bzero(&p, sizeof(p));
+ p.es_port = port;
+ p.es_ifmr.ifm_ulist = ifm_ulist;
+ p.es_ifmr.ifm_count = IFMEDIAREQ_NULISTENTRIES;
+ if (ioctl(cfg->fd, IOETHERSWITCHGETPORT, &p) != 0)
+ err(EX_OSERR, "ioctl(IOETHERSWITCHGETPORT)");
+ printf("port%d:\n", port);
+ if (cfg->conf.vlan_mode == ETHERSWITCH_VLAN_DOT1Q)
+ printf("\tpvid: %d\n", p.es_pvid);
+ printb("\tflags", p.es_flags, ETHERSWITCH_PORT_FLAGS_BITS);
+ printf("\n");
+ printf("\tmedia: ");
+ print_media_word(p.es_ifmr.ifm_current, 1);
+ if (p.es_ifmr.ifm_active != p.es_ifmr.ifm_current) {
+ putchar(' ');
+ putchar('(');
+ print_media_word(p.es_ifmr.ifm_active, 0);
+ putchar(')');
+ }
+ putchar('\n');
+ printf("\tstatus: %s\n", (p.es_ifmr.ifm_status & IFM_ACTIVE) != 0 ? "active" : "no carrier");
+ if (cfg->mediatypes) {
+ printf("\tsupported media:\n");
+ if (p.es_ifmr.ifm_count > IFMEDIAREQ_NULISTENTRIES)
+ p.es_ifmr.ifm_count = IFMEDIAREQ_NULISTENTRIES;
+ for (i=0; i<p.es_ifmr.ifm_count; i++) {
+ printf("\t\tmedia ");
+ print_media_word(ifm_ulist[i], 0);
+ putchar('\n');
+ }
+ }
+}
+
+static void
+print_vlangroup(struct cfg *cfg, int vlangroup)
+{
+ etherswitch_vlangroup_t vg;
+ int i, comma;
+
+ vg.es_vlangroup = vlangroup;
+ if (ioctl(cfg->fd, IOETHERSWITCHGETVLANGROUP, &vg) != 0)
+ err(EX_OSERR, "ioctl(IOETHERSWITCHGETVLANGROUP)");
+ if ((vg.es_vid & ETHERSWITCH_VID_VALID) == 0)
+ return;
+ vg.es_vid &= ETHERSWITCH_VID_MASK;
+ printf("vlangroup%d:\n", vlangroup);
+ if (cfg->conf.vlan_mode == ETHERSWITCH_VLAN_PORT)
+ printf("\tport: %d\n", vg.es_vid);
+ else
+ printf("\tvlan: %d\n", vg.es_vid);
+ printf("\tmembers ");
+ comma = 0;
+ if (vg.es_member_ports != 0)
+ for (i=0; i<cfg->info.es_nports; i++) {
+ if ((vg.es_member_ports & ETHERSWITCH_PORTMASK(i)) != 0) {
+ if (comma)
+ printf(",");
+ printf("%d", i);
+ if ((vg.es_untagged_ports & ETHERSWITCH_PORTMASK(i)) == 0)
+ printf("t");
+ comma = 1;
+ }
+ }
+ else
+ printf("none");
+ printf("\n");
+}
+
+static void
+print_info(struct cfg *cfg)
+{
+ const char *c;
+ int i;
+
+ c = strrchr(cfg->controlfile, '/');
+ if (c != NULL)
+ c = c + 1;
+ else
+ c = cfg->controlfile;
+ if (cfg->verbose) {
+ printf("%s: %s with %d ports and %d VLAN groups\n", c,
+ cfg->info.es_name, cfg->info.es_nports,
+ cfg->info.es_nvlangroups);
+ printf("%s: ", c);
+ printb("VLAN capabilities", cfg->info.es_vlan_caps,
+ ETHERSWITCH_VLAN_CAPS_BITS);
+ printf("\n");
+ }
+ print_config(cfg);
+ for (i=0; i<cfg->info.es_nports; i++) {
+ print_port(cfg, i);
+ }
+ for (i=0; i<cfg->info.es_nvlangroups; i++) {
+ print_vlangroup(cfg, i);
+ }
+}
+
+static void
+usage(struct cfg *cfg __unused, char *argv[] __unused)
+{
+ fprintf(stderr, "usage: etherswitchctl\n");
+ fprintf(stderr, "\tetherswitchcfg [-f control file] info\n");
+ fprintf(stderr, "\tetherswitchcfg [-f control file] config "
+ "command parameter\n");
+ fprintf(stderr, "\t\tconfig commands: vlan_mode\n");
+ fprintf(stderr, "\tetherswitchcfg [-f control file] phy "
+ "phy.register[=value]\n");
+ fprintf(stderr, "\tetherswitchcfg [-f control file] portX "
+ "[flags] command parameter\n");
+ fprintf(stderr, "\t\tport commands: pvid, media, mediaopt\n");
+ fprintf(stderr, "\tetherswitchcfg [-f control file] reg "
+ "register[=value]\n");
+ fprintf(stderr, "\tetherswitchcfg [-f control file] vlangroupX "
+ "command parameter\n");
+ fprintf(stderr, "\t\tvlangroup commands: vlan, members\n");
+ exit(EX_USAGE);
+}
+
+static void
+newmode(struct cfg *cfg, enum cmdmode mode)
+{
+ if (mode == cfg->mode)
+ return;
+ switch (cfg->mode) {
+ case MODE_NONE:
+ break;
+ case MODE_CONFIG:
+ /*
+ * Read the updated the configuration (it can be different
+ * from the last time we read it).
+ */
+ if (ioctl(cfg->fd, IOETHERSWITCHGETCONF, &cfg->conf) != 0)
+ err(EX_OSERR, "ioctl(IOETHERSWITCHGETCONF)");
+ print_config(cfg);
+ break;
+ case MODE_PORT:
+ print_port(cfg, cfg->unit);
+ break;
+ case MODE_VLANGROUP:
+ print_vlangroup(cfg, cfg->unit);
+ break;
+ case MODE_REGISTER:
+ case MODE_PHYREG:
+ break;
+ }
+ cfg->mode = mode;
+}
+
+int
+main(int argc, char *argv[])
+{
+ int ch;
+ struct cfg cfg;
+ int i;
+
+ bzero(&cfg, sizeof(cfg));
+ cfg.controlfile = "/dev/etherswitch0";
+ while ((ch = getopt(argc, argv, "f:mv?")) != -1)
+ switch(ch) {
+ case 'f':
+ cfg.controlfile = optarg;
+ break;
+ case 'm':
+ cfg.mediatypes++;
+ break;
+ case 'v':
+ cfg.verbose++;
+ break;
+ case '?':
+ /* FALLTHROUGH */
+ default:
+ usage(&cfg, argv);
+ }
+ argc -= optind;
+ argv += optind;
+ cfg.fd = open(cfg.controlfile, O_RDONLY);
+ if (cfg.fd < 0)
+ err(EX_UNAVAILABLE, "Can't open control file: %s", cfg.controlfile);
+ if (ioctl(cfg.fd, IOETHERSWITCHGETINFO, &cfg.info) != 0)
+ err(EX_OSERR, "ioctl(IOETHERSWITCHGETINFO)");
+ if (ioctl(cfg.fd, IOETHERSWITCHGETCONF, &cfg.conf) != 0)
+ err(EX_OSERR, "ioctl(IOETHERSWITCHGETCONF)");
+ if (argc == 0) {
+ print_info(&cfg);
+ return (0);
+ }
+ cfg.mode = MODE_NONE;
+ while (argc > 0) {
+ switch(cfg.mode) {
+ case MODE_NONE:
+ if (strcmp(argv[0], "info") == 0) {
+ print_info(&cfg);
+ } else if (sscanf(argv[0], "port%d", &cfg.unit) == 1) {
+ if (cfg.unit < 0 || cfg.unit >= cfg.info.es_nports)
+ errx(EX_USAGE, "port unit must be between 0 and %d", cfg.info.es_nports - 1);
+ newmode(&cfg, MODE_PORT);
+ } else if (sscanf(argv[0], "vlangroup%d", &cfg.unit) == 1) {
+ if (cfg.unit < 0 || cfg.unit >= cfg.info.es_nvlangroups)
+ errx(EX_USAGE,
+ "vlangroup unit must be between 0 and %d",
+ cfg.info.es_nvlangroups - 1);
+ newmode(&cfg, MODE_VLANGROUP);
+ } else if (strcmp(argv[0], "config") == 0) {
+ newmode(&cfg, MODE_CONFIG);
+ } else if (strcmp(argv[0], "phy") == 0) {
+ newmode(&cfg, MODE_PHYREG);
+ } else if (strcmp(argv[0], "reg") == 0) {
+ newmode(&cfg, MODE_REGISTER);
+ } else if (strcmp(argv[0], "help") == 0) {
+ usage(&cfg, argv);
+ } else {
+ errx(EX_USAGE, "Unknown command \"%s\"", argv[0]);
+ }
+ break;
+ case MODE_PORT:
+ case MODE_CONFIG:
+ case MODE_VLANGROUP:
+ for(i=0; cmds[i].name != NULL; i++) {
+ if (cfg.mode == cmds[i].mode && strcmp(argv[0], cmds[i].name) == 0) {
+ if (argc < (cmds[i].args + 1)) {
+ printf("%s needs an argument\n", cmds[i].name);
+ break;
+ }
+ (cmds[i].f)(&cfg, argv);
+ argc -= cmds[i].args;
+ argv += cmds[i].args;
+ break;
+ }
+ }
+ if (cmds[i].name == NULL) {
+ newmode(&cfg, MODE_NONE);
+ continue;
+ }
+ break;
+ case MODE_REGISTER:
+ if (set_register(&cfg, argv[0]) != 0) {
+ newmode(&cfg, MODE_NONE);
+ continue;
+ }
+ break;
+ case MODE_PHYREG:
+ if (set_phyregister(&cfg, argv[0]) != 0) {
+ newmode(&cfg, MODE_NONE);
+ continue;
+ }
+ break;
+ }
+ argc--;
+ argv++;
+ }
+ /* switch back to command mode to print configuration for last command */
+ newmode(&cfg, MODE_NONE);
+ close(cfg.fd);
+ return (0);
+}
+
+static struct cmds cmds[] = {
+ { MODE_PORT, "pvid", 1, set_port_vid },
+ { MODE_PORT, "media", 1, set_port_media },
+ { MODE_PORT, "mediaopt", 1, set_port_mediaopt },
+ { MODE_PORT, "addtag", 0, set_port_flag },
+ { MODE_PORT, "-addtag", 0, set_port_flag },
+ { MODE_PORT, "ingress", 0, set_port_flag },
+ { MODE_PORT, "-ingress", 0, set_port_flag },
+ { MODE_PORT, "striptag", 0, set_port_flag },
+ { MODE_PORT, "-striptag", 0, set_port_flag },
+ { MODE_PORT, "doubletag", 0, set_port_flag },
+ { MODE_PORT, "-doubletag", 0, set_port_flag },
+ { MODE_PORT, "firstlock", 0, set_port_flag },
+ { MODE_PORT, "-firstlock", 0, set_port_flag },
+ { MODE_PORT, "dropuntagged", 0, set_port_flag },
+ { MODE_PORT, "-dropuntagged", 0, set_port_flag },
+ { MODE_CONFIG, "vlan_mode", 1, set_vlan_mode },
+ { MODE_VLANGROUP, "vlan", 1, set_vlangroup_vid },
+ { MODE_VLANGROUP, "members", 1, set_vlangroup_members },
+ { 0, NULL, 0, NULL }
+};
diff --git a/sbin/etherswitchcfg/ifmedia.c b/sbin/etherswitchcfg/ifmedia.c
new file mode 100644
index 0000000..b9bd3b9
--- /dev/null
+++ b/sbin/etherswitchcfg/ifmedia.c
@@ -0,0 +1,812 @@
+/* $NetBSD: ifconfig.c,v 1.34 1997/04/21 01:17:58 lukem Exp $ */
+/* $FreeBSD$ */
+
+/*
+ * Copyright (c) 1997 Jason R. Thorpe.
+ * 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 for the NetBSD Project
+ * by Jason R. Thorpe.
+ * 4. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 1983, 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.
+ * 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.
+ */
+/*
+ * based on sbin/ifconfig/ifmedia.c r221954
+ */
+
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+#include <sys/time.h>
+
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <net/if_types.h>
+#include <net/if_media.h>
+#include <net/route.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+void domediaopt(const char *, int, int);
+int get_media_subtype(int, const char *);
+int get_media_mode(int, const char *);
+int get_media_options(int, const char *);
+int lookup_media_word(struct ifmedia_description *, const char *);
+void print_media_word(int, int);
+void print_media_word_ifconfig(int);
+
+#if 0
+static struct ifmedia_description *get_toptype_desc(int);
+static struct ifmedia_type_to_subtype *get_toptype_ttos(int);
+static struct ifmedia_description *get_subtype_desc(int,
+ struct ifmedia_type_to_subtype *ttos);
+
+#define IFM_OPMODE(x) \
+ ((x) & (IFM_IEEE80211_ADHOC | IFM_IEEE80211_HOSTAP | \
+ IFM_IEEE80211_IBSS | IFM_IEEE80211_WDS | IFM_IEEE80211_MONITOR | \
+ IFM_IEEE80211_MBSS))
+#define IFM_IEEE80211_STA 0
+
+static void
+media_status(int s)
+{
+ struct ifmediareq ifmr;
+ int *media_list, i;
+
+ (void) memset(&ifmr, 0, sizeof(ifmr));
+ (void) strncpy(ifmr.ifm_name, name, sizeof(ifmr.ifm_name));
+
+ if (ioctl(s, SIOCGIFMEDIA, (caddr_t)&ifmr) < 0) {
+ /*
+ * Interface doesn't support SIOC{G,S}IFMEDIA.
+ */
+ return;
+ }
+
+ if (ifmr.ifm_count == 0) {
+ warnx("%s: no media types?", name);
+ return;
+ }
+
+ media_list = (int *)malloc(ifmr.ifm_count * sizeof(int));
+ if (media_list == NULL)
+ err(1, "malloc");
+ ifmr.ifm_ulist = media_list;
+
+ if (ioctl(s, SIOCGIFMEDIA, (caddr_t)&ifmr) < 0)
+ err(1, "SIOCGIFMEDIA");
+
+ printf("\tmedia: ");
+ print_media_word(ifmr.ifm_current, 1);
+ if (ifmr.ifm_active != ifmr.ifm_current) {
+ putchar(' ');
+ putchar('(');
+ print_media_word(ifmr.ifm_active, 0);
+ putchar(')');
+ }
+
+ putchar('\n');
+
+ if (ifmr.ifm_status & IFM_AVALID) {
+ printf("\tstatus: ");
+ switch (IFM_TYPE(ifmr.ifm_active)) {
+ case IFM_ETHER:
+ case IFM_ATM:
+ if (ifmr.ifm_status & IFM_ACTIVE)
+ printf("active");
+ else
+ printf("no carrier");
+ break;
+
+ case IFM_FDDI:
+ case IFM_TOKEN:
+ if (ifmr.ifm_status & IFM_ACTIVE)
+ printf("inserted");
+ else
+ printf("no ring");
+ break;
+
+ case IFM_IEEE80211:
+ if (ifmr.ifm_status & IFM_ACTIVE) {
+ /* NB: only sta mode associates */
+ if (IFM_OPMODE(ifmr.ifm_active) == IFM_IEEE80211_STA)
+ printf("associated");
+ else
+ printf("running");
+ } else
+ printf("no carrier");
+ break;
+ }
+ putchar('\n');
+ }
+
+ if (ifmr.ifm_count > 0 && supmedia) {
+ printf("\tsupported media:\n");
+ for (i = 0; i < ifmr.ifm_count; i++) {
+ printf("\t\t");
+ print_media_word_ifconfig(media_list[i]);
+ putchar('\n');
+ }
+ }
+
+ free(media_list);
+}
+
+struct ifmediareq *
+ifmedia_getstate(int s)
+{
+ static struct ifmediareq *ifmr = NULL;
+ int *mwords;
+
+ if (ifmr == NULL) {
+ ifmr = (struct ifmediareq *)malloc(sizeof(struct ifmediareq));
+ if (ifmr == NULL)
+ err(1, "malloc");
+
+ (void) memset(ifmr, 0, sizeof(struct ifmediareq));
+ (void) strncpy(ifmr->ifm_name, name,
+ sizeof(ifmr->ifm_name));
+
+ ifmr->ifm_count = 0;
+ ifmr->ifm_ulist = NULL;
+
+ /*
+ * We must go through the motions of reading all
+ * supported media because we need to know both
+ * the current media type and the top-level type.
+ */
+
+ if (ioctl(s, SIOCGIFMEDIA, (caddr_t)ifmr) < 0) {
+ err(1, "SIOCGIFMEDIA");
+ }
+
+ if (ifmr->ifm_count == 0)
+ errx(1, "%s: no media types?", name);
+
+ mwords = (int *)malloc(ifmr->ifm_count * sizeof(int));
+ if (mwords == NULL)
+ err(1, "malloc");
+
+ ifmr->ifm_ulist = mwords;
+ if (ioctl(s, SIOCGIFMEDIA, (caddr_t)ifmr) < 0)
+ err(1, "SIOCGIFMEDIA");
+ }
+
+ return ifmr;
+}
+
+static void
+setifmediacallback(int s, void *arg)
+{
+ struct ifmediareq *ifmr = (struct ifmediareq *)arg;
+ static int did_it = 0;
+
+ if (!did_it) {
+ ifr.ifr_media = ifmr->ifm_current;
+ if (ioctl(s, SIOCSIFMEDIA, (caddr_t)&ifr) < 0)
+ err(1, "SIOCSIFMEDIA (media)");
+ free(ifmr->ifm_ulist);
+ free(ifmr);
+ did_it = 1;
+ }
+}
+
+static void
+setmedia(const char *val, int d, int s, const struct afswtch *afp)
+{
+ struct ifmediareq *ifmr;
+ int subtype;
+
+ ifmr = ifmedia_getstate(s);
+
+ /*
+ * We are primarily concerned with the top-level type.
+ * However, "current" may be only IFM_NONE, so we just look
+ * for the top-level type in the first "supported type"
+ * entry.
+ *
+ * (I'm assuming that all supported media types for a given
+ * interface will be the same top-level type..)
+ */
+ subtype = get_media_subtype(IFM_TYPE(ifmr->ifm_ulist[0]), val);
+
+ strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
+ ifr.ifr_media = (ifmr->ifm_current & IFM_IMASK) |
+ IFM_TYPE(ifmr->ifm_ulist[0]) | subtype;
+
+ ifmr->ifm_current = ifr.ifr_media;
+ callback_register(setifmediacallback, (void *)ifmr);
+}
+
+static void
+setmediaopt(const char *val, int d, int s, const struct afswtch *afp)
+{
+
+ domediaopt(val, 0, s);
+}
+
+static void
+unsetmediaopt(const char *val, int d, int s, const struct afswtch *afp)
+{
+
+ domediaopt(val, 1, s);
+}
+
+static void
+domediaopt(const char *val, int clear, int s)
+{
+ struct ifmediareq *ifmr;
+ int options;
+
+ ifmr = ifmedia_getstate(s);
+
+ options = get_media_options(IFM_TYPE(ifmr->ifm_ulist[0]), val);
+
+ strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
+ ifr.ifr_media = ifmr->ifm_current;
+ if (clear)
+ ifr.ifr_media &= ~options;
+ else {
+ if (options & IFM_HDX) {
+ ifr.ifr_media &= ~IFM_FDX;
+ options &= ~IFM_HDX;
+ }
+ ifr.ifr_media |= options;
+ }
+ ifmr->ifm_current = ifr.ifr_media;
+ callback_register(setifmediacallback, (void *)ifmr);
+}
+
+static void
+setmediainst(const char *val, int d, int s, const struct afswtch *afp)
+{
+ struct ifmediareq *ifmr;
+ int inst;
+
+ ifmr = ifmedia_getstate(s);
+
+ inst = atoi(val);
+ if (inst < 0 || inst > (int)IFM_INST_MAX)
+ errx(1, "invalid media instance: %s", val);
+
+ strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
+ ifr.ifr_media = (ifmr->ifm_current & ~IFM_IMASK) | inst << IFM_ISHIFT;
+
+ ifmr->ifm_current = ifr.ifr_media;
+ callback_register(setifmediacallback, (void *)ifmr);
+}
+
+static void
+setmediamode(const char *val, int d, int s, const struct afswtch *afp)
+{
+ struct ifmediareq *ifmr;
+ int mode;
+
+ ifmr = ifmedia_getstate(s);
+
+ mode = get_media_mode(IFM_TYPE(ifmr->ifm_ulist[0]), val);
+
+ strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
+ ifr.ifr_media = (ifmr->ifm_current & ~IFM_MMASK) | mode;
+
+ ifmr->ifm_current = ifr.ifr_media;
+ callback_register(setifmediacallback, (void *)ifmr);
+}
+#endif
+
+/**********************************************************************
+ * A good chunk of this is duplicated from sys/net/ifmedia.c
+ **********************************************************************/
+
+static struct ifmedia_description ifm_type_descriptions[] =
+ IFM_TYPE_DESCRIPTIONS;
+
+static struct ifmedia_description ifm_subtype_ethernet_descriptions[] =
+ IFM_SUBTYPE_ETHERNET_DESCRIPTIONS;
+
+static struct ifmedia_description ifm_subtype_ethernet_aliases[] =
+ IFM_SUBTYPE_ETHERNET_ALIASES;
+
+static struct ifmedia_description ifm_subtype_ethernet_option_descriptions[] =
+ IFM_SUBTYPE_ETHERNET_OPTION_DESCRIPTIONS;
+
+static struct ifmedia_description ifm_subtype_tokenring_descriptions[] =
+ IFM_SUBTYPE_TOKENRING_DESCRIPTIONS;
+
+static struct ifmedia_description ifm_subtype_tokenring_aliases[] =
+ IFM_SUBTYPE_TOKENRING_ALIASES;
+
+static struct ifmedia_description ifm_subtype_tokenring_option_descriptions[] =
+ IFM_SUBTYPE_TOKENRING_OPTION_DESCRIPTIONS;
+
+static struct ifmedia_description ifm_subtype_fddi_descriptions[] =
+ IFM_SUBTYPE_FDDI_DESCRIPTIONS;
+
+static struct ifmedia_description ifm_subtype_fddi_aliases[] =
+ IFM_SUBTYPE_FDDI_ALIASES;
+
+static struct ifmedia_description ifm_subtype_fddi_option_descriptions[] =
+ IFM_SUBTYPE_FDDI_OPTION_DESCRIPTIONS;
+
+static struct ifmedia_description ifm_subtype_ieee80211_descriptions[] =
+ IFM_SUBTYPE_IEEE80211_DESCRIPTIONS;
+
+static struct ifmedia_description ifm_subtype_ieee80211_aliases[] =
+ IFM_SUBTYPE_IEEE80211_ALIASES;
+
+static struct ifmedia_description ifm_subtype_ieee80211_option_descriptions[] =
+ IFM_SUBTYPE_IEEE80211_OPTION_DESCRIPTIONS;
+
+static struct ifmedia_description ifm_subtype_ieee80211_mode_descriptions[] =
+ IFM_SUBTYPE_IEEE80211_MODE_DESCRIPTIONS;
+
+static struct ifmedia_description ifm_subtype_ieee80211_mode_aliases[] =
+ IFM_SUBTYPE_IEEE80211_MODE_ALIASES;
+
+static struct ifmedia_description ifm_subtype_atm_descriptions[] =
+ IFM_SUBTYPE_ATM_DESCRIPTIONS;
+
+static struct ifmedia_description ifm_subtype_atm_aliases[] =
+ IFM_SUBTYPE_ATM_ALIASES;
+
+static struct ifmedia_description ifm_subtype_atm_option_descriptions[] =
+ IFM_SUBTYPE_ATM_OPTION_DESCRIPTIONS;
+
+static struct ifmedia_description ifm_subtype_shared_descriptions[] =
+ IFM_SUBTYPE_SHARED_DESCRIPTIONS;
+
+static struct ifmedia_description ifm_subtype_shared_aliases[] =
+ IFM_SUBTYPE_SHARED_ALIASES;
+
+static struct ifmedia_description ifm_shared_option_descriptions[] =
+ IFM_SHARED_OPTION_DESCRIPTIONS;
+
+static struct ifmedia_description ifm_shared_option_aliases[] =
+ IFM_SHARED_OPTION_ALIASES;
+
+struct ifmedia_type_to_subtype {
+ struct {
+ struct ifmedia_description *desc;
+ int alias;
+ } subtypes[5];
+ struct {
+ struct ifmedia_description *desc;
+ int alias;
+ } options[4];
+ struct {
+ struct ifmedia_description *desc;
+ int alias;
+ } modes[3];
+};
+
+/* must be in the same order as IFM_TYPE_DESCRIPTIONS */
+static struct ifmedia_type_to_subtype ifmedia_types_to_subtypes[] = {
+ {
+ {
+ { &ifm_subtype_shared_descriptions[0], 0 },
+ { &ifm_subtype_shared_aliases[0], 1 },
+ { &ifm_subtype_ethernet_descriptions[0], 0 },
+ { &ifm_subtype_ethernet_aliases[0], 1 },
+ { NULL, 0 },
+ },
+ {
+ { &ifm_shared_option_descriptions[0], 0 },
+ { &ifm_shared_option_aliases[0], 1 },
+ { &ifm_subtype_ethernet_option_descriptions[0], 0 },
+ { NULL, 0 },
+ },
+ {
+ { NULL, 0 },
+ },
+ },
+ {
+ {
+ { &ifm_subtype_shared_descriptions[0], 0 },
+ { &ifm_subtype_shared_aliases[0], 1 },
+ { &ifm_subtype_tokenring_descriptions[0], 0 },
+ { &ifm_subtype_tokenring_aliases[0], 1 },
+ { NULL, 0 },
+ },
+ {
+ { &ifm_shared_option_descriptions[0], 0 },
+ { &ifm_shared_option_aliases[0], 1 },
+ { &ifm_subtype_tokenring_option_descriptions[0], 0 },
+ { NULL, 0 },
+ },
+ {
+ { NULL, 0 },
+ },
+ },
+ {
+ {
+ { &ifm_subtype_shared_descriptions[0], 0 },
+ { &ifm_subtype_shared_aliases[0], 1 },
+ { &ifm_subtype_fddi_descriptions[0], 0 },
+ { &ifm_subtype_fddi_aliases[0], 1 },
+ { NULL, 0 },
+ },
+ {
+ { &ifm_shared_option_descriptions[0], 0 },
+ { &ifm_shared_option_aliases[0], 1 },
+ { &ifm_subtype_fddi_option_descriptions[0], 0 },
+ { NULL, 0 },
+ },
+ {
+ { NULL, 0 },
+ },
+ },
+ {
+ {
+ { &ifm_subtype_shared_descriptions[0], 0 },
+ { &ifm_subtype_shared_aliases[0], 1 },
+ { &ifm_subtype_ieee80211_descriptions[0], 0 },
+ { &ifm_subtype_ieee80211_aliases[0], 1 },
+ { NULL, 0 },
+ },
+ {
+ { &ifm_shared_option_descriptions[0], 0 },
+ { &ifm_shared_option_aliases[0], 1 },
+ { &ifm_subtype_ieee80211_option_descriptions[0], 0 },
+ { NULL, 0 },
+ },
+ {
+ { &ifm_subtype_ieee80211_mode_descriptions[0], 0 },
+ { &ifm_subtype_ieee80211_mode_aliases[0], 0 },
+ { NULL, 0 },
+ },
+ },
+ {
+ {
+ { &ifm_subtype_shared_descriptions[0], 0 },
+ { &ifm_subtype_shared_aliases[0], 1 },
+ { &ifm_subtype_atm_descriptions[0], 0 },
+ { &ifm_subtype_atm_aliases[0], 1 },
+ { NULL, 0 },
+ },
+ {
+ { &ifm_shared_option_descriptions[0], 0 },
+ { &ifm_shared_option_aliases[0], 1 },
+ { &ifm_subtype_atm_option_descriptions[0], 0 },
+ { NULL, 0 },
+ },
+ {
+ { NULL, 0 },
+ },
+ },
+};
+
+int
+get_media_subtype(int type, const char *val)
+{
+ struct ifmedia_description *desc;
+ struct ifmedia_type_to_subtype *ttos;
+ int rval, i;
+
+ /* Find the top-level interface type. */
+ for (desc = ifm_type_descriptions, ttos = ifmedia_types_to_subtypes;
+ desc->ifmt_string != NULL; desc++, ttos++)
+ if (type == desc->ifmt_word)
+ break;
+ if (desc->ifmt_string == NULL)
+ errx(1, "unknown media type 0x%x", type);
+
+ for (i = 0; ttos->subtypes[i].desc != NULL; i++) {
+ rval = lookup_media_word(ttos->subtypes[i].desc, val);
+ if (rval != -1)
+ return (rval);
+ }
+ errx(1, "unknown media subtype: %s", val);
+ /*NOTREACHED*/
+}
+
+int
+get_media_mode(int type, const char *val)
+{
+ struct ifmedia_description *desc;
+ struct ifmedia_type_to_subtype *ttos;
+ int rval, i;
+
+ /* Find the top-level interface type. */
+ for (desc = ifm_type_descriptions, ttos = ifmedia_types_to_subtypes;
+ desc->ifmt_string != NULL; desc++, ttos++)
+ if (type == desc->ifmt_word)
+ break;
+ if (desc->ifmt_string == NULL)
+ errx(1, "unknown media mode 0x%x", type);
+
+ for (i = 0; ttos->modes[i].desc != NULL; i++) {
+ rval = lookup_media_word(ttos->modes[i].desc, val);
+ if (rval != -1)
+ return (rval);
+ }
+ return -1;
+}
+
+int
+get_media_options(int type, const char *val)
+{
+ struct ifmedia_description *desc;
+ struct ifmedia_type_to_subtype *ttos;
+ char *optlist, *optptr;
+ int option = 0, i, rval = 0;
+
+ /* We muck with the string, so copy it. */
+ optlist = strdup(val);
+ if (optlist == NULL)
+ err(1, "strdup");
+
+ /* Find the top-level interface type. */
+ for (desc = ifm_type_descriptions, ttos = ifmedia_types_to_subtypes;
+ desc->ifmt_string != NULL; desc++, ttos++)
+ if (type == desc->ifmt_word)
+ break;
+ if (desc->ifmt_string == NULL)
+ errx(1, "unknown media type 0x%x", type);
+
+ /*
+ * Look up the options in the user-provided comma-separated
+ * list.
+ */
+ optptr = optlist;
+ for (; (optptr = strtok(optptr, ",")) != NULL; optptr = NULL) {
+ for (i = 0; ttos->options[i].desc != NULL; i++) {
+ option = lookup_media_word(ttos->options[i].desc, optptr);
+ if (option != -1)
+ break;
+ }
+ if (option == 0)
+ errx(1, "unknown option: %s", optptr);
+ rval |= option;
+ }
+
+ free(optlist);
+ return (rval);
+}
+
+int
+lookup_media_word(struct ifmedia_description *desc, const char *val)
+{
+
+ for (; desc->ifmt_string != NULL; desc++)
+ if (strcasecmp(desc->ifmt_string, val) == 0)
+ return (desc->ifmt_word);
+
+ return (-1);
+}
+
+static struct ifmedia_description *get_toptype_desc(int ifmw)
+{
+ struct ifmedia_description *desc;
+
+ for (desc = ifm_type_descriptions; desc->ifmt_string != NULL; desc++)
+ if (IFM_TYPE(ifmw) == desc->ifmt_word)
+ break;
+
+ return desc;
+}
+
+static struct ifmedia_type_to_subtype *get_toptype_ttos(int ifmw)
+{
+ struct ifmedia_description *desc;
+ struct ifmedia_type_to_subtype *ttos;
+
+ for (desc = ifm_type_descriptions, ttos = ifmedia_types_to_subtypes;
+ desc->ifmt_string != NULL; desc++, ttos++)
+ if (IFM_TYPE(ifmw) == desc->ifmt_word)
+ break;
+
+ return ttos;
+}
+
+static struct ifmedia_description *get_subtype_desc(int ifmw,
+ struct ifmedia_type_to_subtype *ttos)
+{
+ int i;
+ struct ifmedia_description *desc;
+
+ for (i = 0; ttos->subtypes[i].desc != NULL; i++) {
+ if (ttos->subtypes[i].alias)
+ continue;
+ for (desc = ttos->subtypes[i].desc;
+ desc->ifmt_string != NULL; desc++) {
+ if (IFM_SUBTYPE(ifmw) == desc->ifmt_word)
+ return desc;
+ }
+ }
+
+ return NULL;
+}
+
+static struct ifmedia_description *get_mode_desc(int ifmw,
+ struct ifmedia_type_to_subtype *ttos)
+{
+ int i;
+ struct ifmedia_description *desc;
+
+ for (i = 0; ttos->modes[i].desc != NULL; i++) {
+ if (ttos->modes[i].alias)
+ continue;
+ for (desc = ttos->modes[i].desc;
+ desc->ifmt_string != NULL; desc++) {
+ if (IFM_MODE(ifmw) == desc->ifmt_word)
+ return desc;
+ }
+ }
+
+ return NULL;
+}
+
+void
+print_media_word(int ifmw, int print_toptype)
+{
+ struct ifmedia_description *desc;
+ struct ifmedia_type_to_subtype *ttos;
+ int seen_option = 0, i;
+
+ /* Find the top-level interface type. */
+ desc = get_toptype_desc(ifmw);
+ ttos = get_toptype_ttos(ifmw);
+ if (desc->ifmt_string == NULL) {
+ printf("<unknown type>");
+ return;
+ } else if (print_toptype) {
+ printf("%s", desc->ifmt_string);
+ }
+
+ /*
+ * Don't print the top-level type; it's not like we can
+ * change it, or anything.
+ */
+
+ /* Find subtype. */
+ desc = get_subtype_desc(ifmw, ttos);
+ if (desc == NULL) {
+ printf("<unknown subtype>");
+ return;
+ }
+
+ if (print_toptype)
+ putchar(' ');
+
+ printf("%s", desc->ifmt_string);
+
+ if (print_toptype) {
+ desc = get_mode_desc(ifmw, ttos);
+ if (desc != NULL && strcasecmp("autoselect", desc->ifmt_string))
+ printf(" mode %s", desc->ifmt_string);
+ }
+
+ /* Find options. */
+ for (i = 0; ttos->options[i].desc != NULL; i++) {
+ if (ttos->options[i].alias)
+ continue;
+ for (desc = ttos->options[i].desc;
+ desc->ifmt_string != NULL; desc++) {
+ if (ifmw & desc->ifmt_word) {
+ if (seen_option == 0)
+ printf(" <");
+ printf("%s%s", seen_option++ ? "," : "",
+ desc->ifmt_string);
+ }
+ }
+ }
+ printf("%s", seen_option ? ">" : "");
+
+ if (print_toptype && IFM_INST(ifmw) != 0)
+ printf(" instance %d", IFM_INST(ifmw));
+}
+
+void
+print_media_word_ifconfig(int ifmw)
+{
+ struct ifmedia_description *desc;
+ struct ifmedia_type_to_subtype *ttos;
+ int seen_option = 0, i;
+
+ /* Find the top-level interface type. */
+ desc = get_toptype_desc(ifmw);
+ ttos = get_toptype_ttos(ifmw);
+ if (desc->ifmt_string == NULL) {
+ printf("<unknown type>");
+ return;
+ }
+
+ /*
+ * Don't print the top-level type; it's not like we can
+ * change it, or anything.
+ */
+
+ /* Find subtype. */
+ desc = get_subtype_desc(ifmw, ttos);
+ if (desc == NULL) {
+ printf("<unknown subtype>");
+ return;
+ }
+
+ printf("media %s", desc->ifmt_string);
+
+ desc = get_mode_desc(ifmw, ttos);
+ if (desc != NULL)
+ printf(" mode %s", desc->ifmt_string);
+
+ /* Find options. */
+ for (i = 0; ttos->options[i].desc != NULL; i++) {
+ if (ttos->options[i].alias)
+ continue;
+ for (desc = ttos->options[i].desc;
+ desc->ifmt_string != NULL; desc++) {
+ if (ifmw & desc->ifmt_word) {
+ if (seen_option == 0)
+ printf(" mediaopt ");
+ printf("%s%s", seen_option++ ? "," : "",
+ desc->ifmt_string);
+ }
+ }
+ }
+
+ if (IFM_INST(ifmw) != 0)
+ printf(" instance %d", IFM_INST(ifmw));
+}
+
+/**********************************************************************
+ * ...until here.
+ **********************************************************************/
diff --git a/sbin/fdisk/Makefile b/sbin/fdisk/Makefile
new file mode 100644
index 0000000..a49c699
--- /dev/null
+++ b/sbin/fdisk/Makefile
@@ -0,0 +1,15 @@
+# $FreeBSD$
+
+PROG= fdisk
+SRCS= fdisk.c geom_mbr_enc.c
+WARNS?= 4
+MAN= fdisk.8
+
+.PATH: ${.CURDIR}/../../sys/geom
+
+LIBADD= geom
+
+.include <bsd.prog.mk>
+
+test: ${PROG}
+ sh ${.CURDIR}/runtest.sh
diff --git a/sbin/fdisk/fdisk.8 b/sbin/fdisk/fdisk.8
new file mode 100644
index 0000000..fcab133
--- /dev/null
+++ b/sbin/fdisk/fdisk.8
@@ -0,0 +1,501 @@
+.\" $FreeBSD$
+.\"
+.Dd October 1, 2013
+.Dt FDISK 8
+.Os
+.Sh NAME
+.Nm fdisk
+.Nd PC slice table maintenance utility
+.Sh SYNOPSIS
+.Nm
+.Op Fl BIaipqstu
+.Op Fl b Ar bootcode
+.Op Fl 1234
+.Op Ar disk
+.Nm
+.Fl f Ar configfile
+.Op Fl itv
+.Op Ar disk
+.Sh PROLOGUE
+In order for the BIOS to boot the kernel,
+certain conventions must be adhered to.
+Sector 0 of the disk must contain boot code,
+a slice table,
+and a magic number.
+BIOS slices can be used to break the disk up into several pieces.
+The BIOS brings in sector 0 and verifies the magic number.
+The sector
+0 boot code then searches the slice table to determine which
+slice is marked
+.Dq active .
+This boot code then brings in the bootstrap from the
+active slice and, if marked bootable, runs it.
+Under
+.Tn DOS ,
+you can have one or more slices with one active.
+The
+.Tn DOS
+.Nm
+utility can be used to divide space on the disk into slices and set one
+active.
+.Sh DESCRIPTION
+The
+.Fx
+utility,
+.Nm ,
+serves a similar purpose to the
+.Tn DOS
+utility.
+The first form is used to
+display slice information or to interactively edit the slice
+table.
+The second is used to write a slice table using a
+.Ar configfile ,
+and is designed to be used by other scripts/programs.
+.Pp
+Options are:
+.Bl -tag -width indent
+.It Fl a
+Change the active slice only.
+Ignored if
+.Fl f
+is given.
+.It Fl b Ar bootcode
+Get the boot code from the file
+.Ar bootcode .
+Default is
+.Pa /boot/mbr .
+.It Fl B
+Reinitialize the boot code contained in sector 0 of the disk.
+Ignored if
+.Fl f
+is given.
+.It Fl f Ar configfile
+Set slice values using the file
+.Ar configfile .
+The
+.Ar configfile
+only modifies explicitly specified slices, unless
+.Fl i
+is also given, in which case all existing slices are deleted (marked
+as
+.Dq unused )
+before the
+.Ar configfile
+is read.
+The
+.Ar configfile
+can be
+.Sq Fl ,
+in which case standard input is read.
+See
+.Sx CONFIGURATION FILE ,
+below, for file syntax.
+.Pp
+.Em WARNING :
+when
+.Fl f
+is used, you are not asked if you really want to write the slices
+table (as you are in the interactive mode).
+Use with caution!
+.It Fl i
+Initialize sector 0 of the disk.
+Existing slice entries will be cleared
+(marked as unused) before editing.
+(Compare with
+.Fl u . )
+.It Fl I
+Initialize sector 0 slice table
+for one
+.Fx
+slice covering the entire disk.
+.It Fl p
+Print a slice table in
+.Nm
+configuration file format and exit; see
+.Sx CONFIGURATION FILE ,
+below.
+.It Fl q
+Be quiet.
+Benign warnings (such as "GEOM not found") are suppressed.
+.It Fl s
+Print summary information and exit.
+.It Fl t
+Test mode; do not write slice values.
+Generally used with the
+.Fl f
+option to see what would be written to the slice table.
+Implies
+.Fl v .
+.It Fl u
+Update (edit) the disk's sector 0 slice table.
+Ignored if
+.Fl f
+is given.
+.It Fl v
+Be verbose.
+When
+.Fl f
+is used,
+.Nm
+prints out the slice table that is written to the disk.
+.It Fl 1234
+Operate on a single slice table entry only.
+Ignored if
+.Fl f
+is given.
+.El
+.Pp
+The final disk name can be provided as a
+.Dq bare
+disk name only, e.g.\&
+.Pa da0 ,
+or as a full pathname.
+If omitted,
+.Nm
+tries to figure out the default disk device name from the
+mounted root device.
+.Pp
+When called with no arguments, it prints the sector 0 slice table.
+An example follows:
+.Bd -literal
+ ******* Working on device /dev/ada0 *******
+ parameters extracted from in-core disklabel are:
+ cylinders=769 heads=15 sectors/track=33 (495 blks/cyl)
+
+ parameters to be used for BIOS calculations are:
+ cylinders=769 heads=15 sectors/track=33 (495 blks/cyl)
+
+ Warning: BIOS sector numbering starts with sector 1
+ Information from DOS bootblock is:
+ The data for partition 1 is:
+ sysid 165,(FreeBSD/NetBSD/386BSD)
+ start 495, size 380160 (185 Meg), flag 0
+ beg: cyl 1/ sector 1/ head 0;
+ end: cyl 768/ sector 33/ head 14
+ The data for partition 2 is:
+ sysid 164,(unknown)
+ start 378180, size 2475 (1 Meg), flag 0
+ beg: cyl 764/ sector 1/ head 0;
+ end: cyl 768/ sector 33/ head 14
+ The data for partition 3 is:
+ <UNUSED>
+ The data for partition 4 is:
+ sysid 99,(ISC UNIX, other System V/386, GNU HURD or Mach)
+ start 380656, size 224234 (109 Meg), flag 80
+ beg: cyl 769/ sector 2/ head 0;
+ end: cyl 197/ sector 33/ head 14
+.Ed
+.Pp
+The disk is divided into three slices that happen to fill the disk.
+The second slice overlaps the end of the first.
+(Used for debugging purposes.)
+.Bl -tag -width ".Em cyl , sector No and Em head"
+.It Em sysid
+is used to label the slice.
+.Fx
+reserves the
+magic number 165 decimal (A5 in hex).
+.It Xo
+.Em start
+and
+.Em size
+.Xc
+fields provide the start address
+and size of a slice in sectors.
+.It Em "flag 80"
+specifies that this is the active slice.
+.It Xo
+.Em cyl , sector
+and
+.Em head
+.Xc
+fields are used to specify the beginning and end addresses of the slice.
+.El
+.Pp
+.Em Note :
+these numbers are calculated using BIOS's understanding of the disk geometry
+and saved in the bootblock.
+.Pp
+The
+.Fl i
+and
+.Fl u
+flags are used to indicate that the slice data is to be updated.
+Unless the
+.Fl f
+option is also given,
+.Nm
+will enter a conversational mode.
+In this mode, no changes will be written to disk unless you explicitly tell
+.Nm
+to.
+.Pp
+The
+.Nm
+utility will display each slice and ask whether you want to edit it.
+If you say yes,
+.Nm
+will step through each field, show you the old value,
+and ask you for a new one.
+When you are done with the slice,
+.Nm
+will display it and ask you whether it is correct.
+It will then proceed to the next entry.
+.Pp
+Getting the
+.Em cyl , sector ,
+and
+.Em head
+fields correct is tricky, so by default,
+they will be calculated for you;
+you can specify them if you choose to though.
+.Pp
+After all the slices are processed,
+you are given the option to change the
+.Dq active
+slice.
+Finally, when all the new data for sector 0 has been accumulated,
+you are asked to confirm whether you really want to rewrite it.
+.Pp
+The difference between the
+.Fl u
+and
+.Fl i
+flags is that
+the
+.Fl u
+flag edits (updates) the existing slice parameters
+while the
+.Fl i
+flag is used to
+.Dq initialize
+them (old values will be ignored);
+if you edit the first slice,
+.Fl i
+will also set it up to use the whole disk for
+.Fx
+and make it active.
+.Sh NOTES
+The automatic calculation of starting cylinder etc.\& uses
+a set of figures that represent what the BIOS thinks the
+geometry of the drive is.
+These figures are taken from the in-core disklabel by default,
+but
+.Nm
+initially gives you an opportunity to change them.
+This allows you to create a bootblock that can work with drives
+that use geometry translation under the BIOS.
+.Pp
+If you hand craft your disk layout,
+please make sure that the
+.Fx
+slice starts on a cylinder boundary.
+.Pp
+Editing an existing slice will most likely result in the loss of
+all data in that slice.
+.Pp
+You should run
+.Nm
+interactively once or twice to see how it works.
+This is completely safe as long as you answer the last question
+in the negative.
+There are subtleties that
+.Nm
+detects that are not fully explained in this manual page.
+.Sh CONFIGURATION FILE
+When the
+.Fl f
+option is given, a disk's slice table can be written using values
+from a
+.Ar configfile .
+The syntax of this file is very simple;
+each line is either a comment or a specification, as follows:
+.Bl -tag -width indent
+.It Ic # Ar comment ...
+Lines beginning with a
+.Ic #
+are comments and are ignored.
+.It Ic g Ar spec1 spec2 spec3
+Set the BIOS geometry used in slice calculations.
+There must be
+three values specified, with a letter preceding each number:
+.Bl -tag -width indent
+.It Cm c Ns Ar num
+Set the number of cylinders to
+.Ar num .
+.It Cm h Ns Ar num
+Set the number of heads to
+.Ar num .
+.It Cm s Ns Ar num
+Set the number of sectors/track to
+.Ar num .
+.El
+.Pp
+These specs can occur in any order, as the leading letter determines
+which value is which; however, all three must be specified.
+.Pp
+This line must occur before any lines that specify slice
+information.
+.Pp
+It is an error if the following is not true:
+.Bd -literal -offset indent
+1 <= number of cylinders
+1 <= number of heads <= 256
+1 <= number of sectors/track < 64
+.Ed
+.Pp
+The number of cylinders should be less than or equal to 1024, but this
+is not enforced, although a warning will be printed.
+Note that bootable
+.Fx
+slices (the
+.Dq Pa /
+file system) must lie completely within the
+first 1024 cylinders; if this is not true, booting may fail.
+Non-bootable slices do not have this restriction.
+.Pp
+Example (all of these are equivalent), for a disk with 1019 cylinders,
+39 heads, and 63 sectors:
+.Bd -literal -offset indent
+g c1019 h39 s63
+g h39 c1019 s63
+g s63 h39 c1019
+.Ed
+.It Ic p Ar slice type start length
+Set the slice given by
+.Ar slice
+(1-4) to type
+.Ar type ,
+starting at sector
+.Ar start
+for
+.Ar length
+sectors.
+If the
+.Ar start
+or
+.Ar length
+is suffixed with a
+.Em K ,
+.Em M
+or
+.Em G ,
+it is taken as a
+.Em Kilobyte ,
+.Em Megabyte
+or
+.Em Gigabyte
+measurement respectively.
+If the
+.Ar start
+is given as
+.Qq *
+it is set to the value of the previous partition end.
+If the
+.Ar length
+is given as
+.Qq *
+the partition end is set to the end of the disk.
+.Pp
+Only those slices explicitly mentioned by these lines are modified;
+any slice not referenced by a
+.Ic p
+line will not be modified.
+However, if an invalid slice table is present, or the
+.Fl i
+option is specified, all existing slice entries will be cleared
+(marked as unused), and these
+.Ic p
+lines will have to be used to
+explicitly set slice information.
+If multiple slices need to be
+set, multiple
+.Ic p
+lines must be specified; one for each slice.
+.Pp
+These slice lines must occur after any geometry specification lines,
+if one is present.
+.Pp
+The
+.Ar type
+is 165 for
+.Fx
+slices.
+Specifying a slice type of zero is
+the same as clearing the slice and marking it as unused; however,
+dummy values (such as
+.Dq 0 )
+must still be specified for
+.Ar start
+and
+.Ar length .
+.Pp
+Note: the start offset will be rounded upwards to a head boundary if
+necessary, and the end offset will be rounded downwards to a cylinder
+boundary if necessary.
+.Pp
+Example: to clear slice 4 and mark it as unused:
+.Pp
+.Dl "p 4 0 0 0"
+.Pp
+Example: to set slice 1 to a
+.Fx
+slice, starting at sector 1
+for 2503871 sectors (note: these numbers will be rounded upwards and
+downwards to correspond to head and cylinder boundaries):
+.Pp
+.Dl "p 1 165 1 2503871"
+.Pp
+Example: to set slices 1, 2 and 4 to
+.Fx
+slices, the first being 2 Gigabytes, the second being 10 Gigabytes and the
+forth being the remainder of the disk (again, numbers will be rounded
+appropriately):
+.Pp
+.Dl "p 1 165 63 2G"
+.Dl "p 2 165 * 10G"
+.Dl "p 3 0 0 0"
+.Dl "p 4 165 * *"
+.It Ic a Ar slice
+Make
+.Ar slice
+the active slice.
+Can occur anywhere in the config file, but only
+one must be present.
+.Pp
+Example: to make slice 1 the active slice:
+.Pp
+.Dl "a 1"
+.El
+.Sh FILES
+.Bl -tag -width ".Pa /boot/mbr" -compact
+.It Pa /boot/mbr
+The default boot code.
+.El
+.Sh SEE ALSO
+.Xr boot0cfg 8 ,
+.Xr bsdlabel 8 ,
+.Xr gpart 8 ,
+.Xr newfs 8
+.Sh BUGS
+The default boot code will not necessarily handle all slice types
+correctly, in particular those introduced since
+.Tn MS-DOS
+6.x.
+.Pp
+The entire utility should be made more user-friendly.
+.Pp
+Most users new to
+.Fx
+do not understand the difference between
+.Dq slice
+and
+.Dq partition ,
+causing difficulty to adjust.
+.Pp
+You cannot use this command to completely dedicate a disk to
+.Fx .
+The
+.Xr bsdlabel 8
+command must be used for this.
diff --git a/sbin/fdisk/fdisk.c b/sbin/fdisk/fdisk.c
new file mode 100644
index 0000000..819c1c7
--- /dev/null
+++ b/sbin/fdisk/fdisk.c
@@ -0,0 +1,1534 @@
+/*
+ * Mach Operating System
+ * Copyright (c) 1992 Carnegie Mellon University
+ * All Rights Reserved.
+ *
+ * Permission to use, copy, modify and distribute this software and its
+ * documentation is hereby granted, provided that both the copyright
+ * notice and this permission notice appear in all copies of the
+ * software, derivative works or modified versions, and any portions
+ * thereof, and that both notices appear in supporting documentation.
+ *
+ * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS"
+ * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR
+ * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
+ *
+ * Carnegie Mellon requests users of this software to return to
+ *
+ * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU
+ * School of Computer Science
+ * Carnegie Mellon University
+ * Pittsburgh PA 15213-3890
+ *
+ * any improvements or extensions that they make and grant Carnegie Mellon
+ * the rights to redistribute these changes.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/disk.h>
+#include <sys/disklabel.h>
+#include <sys/diskmbr.h>
+#include <sys/endian.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/mount.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <err.h>
+#include <errno.h>
+#include <libgeom.h>
+#include <paths.h>
+#include <regex.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+static int iotest;
+
+#define NO_DISK_SECTORS ((u_int32_t)-1)
+#define NO_TRACK_CYLINDERS 1023
+#define NO_TRACK_HEADS 255
+#define NO_TRACK_SECTORS 63
+#define LBUF 100
+static char lbuf[LBUF];
+
+/*
+ *
+ * Ported to 386bsd by Julian Elischer Thu Oct 15 20:26:46 PDT 1992
+ *
+ * 14-Dec-89 Robert Baron (rvb) at Carnegie-Mellon University
+ * Copyright (c) 1989 Robert. V. Baron
+ * Created.
+ */
+
+#define Decimal(str, ans, tmp, maxval) if (decimal(str, &tmp, ans, maxval)) ans = tmp
+
+#define RoundCyl(x) ((((x) + cylsecs - 1) / cylsecs) * cylsecs)
+
+#define MAX_SEC_SIZE 2048 /* maximum section size that is supported */
+#define MIN_SEC_SIZE 512 /* the sector size to start sensing at */
+static int secsize = 0; /* the sensed sector size */
+
+static char *disk;
+
+static int cyls, sectors, heads, cylsecs;
+static u_int32_t disksecs;
+
+struct mboot {
+ unsigned char *bootinst; /* boot code */
+ off_t bootinst_size;
+ struct dos_partition parts[NDOSPART];
+};
+
+static struct mboot mboot;
+static int fd;
+
+#define ACTIVE 0x80
+
+static uint dos_cyls;
+static uint dos_heads;
+static uint dos_sectors;
+static uint dos_cylsecs;
+
+#define DOSSECT(s,c) ((s & 0x3f) | ((c >> 2) & 0xc0))
+#define DOSCYL(c) (c & 0xff)
+
+#define MAX_ARGS 10
+
+static int current_line_number;
+
+static int geom_processed = 0;
+static int part_processed = 0;
+static int active_processed = 0;
+
+typedef struct cmd {
+ char cmd;
+ int n_args;
+ struct arg {
+ char argtype;
+ unsigned long arg_val;
+ char * arg_str;
+ } args[MAX_ARGS];
+} CMD;
+
+static int B_flag = 0; /* replace boot code */
+static int I_flag = 0; /* use entire disk for FreeBSD */
+static int a_flag = 0; /* set active partition */
+static char *b_flag = NULL; /* path to boot code */
+static int i_flag = 0; /* replace partition data */
+static int q_flag = 0; /* Be quiet */
+static int u_flag = 0; /* update partition data */
+static int s_flag = 0; /* Print a summary and exit */
+static int t_flag = 0; /* test only */
+static char *f_flag = NULL; /* Read config info from file */
+static int v_flag = 0; /* Be verbose */
+static int print_config_flag = 0;
+
+/*
+ * A list of partition types, probably outdated.
+ */
+static const char *const part_types[256] = {
+ [0x00] = "unused",
+ [0x01] = "Primary DOS with 12 bit FAT",
+ [0x02] = "XENIX / file system",
+ [0x03] = "XENIX /usr file system",
+ [0x04] = "Primary DOS with 16 bit FAT (< 32MB)",
+ [0x05] = "Extended DOS",
+ [0x06] = "Primary DOS, 16 bit FAT (>= 32MB)",
+ [0x07] = "NTFS, OS/2 HPFS, QNX-2 (16 bit) or Advanced UNIX",
+ [0x08] = "AIX file system or SplitDrive",
+ [0x09] = "AIX boot partition or Coherent",
+ [0x0A] = "OS/2 Boot Manager, OPUS or Coherent swap",
+ [0x0B] = "DOS or Windows 95 with 32 bit FAT",
+ [0x0C] = "DOS or Windows 95 with 32 bit FAT (LBA)",
+ [0x0E] = "Primary 'big' DOS (>= 32MB, LBA)",
+ [0x0F] = "Extended DOS (LBA)",
+ [0x10] = "OPUS",
+ [0x11] = "OS/2 BM: hidden DOS with 12-bit FAT",
+ [0x12] = "Compaq diagnostics",
+ [0x14] = "OS/2 BM: hidden DOS with 16-bit FAT (< 32MB)",
+ [0x16] = "OS/2 BM: hidden DOS with 16-bit FAT (>= 32MB)",
+ [0x17] = "OS/2 BM: hidden IFS (e.g. HPFS)",
+ [0x18] = "AST Windows swapfile",
+ [0x1b] = "ASUS Recovery partition (NTFS)",
+ [0x24] = "NEC DOS",
+ [0x3C] = "PartitionMagic recovery",
+ [0x39] = "plan9",
+ [0x40] = "VENIX 286",
+ [0x41] = "Linux/MINIX (sharing disk with DRDOS)",
+ [0x42] = "SFS or Linux swap (sharing disk with DRDOS)",
+ [0x43] = "Linux native (sharing disk with DRDOS)",
+ [0x4D] = "QNX 4.2 Primary",
+ [0x4E] = "QNX 4.2 Secondary",
+ [0x4F] = "QNX 4.2 Tertiary",
+ [0x50] = "DM (disk manager)",
+ [0x51] = "DM6 Aux1 (or Novell)",
+ [0x52] = "CP/M or Microport SysV/AT",
+ [0x53] = "DM6 Aux3",
+ [0x54] = "DM6",
+ [0x55] = "EZ-Drive (disk manager)",
+ [0x56] = "Golden Bow (disk manager)",
+ [0x5c] = "Priam Edisk (disk manager)", /* according to S. Widlake */
+ [0x61] = "SpeedStor",
+ [0x63] = "System V/386 (such as ISC UNIX), GNU HURD or Mach",
+ [0x64] = "Novell Netware/286 2.xx",
+ [0x65] = "Novell Netware/386 3.xx",
+ [0x70] = "DiskSecure Multi-Boot",
+ [0x75] = "PCIX",
+ [0x77] = "QNX4.x",
+ [0x78] = "QNX4.x 2nd part",
+ [0x79] = "QNX4.x 3rd part",
+ [0x80] = "Minix until 1.4a",
+ [0x81] = "Minix since 1.4b, early Linux partition or Mitac disk manager",
+ [0x82] = "Linux swap or Solaris x86",
+ [0x83] = "Linux native",
+ [0x84] = "OS/2 hidden C: drive",
+ [0x85] = "Linux extended",
+ [0x86] = "NTFS volume set??",
+ [0x87] = "NTFS volume set??",
+ [0x93] = "Amoeba file system",
+ [0x94] = "Amoeba bad block table",
+ [0x9F] = "BSD/OS",
+ [0xA0] = "Suspend to Disk",
+ [0xA5] = "FreeBSD/NetBSD/386BSD",
+ [0xA6] = "OpenBSD",
+ [0xA7] = "NeXTSTEP",
+ [0xA9] = "NetBSD",
+ [0xAC] = "IBM JFS",
+ [0xAF] = "HFS+",
+ [0xB7] = "BSDI BSD/386 file system",
+ [0xB8] = "BSDI BSD/386 swap",
+ [0xBE] = "Solaris x86 boot",
+ [0xBF] = "Solaris x86 (new)",
+ [0xC1] = "DRDOS/sec with 12-bit FAT",
+ [0xC4] = "DRDOS/sec with 16-bit FAT (< 32MB)",
+ [0xC6] = "DRDOS/sec with 16-bit FAT (>= 32MB)",
+ [0xC7] = "Syrinx",
+ [0xDB] = "CP/M, Concurrent CP/M, Concurrent DOS or CTOS",
+ [0xDE] = "DELL Utilities - FAT filesystem",
+ [0xE1] = "DOS access or SpeedStor with 12-bit FAT extended partition",
+ [0xE3] = "DOS R/O or SpeedStor",
+ [0xE4] = "SpeedStor with 16-bit FAT extended partition < 1024 cyl.",
+ [0xEB] = "BeOS file system",
+ [0xEE] = "EFI GPT",
+ [0xEF] = "EFI System Partition",
+ [0xF1] = "SpeedStor",
+ [0xF2] = "DOS 3.3+ Secondary",
+ [0xF4] = "SpeedStor large partition",
+ [0xFB] = "VMware VMFS",
+ [0xFE] = "SpeedStor >1024 cyl. or LANstep",
+ [0xFF] = "Xenix bad blocks table",
+};
+
+static const char *
+get_type(int t)
+{
+ const char *ret;
+
+ ret = (t >= 0 && t <= 255) ? part_types[t] : NULL;
+ return ret ? ret : "unknown";
+}
+
+
+static int geom_class_available(const char *);
+static void print_s0(void);
+static void print_part(const struct dos_partition *);
+static void init_sector0(unsigned long start);
+static void init_boot(void);
+static void change_part(int i);
+static void print_params(void);
+static void change_active(int which);
+static void change_code(void);
+static void get_params_to_use(void);
+static char *get_rootdisk(void);
+static void dos(struct dos_partition *partp);
+static int open_disk(int flag);
+static ssize_t read_disk(off_t sector, void *buf);
+static int write_disk(off_t sector, void *buf);
+static int get_params(void);
+static int read_s0(void);
+static int write_s0(void);
+static int ok(const char *str);
+static int decimal(const char *str, int *num, int deflt, uint32_t maxval);
+static int read_config(char *config_file);
+static void reset_boot(void);
+static int sanitize_partition(struct dos_partition *);
+static void usage(void);
+
+int
+main(int argc, char *argv[])
+{
+ int c, i;
+ int partition = -1;
+ struct dos_partition *partp;
+
+ while ((c = getopt(argc, argv, "BIab:f:ipqstuv1234")) != -1)
+ switch (c) {
+ case 'B':
+ B_flag = 1;
+ break;
+ case 'I':
+ I_flag = 1;
+ break;
+ case 'a':
+ a_flag = 1;
+ break;
+ case 'b':
+ b_flag = optarg;
+ break;
+ case 'f':
+ f_flag = optarg;
+ break;
+ case 'i':
+ i_flag = 1;
+ break;
+ case 'p':
+ print_config_flag = 1;
+ break;
+ case 'q':
+ q_flag = 1;
+ break;
+ case 's':
+ s_flag = 1;
+ break;
+ case 't':
+ t_flag = 1;
+ break;
+ case 'u':
+ u_flag = 1;
+ break;
+ case 'v':
+ v_flag = 1;
+ break;
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ partition = c - '0';
+ break;
+ default:
+ usage();
+ }
+ if (f_flag || i_flag)
+ u_flag = 1;
+ if (t_flag)
+ v_flag = 1;
+ argc -= optind;
+ argv += optind;
+
+ if (argc == 0) {
+ disk = get_rootdisk();
+ } else {
+ disk = g_device_path(argv[0]);
+ if (disk == NULL)
+ err(1, "unable to get correct path for %s", argv[0]);
+ }
+ if (open_disk(u_flag) < 0)
+ err(1, "cannot open disk %s", disk);
+
+ /* (abu)use mboot.bootinst to probe for the sector size */
+ if ((mboot.bootinst = malloc(MAX_SEC_SIZE)) == NULL)
+ err(1, "cannot allocate buffer to determine disk sector size");
+ if (read_disk(0, mboot.bootinst) == -1)
+ errx(1, "could not detect sector size");
+ free(mboot.bootinst);
+ mboot.bootinst = NULL;
+
+ if (print_config_flag) {
+ if (read_s0())
+ err(1, "read_s0");
+
+ printf("# %s\n", disk);
+ printf("g c%d h%d s%d\n", dos_cyls, dos_heads, dos_sectors);
+
+ for (i = 0; i < NDOSPART; i++) {
+ partp = &mboot.parts[i];
+
+ if (partp->dp_start == 0 && partp->dp_size == 0)
+ continue;
+
+ printf("p %d 0x%02x %lu %lu\n", i + 1, partp->dp_typ,
+ (u_long)partp->dp_start, (u_long)partp->dp_size);
+
+ /* Fill flags for the partition. */
+ if (partp->dp_flag & 0x80)
+ printf("a %d\n", i + 1);
+ }
+ exit(0);
+ }
+ if (s_flag) {
+ if (read_s0())
+ err(1, "read_s0");
+ printf("%s: %d cyl %d hd %d sec\n", disk, dos_cyls, dos_heads,
+ dos_sectors);
+ printf("Part %11s %11s Type Flags\n", "Start", "Size");
+ for (i = 0; i < NDOSPART; i++) {
+ partp = &mboot.parts[i];
+ if (partp->dp_start == 0 && partp->dp_size == 0)
+ continue;
+ printf("%4d: %11lu %11lu 0x%02x 0x%02x\n", i + 1,
+ (u_long) partp->dp_start,
+ (u_long) partp->dp_size, partp->dp_typ,
+ partp->dp_flag);
+ }
+ exit(0);
+ }
+
+ printf("******* Working on device %s *******\n",disk);
+
+ if (I_flag) {
+ read_s0();
+ reset_boot();
+ partp = &mboot.parts[0];
+ partp->dp_typ = DOSPTYP_386BSD;
+ partp->dp_flag = ACTIVE;
+ partp->dp_start = dos_sectors;
+ partp->dp_size = (disksecs / dos_cylsecs) * dos_cylsecs -
+ dos_sectors;
+ dos(partp);
+ if (v_flag)
+ print_s0();
+ if (!t_flag)
+ write_s0();
+ exit(0);
+ }
+ if (f_flag) {
+ if (read_s0() || i_flag)
+ reset_boot();
+ if (!read_config(f_flag))
+ exit(1);
+ if (v_flag)
+ print_s0();
+ if (!t_flag)
+ write_s0();
+ } else {
+ if(u_flag)
+ get_params_to_use();
+ else
+ print_params();
+
+ if (read_s0())
+ init_sector0(dos_sectors);
+
+ printf("Media sector size is %d\n", secsize);
+ printf("Warning: BIOS sector numbering starts with sector 1\n");
+ printf("Information from DOS bootblock is:\n");
+ if (partition == -1)
+ for (i = 1; i <= NDOSPART; i++)
+ change_part(i);
+ else
+ change_part(partition);
+
+ if (u_flag || a_flag)
+ change_active(partition);
+
+ if (B_flag)
+ change_code();
+
+ if (u_flag || a_flag || B_flag) {
+ if (!t_flag) {
+ printf("\nWe haven't changed the partition table yet. ");
+ printf("This is your last chance.\n");
+ }
+ print_s0();
+ if (!t_flag) {
+ if (ok("Should we write new partition table?"))
+ write_s0();
+ } else {
+ printf("\n-t flag specified -- partition table not written.\n");
+ }
+ }
+ }
+
+ exit(0);
+}
+
+static void
+usage()
+{
+ fprintf(stderr, "%s%s",
+ "usage: fdisk [-BIaipqstu] [-b bootcode] [-1234] [disk]\n",
+ " fdisk -f configfile [-itv] [disk]\n");
+ exit(1);
+}
+
+static void
+print_s0(void)
+{
+ int i;
+
+ print_params();
+ printf("Information from DOS bootblock is:\n");
+ for (i = 1; i <= NDOSPART; i++) {
+ printf("%d: ", i);
+ print_part(&mboot.parts[i - 1]);
+ }
+}
+
+static struct dos_partition mtpart;
+
+static void
+print_part(const struct dos_partition *partp)
+{
+ u_int64_t part_mb;
+
+ if (!bcmp(partp, &mtpart, sizeof (struct dos_partition))) {
+ printf("<UNUSED>\n");
+ return;
+ }
+ /*
+ * Be careful not to overflow.
+ */
+ part_mb = partp->dp_size;
+ part_mb *= secsize;
+ part_mb /= (1024 * 1024);
+ printf("sysid %d (%#04x),(%s)\n", partp->dp_typ, partp->dp_typ,
+ get_type(partp->dp_typ));
+ printf(" start %lu, size %lu (%ju Meg), flag %x%s\n",
+ (u_long)partp->dp_start,
+ (u_long)partp->dp_size,
+ (uintmax_t)part_mb,
+ partp->dp_flag,
+ partp->dp_flag == ACTIVE ? " (active)" : "");
+ printf("\tbeg: cyl %d/ head %d/ sector %d;\n\tend: cyl %d/ head %d/ sector %d\n"
+ ,DPCYL(partp->dp_scyl, partp->dp_ssect)
+ ,partp->dp_shd
+ ,DPSECT(partp->dp_ssect)
+ ,DPCYL(partp->dp_ecyl, partp->dp_esect)
+ ,partp->dp_ehd
+ ,DPSECT(partp->dp_esect));
+}
+
+
+static void
+init_boot(void)
+{
+ const char *fname;
+ int fdesc, n;
+ struct stat sb;
+
+ fname = b_flag ? b_flag : "/boot/mbr";
+ if ((fdesc = open(fname, O_RDONLY)) == -1 ||
+ fstat(fdesc, &sb) == -1)
+ err(1, "%s", fname);
+ if (sb.st_size == 0)
+ errx(1, "%s is empty, must not be.", fname);
+ if ((mboot.bootinst_size = sb.st_size) % secsize != 0)
+ errx(1, "%s: length must be a multiple of sector size", fname);
+ if (mboot.bootinst != NULL)
+ free(mboot.bootinst);
+ if ((mboot.bootinst = malloc(mboot.bootinst_size = sb.st_size)) == NULL)
+ errx(1, "%s: unable to allocate read buffer", fname);
+ if ((n = read(fdesc, mboot.bootinst, mboot.bootinst_size)) == -1 ||
+ close(fdesc))
+ err(1, "%s", fname);
+ if (n != mboot.bootinst_size)
+ errx(1, "%s: short read", fname);
+}
+
+
+static void
+init_sector0(unsigned long start)
+{
+ struct dos_partition *partp = &mboot.parts[0];
+
+ init_boot();
+
+ partp->dp_typ = DOSPTYP_386BSD;
+ partp->dp_flag = ACTIVE;
+ start = ((start + dos_sectors - 1) / dos_sectors) * dos_sectors;
+ if(start == 0)
+ start = dos_sectors;
+ partp->dp_start = start;
+ partp->dp_size = (disksecs / dos_cylsecs) * dos_cylsecs - start;
+
+ dos(partp);
+}
+
+static void
+change_part(int i)
+{
+ struct dos_partition *partp = &mboot.parts[i - 1];
+
+ printf("The data for partition %d is:\n", i);
+ print_part(partp);
+
+ if (u_flag && ok("Do you want to change it?")) {
+ int tmp;
+
+ if (i_flag) {
+ bzero(partp, sizeof (*partp));
+ if (i == 1) {
+ init_sector0(1);
+ printf("\nThe static data for the slice 1 has been reinitialized to:\n");
+ print_part(partp);
+ }
+ }
+
+ do {
+ Decimal("sysid (165=FreeBSD)", partp->dp_typ, tmp, 255);
+ Decimal("start", partp->dp_start, tmp, NO_DISK_SECTORS);
+ Decimal("size", partp->dp_size, tmp, NO_DISK_SECTORS);
+ if (!sanitize_partition(partp)) {
+ warnx("ERROR: failed to adjust; setting sysid to 0");
+ partp->dp_typ = 0;
+ }
+
+ if (ok("Explicitly specify beg/end address ?"))
+ {
+ int tsec,tcyl,thd;
+ tcyl = DPCYL(partp->dp_scyl,partp->dp_ssect);
+ thd = partp->dp_shd;
+ tsec = DPSECT(partp->dp_ssect);
+ Decimal("beginning cylinder", tcyl, tmp, NO_TRACK_CYLINDERS);
+ Decimal("beginning head", thd, tmp, NO_TRACK_HEADS);
+ Decimal("beginning sector", tsec, tmp, NO_TRACK_SECTORS);
+ partp->dp_scyl = DOSCYL(tcyl);
+ partp->dp_ssect = DOSSECT(tsec,tcyl);
+ partp->dp_shd = thd;
+
+ tcyl = DPCYL(partp->dp_ecyl,partp->dp_esect);
+ thd = partp->dp_ehd;
+ tsec = DPSECT(partp->dp_esect);
+ Decimal("ending cylinder", tcyl, tmp, NO_TRACK_CYLINDERS);
+ Decimal("ending head", thd, tmp, NO_TRACK_HEADS);
+ Decimal("ending sector", tsec, tmp, NO_TRACK_SECTORS);
+ partp->dp_ecyl = DOSCYL(tcyl);
+ partp->dp_esect = DOSSECT(tsec,tcyl);
+ partp->dp_ehd = thd;
+ } else
+ dos(partp);
+
+ print_part(partp);
+ } while (!ok("Are we happy with this entry?"));
+ }
+}
+
+static void
+print_params()
+{
+ printf("parameters extracted from in-core disklabel are:\n");
+ printf("cylinders=%d heads=%d sectors/track=%d (%d blks/cyl)\n\n"
+ ,cyls,heads,sectors,cylsecs);
+ if (dos_cyls > 1023 || dos_heads > 255 || dos_sectors > 63)
+ printf("Figures below won't work with BIOS for partitions not in cyl 1\n");
+ printf("parameters to be used for BIOS calculations are:\n");
+ printf("cylinders=%d heads=%d sectors/track=%d (%d blks/cyl)\n\n"
+ ,dos_cyls,dos_heads,dos_sectors,dos_cylsecs);
+}
+
+static void
+change_active(int which)
+{
+ struct dos_partition *partp = &mboot.parts[0];
+ int active, i, new, tmp;
+
+ active = -1;
+ for (i = 0; i < NDOSPART; i++) {
+ if ((partp[i].dp_flag & ACTIVE) == 0)
+ continue;
+ printf("Partition %d is marked active\n", i + 1);
+ if (active == -1)
+ active = i + 1;
+ }
+ if (a_flag && which != -1)
+ active = which;
+ else if (active == -1)
+ active = 1;
+
+ if (!ok("Do you want to change the active partition?"))
+ return;
+setactive:
+ do {
+ new = active;
+ Decimal("active partition", new, tmp, 0);
+ if (new < 1 || new > 4) {
+ printf("Active partition number must be in range 1-4."
+ " Try again.\n");
+ goto setactive;
+ }
+ active = new;
+ } while (!ok("Are you happy with this choice"));
+ for (i = 0; i < NDOSPART; i++)
+ partp[i].dp_flag = 0;
+ if (active > 0 && active <= NDOSPART)
+ partp[active-1].dp_flag = ACTIVE;
+}
+
+static void
+change_code()
+{
+ if (ok("Do you want to change the boot code?"))
+ init_boot();
+}
+
+void
+get_params_to_use()
+{
+ int tmp;
+ print_params();
+ if (ok("Do you want to change our idea of what BIOS thinks ?"))
+ {
+ do
+ {
+ Decimal("BIOS's idea of #cylinders", dos_cyls, tmp, 0);
+ Decimal("BIOS's idea of #heads", dos_heads, tmp, 0);
+ Decimal("BIOS's idea of #sectors", dos_sectors, tmp, 0);
+ dos_cylsecs = dos_heads * dos_sectors;
+ print_params();
+ }
+ while(!ok("Are you happy with this choice"));
+ }
+}
+
+
+/***********************************************\
+* Change real numbers into strange dos numbers *
+\***********************************************/
+static void
+dos(struct dos_partition *partp)
+{
+ int cy, sec;
+ u_int32_t end;
+
+ if (partp->dp_typ == 0 && partp->dp_start == 0 && partp->dp_size == 0) {
+ memcpy(partp, &mtpart, sizeof(*partp));
+ return;
+ }
+
+ /* Start c/h/s. */
+ partp->dp_shd = partp->dp_start % dos_cylsecs / dos_sectors;
+ cy = partp->dp_start / dos_cylsecs;
+ sec = partp->dp_start % dos_sectors + 1;
+ partp->dp_scyl = DOSCYL(cy);
+ partp->dp_ssect = DOSSECT(sec, cy);
+
+ /* End c/h/s. */
+ end = partp->dp_start + partp->dp_size - 1;
+ partp->dp_ehd = end % dos_cylsecs / dos_sectors;
+ cy = end / dos_cylsecs;
+ sec = end % dos_sectors + 1;
+ partp->dp_ecyl = DOSCYL(cy);
+ partp->dp_esect = DOSSECT(sec, cy);
+}
+
+static int
+open_disk(int flag)
+{
+ int rwmode;
+
+ /* Write mode if one of these flags are set. */
+ rwmode = (a_flag || I_flag || B_flag || flag);
+ fd = g_open(disk, rwmode);
+ /* If the mode fails, try read-only if we didn't. */
+ if (fd == -1 && errno == EPERM && rwmode)
+ fd = g_open(disk, 0);
+ if (fd == -1 && errno == ENXIO)
+ return -2;
+ if (fd == -1) {
+ warnx("can't open device %s", disk);
+ return -1;
+ }
+ if (get_params() == -1) {
+ warnx("can't get disk parameters on %s", disk);
+ return -1;
+ }
+ return fd;
+}
+
+static ssize_t
+read_disk(off_t sector, void *buf)
+{
+
+ lseek(fd, (sector * 512), 0);
+ if (secsize == 0)
+ for (secsize = MIN_SEC_SIZE; secsize <= MAX_SEC_SIZE;
+ secsize *= 2) {
+ /* try the read */
+ int size = read(fd, buf, secsize);
+ if (size == secsize)
+ /* it worked so return */
+ return secsize;
+ }
+ else
+ return read(fd, buf, secsize);
+
+ /* we failed to read at any of the sizes */
+ return -1;
+}
+
+static int
+geom_class_available(const char *name)
+{
+ struct gclass *class;
+ struct gmesh mesh;
+ int error;
+
+ error = geom_gettree(&mesh);
+ if (error != 0)
+ errc(1, error, "Cannot get GEOM tree");
+
+ LIST_FOREACH(class, &mesh.lg_class, lg_class) {
+ if (strcmp(class->lg_name, name) == 0) {
+ geom_deletetree(&mesh);
+ return (1);
+ }
+ }
+
+ geom_deletetree(&mesh);
+
+ return (0);
+}
+
+static int
+write_disk(off_t sector, void *buf)
+{
+ struct gctl_req *grq;
+ const char *errmsg;
+ char *pname;
+ int error;
+
+ /* Check that GEOM_MBR is available */
+ if (geom_class_available("MBR") != 0) {
+ grq = gctl_get_handle();
+ gctl_ro_param(grq, "verb", -1, "write MBR");
+ gctl_ro_param(grq, "class", -1, "MBR");
+ pname = g_providername(fd);
+ if (pname == NULL) {
+ warn("Error getting providername for %s", disk);
+ return (-1);
+ }
+ gctl_ro_param(grq, "geom", -1, pname);
+ gctl_ro_param(grq, "data", secsize, buf);
+ errmsg = gctl_issue(grq);
+ free(pname);
+ if (errmsg == NULL) {
+ gctl_free(grq);
+ return(0);
+ }
+ if (!q_flag)
+ warnx("GEOM_MBR: %s", errmsg);
+ gctl_free(grq);
+ } else {
+ /*
+ * Try to write MBR directly. This may help when disk
+ * is not in use.
+ * XXX: hardcoded sectorsize
+ */
+ error = pwrite(fd, buf, secsize, (sector * 512));
+ if (error == secsize)
+ return (0);
+ }
+
+ /*
+ * GEOM_MBR is not available or failed to write MBR.
+ * Now check that we have GEOM_PART and recommend to use gpart (8).
+ */
+ if (geom_class_available("PART") != 0)
+ warnx("Failed to write MBR. Try to use gpart(8).");
+ else
+ warnx("Failed to write sector zero");
+ return(EINVAL);
+}
+
+static int
+get_params()
+{
+ int error;
+ u_int u;
+ off_t o;
+
+ error = ioctl(fd, DIOCGFWSECTORS, &u);
+ if (error == 0)
+ sectors = dos_sectors = u;
+ else
+ sectors = dos_sectors = 63;
+
+ error = ioctl(fd, DIOCGFWHEADS, &u);
+ if (error == 0)
+ heads = dos_heads = u;
+ else
+ heads = dos_heads = 255;
+
+ dos_cylsecs = cylsecs = heads * sectors;
+ disksecs = cyls * heads * sectors;
+
+ u = g_sectorsize(fd);
+ if (u <= 0)
+ return (-1);
+
+ o = g_mediasize(fd);
+ if (o < 0)
+ return (-1);
+ if (o / u <= NO_DISK_SECTORS)
+ disksecs = o / u;
+ else
+ disksecs = NO_DISK_SECTORS;
+ cyls = dos_cyls = o / (u * dos_heads * dos_sectors);
+
+ return (0);
+}
+
+static int
+read_s0()
+{
+ int i;
+
+ mboot.bootinst_size = secsize;
+ if (mboot.bootinst != NULL)
+ free(mboot.bootinst);
+ if ((mboot.bootinst = malloc(mboot.bootinst_size)) == NULL) {
+ warnx("unable to allocate buffer to read fdisk "
+ "partition table");
+ return -1;
+ }
+ if (read_disk(0, mboot.bootinst) == -1) {
+ warnx("can't read fdisk partition table");
+ return -1;
+ }
+ if (le16dec(&mboot.bootinst[DOSMAGICOFFSET]) != DOSMAGIC) {
+ warnx("invalid fdisk partition table found");
+ /* So should we initialize things */
+ return -1;
+ }
+ for (i = 0; i < NDOSPART; i++)
+ dos_partition_dec(
+ &mboot.bootinst[DOSPARTOFF + i * DOSPARTSIZE],
+ &mboot.parts[i]);
+ return 0;
+}
+
+static int
+write_s0()
+{
+ int sector, i;
+
+ if (iotest) {
+ print_s0();
+ return 0;
+ }
+ for(i = 0; i < NDOSPART; i++)
+ dos_partition_enc(&mboot.bootinst[DOSPARTOFF + i * DOSPARTSIZE],
+ &mboot.parts[i]);
+ le16enc(&mboot.bootinst[DOSMAGICOFFSET], DOSMAGIC);
+ for(sector = 0; sector < mboot.bootinst_size / secsize; sector++)
+ if (write_disk(sector,
+ &mboot.bootinst[sector * secsize]) == -1) {
+ warn("can't write fdisk partition table");
+ return -1;
+ }
+ return(0);
+}
+
+
+static int
+ok(const char *str)
+{
+ printf("%s [n] ", str);
+ fflush(stdout);
+ if (fgets(lbuf, LBUF, stdin) == NULL)
+ exit(1);
+ lbuf[strlen(lbuf)-1] = 0;
+
+ if (*lbuf &&
+ (!strcmp(lbuf, "yes") || !strcmp(lbuf, "YES") ||
+ !strcmp(lbuf, "y") || !strcmp(lbuf, "Y")))
+ return 1;
+ else
+ return 0;
+}
+
+static int
+decimal(const char *str, int *num, int deflt, uint32_t maxval)
+{
+ long long acc;
+ int c;
+ char *cp;
+
+ while (1) {
+ acc = 0;
+ printf("Supply a decimal value for \"%s\" [%d] ", str, deflt);
+ fflush(stdout);
+ if (fgets(lbuf, LBUF, stdin) == NULL)
+ exit(1);
+ lbuf[strlen(lbuf)-1] = 0;
+
+ if (!*lbuf)
+ return 0;
+
+ cp = lbuf;
+ while ((c = *cp) && (c == ' ' || c == '\t')) cp++;
+ if (!c)
+ return 0;
+ while ((c = *cp++)) {
+ if (c <= '9' && c >= '0') {
+ if (acc <= maxval || maxval == 0)
+ acc = acc * 10 + c - '0';
+ } else
+ break;
+ }
+ if (c == ' ' || c == '\t')
+ while ((c = *cp) && (c == ' ' || c == '\t')) cp++;
+ if (!c) {
+ if (maxval > 0 && acc > maxval) {
+ acc = maxval;
+ printf("%s exceeds maximum value allowed for "
+ "this field. The value has been reduced "
+ "to %lld\n", lbuf, acc);
+ }
+ *num = acc;
+ return 1;
+ } else
+ printf("%s is an invalid decimal number. Try again.\n",
+ lbuf);
+ }
+}
+
+
+static void
+parse_config_line(char *line, CMD *command)
+{
+ char *cp, *end;
+
+ cp = line;
+ while (1) {
+ memset(command, 0, sizeof(*command));
+
+ while (isspace(*cp)) ++cp;
+ if (*cp == '\0' || *cp == '#')
+ break;
+ command->cmd = *cp++;
+
+ /*
+ * Parse args
+ */
+ while (1) {
+ while (isspace(*cp)) ++cp;
+ if (*cp == '\0')
+ break; /* eol */
+ if (*cp == '#')
+ break; /* found comment */
+ if (isalpha(*cp))
+ command->args[command->n_args].argtype = *cp++;
+ end = NULL;
+ command->args[command->n_args].arg_val = strtoul(cp, &end, 0);
+ if (cp == end || (!isspace(*end) && *end != '\0')) {
+ char ch;
+ end = cp;
+ while (!isspace(*end) && *end != '\0') ++end;
+ ch = *end; *end = '\0';
+ command->args[command->n_args].arg_str = strdup(cp);
+ *end = ch;
+ } else
+ command->args[command->n_args].arg_str = NULL;
+ cp = end;
+ command->n_args++;
+ }
+ break;
+ }
+}
+
+
+static int
+process_geometry(CMD *command)
+{
+ int status = 1, i;
+
+ while (1) {
+ geom_processed = 1;
+ if (part_processed) {
+ warnx(
+ "ERROR line %d: the geometry specification line must occur before\n\
+ all partition specifications",
+ current_line_number);
+ status = 0;
+ break;
+ }
+ if (command->n_args != 3) {
+ warnx("ERROR line %d: incorrect number of geometry args",
+ current_line_number);
+ status = 0;
+ break;
+ }
+ dos_cyls = 0;
+ dos_heads = 0;
+ dos_sectors = 0;
+ for (i = 0; i < 3; ++i) {
+ switch (command->args[i].argtype) {
+ case 'c':
+ dos_cyls = command->args[i].arg_val;
+ break;
+ case 'h':
+ dos_heads = command->args[i].arg_val;
+ break;
+ case 's':
+ dos_sectors = command->args[i].arg_val;
+ break;
+ default:
+ warnx(
+ "ERROR line %d: unknown geometry arg type: '%c' (0x%02x)",
+ current_line_number, command->args[i].argtype,
+ command->args[i].argtype);
+ status = 0;
+ break;
+ }
+ }
+ if (status == 0)
+ break;
+
+ dos_cylsecs = dos_heads * dos_sectors;
+
+ /*
+ * Do sanity checks on parameter values
+ */
+ if (dos_cyls == 0) {
+ warnx("ERROR line %d: number of cylinders not specified",
+ current_line_number);
+ status = 0;
+ }
+ if (dos_cyls > 1024) {
+ warnx(
+ "WARNING line %d: number of cylinders (%d) may be out-of-range\n\
+ (must be within 1-1024 for normal BIOS operation, unless the entire disk\n\
+ is dedicated to FreeBSD)",
+ current_line_number, dos_cyls);
+ }
+
+ if (dos_heads == 0) {
+ warnx("ERROR line %d: number of heads not specified",
+ current_line_number);
+ status = 0;
+ } else if (dos_heads > 256) {
+ warnx("ERROR line %d: number of heads must be within (1-256)",
+ current_line_number);
+ status = 0;
+ }
+
+ if (dos_sectors == 0) {
+ warnx("ERROR line %d: number of sectors not specified",
+ current_line_number);
+ status = 0;
+ } else if (dos_sectors > 63) {
+ warnx("ERROR line %d: number of sectors must be within (1-63)",
+ current_line_number);
+ status = 0;
+ }
+
+ break;
+ }
+ return (status);
+}
+
+static u_int32_t
+str2sectors(const char *str)
+{
+ char *end;
+ unsigned long val;
+
+ val = strtoul(str, &end, 0);
+ if (str == end || *end == '\0') {
+ warnx("ERROR line %d: unexpected size: \'%s\'",
+ current_line_number, str);
+ return NO_DISK_SECTORS;
+ }
+
+ if (*end == 'K')
+ val *= 1024UL / secsize;
+ else if (*end == 'M')
+ val *= 1024UL * 1024UL / secsize;
+ else if (*end == 'G')
+ val *= 1024UL * 1024UL * 1024UL / secsize;
+ else {
+ warnx("ERROR line %d: unexpected modifier: %c "
+ "(not K/M/G)", current_line_number, *end);
+ return NO_DISK_SECTORS;
+ }
+
+ return val;
+}
+
+static int
+process_partition(CMD *command)
+{
+ int status = 0, partition;
+ u_int32_t prev_head_boundary, prev_cyl_boundary;
+ u_int32_t adj_size, max_end;
+ struct dos_partition *partp;
+
+ while (1) {
+ part_processed = 1;
+ if (command->n_args != 4) {
+ warnx("ERROR line %d: incorrect number of partition args",
+ current_line_number);
+ break;
+ }
+ partition = command->args[0].arg_val;
+ if (partition < 1 || partition > 4) {
+ warnx("ERROR line %d: invalid partition number %d",
+ current_line_number, partition);
+ break;
+ }
+ partp = &mboot.parts[partition - 1];
+ bzero(partp, sizeof (*partp));
+ partp->dp_typ = command->args[1].arg_val;
+ if (command->args[2].arg_str != NULL) {
+ if (strcmp(command->args[2].arg_str, "*") == 0) {
+ int i;
+ partp->dp_start = dos_sectors;
+ for (i = 1; i < partition; i++) {
+ struct dos_partition *prev_partp;
+ prev_partp = ((struct dos_partition *)
+ &mboot.parts) + i - 1;
+ if (prev_partp->dp_typ != 0)
+ partp->dp_start = prev_partp->dp_start +
+ prev_partp->dp_size;
+ }
+ if (partp->dp_start % dos_sectors != 0) {
+ prev_head_boundary = partp->dp_start /
+ dos_sectors * dos_sectors;
+ partp->dp_start = prev_head_boundary +
+ dos_sectors;
+ }
+ } else {
+ partp->dp_start = str2sectors(command->args[2].arg_str);
+ if (partp->dp_start == NO_DISK_SECTORS)
+ break;
+ }
+ } else
+ partp->dp_start = command->args[2].arg_val;
+
+ if (command->args[3].arg_str != NULL) {
+ if (strcmp(command->args[3].arg_str, "*") == 0)
+ partp->dp_size = ((disksecs / dos_cylsecs) *
+ dos_cylsecs) - partp->dp_start;
+ else {
+ partp->dp_size = str2sectors(command->args[3].arg_str);
+ if (partp->dp_size == NO_DISK_SECTORS)
+ break;
+ }
+ prev_cyl_boundary = ((partp->dp_start + partp->dp_size) /
+ dos_cylsecs) * dos_cylsecs;
+ if (prev_cyl_boundary > partp->dp_start)
+ partp->dp_size = prev_cyl_boundary - partp->dp_start;
+ } else
+ partp->dp_size = command->args[3].arg_val;
+
+ max_end = partp->dp_start + partp->dp_size;
+
+ if (partp->dp_typ == 0) {
+ /*
+ * Get out, the partition is marked as unused.
+ */
+ /*
+ * Insure that it's unused.
+ */
+ bzero(partp, sizeof(*partp));
+ status = 1;
+ break;
+ }
+
+ /*
+ * Adjust start upwards, if necessary, to fall on a head boundary.
+ */
+ if (partp->dp_start % dos_sectors != 0) {
+ prev_head_boundary = partp->dp_start / dos_sectors * dos_sectors;
+ if (max_end < dos_sectors ||
+ prev_head_boundary > max_end - dos_sectors) {
+ /*
+ * Can't go past end of partition
+ */
+ warnx(
+ "ERROR line %d: unable to adjust start of partition %d to fall on\n\
+ a head boundary",
+ current_line_number, partition);
+ break;
+ }
+ warnx(
+ "WARNING: adjusting start offset of partition %d\n\
+ from %u to %u, to fall on a head boundary",
+ partition, (u_int)partp->dp_start,
+ (u_int)(prev_head_boundary + dos_sectors));
+ partp->dp_start = prev_head_boundary + dos_sectors;
+ }
+
+ /*
+ * Adjust size downwards, if necessary, to fall on a cylinder
+ * boundary.
+ */
+ prev_cyl_boundary =
+ ((partp->dp_start + partp->dp_size) / dos_cylsecs) * dos_cylsecs;
+ if (prev_cyl_boundary > partp->dp_start)
+ adj_size = prev_cyl_boundary - partp->dp_start;
+ else {
+ warnx(
+ "ERROR: could not adjust partition to start on a head boundary\n\
+ and end on a cylinder boundary.");
+ return (0);
+ }
+ if (adj_size != partp->dp_size) {
+ warnx(
+ "WARNING: adjusting size of partition %d from %u to %u\n\
+ to end on a cylinder boundary",
+ partition, (u_int)partp->dp_size, (u_int)adj_size);
+ partp->dp_size = adj_size;
+ }
+ if (partp->dp_size == 0) {
+ warnx("ERROR line %d: size of partition %d is zero",
+ current_line_number, partition);
+ break;
+ }
+
+ dos(partp);
+ status = 1;
+ break;
+ }
+ return (status);
+}
+
+
+static int
+process_active(CMD *command)
+{
+ int status = 0, partition, i;
+ struct dos_partition *partp;
+
+ while (1) {
+ active_processed = 1;
+ if (command->n_args != 1) {
+ warnx("ERROR line %d: incorrect number of active args",
+ current_line_number);
+ status = 0;
+ break;
+ }
+ partition = command->args[0].arg_val;
+ if (partition < 1 || partition > 4) {
+ warnx("ERROR line %d: invalid partition number %d",
+ current_line_number, partition);
+ break;
+ }
+ /*
+ * Reset active partition
+ */
+ partp = mboot.parts;
+ for (i = 0; i < NDOSPART; i++)
+ partp[i].dp_flag = 0;
+ partp[partition-1].dp_flag = ACTIVE;
+
+ status = 1;
+ break;
+ }
+ return (status);
+}
+
+
+static int
+process_line(char *line)
+{
+ CMD command;
+ int status = 1;
+
+ while (1) {
+ parse_config_line(line, &command);
+ switch (command.cmd) {
+ case 0:
+ /*
+ * Comment or blank line
+ */
+ break;
+ case 'g':
+ /*
+ * Set geometry
+ */
+ status = process_geometry(&command);
+ break;
+ case 'p':
+ status = process_partition(&command);
+ break;
+ case 'a':
+ status = process_active(&command);
+ break;
+ default:
+ status = 0;
+ break;
+ }
+ break;
+ }
+ return (status);
+}
+
+
+static int
+read_config(char *config_file)
+{
+ FILE *fp = NULL;
+ int status = 1;
+ char buf[1010];
+
+ while (1) {
+ if (strcmp(config_file, "-") != 0) {
+ /*
+ * We're not reading from stdin
+ */
+ if ((fp = fopen(config_file, "r")) == NULL) {
+ status = 0;
+ break;
+ }
+ } else {
+ fp = stdin;
+ }
+ current_line_number = 0;
+ while (!feof(fp)) {
+ if (fgets(buf, sizeof(buf), fp) == NULL)
+ break;
+ ++current_line_number;
+ status = process_line(buf);
+ if (status == 0)
+ break;
+ }
+ break;
+ }
+ if (fp) {
+ /*
+ * It doesn't matter if we're reading from stdin, as we've reached EOF
+ */
+ fclose(fp);
+ }
+ return (status);
+}
+
+
+static void
+reset_boot(void)
+{
+ int i;
+ struct dos_partition *partp;
+
+ init_boot();
+ for (i = 0; i < 4; ++i) {
+ partp = &mboot.parts[i];
+ bzero(partp, sizeof(*partp));
+ }
+}
+
+static int
+sanitize_partition(struct dos_partition *partp)
+{
+ u_int32_t prev_head_boundary, prev_cyl_boundary;
+ u_int32_t max_end, size, start;
+
+ start = partp->dp_start;
+ size = partp->dp_size;
+ max_end = start + size;
+ /* Only allow a zero size if the partition is being marked unused. */
+ if (size == 0) {
+ if (start == 0 && partp->dp_typ == 0)
+ return (1);
+ warnx("ERROR: size of partition is zero");
+ return (0);
+ }
+ /* Return if no adjustment is necessary. */
+ if (start % dos_sectors == 0 && (start + size) % dos_sectors == 0)
+ return (1);
+
+ if (start == 0) {
+ warnx("WARNING: partition overlaps with partition table");
+ if (ok("Correct this automatically?"))
+ start = dos_sectors;
+ }
+ if (start % dos_sectors != 0)
+ warnx("WARNING: partition does not start on a head boundary");
+ if ((start +size) % dos_sectors != 0)
+ warnx("WARNING: partition does not end on a cylinder boundary");
+ warnx("WARNING: this may confuse the BIOS or some operating systems");
+ if (!ok("Correct this automatically?"))
+ return (1);
+
+ /*
+ * Adjust start upwards, if necessary, to fall on a head boundary.
+ */
+ if (start % dos_sectors != 0) {
+ prev_head_boundary = start / dos_sectors * dos_sectors;
+ if (max_end < dos_sectors ||
+ prev_head_boundary >= max_end - dos_sectors) {
+ /*
+ * Can't go past end of partition
+ */
+ warnx(
+ "ERROR: unable to adjust start of partition to fall on a head boundary");
+ return (0);
+ }
+ start = prev_head_boundary + dos_sectors;
+ }
+
+ /*
+ * Adjust size downwards, if necessary, to fall on a cylinder
+ * boundary.
+ */
+ prev_cyl_boundary = ((start + size) / dos_cylsecs) * dos_cylsecs;
+ if (prev_cyl_boundary > start)
+ size = prev_cyl_boundary - start;
+ else {
+ warnx("ERROR: could not adjust partition to start on a head boundary\n\
+ and end on a cylinder boundary.");
+ return (0);
+ }
+
+ /* Finally, commit any changes to partp and return. */
+ if (start != partp->dp_start) {
+ warnx("WARNING: adjusting start offset of partition to %u",
+ (u_int)start);
+ partp->dp_start = start;
+ }
+ if (size != partp->dp_size) {
+ warnx("WARNING: adjusting size of partition to %u", (u_int)size);
+ partp->dp_size = size;
+ }
+
+ return (1);
+}
+
+/*
+ * Try figuring out the root device's canonical disk name.
+ * The following choices are considered:
+ * /dev/ad0s1a => /dev/ad0
+ * /dev/da0a => /dev/da0
+ * /dev/vinum/root => /dev/vinum/root
+ * A ".eli" part is removed if it exists (see geli(8)).
+ * A ".journal" ending is removed if it exists (see gjournal(8)).
+ */
+static char *
+get_rootdisk(void)
+{
+ struct statfs rootfs;
+ regex_t re;
+#define NMATCHES 2
+ regmatch_t rm[NMATCHES];
+ char dev[PATH_MAX], *s;
+ int rv;
+
+ if (statfs("/", &rootfs) == -1)
+ err(1, "statfs(\"/\")");
+
+ if ((rv = regcomp(&re, "^(/dev/[a-z/]+[0-9]*)([sp][0-9]+)?[a-h]?(\\.journal)?$",
+ REG_EXTENDED)) != 0)
+ errx(1, "regcomp() failed (%d)", rv);
+ strlcpy(dev, rootfs.f_mntfromname, sizeof (dev));
+ if ((s = strstr(dev, ".eli")) != NULL)
+ memmove(s, s+4, strlen(s + 4) + 1);
+
+ if ((rv = regexec(&re, dev, NMATCHES, rm, 0)) != 0)
+ errx(1,
+"mounted root fs resource doesn't match expectations (regexec returned %d)",
+ rv);
+ if ((s = malloc(rm[1].rm_eo - rm[1].rm_so + 1)) == NULL)
+ errx(1, "out of memory");
+ memcpy(s, rootfs.f_mntfromname + rm[1].rm_so,
+ rm[1].rm_eo - rm[1].rm_so);
+ s[rm[1].rm_eo - rm[1].rm_so] = 0;
+
+ return s;
+}
diff --git a/sbin/fdisk/runtest.sh b/sbin/fdisk/runtest.sh
new file mode 100644
index 0000000..f25f427
--- /dev/null
+++ b/sbin/fdisk/runtest.sh
@@ -0,0 +1,30 @@
+#!/bin/sh
+# $FreeBSD$
+
+set -e
+MD=`mdconfig -a -t malloc -s 4m -x 63 -y 16`
+if [ ! -c /dev/${MD} ] ; then
+ echo "MD device $MD did not materialize" 1>&2
+ exit 2
+fi
+trap "mdconfig -d -u ${MD}" EXIT INT TERM
+
+# Create an empty bootcode file to isolate our checksum from any changes
+# which might happen to the boot code file.
+dd if=/dev/zero of=tmp count=1 > /dev/null 2>&1
+./fdisk -b tmp -I $MD > /dev/null 2>&1
+rm tmp
+
+c=`dd if=/dev/${MD} count=1 2>/dev/null | md5`
+if [ $c != ea4277fcccb6a927a1a497a6b15bfb8c ] ; then
+ echo "FAILED: 'fdisk -I' gives bad checksum ($c)" 1>&2
+ exit 1
+fi
+echo "PASSED: fdisk -I"
+c=`./fdisk $MD | md5`
+if [ $c != 4b126d7ac4c6b2af7ef27ede8ef102ec ] ; then
+ echo "FAILED: 'fdisk' gives bad checksum ($c)" 1>&2
+ exit 1
+fi
+echo "PASSED: fdisk"
+exit 0
diff --git a/sbin/fdisk_pc98/Makefile b/sbin/fdisk_pc98/Makefile
new file mode 100644
index 0000000..9915267
--- /dev/null
+++ b/sbin/fdisk_pc98/Makefile
@@ -0,0 +1,12 @@
+# $FreeBSD$
+
+PROG= fdisk
+SRCS= fdisk.c geom_pc98_enc.c
+WARNS?= 4
+MAN= fdisk.8
+
+.PATH: ${.CURDIR}/../../sys/geom
+
+LIBADD= geom
+
+.include <bsd.prog.mk>
diff --git a/sbin/fdisk_pc98/fdisk.8 b/sbin/fdisk_pc98/fdisk.8
new file mode 100644
index 0000000..aa3a6c4
--- /dev/null
+++ b/sbin/fdisk_pc98/fdisk.8
@@ -0,0 +1,471 @@
+.\" $FreeBSD$
+.\"
+.Dd April 30, 2007
+.Dt FDISK 8
+.Os
+.Sh NAME
+.Nm fdisk
+.Nd NEC PC-98x1 slice table maintenance utility
+.Sh SYNOPSIS
+.Nm
+.Op Fl BIaistuv
+.Op Fl 12345678
+.Op Ar disk
+.Nm
+.Fl f Ar configfile
+.Op Fl itv
+.Op Ar disk
+.Sh PROLOGUE
+In order for the BIOS to boot the kernel,
+certain conventions must be adhered to.
+Sector 0 of the disk must contain boot code,
+a slice table,
+and a magic number.
+BIOS slices can be used to break the disk up into several pieces.
+The BIOS brings in sector 0 and verifies the magic number.
+The sector
+0 boot code then searches the slice table to determine which
+slice is marked
+.Dq active .
+This boot code then brings in the bootstrap from the
+active slice and, if marked bootable, runs it.
+Under
+.Tn DOS ,
+you can have one or more slices with one active.
+The
+.Tn DOS
+.Nm
+utility can be used to divide space on the disk into slices and set one
+active.
+.Sh DESCRIPTION
+The
+.Fx
+utility,
+.Nm ,
+serves a similar purpose to the
+.Tn DOS
+utility.
+The first form is used to
+display slice information or to interactively edit the slice
+table.
+The second is used to write a slice table using a
+.Ar configfile ,
+and is designed to be used by other scripts/programs.
+.Pp
+Options are:
+.Bl -tag -width indent
+.It Fl a
+Change the active slice only.
+Ignored if
+.Fl f
+is given.
+.It Fl B
+Reinitialize the boot code contained in sector 0 of the disk.
+Ignored if
+.Fl f
+is given.
+.It Fl f Ar configfile
+Set slice values using the file
+.Ar configfile .
+The
+.Ar configfile
+always modifies existing slices, unless
+.Fl i
+is also given, in which case all existing slices are deleted (marked
+as
+.Dq unused )
+before the
+.Ar configfile
+is read.
+The
+.Ar configfile
+can be
+.Sq Fl ,
+in which case standard input is read.
+See
+.Sx CONFIGURATION FILE ,
+below, for file syntax.
+.Pp
+.Em WARNING :
+when
+.Fl f
+is used, you are not asked if you really want to write the slices
+table (as you are in the interactive mode).
+Use with caution!
+.It Fl i
+Initialize sector 0 of the disk.
+This implies
+.Fl u ,
+unless
+.Fl f
+is given.
+.It Fl I
+Initialize sector 0 slice table
+for one
+.Fx
+slice covering the entire disk.
+Some space at the start of the disk will reserved for the IPL program
+and the pc98 slice table itself.
+.It Fl s
+Print summary information and exit.
+.It Fl t
+Test mode; do not write slice values.
+Generally used with the
+.Fl f
+option to see what would be written to the slice table.
+Implies
+.Fl v .
+.It Fl u
+Update (edit) the disk's sector 0 slice table.
+Ignored if
+.Fl f
+is given.
+.It Fl v
+Be verbose.
+Slices that are unused are suppressed unless this flag is specified.
+When
+.Fl f
+is used,
+.Nm
+prints out the slice table that is written to the disk.
+.It Fl 12345678
+Operate on a single slice table entry only.
+Ignored if
+.Fl f
+is given.
+.El
+.Pp
+The final disk name can be provided as a
+.Dq bare
+disk name only, e.g.\&
+.Pa da0 ,
+or as a full pathname.
+If omitted,
+.Nm
+tries to figure out the default disk device name from the
+mounted root device.
+.Pp
+When called with no arguments, it prints the sector 0 slice table.
+An example follows:
+.Bd -literal
+ ******* Working on device /dev/da0 *******
+ parameters extracted from in-core disklabel are:
+ cylinders=33075 heads=8 sectors/track=32 (256 blks/cyl)
+
+ parameters to be used for BIOS calculations are:
+ cylinders=33075 heads=8 sectors/track=32 (256 blks/cyl)
+
+ Media sector size is 512
+ Warning: BIOS sector numbering starts with sector 1
+ Information from DOS bootblock is:
+ The data for partition 1 is:
+ sysmid 148,(FreeBSD/NetBSD/386BSD)
+ start 256, size 2490112 (1215 Meg), sid 196
+ beg: cyl 1/ sector 0/ head 0;
+ end: cyl 9727/ sector 0/ head 0
+ system Name FreeBSD(98)
+ The data for partition 2 is:
+ sysmid 148,(FreeBSD/NetBSD/386BSD)
+ start 2490368, size 5505024 (2688 Meg), sid 196
+ beg: cyl 9728/ sector 0/ head 0;
+ end: cyl 31231/ sector 0/ head 0
+ system Name FreeBSD(98)
+ The data for partition 3 is:
+ <UNUSED>
+ The data for partition 4 is:
+ <UNUSED>
+ The data for partition 5 is:
+ <UNUSED>
+ The data for partition 6 is:
+ <UNUSED>
+ The data for partition 7 is:
+ <UNUSED>
+ The data for partition 8 is:
+ <UNUSED>
+ The data for partition 9 is:
+ <UNUSED>
+ The data for partition 10 is:
+ <UNUSED>
+ The data for partition 11 is:
+ <UNUSED>
+ The data for partition 12 is:
+ <UNUSED>
+ The data for partition 13 is:
+ <UNUSED>
+ The data for partition 14 is:
+ <UNUSED>
+ The data for partition 15 is:
+ <UNUSED>
+ The data for partition 16 is:
+ <UNUSED>
+.Ed
+.Pp
+The disk is divided into three slices that happen to fill the disk.
+The second slice overlaps the end of the first.
+(Used for debugging purposes.)
+.Bl -tag -width ".Em cyl , sector No and Em head"
+.It Em sysmid
+is used to label the slice.
+.Fx
+reserves the
+magic number 148 decimal (94 in hex).
+.It Xo
+.Em start
+and
+.Em size
+.Xc
+fields provide the start address
+and size of a slice in sectors.
+.It Xo
+.Em cyl , sector
+and
+.Em head
+.Xc
+fields are used to specify the beginning and end addresses of the slice.
+.It Em "system Name"
+is the name of the slice.
+.El
+.Pp
+.Em Note :
+these numbers are calculated using BIOS's understanding of the disk geometry
+and saved in the bootblock.
+.Pp
+The
+.Fl i
+and
+.Fl u
+flags are used to indicate that the slice data is to be updated.
+Unless the
+.Fl f
+option is also given,
+.Nm
+will enter a conversational mode.
+In this mode, no changes will be written to disk unless you explicitly tell
+.Nm
+to.
+.Pp
+The
+.Nm
+utility will display each slice and ask whether you want to edit it.
+If you say yes,
+.Nm
+will step through each field, show you the old value,
+and ask you for a new one.
+When you are done with the slice,
+.Nm
+will display it and ask you whether it is correct.
+It will then proceed to the next entry.
+.Pp
+Getting the
+.Em cyl , sector ,
+and
+.Em head
+fields correct is tricky, so by default,
+they will be calculated for you;
+you can specify them if you choose to though.
+.Pp
+After all the slices are processed,
+you are given the option to change the
+.Dq active
+slice.
+Finally, when all the new data for sector 0 has been accumulated,
+you are asked to confirm whether you really want to rewrite it.
+.Pp
+The difference between the
+.Fl u
+and
+.Fl i
+flags is that
+the
+.Fl u
+flag edits (updates) the existing slice parameters
+while the
+.Fl i
+flag is used to
+.Dq initialize
+them (old values will be ignored);
+it will setup the last BIOS slice to use the whole disk for
+.Fx ;
+and make it active.
+.Sh NOTES
+The automatic calculation of starting cylinder etc.\& uses
+a set of figures that represent what the BIOS thinks the
+geometry of the drive is.
+These figures are taken from the in-core disklabel by default,
+but
+.Nm
+initially gives you an opportunity to change them.
+This allows you to create a bootblock that can work with drives
+that use geometry translation under the BIOS.
+.Pp
+If you hand craft your disk layout,
+please make sure that the
+.Fx
+slice starts on a cylinder boundary.
+.Pp
+Editing an existing slice will most likely result in the loss of
+all data in that slice.
+.Pp
+You should run
+.Nm
+interactively once or twice to see how it works.
+This is completely safe as long as you answer the last question
+in the negative.
+There are subtleties that
+.Nm
+detects that are not fully explained in this manual page.
+.Sh CONFIGURATION FILE
+When the
+.Fl f
+option is given, a disk's slice table can be written using values
+from a
+.Ar configfile .
+The syntax of this file is very simple;
+each line is either a comment or a specification, as follows:
+.Bl -tag -width indent
+.It Ic # Ar comment ...
+Lines beginning with a
+.Ic #
+are comments and are ignored.
+.It Ic g Ar spec1 spec2 spec3
+Set the BIOS geometry used in slice calculations.
+There must be
+three values specified, with a letter preceding each number:
+.Bl -tag -width indent
+.It Cm c Ns Ar num
+Set the number of cylinders to
+.Ar num .
+.It Cm h Ns Ar num
+Set the number of heads to
+.Ar num .
+.It Cm s Ns Ar num
+Set the number of sectors/track to
+.Ar num .
+.El
+.Pp
+These specs can occur in any order, as the leading letter determines
+which value is which; however, all three must be specified.
+.Pp
+This line must occur before any lines that specify slice
+information.
+.Pp
+It is an error if the following is not true:
+.Bd -literal -offset indent
+1 <= number of cylinders
+1 <= number of heads <= 256
+1 <= number of sectors/track < 64
+.Ed
+.Pp
+The number of cylinders should be less than or equal to 1024, but this
+is not enforced, although a warning will be printed.
+Note that bootable
+.Fx
+slices (the
+.Dq Pa /
+file system) must lie completely within the
+first 1024 cylinders; if this is not true, booting may fail.
+Non-bootable slices do not have this restriction.
+.Pp
+Example (all of these are equivalent), for a disk with 1019 cylinders,
+39 heads, and 63 sectors:
+.Bd -literal -offset indent
+g c1019 h39 s63
+g h39 c1019 s63
+g s63 h39 c1019
+.Ed
+.It Ic p Ar slice type start length
+Set the slice given by
+.Ar slice
+(1-8) to type
+.Ar type ,
+starting at sector
+.Ar start
+for
+.Ar length
+sectors.
+.Pp
+Only those slices explicitly mentioned by these lines are modified;
+any slice not referenced by a
+.Ic p
+line will not be modified.
+However, if an invalid slice table is present, or the
+.Fl i
+option is specified, all existing slice entries will be cleared
+(marked as unused), and these
+.Ic p
+lines will have to be used to
+explicitly set slice information.
+If multiple slices need to be
+set, multiple
+.Ic p
+lines must be specified; one for each slice.
+.Pp
+These slice lines must occur after any geometry specification lines,
+if one is present.
+.Pp
+The
+.Ar type
+is 165 for
+.Fx
+slices.
+Specifying a slice type of zero is
+the same as clearing the slice and marking it as unused; however,
+dummy values (such as
+.Dq 0 )
+must still be specified for
+.Ar start
+and
+.Ar length .
+.Pp
+Note: the start offset will be rounded upwards to a head boundary if
+necessary, and the end offset will be rounded downwards to a cylinder
+boundary if necessary.
+.Pp
+Example: to clear slice 4 and mark it as unused:
+.Pp
+.Dl "p 4 0 0 0"
+.Pp
+Example: to set slice 1 to a
+.Fx
+slice, starting at sector 1
+for 2503871 sectors (note: these numbers will be rounded upwards and
+downwards to correspond to head and cylinder boundaries):
+.Pp
+.Dl "p 1 165 1 2503871"
+.It Ic a Ar slice
+Make
+.Ar slice
+the active slice.
+Can occur anywhere in the config file, but only
+one must be present.
+.Pp
+Example: to make slice 1 the active slice:
+.Pp
+.Dl "a 1"
+.El
+.Sh SEE ALSO
+.Xr boot98cfg 8 ,
+.Xr bsdlabel 8 ,
+.Xr gpart 8 ,
+.Xr newfs 8
+.Sh BUGS
+The default boot code will not necessarily handle all slice types
+correctly, in particular those introduced since
+.Tn MS-DOS
+6.x.
+.Pp
+The entire utility should be made more user-friendly.
+.Pp
+Most users new to
+.Fx
+do not understand the difference between
+.Dq slice
+and
+.Dq partition ,
+causing difficulty to adjust.
+.Pp
+You cannot use this command to completely dedicate a disk to
+.Fx .
+The
+.Xr bsdlabel 8
+command must be used for this.
diff --git a/sbin/fdisk_pc98/fdisk.c b/sbin/fdisk_pc98/fdisk.c
new file mode 100644
index 0000000..479e1e4
--- /dev/null
+++ b/sbin/fdisk_pc98/fdisk.c
@@ -0,0 +1,918 @@
+/*
+ * Mach Operating System
+ * Copyright (c) 1992 Carnegie Mellon University
+ * All Rights Reserved.
+ *
+ * Permission to use, copy, modify and distribute this software and its
+ * documentation is hereby granted, provided that both the copyright
+ * notice and this permission notice appear in all copies of the
+ * software, derivative works or modified versions, and any portions
+ * thereof, and that both notices appear in supporting documentation.
+ *
+ * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS"
+ * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR
+ * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
+ *
+ * Carnegie Mellon requests users of this software to return to
+ *
+ * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU
+ * School of Computer Science
+ * Carnegie Mellon University
+ * Pittsburgh PA 15213-3890
+ *
+ * any improvements or extensions that they make and grant Carnegie Mellon
+ * the rights to redistribute these changes.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/disk.h>
+#include <sys/disklabel.h>
+#include <sys/diskpc98.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/mount.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <err.h>
+#include <errno.h>
+#include <libgeom.h>
+#include <paths.h>
+#include <regex.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+int iotest;
+
+#define LBUF 100
+static char lbuf[LBUF];
+
+/*
+ *
+ * Ported to 386bsd by Julian Elischer Thu Oct 15 20:26:46 PDT 1992
+ *
+ * 14-Dec-89 Robert Baron (rvb) at Carnegie-Mellon University
+ * Copyright (c) 1989 Robert. V. Baron
+ * Created.
+ */
+
+#define Decimal(str, ans, tmp) if (decimal(str, &tmp, ans)) ans = tmp
+#define String(str, ans, len) {char *z = ans; char **dflt = &z; if (string(str, dflt)) strncpy(ans, *dflt, len); }
+
+#define RoundCyl(x) ((((x) + cylsecs - 1) / cylsecs) * cylsecs)
+
+#define MAX_SEC_SIZE 2048 /* maximum section size that is supported */
+#define MIN_SEC_SIZE 512 /* the sector size to start sensing at */
+static int secsize = 0; /* the sensed sector size */
+
+static char *disk;
+
+static int cyls, sectors, heads, cylsecs, disksecs;
+
+struct mboot {
+ unsigned char padding[2]; /* force the longs to be long aligned */
+ unsigned char bootinst[510];
+ unsigned short int signature;
+ struct pc98_partition parts[8];
+ unsigned char large_sector_overflow[MAX_SEC_SIZE-MIN_SEC_SIZE];
+};
+
+static struct mboot mboot;
+static int fd;
+
+static uint dos_cyls;
+static uint dos_heads;
+static uint dos_sectors;
+static uint dos_cylsecs;
+
+#define MAX_ARGS 10
+
+typedef struct cmd {
+ char cmd;
+ int n_args;
+ struct arg {
+ char argtype;
+ int arg_val;
+ } args[MAX_ARGS];
+} CMD;
+
+static int B_flag = 0; /* replace boot code */
+static int I_flag = 0; /* Inizialize disk to defaults */
+static int a_flag = 0; /* set active partition */
+static int i_flag = 0; /* replace partition data */
+static int u_flag = 0; /* update partition data */
+static int s_flag = 0; /* Print a summary and exit */
+static int t_flag = 0; /* test only */
+static char *f_flag = NULL; /* Read config info from file */
+static int v_flag = 0; /* Be verbose */
+
+static struct part_type
+{
+ unsigned char type;
+ const char *name;
+} part_types[] = {
+ {0x00, "unused"}
+ ,{0x01, "Primary DOS with 12 bit FAT"}
+ ,{0x11, "MSDOS"}
+ ,{0x20, "MSDOS"}
+ ,{0x21, "MSDOS"}
+ ,{0x22, "MSDOS"}
+ ,{0x23, "MSDOS"}
+ ,{0x02, "XENIX / file system"}
+ ,{0x03, "XENIX /usr file system"}
+ ,{0x04, "PC-UX"}
+ ,{0x05, "Extended DOS"}
+ ,{0x06, "Primary 'big' DOS (> 32MB)"}
+ ,{0x07, "OS/2 HPFS, QNX or Advanced UNIX"}
+ ,{0x08, "AIX file system"}
+ ,{0x09, "AIX boot partition or Coherent"}
+ ,{0x0A, "OS/2 Boot Manager or OPUS"}
+ ,{0x10, "OPUS"}
+ ,{0x14, "FreeBSD/NetBSD/386BSD"}
+ ,{0x94, "FreeBSD/NetBSD/386BSD"}
+ ,{0x40, "VENIX 286"}
+ ,{0x50, "DM"}
+ ,{0x51, "DM"}
+ ,{0x52, "CP/M or Microport SysV/AT"}
+ ,{0x56, "GB"}
+ ,{0x61, "Speed"}
+ ,{0x63, "ISC UNIX, other System V/386, GNU HURD or Mach"}
+ ,{0x64, "Novell Netware 2.xx"}
+ ,{0x65, "Novell Netware 3.xx"}
+ ,{0x75, "PCIX"}
+ ,{0x40, "Minix"}
+};
+
+static void print_s0(int which);
+static void print_part(int i);
+static void init_sector0(unsigned long start);
+static void init_boot(void);
+static void change_part(int i, int force);
+static void print_params(void);
+static void change_active(int which);
+static void change_code(void);
+static void get_params_to_use(void);
+static char *get_rootdisk(void);
+static void dos(u_int32_t start, u_int32_t size, struct pc98_partition *partp);
+static int open_disk(int flag);
+static ssize_t read_disk(off_t sector, void *buf);
+static int write_disk(off_t sector, void *buf);
+static int get_params(void);
+static int read_s0(void);
+static int write_s0(void);
+static int ok(const char *str);
+static int decimal(const char *str, int *num, int deflt);
+static const char *get_type(int type);
+static void usage(void);
+static int string(const char *str, char **ans);
+static void reset_boot(void);
+
+int
+main(int argc, char *argv[])
+{
+ struct stat sb;
+ int c, i;
+ int partition = -1;
+ struct pc98_partition *partp;
+
+ while ((c = getopt(argc, argv, "BIa:f:istuv12345678")) != -1)
+ switch (c) {
+ case 'B':
+ B_flag = 1;
+ break;
+ case 'I':
+ I_flag = 1;
+ break;
+ case 'a':
+ a_flag = 1;
+ break;
+ case 'f':
+ f_flag = optarg;
+ break;
+ case 'i':
+ i_flag = 1;
+ break;
+ case 's':
+ s_flag = 1;
+ break;
+ case 't':
+ t_flag = 1;
+ break;
+ case 'u':
+ u_flag = 1;
+ break;
+ case 'v':
+ v_flag = 1;
+ break;
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ partition = c - '0';
+ break;
+ default:
+ usage();
+ }
+ if (f_flag || i_flag)
+ u_flag = 1;
+ if (t_flag)
+ v_flag = 1;
+ argc -= optind;
+ argv += optind;
+
+ if (argc == 0) {
+ disk = get_rootdisk();
+ } else {
+ if (stat(argv[0], &sb) == 0) {
+ /* OK, full pathname given */
+ disk = argv[0];
+ } else if (errno == ENOENT && argv[0][0] != '/') {
+ /* Try prepending "/dev" */
+ asprintf(&disk, "%s%s", _PATH_DEV, argv[0]);
+ if (disk == NULL)
+ errx(1, "out of memory");
+ } else {
+ /* other stat error, let it fail below */
+ disk = argv[0];
+ }
+ }
+ if (open_disk(u_flag) < 0)
+ err(1, "cannot open disk %s", disk);
+
+ if (s_flag) {
+ if (read_s0())
+ err(1, "read_s0");
+ printf("%s: %d cyl %d hd %d sec\n", disk, dos_cyls, dos_heads,
+ dos_sectors);
+ printf("Part %11s %11s %4s %4s %-16s\n", "Start", "Size", "MID",
+ "SID", "Name");
+ for (i = 0; i < PC98_NPARTS; i++) {
+ partp = ((struct pc98_partition *) &mboot.parts) + i;
+ if (partp->dp_sid == 0)
+ continue;
+ printf("%4d: %11u %11u 0x%02x 0x%02x %-16.16s\n", i + 1,
+ partp->dp_scyl * cylsecs,
+ (partp->dp_ecyl - partp->dp_scyl + 1) * cylsecs,
+ partp->dp_mid, partp->dp_sid, partp->dp_name);
+ }
+ exit(0);
+ }
+
+ printf("******* Working on device %s *******\n",disk);
+
+ if (I_flag) {
+ read_s0();
+ reset_boot();
+ partp = (struct pc98_partition *) (&mboot.parts[0]);
+ partp->dp_mid = DOSMID_386BSD;
+ partp->dp_sid = DOSSID_386BSD;
+ strncpy(partp->dp_name, "FreeBSD", sizeof(partp->dp_name));
+ /* Start c/h/s. */
+ partp->dp_scyl = partp->dp_ipl_cyl = 1;
+ partp->dp_shd = partp->dp_ipl_head = 1;
+ partp->dp_ssect = partp->dp_ipl_sct = 0;
+
+ /* End c/h/s. */
+ partp->dp_ecyl = dos_cyls - 1;
+ partp->dp_ehd = dos_cylsecs / dos_sectors;
+ partp->dp_esect = dos_sectors;
+
+ if (v_flag)
+ print_s0(-1);
+ if (!t_flag)
+ write_s0();
+ exit(0);
+ }
+
+ if (f_flag) {
+ if (v_flag)
+ print_s0(-1);
+ if (!t_flag)
+ write_s0();
+ } else {
+ if(u_flag)
+ get_params_to_use();
+ else
+ print_params();
+
+ if (read_s0())
+ init_sector0(dos_sectors);
+
+ printf("Media sector size is %d\n", secsize);
+ printf("Warning: BIOS sector numbering starts with sector 1\n");
+ printf("Information from DOS bootblock is:\n");
+ if (partition == -1)
+ for (i = 1; i <= PC98_NPARTS; i++)
+ change_part(i, v_flag);
+ else
+ change_part(partition, 1);
+
+ if (u_flag || a_flag)
+ change_active(partition);
+
+ if (B_flag)
+ change_code();
+
+ if (u_flag || a_flag || B_flag) {
+ if (!t_flag) {
+ printf("\nWe haven't changed the partition table yet. ");
+ printf("This is your last chance.\n");
+ }
+ print_s0(-1);
+ if (!t_flag) {
+ if (ok("Should we write new partition table?"))
+ write_s0();
+ } else {
+ printf("\n-t flag specified -- partition table not written.\n");
+ }
+ }
+ }
+
+ exit(0);
+}
+
+static void
+usage()
+{
+ fprintf(stderr, "%s%s",
+ "usage: fdisk [-BIaistu] [-12345678] [disk]\n",
+ " fdisk -f configfile [-itv] [disk]\n");
+ exit(1);
+}
+
+static struct pc98_partition mtpart;
+
+static int
+part_unused(int i)
+{
+ struct pc98_partition *partp;
+
+ partp = ((struct pc98_partition *) &mboot.parts) + i - 1;
+ return (bcmp(partp, &mtpart, sizeof (struct pc98_partition)) == 0);
+}
+
+static void
+print_s0(int which)
+{
+ int i;
+
+ print_params();
+ printf("Information from DOS bootblock is:\n");
+ if (which == -1) {
+ for (i = 1; i <= PC98_NPARTS; i++)
+ if (v_flag || !part_unused(i)) {
+ printf("%d: ", i);
+ print_part(i);
+ }
+ }
+ else
+ print_part(which);
+}
+
+static void
+print_part(int i)
+{
+ struct pc98_partition *partp;
+ u_int64_t part_sz, part_mb;
+
+ if (part_unused(i)) {
+ printf("<UNUSED>\n");
+ return;
+ }
+ /*
+ * Be careful not to overflow.
+ */
+ partp = ((struct pc98_partition *) &mboot.parts) + i - 1;
+ part_sz = (partp->dp_ecyl - partp->dp_scyl + 1) * cylsecs;
+ part_mb = part_sz * secsize;
+ part_mb /= (1024 * 1024);
+ printf("sysmid %d (%#04x),(%s)\n", partp->dp_mid, partp->dp_mid,
+ get_type(partp->dp_mid));
+ printf(" start %lu, size %lu (%ju Meg), sid %d\n",
+ (u_long)(partp->dp_scyl * cylsecs), (u_long)part_sz,
+ (uintmax_t)part_mb, partp->dp_sid);
+ printf("\tbeg: cyl %d/ head %d/ sector %d;\n\tend: cyl %d/ head %d/ sector %d\n"
+ ,partp->dp_scyl
+ ,partp->dp_shd
+ ,partp->dp_ssect
+ ,partp->dp_ecyl
+ ,partp->dp_ehd
+ ,partp->dp_esect);
+ printf ("\tsystem Name %.16s\n", partp->dp_name);
+}
+
+
+static void
+init_boot(void)
+{
+
+ mboot.signature = PC98_MAGIC;
+}
+
+
+static void
+init_sector0(unsigned long start)
+{
+ struct pc98_partition *partp =
+ (struct pc98_partition *)(&mboot.parts[0]);
+
+ init_boot();
+
+ partp->dp_mid = DOSMID_386BSD;
+ partp->dp_sid = DOSSID_386BSD;
+
+ dos(start, disksecs - start, partp);
+}
+
+static void
+change_part(int i, int force)
+{
+ struct pc98_partition *partp =
+ ((struct pc98_partition *) &mboot.parts) + i - 1;
+
+ if (!force && part_unused(i))
+ return;
+
+ printf("The data for partition %d is:\n", i);
+ print_part(i);
+
+ if (u_flag && ok("Do you want to change it?")) {
+ int tmp;
+
+ if (i_flag) {
+ bzero((char *)partp, sizeof (struct pc98_partition));
+ if (i == 1) {
+ init_sector0(1);
+ printf("\nThe static data for the slice 1 has been reinitialized to:\n");
+ print_part(i);
+ }
+ }
+ do {
+ int x_start = partp->dp_scyl * cylsecs ;
+ int x_size = (partp->dp_ecyl - partp->dp_scyl + 1) * cylsecs;
+ Decimal("sysmid", partp->dp_mid, tmp);
+ Decimal("syssid", partp->dp_sid, tmp);
+ String ("system name", partp->dp_name, 16);
+ Decimal("start", x_start, tmp);
+ Decimal("size", x_size, tmp);
+
+ if (ok("Explicitly specify beg/end address ?"))
+ {
+ int tsec,tcyl,thd;
+ tcyl = partp->dp_scyl;
+ thd = partp->dp_shd;
+ tsec = partp->dp_ssect;
+ Decimal("beginning cylinder", tcyl, tmp);
+ Decimal("beginning head", thd, tmp);
+ Decimal("beginning sector", tsec, tmp);
+ partp->dp_scyl = tcyl;
+ partp->dp_ssect = tsec;
+ partp->dp_shd = thd;
+ partp->dp_ipl_cyl = partp->dp_scyl;
+ partp->dp_ipl_sct = partp->dp_ssect;
+ partp->dp_ipl_head = partp->dp_shd;
+
+ tcyl = partp->dp_ecyl;
+ thd = partp->dp_ehd;
+ tsec = partp->dp_esect;
+ Decimal("ending cylinder", tcyl, tmp);
+ Decimal("ending head", thd, tmp);
+ Decimal("ending sector", tsec, tmp);
+ partp->dp_ecyl = tcyl;
+ partp->dp_esect = tsec;
+ partp->dp_ehd = thd;
+ } else
+ dos(x_start, x_size, partp);
+
+ print_part(i);
+ } while (!ok("Are we happy with this entry?"));
+ }
+}
+
+static void
+print_params()
+{
+ printf("parameters extracted from in-core disklabel are:\n");
+ printf("cylinders=%d heads=%d sectors/track=%d (%d blks/cyl)\n\n"
+ ,cyls,heads,sectors,cylsecs);
+ if (dos_cyls > 65535 || dos_heads > 255 || dos_sectors > 255)
+ printf("Figures below won't work with BIOS for partitions not in cyl 1\n");
+ printf("parameters to be used for BIOS calculations are:\n");
+ printf("cylinders=%d heads=%d sectors/track=%d (%d blks/cyl)\n\n"
+ ,dos_cyls,dos_heads,dos_sectors,dos_cylsecs);
+}
+
+static void
+change_active(int which)
+{
+ struct pc98_partition *partp = &mboot.parts[0];
+ int active, i, new, tmp;
+
+ active = -1;
+ for (i = 0; i < PC98_NPARTS; i++) {
+ if ((partp[i].dp_sid & PC98_SID_ACTIVE) == 0)
+ continue;
+ printf("Partition %d is marked active\n", i + 1);
+ if (active == -1)
+ active = i + 1;
+ }
+ if (a_flag && which != -1)
+ active = which;
+ else if (active == -1)
+ active = 1;
+
+ if (!ok("Do you want to change the active partition?"))
+ return;
+setactive:
+ do {
+ new = active;
+ Decimal("active partition", new, tmp);
+ if (new < 1 || new > 8) {
+ printf("Active partition number must be in range 1-8."
+ " Try again.\n");
+ goto setactive;
+ }
+ active = new;
+ } while (!ok("Are you happy with this choice"));
+ if (active > 0 && active <= 8)
+ partp[active-1].dp_sid |= PC98_SID_ACTIVE;
+}
+
+static void
+change_code()
+{
+ if (ok("Do you want to change the boot code?"))
+ init_boot();
+}
+
+void
+get_params_to_use()
+{
+ int tmp;
+ print_params();
+ if (ok("Do you want to change our idea of what BIOS thinks ?"))
+ {
+ do
+ {
+ Decimal("BIOS's idea of #cylinders", dos_cyls, tmp);
+ Decimal("BIOS's idea of #heads", dos_heads, tmp);
+ Decimal("BIOS's idea of #sectors", dos_sectors, tmp);
+ dos_cylsecs = dos_heads * dos_sectors;
+ print_params();
+ }
+ while(!ok("Are you happy with this choice"));
+ }
+}
+
+
+/***********************************************\
+* Change real numbers into strange dos numbers *
+\***********************************************/
+static void
+dos(u_int32_t start, u_int32_t size, struct pc98_partition *partp)
+{
+ u_int32_t end;
+
+ if (partp->dp_mid == 0 && partp->dp_sid == 0 &&
+ start == 0 && size == 0) {
+ memcpy(partp, &mtpart, sizeof(*partp));
+ return;
+ }
+
+ /* Start c/h/s. */
+ partp->dp_scyl = partp->dp_ipl_cyl = start / dos_cylsecs;
+ partp->dp_shd = partp->dp_ipl_head = start % dos_cylsecs / dos_sectors;
+ partp->dp_ssect = partp->dp_ipl_sct = start % dos_sectors;
+
+ /* End c/h/s. */
+ end = start + size - cylsecs;
+ partp->dp_ecyl = end / dos_cylsecs;
+ partp->dp_ehd = end % dos_cylsecs / dos_sectors;
+ partp->dp_esect = end % dos_sectors;
+}
+
+static int
+open_disk(int flag)
+{
+ struct stat st;
+ int rwmode;
+
+ if (stat(disk, &st) == -1) {
+ if (errno == ENOENT)
+ return -2;
+ warnx("can't get file status of %s", disk);
+ return -1;
+ }
+ if ( !(st.st_mode & S_IFCHR) )
+ warnx("device %s is not character special", disk);
+ rwmode = I_flag || a_flag || B_flag || flag ? O_RDWR : O_RDONLY;
+ fd = open(disk, rwmode);
+ if (fd == -1 && errno == EPERM && rwmode == O_RDWR)
+ fd = open(disk, O_RDONLY);
+ if (fd == -1 && errno == ENXIO)
+ return -2;
+ if (fd == -1) {
+ warnx("can't open device %s", disk);
+ return -1;
+ }
+ if (get_params() == -1) {
+ warnx("can't get disk parameters on %s", disk);
+ return -1;
+ }
+ return fd;
+}
+
+static ssize_t
+read_disk(off_t sector, void *buf)
+{
+
+ lseek(fd, (sector * 512), 0);
+ return read(fd, buf,
+ secsize > MIN_SEC_SIZE ? secsize : MIN_SEC_SIZE * 2);
+}
+
+static int
+write_disk(off_t sector, void *buf)
+{
+ int error;
+ struct gctl_req *grq;
+ const char *q;
+ char fbuf[BUFSIZ];
+ int i, fdw, sz;
+
+ sz = secsize > MIN_SEC_SIZE ? secsize : MIN_SEC_SIZE * 2;
+ grq = gctl_get_handle();
+ gctl_ro_param(grq, "verb", -1, "write PC98");
+ gctl_ro_param(grq, "class", -1, "PC98");
+ q = strrchr(disk, '/');
+ if (q == NULL)
+ q = disk;
+ else
+ q++;
+ gctl_ro_param(grq, "geom", -1, q);
+ gctl_ro_param(grq, "data", sz, buf);
+ q = gctl_issue(grq);
+ if (q == NULL) {
+ gctl_free(grq);
+ return(0);
+ }
+ warnx("Geom problem: %s", q);
+ gctl_free(grq);
+
+ warnx("Warning: Partitioning via geom failed, trying raw write");
+ error = pwrite(fd, buf, sz, sector * 512);
+ if (error == sz)
+ return (0);
+
+ for (i = 0; i < PC98_NPARTS; i++) {
+ sprintf(fbuf, "%ss%d", disk, i + 1);
+ fdw = open(fbuf, O_RDWR, 0);
+ if (fdw < 0)
+ continue;
+ error = ioctl(fdw, DIOCSPC98, buf);
+ close(fdw);
+ if (error == 0)
+ return (0);
+ }
+ warnx("Failed to write sector zero");
+ return(EINVAL);
+}
+
+static int
+get_params()
+{
+ int error;
+ u_int u;
+ off_t o;
+
+ error = ioctl(fd, DIOCGFWSECTORS, &u);
+ if (error == 0)
+ sectors = dos_sectors = u;
+ else
+ sectors = dos_sectors = 17;
+
+ error = ioctl(fd, DIOCGFWHEADS, &u);
+ if (error == 0)
+ heads = dos_heads = u;
+ else
+ heads = dos_heads = 8;
+
+ dos_cylsecs = cylsecs = heads * sectors;
+ disksecs = cyls * heads * sectors;
+
+ error = ioctl(fd, DIOCGSECTORSIZE, &u);
+ if (error != 0 || u == 0)
+ u = 512;
+ secsize = u;
+
+ error = ioctl(fd, DIOCGMEDIASIZE, &o);
+ if (error == 0) {
+ disksecs = o / u;
+ cyls = dos_cyls = o / (u * dos_heads * dos_sectors);
+ }
+
+ return (disksecs);
+}
+
+
+static int
+read_s0()
+{
+
+ if (read_disk(0, (char *) mboot.bootinst) == -1) {
+ warnx("can't read fdisk partition table");
+ return -1;
+ }
+ if (mboot.signature != PC98_MAGIC) {
+ warnx("invalid fdisk partition table found");
+ /* So should we initialize things */
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+write_s0()
+{
+
+ if (iotest) {
+ print_s0(-1);
+ return 0;
+ }
+
+ /*
+ * write enable label sector before write (if necessary),
+ * disable after writing.
+ * needed if the disklabel protected area also protects
+ * sector 0. (e.g. empty disk)
+ */
+ if (write_disk(0, (char *) mboot.bootinst) == -1) {
+ warn("can't write fdisk partition table");
+ return -1;
+ }
+
+ return(0);
+}
+
+
+static int
+ok(const char *str)
+{
+ printf("%s [n] ", str);
+ fflush(stdout);
+ if (fgets(lbuf, LBUF, stdin) == NULL)
+ exit(1);
+ lbuf[strlen(lbuf)-1] = 0;
+
+ if (*lbuf &&
+ (!strcmp(lbuf, "yes") || !strcmp(lbuf, "YES") ||
+ !strcmp(lbuf, "y") || !strcmp(lbuf, "Y")))
+ return 1;
+ else
+ return 0;
+}
+
+static int
+decimal(const char *str, int *num, int deflt)
+{
+ int acc = 0, c;
+ char *cp;
+
+ while (1) {
+ printf("Supply a decimal value for \"%s\" [%d] ", str, deflt);
+ fflush(stdout);
+ if (fgets(lbuf, LBUF, stdin) == NULL)
+ exit(1);
+ lbuf[strlen(lbuf)-1] = 0;
+
+ if (!*lbuf)
+ return 0;
+
+ cp = lbuf;
+ while ((c = *cp) && (c == ' ' || c == '\t')) cp++;
+ if (!c)
+ return 0;
+ while ((c = *cp++)) {
+ if (c <= '9' && c >= '0')
+ acc = acc * 10 + c - '0';
+ else
+ break;
+ }
+ if (c == ' ' || c == '\t')
+ while ((c = *cp) && (c == ' ' || c == '\t')) cp++;
+ if (!c) {
+ *num = acc;
+ return 1;
+ } else
+ printf("%s is an invalid decimal number. Try again.\n",
+ lbuf);
+ }
+
+}
+
+static int
+string(const char *str, char **ans)
+{
+ int i, c;
+ char *cp = lbuf;
+
+ while (1) {
+ printf("Supply a string value for \"%s\" [%s] ", str, *ans);
+ fgets(lbuf, LBUF, stdin);
+ lbuf[strlen(lbuf)-1] = 0;
+
+ if (!*lbuf)
+ return 0;
+
+ while ((c = *cp) && (c == ' ' || c == '\t')) cp++;
+ if (c == '"') {
+ c = *++cp;
+ *ans = cp;
+ while ((c = *cp) && c != '"') cp++;
+ } else {
+ *ans = cp;
+ while ((c = *cp) && c != ' ' && c != '\t') cp++;
+ }
+
+ for (i = strlen(*ans); i < 16; i++)
+ (*ans)[i] = ' ';
+ (*ans)[16] = 0;
+
+ return 1;
+ }
+}
+
+static const char *
+get_type(int type)
+{
+ int numentries = (sizeof(part_types)/sizeof(struct part_type));
+ int counter = 0;
+ struct part_type *ptr = part_types;
+
+
+ while(counter < numentries) {
+ if(ptr->type == (type & 0x7f))
+ return(ptr->name);
+ ptr++;
+ counter++;
+ }
+ return("unknown");
+}
+
+/*
+ * Try figuring out the root device's canonical disk name.
+ * The following choices are considered:
+ * /dev/ad0s1a => /dev/ad0
+ * /dev/da0a => /dev/da0
+ * /dev/vinum/root => /dev/vinum/root
+ */
+static char *
+get_rootdisk(void)
+{
+ struct statfs rootfs;
+ regex_t re;
+#define NMATCHES 2
+ regmatch_t rm[NMATCHES];
+ char *s;
+ int rv;
+
+ if (statfs("/", &rootfs) == -1)
+ err(1, "statfs(\"/\")");
+
+ if ((rv = regcomp(&re, "^(/dev/[a-z]+[0-9]+)([sp][0-9]+)?[a-h]?$",
+ REG_EXTENDED)) != 0)
+ errx(1, "regcomp() failed (%d)", rv);
+ if ((rv = regexec(&re, rootfs.f_mntfromname, NMATCHES, rm, 0)) != 0)
+ errx(1,
+"mounted root fs resource doesn't match expectations (regexec returned %d)",
+ rv);
+ if ((s = malloc(rm[1].rm_eo - rm[1].rm_so + 1)) == NULL)
+ errx(1, "out of memory");
+ memcpy(s, rootfs.f_mntfromname + rm[1].rm_so,
+ rm[1].rm_eo - rm[1].rm_so);
+ s[rm[1].rm_eo - rm[1].rm_so] = 0;
+
+ return s;
+}
+
+static void
+reset_boot(void)
+{
+ int i;
+ struct pc98_partition *partp;
+
+ init_boot();
+ for (i = 1; i <= PC98_NPARTS; i++) {
+ partp = ((struct pc98_partition *) &mboot.parts) + i - 1;
+ bzero((char *)partp, sizeof (struct pc98_partition));
+ }
+}
diff --git a/sbin/ffsinfo/Makefile b/sbin/ffsinfo/Makefile
new file mode 100644
index 0000000..7fd033d
--- /dev/null
+++ b/sbin/ffsinfo/Makefile
@@ -0,0 +1,18 @@
+# @(#)Makefile 8.8 (Berkeley) 6/21/2000
+#
+# $TSHeader: src/sbin/ffsinfo/Makefile,v 1.3 2000/12/05 19:45:10 tomsoft Exp $
+# $FreeBSD$
+#
+
+GROWFS= ${.CURDIR}/../growfs
+.PATH: ${GROWFS}
+
+PROG= ffsinfo
+SRCS= ffsinfo.c debug.c
+MAN= ffsinfo.8
+
+WARNS?= 1
+CFLAGS+=-DFS_DEBUG -I${GROWFS}
+LIBADD= ufs
+
+.include <bsd.prog.mk>
diff --git a/sbin/ffsinfo/ffsinfo.8 b/sbin/ffsinfo/ffsinfo.8
new file mode 100644
index 0000000..d3c8e9f
--- /dev/null
+++ b/sbin/ffsinfo/ffsinfo.8
@@ -0,0 +1,145 @@
+.\" Copyright (c) 2000 Christoph Herrmann, Thomas-Henning von Kamptz
+.\" Copyright (c) 1980, 1989, 1993 The Regents of the University of California.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" Christoph Herrmann and Thomas-Henning von Kamptz, Munich and Frankfurt.
+.\"
+.\" 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, as well as Christoph
+.\" Herrmann and Thomas-Henning von Kamptz.
+.\" 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.
+.\"
+.\" $TSHeader: src/sbin/ffsinfo/ffsinfo.8,v 1.3 2000/12/12 19:30:55 tomsoft Exp $
+.\" $FreeBSD$
+.\"
+.Dd September 8, 2000
+.Dt FFSINFO 8
+.Os
+.Sh NAME
+.Nm ffsinfo
+.Nd "dump all meta information of an existing ufs file system"
+.Sh SYNOPSIS
+.Nm
+.Op Fl g Ar cylinder_group
+.Op Fl i Ar inode
+.Op Fl l Ar level
+.Op Fl o Ar outfile
+.Ar special | file
+.Sh DESCRIPTION
+The
+.Nm
+utility extends the
+.Xr dumpfs 8
+utility.
+.Pp
+The output is appended to the file
+.Pa outfile .
+Also expect the output file to be rather large.
+Up to 2 percent of the size of the specified file system is not uncommon.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl g Ar cylinder_group
+This restricts the dump to information about this cylinder group only.
+Here
+.Ar 0
+means the first cylinder group and
+.Ar -1
+the last one.
+.It Fl i Ar inode
+This restricts the dump to information about this particular inode only.
+Here the minimum acceptable inode is
+.Ar 2 .
+If this option is omitted but a cylinder group is defined then only inodes
+within that cylinder group are dumped.
+.It Fl l Ar level
+The level of detail which will be dumped.
+This value defaults to
+.Ar 255
+and is the
+.Dq bitwise or
+of the following table:
+.Pp
+.Bl -hang -width indent -compact
+.It Ar 0x001
+initial superblock
+.It Ar 0x002
+superblock copies in each cylinder group
+.It Ar 0x004
+cylinder group summary in initial cylinder group
+.It Ar 0x008
+cylinder group information
+.It Ar 0x010
+inode allocation bitmap
+.It Ar 0x020
+fragment allocation bitmap
+.It Ar 0x040
+cluster maps and summary
+.It Ar 0x100
+inode information
+.It Ar 0x200
+indirect block dump
+.El
+.It Fl o Ar outfile
+This sets the output filename where the dump is written to, and
+must be specified.
+If
+.Fl
+is provided, output will be sent to stdout.
+.El
+.Sh EXAMPLES
+.Dl ffsinfo -o /var/tmp/ffsinfo -l 1023 /dev/vinum/testvol
+.Pp
+will dump
+.Pa /dev/vinum/testvol
+to
+.Pa /var/tmp/ffsinfo
+with all available information.
+.Sh SEE ALSO
+.Xr disklabel 8 ,
+.Xr dumpfs 8 ,
+.Xr fsck 8 ,
+.Xr growfs 8 ,
+.Xr gvinum 8 ,
+.Xr newfs 8 ,
+.Xr tunefs 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 4.4 .
+.Sh AUTHORS
+.An Christoph Herrmann Aq Mt chm@FreeBSD.org
+.An Thomas-Henning von Kamptz Aq Mt tomsoft@FreeBSD.org
+.An The GROWFS team Aq Mt growfs@Tomsoft.COM
+.Sh BUGS
+Snapshots are handled like plain files.
+They should get their own level to provide for independent control of the
+amount of what gets dumped.
+It probably also makes sense to some extend to dump the snapshot as a
+file system.
diff --git a/sbin/ffsinfo/ffsinfo.c b/sbin/ffsinfo/ffsinfo.c
new file mode 100644
index 0000000..c1e7c87
--- /dev/null
+++ b/sbin/ffsinfo/ffsinfo.c
@@ -0,0 +1,646 @@
+/*
+ * Copyright (c) 2000 Christoph Herrmann, Thomas-Henning von Kamptz
+ * Copyright (c) 1980, 1989, 1993 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Christoph Herrmann and Thomas-Henning von Kamptz, Munich and Frankfurt.
+ *
+ * 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, as well as Christoph
+ * Herrmann and Thomas-Henning von Kamptz.
+ * 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.
+ *
+ * $TSHeader: src/sbin/ffsinfo/ffsinfo.c,v 1.4 2000/12/12 19:30:55 tomsoft Exp $
+ *
+ */
+
+#ifndef lint
+static const char copyright[] =
+"@(#) Copyright (c) 2000 Christoph Herrmann, Thomas-Henning von Kamptz\n\
+Copyright (c) 1980, 1989, 1993 The Regents of the University of California.\n\
+All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+/* ********************************************************** INCLUDES ***** */
+#include <sys/param.h>
+#include <sys/disklabel.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+
+#include <ufs/ufs/ufsmount.h>
+#include <ufs/ufs/dinode.h>
+#include <ufs/ffs/fs.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libufs.h>
+#include <paths.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "debug.h"
+
+/* *********************************************************** GLOBALS ***** */
+#ifdef FS_DEBUG
+int _dbg_lvl_ = (DL_INFO); /* DL_TRC */
+#endif /* FS_DEBUG */
+
+static struct uufsd disk;
+
+#define sblock disk.d_fs
+#define acg disk.d_cg
+
+static union {
+ struct fs fs;
+ char pad[SBLOCKSIZE];
+} fsun;
+
+#define osblock fsun.fs
+
+static char i1blk[MAXBSIZE];
+static char i2blk[MAXBSIZE];
+static char i3blk[MAXBSIZE];
+
+static struct csum *fscs;
+
+/* ******************************************************** PROTOTYPES ***** */
+static void usage(void);
+static void dump_whole_ufs1_inode(ino_t, int);
+static void dump_whole_ufs2_inode(ino_t, int);
+
+#define DUMP_WHOLE_INODE(A,B) \
+ ( disk.d_ufs == 1 \
+ ? dump_whole_ufs1_inode((A),(B)) : dump_whole_ufs2_inode((A),(B)) )
+
+/* ************************************************************** main ***** */
+/*
+ * ffsinfo(8) is a tool to dump all metadata of a file system. It helps to find
+ * errors is the file system much easier. You can run ffsinfo before and after
+ * an fsck(8), and compare the two ascii dumps easy with diff, and you see
+ * directly where the problem is. You can control how much detail you want to
+ * see with some command line arguments. You can also easy check the status
+ * of a file system, like is there is enough space for growing a file system,
+ * or how many active snapshots do we have. It provides much more detailed
+ * information then dumpfs. Snapshots, as they are very new, are not really
+ * supported. They are just mentioned currently, but it is planned to run
+ * also over active snapshots, to even get that output.
+ */
+int
+main(int argc, char **argv)
+{
+ DBG_FUNC("main")
+ char *device, *special;
+ int ch;
+ size_t len;
+ struct stat st;
+ struct csum *dbg_csp;
+ int dbg_csc;
+ char dbg_line[80];
+ int cylno,i;
+ int cfg_cg, cfg_in, cfg_lv;
+ int cg_start, cg_stop;
+ ino_t in;
+ char *out_file;
+
+ DBG_ENTER;
+
+ cfg_lv = 0xff;
+ cfg_in = -2;
+ cfg_cg = -2;
+ out_file = strdup("-");
+
+ while ((ch = getopt(argc, argv, "g:i:l:o:")) != -1) {
+ switch (ch) {
+ case 'g':
+ cfg_cg = strtol(optarg, NULL, 0);
+ if (errno == EINVAL || errno == ERANGE)
+ err(1, "%s", optarg);
+ if (cfg_cg < -1)
+ usage();
+ break;
+ case 'i':
+ cfg_in = strtol(optarg, NULL, 0);
+ if (errno == EINVAL || errno == ERANGE)
+ err(1, "%s", optarg);
+ if (cfg_in < 0)
+ usage();
+ break;
+ case 'l':
+ cfg_lv = strtol(optarg, NULL, 0);
+ if (errno == EINVAL||errno == ERANGE)
+ err(1, "%s", optarg);
+ if (cfg_lv < 0x1 || cfg_lv > 0x3ff)
+ usage();
+ break;
+ case 'o':
+ free(out_file);
+ out_file = strdup(optarg);
+ if (out_file == NULL)
+ errx(1, "strdup failed");
+ break;
+ case '?':
+ /* FALLTHROUGH */
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 1)
+ usage();
+ device = *argv;
+
+ /*
+ * Now we try to guess the (raw)device name.
+ */
+ if (0 == strrchr(device, '/') && stat(device, &st) == -1) {
+ /*-
+ * No path prefix was given, so try in this order:
+ * /dev/r%s
+ * /dev/%s
+ * /dev/vinum/r%s
+ * /dev/vinum/%s.
+ *
+ * FreeBSD now doesn't distinguish between raw and block
+ * devices any longer, but it should still work this way.
+ */
+ len = strlen(device) + strlen(_PATH_DEV) + 2 + strlen("vinum/");
+ special = (char *)malloc(len);
+ if (special == NULL)
+ errx(1, "malloc failed");
+ snprintf(special, len, "%sr%s", _PATH_DEV, device);
+ if (stat(special, &st) == -1) {
+ snprintf(special, len, "%s%s", _PATH_DEV, device);
+ if (stat(special, &st) == -1) {
+ snprintf(special, len, "%svinum/r%s",
+ _PATH_DEV, device);
+ if (stat(special, &st) == -1)
+ /* For now this is the 'last resort' */
+ snprintf(special, len, "%svinum/%s",
+ _PATH_DEV, device);
+ }
+ }
+ device = special;
+ }
+
+ if (ufs_disk_fillout(&disk, device) == -1)
+ err(1, "ufs_disk_fillout(%s) failed: %s", device, disk.d_error);
+
+ DBG_OPEN(out_file); /* already here we need a superblock */
+
+ if (cfg_lv & 0x001)
+ DBG_DUMP_FS(&sblock, "primary sblock");
+
+ /* Determine here what cylinder groups to dump */
+ if (cfg_cg==-2) {
+ cg_start = 0;
+ cg_stop = sblock.fs_ncg;
+ } else if (cfg_cg == -1) {
+ cg_start = sblock.fs_ncg - 1;
+ cg_stop = sblock.fs_ncg;
+ } else if (cfg_cg < sblock.fs_ncg) {
+ cg_start = cfg_cg;
+ cg_stop = cfg_cg + 1;
+ } else {
+ cg_start = sblock.fs_ncg;
+ cg_stop = sblock.fs_ncg;
+ }
+
+ if (cfg_lv & 0x004) {
+ fscs = (struct csum *)calloc((size_t)1,
+ (size_t)sblock.fs_cssize);
+ if (fscs == NULL)
+ errx(1, "calloc failed");
+
+ /* get the cylinder summary into the memory ... */
+ for (i = 0; i < sblock.fs_cssize; i += sblock.fs_bsize) {
+ if (bread(&disk, fsbtodb(&sblock,
+ sblock.fs_csaddr + numfrags(&sblock, i)),
+ (void *)(((char *)fscs)+i),
+ (size_t)(sblock.fs_cssize-i < sblock.fs_bsize ?
+ sblock.fs_cssize - i : sblock.fs_bsize)) == -1)
+ err(1, "bread: %s", disk.d_error);
+ }
+
+ dbg_csp = fscs;
+ /* ... and dump it */
+ for(dbg_csc=0; dbg_csc<sblock.fs_ncg; dbg_csc++) {
+ snprintf(dbg_line, sizeof(dbg_line),
+ "%d. csum in fscs", dbg_csc);
+ DBG_DUMP_CSUM(&sblock,
+ dbg_line,
+ dbg_csp++);
+ }
+ }
+
+ if (cfg_lv & 0xf8) {
+ /* for each requested cylinder group ... */
+ for (cylno = cg_start; cylno < cg_stop; cylno++) {
+ snprintf(dbg_line, sizeof(dbg_line), "cgr %d", cylno);
+ if (cfg_lv & 0x002) {
+ /* dump the superblock copies */
+ if (bread(&disk, fsbtodb(&sblock,
+ cgsblock(&sblock, cylno)),
+ (void *)&osblock, SBLOCKSIZE) == -1)
+ err(1, "bread: %s", disk.d_error);
+ DBG_DUMP_FS(&osblock, dbg_line);
+ }
+
+ /*
+ * Read the cylinder group and dump whatever was
+ * requested.
+ */
+ if (bread(&disk, fsbtodb(&sblock,
+ cgtod(&sblock, cylno)), (void *)&acg,
+ (size_t)sblock.fs_cgsize) == -1)
+ err(1, "bread: %s", disk.d_error);
+
+ if (cfg_lv & 0x008)
+ DBG_DUMP_CG(&sblock, dbg_line, &acg);
+ if (cfg_lv & 0x010)
+ DBG_DUMP_INMAP(&sblock, dbg_line, &acg);
+ if (cfg_lv & 0x020)
+ DBG_DUMP_FRMAP(&sblock, dbg_line, &acg);
+ if (cfg_lv & 0x040) {
+ DBG_DUMP_CLMAP(&sblock, dbg_line, &acg);
+ DBG_DUMP_CLSUM(&sblock, dbg_line, &acg);
+ }
+ #ifdef NOT_CURRENTLY
+ /*
+ * See the comment in sbin/growfs/debug.c for why this
+ * is currently disabled, and what needs to be done to
+ * re-enable it.
+ */
+ if (disk.d_ufs == 1 && cfg_lv & 0x080)
+ DBG_DUMP_SPTBL(&sblock, dbg_line, &acg);
+ #endif
+ }
+ }
+
+ if (cfg_lv & 0x300) {
+ /* Dump the requested inode(s) */
+ if (cfg_in != -2)
+ DUMP_WHOLE_INODE((ino_t)cfg_in, cfg_lv);
+ else {
+ for (in = cg_start * sblock.fs_ipg;
+ in < (ino_t)cg_stop * sblock.fs_ipg;
+ in++)
+ DUMP_WHOLE_INODE(in, cfg_lv);
+ }
+ }
+
+ DBG_CLOSE;
+ DBG_LEAVE;
+
+ return 0;
+}
+
+/* ********************************************** dump_whole_ufs1_inode ***** */
+/*
+ * Here we dump a list of all blocks allocated by this inode. We follow
+ * all indirect blocks.
+ */
+void
+dump_whole_ufs1_inode(ino_t inode, int level)
+{
+ DBG_FUNC("dump_whole_ufs1_inode")
+ struct ufs1_dinode *ino;
+ int rb, mode;
+ unsigned int ind2ctr, ind3ctr;
+ ufs1_daddr_t *ind2ptr, *ind3ptr;
+ char comment[80];
+
+ DBG_ENTER;
+
+ /*
+ * Read the inode from disk/cache.
+ */
+ if (getino(&disk, (void **)&ino, inode, &mode) == -1)
+ err(1, "getino: %s", disk.d_error);
+
+ if(ino->di_nlink==0) {
+ DBG_LEAVE;
+ return; /* inode not in use */
+ }
+
+ /*
+ * Dump the main inode structure.
+ */
+ snprintf(comment, sizeof(comment), "Inode 0x%08jx", (uintmax_t)inode);
+ if (level & 0x100) {
+ DBG_DUMP_INO(&sblock,
+ comment,
+ ino);
+ }
+
+ if (!(level & 0x200)) {
+ DBG_LEAVE;
+ return;
+ }
+
+ /*
+ * Ok, now prepare for dumping all direct and indirect pointers.
+ */
+ rb=howmany(ino->di_size, sblock.fs_bsize)-NDADDR;
+ if(rb>0) {
+ /*
+ * Dump single indirect block.
+ */
+ if (bread(&disk, fsbtodb(&sblock, ino->di_ib[0]), (void *)&i1blk,
+ (size_t)sblock.fs_bsize) == -1) {
+ err(1, "bread: %s", disk.d_error);
+ }
+ snprintf(comment, sizeof(comment), "Inode 0x%08jx: indirect 0",
+ (uintmax_t)inode);
+ DBG_DUMP_IBLK(&sblock,
+ comment,
+ i1blk,
+ (size_t)rb);
+ rb-=howmany(sblock.fs_bsize, sizeof(ufs1_daddr_t));
+ }
+ if(rb>0) {
+ /*
+ * Dump double indirect blocks.
+ */
+ if (bread(&disk, fsbtodb(&sblock, ino->di_ib[1]), (void *)&i2blk,
+ (size_t)sblock.fs_bsize) == -1) {
+ err(1, "bread: %s", disk.d_error);
+ }
+ snprintf(comment, sizeof(comment), "Inode 0x%08jx: indirect 1",
+ (uintmax_t)inode);
+ DBG_DUMP_IBLK(&sblock,
+ comment,
+ i2blk,
+ howmany(rb, howmany(sblock.fs_bsize, sizeof(ufs1_daddr_t))));
+ for(ind2ctr=0; ((ind2ctr < howmany(sblock.fs_bsize,
+ sizeof(ufs1_daddr_t))) && (rb>0)); ind2ctr++) {
+ ind2ptr=&((ufs1_daddr_t *)(void *)&i2blk)[ind2ctr];
+
+ if (bread(&disk, fsbtodb(&sblock, *ind2ptr), (void *)&i1blk,
+ (size_t)sblock.fs_bsize) == -1) {
+ err(1, "bread: %s", disk.d_error);
+ }
+ snprintf(comment, sizeof(comment),
+ "Inode 0x%08jx: indirect 1->%d", (uintmax_t)inode,
+ ind2ctr);
+ DBG_DUMP_IBLK(&sblock,
+ comment,
+ i1blk,
+ (size_t)rb);
+ rb-=howmany(sblock.fs_bsize, sizeof(ufs1_daddr_t));
+ }
+ }
+ if(rb>0) {
+ /*
+ * Dump triple indirect blocks.
+ */
+ if (bread(&disk, fsbtodb(&sblock, ino->di_ib[2]), (void *)&i3blk,
+ (size_t)sblock.fs_bsize) == -1) {
+ err(1, "bread: %s", disk.d_error);
+ }
+ snprintf(comment, sizeof(comment), "Inode 0x%08jx: indirect 2",
+ (uintmax_t)inode);
+#define SQUARE(a) ((a)*(a))
+ DBG_DUMP_IBLK(&sblock,
+ comment,
+ i3blk,
+ howmany(rb,
+ SQUARE(howmany(sblock.fs_bsize, sizeof(ufs1_daddr_t)))));
+#undef SQUARE
+ for(ind3ctr=0; ((ind3ctr<howmany(sblock.fs_bsize,
+ sizeof(ufs1_daddr_t)))&&(rb>0)); ind3ctr++) {
+ ind3ptr=&((ufs1_daddr_t *)(void *)&i3blk)[ind3ctr];
+
+ if (bread(&disk, fsbtodb(&sblock, *ind3ptr), (void *)&i2blk,
+ (size_t)sblock.fs_bsize) == -1) {
+ err(1, "bread: %s", disk.d_error);
+ }
+ snprintf(comment, sizeof(comment),
+ "Inode 0x%08jx: indirect 2->%d", (uintmax_t)inode,
+ ind3ctr);
+ DBG_DUMP_IBLK(&sblock,
+ comment,
+ i2blk,
+ howmany(rb,
+ howmany(sblock.fs_bsize, sizeof(ufs1_daddr_t))));
+ for(ind2ctr=0; ((ind2ctr < howmany(sblock.fs_bsize,
+ sizeof(ufs1_daddr_t)))&&(rb>0)); ind2ctr++) {
+ ind2ptr=&((ufs1_daddr_t *)(void *)&i2blk)
+ [ind2ctr];
+ if (bread(&disk, fsbtodb(&sblock, *ind2ptr),
+ (void *)&i1blk, (size_t)sblock.fs_bsize)
+ == -1) {
+ err(1, "bread: %s", disk.d_error);
+ }
+ snprintf(comment, sizeof(comment),
+ "Inode 0x%08jx: indirect 2->%d->%d",
+ (uintmax_t)inode, ind3ctr, ind3ctr);
+ DBG_DUMP_IBLK(&sblock,
+ comment,
+ i1blk,
+ (size_t)rb);
+ rb-=howmany(sblock.fs_bsize,
+ sizeof(ufs1_daddr_t));
+ }
+ }
+ }
+
+ DBG_LEAVE;
+ return;
+}
+
+/* ********************************************** dump_whole_ufs2_inode ***** */
+/*
+ * Here we dump a list of all blocks allocated by this inode. We follow
+ * all indirect blocks.
+ */
+void
+dump_whole_ufs2_inode(ino_t inode, int level)
+{
+ DBG_FUNC("dump_whole_ufs2_inode")
+ struct ufs2_dinode *ino;
+ int rb, mode;
+ unsigned int ind2ctr, ind3ctr;
+ ufs2_daddr_t *ind2ptr, *ind3ptr;
+ char comment[80];
+
+ DBG_ENTER;
+
+ /*
+ * Read the inode from disk/cache.
+ */
+ if (getino(&disk, (void **)&ino, inode, &mode) == -1)
+ err(1, "getino: %s", disk.d_error);
+
+ if (ino->di_nlink == 0) {
+ DBG_LEAVE;
+ return; /* inode not in use */
+ }
+
+ /*
+ * Dump the main inode structure.
+ */
+ snprintf(comment, sizeof(comment), "Inode 0x%08jx", (uintmax_t)inode);
+ if (level & 0x100) {
+ DBG_DUMP_INO(&sblock, comment, ino);
+ }
+
+ if (!(level & 0x200)) {
+ DBG_LEAVE;
+ return;
+ }
+
+ /*
+ * Ok, now prepare for dumping all direct and indirect pointers.
+ */
+ rb = howmany(ino->di_size, sblock.fs_bsize) - NDADDR;
+ if (rb > 0) {
+ /*
+ * Dump single indirect block.
+ */
+ if (bread(&disk, fsbtodb(&sblock, ino->di_ib[0]), (void *)&i1blk,
+ (size_t)sblock.fs_bsize) == -1) {
+ err(1, "bread: %s", disk.d_error);
+ }
+ snprintf(comment, sizeof(comment), "Inode 0x%08jx: indirect 0",
+ (uintmax_t)inode);
+ DBG_DUMP_IBLK(&sblock, comment, i1blk, (size_t)rb);
+ rb -= howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t));
+ }
+ if (rb > 0) {
+ /*
+ * Dump double indirect blocks.
+ */
+ if (bread(&disk, fsbtodb(&sblock, ino->di_ib[1]), (void *)&i2blk,
+ (size_t)sblock.fs_bsize) == -1) {
+ err(1, "bread: %s", disk.d_error);
+ }
+ snprintf(comment, sizeof(comment), "Inode 0x%08jx: indirect 1",
+ (uintmax_t)inode);
+ DBG_DUMP_IBLK(&sblock,
+ comment,
+ i2blk,
+ howmany(rb, howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t))));
+ for (ind2ctr = 0; ((ind2ctr < howmany(sblock.fs_bsize,
+ sizeof(ufs2_daddr_t))) && (rb>0)); ind2ctr++) {
+ ind2ptr = &((ufs2_daddr_t *)(void *)&i2blk)[ind2ctr];
+
+ if (bread(&disk, fsbtodb(&sblock, *ind2ptr), (void *)&i1blk,
+ (size_t)sblock.fs_bsize) == -1) {
+ err(1, "bread: %s", disk.d_error);
+ }
+ snprintf(comment, sizeof(comment),
+ "Inode 0x%08jx: indirect 1->%d",
+ (uintmax_t)inode, ind2ctr);
+ DBG_DUMP_IBLK(&sblock, comment, i1blk, (size_t)rb);
+ rb -= howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t));
+ }
+ }
+ if (rb > 0) {
+ /*
+ * Dump triple indirect blocks.
+ */
+ if (bread(&disk, fsbtodb(&sblock, ino->di_ib[2]), (void *)&i3blk,
+ (size_t)sblock.fs_bsize) == -1) {
+ err(1, "bread: %s", disk.d_error);
+ }
+ snprintf(comment, sizeof(comment), "Inode 0x%08jx: indirect 2",
+ (uintmax_t)inode);
+#define SQUARE(a) ((a)*(a))
+ DBG_DUMP_IBLK(&sblock,
+ comment,
+ i3blk,
+ howmany(rb,
+ SQUARE(howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t)))));
+#undef SQUARE
+ for (ind3ctr = 0; ((ind3ctr < howmany(sblock.fs_bsize,
+ sizeof(ufs2_daddr_t))) && (rb > 0)); ind3ctr++) {
+ ind3ptr = &((ufs2_daddr_t *)(void *)&i3blk)[ind3ctr];
+
+ if (bread(&disk, fsbtodb(&sblock, *ind3ptr), (void *)&i2blk,
+ (size_t)sblock.fs_bsize) == -1) {
+ err(1, "bread: %s", disk.d_error);
+ }
+ snprintf(comment, sizeof(comment),
+ "Inode 0x%08jx: indirect 2->%d",
+ (uintmax_t)inode, ind3ctr);
+ DBG_DUMP_IBLK(&sblock,
+ comment,
+ i2blk,
+ howmany(rb,
+ howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t))));
+ for (ind2ctr = 0; ((ind2ctr < howmany(sblock.fs_bsize,
+ sizeof(ufs2_daddr_t))) && (rb > 0)); ind2ctr++) {
+ ind2ptr = &((ufs2_daddr_t *)(void *)&i2blk) [ind2ctr];
+ if (bread(&disk, fsbtodb(&sblock, *ind2ptr), (void *)&i1blk,
+ (size_t)sblock.fs_bsize) == -1) {
+ err(1, "bread: %s", disk.d_error);
+ }
+ snprintf(comment, sizeof(comment),
+ "Inode 0x%08jx: indirect 2->%d->%d",
+ (uintmax_t)inode, ind3ctr, ind3ctr);
+ DBG_DUMP_IBLK(&sblock, comment, i1blk, (size_t)rb);
+ rb -= howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t));
+ }
+ }
+ }
+
+ DBG_LEAVE;
+ return;
+}
+
+/* ************************************************************* usage ***** */
+/*
+ * Dump a line of usage.
+ */
+void
+usage(void)
+{
+ DBG_FUNC("usage")
+
+ DBG_ENTER;
+
+ fprintf(stderr,
+ "usage: ffsinfo [-g cylinder_group] [-i inode] [-l level] "
+ "[-o outfile]\n"
+ " special | file\n");
+
+ DBG_LEAVE;
+ exit(1);
+}
diff --git a/sbin/fsck/Makefile b/sbin/fsck/Makefile
new file mode 100644
index 0000000..22de03c
--- /dev/null
+++ b/sbin/fsck/Makefile
@@ -0,0 +1,8 @@
+# $NetBSD: Makefile,v 1.14 1996/09/27 22:38:37 christos Exp $
+# $FreeBSD$
+
+PROG= fsck
+SRCS= fsck.c fsutil.c preen.c
+MAN= fsck.8
+
+.include <bsd.prog.mk>
diff --git a/sbin/fsck/fsck.8 b/sbin/fsck/fsck.8
new file mode 100644
index 0000000..5d2a019
--- /dev/null
+++ b/sbin/fsck/fsck.8
@@ -0,0 +1,231 @@
+.\" $NetBSD: fsck.8,v 1.19 1999/03/10 00:08:33 erh Exp $
+.\"
+.\" Copyright (c) 1996 Christos Zoulas. 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 Christos Zoulas.
+.\" 4. The name of the author may not be used to endorse or promote products
+.\" derived from this software without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+.\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+.\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd May 23, 2014
+.Dt FSCK 8
+.Os
+.Sh NAME
+.Nm fsck
+.Nd file system consistency check and interactive repair
+.Sh SYNOPSIS
+.Nm
+.Op Fl Cdfnpvy
+.Op Fl B | F
+.Op Fl T Ar fstype : Ns Ar fsoptions
+.Op Fl t Ar fstype
+.Op Fl c Ar fstab
+.Oo Ar special | node Oc ...
+.Sh DESCRIPTION
+The
+.Nm
+utility invokes file system-specific programs to check
+the special devices listed in the
+.Xr fstab 5
+file or in the command line for consistency.
+.Pp
+It is normally used in the script
+.Pa /etc/rc
+during automatic reboot.
+Traditionally,
+.Nm
+is invoked before the file systems are mounted
+and all checks are done to completion at that time.
+If background checking is available,
+.Nm
+is invoked twice.
+It is first invoked at the traditional time,
+before the file systems are mounted, with the
+.Fl F
+flag to do checking on all the file systems
+that cannot do background checking.
+It is then invoked a second time,
+after the system has completed going multiuser, with the
+.Fl B
+flag to do checking on all the file systems
+that can do background checking.
+Unlike the foreground checking,
+the background checking is started asynchronously
+so that other system activity can proceed
+even on the file systems that are being checked.
+.Pp
+If no file systems are specified,
+.Nm
+reads the table
+.Pa /etc/fstab
+to determine which file systems to check.
+Only partitions in
+.Pa /etc/fstab
+that are mounted
+.Dq rw ,
+.Dq rq
+or
+.Dq ro
+and that have non-zero pass number are checked.
+File systems with pass number 1 (normally just the root file system)
+are always checked one at a time.
+.Pp
+If not in preen mode, the remaining entries are checked in order of
+increasing pass number one at a time.
+This is needed when interaction with
+.Nm
+is required.
+.Pp
+In preen mode, after pass 1 completes, all remaining file systems are checked,
+in pass number order running one process per disk drive in parallel for each
+pass number in increasing order.
+.Pp
+In other words: In preen mode all pass 1 partitions are checked sequentially.
+Next all pass 2 partitions are checked in parallel, one process per disk drive.
+Next all pass 3 partitions are checked in parallel, one process per disk drive.
+etc.
+.Pp
+The disk drive containing each file system is inferred from the shortest prefix
+of the device name that ends in a digit; the remaining characters are assumed
+to be the partition and slice designators.
+.Pp
+If the
+.Fl t
+or
+.Fl T
+flags are not specified,
+.Nm
+will attempt to determine the file system type and call the
+appropriated file system check utility.
+Failure to detect the file system type will cause
+.Nm
+to fail with a message that the partition has an unknown file system type.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl C
+Check if the
+.Dq clean
+flag is set in the superblock and skip file system checks if file system was
+properly dismounted and marked clean.
+.It Fl c Ar fstab
+Specify the
+.Pa fstab
+file to use.
+.It Fl d
+Debugging mode.
+Just print the commands without executing them.
+Available
+only if
+.Nm
+is compiled to support it.
+.It Fl f
+Force checking of file systems, even when they are marked clean (for file systems
+that support this).
+.It Fl n
+Causes
+.Nm
+to assume no as the answer to all operator questions, except "CONTINUE?".
+.It Fl p
+Enter preen mode.
+In preen mode, only a restricted class of innocuous
+file system inconsistencies will be corrected.
+If unexpected inconsistencies caused by hardware or
+software failures are encountered, the check program
+will exit with a failure.
+See the manual pages for the individual check programs
+for a list of the sorts of failures that they correct
+when running in preen mode.
+.It Fl F
+Run in foreground mode.
+The check program for each file system is invoked with the
+.Fl F
+flag to determine whether it wishes to run as part of
+the boot up sequence,
+or if it is able to do its job in background after the
+system is up and running.
+A non-zero exit code indicates that it wants to run in foreground
+and the check program is invoked.
+A zero exit code indicates that it is able to run later in background
+and just a deferred message is printed.
+.It Fl B
+Run in background mode.
+The check program for each file system is invoked with the
+.Fl F
+flag to determine whether it wishes to run as part of
+the boot up sequence,
+or if it is able to do its job in background after the
+system is up and running.
+A non-zero exit code indicates that it wanted to run in foreground
+which is assumed to have been done, so the file system is skipped.
+A zero exit code indicates that it is able to run in background
+so the check program is invoked with the
+.Fl B
+flag to indicate that a check on the active file system should be done.
+When running in background mode,
+only one file system at a time will be checked.
+Note that background
+.Nm
+is limited to checking for only the most commonly occurring
+file system abnormalities.
+Under certain circumstances,
+some errors can escape background
+.Nm .
+It is recommended that you perform foreground
+.Nm
+on your systems periodically and whenever you encounter
+file-system\-related panics.
+.It Fl t Ar fstype
+Invoke
+.Nm
+only for the comma separated list of file system types.
+If the
+list starts with
+.Dq no
+then invoke
+.Nm
+for the file system types that are not specified in the list.
+.It Fl v
+Print the commands before executing them.
+.It Fl y
+Causes
+.Nm
+to assume yes
+as the answer to all operator questions.
+.It Fl T Ar fstype : Ns Ar fsoptions
+List of comma separated file system specific options for the specified
+file system type, in the same format as
+.Xr mount 8 .
+.El
+.Sh FILES
+.Bl -tag -width /etc/fstab -compact
+.It Pa /etc/fstab
+file system table
+.El
+.Sh SEE ALSO
+.Xr fstab 5 ,
+.Xr fsck_ffs 8 ,
+.Xr fsck_msdosfs 8 ,
+.Xr mount 8
diff --git a/sbin/fsck/fsck.c b/sbin/fsck/fsck.c
new file mode 100644
index 0000000..63ff153
--- /dev/null
+++ b/sbin/fsck/fsck.c
@@ -0,0 +1,581 @@
+/* $NetBSD: fsck.c,v 1.30 2003/08/07 10:04:15 agc Exp $ */
+
+/*
+ * Copyright (c) 1996 Christos Zoulas. All rights reserved.
+ * Copyright (c) 1980, 1989, 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. 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.
+ *
+ * From: @(#)mount.c 8.19 (Berkeley) 4/19/94
+ * From: $NetBSD: mount.c,v 1.24 1995/11/18 03:34:29 cgd Exp
+ * $NetBSD: fsck.c,v 1.30 2003/08/07 10:04:15 agc Exp $
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/mount.h>
+#include <sys/queue.h>
+#include <sys/wait.h>
+#include <sys/disk.h>
+#include <sys/ioctl.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fstab.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "fsutil.h"
+
+static enum { IN_LIST, NOT_IN_LIST } which = NOT_IN_LIST;
+
+static TAILQ_HEAD(fstypelist, entry) opthead, selhead;
+
+struct entry {
+ char *type;
+ char *options;
+ TAILQ_ENTRY(entry) entries;
+};
+
+static char *options = NULL;
+static int flags = 0;
+static int forceflag = 0;
+
+static int checkfs(const char *, const char *, const char *, const char *, pid_t *);
+static int selected(const char *);
+static void addoption(char *);
+static const char *getoptions(const char *);
+static void addentry(struct fstypelist *, const char *, const char *);
+static void maketypelist(char *);
+static void catopt(char **, const char *);
+static void mangle(char *, int *, const char ** volatile *, int *);
+static const char *getfstype(const char *);
+static void usage(void) __dead2;
+static int isok(struct fstab *);
+
+static struct {
+ const char *ptype;
+ const char *name;
+} ptype_map[] = {
+ { "ufs", "ffs" },
+ { "ffs", "ffs" },
+ { "fat", "msdosfs" },
+ { "efi", "msdosfs" },
+ { NULL, NULL },
+};
+
+int
+main(int argc, char *argv[])
+{
+ struct fstab *fs;
+ int i, rval = 0;
+ const char *vfstype = NULL;
+ char globopt[3];
+ const char *etc_fstab;
+
+ globopt[0] = '-';
+ globopt[2] = '\0';
+
+ TAILQ_INIT(&selhead);
+ TAILQ_INIT(&opthead);
+
+ etc_fstab = NULL;
+ while ((i = getopt(argc, argv, "BCdvpfFnyl:t:T:c:")) != -1)
+ switch (i) {
+ case 'B':
+ if (flags & CHECK_BACKGRD)
+ errx(1, "Cannot specify -B and -F.");
+ flags |= DO_BACKGRD;
+ break;
+
+ case 'd':
+ flags |= CHECK_DEBUG;
+ break;
+
+ case 'v':
+ flags |= CHECK_VERBOSE;
+ break;
+
+ case 'F':
+ if (flags & DO_BACKGRD)
+ errx(1, "Cannot specify -B and -F.");
+ flags |= CHECK_BACKGRD;
+ break;
+
+ case 'p':
+ flags |= CHECK_PREEN;
+ /*FALLTHROUGH*/
+ case 'C':
+ flags |= CHECK_CLEAN;
+ /*FALLTHROUGH*/
+ case 'n':
+ case 'y':
+ globopt[1] = i;
+ catopt(&options, globopt);
+ break;
+
+ case 'f':
+ forceflag = 1;
+ globopt[1] = i;
+ catopt(&options, globopt);
+ break;
+
+ case 'l':
+ warnx("Ignoring obsolete -l option\n");
+ break;
+
+ case 'T':
+ if (*optarg)
+ addoption(optarg);
+ break;
+
+ case 't':
+ if (!TAILQ_EMPTY(&selhead))
+ errx(1, "only one -t option may be specified.");
+
+ maketypelist(optarg);
+ vfstype = optarg;
+ break;
+
+ case 'c':
+ etc_fstab = optarg;
+ break;
+
+ case '?':
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (etc_fstab != NULL)
+ setfstab(etc_fstab);
+
+ if (argc == 0)
+ return checkfstab(flags, isok, checkfs);
+
+#define BADTYPE(type) \
+ (strcmp(type, FSTAB_RO) && \
+ strcmp(type, FSTAB_RW) && strcmp(type, FSTAB_RQ))
+
+
+ for (; argc--; argv++) {
+ const char *spec, *mntpt, *type, *cp;
+ char device[MAXPATHLEN];
+ struct statfs *mntp;
+
+ mntpt = NULL;
+ spec = *argv;
+ cp = strrchr(spec, '/');
+ if (cp == 0) {
+ (void)snprintf(device, sizeof(device), "%s%s",
+ _PATH_DEV, spec);
+ spec = device;
+ }
+ mntp = getmntpt(spec);
+ if (mntp != NULL) {
+ spec = mntp->f_mntfromname;
+ mntpt = mntp->f_mntonname;
+ }
+ if ((fs = getfsfile(spec)) == NULL &&
+ (fs = getfsspec(spec)) == NULL) {
+ if (vfstype == NULL)
+ vfstype = getfstype(spec);
+ if (vfstype == NULL)
+ errx(1, "Could not determine filesystem type");
+ type = vfstype;
+ devcheck(spec);
+ } else {
+ spec = fs->fs_spec;
+ type = fs->fs_vfstype;
+ mntpt = fs->fs_file;
+ if (BADTYPE(fs->fs_type))
+ errx(1, "%s has unknown file system type.",
+ spec);
+ }
+ if ((flags & CHECK_BACKGRD) &&
+ checkfs(type, spec, mntpt, "-F", NULL) == 0) {
+ printf("%s: DEFER FOR BACKGROUND CHECKING\n", *argv);
+ continue;
+ }
+ if ((flags & DO_BACKGRD) && forceflag == 0 &&
+ checkfs(type, spec, mntpt, "-F", NULL) != 0)
+ continue;
+
+ rval |= checkfs(type, spec, mntpt, NULL, NULL);
+ }
+
+ return rval;
+}
+
+
+static int
+isok(struct fstab *fs)
+{
+ int i;
+
+ if (fs->fs_passno == 0)
+ return (0);
+ if (BADTYPE(fs->fs_type))
+ return (0);
+ if (!selected(fs->fs_vfstype))
+ return (0);
+ /*
+ * If the -B flag has been given, then process the needed
+ * background checks. Background checks cannot be run on
+ * file systems that will be mounted read-only or that were
+ * not mounted at boot time (typically those marked `noauto').
+ * If these basic tests are passed, check with the file system
+ * itself to see if it is willing to do background checking
+ * by invoking its check program with the -F flag.
+ */
+ if (flags & DO_BACKGRD) {
+ if (!strcmp(fs->fs_type, FSTAB_RO))
+ return (0);
+ if (getmntpt(fs->fs_spec) == NULL)
+ return (0);
+ if (checkfs(fs->fs_vfstype, fs->fs_spec, fs->fs_file, "-F", 0))
+ return (0);
+ return (1);
+ }
+ /*
+ * If the -F flag has been given, then consider deferring the
+ * check to background. Background checks cannot be run on
+ * file systems that will be mounted read-only or that will
+ * not be mounted at boot time (e.g., marked `noauto'). If
+ * these basic tests are passed, check with the file system
+ * itself to see if it is willing to defer to background
+ * checking by invoking its check program with the -F flag.
+ */
+ if ((flags & CHECK_BACKGRD) == 0 || !strcmp(fs->fs_type, FSTAB_RO))
+ return (1);
+ for (i = strlen(fs->fs_mntops) - 6; i >= 0; i--)
+ if (!strncmp(&fs->fs_mntops[i], "noauto", 6))
+ break;
+ if (i >= 0)
+ return (1);
+ if (checkfs(fs->fs_vfstype, fs->fs_spec, fs->fs_file, "-F", NULL) != 0)
+ return (1);
+ printf("%s: DEFER FOR BACKGROUND CHECKING\n", fs->fs_spec);
+ return (0);
+}
+
+
+static int
+checkfs(const char *pvfstype, const char *spec, const char *mntpt,
+ const char *auxopt, pid_t *pidp)
+{
+ const char ** volatile argv;
+ pid_t pid;
+ int argc, i, status, maxargc;
+ char *optbuf, execbase[MAXPATHLEN];
+ char *vfstype = NULL;
+ const char *extra = NULL;
+
+#ifdef __GNUC__
+ /* Avoid vfork clobbering */
+ (void) &optbuf;
+ (void) &vfstype;
+#endif
+ /*
+ * We convert the vfstype to lowercase and any spaces to underscores
+ * to not confuse the issue
+ *
+ * XXX This is a kludge to make automatic filesystem type guessing
+ * from the disklabel work for "4.2BSD" filesystems. It does a
+ * very limited subset of transliteration to a normalised form of
+ * filesystem name, and we do not seem to enforce a filesystem
+ * name character set.
+ */
+ vfstype = strdup(pvfstype);
+ if (vfstype == NULL)
+ perr("strdup(pvfstype)");
+ for (i = 0; i < (int)strlen(vfstype); i++) {
+ vfstype[i] = tolower(vfstype[i]);
+ if (vfstype[i] == ' ')
+ vfstype[i] = '_';
+ }
+
+ extra = getoptions(vfstype);
+ optbuf = NULL;
+ if (options)
+ catopt(&optbuf, options);
+ if (extra)
+ catopt(&optbuf, extra);
+ if (auxopt)
+ catopt(&optbuf, auxopt);
+ else if (flags & DO_BACKGRD)
+ catopt(&optbuf, "-B");
+
+ maxargc = 64;
+ argv = emalloc(sizeof(char *) * maxargc);
+
+ (void) snprintf(execbase, sizeof(execbase), "fsck_%s", vfstype);
+ argc = 0;
+ argv[argc++] = execbase;
+ if (optbuf)
+ mangle(optbuf, &argc, &argv, &maxargc);
+ argv[argc++] = spec;
+ argv[argc] = NULL;
+
+ if (flags & (CHECK_DEBUG|CHECK_VERBOSE)) {
+ (void)printf("start %s %swait", mntpt,
+ pidp ? "no" : "");
+ for (i = 0; i < argc; i++)
+ (void)printf(" %s", argv[i]);
+ (void)printf("\n");
+ }
+
+ switch (pid = vfork()) {
+ case -1: /* Error. */
+ warn("vfork");
+ if (optbuf)
+ free(optbuf);
+ free(vfstype);
+ return (1);
+
+ case 0: /* Child. */
+ if ((flags & CHECK_DEBUG) && auxopt == NULL)
+ _exit(0);
+
+ /* Go find an executable. */
+ execvP(execbase, _PATH_SYSPATH, __DECONST(char * const *, argv));
+ if (spec)
+ warn("exec %s for %s in %s", execbase, spec, _PATH_SYSPATH);
+ else
+ warn("exec %s in %s", execbase, _PATH_SYSPATH);
+ _exit(1);
+ /* NOTREACHED */
+
+ default: /* Parent. */
+ if (optbuf)
+ free(optbuf);
+
+ free(vfstype);
+
+ if (pidp) {
+ *pidp = pid;
+ return 0;
+ }
+
+ if (waitpid(pid, &status, 0) < 0) {
+ warn("waitpid");
+ return (1);
+ }
+
+ if (WIFEXITED(status)) {
+ if (WEXITSTATUS(status) != 0)
+ return (WEXITSTATUS(status));
+ }
+ else if (WIFSIGNALED(status)) {
+ warnx("%s: %s", spec, strsignal(WTERMSIG(status)));
+ return (1);
+ }
+ break;
+ }
+
+ return (0);
+}
+
+
+static int
+selected(const char *type)
+{
+ struct entry *e;
+
+ /* If no type specified, it's always selected. */
+ TAILQ_FOREACH(e, &selhead, entries)
+ if (!strncmp(e->type, type, MFSNAMELEN))
+ return which == IN_LIST ? 1 : 0;
+
+ return which == IN_LIST ? 0 : 1;
+}
+
+
+static const char *
+getoptions(const char *type)
+{
+ struct entry *e;
+
+ TAILQ_FOREACH(e, &opthead, entries)
+ if (!strncmp(e->type, type, MFSNAMELEN))
+ return e->options;
+ return "";
+}
+
+
+static void
+addoption(char *optstr)
+{
+ char *newoptions;
+ struct entry *e;
+
+ if ((newoptions = strchr(optstr, ':')) == NULL)
+ errx(1, "Invalid option string");
+
+ *newoptions++ = '\0';
+
+ TAILQ_FOREACH(e, &opthead, entries)
+ if (!strncmp(e->type, optstr, MFSNAMELEN)) {
+ catopt(&e->options, newoptions);
+ return;
+ }
+ addentry(&opthead, optstr, newoptions);
+}
+
+
+static void
+addentry(struct fstypelist *list, const char *type, const char *opts)
+{
+ struct entry *e;
+
+ e = emalloc(sizeof(struct entry));
+ e->type = estrdup(type);
+ e->options = estrdup(opts);
+ TAILQ_INSERT_TAIL(list, e, entries);
+}
+
+
+static void
+maketypelist(char *fslist)
+{
+ char *ptr;
+
+ if ((fslist == NULL) || (fslist[0] == '\0'))
+ errx(1, "empty type list");
+
+ if (fslist[0] == 'n' && fslist[1] == 'o') {
+ fslist += 2;
+ which = NOT_IN_LIST;
+ }
+ else
+ which = IN_LIST;
+
+ while ((ptr = strsep(&fslist, ",")) != NULL)
+ addentry(&selhead, ptr, "");
+
+}
+
+
+static void
+catopt(char **sp, const char *o)
+{
+ char *s;
+ size_t i, j;
+
+ s = *sp;
+ if (s) {
+ i = strlen(s);
+ j = i + 1 + strlen(o) + 1;
+ s = erealloc(s, j);
+ (void)snprintf(s + i, j, ",%s", o);
+ } else
+ s = estrdup(o);
+ *sp = s;
+}
+
+
+static void
+mangle(char *opts, int *argcp, const char ** volatile *argvp, int *maxargcp)
+{
+ char *p, *s;
+ int argc, maxargc;
+ const char **argv;
+
+ argc = *argcp;
+ argv = *argvp;
+ maxargc = *maxargcp;
+
+ for (s = opts; (p = strsep(&s, ",")) != NULL;) {
+ /* Always leave space for one more argument and the NULL. */
+ if (argc >= maxargc - 3) {
+ maxargc <<= 1;
+ argv = erealloc(argv, maxargc * sizeof(char *));
+ }
+ if (*p != '\0') {
+ if (*p == '-') {
+ argv[argc++] = p;
+ p = strchr(p, '=');
+ if (p) {
+ *p = '\0';
+ argv[argc++] = p+1;
+ }
+ } else {
+ argv[argc++] = "-o";
+ argv[argc++] = p;
+ }
+ }
+ }
+
+ *argcp = argc;
+ *argvp = argv;
+ *maxargcp = maxargc;
+}
+
+static const char *
+getfstype(const char *str)
+{
+ struct diocgattr_arg attr;
+ int fd, i;
+
+ if ((fd = open(str, O_RDONLY)) == -1)
+ err(1, "cannot open `%s'", str);
+
+ strncpy(attr.name, "PART::type", sizeof(attr.name));
+ memset(&attr.value, 0, sizeof(attr.value));
+ attr.len = sizeof(attr.value);
+ if (ioctl(fd, DIOCGATTR, &attr) == -1) {
+ (void) close(fd);
+ return(NULL);
+ }
+ (void) close(fd);
+ for (i = 0; ptype_map[i].ptype != NULL; i++)
+ if (strstr(attr.value.str, ptype_map[i].ptype) != NULL)
+ return (ptype_map[i].name);
+ return (NULL);
+}
+
+
+static void
+usage(void)
+{
+ static const char common[] =
+ "[-Cdfnpvy] [-B | -F] [-T fstype:fsoptions] [-t fstype] [-c fstab]";
+
+ (void)fprintf(stderr, "usage: %s %s [special | node] ...\n",
+ getprogname(), common);
+ exit(1);
+}
diff --git a/sbin/fsck/fsutil.c b/sbin/fsck/fsutil.c
new file mode 100644
index 0000000..935992e
--- /dev/null
+++ b/sbin/fsck/fsutil.c
@@ -0,0 +1,226 @@
+/* $NetBSD: fsutil.c,v 1.15 2006/06/05 16:52:05 christos Exp $ */
+
+/*
+ * Copyright (c) 1990, 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. 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.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: fsutil.c,v 1.15 2006/06/05 16:52:05 christos Exp $");
+#endif /* not lint */
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/mount.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fstab.h>
+#include <paths.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "fsutil.h"
+
+static const char *dev = NULL;
+static int preen = 0;
+
+static void vmsg(int, const char *, va_list) __printflike(2, 0);
+
+void
+setcdevname(const char *cd, int pr)
+{
+ dev = cd;
+ preen = pr;
+}
+
+const char *
+cdevname(void)
+{
+ return dev;
+}
+
+static void
+vmsg(int fatal, const char *fmt, va_list ap)
+{
+ if (!fatal && preen)
+ (void) printf("%s: ", dev);
+
+ (void) vprintf(fmt, ap);
+
+ if (fatal && preen)
+ (void) printf("\n");
+
+ if (fatal && preen) {
+ (void) printf(
+ "%s: UNEXPECTED INCONSISTENCY; RUN %s MANUALLY.\n",
+ dev, getprogname());
+ exit(8);
+ }
+}
+
+/*VARARGS*/
+void
+pfatal(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vmsg(1, fmt, ap);
+ va_end(ap);
+}
+
+/*VARARGS*/
+void
+pwarn(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vmsg(0, fmt, ap);
+ va_end(ap);
+}
+
+void
+perr(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vmsg(1, fmt, ap);
+ va_end(ap);
+}
+
+void
+panic(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vmsg(1, fmt, ap);
+ va_end(ap);
+ exit(8);
+}
+
+const char *
+devcheck(const char *origname)
+{
+ struct stat stslash, stchar;
+
+ if (stat("/", &stslash) < 0) {
+ perr("Can't stat `/'");
+ return (origname);
+ }
+ if (stat(origname, &stchar) < 0) {
+ perr("Can't stat %s\n", origname);
+ return (origname);
+ }
+ if (!S_ISCHR(stchar.st_mode)) {
+ perr("%s is not a char device\n", origname);
+ }
+ return (origname);
+}
+
+/*
+ * Get the mount point information for name.
+ */
+struct statfs *
+getmntpt(const char *name)
+{
+ struct stat devstat, mntdevstat;
+ char device[sizeof(_PATH_DEV) - 1 + MNAMELEN];
+ char *dev_name;
+ struct statfs *mntbuf, *statfsp;
+ int i, mntsize, isdev;
+
+ if (stat(name, &devstat) != 0)
+ return (NULL);
+ if (S_ISCHR(devstat.st_mode) || S_ISBLK(devstat.st_mode))
+ isdev = 1;
+ else
+ isdev = 0;
+ mntsize = getmntinfo(&mntbuf, MNT_NOWAIT);
+ for (i = 0; i < mntsize; i++) {
+ statfsp = &mntbuf[i];
+ dev_name = statfsp->f_mntfromname;
+ if (*dev_name != '/') {
+ strcpy(device, _PATH_DEV);
+ strcat(device, dev_name);
+ strcpy(statfsp->f_mntfromname, device);
+ }
+ if (isdev == 0) {
+ if (strcmp(name, statfsp->f_mntonname))
+ continue;
+ return (statfsp);
+ }
+ if (stat(dev_name, &mntdevstat) == 0 &&
+ mntdevstat.st_rdev == devstat.st_rdev)
+ return (statfsp);
+ }
+ statfsp = NULL;
+ return (statfsp);
+}
+
+
+void *
+emalloc(size_t s)
+{
+ void *p;
+
+ p = malloc(s);
+ if (p == NULL)
+ err(1, "malloc failed");
+ return (p);
+}
+
+
+void *
+erealloc(void *p, size_t s)
+{
+ void *q;
+
+ q = realloc(p, s);
+ if (q == NULL)
+ err(1, "realloc failed");
+ return (q);
+}
+
+
+char *
+estrdup(const char *s)
+{
+ char *p;
+
+ p = strdup(s);
+ if (p == NULL)
+ err(1, "strdup failed");
+ return (p);
+}
diff --git a/sbin/fsck/fsutil.h b/sbin/fsck/fsutil.h
new file mode 100644
index 0000000..013b821
--- /dev/null
+++ b/sbin/fsck/fsutil.h
@@ -0,0 +1,50 @@
+/* $NetBSD: fsutil.h,v 1.114 2009/10/21 01:07:46 snj Exp $ */
+
+/*
+ * Copyright (c) 1996 Christos Zoulas. 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.
+ *
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+void pfatal(const char *, ...) __printflike(1, 2);
+void pwarn(const char *, ...) __printflike(1, 2);
+void perr(const char *, ...) __printflike(1, 2);
+void panic(const char *, ...) __dead2 __printflike(1, 2);
+const char *devcheck(const char *);
+const char *cdevname(void);
+void setcdevname(const char *, int);
+struct statfs *getmntpt(const char *);
+void *emalloc(size_t);
+void *erealloc(void *, size_t);
+char *estrdup(const char *);
+
+#define CHECK_PREEN 0x0001
+#define CHECK_VERBOSE 0x0002
+#define CHECK_DEBUG 0x0004
+#define CHECK_BACKGRD 0x0008
+#define DO_BACKGRD 0x0010
+#define CHECK_CLEAN 0x0020
+
+struct fstab;
+int checkfstab(int, int (*)(struct fstab *),
+ int (*) (const char *, const char *, const char *, const char *, pid_t *));
diff --git a/sbin/fsck/preen.c b/sbin/fsck/preen.c
new file mode 100644
index 0000000..18f2801
--- /dev/null
+++ b/sbin/fsck/preen.c
@@ -0,0 +1,333 @@
+/* $NetBSD: preen.c,v 1.18 1998/07/26 20:02:36 mycroft Exp $ */
+
+/*
+ * Copyright (c) 1990, 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.
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)preen.c 8.5 (Berkeley) 4/28/95";
+#else
+__RCSID("$NetBSD: preen.c,v 1.18 1998/07/26 20:02:36 mycroft Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/queue.h>
+
+#include <err.h>
+#include <ctype.h>
+#include <fstab.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "fsutil.h"
+
+struct partentry {
+ TAILQ_ENTRY(partentry) p_entries;
+ char *p_devname; /* device name */
+ char *p_mntpt; /* mount point */
+ char *p_type; /* file system type */
+};
+
+static TAILQ_HEAD(part, partentry) badh;
+
+struct diskentry {
+ TAILQ_ENTRY(diskentry) d_entries;
+ char *d_name; /* disk base name */
+ TAILQ_HEAD(prt, partentry) d_part; /* list of partitions on disk */
+ int d_pid; /* 0 or pid of fsck proc */
+};
+
+static TAILQ_HEAD(disk, diskentry) diskh;
+
+static int nrun = 0, ndisks = 0;
+
+static struct diskentry *finddisk(const char *);
+static void addpart(const char *, const char *, const char *);
+static int startdisk(struct diskentry *,
+ int (*)(const char *, const char *, const char *, const char *, pid_t *));
+static void printpart(void);
+
+int
+checkfstab(int flags, int (*docheck)(struct fstab *),
+ int (*checkit)(const char *, const char *, const char *, const char *, pid_t *))
+{
+ struct fstab *fs;
+ struct diskentry *d, *nextdisk;
+ struct partentry *p;
+ int ret, pid, retcode, passno, sumstatus, status, nextpass;
+ const char *name;
+
+ TAILQ_INIT(&badh);
+ TAILQ_INIT(&diskh);
+
+ sumstatus = 0;
+
+ nextpass = 0;
+ for (passno = 1; nextpass != INT_MAX; passno = nextpass) {
+ if (flags & CHECK_DEBUG)
+ printf("pass %d\n", passno);
+
+ nextpass = INT_MAX;
+ if (setfsent() == 0) {
+ warnx("Can't open checklist file: %s\n", _PATH_FSTAB);
+ return (8);
+ }
+ while ((fs = getfsent()) != 0) {
+ name = fs->fs_spec;
+ if (fs->fs_passno > passno && fs->fs_passno < nextpass)
+ nextpass = fs->fs_passno;
+
+ if (passno != fs->fs_passno)
+ continue;
+
+ if ((*docheck)(fs) == 0)
+ continue;
+
+ if (flags & CHECK_DEBUG)
+ printf("pass %d, name %s\n", passno, name);
+
+ if ((flags & CHECK_PREEN) == 0 || passno == 1 ||
+ (flags & DO_BACKGRD) != 0) {
+ if (name == NULL) {
+ if (flags & CHECK_PREEN)
+ return 8;
+ else
+ continue;
+ }
+ sumstatus = (*checkit)(fs->fs_vfstype,
+ name, fs->fs_file, NULL, NULL);
+
+ if (sumstatus)
+ return (sumstatus);
+ continue;
+ }
+ if (name == NULL) {
+ (void) fprintf(stderr,
+ "BAD DISK NAME %s\n", fs->fs_spec);
+ sumstatus |= 8;
+ continue;
+ }
+ addpart(fs->fs_vfstype, name, fs->fs_file);
+ }
+
+ if ((flags & CHECK_PREEN) == 0 || passno == 1 ||
+ (flags & DO_BACKGRD) != 0)
+ continue;
+
+ if (flags & CHECK_DEBUG) {
+ printf("Parallel start\n");
+ printpart();
+ }
+
+ TAILQ_FOREACH(nextdisk, &diskh, d_entries) {
+ if ((ret = startdisk(nextdisk, checkit)) != 0)
+ return ret;
+ }
+
+ if (flags & CHECK_DEBUG)
+ printf("Parallel wait\n");
+ while ((pid = wait(&status)) != -1) {
+ TAILQ_FOREACH(d, &diskh, d_entries)
+ if (d->d_pid == pid)
+ break;
+
+ if (d == NULL) {
+ warnx("Unknown pid %d\n", pid);
+ continue;
+ }
+
+ if (WIFEXITED(status))
+ retcode = WEXITSTATUS(status);
+ else
+ retcode = 0;
+
+ p = TAILQ_FIRST(&d->d_part);
+
+ if (flags & (CHECK_DEBUG|CHECK_VERBOSE))
+ (void) printf("done %s: %s (%s) = 0x%x\n",
+ p->p_type, p->p_devname, p->p_mntpt,
+ status);
+
+ if (WIFSIGNALED(status)) {
+ (void) fprintf(stderr,
+ "%s: %s (%s): EXITED WITH SIGNAL %d\n",
+ p->p_type, p->p_devname, p->p_mntpt,
+ WTERMSIG(status));
+ retcode = 8;
+ }
+
+ TAILQ_REMOVE(&d->d_part, p, p_entries);
+
+ if (retcode != 0) {
+ TAILQ_INSERT_TAIL(&badh, p, p_entries);
+ sumstatus |= retcode;
+ } else {
+ free(p->p_type);
+ free(p->p_devname);
+ free(p);
+ }
+ d->d_pid = 0;
+ nrun--;
+
+ if (TAILQ_EMPTY(&d->d_part)) {
+ TAILQ_REMOVE(&diskh, d, d_entries);
+ ndisks--;
+ } else {
+ if ((ret = startdisk(d, checkit)) != 0)
+ return ret;
+ }
+ }
+ if (flags & CHECK_DEBUG) {
+ printf("Parallel end\n");
+ printpart();
+ }
+ }
+
+ if (!(flags & CHECK_PREEN))
+ return 0;
+
+ if (sumstatus) {
+ p = TAILQ_FIRST(&badh);
+ if (p == NULL)
+ return (sumstatus);
+
+ (void) fprintf(stderr,
+ "THE FOLLOWING FILE SYSTEM%s HAD AN %s\n\t",
+ TAILQ_NEXT(p, p_entries) ? "S" : "",
+ "UNEXPECTED INCONSISTENCY:");
+
+ for (; p; p = TAILQ_NEXT(p, p_entries))
+ (void) fprintf(stderr,
+ "%s: %s (%s)%s", p->p_type, p->p_devname,
+ p->p_mntpt, TAILQ_NEXT(p, p_entries) ? ", " : "\n");
+
+ return sumstatus;
+ }
+ (void) endfsent();
+ return (0);
+}
+
+
+static struct diskentry *
+finddisk(const char *name)
+{
+ const char *p;
+ size_t len = 0;
+ struct diskentry *d;
+
+ p = strrchr(name, '/');
+ if (p == NULL)
+ p = name;
+ else
+ p++;
+ for (; *p && !isdigit(*p); p++)
+ continue;
+ for (; *p && isdigit(*p); p++)
+ continue;
+ len = p - name;
+ if (len == 0)
+ len = strlen(name);
+
+ TAILQ_FOREACH(d, &diskh, d_entries)
+ if (strncmp(d->d_name, name, len) == 0 && d->d_name[len] == 0)
+ return d;
+
+ d = emalloc(sizeof(*d));
+ d->d_name = estrdup(name);
+ d->d_name[len] = '\0';
+ TAILQ_INIT(&d->d_part);
+ d->d_pid = 0;
+
+ TAILQ_INSERT_TAIL(&diskh, d, d_entries);
+ ndisks++;
+
+ return d;
+}
+
+
+static void
+printpart(void)
+{
+ struct diskentry *d;
+ struct partentry *p;
+
+ TAILQ_FOREACH(d, &diskh, d_entries) {
+ (void) printf("disk %s: ", d->d_name);
+ TAILQ_FOREACH(p, &d->d_part, p_entries)
+ (void) printf("%s ", p->p_devname);
+ (void) printf("\n");
+ }
+}
+
+
+static void
+addpart(const char *type, const char *dev, const char *mntpt)
+{
+ struct diskentry *d = finddisk(dev);
+ struct partentry *p;
+
+ TAILQ_FOREACH(p, &d->d_part, p_entries)
+ if (strcmp(p->p_devname, dev) == 0) {
+ warnx("%s in fstab more than once!\n", dev);
+ return;
+ }
+
+ p = emalloc(sizeof(*p));
+ p->p_devname = estrdup(dev);
+ p->p_mntpt = estrdup(mntpt);
+ p->p_type = estrdup(type);
+
+ TAILQ_INSERT_TAIL(&d->d_part, p, p_entries);
+}
+
+
+static int
+startdisk(struct diskentry *d, int (*checkit)(const char *, const char *,
+ const char *, const char *, pid_t *))
+{
+ struct partentry *p = TAILQ_FIRST(&d->d_part);
+ int rv;
+
+ while ((rv = (*checkit)(p->p_type, p->p_devname, p->p_mntpt,
+ NULL, &d->d_pid)) != 0 && nrun > 0)
+ sleep(10);
+
+ if (rv == 0)
+ nrun++;
+
+ return rv;
+}
diff --git a/sbin/fsck_ffs/Makefile b/sbin/fsck_ffs/Makefile
new file mode 100644
index 0000000..0275469
--- /dev/null
+++ b/sbin/fsck_ffs/Makefile
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# @(#)Makefile 8.2 (Berkeley) 4/27/95
+
+PROG= fsck_ffs
+LINKS+= ${BINDIR}/fsck_ffs ${BINDIR}/fsck_ufs
+LINKS+= ${BINDIR}/fsck_ffs ${BINDIR}/fsck_4.2bsd
+MAN= fsck_ffs.8
+MLINKS= fsck_ffs.8 fsck_ufs.8 fsck_ffs.8 fsck_4.2bsd.8
+SRCS= dir.c ea.c fsutil.c inode.c main.c pass1.c pass1b.c pass2.c pass3.c \
+ pass4.c pass5.c setup.c suj.c utilities.c gjournal.c getmntopts.c \
+ globs.c
+LIBADD= ufs
+WARNS?= 2
+CFLAGS+= -I${.CURDIR} -I${.CURDIR}/../mount
+
+.PATH: ${.CURDIR}/../../sys/ufs/ffs ${.CURDIR}/../mount
+
+.include <bsd.prog.mk>
diff --git a/sbin/fsck_ffs/dir.c b/sbin/fsck_ffs/dir.c
new file mode 100644
index 0000000..9203000
--- /dev/null
+++ b/sbin/fsck_ffs/dir.c
@@ -0,0 +1,709 @@
+/*
+ * Copyright (c) 1980, 1986, 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.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if 0
+#ifndef lint
+static const char sccsid[] = "@(#)dir.c 8.8 (Berkeley) 4/28/95";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/sysctl.h>
+
+#include <ufs/ufs/dinode.h>
+#include <ufs/ufs/dir.h>
+#include <ufs/ffs/fs.h>
+
+#include <err.h>
+#include <string.h>
+
+#include "fsck.h"
+
+static struct dirtemplate emptydir = {
+ 0, DIRBLKSIZ, DT_UNKNOWN, 0, "",
+ 0, 0, DT_UNKNOWN, 0, ""
+};
+static struct dirtemplate dirhead = {
+ 0, 12, DT_DIR, 1, ".",
+ 0, DIRBLKSIZ - 12, DT_DIR, 2, ".."
+};
+
+static int chgino(struct inodesc *);
+static int dircheck(struct inodesc *, struct direct *);
+static int expanddir(union dinode *dp, char *name);
+static void freedir(ino_t ino, ino_t parent);
+static struct direct *fsck_readdir(struct inodesc *);
+static struct bufarea *getdirblk(ufs2_daddr_t blkno, long size);
+static int lftempname(char *bufp, ino_t ino);
+static int mkentry(struct inodesc *);
+
+/*
+ * Propagate connected state through the tree.
+ */
+void
+propagate(void)
+{
+ struct inoinfo **inpp, *inp;
+ struct inoinfo **inpend;
+ long change;
+
+ inpend = &inpsort[inplast];
+ do {
+ change = 0;
+ for (inpp = inpsort; inpp < inpend; inpp++) {
+ inp = *inpp;
+ if (inp->i_parent == 0)
+ continue;
+ if (inoinfo(inp->i_parent)->ino_state == DFOUND &&
+ INO_IS_DUNFOUND(inp->i_number)) {
+ inoinfo(inp->i_number)->ino_state = DFOUND;
+ change++;
+ }
+ }
+ } while (change > 0);
+}
+
+/*
+ * Scan each entry in a directory block.
+ */
+int
+dirscan(struct inodesc *idesc)
+{
+ struct direct *dp;
+ struct bufarea *bp;
+ u_int dsize, n;
+ long blksiz;
+ char dbuf[DIRBLKSIZ];
+
+ if (idesc->id_type != DATA)
+ errx(EEXIT, "wrong type to dirscan %d", idesc->id_type);
+ if (idesc->id_entryno == 0 &&
+ (idesc->id_filesize & (DIRBLKSIZ - 1)) != 0)
+ idesc->id_filesize = roundup(idesc->id_filesize, DIRBLKSIZ);
+ blksiz = idesc->id_numfrags * sblock.fs_fsize;
+ if (chkrange(idesc->id_blkno, idesc->id_numfrags)) {
+ idesc->id_filesize -= blksiz;
+ return (SKIP);
+ }
+ idesc->id_loc = 0;
+ for (dp = fsck_readdir(idesc); dp != NULL; dp = fsck_readdir(idesc)) {
+ dsize = dp->d_reclen;
+ if (dsize > sizeof(dbuf))
+ dsize = sizeof(dbuf);
+ memmove(dbuf, dp, (size_t)dsize);
+ idesc->id_dirp = (struct direct *)dbuf;
+ if ((n = (*idesc->id_func)(idesc)) & ALTERED) {
+ bp = getdirblk(idesc->id_blkno, blksiz);
+ memmove(bp->b_un.b_buf + idesc->id_loc - dsize, dbuf,
+ (size_t)dsize);
+ dirty(bp);
+ sbdirty();
+ rerun = 1;
+ }
+ if (n & STOP)
+ return (n);
+ }
+ return (idesc->id_filesize > 0 ? KEEPON : STOP);
+}
+
+/*
+ * get next entry in a directory.
+ */
+static struct direct *
+fsck_readdir(struct inodesc *idesc)
+{
+ struct direct *dp, *ndp;
+ struct bufarea *bp;
+ long size, blksiz, fix, dploc;
+
+ blksiz = idesc->id_numfrags * sblock.fs_fsize;
+ bp = getdirblk(idesc->id_blkno, blksiz);
+ if (idesc->id_loc % DIRBLKSIZ == 0 && idesc->id_filesize > 0 &&
+ idesc->id_loc < blksiz) {
+ dp = (struct direct *)(bp->b_un.b_buf + idesc->id_loc);
+ if (dircheck(idesc, dp))
+ goto dpok;
+ if (idesc->id_fix == IGNORE)
+ return (0);
+ fix = dofix(idesc, "DIRECTORY CORRUPTED");
+ bp = getdirblk(idesc->id_blkno, blksiz);
+ dp = (struct direct *)(bp->b_un.b_buf + idesc->id_loc);
+ dp->d_reclen = DIRBLKSIZ;
+ dp->d_ino = 0;
+ dp->d_type = 0;
+ dp->d_namlen = 0;
+ dp->d_name[0] = '\0';
+ if (fix)
+ dirty(bp);
+ idesc->id_loc += DIRBLKSIZ;
+ idesc->id_filesize -= DIRBLKSIZ;
+ return (dp);
+ }
+dpok:
+ if (idesc->id_filesize <= 0 || idesc->id_loc >= blksiz)
+ return NULL;
+ dploc = idesc->id_loc;
+ dp = (struct direct *)(bp->b_un.b_buf + dploc);
+ idesc->id_loc += dp->d_reclen;
+ idesc->id_filesize -= dp->d_reclen;
+ if ((idesc->id_loc % DIRBLKSIZ) == 0)
+ return (dp);
+ ndp = (struct direct *)(bp->b_un.b_buf + idesc->id_loc);
+ if (idesc->id_loc < blksiz && idesc->id_filesize > 0 &&
+ dircheck(idesc, ndp) == 0) {
+ size = DIRBLKSIZ - (idesc->id_loc % DIRBLKSIZ);
+ idesc->id_loc += size;
+ idesc->id_filesize -= size;
+ if (idesc->id_fix == IGNORE)
+ return (0);
+ fix = dofix(idesc, "DIRECTORY CORRUPTED");
+ bp = getdirblk(idesc->id_blkno, blksiz);
+ dp = (struct direct *)(bp->b_un.b_buf + dploc);
+ dp->d_reclen += size;
+ if (fix)
+ dirty(bp);
+ }
+ return (dp);
+}
+
+/*
+ * Verify that a directory entry is valid.
+ * This is a superset of the checks made in the kernel.
+ */
+static int
+dircheck(struct inodesc *idesc, struct direct *dp)
+{
+ size_t size;
+ char *cp;
+ u_char type;
+ u_int8_t namlen;
+ int spaceleft;
+
+ spaceleft = DIRBLKSIZ - (idesc->id_loc % DIRBLKSIZ);
+ if (dp->d_reclen == 0 ||
+ dp->d_reclen > spaceleft ||
+ (dp->d_reclen & 0x3) != 0)
+ goto bad;
+ if (dp->d_ino == 0)
+ return (1);
+ size = DIRSIZ(0, dp);
+ namlen = dp->d_namlen;
+ type = dp->d_type;
+ if (dp->d_reclen < size ||
+ idesc->id_filesize < size ||
+ namlen == 0 ||
+ type > 15)
+ goto bad;
+ for (cp = dp->d_name, size = 0; size < namlen; size++)
+ if (*cp == '\0' || (*cp++ == '/'))
+ goto bad;
+ if (*cp != '\0')
+ goto bad;
+ return (1);
+bad:
+ if (debug)
+ printf("Bad dir: ino %d reclen %d namlen %d type %d name %s\n",
+ dp->d_ino, dp->d_reclen, dp->d_namlen, dp->d_type,
+ dp->d_name);
+ return (0);
+}
+
+void
+direrror(ino_t ino, const char *errmesg)
+{
+
+ fileerror(ino, ino, errmesg);
+}
+
+void
+fileerror(ino_t cwd, ino_t ino, const char *errmesg)
+{
+ union dinode *dp;
+ char pathbuf[MAXPATHLEN + 1];
+
+ pwarn("%s ", errmesg);
+ pinode(ino);
+ printf("\n");
+ getpathname(pathbuf, cwd, ino);
+ if (ino < ROOTINO || ino > maxino) {
+ pfatal("NAME=%s\n", pathbuf);
+ return;
+ }
+ dp = ginode(ino);
+ if (ftypeok(dp))
+ pfatal("%s=%s\n",
+ (DIP(dp, di_mode) & IFMT) == IFDIR ? "DIR" : "FILE",
+ pathbuf);
+ else
+ pfatal("NAME=%s\n", pathbuf);
+}
+
+void
+adjust(struct inodesc *idesc, int lcnt)
+{
+ union dinode *dp;
+ int saveresolved;
+
+ dp = ginode(idesc->id_number);
+ if (DIP(dp, di_nlink) == lcnt) {
+ /*
+ * If we have not hit any unresolved problems, are running
+ * in preen mode, and are on a file system using soft updates,
+ * then just toss any partially allocated files.
+ */
+ if (resolved && (preen || bkgrdflag) && usedsoftdep) {
+ clri(idesc, "UNREF", 1);
+ return;
+ } else {
+ /*
+ * The file system can be marked clean even if
+ * a file is not linked up, but is cleared.
+ * Hence, resolved should not be cleared when
+ * linkup is answered no, but clri is answered yes.
+ */
+ saveresolved = resolved;
+ if (linkup(idesc->id_number, (ino_t)0, NULL) == 0) {
+ resolved = saveresolved;
+ clri(idesc, "UNREF", 0);
+ return;
+ }
+ /*
+ * Account for the new reference created by linkup().
+ */
+ dp = ginode(idesc->id_number);
+ lcnt--;
+ }
+ }
+ if (lcnt != 0) {
+ pwarn("LINK COUNT %s", (lfdir == idesc->id_number) ? lfname :
+ ((DIP(dp, di_mode) & IFMT) == IFDIR ? "DIR" : "FILE"));
+ pinode(idesc->id_number);
+ printf(" COUNT %d SHOULD BE %d",
+ DIP(dp, di_nlink), DIP(dp, di_nlink) - lcnt);
+ if (preen || usedsoftdep) {
+ if (lcnt < 0) {
+ printf("\n");
+ pfatal("LINK COUNT INCREASING");
+ }
+ if (preen)
+ printf(" (ADJUSTED)\n");
+ }
+ if (preen || reply("ADJUST") == 1) {
+ if (bkgrdflag == 0) {
+ DIP_SET(dp, di_nlink, DIP(dp, di_nlink) - lcnt);
+ inodirty();
+ } else {
+ cmd.value = idesc->id_number;
+ cmd.size = -lcnt;
+ if (debug)
+ printf("adjrefcnt ino %ld amt %lld\n",
+ (long)cmd.value,
+ (long long)cmd.size);
+ if (sysctl(adjrefcnt, MIBSIZE, 0, 0,
+ &cmd, sizeof cmd) == -1)
+ rwerror("ADJUST INODE", cmd.value);
+ }
+ }
+ }
+}
+
+static int
+mkentry(struct inodesc *idesc)
+{
+ struct direct *dirp = idesc->id_dirp;
+ struct direct newent;
+ int newlen, oldlen;
+
+ newent.d_namlen = strlen(idesc->id_name);
+ newlen = DIRSIZ(0, &newent);
+ if (dirp->d_ino != 0)
+ oldlen = DIRSIZ(0, dirp);
+ else
+ oldlen = 0;
+ if (dirp->d_reclen - oldlen < newlen)
+ return (KEEPON);
+ newent.d_reclen = dirp->d_reclen - oldlen;
+ dirp->d_reclen = oldlen;
+ dirp = (struct direct *)(((char *)dirp) + oldlen);
+ dirp->d_ino = idesc->id_parent; /* ino to be entered is in id_parent */
+ dirp->d_reclen = newent.d_reclen;
+ dirp->d_type = inoinfo(idesc->id_parent)->ino_type;
+ dirp->d_namlen = newent.d_namlen;
+ memmove(dirp->d_name, idesc->id_name, (size_t)newent.d_namlen + 1);
+ return (ALTERED|STOP);
+}
+
+static int
+chgino(struct inodesc *idesc)
+{
+ struct direct *dirp = idesc->id_dirp;
+
+ if (memcmp(dirp->d_name, idesc->id_name, (int)dirp->d_namlen + 1))
+ return (KEEPON);
+ dirp->d_ino = idesc->id_parent;
+ dirp->d_type = inoinfo(idesc->id_parent)->ino_type;
+ return (ALTERED|STOP);
+}
+
+int
+linkup(ino_t orphan, ino_t parentdir, char *name)
+{
+ union dinode *dp;
+ int lostdir;
+ ino_t oldlfdir;
+ struct inodesc idesc;
+ char tempname[BUFSIZ];
+
+ memset(&idesc, 0, sizeof(struct inodesc));
+ dp = ginode(orphan);
+ lostdir = (DIP(dp, di_mode) & IFMT) == IFDIR;
+ pwarn("UNREF %s ", lostdir ? "DIR" : "FILE");
+ pinode(orphan);
+ if (preen && DIP(dp, di_size) == 0)
+ return (0);
+ if (cursnapshot != 0) {
+ pfatal("FILE LINKUP IN SNAPSHOT");
+ return (0);
+ }
+ if (preen)
+ printf(" (RECONNECTED)\n");
+ else
+ if (reply("RECONNECT") == 0)
+ return (0);
+ if (lfdir == 0) {
+ dp = ginode(ROOTINO);
+ idesc.id_name = strdup(lfname);
+ idesc.id_type = DATA;
+ idesc.id_func = findino;
+ idesc.id_number = ROOTINO;
+ if ((ckinode(dp, &idesc) & FOUND) != 0) {
+ lfdir = idesc.id_parent;
+ } else {
+ pwarn("NO lost+found DIRECTORY");
+ if (preen || reply("CREATE")) {
+ lfdir = allocdir(ROOTINO, (ino_t)0, lfmode);
+ if (lfdir != 0) {
+ if (makeentry(ROOTINO, lfdir, lfname) != 0) {
+ numdirs++;
+ if (preen)
+ printf(" (CREATED)\n");
+ } else {
+ freedir(lfdir, ROOTINO);
+ lfdir = 0;
+ if (preen)
+ printf("\n");
+ }
+ }
+ }
+ }
+ if (lfdir == 0) {
+ pfatal("SORRY. CANNOT CREATE lost+found DIRECTORY");
+ printf("\n\n");
+ return (0);
+ }
+ }
+ dp = ginode(lfdir);
+ if ((DIP(dp, di_mode) & IFMT) != IFDIR) {
+ pfatal("lost+found IS NOT A DIRECTORY");
+ if (reply("REALLOCATE") == 0)
+ return (0);
+ oldlfdir = lfdir;
+ if ((lfdir = allocdir(ROOTINO, (ino_t)0, lfmode)) == 0) {
+ pfatal("SORRY. CANNOT CREATE lost+found DIRECTORY\n\n");
+ return (0);
+ }
+ if ((changeino(ROOTINO, lfname, lfdir) & ALTERED) == 0) {
+ pfatal("SORRY. CANNOT CREATE lost+found DIRECTORY\n\n");
+ return (0);
+ }
+ inodirty();
+ idesc.id_type = ADDR;
+ idesc.id_func = pass4check;
+ idesc.id_number = oldlfdir;
+ adjust(&idesc, inoinfo(oldlfdir)->ino_linkcnt + 1);
+ inoinfo(oldlfdir)->ino_linkcnt = 0;
+ dp = ginode(lfdir);
+ }
+ if (inoinfo(lfdir)->ino_state != DFOUND) {
+ pfatal("SORRY. NO lost+found DIRECTORY\n\n");
+ return (0);
+ }
+ (void)lftempname(tempname, orphan);
+ if (makeentry(lfdir, orphan, (name ? name : tempname)) == 0) {
+ pfatal("SORRY. NO SPACE IN lost+found DIRECTORY");
+ printf("\n\n");
+ return (0);
+ }
+ inoinfo(orphan)->ino_linkcnt--;
+ if (lostdir) {
+ if ((changeino(orphan, "..", lfdir) & ALTERED) == 0 &&
+ parentdir != (ino_t)-1)
+ (void)makeentry(orphan, lfdir, "..");
+ dp = ginode(lfdir);
+ DIP_SET(dp, di_nlink, DIP(dp, di_nlink) + 1);
+ inodirty();
+ inoinfo(lfdir)->ino_linkcnt++;
+ pwarn("DIR I=%lu CONNECTED. ", (u_long)orphan);
+ if (parentdir != (ino_t)-1) {
+ printf("PARENT WAS I=%lu\n", (u_long)parentdir);
+ /*
+ * The parent directory, because of the ordering
+ * guarantees, has had the link count incremented
+ * for the child, but no entry was made. This
+ * fixes the parent link count so that fsck does
+ * not need to be rerun.
+ */
+ inoinfo(parentdir)->ino_linkcnt++;
+ }
+ if (preen == 0)
+ printf("\n");
+ }
+ return (1);
+}
+
+/*
+ * fix an entry in a directory.
+ */
+int
+changeino(ino_t dir, const char *name, ino_t newnum)
+{
+ struct inodesc idesc;
+
+ memset(&idesc, 0, sizeof(struct inodesc));
+ idesc.id_type = DATA;
+ idesc.id_func = chgino;
+ idesc.id_number = dir;
+ idesc.id_fix = DONTKNOW;
+ idesc.id_name = strdup(name);
+ idesc.id_parent = newnum; /* new value for name */
+ return (ckinode(ginode(dir), &idesc));
+}
+
+/*
+ * make an entry in a directory
+ */
+int
+makeentry(ino_t parent, ino_t ino, const char *name)
+{
+ union dinode *dp;
+ struct inodesc idesc;
+ char pathbuf[MAXPATHLEN + 1];
+
+ if (parent < ROOTINO || parent >= maxino ||
+ ino < ROOTINO || ino >= maxino)
+ return (0);
+ memset(&idesc, 0, sizeof(struct inodesc));
+ idesc.id_type = DATA;
+ idesc.id_func = mkentry;
+ idesc.id_number = parent;
+ idesc.id_parent = ino; /* this is the inode to enter */
+ idesc.id_fix = DONTKNOW;
+ idesc.id_name = strdup(name);
+ dp = ginode(parent);
+ if (DIP(dp, di_size) % DIRBLKSIZ) {
+ DIP_SET(dp, di_size, roundup(DIP(dp, di_size), DIRBLKSIZ));
+ inodirty();
+ }
+ if ((ckinode(dp, &idesc) & ALTERED) != 0)
+ return (1);
+ getpathname(pathbuf, parent, parent);
+ dp = ginode(parent);
+ if (expanddir(dp, pathbuf) == 0)
+ return (0);
+ return (ckinode(dp, &idesc) & ALTERED);
+}
+
+/*
+ * Attempt to expand the size of a directory
+ */
+static int
+expanddir(union dinode *dp, char *name)
+{
+ ufs2_daddr_t lastbn, newblk;
+ struct bufarea *bp;
+ char *cp, firstblk[DIRBLKSIZ];
+
+ lastbn = lblkno(&sblock, DIP(dp, di_size));
+ if (lastbn >= NDADDR - 1 || DIP(dp, di_db[lastbn]) == 0 ||
+ DIP(dp, di_size) == 0)
+ return (0);
+ if ((newblk = allocblk(sblock.fs_frag)) == 0)
+ return (0);
+ DIP_SET(dp, di_db[lastbn + 1], DIP(dp, di_db[lastbn]));
+ DIP_SET(dp, di_db[lastbn], newblk);
+ DIP_SET(dp, di_size, DIP(dp, di_size) + sblock.fs_bsize);
+ DIP_SET(dp, di_blocks, DIP(dp, di_blocks) + btodb(sblock.fs_bsize));
+ bp = getdirblk(DIP(dp, di_db[lastbn + 1]),
+ sblksize(&sblock, DIP(dp, di_size), lastbn + 1));
+ if (bp->b_errs)
+ goto bad;
+ memmove(firstblk, bp->b_un.b_buf, DIRBLKSIZ);
+ bp = getdirblk(newblk, sblock.fs_bsize);
+ if (bp->b_errs)
+ goto bad;
+ memmove(bp->b_un.b_buf, firstblk, DIRBLKSIZ);
+ for (cp = &bp->b_un.b_buf[DIRBLKSIZ];
+ cp < &bp->b_un.b_buf[sblock.fs_bsize];
+ cp += DIRBLKSIZ)
+ memmove(cp, &emptydir, sizeof emptydir);
+ dirty(bp);
+ bp = getdirblk(DIP(dp, di_db[lastbn + 1]),
+ sblksize(&sblock, DIP(dp, di_size), lastbn + 1));
+ if (bp->b_errs)
+ goto bad;
+ memmove(bp->b_un.b_buf, &emptydir, sizeof emptydir);
+ pwarn("NO SPACE LEFT IN %s", name);
+ if (preen)
+ printf(" (EXPANDED)\n");
+ else if (reply("EXPAND") == 0)
+ goto bad;
+ dirty(bp);
+ inodirty();
+ return (1);
+bad:
+ DIP_SET(dp, di_db[lastbn], DIP(dp, di_db[lastbn + 1]));
+ DIP_SET(dp, di_db[lastbn + 1], 0);
+ DIP_SET(dp, di_size, DIP(dp, di_size) - sblock.fs_bsize);
+ DIP_SET(dp, di_blocks, DIP(dp, di_blocks) - btodb(sblock.fs_bsize));
+ freeblk(newblk, sblock.fs_frag);
+ return (0);
+}
+
+/*
+ * allocate a new directory
+ */
+ino_t
+allocdir(ino_t parent, ino_t request, int mode)
+{
+ ino_t ino;
+ char *cp;
+ union dinode *dp;
+ struct bufarea *bp;
+ struct inoinfo *inp;
+ struct dirtemplate *dirp;
+
+ ino = allocino(request, IFDIR|mode);
+ dirp = &dirhead;
+ dirp->dot_ino = ino;
+ dirp->dotdot_ino = parent;
+ dp = ginode(ino);
+ bp = getdirblk(DIP(dp, di_db[0]), sblock.fs_fsize);
+ if (bp->b_errs) {
+ freeino(ino);
+ return (0);
+ }
+ memmove(bp->b_un.b_buf, dirp, sizeof(struct dirtemplate));
+ for (cp = &bp->b_un.b_buf[DIRBLKSIZ];
+ cp < &bp->b_un.b_buf[sblock.fs_fsize];
+ cp += DIRBLKSIZ)
+ memmove(cp, &emptydir, sizeof emptydir);
+ dirty(bp);
+ DIP_SET(dp, di_nlink, 2);
+ inodirty();
+ if (ino == ROOTINO) {
+ inoinfo(ino)->ino_linkcnt = DIP(dp, di_nlink);
+ cacheino(dp, ino);
+ return(ino);
+ }
+ if (!INO_IS_DVALID(parent)) {
+ freeino(ino);
+ return (0);
+ }
+ cacheino(dp, ino);
+ inp = getinoinfo(ino);
+ inp->i_parent = parent;
+ inp->i_dotdot = parent;
+ inoinfo(ino)->ino_state = inoinfo(parent)->ino_state;
+ if (inoinfo(ino)->ino_state == DSTATE) {
+ inoinfo(ino)->ino_linkcnt = DIP(dp, di_nlink);
+ inoinfo(parent)->ino_linkcnt++;
+ }
+ dp = ginode(parent);
+ DIP_SET(dp, di_nlink, DIP(dp, di_nlink) + 1);
+ inodirty();
+ return (ino);
+}
+
+/*
+ * free a directory inode
+ */
+static void
+freedir(ino_t ino, ino_t parent)
+{
+ union dinode *dp;
+
+ if (ino != parent) {
+ dp = ginode(parent);
+ DIP_SET(dp, di_nlink, DIP(dp, di_nlink) - 1);
+ inodirty();
+ }
+ freeino(ino);
+}
+
+/*
+ * generate a temporary name for the lost+found directory.
+ */
+static int
+lftempname(char *bufp, ino_t ino)
+{
+ ino_t in;
+ char *cp;
+ int namlen;
+
+ cp = bufp + 2;
+ for (in = maxino; in > 0; in /= 10)
+ cp++;
+ *--cp = 0;
+ namlen = cp - bufp;
+ in = ino;
+ while (cp > bufp) {
+ *--cp = (in % 10) + '0';
+ in /= 10;
+ }
+ *cp = '#';
+ return (namlen);
+}
+
+/*
+ * Get a directory block.
+ * Insure that it is held until another is requested.
+ */
+static struct bufarea *
+getdirblk(ufs2_daddr_t blkno, long size)
+{
+
+ if (pdirbp != 0)
+ pdirbp->b_flags &= ~B_INUSE;
+ pdirbp = getdatablk(blkno, size, BT_DIRDATA);
+ return (pdirbp);
+}
diff --git a/sbin/fsck_ffs/ea.c b/sbin/fsck_ffs/ea.c
new file mode 100644
index 0000000..e9c3fce
--- /dev/null
+++ b/sbin/fsck_ffs/ea.c
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2002 Poul-Henning Kamp
+ * Copyright (c) 2002 Networks Associates Technology, Inc.
+ * All rights reserved.
+ *
+ * This software was developed for the FreeBSD Project by Poul-Henning Kamp
+ * and NAI Labs, the Security Research Division of Network Associates, Inc.
+ * under DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), as part of the
+ * DARPA CHATS research program.
+ *
+ * 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 names of the authors may not 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/time.h>
+#include <sys/stdint.h>
+
+#include <ufs/ufs/dinode.h>
+#include <ufs/ufs/dir.h>
+#include <ufs/ffs/fs.h>
+
+#include <err.h>
+#include <string.h>
+
+#include "fsck.h"
+
+/*
+ * Scan each entry in an ea block.
+ */
+int
+eascan(struct inodesc *idesc, struct ufs2_dinode *dp)
+{
+#if 1
+ return (0);
+#else
+ struct bufarea *bp;
+ u_int dsize, n;
+ u_char *cp;
+ long blksiz;
+ char dbuf[DIRBLKSIZ];
+
+ printf("Inode %ju extsize %ju\n",
+ (intmax_t)idesc->id_number, (uintmax_t)dp->di_extsize);
+ if (dp->di_extsize == 0)
+ return 0;
+ if (dp->di_extsize <= sblock.fs_fsize)
+ blksiz = sblock.fs_fsize;
+ else
+ blksiz = sblock.fs_bsize;
+ printf("blksiz = %ju\n", (intmax_t)blksiz);
+ bp = getdatablk(dp->di_extb[0], blksiz, BT_EXTATTR);
+ cp = (u_char *)bp->b_un.b_buf;
+ for (n = 0; n < blksiz; n++) {
+ printf("%02x", cp[n]);
+ if ((n & 31) == 31)
+ printf("\n");
+ }
+ return (STOP);
+#endif
+}
diff --git a/sbin/fsck_ffs/fsck.h b/sbin/fsck_ffs/fsck.h
new file mode 100644
index 0000000..c0ec651
--- /dev/null
+++ b/sbin/fsck_ffs/fsck.h
@@ -0,0 +1,474 @@
+/*
+ * Copyright (c) 2002 Networks Associates Technology, Inc.
+ * All rights reserved.
+ *
+ * This software was developed for the FreeBSD Project by Marshall
+ * Kirk McKusick and Network Associates Laboratories, the Security
+ * Research Division of Network Associates, Inc. under DARPA/SPAWAR
+ * contract N66001-01-C-8035 ("CBOSS"), as part of the DARPA CHATS
+ * research program.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * Copyright (c) 1980, 1986, 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.
+ * 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.
+ *
+ * @(#)fsck.h 8.4 (Berkeley) 5/9/95
+ * $FreeBSD$
+ */
+
+#ifndef _FSCK_H_
+#define _FSCK_H_
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <sys/queue.h>
+
+#define MAXDUP 10 /* limit on dup blks (per inode) */
+#define MAXBAD 10 /* limit on bad blks (per inode) */
+#define MINBUFS 10 /* minimum number of buffers required */
+#define MAXBUFS 40 /* maximum space to allocate to buffers */
+#define INOBUFSIZE 64*1024 /* size of buffer to read inodes in pass1 */
+#define ZEROBUFSIZE (dev_bsize * 128) /* size of zero buffer used by -Z */
+
+union dinode {
+ struct ufs1_dinode dp1;
+ struct ufs2_dinode dp2;
+};
+#define DIP(dp, field) \
+ ((sblock.fs_magic == FS_UFS1_MAGIC) ? \
+ (dp)->dp1.field : (dp)->dp2.field)
+
+#define DIP_SET(dp, field, val) do { \
+ if (sblock.fs_magic == FS_UFS1_MAGIC) \
+ (dp)->dp1.field = (val); \
+ else \
+ (dp)->dp2.field = (val); \
+ } while (0)
+
+/*
+ * Each inode on the file system is described by the following structure.
+ * The linkcnt is initially set to the value in the inode. Each time it
+ * is found during the descent in passes 2, 3, and 4 the count is
+ * decremented. Any inodes whose count is non-zero after pass 4 needs to
+ * have its link count adjusted by the value remaining in ino_linkcnt.
+ */
+struct inostat {
+ char ino_state; /* state of inode, see below */
+ char ino_type; /* type of inode */
+ short ino_linkcnt; /* number of links not found */
+};
+/*
+ * Inode states.
+ */
+#define USTATE 0x1 /* inode not allocated */
+#define FSTATE 0x2 /* inode is file */
+#define FZLINK 0x3 /* inode is file with a link count of zero */
+#define DSTATE 0x4 /* inode is directory */
+#define DZLINK 0x5 /* inode is directory with a zero link count */
+#define DFOUND 0x6 /* directory found during descent */
+/* 0x7 UNUSED - see S_IS_DVALID() definition */
+#define DCLEAR 0x8 /* directory is to be cleared */
+#define FCLEAR 0x9 /* file is to be cleared */
+/* DUNFOUND === (state == DSTATE || state == DZLINK) */
+#define S_IS_DUNFOUND(state) (((state) & ~0x1) == DSTATE)
+/* DVALID === (state == DSTATE || state == DZLINK || state == DFOUND) */
+#define S_IS_DVALID(state) (((state) & ~0x3) == DSTATE)
+#define INO_IS_DUNFOUND(ino) S_IS_DUNFOUND(inoinfo(ino)->ino_state)
+#define INO_IS_DVALID(ino) S_IS_DVALID(inoinfo(ino)->ino_state)
+/*
+ * Inode state information is contained on per cylinder group lists
+ * which are described by the following structure.
+ */
+struct inostatlist {
+ long il_numalloced; /* number of inodes allocated in this cg */
+ struct inostat *il_stat;/* inostat info for this cylinder group */
+} *inostathead;
+
+/*
+ * buffer cache structure.
+ */
+struct bufarea {
+ TAILQ_ENTRY(bufarea) b_list; /* buffer list */
+ ufs2_daddr_t b_bno;
+ int b_size;
+ int b_errs;
+ int b_flags;
+ int b_type;
+ union {
+ char *b_buf; /* buffer space */
+ ufs1_daddr_t *b_indir1; /* UFS1 indirect block */
+ ufs2_daddr_t *b_indir2; /* UFS2 indirect block */
+ struct fs *b_fs; /* super block */
+ struct cg *b_cg; /* cylinder group */
+ struct ufs1_dinode *b_dinode1; /* UFS1 inode block */
+ struct ufs2_dinode *b_dinode2; /* UFS2 inode block */
+ } b_un;
+ char b_dirty;
+};
+
+#define IBLK(bp, i) \
+ ((sblock.fs_magic == FS_UFS1_MAGIC) ? \
+ (bp)->b_un.b_indir1[i] : (bp)->b_un.b_indir2[i])
+
+#define IBLK_SET(bp, i, val) do { \
+ if (sblock.fs_magic == FS_UFS1_MAGIC) \
+ (bp)->b_un.b_indir1[i] = (val); \
+ else \
+ (bp)->b_un.b_indir2[i] = (val); \
+ } while (0)
+
+/*
+ * Buffer flags
+ */
+#define B_INUSE 0x00000001 /* Buffer is in use */
+/*
+ * Type of data in buffer
+ */
+#define BT_UNKNOWN 0 /* Buffer holds a superblock */
+#define BT_SUPERBLK 1 /* Buffer holds a superblock */
+#define BT_CYLGRP 2 /* Buffer holds a cylinder group map */
+#define BT_LEVEL1 3 /* Buffer holds single level indirect */
+#define BT_LEVEL2 4 /* Buffer holds double level indirect */
+#define BT_LEVEL3 5 /* Buffer holds triple level indirect */
+#define BT_EXTATTR 6 /* Buffer holds external attribute data */
+#define BT_INODES 7 /* Buffer holds external attribute data */
+#define BT_DIRDATA 8 /* Buffer holds directory data */
+#define BT_DATA 9 /* Buffer holds user data */
+#define BT_NUMBUFTYPES 10
+#define BT_NAMES { \
+ "unknown", \
+ "Superblock", \
+ "Cylinder Group", \
+ "Single Level Indirect", \
+ "Double Level Indirect", \
+ "Triple Level Indirect", \
+ "External Attribute", \
+ "Inode Block", \
+ "Directory Contents", \
+ "User Data" }
+extern long readcnt[BT_NUMBUFTYPES];
+extern long totalreadcnt[BT_NUMBUFTYPES];
+extern struct timespec readtime[BT_NUMBUFTYPES];
+extern struct timespec totalreadtime[BT_NUMBUFTYPES];
+extern struct timespec startprog;
+
+extern struct bufarea sblk; /* file system superblock */
+extern struct bufarea *pdirbp; /* current directory contents */
+extern struct bufarea *pbp; /* current inode block */
+
+#define dirty(bp) do { \
+ if (fswritefd < 0) \
+ pfatal("SETTING DIRTY FLAG IN READ_ONLY MODE\n"); \
+ else \
+ (bp)->b_dirty = 1; \
+} while (0)
+#define initbarea(bp, type) do { \
+ (bp)->b_dirty = 0; \
+ (bp)->b_bno = (ufs2_daddr_t)-1; \
+ (bp)->b_flags = 0; \
+ (bp)->b_type = type; \
+} while (0)
+
+#define sbdirty() dirty(&sblk)
+#define sblock (*sblk.b_un.b_fs)
+
+enum fixstate {DONTKNOW, NOFIX, FIX, IGNORE};
+extern ino_t cursnapshot;
+
+struct inodesc {
+ enum fixstate id_fix; /* policy on fixing errors */
+ int (*id_func)(struct inodesc *);
+ /* function to be applied to blocks of inode */
+ ino_t id_number; /* inode number described */
+ ino_t id_parent; /* for DATA nodes, their parent */
+ ufs_lbn_t id_lbn; /* logical block number of current block */
+ ufs2_daddr_t id_blkno; /* current block number being examined */
+ int id_numfrags; /* number of frags contained in block */
+ off_t id_filesize; /* for DATA nodes, the size of the directory */
+ ufs2_daddr_t id_entryno;/* for DATA nodes, current entry number */
+ int id_loc; /* for DATA nodes, current location in dir */
+ struct direct *id_dirp; /* for DATA nodes, ptr to current entry */
+ char *id_name; /* for DATA nodes, name to find or enter */
+ char id_type; /* type of descriptor, DATA or ADDR */
+};
+/* file types */
+#define DATA 1 /* a directory */
+#define SNAP 2 /* a snapshot */
+#define ADDR 3 /* anything but a directory or a snapshot */
+
+/*
+ * Linked list of duplicate blocks.
+ *
+ * The list is composed of two parts. The first part of the
+ * list (from duplist through the node pointed to by muldup)
+ * contains a single copy of each duplicate block that has been
+ * found. The second part of the list (from muldup to the end)
+ * contains duplicate blocks that have been found more than once.
+ * To check if a block has been found as a duplicate it is only
+ * necessary to search from duplist through muldup. To find the
+ * total number of times that a block has been found as a duplicate
+ * the entire list must be searched for occurrences of the block
+ * in question. The following diagram shows a sample list where
+ * w (found twice), x (found once), y (found three times), and z
+ * (found once) are duplicate block numbers:
+ *
+ * w -> y -> x -> z -> y -> w -> y
+ * ^ ^
+ * | |
+ * duplist muldup
+ */
+struct dups {
+ struct dups *next;
+ ufs2_daddr_t dup;
+};
+struct dups *duplist; /* head of dup list */
+struct dups *muldup; /* end of unique duplicate dup block numbers */
+
+/*
+ * Inode cache data structures.
+ */
+struct inoinfo {
+ struct inoinfo *i_nexthash; /* next entry in hash chain */
+ ino_t i_number; /* inode number of this entry */
+ ino_t i_parent; /* inode number of parent */
+ ino_t i_dotdot; /* inode number of `..' */
+ size_t i_isize; /* size of inode */
+ u_int i_numblks; /* size of block array in bytes */
+ ufs2_daddr_t i_blks[1]; /* actually longer */
+} **inphead, **inpsort;
+extern long numdirs, dirhash, listmax, inplast;
+extern long countdirs; /* number of directories we actually found */
+
+#define MIBSIZE 3 /* size of fsck sysctl MIBs */
+extern int adjrefcnt[MIBSIZE]; /* MIB command to adjust inode reference cnt */
+extern int adjblkcnt[MIBSIZE]; /* MIB command to adjust inode block count */
+extern int adjndir[MIBSIZE]; /* MIB command to adjust number of directories */
+extern int adjnbfree[MIBSIZE]; /* MIB command to adjust number of free blocks */
+extern int adjnifree[MIBSIZE]; /* MIB command to adjust number of free inodes */
+extern int adjnffree[MIBSIZE]; /* MIB command to adjust number of free frags */
+extern int adjnumclusters[MIBSIZE]; /* MIB command to adjust number of free clusters */
+extern int freefiles[MIBSIZE]; /* MIB command to free a set of files */
+extern int freedirs[MIBSIZE]; /* MIB command to free a set of directories */
+extern int freeblks[MIBSIZE]; /* MIB command to free a set of data blocks */
+extern struct fsck_cmd cmd; /* sysctl file system update commands */
+extern char snapname[BUFSIZ]; /* when doing snapshots, the name of the file */
+extern char *cdevname; /* name of device being checked */
+extern long dev_bsize; /* computed value of DEV_BSIZE */
+extern long secsize; /* actual disk sector size */
+extern u_int real_dev_bsize; /* actual disk sector size, not overriden */
+extern char nflag; /* assume a no response */
+extern char yflag; /* assume a yes response */
+extern int bkgrdflag; /* use a snapshot to run on an active system */
+extern int bflag; /* location of alternate super block */
+extern int debug; /* output debugging info */
+extern int Eflag; /* delete empty data blocks */
+extern int Zflag; /* zero empty data blocks */
+extern int inoopt; /* trim out unused inodes */
+extern char ckclean; /* only do work if not cleanly unmounted */
+extern int cvtlevel; /* convert to newer file system format */
+extern int bkgrdcheck; /* determine if background check is possible */
+extern int bkgrdsumadj; /* whether the kernel have ability to adjust superblock summary */
+extern char usedsoftdep; /* just fix soft dependency inconsistencies */
+extern char preen; /* just fix normal inconsistencies */
+extern char rerun; /* rerun fsck. Only used in non-preen mode */
+extern int returntosingle; /* 1 => return to single user mode on exit */
+extern char resolved; /* cleared if unresolved changes => not clean */
+extern char havesb; /* superblock has been read */
+extern char skipclean; /* skip clean file systems if preening */
+extern int fsmodified; /* 1 => write done to file system */
+extern int fsreadfd; /* file descriptor for reading file system */
+extern int fswritefd; /* file descriptor for writing file system */
+extern int surrender; /* Give up if reads fail */
+extern int wantrestart; /* Restart fsck on early termination */
+
+extern ufs2_daddr_t maxfsblock; /* number of blocks in the file system */
+extern char *blockmap; /* ptr to primary blk allocation map */
+extern ino_t maxino; /* number of inodes in file system */
+
+extern ino_t lfdir; /* lost & found directory inode number */
+extern const char *lfname; /* lost & found directory name */
+extern int lfmode; /* lost & found directory creation mode */
+
+extern ufs2_daddr_t n_blks; /* number of blocks in use */
+extern ino_t n_files; /* number of files in use */
+
+extern volatile sig_atomic_t got_siginfo; /* received a SIGINFO */
+extern volatile sig_atomic_t got_sigalarm; /* received a SIGALRM */
+
+#define clearinode(dp) \
+ if (sblock.fs_magic == FS_UFS1_MAGIC) { \
+ (dp)->dp1 = ufs1_zino; \
+ } else { \
+ (dp)->dp2 = ufs2_zino; \
+ }
+extern struct ufs1_dinode ufs1_zino;
+extern struct ufs2_dinode ufs2_zino;
+
+#define setbmap(blkno) setbit(blockmap, blkno)
+#define testbmap(blkno) isset(blockmap, blkno)
+#define clrbmap(blkno) clrbit(blockmap, blkno)
+
+#define STOP 0x01
+#define SKIP 0x02
+#define KEEPON 0x04
+#define ALTERED 0x08
+#define FOUND 0x10
+
+#define EEXIT 8 /* Standard error exit. */
+#define ERESTART -1
+
+int flushentry(void);
+/*
+ * Wrapper for malloc() that flushes the cylinder group cache to try
+ * to get space.
+ */
+static inline void*
+Malloc(size_t size)
+{
+ void *retval;
+
+ while ((retval = malloc(size)) == NULL)
+ if (flushentry() == 0)
+ break;
+ return (retval);
+}
+
+/*
+ * Wrapper for calloc() that flushes the cylinder group cache to try
+ * to get space.
+ */
+static inline void*
+Calloc(size_t cnt, size_t size)
+{
+ void *retval;
+
+ while ((retval = calloc(cnt, size)) == NULL)
+ if (flushentry() == 0)
+ break;
+ return (retval);
+}
+
+struct fstab;
+
+
+void adjust(struct inodesc *, int lcnt);
+ufs2_daddr_t allocblk(long frags);
+ino_t allocdir(ino_t parent, ino_t request, int mode);
+ino_t allocino(ino_t request, int type);
+void blkerror(ino_t ino, const char *type, ufs2_daddr_t blk);
+char *blockcheck(char *name);
+int blread(int fd, char *buf, ufs2_daddr_t blk, long size);
+void bufinit(void);
+void blwrite(int fd, char *buf, ufs2_daddr_t blk, ssize_t size);
+void blerase(int fd, ufs2_daddr_t blk, long size);
+void blzero(int fd, ufs2_daddr_t blk, long size);
+void cacheino(union dinode *dp, ino_t inumber);
+void catch(int);
+void catchquit(int);
+int changeino(ino_t dir, const char *name, ino_t newnum);
+int check_cgmagic(int cg, struct bufarea *cgbp);
+int chkrange(ufs2_daddr_t blk, int cnt);
+void ckfini(int markclean);
+int ckinode(union dinode *dp, struct inodesc *);
+void clri(struct inodesc *, const char *type, int flag);
+int clearentry(struct inodesc *);
+void direrror(ino_t ino, const char *errmesg);
+int dirscan(struct inodesc *);
+int dofix(struct inodesc *, const char *msg);
+int eascan(struct inodesc *, struct ufs2_dinode *dp);
+void fileerror(ino_t cwd, ino_t ino, const char *errmesg);
+void finalIOstats(void);
+int findino(struct inodesc *);
+int findname(struct inodesc *);
+void flush(int fd, struct bufarea *bp);
+void freeblk(ufs2_daddr_t blkno, long frags);
+void freeino(ino_t ino);
+void freeinodebuf(void);
+void fsutilinit(void);
+int ftypeok(union dinode *dp);
+void getblk(struct bufarea *bp, ufs2_daddr_t blk, long size);
+struct bufarea *cgget(int cg);
+struct bufarea *getdatablk(ufs2_daddr_t blkno, long size, int type);
+struct inoinfo *getinoinfo(ino_t inumber);
+union dinode *getnextinode(ino_t inumber, int rebuildcg);
+void getpathname(char *namebuf, ino_t curdir, ino_t ino);
+union dinode *ginode(ino_t inumber);
+void infohandler(int sig);
+void alarmhandler(int sig);
+void inocleanup(void);
+void inodirty(void);
+struct inostat *inoinfo(ino_t inum);
+void IOstats(char *what);
+int linkup(ino_t orphan, ino_t parentdir, char *name);
+int makeentry(ino_t parent, ino_t ino, const char *name);
+void panic(const char *fmt, ...) __printflike(1, 2);
+void pass1(void);
+void pass1b(void);
+int pass1check(struct inodesc *);
+void pass2(void);
+void pass3(void);
+void pass4(void);
+int pass4check(struct inodesc *);
+void pass5(void);
+void pfatal(const char *fmt, ...) __printflike(1, 2);
+void pinode(ino_t ino);
+void propagate(void);
+void pwarn(const char *fmt, ...) __printflike(1, 2);
+int readsb(int listerr);
+int reply(const char *question);
+void rwerror(const char *mesg, ufs2_daddr_t blk);
+void sblock_init(void);
+void setinodebuf(ino_t);
+int setup(char *dev);
+void gjournal_check(const char *filesys);
+int suj_check(const char *filesys);
+void update_maps(struct cg *, struct cg*, int);
+void fsckinit(void);
+
+#endif /* !_FSCK_H_ */
diff --git a/sbin/fsck_ffs/fsck_ffs.8 b/sbin/fsck_ffs/fsck_ffs.8
new file mode 100644
index 0000000..828df27
--- /dev/null
+++ b/sbin/fsck_ffs/fsck_ffs.8
@@ -0,0 +1,395 @@
+.\"
+.\" Copyright (c) 1980, 1989, 1991, 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.
+.\" 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.
+.\"
+.\" @(#)fsck.8 8.4 (Berkeley) 5/9/95
+.\" $FreeBSD$
+.\"
+.Dd July 30, 2013
+.Dt FSCK_FFS 8
+.Os
+.Sh NAME
+.Nm fsck_ffs ,
+.Nm fsck_ufs
+.Nd file system consistency check and interactive repair
+.Sh SYNOPSIS
+.Nm
+.Op Fl BEFfnpRryZ
+.Op Fl b Ar block
+.Op Fl c Ar level
+.Op Fl m Ar mode
+.Ar filesystem
+.Ar ...
+.Sh DESCRIPTION
+The specified disk partitions and/or file systems are checked.
+In "preen" or "check clean" mode the clean flag of each file system's
+superblock is examined and only those file systems that are not marked clean
+are checked.
+File systems are marked clean when they are unmounted,
+when they have been mounted read-only, or when
+.Nm
+runs on them successfully.
+If the
+.Fl f
+option is specified, the file systems
+will be checked regardless of the state of their clean flag.
+.Pp
+The kernel takes care that only a restricted class of innocuous file system
+inconsistencies can happen unless hardware or software failures intervene.
+These are limited to the following:
+.Pp
+.Bl -item -compact -offset indent
+.It
+Unreferenced inodes
+.It
+Link counts in inodes too large
+.It
+Missing blocks in the free map
+.It
+Blocks in the free map also in files
+.It
+Counts in the super-block wrong
+.El
+.Pp
+These are the only inconsistencies that
+.Nm
+with the
+.Fl p
+option will correct; if it encounters other inconsistencies, it exits
+with an abnormal return status and an automatic reboot will then fail.
+For each corrected inconsistency one or more lines will be printed
+identifying the file system on which the correction will take place,
+and the nature of the correction.
+After successfully correcting a file system,
+.Nm
+will print the number of files on that file system,
+the number of used and free blocks,
+and the percentage of fragmentation.
+.Pp
+If sent a
+.Dv QUIT
+signal,
+.Nm
+will finish the file system checks, then exit with an abnormal
+return status that causes an automatic reboot to fail.
+This is useful when you want to finish the file system checks during an
+automatic reboot,
+but do not want the machine to come up multiuser after the checks complete.
+.Pp
+If
+.Nm
+receives a
+.Dv SIGINFO
+(see the
+.Dq status
+argument for
+.Xr stty 1 )
+signal, a line will be written to the standard output indicating
+the name of the device currently being checked, the current phase
+number and phase-specific progress information.
+.Pp
+Without the
+.Fl p
+option,
+.Nm
+audits and interactively repairs inconsistent conditions for file systems.
+If the file system is inconsistent the operator is prompted for concurrence
+before each correction is attempted.
+It should be noted that some of the corrective actions which are not
+correctable under the
+.Fl p
+option will result in some loss of data.
+The amount and severity of data lost may be determined from the diagnostic
+output.
+The default action for each consistency correction
+is to wait for the operator to respond
+.Li yes
+or
+.Li no .
+If the operator does not have write permission on the file system
+.Nm
+will default to a
+.Fl n
+action.
+.Pp
+The following flags are interpreted by
+.Nm :
+.Bl -tag -width indent
+.It Fl B
+A check is done on the specified and possibly active file system.
+The set of corrections that can be done is limited to those done
+when running in preen mode (see the
+.Fl p
+flag).
+If unexpected errors are found,
+the file system is marked as needing a foreground check and
+.Nm
+exits without attempting any further cleaning.
+.It Fl E
+Clear unallocated blocks, notifying the underlying device that they
+are not used and that their contents may be discarded.
+This is useful for filesystems which have been mounted on systems
+without TRIM support, or with TRIM support disabled, as well as
+filesystems which have been copied from one device to another.
+.Pp
+See the
+.Fl E
+and
+.Fl t
+flags of
+.Xr newfs 8 ,
+and
+the
+.Fl t
+flag of
+.Xr tunefs 8 .
+.It Fl F
+Determine whether the file system needs to be cleaned immediately
+in foreground, or if its cleaning can be deferred to background.
+To be eligible for background cleaning it must have been running
+with soft updates, not have been marked as needing a foreground check,
+and be mounted and writable when the background check is to be done.
+If these conditions are met, then
+.Nm
+exits with a zero exit status.
+Otherwise it exits with a non-zero exit status.
+If the file system is clean,
+it will exit with a non-zero exit status so that the clean status
+of the file system can be verified and reported during the foreground
+checks.
+Note that when invoked with the
+.Fl F
+flag, no cleanups are done.
+The only thing that
+.Nm
+does is to determine whether a foreground or background
+check is needed and exit with an appropriate status code.
+.It Fl b
+Use the block specified immediately after the flag as
+the super block for the file system.
+An alternate super block is usually located at block 32 for UFS1,
+and block 160 for UFS2.
+.Pp
+See the
+.Fl N
+flag of
+.Xr newfs 8 .
+.It Fl C
+Check if file system was dismounted cleanly.
+If so, skip file system checks (like "preen").
+However, if the file system was not cleanly dismounted, do full checks,
+as if
+.Nm
+was invoked without
+.Fl C .
+.It Fl c
+Convert the file system to the specified level.
+Note that the level of a file system can only be raised.
+There are currently four levels defined:
+.Bl -tag -width indent
+.It 0
+The file system is in the old (static table) format.
+.It 1
+The file system is in the new (dynamic table) format.
+.It 2
+The file system supports 32-bit uid's and gid's,
+short symbolic links are stored in the inode,
+and directories have an added field showing the file type.
+.It 3
+If maxcontig is greater than one,
+build the free segment maps to aid in finding contiguous sets of blocks.
+If maxcontig is equal to one, delete any existing segment maps.
+.El
+.Pp
+In interactive mode,
+.Nm
+will list the conversion to be made
+and ask whether the conversion should be done.
+If a negative answer is given,
+no further operations are done on the file system.
+In preen mode,
+the conversion is listed and done if
+possible without user interaction.
+Conversion in preen mode is best used when all the file systems
+are being converted at once.
+The format of a file system can be determined from the
+first line of output from
+.Xr dumpfs 8 .
+.Pp
+This option implies the
+.Fl f
+flag.
+.It Fl f
+Force
+.Nm
+to check
+.Sq clean
+file systems when preening.
+.It Fl m
+Use the mode specified in octal immediately after the flag as the
+permission bits to use when creating the
+.Pa lost+found
+directory rather than the default 1777.
+In particular, systems that do not wish to have lost files accessible
+by all users on the system should use a more restrictive
+set of permissions such as 700.
+.It Fl n
+Assume a no response to all questions asked by
+.Nm
+except for
+.Ql CONTINUE? ,
+which is assumed to be affirmative;
+do not open the file system for writing.
+.It Fl p
+Preen file systems (see above).
+.It Fl R
+Instruct fsck_ffs to restart itself if it encounters certain errors that
+warrant another run. It will limit itself to a maximum of 10 restarts
+in a given run in order to avoid an endless loop with extremely corrupted
+filesystems.
+.It Fl r
+Free up excess unused inodes.
+Decreasing the number of preallocated inodes reduces the
+running time of future runs of
+.Nm
+and frees up space that can allocated to files.
+The
+.Fl r
+option is ignored when running in preen mode.
+.It Fl S
+Surrender on error.
+With this flag enabled, a hard error returned on disk i/o will cause
+.Nm
+to abort instead of continuing on and possibly tripping over more i/o errors.
+.It Fl y
+Assume a yes response to all questions asked by
+.Nm ;
+this should be used with great caution as this is a free license
+to continue after essentially unlimited trouble has been encountered.
+.It Fl Z
+Similar to
+.Fl E ,
+but overwrites unused blocks with zeroes.
+If both
+.Fl E
+and
+.Fl Z
+are specified, blocks are first zeroed and then erased.
+.El
+.Pp
+Inconsistencies checked are as follows:
+.Pp
+.Bl -enum -compact
+.It
+Blocks claimed by more than one inode or the free map.
+.It
+Blocks claimed by an inode outside the range of the file system.
+.It
+Incorrect link counts.
+.It
+Size checks:
+.Bl -item -offset indent -compact
+.It
+Directory size not a multiple of DIRBLKSIZ.
+.It
+Partially truncated file.
+.El
+.It
+Bad inode format.
+.It
+Blocks not accounted for anywhere.
+.It
+Directory checks:
+.Bl -item -offset indent -compact
+.It
+File pointing to unallocated inode.
+.It
+Inode number out of range.
+.It
+Directories with unallocated blocks (holes).
+.It
+Dot or dot-dot not the first two entries of a directory
+or having the wrong inode number.
+.El
+.It
+Super Block checks:
+.Bl -item -offset indent -compact
+.It
+More blocks for inodes than there are in the file system.
+.It
+Bad free block map format.
+.It
+Total free block and/or free inode count incorrect.
+.El
+.El
+.Pp
+Orphaned files and directories (allocated but unreferenced) are,
+with the operator's concurrence, reconnected by
+placing them in the
+.Pa lost+found
+directory.
+The name assigned is the inode number.
+If the
+.Pa lost+found
+directory does not exist, it is created.
+If there is insufficient space its size is increased.
+.Pp
+The full foreground
+.Nm
+checks for many more problems that may occur after an
+unrecoverable disk write error.
+Thus, it is recommended that you perform foreground
+.Nm
+on your systems periodically and whenever you encounter
+unrecoverable disk write errors or file-system\-related panics.
+.Sh FILES
+.Bl -tag -width /etc/fstab -compact
+.It Pa /etc/fstab
+contains default list of file systems to check.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Pp
+If the option
+.Fl F
+is used,
+.Nm
+exits 7 if the file system is clean.
+.Sh DIAGNOSTICS
+The diagnostics produced by
+.Nm
+are fully enumerated and explained in Appendix A of
+.Rs
+.%T "Fsck \- The UNIX File System Check Program"
+.Re
+.Sh SEE ALSO
+.Xr fs 5 ,
+.Xr fstab 5 ,
+.Xr fsck 8 ,
+.Xr fsdb 8 ,
+.Xr newfs 8 ,
+.Xr reboot 8
diff --git a/sbin/fsck_ffs/fsutil.c b/sbin/fsck_ffs/fsutil.c
new file mode 100644
index 0000000..bc80e2f
--- /dev/null
+++ b/sbin/fsck_ffs/fsutil.c
@@ -0,0 +1,1046 @@
+/*
+ * Copyright (c) 1980, 1986, 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.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if 0
+#ifndef lint
+static const char sccsid[] = "@(#)utilities.c 8.6 (Berkeley) 5/19/95";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/sysctl.h>
+#include <sys/disk.h>
+#include <sys/disklabel.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+
+#include <ufs/ufs/dinode.h>
+#include <ufs/ufs/dir.h>
+#include <ufs/ffs/fs.h>
+
+#include <err.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+#include <fstab.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "fsck.h"
+
+static void slowio_start(void);
+static void slowio_end(void);
+static void printIOstats(void);
+
+static long diskreads, totaldiskreads, totalreads; /* Disk cache statistics */
+static struct timespec startpass, finishpass;
+struct timeval slowio_starttime;
+int slowio_delay_usec = 10000; /* Initial IO delay for background fsck */
+int slowio_pollcnt;
+static struct bufarea cgblk; /* backup buffer for cylinder group blocks */
+static TAILQ_HEAD(buflist, bufarea) bufhead; /* head of buffer cache list */
+static int numbufs; /* size of buffer cache */
+static char *buftype[BT_NUMBUFTYPES] = BT_NAMES;
+static struct bufarea *cgbufs; /* header for cylinder group cache */
+static int flushtries; /* number of tries to reclaim memory */
+
+void
+fsutilinit(void)
+{
+ diskreads = totaldiskreads = totalreads = 0;
+ bzero(&startpass, sizeof(struct timespec));
+ bzero(&finishpass, sizeof(struct timespec));
+ bzero(&slowio_starttime, sizeof(struct timeval));
+ slowio_delay_usec = 10000;
+ slowio_pollcnt = 0;
+ bzero(&cgblk, sizeof(struct bufarea));
+ TAILQ_INIT(&bufhead);
+ numbufs = 0;
+ /* buftype ? */
+ cgbufs = NULL;
+ flushtries = 0;
+}
+
+int
+ftypeok(union dinode *dp)
+{
+ switch (DIP(dp, di_mode) & IFMT) {
+
+ case IFDIR:
+ case IFREG:
+ case IFBLK:
+ case IFCHR:
+ case IFLNK:
+ case IFSOCK:
+ case IFIFO:
+ return (1);
+
+ default:
+ if (debug)
+ printf("bad file type 0%o\n", DIP(dp, di_mode));
+ return (0);
+ }
+}
+
+int
+reply(const char *question)
+{
+ int persevere;
+ char c;
+
+ if (preen)
+ pfatal("INTERNAL ERROR: GOT TO reply()");
+ persevere = !strcmp(question, "CONTINUE");
+ printf("\n");
+ if (!persevere && (nflag || (fswritefd < 0 && bkgrdflag == 0))) {
+ printf("%s? no\n\n", question);
+ resolved = 0;
+ return (0);
+ }
+ if (yflag || (persevere && nflag)) {
+ printf("%s? yes\n\n", question);
+ return (1);
+ }
+ do {
+ printf("%s? [yn] ", question);
+ (void) fflush(stdout);
+ c = getc(stdin);
+ while (c != '\n' && getc(stdin) != '\n') {
+ if (feof(stdin)) {
+ resolved = 0;
+ return (0);
+ }
+ }
+ } while (c != 'y' && c != 'Y' && c != 'n' && c != 'N');
+ printf("\n");
+ if (c == 'y' || c == 'Y')
+ return (1);
+ resolved = 0;
+ return (0);
+}
+
+/*
+ * Look up state information for an inode.
+ */
+struct inostat *
+inoinfo(ino_t inum)
+{
+ static struct inostat unallocated = { USTATE, 0, 0 };
+ struct inostatlist *ilp;
+ int iloff;
+
+ if (inum > maxino)
+ errx(EEXIT, "inoinfo: inumber %ju out of range",
+ (uintmax_t)inum);
+ ilp = &inostathead[inum / sblock.fs_ipg];
+ iloff = inum % sblock.fs_ipg;
+ if (iloff >= ilp->il_numalloced)
+ return (&unallocated);
+ return (&ilp->il_stat[iloff]);
+}
+
+/*
+ * Malloc buffers and set up cache.
+ */
+void
+bufinit(void)
+{
+ struct bufarea *bp;
+ long bufcnt, i;
+ char *bufp;
+
+ pbp = pdirbp = (struct bufarea *)0;
+ bufp = Malloc((unsigned int)sblock.fs_bsize);
+ if (bufp == 0)
+ errx(EEXIT, "cannot allocate buffer pool");
+ cgblk.b_un.b_buf = bufp;
+ initbarea(&cgblk, BT_CYLGRP);
+ TAILQ_INIT(&bufhead);
+ bufcnt = MAXBUFS;
+ if (bufcnt < MINBUFS)
+ bufcnt = MINBUFS;
+ for (i = 0; i < bufcnt; i++) {
+ bp = (struct bufarea *)Malloc(sizeof(struct bufarea));
+ bufp = Malloc((unsigned int)sblock.fs_bsize);
+ if (bp == NULL || bufp == NULL) {
+ if (i >= MINBUFS)
+ break;
+ errx(EEXIT, "cannot allocate buffer pool");
+ }
+ bp->b_un.b_buf = bufp;
+ TAILQ_INSERT_HEAD(&bufhead, bp, b_list);
+ initbarea(bp, BT_UNKNOWN);
+ }
+ numbufs = i; /* save number of buffers */
+ for (i = 0; i < BT_NUMBUFTYPES; i++) {
+ readtime[i].tv_sec = totalreadtime[i].tv_sec = 0;
+ readtime[i].tv_nsec = totalreadtime[i].tv_nsec = 0;
+ readcnt[i] = totalreadcnt[i] = 0;
+ }
+}
+
+/*
+ * Manage cylinder group buffers.
+ */
+static struct bufarea *cgbufs; /* header for cylinder group cache */
+static int flushtries; /* number of tries to reclaim memory */
+
+struct bufarea *
+cgget(int cg)
+{
+ struct bufarea *cgbp;
+ struct cg *cgp;
+
+ if (cgbufs == NULL) {
+ cgbufs = calloc(sblock.fs_ncg, sizeof(struct bufarea));
+ if (cgbufs == NULL)
+ errx(EEXIT, "cannot allocate cylinder group buffers");
+ }
+ cgbp = &cgbufs[cg];
+ if (cgbp->b_un.b_cg != NULL)
+ return (cgbp);
+ cgp = NULL;
+ if (flushtries == 0)
+ cgp = malloc((unsigned int)sblock.fs_cgsize);
+ if (cgp == NULL) {
+ getblk(&cgblk, cgtod(&sblock, cg), sblock.fs_cgsize);
+ return (&cgblk);
+ }
+ cgbp->b_un.b_cg = cgp;
+ initbarea(cgbp, BT_CYLGRP);
+ getblk(cgbp, cgtod(&sblock, cg), sblock.fs_cgsize);
+ return (cgbp);
+}
+
+/*
+ * Attempt to flush a cylinder group cache entry.
+ * Return whether the flush was successful.
+ */
+int
+flushentry(void)
+{
+ struct bufarea *cgbp;
+
+ if (flushtries == sblock.fs_ncg || cgbufs == NULL)
+ return (0);
+ cgbp = &cgbufs[flushtries++];
+ if (cgbp->b_un.b_cg == NULL)
+ return (0);
+ flush(fswritefd, cgbp);
+ free(cgbp->b_un.b_buf);
+ cgbp->b_un.b_buf = NULL;
+ return (1);
+}
+
+/*
+ * Manage a cache of directory blocks.
+ */
+struct bufarea *
+getdatablk(ufs2_daddr_t blkno, long size, int type)
+{
+ struct bufarea *bp;
+
+ TAILQ_FOREACH(bp, &bufhead, b_list)
+ if (bp->b_bno == fsbtodb(&sblock, blkno))
+ goto foundit;
+ TAILQ_FOREACH_REVERSE(bp, &bufhead, buflist, b_list)
+ if ((bp->b_flags & B_INUSE) == 0)
+ break;
+ if (bp == NULL)
+ errx(EEXIT, "deadlocked buffer pool");
+ bp->b_type = type;
+ getblk(bp, blkno, size);
+ /* fall through */
+foundit:
+ if (debug && bp->b_type != type)
+ printf("Buffer type changed from %s to %s\n",
+ buftype[bp->b_type], buftype[type]);
+ TAILQ_REMOVE(&bufhead, bp, b_list);
+ TAILQ_INSERT_HEAD(&bufhead, bp, b_list);
+ bp->b_flags |= B_INUSE;
+ return (bp);
+}
+
+/*
+ * Timespec operations (from <sys/time.h>).
+ */
+#define timespecsub(vvp, uvp) \
+ do { \
+ (vvp)->tv_sec -= (uvp)->tv_sec; \
+ (vvp)->tv_nsec -= (uvp)->tv_nsec; \
+ if ((vvp)->tv_nsec < 0) { \
+ (vvp)->tv_sec--; \
+ (vvp)->tv_nsec += 1000000000; \
+ } \
+ } while (0)
+#define timespecadd(vvp, uvp) \
+ do { \
+ (vvp)->tv_sec += (uvp)->tv_sec; \
+ (vvp)->tv_nsec += (uvp)->tv_nsec; \
+ if ((vvp)->tv_nsec >= 1000000000) { \
+ (vvp)->tv_sec++; \
+ (vvp)->tv_nsec -= 1000000000; \
+ } \
+ } while (0)
+
+void
+getblk(struct bufarea *bp, ufs2_daddr_t blk, long size)
+{
+ ufs2_daddr_t dblk;
+ struct timespec start, finish;
+
+ dblk = fsbtodb(&sblock, blk);
+ if (bp->b_bno == dblk) {
+ totalreads++;
+ } else {
+ flush(fswritefd, bp);
+ if (debug) {
+ readcnt[bp->b_type]++;
+ clock_gettime(CLOCK_REALTIME_PRECISE, &start);
+ }
+ bp->b_errs = blread(fsreadfd, bp->b_un.b_buf, dblk, size);
+ if (debug) {
+ clock_gettime(CLOCK_REALTIME_PRECISE, &finish);
+ timespecsub(&finish, &start);
+ timespecadd(&readtime[bp->b_type], &finish);
+ }
+ bp->b_bno = dblk;
+ bp->b_size = size;
+ }
+}
+
+void
+flush(int fd, struct bufarea *bp)
+{
+ int i, j;
+
+ if (!bp->b_dirty)
+ return;
+ bp->b_dirty = 0;
+ if (fswritefd < 0) {
+ pfatal("WRITING IN READ_ONLY MODE.\n");
+ return;
+ }
+ if (bp->b_errs != 0)
+ pfatal("WRITING %sZERO'ED BLOCK %lld TO DISK\n",
+ (bp->b_errs == bp->b_size / dev_bsize) ? "" : "PARTIALLY ",
+ (long long)bp->b_bno);
+ bp->b_errs = 0;
+ blwrite(fd, bp->b_un.b_buf, bp->b_bno, bp->b_size);
+ if (bp != &sblk)
+ return;
+ for (i = 0, j = 0; i < sblock.fs_cssize; i += sblock.fs_bsize, j++) {
+ blwrite(fswritefd, (char *)sblock.fs_csp + i,
+ fsbtodb(&sblock, sblock.fs_csaddr + j * sblock.fs_frag),
+ sblock.fs_cssize - i < sblock.fs_bsize ?
+ sblock.fs_cssize - i : sblock.fs_bsize);
+ }
+}
+
+void
+rwerror(const char *mesg, ufs2_daddr_t blk)
+{
+
+ if (bkgrdcheck)
+ exit(EEXIT);
+ if (preen == 0)
+ printf("\n");
+ pfatal("CANNOT %s: %ld", mesg, (long)blk);
+ if (reply("CONTINUE") == 0)
+ exit(EEXIT);
+}
+
+void
+ckfini(int markclean)
+{
+ struct bufarea *bp, *nbp;
+ int ofsmodified, cnt;
+
+ if (bkgrdflag) {
+ unlink(snapname);
+ if ((!(sblock.fs_flags & FS_UNCLEAN)) != markclean) {
+ cmd.value = FS_UNCLEAN;
+ cmd.size = markclean ? -1 : 1;
+ if (sysctlbyname("vfs.ffs.setflags", 0, 0,
+ &cmd, sizeof cmd) == -1)
+ rwerror("SET FILE SYSTEM FLAGS", FS_UNCLEAN);
+ if (!preen) {
+ printf("\n***** FILE SYSTEM MARKED %s *****\n",
+ markclean ? "CLEAN" : "DIRTY");
+ if (!markclean)
+ rerun = 1;
+ }
+ } else if (!preen && !markclean) {
+ printf("\n***** FILE SYSTEM STILL DIRTY *****\n");
+ rerun = 1;
+ }
+ }
+ if (debug && totalreads > 0)
+ printf("cache with %d buffers missed %ld of %ld (%d%%)\n",
+ numbufs, totaldiskreads, totalreads,
+ (int)(totaldiskreads * 100 / totalreads));
+ if (fswritefd < 0) {
+ (void)close(fsreadfd);
+ return;
+ }
+ flush(fswritefd, &sblk);
+ if (havesb && cursnapshot == 0 && sblock.fs_magic == FS_UFS2_MAGIC &&
+ sblk.b_bno != sblock.fs_sblockloc / dev_bsize &&
+ !preen && reply("UPDATE STANDARD SUPERBLOCK")) {
+ sblk.b_bno = sblock.fs_sblockloc / dev_bsize;
+ sbdirty();
+ flush(fswritefd, &sblk);
+ }
+ flush(fswritefd, &cgblk);
+ free(cgblk.b_un.b_buf);
+ cnt = 0;
+ TAILQ_FOREACH_REVERSE_SAFE(bp, &bufhead, buflist, b_list, nbp) {
+ TAILQ_REMOVE(&bufhead, bp, b_list);
+ cnt++;
+ flush(fswritefd, bp);
+ free(bp->b_un.b_buf);
+ free((char *)bp);
+ }
+ if (numbufs != cnt)
+ errx(EEXIT, "panic: lost %d buffers", numbufs - cnt);
+ if (cgbufs != NULL) {
+ for (cnt = 0; cnt < sblock.fs_ncg; cnt++) {
+ if (cgbufs[cnt].b_un.b_cg == NULL)
+ continue;
+ flush(fswritefd, &cgbufs[cnt]);
+ free(cgbufs[cnt].b_un.b_cg);
+ }
+ free(cgbufs);
+ }
+ pbp = pdirbp = (struct bufarea *)0;
+ if (cursnapshot == 0 && sblock.fs_clean != markclean) {
+ if ((sblock.fs_clean = markclean) != 0) {
+ sblock.fs_flags &= ~(FS_UNCLEAN | FS_NEEDSFSCK);
+ sblock.fs_pendingblocks = 0;
+ sblock.fs_pendinginodes = 0;
+ }
+ sbdirty();
+ ofsmodified = fsmodified;
+ flush(fswritefd, &sblk);
+ fsmodified = ofsmodified;
+ if (!preen) {
+ printf("\n***** FILE SYSTEM MARKED %s *****\n",
+ markclean ? "CLEAN" : "DIRTY");
+ if (!markclean)
+ rerun = 1;
+ }
+ } else if (!preen) {
+ if (markclean) {
+ printf("\n***** FILE SYSTEM IS CLEAN *****\n");
+ } else {
+ printf("\n***** FILE SYSTEM STILL DIRTY *****\n");
+ rerun = 1;
+ }
+ }
+ (void)close(fsreadfd);
+ (void)close(fswritefd);
+}
+
+/*
+ * Print out I/O statistics.
+ */
+void
+IOstats(char *what)
+{
+ int i;
+
+ if (debug == 0)
+ return;
+ if (diskreads == 0) {
+ printf("%s: no I/O\n\n", what);
+ return;
+ }
+ if (startpass.tv_sec == 0)
+ startpass = startprog;
+ printf("%s: I/O statistics\n", what);
+ printIOstats();
+ totaldiskreads += diskreads;
+ diskreads = 0;
+ for (i = 0; i < BT_NUMBUFTYPES; i++) {
+ timespecadd(&totalreadtime[i], &readtime[i]);
+ totalreadcnt[i] += readcnt[i];
+ readtime[i].tv_sec = readtime[i].tv_nsec = 0;
+ readcnt[i] = 0;
+ }
+ clock_gettime(CLOCK_REALTIME_PRECISE, &startpass);
+}
+
+void
+finalIOstats(void)
+{
+ int i;
+
+ if (debug == 0)
+ return;
+ printf("Final I/O statistics\n");
+ totaldiskreads += diskreads;
+ diskreads = totaldiskreads;
+ startpass = startprog;
+ for (i = 0; i < BT_NUMBUFTYPES; i++) {
+ timespecadd(&totalreadtime[i], &readtime[i]);
+ totalreadcnt[i] += readcnt[i];
+ readtime[i] = totalreadtime[i];
+ readcnt[i] = totalreadcnt[i];
+ }
+ printIOstats();
+}
+
+static void printIOstats(void)
+{
+ long long msec, totalmsec;
+ int i;
+
+ clock_gettime(CLOCK_REALTIME_PRECISE, &finishpass);
+ timespecsub(&finishpass, &startpass);
+ printf("Running time: %jd.%03ld sec\n",
+ (intmax_t)finishpass.tv_sec, finishpass.tv_nsec / 1000000);
+ printf("buffer reads by type:\n");
+ for (totalmsec = 0, i = 0; i < BT_NUMBUFTYPES; i++)
+ totalmsec += readtime[i].tv_sec * 1000 +
+ readtime[i].tv_nsec / 1000000;
+ if (totalmsec == 0)
+ totalmsec = 1;
+ for (i = 0; i < BT_NUMBUFTYPES; i++) {
+ if (readcnt[i] == 0)
+ continue;
+ msec =
+ readtime[i].tv_sec * 1000 + readtime[i].tv_nsec / 1000000;
+ printf("%21s:%8ld %2ld.%ld%% %4jd.%03ld sec %2lld.%lld%%\n",
+ buftype[i], readcnt[i], readcnt[i] * 100 / diskreads,
+ (readcnt[i] * 1000 / diskreads) % 10,
+ (intmax_t)readtime[i].tv_sec, readtime[i].tv_nsec / 1000000,
+ msec * 100 / totalmsec, (msec * 1000 / totalmsec) % 10);
+ }
+ printf("\n");
+}
+
+int
+blread(int fd, char *buf, ufs2_daddr_t blk, long size)
+{
+ char *cp;
+ int i, errs;
+ off_t offset;
+
+ offset = blk;
+ offset *= dev_bsize;
+ if (bkgrdflag)
+ slowio_start();
+ totalreads++;
+ diskreads++;
+ if (lseek(fd, offset, 0) < 0)
+ rwerror("SEEK BLK", blk);
+ else if (read(fd, buf, (int)size) == size) {
+ if (bkgrdflag)
+ slowio_end();
+ return (0);
+ }
+
+ /*
+ * This is handled specially here instead of in rwerror because
+ * rwerror is used for all sorts of errors, not just true read/write
+ * errors. It should be refactored and fixed.
+ */
+ if (surrender) {
+ pfatal("CANNOT READ_BLK: %ld", (long)blk);
+ errx(EEXIT, "ABORTING DUE TO READ ERRORS");
+ } else
+ rwerror("READ BLK", blk);
+
+ if (lseek(fd, offset, 0) < 0)
+ rwerror("SEEK BLK", blk);
+ errs = 0;
+ memset(buf, 0, (size_t)size);
+ printf("THE FOLLOWING DISK SECTORS COULD NOT BE READ:");
+ for (cp = buf, i = 0; i < size; i += secsize, cp += secsize) {
+ if (read(fd, cp, (int)secsize) != secsize) {
+ (void)lseek(fd, offset + i + secsize, 0);
+ if (secsize != dev_bsize && dev_bsize != 1)
+ printf(" %jd (%jd),",
+ (intmax_t)(blk * dev_bsize + i) / secsize,
+ (intmax_t)blk + i / dev_bsize);
+ else
+ printf(" %jd,", (intmax_t)blk + i / dev_bsize);
+ errs++;
+ }
+ }
+ printf("\n");
+ if (errs)
+ resolved = 0;
+ return (errs);
+}
+
+void
+blwrite(int fd, char *buf, ufs2_daddr_t blk, ssize_t size)
+{
+ int i;
+ char *cp;
+ off_t offset;
+
+ if (fd < 0)
+ return;
+ offset = blk;
+ offset *= dev_bsize;
+ if (lseek(fd, offset, 0) < 0)
+ rwerror("SEEK BLK", blk);
+ else if (write(fd, buf, size) == size) {
+ fsmodified = 1;
+ return;
+ }
+ resolved = 0;
+ rwerror("WRITE BLK", blk);
+ if (lseek(fd, offset, 0) < 0)
+ rwerror("SEEK BLK", blk);
+ printf("THE FOLLOWING SECTORS COULD NOT BE WRITTEN:");
+ for (cp = buf, i = 0; i < size; i += dev_bsize, cp += dev_bsize)
+ if (write(fd, cp, dev_bsize) != dev_bsize) {
+ (void)lseek(fd, offset + i + dev_bsize, 0);
+ printf(" %jd,", (intmax_t)blk + i / dev_bsize);
+ }
+ printf("\n");
+ return;
+}
+
+void
+blerase(int fd, ufs2_daddr_t blk, long size)
+{
+ off_t ioarg[2];
+
+ if (fd < 0)
+ return;
+ ioarg[0] = blk * dev_bsize;
+ ioarg[1] = size;
+ ioctl(fd, DIOCGDELETE, ioarg);
+ /* we don't really care if we succeed or not */
+ return;
+}
+
+/*
+ * Fill a contiguous region with all-zeroes. Note ZEROBUFSIZE is by
+ * definition a multiple of dev_bsize.
+ */
+void
+blzero(int fd, ufs2_daddr_t blk, long size)
+{
+ static char *zero;
+ off_t offset, len;
+
+ if (fd < 0)
+ return;
+ if (zero == NULL) {
+ zero = calloc(ZEROBUFSIZE, 1);
+ if (zero == NULL)
+ errx(EEXIT, "cannot allocate buffer pool");
+ }
+ offset = blk * dev_bsize;
+ if (lseek(fd, offset, 0) < 0)
+ rwerror("SEEK BLK", blk);
+ while (size > 0) {
+ len = size > ZEROBUFSIZE ? ZEROBUFSIZE : size;
+ if (write(fd, zero, len) != len)
+ rwerror("WRITE BLK", blk);
+ blk += len / dev_bsize;
+ size -= len;
+ }
+}
+
+/*
+ * Verify cylinder group's magic number and other parameters. If the
+ * test fails, offer an option to rebuild the whole cylinder group.
+ */
+int
+check_cgmagic(int cg, struct bufarea *cgbp)
+{
+ struct cg *cgp = cgbp->b_un.b_cg;
+
+ /*
+ * Extended cylinder group checks.
+ */
+ if (cg_chkmagic(cgp) &&
+ ((sblock.fs_magic == FS_UFS1_MAGIC &&
+ cgp->cg_old_niblk == sblock.fs_ipg &&
+ cgp->cg_ndblk <= sblock.fs_fpg &&
+ cgp->cg_old_ncyl <= sblock.fs_old_cpg) ||
+ (sblock.fs_magic == FS_UFS2_MAGIC &&
+ cgp->cg_niblk == sblock.fs_ipg &&
+ cgp->cg_ndblk <= sblock.fs_fpg &&
+ cgp->cg_initediblk <= sblock.fs_ipg))) {
+ return (1);
+ }
+ pfatal("CYLINDER GROUP %d: BAD MAGIC NUMBER", cg);
+ if (!reply("REBUILD CYLINDER GROUP")) {
+ printf("YOU WILL NEED TO RERUN FSCK.\n");
+ rerun = 1;
+ return (1);
+ }
+ /*
+ * Zero out the cylinder group and then initialize critical fields.
+ * Bit maps and summaries will be recalculated by later passes.
+ */
+ memset(cgp, 0, (size_t)sblock.fs_cgsize);
+ cgp->cg_magic = CG_MAGIC;
+ cgp->cg_cgx = cg;
+ cgp->cg_niblk = sblock.fs_ipg;
+ cgp->cg_initediblk = sblock.fs_ipg < 2 * INOPB(&sblock) ?
+ sblock.fs_ipg : 2 * INOPB(&sblock);
+ if (cgbase(&sblock, cg) + sblock.fs_fpg < sblock.fs_size)
+ cgp->cg_ndblk = sblock.fs_fpg;
+ else
+ cgp->cg_ndblk = sblock.fs_size - cgbase(&sblock, cg);
+ cgp->cg_iusedoff = &cgp->cg_space[0] - (u_char *)(&cgp->cg_firstfield);
+ if (sblock.fs_magic == FS_UFS1_MAGIC) {
+ cgp->cg_niblk = 0;
+ cgp->cg_initediblk = 0;
+ cgp->cg_old_ncyl = sblock.fs_old_cpg;
+ cgp->cg_old_niblk = sblock.fs_ipg;
+ cgp->cg_old_btotoff = cgp->cg_iusedoff;
+ cgp->cg_old_boff = cgp->cg_old_btotoff +
+ sblock.fs_old_cpg * sizeof(int32_t);
+ cgp->cg_iusedoff = cgp->cg_old_boff +
+ sblock.fs_old_cpg * sizeof(u_int16_t);
+ }
+ cgp->cg_freeoff = cgp->cg_iusedoff + howmany(sblock.fs_ipg, CHAR_BIT);
+ cgp->cg_nextfreeoff = cgp->cg_freeoff + howmany(sblock.fs_fpg,CHAR_BIT);
+ if (sblock.fs_contigsumsize > 0) {
+ cgp->cg_nclusterblks = cgp->cg_ndblk / sblock.fs_frag;
+ cgp->cg_clustersumoff =
+ roundup(cgp->cg_nextfreeoff, sizeof(u_int32_t));
+ cgp->cg_clustersumoff -= sizeof(u_int32_t);
+ cgp->cg_clusteroff = cgp->cg_clustersumoff +
+ (sblock.fs_contigsumsize + 1) * sizeof(u_int32_t);
+ cgp->cg_nextfreeoff = cgp->cg_clusteroff +
+ howmany(fragstoblks(&sblock, sblock.fs_fpg), CHAR_BIT);
+ }
+ dirty(cgbp);
+ return (0);
+}
+
+/*
+ * allocate a data block with the specified number of fragments
+ */
+ufs2_daddr_t
+allocblk(long frags)
+{
+ int i, j, k, cg, baseblk;
+ struct bufarea *cgbp;
+ struct cg *cgp;
+
+ if (frags <= 0 || frags > sblock.fs_frag)
+ return (0);
+ for (i = 0; i < maxfsblock - sblock.fs_frag; i += sblock.fs_frag) {
+ for (j = 0; j <= sblock.fs_frag - frags; j++) {
+ if (testbmap(i + j))
+ continue;
+ for (k = 1; k < frags; k++)
+ if (testbmap(i + j + k))
+ break;
+ if (k < frags) {
+ j += k;
+ continue;
+ }
+ cg = dtog(&sblock, i + j);
+ cgbp = cgget(cg);
+ cgp = cgbp->b_un.b_cg;
+ if (!check_cgmagic(cg, cgbp))
+ return (0);
+ baseblk = dtogd(&sblock, i + j);
+ for (k = 0; k < frags; k++) {
+ setbmap(i + j + k);
+ clrbit(cg_blksfree(cgp), baseblk + k);
+ }
+ n_blks += frags;
+ if (frags == sblock.fs_frag)
+ cgp->cg_cs.cs_nbfree--;
+ else
+ cgp->cg_cs.cs_nffree -= frags;
+ dirty(cgbp);
+ return (i + j);
+ }
+ }
+ return (0);
+}
+
+/*
+ * Free a previously allocated block
+ */
+void
+freeblk(ufs2_daddr_t blkno, long frags)
+{
+ struct inodesc idesc;
+
+ idesc.id_blkno = blkno;
+ idesc.id_numfrags = frags;
+ (void)pass4check(&idesc);
+}
+
+/* Slow down IO so as to leave some disk bandwidth for other processes */
+void
+slowio_start()
+{
+
+ /* Delay one in every 8 operations */
+ slowio_pollcnt = (slowio_pollcnt + 1) & 7;
+ if (slowio_pollcnt == 0) {
+ gettimeofday(&slowio_starttime, NULL);
+ }
+}
+
+void
+slowio_end()
+{
+ struct timeval tv;
+ int delay_usec;
+
+ if (slowio_pollcnt != 0)
+ return;
+
+ /* Update the slowdown interval. */
+ gettimeofday(&tv, NULL);
+ delay_usec = (tv.tv_sec - slowio_starttime.tv_sec) * 1000000 +
+ (tv.tv_usec - slowio_starttime.tv_usec);
+ if (delay_usec < 64)
+ delay_usec = 64;
+ if (delay_usec > 2500000)
+ delay_usec = 2500000;
+ slowio_delay_usec = (slowio_delay_usec * 63 + delay_usec) >> 6;
+ /* delay by 8 times the average IO delay */
+ if (slowio_delay_usec > 64)
+ usleep(slowio_delay_usec * 8);
+}
+
+/*
+ * Find a pathname
+ */
+void
+getpathname(char *namebuf, ino_t curdir, ino_t ino)
+{
+ int len;
+ char *cp;
+ struct inodesc idesc;
+ static int busy = 0;
+
+ if (curdir == ino && ino == ROOTINO) {
+ (void)strcpy(namebuf, "/");
+ return;
+ }
+ if (busy || !INO_IS_DVALID(curdir)) {
+ (void)strcpy(namebuf, "?");
+ return;
+ }
+ busy = 1;
+ memset(&idesc, 0, sizeof(struct inodesc));
+ idesc.id_type = DATA;
+ idesc.id_fix = IGNORE;
+ cp = &namebuf[MAXPATHLEN - 1];
+ *cp = '\0';
+ if (curdir != ino) {
+ idesc.id_parent = curdir;
+ goto namelookup;
+ }
+ while (ino != ROOTINO) {
+ idesc.id_number = ino;
+ idesc.id_func = findino;
+ idesc.id_name = strdup("..");
+ if ((ckinode(ginode(ino), &idesc) & FOUND) == 0)
+ break;
+ namelookup:
+ idesc.id_number = idesc.id_parent;
+ idesc.id_parent = ino;
+ idesc.id_func = findname;
+ idesc.id_name = namebuf;
+ if ((ckinode(ginode(idesc.id_number), &idesc)&FOUND) == 0)
+ break;
+ len = strlen(namebuf);
+ cp -= len;
+ memmove(cp, namebuf, (size_t)len);
+ *--cp = '/';
+ if (cp < &namebuf[MAXNAMLEN])
+ break;
+ ino = idesc.id_number;
+ }
+ busy = 0;
+ if (ino != ROOTINO)
+ *--cp = '?';
+ memmove(namebuf, cp, (size_t)(&namebuf[MAXPATHLEN] - cp));
+}
+
+void
+catch(int sig __unused)
+{
+
+ ckfini(0);
+ exit(12);
+}
+
+/*
+ * When preening, allow a single quit to signal
+ * a special exit after file system checks complete
+ * so that reboot sequence may be interrupted.
+ */
+void
+catchquit(int sig __unused)
+{
+ printf("returning to single-user after file system check\n");
+ returntosingle = 1;
+ (void)signal(SIGQUIT, SIG_DFL);
+}
+
+/*
+ * determine whether an inode should be fixed.
+ */
+int
+dofix(struct inodesc *idesc, const char *msg)
+{
+
+ switch (idesc->id_fix) {
+
+ case DONTKNOW:
+ if (idesc->id_type == DATA)
+ direrror(idesc->id_number, msg);
+ else
+ pwarn("%s", msg);
+ if (preen) {
+ printf(" (SALVAGED)\n");
+ idesc->id_fix = FIX;
+ return (ALTERED);
+ }
+ if (reply("SALVAGE") == 0) {
+ idesc->id_fix = NOFIX;
+ return (0);
+ }
+ idesc->id_fix = FIX;
+ return (ALTERED);
+
+ case FIX:
+ return (ALTERED);
+
+ case NOFIX:
+ case IGNORE:
+ return (0);
+
+ default:
+ errx(EEXIT, "UNKNOWN INODESC FIX MODE %d", idesc->id_fix);
+ }
+ /* NOTREACHED */
+ return (0);
+}
+
+#include <stdarg.h>
+
+/*
+ * An unexpected inconsistency occurred.
+ * Die if preening or file system is running with soft dependency protocol,
+ * otherwise just print message and continue.
+ */
+void
+pfatal(const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ if (!preen) {
+ (void)vfprintf(stdout, fmt, ap);
+ va_end(ap);
+ if (usedsoftdep)
+ (void)fprintf(stdout,
+ "\nUNEXPECTED SOFT UPDATE INCONSISTENCY\n");
+ /*
+ * Force foreground fsck to clean up inconsistency.
+ */
+ if (bkgrdflag) {
+ cmd.value = FS_NEEDSFSCK;
+ cmd.size = 1;
+ if (sysctlbyname("vfs.ffs.setflags", 0, 0,
+ &cmd, sizeof cmd) == -1)
+ pwarn("CANNOT SET FS_NEEDSFSCK FLAG\n");
+ fprintf(stdout, "CANNOT RUN IN BACKGROUND\n");
+ ckfini(0);
+ exit(EEXIT);
+ }
+ return;
+ }
+ if (cdevname == NULL)
+ cdevname = strdup("fsck");
+ (void)fprintf(stdout, "%s: ", cdevname);
+ (void)vfprintf(stdout, fmt, ap);
+ (void)fprintf(stdout,
+ "\n%s: UNEXPECTED%sINCONSISTENCY; RUN fsck MANUALLY.\n",
+ cdevname, usedsoftdep ? " SOFT UPDATE " : " ");
+ /*
+ * Force foreground fsck to clean up inconsistency.
+ */
+ if (bkgrdflag) {
+ cmd.value = FS_NEEDSFSCK;
+ cmd.size = 1;
+ if (sysctlbyname("vfs.ffs.setflags", 0, 0,
+ &cmd, sizeof cmd) == -1)
+ pwarn("CANNOT SET FS_NEEDSFSCK FLAG\n");
+ }
+ ckfini(0);
+ exit(EEXIT);
+}
+
+/*
+ * Pwarn just prints a message when not preening or running soft dependency
+ * protocol, or a warning (preceded by filename) when preening.
+ */
+void
+pwarn(const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ if (preen)
+ (void)fprintf(stdout, "%s: ", cdevname);
+ (void)vfprintf(stdout, fmt, ap);
+ va_end(ap);
+}
+
+/*
+ * Stub for routines from kernel.
+ */
+void
+panic(const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ pfatal("INTERNAL INCONSISTENCY:");
+ (void)vfprintf(stdout, fmt, ap);
+ va_end(ap);
+ exit(EEXIT);
+}
diff --git a/sbin/fsck_ffs/gjournal.c b/sbin/fsck_ffs/gjournal.c
new file mode 100644
index 0000000..ba8dc34
--- /dev/null
+++ b/sbin/fsck_ffs/gjournal.c
@@ -0,0 +1,510 @@
+/*-
+ * Copyright (c) 2006 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ *
+ * Copyright (c) 1982, 1986, 1989, 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.
+ * 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/disklabel.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+
+#include <ufs/ufs/ufsmount.h>
+#include <ufs/ufs/dinode.h>
+#include <ufs/ffs/fs.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <libufs.h>
+#include <strings.h>
+#include <err.h>
+#include <assert.h>
+
+#include "fsck.h"
+
+struct cgchain {
+ union {
+ struct cg cgcu_cg;
+ char cgcu_buf[MAXBSIZE];
+ } cgc_union;
+ int cgc_busy;
+ int cgc_dirty;
+ LIST_ENTRY(cgchain) cgc_next;
+};
+#define cgc_cg cgc_union.cgcu_cg
+
+#define MAX_CACHED_CGS 1024
+static unsigned ncgs = 0;
+static LIST_HEAD(, cgchain) cglist = LIST_HEAD_INITIALIZER(cglist);
+
+static const char *devnam;
+static struct uufsd *disk = NULL;
+static struct fs *fs = NULL;
+struct ufs2_dinode ufs2_zino;
+
+static void putcgs(void);
+
+/*
+ * Return cylinder group from the cache or load it if it is not in the
+ * cache yet.
+ * Don't cache more than MAX_CACHED_CGS cylinder groups.
+ */
+static struct cgchain *
+getcg(int cg)
+{
+ struct cgchain *cgc;
+
+ assert(disk != NULL && fs != NULL);
+ LIST_FOREACH(cgc, &cglist, cgc_next) {
+ if (cgc->cgc_cg.cg_cgx == cg) {
+ //printf("%s: Found cg=%d\n", __func__, cg);
+ return (cgc);
+ }
+ }
+ /*
+ * Our cache is full? Let's clean it up.
+ */
+ if (ncgs >= MAX_CACHED_CGS) {
+ //printf("%s: Flushing CGs.\n", __func__);
+ putcgs();
+ }
+ cgc = malloc(sizeof(*cgc));
+ if (cgc == NULL) {
+ /*
+ * Cannot allocate memory?
+ * Let's put all currently loaded and not busy cylinder groups
+ * on disk and try again.
+ */
+ //printf("%s: No memory, flushing CGs.\n", __func__);
+ putcgs();
+ cgc = malloc(sizeof(*cgc));
+ if (cgc == NULL)
+ err(1, "malloc(%zu)", sizeof(*cgc));
+ }
+ if (cgread1(disk, cg) == -1)
+ err(1, "cgread1(%d)", cg);
+ bcopy(&disk->d_cg, &cgc->cgc_cg, sizeof(cgc->cgc_union));
+ cgc->cgc_busy = 0;
+ cgc->cgc_dirty = 0;
+ LIST_INSERT_HEAD(&cglist, cgc, cgc_next);
+ ncgs++;
+ //printf("%s: Read cg=%d\n", __func__, cg);
+ return (cgc);
+}
+
+/*
+ * Mark cylinder group as dirty - it will be written back on putcgs().
+ */
+static void
+dirtycg(struct cgchain *cgc)
+{
+
+ cgc->cgc_dirty = 1;
+}
+
+/*
+ * Mark cylinder group as busy - it will not be freed on putcgs().
+ */
+static void
+busycg(struct cgchain *cgc)
+{
+
+ cgc->cgc_busy = 1;
+}
+
+/*
+ * Unmark the given cylinder group as busy.
+ */
+static void
+unbusycg(struct cgchain *cgc)
+{
+
+ cgc->cgc_busy = 0;
+}
+
+/*
+ * Write back all dirty cylinder groups.
+ * Free all non-busy cylinder groups.
+ */
+static void
+putcgs(void)
+{
+ struct cgchain *cgc, *cgc2;
+
+ assert(disk != NULL && fs != NULL);
+ LIST_FOREACH_SAFE(cgc, &cglist, cgc_next, cgc2) {
+ if (cgc->cgc_busy)
+ continue;
+ LIST_REMOVE(cgc, cgc_next);
+ ncgs--;
+ if (cgc->cgc_dirty) {
+ bcopy(&cgc->cgc_cg, &disk->d_cg,
+ sizeof(cgc->cgc_union));
+ if (cgwrite1(disk, cgc->cgc_cg.cg_cgx) == -1)
+ err(1, "cgwrite1(%d)", cgc->cgc_cg.cg_cgx);
+ //printf("%s: Wrote cg=%d\n", __func__,
+ // cgc->cgc_cg.cg_cgx);
+ }
+ free(cgc);
+ }
+}
+
+#if 0
+/*
+ * Free all non-busy cylinder groups without storing the dirty ones.
+ */
+static void
+cancelcgs(void)
+{
+ struct cgchain *cgc;
+
+ assert(disk != NULL && fs != NULL);
+ while ((cgc = LIST_FIRST(&cglist)) != NULL) {
+ if (cgc->cgc_busy)
+ continue;
+ LIST_REMOVE(cgc, cgc_next);
+ //printf("%s: Canceled cg=%d\n", __func__, cgc->cgc_cg.cg_cgx);
+ free(cgc);
+ }
+}
+#endif
+
+/*
+ * Open the given provider, load superblock.
+ */
+static void
+opendisk(void)
+{
+ if (disk != NULL)
+ return;
+ disk = malloc(sizeof(*disk));
+ if (disk == NULL)
+ err(1, "malloc(%zu)", sizeof(*disk));
+ if (ufs_disk_fillout(disk, devnam) == -1) {
+ err(1, "ufs_disk_fillout(%s) failed: %s", devnam,
+ disk->d_error);
+ }
+ fs = &disk->d_fs;
+}
+
+/*
+ * Mark file system as clean, write the super-block back, close the disk.
+ */
+static void
+closedisk(void)
+{
+
+ fs->fs_clean = 1;
+ if (sbwrite(disk, 0) == -1)
+ err(1, "sbwrite(%s)", devnam);
+ if (ufs_disk_close(disk) == -1)
+ err(1, "ufs_disk_close(%s)", devnam);
+ free(disk);
+ disk = NULL;
+ fs = NULL;
+}
+
+static void
+blkfree(ufs2_daddr_t bno, long size)
+{
+ struct cgchain *cgc;
+ struct cg *cgp;
+ ufs1_daddr_t fragno, cgbno;
+ int i, cg, blk, frags, bbase;
+ u_int8_t *blksfree;
+
+ cg = dtog(fs, bno);
+ cgc = getcg(cg);
+ dirtycg(cgc);
+ cgp = &cgc->cgc_cg;
+ cgbno = dtogd(fs, bno);
+ blksfree = cg_blksfree(cgp);
+ if (size == fs->fs_bsize) {
+ fragno = fragstoblks(fs, cgbno);
+ if (!ffs_isfreeblock(fs, blksfree, fragno))
+ assert(!"blkfree: freeing free block");
+ ffs_setblock(fs, blksfree, fragno);
+ ffs_clusteracct(fs, cgp, fragno, 1);
+ cgp->cg_cs.cs_nbfree++;
+ fs->fs_cstotal.cs_nbfree++;
+ fs->fs_cs(fs, cg).cs_nbfree++;
+ } else {
+ bbase = cgbno - fragnum(fs, cgbno);
+ /*
+ * decrement the counts associated with the old frags
+ */
+ blk = blkmap(fs, blksfree, bbase);
+ ffs_fragacct(fs, blk, cgp->cg_frsum, -1);
+ /*
+ * deallocate the fragment
+ */
+ frags = numfrags(fs, size);
+ for (i = 0; i < frags; i++) {
+ if (isset(blksfree, cgbno + i))
+ assert(!"blkfree: freeing free frag");
+ setbit(blksfree, cgbno + i);
+ }
+ cgp->cg_cs.cs_nffree += i;
+ fs->fs_cstotal.cs_nffree += i;
+ fs->fs_cs(fs, cg).cs_nffree += i;
+ /*
+ * add back in counts associated with the new frags
+ */
+ blk = blkmap(fs, blksfree, bbase);
+ ffs_fragacct(fs, blk, cgp->cg_frsum, 1);
+ /*
+ * if a complete block has been reassembled, account for it
+ */
+ fragno = fragstoblks(fs, bbase);
+ if (ffs_isblock(fs, blksfree, fragno)) {
+ cgp->cg_cs.cs_nffree -= fs->fs_frag;
+ fs->fs_cstotal.cs_nffree -= fs->fs_frag;
+ fs->fs_cs(fs, cg).cs_nffree -= fs->fs_frag;
+ ffs_clusteracct(fs, cgp, fragno, 1);
+ cgp->cg_cs.cs_nbfree++;
+ fs->fs_cstotal.cs_nbfree++;
+ fs->fs_cs(fs, cg).cs_nbfree++;
+ }
+ }
+}
+
+/*
+ * Recursively free all indirect blocks.
+ */
+static void
+freeindir(ufs2_daddr_t blk, int level)
+{
+ char sblks[MAXBSIZE];
+ ufs2_daddr_t *blks;
+ int i;
+
+ if (bread(disk, fsbtodb(fs, blk), (void *)&sblks, (size_t)fs->fs_bsize) == -1)
+ err(1, "bread: %s", disk->d_error);
+ blks = (ufs2_daddr_t *)&sblks;
+ for (i = 0; i < NINDIR(fs); i++) {
+ if (blks[i] == 0)
+ break;
+ if (level == 0)
+ blkfree(blks[i], fs->fs_bsize);
+ else
+ freeindir(blks[i], level - 1);
+ }
+ blkfree(blk, fs->fs_bsize);
+}
+
+#define dblksize(fs, dino, lbn) \
+ ((dino)->di_size >= smalllblktosize(fs, (lbn) + 1) \
+ ? (fs)->fs_bsize \
+ : fragroundup(fs, blkoff(fs, (dino)->di_size)))
+
+/*
+ * Free all blocks associated with the given inode.
+ */
+static void
+clear_inode(struct ufs2_dinode *dino)
+{
+ ufs2_daddr_t bn;
+ int extblocks, i, level;
+ off_t osize;
+ long bsize;
+
+ extblocks = 0;
+ if (fs->fs_magic == FS_UFS2_MAGIC && dino->di_extsize > 0)
+ extblocks = btodb(fragroundup(fs, dino->di_extsize));
+ /* deallocate external attributes blocks */
+ if (extblocks > 0) {
+ osize = dino->di_extsize;
+ dino->di_blocks -= extblocks;
+ dino->di_extsize = 0;
+ for (i = 0; i < NXADDR; i++) {
+ if (dino->di_extb[i] == 0)
+ continue;
+ blkfree(dino->di_extb[i], sblksize(fs, osize, i));
+ }
+ }
+#define SINGLE 0 /* index of single indirect block */
+#define DOUBLE 1 /* index of double indirect block */
+#define TRIPLE 2 /* index of triple indirect block */
+ /* deallocate indirect blocks */
+ for (level = SINGLE; level <= TRIPLE; level++) {
+ if (dino->di_ib[level] == 0)
+ break;
+ freeindir(dino->di_ib[level], level);
+ }
+ /* deallocate direct blocks and fragments */
+ for (i = 0; i < NDADDR; i++) {
+ bn = dino->di_db[i];
+ if (bn == 0)
+ continue;
+ bsize = dblksize(fs, dino, i);
+ blkfree(bn, bsize);
+ }
+}
+
+void
+gjournal_check(const char *filesys)
+{
+ struct ufs2_dinode *dino;
+ void *p;
+ struct cgchain *cgc;
+ struct cg *cgp;
+ uint8_t *inosused;
+ ino_t cino, ino;
+ int cg, mode;
+
+ devnam = filesys;
+ opendisk();
+ /* Are there any unreferenced inodes in this file system? */
+ if (fs->fs_unrefs == 0) {
+ //printf("No unreferenced inodes.\n");
+ closedisk();
+ return;
+ }
+
+ for (cg = 0; cg < fs->fs_ncg; cg++) {
+ /* Show progress if requested. */
+ if (got_siginfo) {
+ printf("%s: phase j: cyl group %d of %d (%d%%)\n",
+ cdevname, cg, fs->fs_ncg, cg * 100 / fs->fs_ncg);
+ got_siginfo = 0;
+ }
+ if (got_sigalarm) {
+ setproctitle("%s pj %d%%", cdevname,
+ cg * 100 / fs->fs_ncg);
+ got_sigalarm = 0;
+ }
+ cgc = getcg(cg);
+ cgp = &cgc->cgc_cg;
+ /* Are there any unreferenced inodes in this cylinder group? */
+ if (cgp->cg_unrefs == 0)
+ continue;
+ //printf("Analizing cylinder group %d (count=%d)\n", cg, cgp->cg_unrefs);
+ /*
+ * We are going to modify this cylinder group, so we want it to
+ * be written back.
+ */
+ dirtycg(cgc);
+ /* We don't want it to be freed in the meantime. */
+ busycg(cgc);
+ inosused = cg_inosused(cgp);
+ /*
+ * Now go through the list of all inodes in this cylinder group
+ * to find unreferenced ones.
+ */
+ for (cino = 0; cino < fs->fs_ipg; cino++) {
+ ino = fs->fs_ipg * cg + cino;
+ /* Unallocated? Skip it. */
+ if (isclr(inosused, cino))
+ continue;
+ if (getino(disk, &p, ino, &mode) == -1)
+ err(1, "getino(cg=%d ino=%ju)",
+ cg, (uintmax_t)ino);
+ dino = p;
+ /* Not a regular file nor directory? Skip it. */
+ if (!S_ISREG(dino->di_mode) && !S_ISDIR(dino->di_mode))
+ continue;
+ /* Has reference(s)? Skip it. */
+ if (dino->di_nlink > 0)
+ continue;
+ //printf("Clearing inode=%d (size=%jd)\n", ino, (intmax_t)dino->di_size);
+ /* Free inode's blocks. */
+ clear_inode(dino);
+ /* Deallocate it. */
+ clrbit(inosused, cino);
+ /* Update position of last used inode. */
+ if (ino < cgp->cg_irotor)
+ cgp->cg_irotor = ino;
+ /* Update statistics. */
+ cgp->cg_cs.cs_nifree++;
+ fs->fs_cs(fs, cg).cs_nifree++;
+ fs->fs_cstotal.cs_nifree++;
+ cgp->cg_unrefs--;
+ fs->fs_unrefs--;
+ /* If this is directory, update related statistics. */
+ if (S_ISDIR(dino->di_mode)) {
+ cgp->cg_cs.cs_ndir--;
+ fs->fs_cs(fs, cg).cs_ndir--;
+ fs->fs_cstotal.cs_ndir--;
+ }
+ /* Zero-fill the inode. */
+ *dino = ufs2_zino;
+ /* Write the inode back. */
+ if (putino(disk) == -1)
+ err(1, "putino(cg=%d ino=%ju)",
+ cg, (uintmax_t)ino);
+ if (cgp->cg_unrefs == 0) {
+ //printf("No more unreferenced inodes in cg=%d.\n", cg);
+ break;
+ }
+ }
+ /*
+ * We don't need this cylinder group anymore, so feel free to
+ * free it if needed.
+ */
+ unbusycg(cgc);
+ /*
+ * If there are no more unreferenced inodes, there is no need to
+ * check other cylinder groups.
+ */
+ if (fs->fs_unrefs == 0) {
+ //printf("No more unreferenced inodes (cg=%d/%d).\n", cg,
+ // fs->fs_ncg);
+ break;
+ }
+ }
+ /* Write back modified cylinder groups. */
+ putcgs();
+ /* Write back updated statistics and super-block. */
+ closedisk();
+}
diff --git a/sbin/fsck_ffs/globs.c b/sbin/fsck_ffs/globs.c
new file mode 100644
index 0000000..c5b1e1b
--- /dev/null
+++ b/sbin/fsck_ffs/globs.c
@@ -0,0 +1,165 @@
+/*
+ * Copyright (c) 1980, 1986, 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.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if 0
+#ifndef lint
+static const char copyright[] =
+"@(#) Copyright (c) 1980, 1986, 1993\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static char sccsid[] = "@(#)main.c 8.6 (Berkeley) 5/14/95";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <ufs/ufs/dinode.h>
+#include <ufs/ffs/fs.h>
+#include <string.h>
+#include "fsck.h"
+
+long readcnt[BT_NUMBUFTYPES];
+long totalreadcnt[BT_NUMBUFTYPES];
+struct timespec readtime[BT_NUMBUFTYPES];
+struct timespec totalreadtime[BT_NUMBUFTYPES];
+struct timespec startprog;
+struct bufarea sblk; /* file system superblock */
+struct bufarea *pdirbp; /* current directory contents */
+struct bufarea *pbp; /* current inode block */
+ino_t cursnapshot;
+long numdirs, dirhash, listmax, inplast;
+long countdirs; /* number of directories we actually found */
+int adjrefcnt[MIBSIZE]; /* MIB command to adjust inode reference cnt */
+int adjblkcnt[MIBSIZE]; /* MIB command to adjust inode block count */
+int adjndir[MIBSIZE]; /* MIB command to adjust number of directories */
+int adjnbfree[MIBSIZE]; /* MIB command to adjust number of free blocks */
+int adjnifree[MIBSIZE]; /* MIB command to adjust number of free inodes */
+int adjnffree[MIBSIZE]; /* MIB command to adjust number of free frags */
+int adjnumclusters[MIBSIZE]; /* MIB command to adjust number of free clusters */
+int freefiles[MIBSIZE]; /* MIB command to free a set of files */
+int freedirs[MIBSIZE]; /* MIB command to free a set of directories */
+int freeblks[MIBSIZE]; /* MIB command to free a set of data blocks */
+struct fsck_cmd cmd; /* sysctl file system update commands */
+char snapname[BUFSIZ]; /* when doing snapshots, the name of the file */
+char *cdevname; /* name of device being checked */
+long dev_bsize; /* computed value of DEV_BSIZE */
+long secsize; /* actual disk sector size */
+u_int real_dev_bsize; /* actual disk sector size, not overriden */
+char nflag; /* assume a no response */
+char yflag; /* assume a yes response */
+int bkgrdflag; /* use a snapshot to run on an active system */
+int bflag; /* location of alternate super block */
+int debug; /* output debugging info */
+int Eflag; /* delete empty data blocks */
+int Zflag; /* zero empty data blocks */
+int inoopt; /* trim out unused inodes */
+char ckclean; /* only do work if not cleanly unmounted */
+int cvtlevel; /* convert to newer file system format */
+int bkgrdcheck; /* determine if background check is possible */
+int bkgrdsumadj; /* whether the kernel have ability to adjust superblock summary */
+char usedsoftdep; /* just fix soft dependency inconsistencies */
+char preen; /* just fix normal inconsistencies */
+char rerun; /* rerun fsck. Only used in non-preen mode */
+int returntosingle; /* 1 => return to single user mode on exit */
+char resolved; /* cleared if unresolved changes => not clean */
+char havesb; /* superblock has been read */
+char skipclean; /* skip clean file systems if preening */
+int fsmodified; /* 1 => write done to file system */
+int fsreadfd; /* file descriptor for reading file system */
+int fswritefd; /* file descriptor for writing file system */
+int surrender; /* Give up if reads fail */
+int wantrestart; /* Restart fsck on early termination */
+ufs2_daddr_t maxfsblock; /* number of blocks in the file system */
+char *blockmap; /* ptr to primary blk allocation map */
+ino_t maxino; /* number of inodes in file system */
+ino_t lfdir; /* lost & found directory inode number */
+const char *lfname; /* lost & found directory name */
+int lfmode; /* lost & found directory creation mode */
+ufs2_daddr_t n_blks; /* number of blocks in use */
+ino_t n_files; /* number of files in use */
+volatile sig_atomic_t got_siginfo; /* received a SIGINFO */
+volatile sig_atomic_t got_sigalarm; /* received a SIGALRM */
+struct ufs1_dinode ufs1_zino;
+struct ufs2_dinode ufs2_zino;
+
+void
+fsckinit(void)
+{
+ bzero(readcnt, sizeof(long) * BT_NUMBUFTYPES);
+ bzero(totalreadcnt, sizeof(long) * BT_NUMBUFTYPES);
+ bzero(readtime, sizeof(struct timespec) * BT_NUMBUFTYPES);
+ bzero(totalreadtime, sizeof(struct timespec) * BT_NUMBUFTYPES);
+ bzero(&startprog, sizeof(struct timespec));;
+ bzero(&sblk, sizeof(struct bufarea));
+ pdirbp = NULL;
+ pbp = NULL;
+ cursnapshot = 0;
+ numdirs = dirhash = listmax = inplast = 0;
+ countdirs = 0;
+ bzero(adjrefcnt, sizeof(int) * MIBSIZE);
+ bzero(adjblkcnt, sizeof(int) * MIBSIZE);
+ bzero(adjndir, sizeof(int) * MIBSIZE);
+ bzero(adjnbfree, sizeof(int) * MIBSIZE);
+ bzero(adjnifree, sizeof(int) * MIBSIZE);
+ bzero(adjnffree, sizeof(int) * MIBSIZE);
+ bzero(adjnumclusters, sizeof(int) * MIBSIZE);
+ bzero(freefiles, sizeof(int) * MIBSIZE);
+ bzero(freedirs, sizeof(int) * MIBSIZE);
+ bzero(freeblks, sizeof(int) * MIBSIZE);
+ bzero(&cmd, sizeof(struct fsck_cmd));
+ bzero(snapname, sizeof(char) * BUFSIZ);
+ cdevname = NULL;
+ dev_bsize = 0;
+ secsize = 0;
+ real_dev_bsize = 0;
+ bkgrdsumadj = 0;
+ usedsoftdep = 0;
+ rerun = 0;
+ returntosingle = 0;
+ resolved = 0;
+ havesb = 0;
+ fsmodified = 0;
+ fsreadfd = 0;
+ fswritefd = 0;
+ maxfsblock = 0;
+ blockmap = NULL;
+ maxino = 0;
+ lfdir = 0;
+ lfname = "lost+found";
+ lfmode = 0700;
+ n_blks = 0;
+ n_files = 0;
+ got_siginfo = 0;
+ got_sigalarm = 0;
+ bzero(&ufs1_zino, sizeof(struct ufs1_dinode));
+ bzero(&ufs2_zino, sizeof(struct ufs2_dinode));
+}
diff --git a/sbin/fsck_ffs/inode.c b/sbin/fsck_ffs/inode.c
new file mode 100644
index 0000000..e8baf09
--- /dev/null
+++ b/sbin/fsck_ffs/inode.c
@@ -0,0 +1,734 @@
+/*
+ * Copyright (c) 1980, 1986, 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.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if 0
+#ifndef lint
+static const char sccsid[] = "@(#)inode.c 8.8 (Berkeley) 4/28/95";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/stdint.h>
+#include <sys/sysctl.h>
+
+#include <ufs/ufs/dinode.h>
+#include <ufs/ufs/dir.h>
+#include <ufs/ffs/fs.h>
+
+#include <err.h>
+#include <pwd.h>
+#include <string.h>
+#include <time.h>
+
+#include "fsck.h"
+
+static ino_t startinum;
+
+static int iblock(struct inodesc *, long ilevel, off_t isize, int type);
+
+int
+ckinode(union dinode *dp, struct inodesc *idesc)
+{
+ off_t remsize, sizepb;
+ int i, offset, ret;
+ union dinode dino;
+ ufs2_daddr_t ndb;
+ mode_t mode;
+ char pathbuf[MAXPATHLEN + 1];
+
+ if (idesc->id_fix != IGNORE)
+ idesc->id_fix = DONTKNOW;
+ idesc->id_lbn = -1;
+ idesc->id_entryno = 0;
+ idesc->id_filesize = DIP(dp, di_size);
+ mode = DIP(dp, di_mode) & IFMT;
+ if (mode == IFBLK || mode == IFCHR || (mode == IFLNK &&
+ DIP(dp, di_size) < (unsigned)sblock.fs_maxsymlinklen))
+ return (KEEPON);
+ if (sblock.fs_magic == FS_UFS1_MAGIC)
+ dino.dp1 = dp->dp1;
+ else
+ dino.dp2 = dp->dp2;
+ ndb = howmany(DIP(&dino, di_size), sblock.fs_bsize);
+ for (i = 0; i < NDADDR; i++) {
+ idesc->id_lbn++;
+ if (--ndb == 0 &&
+ (offset = blkoff(&sblock, DIP(&dino, di_size))) != 0)
+ idesc->id_numfrags =
+ numfrags(&sblock, fragroundup(&sblock, offset));
+ else
+ idesc->id_numfrags = sblock.fs_frag;
+ if (DIP(&dino, di_db[i]) == 0) {
+ if (idesc->id_type == DATA && ndb >= 0) {
+ /* An empty block in a directory XXX */
+ getpathname(pathbuf, idesc->id_number,
+ idesc->id_number);
+ pfatal("DIRECTORY %s: CONTAINS EMPTY BLOCKS",
+ pathbuf);
+ if (reply("ADJUST LENGTH") == 1) {
+ dp = ginode(idesc->id_number);
+ DIP_SET(dp, di_size,
+ i * sblock.fs_bsize);
+ printf(
+ "YOU MUST RERUN FSCK AFTERWARDS\n");
+ rerun = 1;
+ inodirty();
+
+ }
+ }
+ continue;
+ }
+ idesc->id_blkno = DIP(&dino, di_db[i]);
+ if (idesc->id_type != DATA)
+ ret = (*idesc->id_func)(idesc);
+ else
+ ret = dirscan(idesc);
+ if (ret & STOP)
+ return (ret);
+ }
+ idesc->id_numfrags = sblock.fs_frag;
+ remsize = DIP(&dino, di_size) - sblock.fs_bsize * NDADDR;
+ sizepb = sblock.fs_bsize;
+ for (i = 0; i < NIADDR; i++) {
+ sizepb *= NINDIR(&sblock);
+ if (DIP(&dino, di_ib[i])) {
+ idesc->id_blkno = DIP(&dino, di_ib[i]);
+ ret = iblock(idesc, i + 1, remsize, BT_LEVEL1 + i);
+ if (ret & STOP)
+ return (ret);
+ } else {
+ idesc->id_lbn += sizepb / sblock.fs_bsize;
+ if (idesc->id_type == DATA && remsize > 0) {
+ /* An empty block in a directory XXX */
+ getpathname(pathbuf, idesc->id_number,
+ idesc->id_number);
+ pfatal("DIRECTORY %s: CONTAINS EMPTY BLOCKS",
+ pathbuf);
+ if (reply("ADJUST LENGTH") == 1) {
+ dp = ginode(idesc->id_number);
+ DIP_SET(dp, di_size,
+ DIP(dp, di_size) - remsize);
+ remsize = 0;
+ printf(
+ "YOU MUST RERUN FSCK AFTERWARDS\n");
+ rerun = 1;
+ inodirty();
+ break;
+ }
+ }
+ }
+ remsize -= sizepb;
+ }
+ return (KEEPON);
+}
+
+static int
+iblock(struct inodesc *idesc, long ilevel, off_t isize, int type)
+{
+ struct bufarea *bp;
+ int i, n, (*func)(struct inodesc *), nif;
+ off_t sizepb;
+ char buf[BUFSIZ];
+ char pathbuf[MAXPATHLEN + 1];
+ union dinode *dp;
+
+ if (idesc->id_type != DATA) {
+ func = idesc->id_func;
+ if (((n = (*func)(idesc)) & KEEPON) == 0)
+ return (n);
+ } else
+ func = dirscan;
+ if (chkrange(idesc->id_blkno, idesc->id_numfrags))
+ return (SKIP);
+ bp = getdatablk(idesc->id_blkno, sblock.fs_bsize, type);
+ ilevel--;
+ for (sizepb = sblock.fs_bsize, i = 0; i < ilevel; i++)
+ sizepb *= NINDIR(&sblock);
+ if (howmany(isize, sizepb) > NINDIR(&sblock))
+ nif = NINDIR(&sblock);
+ else
+ nif = howmany(isize, sizepb);
+ if (idesc->id_func == pass1check && nif < NINDIR(&sblock)) {
+ for (i = nif; i < NINDIR(&sblock); i++) {
+ if (IBLK(bp, i) == 0)
+ continue;
+ (void)sprintf(buf, "PARTIALLY TRUNCATED INODE I=%lu",
+ (u_long)idesc->id_number);
+ if (preen) {
+ pfatal("%s", buf);
+ } else if (dofix(idesc, buf)) {
+ IBLK_SET(bp, i, 0);
+ dirty(bp);
+ }
+ }
+ flush(fswritefd, bp);
+ }
+ for (i = 0; i < nif; i++) {
+ if (ilevel == 0)
+ idesc->id_lbn++;
+ if (IBLK(bp, i)) {
+ idesc->id_blkno = IBLK(bp, i);
+ if (ilevel == 0)
+ n = (*func)(idesc);
+ else
+ n = iblock(idesc, ilevel, isize, type);
+ if (n & STOP) {
+ bp->b_flags &= ~B_INUSE;
+ return (n);
+ }
+ } else {
+ if (idesc->id_type == DATA && isize > 0) {
+ /* An empty block in a directory XXX */
+ getpathname(pathbuf, idesc->id_number,
+ idesc->id_number);
+ pfatal("DIRECTORY %s: CONTAINS EMPTY BLOCKS",
+ pathbuf);
+ if (reply("ADJUST LENGTH") == 1) {
+ dp = ginode(idesc->id_number);
+ DIP_SET(dp, di_size,
+ DIP(dp, di_size) - isize);
+ isize = 0;
+ printf(
+ "YOU MUST RERUN FSCK AFTERWARDS\n");
+ rerun = 1;
+ inodirty();
+ bp->b_flags &= ~B_INUSE;
+ return(STOP);
+ }
+ }
+ }
+ isize -= sizepb;
+ }
+ bp->b_flags &= ~B_INUSE;
+ return (KEEPON);
+}
+
+/*
+ * Check that a block in a legal block number.
+ * Return 0 if in range, 1 if out of range.
+ */
+int
+chkrange(ufs2_daddr_t blk, int cnt)
+{
+ int c;
+
+ if (cnt <= 0 || blk <= 0 || blk > maxfsblock ||
+ cnt - 1 > maxfsblock - blk)
+ return (1);
+ if (cnt > sblock.fs_frag ||
+ fragnum(&sblock, blk) + cnt > sblock.fs_frag) {
+ if (debug)
+ printf("bad size: blk %ld, offset %i, size %d\n",
+ (long)blk, (int)fragnum(&sblock, blk), cnt);
+ return (1);
+ }
+ c = dtog(&sblock, blk);
+ if (blk < cgdmin(&sblock, c)) {
+ if ((blk + cnt) > cgsblock(&sblock, c)) {
+ if (debug) {
+ printf("blk %ld < cgdmin %ld;",
+ (long)blk, (long)cgdmin(&sblock, c));
+ printf(" blk + cnt %ld > cgsbase %ld\n",
+ (long)(blk + cnt),
+ (long)cgsblock(&sblock, c));
+ }
+ return (1);
+ }
+ } else {
+ if ((blk + cnt) > cgbase(&sblock, c+1)) {
+ if (debug) {
+ printf("blk %ld >= cgdmin %ld;",
+ (long)blk, (long)cgdmin(&sblock, c));
+ printf(" blk + cnt %ld > sblock.fs_fpg %ld\n",
+ (long)(blk + cnt), (long)sblock.fs_fpg);
+ }
+ return (1);
+ }
+ }
+ return (0);
+}
+
+/*
+ * General purpose interface for reading inodes.
+ */
+union dinode *
+ginode(ino_t inumber)
+{
+ ufs2_daddr_t iblk;
+
+ if (inumber < ROOTINO || inumber > maxino)
+ errx(EEXIT, "bad inode number %ju to ginode",
+ (uintmax_t)inumber);
+ if (startinum == 0 ||
+ inumber < startinum || inumber >= startinum + INOPB(&sblock)) {
+ iblk = ino_to_fsba(&sblock, inumber);
+ if (pbp != 0)
+ pbp->b_flags &= ~B_INUSE;
+ pbp = getdatablk(iblk, sblock.fs_bsize, BT_INODES);
+ startinum = (inumber / INOPB(&sblock)) * INOPB(&sblock);
+ }
+ if (sblock.fs_magic == FS_UFS1_MAGIC)
+ return ((union dinode *)
+ &pbp->b_un.b_dinode1[inumber % INOPB(&sblock)]);
+ return ((union dinode *)&pbp->b_un.b_dinode2[inumber % INOPB(&sblock)]);
+}
+
+/*
+ * Special purpose version of ginode used to optimize first pass
+ * over all the inodes in numerical order.
+ */
+static ino_t nextino, lastinum, lastvalidinum;
+static long readcount, readpercg, fullcnt, inobufsize, partialcnt, partialsize;
+static struct bufarea inobuf;
+
+union dinode *
+getnextinode(ino_t inumber, int rebuildcg)
+{
+ int j;
+ long size;
+ mode_t mode;
+ ufs2_daddr_t ndb, blk;
+ union dinode *dp;
+ static caddr_t nextinop;
+
+ if (inumber != nextino++ || inumber > lastvalidinum)
+ errx(EEXIT, "bad inode number %ju to nextinode",
+ (uintmax_t)inumber);
+ if (inumber >= lastinum) {
+ readcount++;
+ blk = ino_to_fsba(&sblock, lastinum);
+ if (readcount % readpercg == 0) {
+ size = partialsize;
+ lastinum += partialcnt;
+ } else {
+ size = inobufsize;
+ lastinum += fullcnt;
+ }
+ /*
+ * If getblk encounters an error, it will already have zeroed
+ * out the buffer, so we do not need to do so here.
+ */
+ getblk(&inobuf, blk, size);
+ nextinop = inobuf.b_un.b_buf;
+ }
+ dp = (union dinode *)nextinop;
+ if (rebuildcg && nextinop == inobuf.b_un.b_buf) {
+ /*
+ * Try to determine if we have reached the end of the
+ * allocated inodes.
+ */
+ mode = DIP(dp, di_mode) & IFMT;
+ if (mode == 0) {
+ if (memcmp(dp->dp2.di_db, ufs2_zino.di_db,
+ NDADDR * sizeof(ufs2_daddr_t)) ||
+ memcmp(dp->dp2.di_ib, ufs2_zino.di_ib,
+ NIADDR * sizeof(ufs2_daddr_t)) ||
+ dp->dp2.di_mode || dp->dp2.di_size)
+ return (NULL);
+ goto inodegood;
+ }
+ if (!ftypeok(dp))
+ return (NULL);
+ ndb = howmany(DIP(dp, di_size), sblock.fs_bsize);
+ if (ndb < 0)
+ return (NULL);
+ if (mode == IFBLK || mode == IFCHR)
+ ndb++;
+ if (mode == IFLNK) {
+ /*
+ * Fake ndb value so direct/indirect block checks below
+ * will detect any garbage after symlink string.
+ */
+ if (DIP(dp, di_size) < (off_t)sblock.fs_maxsymlinklen) {
+ ndb = howmany(DIP(dp, di_size),
+ sizeof(ufs2_daddr_t));
+ if (ndb > NDADDR) {
+ j = ndb - NDADDR;
+ for (ndb = 1; j > 1; j--)
+ ndb *= NINDIR(&sblock);
+ ndb += NDADDR;
+ }
+ }
+ }
+ for (j = ndb; ndb < NDADDR && j < NDADDR; j++)
+ if (DIP(dp, di_db[j]) != 0)
+ return (NULL);
+ for (j = 0, ndb -= NDADDR; ndb > 0; j++)
+ ndb /= NINDIR(&sblock);
+ for (; j < NIADDR; j++)
+ if (DIP(dp, di_ib[j]) != 0)
+ return (NULL);
+ }
+inodegood:
+ if (sblock.fs_magic == FS_UFS1_MAGIC)
+ nextinop += sizeof(struct ufs1_dinode);
+ else
+ nextinop += sizeof(struct ufs2_dinode);
+ return (dp);
+}
+
+void
+setinodebuf(ino_t inum)
+{
+
+ if (inum % sblock.fs_ipg != 0)
+ errx(EEXIT, "bad inode number %ju to setinodebuf",
+ (uintmax_t)inum);
+ lastvalidinum = inum + sblock.fs_ipg - 1;
+ startinum = 0;
+ nextino = inum;
+ lastinum = inum;
+ readcount = 0;
+ if (inobuf.b_un.b_buf != NULL)
+ return;
+ inobufsize = blkroundup(&sblock, INOBUFSIZE);
+ fullcnt = inobufsize / ((sblock.fs_magic == FS_UFS1_MAGIC) ?
+ sizeof(struct ufs1_dinode) : sizeof(struct ufs2_dinode));
+ readpercg = sblock.fs_ipg / fullcnt;
+ partialcnt = sblock.fs_ipg % fullcnt;
+ partialsize = partialcnt * ((sblock.fs_magic == FS_UFS1_MAGIC) ?
+ sizeof(struct ufs1_dinode) : sizeof(struct ufs2_dinode));
+ if (partialcnt != 0) {
+ readpercg++;
+ } else {
+ partialcnt = fullcnt;
+ partialsize = inobufsize;
+ }
+ initbarea(&inobuf, BT_INODES);
+ if ((inobuf.b_un.b_buf = Malloc((unsigned)inobufsize)) == NULL)
+ errx(EEXIT, "cannot allocate space for inode buffer");
+}
+
+void
+freeinodebuf(void)
+{
+
+ if (inobuf.b_un.b_buf != NULL)
+ free((char *)inobuf.b_un.b_buf);
+ inobuf.b_un.b_buf = NULL;
+}
+
+/*
+ * Routines to maintain information about directory inodes.
+ * This is built during the first pass and used during the
+ * second and third passes.
+ *
+ * Enter inodes into the cache.
+ */
+void
+cacheino(union dinode *dp, ino_t inumber)
+{
+ struct inoinfo *inp, **inpp;
+ int i, blks;
+
+ if (howmany(DIP(dp, di_size), sblock.fs_bsize) > NDADDR)
+ blks = NDADDR + NIADDR;
+ else
+ blks = howmany(DIP(dp, di_size), sblock.fs_bsize);
+ inp = (struct inoinfo *)
+ Malloc(sizeof(*inp) + (blks - 1) * sizeof(ufs2_daddr_t));
+ if (inp == NULL)
+ errx(EEXIT, "cannot increase directory list");
+ inpp = &inphead[inumber % dirhash];
+ inp->i_nexthash = *inpp;
+ *inpp = inp;
+ inp->i_parent = inumber == ROOTINO ? ROOTINO : (ino_t)0;
+ inp->i_dotdot = (ino_t)0;
+ inp->i_number = inumber;
+ inp->i_isize = DIP(dp, di_size);
+ inp->i_numblks = blks;
+ for (i = 0; i < (blks < NDADDR ? blks : NDADDR); i++)
+ inp->i_blks[i] = DIP(dp, di_db[i]);
+ if (blks > NDADDR)
+ for (i = 0; i < NIADDR; i++)
+ inp->i_blks[NDADDR + i] = DIP(dp, di_ib[i]);
+ if (inplast == listmax) {
+ listmax += 100;
+ inpsort = (struct inoinfo **)realloc((char *)inpsort,
+ (unsigned)listmax * sizeof(struct inoinfo *));
+ if (inpsort == NULL)
+ errx(EEXIT, "cannot increase directory list");
+ }
+ inpsort[inplast++] = inp;
+}
+
+/*
+ * Look up an inode cache structure.
+ */
+struct inoinfo *
+getinoinfo(ino_t inumber)
+{
+ struct inoinfo *inp;
+
+ for (inp = inphead[inumber % dirhash]; inp; inp = inp->i_nexthash) {
+ if (inp->i_number != inumber)
+ continue;
+ return (inp);
+ }
+ errx(EEXIT, "cannot find inode %ju", (uintmax_t)inumber);
+ return ((struct inoinfo *)0);
+}
+
+/*
+ * Clean up all the inode cache structure.
+ */
+void
+inocleanup(void)
+{
+ struct inoinfo **inpp;
+
+ if (inphead == NULL)
+ return;
+ for (inpp = &inpsort[inplast - 1]; inpp >= inpsort; inpp--)
+ free((char *)(*inpp));
+ free((char *)inphead);
+ free((char *)inpsort);
+ inphead = inpsort = NULL;
+}
+
+void
+inodirty(void)
+{
+
+ dirty(pbp);
+}
+
+void
+clri(struct inodesc *idesc, const char *type, int flag)
+{
+ union dinode *dp;
+
+ dp = ginode(idesc->id_number);
+ if (flag == 1) {
+ pwarn("%s %s", type,
+ (DIP(dp, di_mode) & IFMT) == IFDIR ? "DIR" : "FILE");
+ pinode(idesc->id_number);
+ }
+ if (preen || reply("CLEAR") == 1) {
+ if (preen)
+ printf(" (CLEARED)\n");
+ n_files--;
+ if (bkgrdflag == 0) {
+ (void)ckinode(dp, idesc);
+ inoinfo(idesc->id_number)->ino_state = USTATE;
+ clearinode(dp);
+ inodirty();
+ } else {
+ cmd.value = idesc->id_number;
+ cmd.size = -DIP(dp, di_nlink);
+ if (debug)
+ printf("adjrefcnt ino %ld amt %lld\n",
+ (long)cmd.value, (long long)cmd.size);
+ if (sysctl(adjrefcnt, MIBSIZE, 0, 0,
+ &cmd, sizeof cmd) == -1)
+ rwerror("ADJUST INODE", cmd.value);
+ }
+ }
+}
+
+int
+findname(struct inodesc *idesc)
+{
+ struct direct *dirp = idesc->id_dirp;
+
+ if (dirp->d_ino != idesc->id_parent || idesc->id_entryno < 2) {
+ idesc->id_entryno++;
+ return (KEEPON);
+ }
+ memmove(idesc->id_name, dirp->d_name, (size_t)dirp->d_namlen + 1);
+ return (STOP|FOUND);
+}
+
+int
+findino(struct inodesc *idesc)
+{
+ struct direct *dirp = idesc->id_dirp;
+
+ if (dirp->d_ino == 0)
+ return (KEEPON);
+ if (strcmp(dirp->d_name, idesc->id_name) == 0 &&
+ dirp->d_ino >= ROOTINO && dirp->d_ino <= maxino) {
+ idesc->id_parent = dirp->d_ino;
+ return (STOP|FOUND);
+ }
+ return (KEEPON);
+}
+
+int
+clearentry(struct inodesc *idesc)
+{
+ struct direct *dirp = idesc->id_dirp;
+
+ if (dirp->d_ino != idesc->id_parent || idesc->id_entryno < 2) {
+ idesc->id_entryno++;
+ return (KEEPON);
+ }
+ dirp->d_ino = 0;
+ return (STOP|FOUND|ALTERED);
+}
+
+void
+pinode(ino_t ino)
+{
+ union dinode *dp;
+ char *p;
+ struct passwd *pw;
+ time_t t;
+
+ printf(" I=%lu ", (u_long)ino);
+ if (ino < ROOTINO || ino > maxino)
+ return;
+ dp = ginode(ino);
+ printf(" OWNER=");
+ if ((pw = getpwuid((int)DIP(dp, di_uid))) != 0)
+ printf("%s ", pw->pw_name);
+ else
+ printf("%u ", (unsigned)DIP(dp, di_uid));
+ printf("MODE=%o\n", DIP(dp, di_mode));
+ if (preen)
+ printf("%s: ", cdevname);
+ printf("SIZE=%ju ", (uintmax_t)DIP(dp, di_size));
+ t = DIP(dp, di_mtime);
+ p = ctime(&t);
+ printf("MTIME=%12.12s %4.4s ", &p[4], &p[20]);
+}
+
+void
+blkerror(ino_t ino, const char *type, ufs2_daddr_t blk)
+{
+
+ pfatal("%jd %s I=%ju", (intmax_t)blk, type, (uintmax_t)ino);
+ printf("\n");
+ switch (inoinfo(ino)->ino_state) {
+
+ case FSTATE:
+ case FZLINK:
+ inoinfo(ino)->ino_state = FCLEAR;
+ return;
+
+ case DSTATE:
+ case DZLINK:
+ inoinfo(ino)->ino_state = DCLEAR;
+ return;
+
+ case FCLEAR:
+ case DCLEAR:
+ return;
+
+ default:
+ errx(EEXIT, "BAD STATE %d TO BLKERR", inoinfo(ino)->ino_state);
+ /* NOTREACHED */
+ }
+}
+
+/*
+ * allocate an unused inode
+ */
+ino_t
+allocino(ino_t request, int type)
+{
+ ino_t ino;
+ union dinode *dp;
+ struct bufarea *cgbp;
+ struct cg *cgp;
+ int cg;
+
+ if (request == 0)
+ request = ROOTINO;
+ else if (inoinfo(request)->ino_state != USTATE)
+ return (0);
+ for (ino = request; ino < maxino; ino++)
+ if (inoinfo(ino)->ino_state == USTATE)
+ break;
+ if (ino == maxino)
+ return (0);
+ cg = ino_to_cg(&sblock, ino);
+ cgbp = cgget(cg);
+ cgp = cgbp->b_un.b_cg;
+ if (!check_cgmagic(cg, cgbp))
+ return (0);
+ setbit(cg_inosused(cgp), ino % sblock.fs_ipg);
+ cgp->cg_cs.cs_nifree--;
+ switch (type & IFMT) {
+ case IFDIR:
+ inoinfo(ino)->ino_state = DSTATE;
+ cgp->cg_cs.cs_ndir++;
+ break;
+ case IFREG:
+ case IFLNK:
+ inoinfo(ino)->ino_state = FSTATE;
+ break;
+ default:
+ return (0);
+ }
+ dirty(cgbp);
+ dp = ginode(ino);
+ DIP_SET(dp, di_db[0], allocblk((long)1));
+ if (DIP(dp, di_db[0]) == 0) {
+ inoinfo(ino)->ino_state = USTATE;
+ return (0);
+ }
+ DIP_SET(dp, di_mode, type);
+ DIP_SET(dp, di_flags, 0);
+ DIP_SET(dp, di_atime, time(NULL));
+ DIP_SET(dp, di_ctime, DIP(dp, di_atime));
+ DIP_SET(dp, di_mtime, DIP(dp, di_ctime));
+ DIP_SET(dp, di_mtimensec, 0);
+ DIP_SET(dp, di_ctimensec, 0);
+ DIP_SET(dp, di_atimensec, 0);
+ DIP_SET(dp, di_size, sblock.fs_fsize);
+ DIP_SET(dp, di_blocks, btodb(sblock.fs_fsize));
+ n_files++;
+ inodirty();
+ inoinfo(ino)->ino_type = IFTODT(type);
+ return (ino);
+}
+
+/*
+ * deallocate an inode
+ */
+void
+freeino(ino_t ino)
+{
+ struct inodesc idesc;
+ union dinode *dp;
+
+ memset(&idesc, 0, sizeof(struct inodesc));
+ idesc.id_type = ADDR;
+ idesc.id_func = pass4check;
+ idesc.id_number = ino;
+ dp = ginode(ino);
+ (void)ckinode(dp, &idesc);
+ clearinode(dp);
+ inodirty();
+ inoinfo(ino)->ino_state = USTATE;
+ n_files--;
+}
diff --git a/sbin/fsck_ffs/main.c b/sbin/fsck_ffs/main.c
new file mode 100644
index 0000000..08c7745
--- /dev/null
+++ b/sbin/fsck_ffs/main.c
@@ -0,0 +1,683 @@
+/*
+ * Copyright (c) 1980, 1986, 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.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if 0
+#ifndef lint
+static const char copyright[] =
+"@(#) Copyright (c) 1980, 1986, 1993\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static char sccsid[] = "@(#)main.c 8.6 (Berkeley) 5/14/95";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/file.h>
+#include <sys/mount.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <sys/sysctl.h>
+#include <sys/uio.h>
+#include <sys/disklabel.h>
+
+#include <ufs/ufs/dinode.h>
+#include <ufs/ffs/fs.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fstab.h>
+#include <grp.h>
+#include <mntopts.h>
+#include <paths.h>
+#include <stdint.h>
+#include <string.h>
+#include <time.h>
+
+#include "fsck.h"
+
+int restarts;
+
+static void usage(void) __dead2;
+static int argtoi(int flag, const char *req, const char *str, int base);
+static int checkfilesys(char *filesys);
+static int chkdoreload(struct statfs *mntp);
+static struct statfs *getmntpt(const char *);
+
+int
+main(int argc, char *argv[])
+{
+ int ch;
+ struct rlimit rlimit;
+ struct itimerval itimerval;
+ int ret = 0;
+
+ sync();
+ skipclean = 1;
+ inoopt = 0;
+ while ((ch = getopt(argc, argv, "b:Bc:CdEfFm:npRrSyZ")) != -1) {
+ switch (ch) {
+ case 'b':
+ skipclean = 0;
+ bflag = argtoi('b', "number", optarg, 10);
+ printf("Alternate super block location: %d\n", bflag);
+ break;
+
+ case 'B':
+ bkgrdflag = 1;
+ break;
+
+ case 'c':
+ skipclean = 0;
+ cvtlevel = argtoi('c', "conversion level", optarg, 10);
+ if (cvtlevel < 3)
+ errx(EEXIT, "cannot do level %d conversion",
+ cvtlevel);
+ break;
+
+ case 'd':
+ debug++;
+ break;
+
+ case 'E':
+ Eflag++;
+ break;
+
+ case 'f':
+ skipclean = 0;
+ break;
+
+ case 'F':
+ bkgrdcheck = 1;
+ break;
+
+ case 'm':
+ lfmode = argtoi('m', "mode", optarg, 8);
+ if (lfmode &~ 07777)
+ errx(EEXIT, "bad mode to -m: %o", lfmode);
+ printf("** lost+found creation mode %o\n", lfmode);
+ break;
+
+ case 'n':
+ nflag++;
+ yflag = 0;
+ break;
+
+ case 'p':
+ preen++;
+ /*FALLTHROUGH*/
+
+ case 'C':
+ ckclean++;
+ break;
+
+ case 'R':
+ wantrestart = 1;
+ break;
+ case 'r':
+ inoopt++;
+ break;
+
+ case 'S':
+ surrender = 1;
+ break;
+
+ case 'y':
+ yflag++;
+ nflag = 0;
+ break;
+
+ case 'Z':
+ Zflag++;
+ break;
+
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (!argc)
+ usage();
+
+ if (signal(SIGINT, SIG_IGN) != SIG_IGN)
+ (void)signal(SIGINT, catch);
+ if (ckclean)
+ (void)signal(SIGQUIT, catchquit);
+ signal(SIGINFO, infohandler);
+ if (bkgrdflag) {
+ signal(SIGALRM, alarmhandler);
+ itimerval.it_interval.tv_sec = 5;
+ itimerval.it_interval.tv_usec = 0;
+ itimerval.it_value.tv_sec = 5;
+ itimerval.it_value.tv_usec = 0;
+ setitimer(ITIMER_REAL, &itimerval, NULL);
+ }
+ /*
+ * Push up our allowed memory limit so we can cope
+ * with huge file systems.
+ */
+ if (getrlimit(RLIMIT_DATA, &rlimit) == 0) {
+ rlimit.rlim_cur = rlimit.rlim_max;
+ (void)setrlimit(RLIMIT_DATA, &rlimit);
+ }
+ while (argc > 0) {
+ if (checkfilesys(*argv) == ERESTART)
+ continue;
+ argc--;
+ argv++;
+ }
+
+ if (returntosingle)
+ ret = 2;
+ exit(ret);
+}
+
+static int
+argtoi(int flag, const char *req, const char *str, int base)
+{
+ char *cp;
+ int ret;
+
+ ret = (int)strtol(str, &cp, base);
+ if (cp == str || *cp)
+ errx(EEXIT, "-%c flag requires a %s", flag, req);
+ return (ret);
+}
+
+/*
+ * Check the specified file system.
+ */
+/* ARGSUSED */
+static int
+checkfilesys(char *filesys)
+{
+ ufs2_daddr_t n_ffree, n_bfree;
+ struct dups *dp;
+ struct statfs *mntp;
+ struct stat snapdir;
+ struct group *grp;
+ struct iovec *iov;
+ char errmsg[255];
+ int iovlen;
+ int cylno;
+ intmax_t blks, files;
+ size_t size;
+
+ iov = NULL;
+ iovlen = 0;
+ errmsg[0] = '\0';
+ fsutilinit();
+ fsckinit();
+
+ cdevname = filesys;
+ if (debug && ckclean)
+ pwarn("starting\n");
+ /*
+ * Make best effort to get the disk name. Check first to see
+ * if it is listed among the mounted file systems. Failing that
+ * check to see if it is listed in /etc/fstab.
+ */
+ mntp = getmntpt(filesys);
+ if (mntp != NULL)
+ filesys = mntp->f_mntfromname;
+ else
+ filesys = blockcheck(filesys);
+ /*
+ * If -F flag specified, check to see whether a background check
+ * is possible and needed. If possible and needed, exit with
+ * status zero. Otherwise exit with status non-zero. A non-zero
+ * exit status will cause a foreground check to be run.
+ */
+ sblock_init();
+ if (bkgrdcheck) {
+ if ((fsreadfd = open(filesys, O_RDONLY)) < 0 || readsb(0) == 0)
+ exit(3); /* Cannot read superblock */
+ close(fsreadfd);
+ /* Earlier background failed or journaled */
+ if (sblock.fs_flags & (FS_NEEDSFSCK | FS_SUJ))
+ exit(4);
+ if ((sblock.fs_flags & FS_DOSOFTDEP) == 0)
+ exit(5); /* Not running soft updates */
+ size = MIBSIZE;
+ if (sysctlnametomib("vfs.ffs.adjrefcnt", adjrefcnt, &size) < 0)
+ exit(6); /* Lacks kernel support */
+ if ((mntp == NULL && sblock.fs_clean == 1) ||
+ (mntp != NULL && (sblock.fs_flags & FS_UNCLEAN) == 0))
+ exit(7); /* Filesystem clean, report it now */
+ exit(0);
+ }
+ if (ckclean && skipclean) {
+ /*
+ * If file system is gjournaled, check it here.
+ */
+ if ((fsreadfd = open(filesys, O_RDONLY)) < 0 || readsb(0) == 0)
+ exit(3); /* Cannot read superblock */
+ close(fsreadfd);
+ if ((sblock.fs_flags & FS_GJOURNAL) != 0) {
+ //printf("GJournaled file system detected on %s.\n",
+ // filesys);
+ if (sblock.fs_clean == 1) {
+ pwarn("FILE SYSTEM CLEAN; SKIPPING CHECKS\n");
+ exit(0);
+ }
+ if ((sblock.fs_flags & (FS_UNCLEAN | FS_NEEDSFSCK)) == 0) {
+ gjournal_check(filesys);
+ if (chkdoreload(mntp) == 0)
+ exit(0);
+ exit(4);
+ } else {
+ pfatal(
+ "UNEXPECTED INCONSISTENCY, CANNOT RUN FAST FSCK\n");
+ }
+ }
+ }
+ /*
+ * If we are to do a background check:
+ * Get the mount point information of the file system
+ * create snapshot file
+ * return created snapshot file
+ * if not found, clear bkgrdflag and proceed with normal fsck
+ */
+ if (bkgrdflag) {
+ if (mntp == NULL) {
+ bkgrdflag = 0;
+ pfatal("NOT MOUNTED, CANNOT RUN IN BACKGROUND\n");
+ } else if ((mntp->f_flags & MNT_SOFTDEP) == 0) {
+ bkgrdflag = 0;
+ pfatal(
+ "NOT USING SOFT UPDATES, CANNOT RUN IN BACKGROUND\n");
+ } else if ((mntp->f_flags & MNT_RDONLY) != 0) {
+ bkgrdflag = 0;
+ pfatal("MOUNTED READ-ONLY, CANNOT RUN IN BACKGROUND\n");
+ } else if ((fsreadfd = open(filesys, O_RDONLY)) >= 0) {
+ if (readsb(0) != 0) {
+ if (sblock.fs_flags & (FS_NEEDSFSCK | FS_SUJ)) {
+ bkgrdflag = 0;
+ pfatal(
+ "UNEXPECTED INCONSISTENCY, CANNOT RUN IN BACKGROUND\n");
+ }
+ if ((sblock.fs_flags & FS_UNCLEAN) == 0 &&
+ skipclean && ckclean) {
+ /*
+ * file system is clean;
+ * skip snapshot and report it clean
+ */
+ pwarn(
+ "FILE SYSTEM CLEAN; SKIPPING CHECKS\n");
+ goto clean;
+ }
+ }
+ close(fsreadfd);
+ }
+ if (bkgrdflag) {
+ snprintf(snapname, sizeof snapname, "%s/.snap",
+ mntp->f_mntonname);
+ if (stat(snapname, &snapdir) < 0) {
+ if (errno != ENOENT) {
+ bkgrdflag = 0;
+ pfatal(
+ "CANNOT FIND SNAPSHOT DIRECTORY %s: %s, CANNOT RUN IN BACKGROUND\n",
+ snapname, strerror(errno));
+ } else if ((grp = getgrnam("operator")) == 0 ||
+ mkdir(snapname, 0770) < 0 ||
+ chown(snapname, -1, grp->gr_gid) < 0 ||
+ chmod(snapname, 0770) < 0) {
+ bkgrdflag = 0;
+ pfatal(
+ "CANNOT CREATE SNAPSHOT DIRECTORY %s: %s, CANNOT RUN IN BACKGROUND\n",
+ snapname, strerror(errno));
+ }
+ } else if (!S_ISDIR(snapdir.st_mode)) {
+ bkgrdflag = 0;
+ pfatal(
+ "%s IS NOT A DIRECTORY, CANNOT RUN IN BACKGROUND\n",
+ snapname);
+ }
+ }
+ if (bkgrdflag) {
+ snprintf(snapname, sizeof snapname,
+ "%s/.snap/fsck_snapshot", mntp->f_mntonname);
+ build_iovec(&iov, &iovlen, "fstype", "ffs", 4);
+ build_iovec(&iov, &iovlen, "from", snapname,
+ (size_t)-1);
+ build_iovec(&iov, &iovlen, "fspath", mntp->f_mntonname,
+ (size_t)-1);
+ build_iovec(&iov, &iovlen, "errmsg", errmsg,
+ sizeof(errmsg));
+ build_iovec(&iov, &iovlen, "update", NULL, 0);
+ build_iovec(&iov, &iovlen, "snapshot", NULL, 0);
+
+ while (nmount(iov, iovlen, mntp->f_flags) < 0) {
+ if (errno == EEXIST && unlink(snapname) == 0)
+ continue;
+ bkgrdflag = 0;
+ pfatal("CANNOT CREATE SNAPSHOT %s: %s %s\n",
+ snapname, strerror(errno), errmsg);
+ break;
+ }
+ if (bkgrdflag != 0)
+ filesys = snapname;
+ }
+ }
+
+ switch (setup(filesys)) {
+ case 0:
+ if (preen)
+ pfatal("CAN'T CHECK FILE SYSTEM.");
+ return (0);
+ case -1:
+ clean:
+ pwarn("clean, %ld free ", (long)(sblock.fs_cstotal.cs_nffree +
+ sblock.fs_frag * sblock.fs_cstotal.cs_nbfree));
+ printf("(%jd frags, %jd blocks, %.1f%% fragmentation)\n",
+ (intmax_t)sblock.fs_cstotal.cs_nffree,
+ (intmax_t)sblock.fs_cstotal.cs_nbfree,
+ sblock.fs_cstotal.cs_nffree * 100.0 / sblock.fs_dsize);
+ return (0);
+ }
+ /*
+ * Determine if we can and should do journal recovery.
+ */
+ if ((sblock.fs_flags & FS_SUJ) == FS_SUJ) {
+ if ((sblock.fs_flags & FS_NEEDSFSCK) != FS_NEEDSFSCK && skipclean) {
+ if (preen || reply("USE JOURNAL")) {
+ if (suj_check(filesys) == 0) {
+ printf("\n***** FILE SYSTEM MARKED CLEAN *****\n");
+ if (chkdoreload(mntp) == 0)
+ exit(0);
+ exit(4);
+ }
+ }
+ printf("** Skipping journal, falling through to full fsck\n\n");
+ }
+ /*
+ * Write the superblock so we don't try to recover the
+ * journal on another pass.
+ */
+ sblock.fs_mtime = time(NULL);
+ sbdirty();
+ }
+
+ /*
+ * Cleared if any questions answered no. Used to decide if
+ * the superblock should be marked clean.
+ */
+ resolved = 1;
+ /*
+ * 1: scan inodes tallying blocks used
+ */
+ if (preen == 0) {
+ printf("** Last Mounted on %s\n", sblock.fs_fsmnt);
+ if (mntp != NULL && mntp->f_flags & MNT_ROOTFS)
+ printf("** Root file system\n");
+ printf("** Phase 1 - Check Blocks and Sizes\n");
+ }
+ clock_gettime(CLOCK_REALTIME_PRECISE, &startprog);
+ pass1();
+ IOstats("Pass1");
+
+ /*
+ * 1b: locate first references to duplicates, if any
+ */
+ if (duplist) {
+ if (preen || usedsoftdep)
+ pfatal("INTERNAL ERROR: dups with %s%s%s",
+ preen ? "-p" : "",
+ (preen && usedsoftdep) ? " and " : "",
+ usedsoftdep ? "softupdates" : "");
+ printf("** Phase 1b - Rescan For More DUPS\n");
+ pass1b();
+ IOstats("Pass1b");
+ }
+
+ /*
+ * 2: traverse directories from root to mark all connected directories
+ */
+ if (preen == 0)
+ printf("** Phase 2 - Check Pathnames\n");
+ pass2();
+ IOstats("Pass2");
+
+ /*
+ * 3: scan inodes looking for disconnected directories
+ */
+ if (preen == 0)
+ printf("** Phase 3 - Check Connectivity\n");
+ pass3();
+ IOstats("Pass3");
+
+ /*
+ * 4: scan inodes looking for disconnected files; check reference counts
+ */
+ if (preen == 0)
+ printf("** Phase 4 - Check Reference Counts\n");
+ pass4();
+ IOstats("Pass4");
+
+ /*
+ * 5: check and repair resource counts in cylinder groups
+ */
+ if (preen == 0)
+ printf("** Phase 5 - Check Cyl groups\n");
+ pass5();
+ IOstats("Pass5");
+
+ /*
+ * print out summary statistics
+ */
+ n_ffree = sblock.fs_cstotal.cs_nffree;
+ n_bfree = sblock.fs_cstotal.cs_nbfree;
+ files = maxino - ROOTINO - sblock.fs_cstotal.cs_nifree - n_files;
+ blks = n_blks +
+ sblock.fs_ncg * (cgdmin(&sblock, 0) - cgsblock(&sblock, 0));
+ blks += cgsblock(&sblock, 0) - cgbase(&sblock, 0);
+ blks += howmany(sblock.fs_cssize, sblock.fs_fsize);
+ blks = maxfsblock - (n_ffree + sblock.fs_frag * n_bfree) - blks;
+ if (bkgrdflag && (files > 0 || blks > 0)) {
+ countdirs = sblock.fs_cstotal.cs_ndir - countdirs;
+ pwarn("Reclaimed: %ld directories, %jd files, %jd fragments\n",
+ countdirs, files - countdirs, blks);
+ }
+ pwarn("%ld files, %jd used, %ju free ",
+ (long)n_files, (intmax_t)n_blks,
+ (uintmax_t)n_ffree + sblock.fs_frag * n_bfree);
+ printf("(%ju frags, %ju blocks, %.1f%% fragmentation)\n",
+ (uintmax_t)n_ffree, (uintmax_t)n_bfree,
+ n_ffree * 100.0 / sblock.fs_dsize);
+ if (debug) {
+ if (files < 0)
+ printf("%jd inodes missing\n", -files);
+ if (blks < 0)
+ printf("%jd blocks missing\n", -blks);
+ if (duplist != NULL) {
+ printf("The following duplicate blocks remain:");
+ for (dp = duplist; dp; dp = dp->next)
+ printf(" %jd,", (intmax_t)dp->dup);
+ printf("\n");
+ }
+ }
+ duplist = (struct dups *)0;
+ muldup = (struct dups *)0;
+ inocleanup();
+ if (fsmodified) {
+ sblock.fs_time = time(NULL);
+ sbdirty();
+ }
+ if (cvtlevel && sblk.b_dirty) {
+ /*
+ * Write out the duplicate super blocks
+ */
+ for (cylno = 0; cylno < sblock.fs_ncg; cylno++)
+ blwrite(fswritefd, (char *)&sblock,
+ fsbtodb(&sblock, cgsblock(&sblock, cylno)),
+ SBLOCKSIZE);
+ }
+ if (rerun)
+ resolved = 0;
+ finalIOstats();
+
+ /*
+ * Check to see if the file system is mounted read-write.
+ */
+ if (bkgrdflag == 0 && mntp != NULL && (mntp->f_flags & MNT_RDONLY) == 0)
+ resolved = 0;
+ ckfini(resolved);
+
+ for (cylno = 0; cylno < sblock.fs_ncg; cylno++)
+ if (inostathead[cylno].il_stat != NULL)
+ free((char *)inostathead[cylno].il_stat);
+ free((char *)inostathead);
+ inostathead = NULL;
+ if (fsmodified && !preen)
+ printf("\n***** FILE SYSTEM WAS MODIFIED *****\n");
+ if (rerun) {
+ if (wantrestart && (restarts++ < 10) &&
+ (preen || reply("RESTART")))
+ return (ERESTART);
+ printf("\n***** PLEASE RERUN FSCK *****\n");
+ }
+ if (chkdoreload(mntp) != 0) {
+ if (!fsmodified)
+ return (0);
+ if (!preen)
+ printf("\n***** REBOOT NOW *****\n");
+ sync();
+ return (4);
+ }
+ return (0);
+}
+
+static int
+chkdoreload(struct statfs *mntp)
+{
+ struct iovec *iov;
+ int iovlen;
+ char errmsg[255];
+
+ if (mntp == NULL)
+ return (0);
+
+ iov = NULL;
+ iovlen = 0;
+ errmsg[0] = '\0';
+ /*
+ * We modified a mounted file system. Do a mount update on
+ * it unless it is read-write, so we can continue using it
+ * as safely as possible.
+ */
+ if (mntp->f_flags & MNT_RDONLY) {
+ build_iovec(&iov, &iovlen, "fstype", "ffs", 4);
+ build_iovec(&iov, &iovlen, "from", mntp->f_mntfromname,
+ (size_t)-1);
+ build_iovec(&iov, &iovlen, "fspath", mntp->f_mntonname,
+ (size_t)-1);
+ build_iovec(&iov, &iovlen, "errmsg", errmsg,
+ sizeof(errmsg));
+ build_iovec(&iov, &iovlen, "update", NULL, 0);
+ build_iovec(&iov, &iovlen, "reload", NULL, 0);
+ /*
+ * XX: We need the following line until we clean up
+ * nmount parsing of root mounts and NFS root mounts.
+ */
+ build_iovec(&iov, &iovlen, "ro", NULL, 0);
+ if (nmount(iov, iovlen, mntp->f_flags) == 0) {
+ return (0);
+ }
+ pwarn("mount reload of '%s' failed: %s %s\n\n",
+ mntp->f_mntonname, strerror(errno), errmsg);
+ return (1);
+ }
+ return (0);
+}
+
+/*
+ * Get the mount point information for name.
+ */
+static struct statfs *
+getmntpt(const char *name)
+{
+ struct stat devstat, mntdevstat;
+ char device[sizeof(_PATH_DEV) - 1 + MNAMELEN];
+ char *ddevname;
+ struct statfs *mntbuf, *statfsp;
+ int i, mntsize, isdev;
+
+ if (stat(name, &devstat) != 0)
+ return (NULL);
+ if (S_ISCHR(devstat.st_mode) || S_ISBLK(devstat.st_mode))
+ isdev = 1;
+ else
+ isdev = 0;
+ mntsize = getmntinfo(&mntbuf, MNT_NOWAIT);
+ for (i = 0; i < mntsize; i++) {
+ statfsp = &mntbuf[i];
+ ddevname = statfsp->f_mntfromname;
+ if (*ddevname != '/') {
+ strcpy(device, _PATH_DEV);
+ strcat(device, ddevname);
+ strcpy(statfsp->f_mntfromname, device);
+ }
+ if (isdev == 0) {
+ if (strcmp(name, statfsp->f_mntonname))
+ continue;
+ return (statfsp);
+ }
+ if (stat(ddevname, &mntdevstat) == 0 &&
+ mntdevstat.st_rdev == devstat.st_rdev)
+ return (statfsp);
+ }
+ statfsp = NULL;
+ return (statfsp);
+}
+
+static void
+usage(void)
+{
+ (void) fprintf(stderr,
+"usage: %s [-BEFfnpry] [-b block] [-c level] [-m mode] filesystem ...\n",
+ getprogname());
+ exit(1);
+}
+
+void
+infohandler(int sig __unused)
+{
+ got_siginfo = 1;
+}
+
+void
+alarmhandler(int sig __unused)
+{
+ got_sigalarm = 1;
+}
diff --git a/sbin/fsck_ffs/pass1.c b/sbin/fsck_ffs/pass1.c
new file mode 100644
index 0000000..67fba6e
--- /dev/null
+++ b/sbin/fsck_ffs/pass1.c
@@ -0,0 +1,522 @@
+/*
+ * Copyright (c) 1980, 1986, 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.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if 0
+#ifndef lint
+static const char sccsid[] = "@(#)pass1.c 8.6 (Berkeley) 4/28/95";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/sysctl.h>
+
+#include <ufs/ufs/dinode.h>
+#include <ufs/ufs/dir.h>
+#include <ufs/ffs/fs.h>
+
+#include <err.h>
+#include <limits.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "fsck.h"
+
+static ufs2_daddr_t badblk;
+static ufs2_daddr_t dupblk;
+static ino_t lastino; /* last inode in use */
+
+static int checkinode(ino_t inumber, struct inodesc *, int rebuildcg);
+
+void
+pass1(void)
+{
+ struct inostat *info;
+ struct inodesc idesc;
+ struct bufarea *cgbp;
+ struct cg *cgp;
+ ino_t inumber, inosused, mininos;
+ ufs2_daddr_t i, cgd;
+ u_int8_t *cp;
+ int c, rebuildcg;
+
+ badblk = dupblk = lastino = 0;
+
+ /*
+ * Set file system reserved blocks in used block map.
+ */
+ for (c = 0; c < sblock.fs_ncg; c++) {
+ cgd = cgdmin(&sblock, c);
+ if (c == 0) {
+ i = cgbase(&sblock, c);
+ } else
+ i = cgsblock(&sblock, c);
+ for (; i < cgd; i++)
+ setbmap(i);
+ }
+ i = sblock.fs_csaddr;
+ cgd = i + howmany(sblock.fs_cssize, sblock.fs_fsize);
+ for (; i < cgd; i++)
+ setbmap(i);
+
+ /*
+ * Find all allocated blocks.
+ */
+ memset(&idesc, 0, sizeof(struct inodesc));
+ idesc.id_func = pass1check;
+ n_files = n_blks = 0;
+ for (c = 0; c < sblock.fs_ncg; c++) {
+ inumber = c * sblock.fs_ipg;
+ setinodebuf(inumber);
+ cgbp = cgget(c);
+ cgp = cgbp->b_un.b_cg;
+ rebuildcg = 0;
+ if (!check_cgmagic(c, cgbp))
+ rebuildcg = 1;
+ if (!rebuildcg && sblock.fs_magic == FS_UFS2_MAGIC) {
+ inosused = cgp->cg_initediblk;
+ if (inosused > sblock.fs_ipg) {
+ pfatal(
+"Too many initialized inodes (%ju > %d) in cylinder group %d\nReset to %d\n",
+ (uintmax_t)inosused,
+ sblock.fs_ipg, c, sblock.fs_ipg);
+ inosused = sblock.fs_ipg;
+ }
+ } else {
+ inosused = sblock.fs_ipg;
+ }
+ if (got_siginfo) {
+ printf("%s: phase 1: cyl group %d of %d (%d%%)\n",
+ cdevname, c, sblock.fs_ncg,
+ c * 100 / sblock.fs_ncg);
+ got_siginfo = 0;
+ }
+ if (got_sigalarm) {
+ setproctitle("%s p1 %d%%", cdevname,
+ c * 100 / sblock.fs_ncg);
+ got_sigalarm = 0;
+ }
+ /*
+ * If we are using soft updates, then we can trust the
+ * cylinder group inode allocation maps to tell us which
+ * inodes are allocated. We will scan the used inode map
+ * to find the inodes that are really in use, and then
+ * read only those inodes in from disk.
+ */
+ if ((preen || inoopt) && usedsoftdep && !rebuildcg) {
+ cp = &cg_inosused(cgp)[(inosused - 1) / CHAR_BIT];
+ for ( ; inosused > 0; inosused -= CHAR_BIT, cp--) {
+ if (*cp == 0)
+ continue;
+ for (i = 1 << (CHAR_BIT - 1); i > 0; i >>= 1) {
+ if (*cp & i)
+ break;
+ inosused--;
+ }
+ break;
+ }
+ if (inosused < 0)
+ inosused = 0;
+ }
+ /*
+ * Allocate inoinfo structures for the allocated inodes.
+ */
+ inostathead[c].il_numalloced = inosused;
+ if (inosused == 0) {
+ inostathead[c].il_stat = 0;
+ continue;
+ }
+ info = Calloc((unsigned)inosused, sizeof(struct inostat));
+ if (info == NULL)
+ errx(EEXIT, "cannot alloc %u bytes for inoinfo",
+ (unsigned)(sizeof(struct inostat) * inosused));
+ inostathead[c].il_stat = info;
+ /*
+ * Scan the allocated inodes.
+ */
+ for (i = 0; i < inosused; i++, inumber++) {
+ if (inumber < ROOTINO) {
+ (void)getnextinode(inumber, rebuildcg);
+ continue;
+ }
+ /*
+ * NULL return indicates probable end of allocated
+ * inodes during cylinder group rebuild attempt.
+ * We always keep trying until we get to the minimum
+ * valid number for this cylinder group.
+ */
+ if (checkinode(inumber, &idesc, rebuildcg) == 0 &&
+ i > cgp->cg_initediblk)
+ break;
+ }
+ /*
+ * This optimization speeds up future runs of fsck
+ * by trimming down the number of inodes in cylinder
+ * groups that formerly had many inodes but now have
+ * fewer in use.
+ */
+ mininos = roundup(inosused + INOPB(&sblock), INOPB(&sblock));
+ if (inoopt && !preen && !rebuildcg &&
+ sblock.fs_magic == FS_UFS2_MAGIC &&
+ cgp->cg_initediblk > 2 * INOPB(&sblock) &&
+ mininos < cgp->cg_initediblk) {
+ i = cgp->cg_initediblk;
+ if (mininos < 2 * INOPB(&sblock))
+ cgp->cg_initediblk = 2 * INOPB(&sblock);
+ else
+ cgp->cg_initediblk = mininos;
+ pwarn("CYLINDER GROUP %d: RESET FROM %ju TO %d %s\n",
+ c, i, cgp->cg_initediblk, "VALID INODES");
+ dirty(cgbp);
+ }
+ if (inosused < sblock.fs_ipg)
+ continue;
+ lastino += 1;
+ if (lastino < (c * sblock.fs_ipg))
+ inosused = 0;
+ else
+ inosused = lastino - (c * sblock.fs_ipg);
+ if (rebuildcg && inosused > cgp->cg_initediblk &&
+ sblock.fs_magic == FS_UFS2_MAGIC) {
+ cgp->cg_initediblk = roundup(inosused, INOPB(&sblock));
+ pwarn("CYLINDER GROUP %d: FOUND %d VALID INODES\n", c,
+ cgp->cg_initediblk);
+ }
+ /*
+ * If we were not able to determine in advance which inodes
+ * were in use, then reduce the size of the inoinfo structure
+ * to the size necessary to describe the inodes that we
+ * really found.
+ */
+ if (inumber == lastino)
+ continue;
+ inostathead[c].il_numalloced = inosused;
+ if (inosused == 0) {
+ free(inostathead[c].il_stat);
+ inostathead[c].il_stat = 0;
+ continue;
+ }
+ info = Calloc((unsigned)inosused, sizeof(struct inostat));
+ if (info == NULL)
+ errx(EEXIT, "cannot alloc %u bytes for inoinfo",
+ (unsigned)(sizeof(struct inostat) * inosused));
+ memmove(info, inostathead[c].il_stat, inosused * sizeof(*info));
+ free(inostathead[c].il_stat);
+ inostathead[c].il_stat = info;
+ }
+ freeinodebuf();
+}
+
+static int
+checkinode(ino_t inumber, struct inodesc *idesc, int rebuildcg)
+{
+ union dinode *dp;
+ off_t kernmaxfilesize;
+ ufs2_daddr_t ndb;
+ mode_t mode;
+ int j, ret, offset;
+
+ if ((dp = getnextinode(inumber, rebuildcg)) == NULL)
+ return (0);
+ mode = DIP(dp, di_mode) & IFMT;
+ if (mode == 0) {
+ if ((sblock.fs_magic == FS_UFS1_MAGIC &&
+ (memcmp(dp->dp1.di_db, ufs1_zino.di_db,
+ NDADDR * sizeof(ufs1_daddr_t)) ||
+ memcmp(dp->dp1.di_ib, ufs1_zino.di_ib,
+ NIADDR * sizeof(ufs1_daddr_t)) ||
+ dp->dp1.di_mode || dp->dp1.di_size)) ||
+ (sblock.fs_magic == FS_UFS2_MAGIC &&
+ (memcmp(dp->dp2.di_db, ufs2_zino.di_db,
+ NDADDR * sizeof(ufs2_daddr_t)) ||
+ memcmp(dp->dp2.di_ib, ufs2_zino.di_ib,
+ NIADDR * sizeof(ufs2_daddr_t)) ||
+ dp->dp2.di_mode || dp->dp2.di_size))) {
+ pfatal("PARTIALLY ALLOCATED INODE I=%lu",
+ (u_long)inumber);
+ if (reply("CLEAR") == 1) {
+ dp = ginode(inumber);
+ clearinode(dp);
+ inodirty();
+ }
+ }
+ inoinfo(inumber)->ino_state = USTATE;
+ return (1);
+ }
+ lastino = inumber;
+ /* This should match the file size limit in ffs_mountfs(). */
+ if (sblock.fs_magic == FS_UFS1_MAGIC)
+ kernmaxfilesize = (off_t)0x40000000 * sblock.fs_bsize - 1;
+ else
+ kernmaxfilesize = sblock.fs_maxfilesize;
+ if (DIP(dp, di_size) > kernmaxfilesize ||
+ DIP(dp, di_size) > sblock.fs_maxfilesize ||
+ (mode == IFDIR && DIP(dp, di_size) > MAXDIRSIZE)) {
+ if (debug)
+ printf("bad size %ju:", (uintmax_t)DIP(dp, di_size));
+ goto unknown;
+ }
+ if (!preen && mode == IFMT && reply("HOLD BAD BLOCK") == 1) {
+ dp = ginode(inumber);
+ DIP_SET(dp, di_size, sblock.fs_fsize);
+ DIP_SET(dp, di_mode, IFREG|0600);
+ inodirty();
+ }
+ if ((mode == IFBLK || mode == IFCHR || mode == IFIFO ||
+ mode == IFSOCK) && DIP(dp, di_size) != 0) {
+ if (debug)
+ printf("bad special-file size %ju:",
+ (uintmax_t)DIP(dp, di_size));
+ goto unknown;
+ }
+ if ((mode == IFBLK || mode == IFCHR) &&
+ (dev_t)DIP(dp, di_rdev) == NODEV) {
+ if (debug)
+ printf("bad special-file rdev NODEV:");
+ goto unknown;
+ }
+ ndb = howmany(DIP(dp, di_size), sblock.fs_bsize);
+ if (ndb < 0) {
+ if (debug)
+ printf("bad size %ju ndb %ju:",
+ (uintmax_t)DIP(dp, di_size), (uintmax_t)ndb);
+ goto unknown;
+ }
+ if (mode == IFBLK || mode == IFCHR)
+ ndb++;
+ if (mode == IFLNK) {
+ /*
+ * Fake ndb value so direct/indirect block checks below
+ * will detect any garbage after symlink string.
+ */
+ if (DIP(dp, di_size) < (off_t)sblock.fs_maxsymlinklen) {
+ if (sblock.fs_magic == FS_UFS1_MAGIC)
+ ndb = howmany(DIP(dp, di_size),
+ sizeof(ufs1_daddr_t));
+ else
+ ndb = howmany(DIP(dp, di_size),
+ sizeof(ufs2_daddr_t));
+ if (ndb > NDADDR) {
+ j = ndb - NDADDR;
+ for (ndb = 1; j > 1; j--)
+ ndb *= NINDIR(&sblock);
+ ndb += NDADDR;
+ }
+ }
+ }
+ for (j = ndb; ndb < NDADDR && j < NDADDR; j++)
+ if (DIP(dp, di_db[j]) != 0) {
+ if (debug)
+ printf("bad direct addr[%d]: %ju\n", j,
+ (uintmax_t)DIP(dp, di_db[j]));
+ goto unknown;
+ }
+ for (j = 0, ndb -= NDADDR; ndb > 0; j++)
+ ndb /= NINDIR(&sblock);
+ for (; j < NIADDR; j++)
+ if (DIP(dp, di_ib[j]) != 0) {
+ if (debug)
+ printf("bad indirect addr: %ju\n",
+ (uintmax_t)DIP(dp, di_ib[j]));
+ goto unknown;
+ }
+ if (ftypeok(dp) == 0)
+ goto unknown;
+ n_files++;
+ inoinfo(inumber)->ino_linkcnt = DIP(dp, di_nlink);
+ if (mode == IFDIR) {
+ if (DIP(dp, di_size) == 0)
+ inoinfo(inumber)->ino_state = DCLEAR;
+ else if (DIP(dp, di_nlink) <= 0)
+ inoinfo(inumber)->ino_state = DZLINK;
+ else
+ inoinfo(inumber)->ino_state = DSTATE;
+ cacheino(dp, inumber);
+ countdirs++;
+ } else if (DIP(dp, di_nlink) <= 0)
+ inoinfo(inumber)->ino_state = FZLINK;
+ else
+ inoinfo(inumber)->ino_state = FSTATE;
+ inoinfo(inumber)->ino_type = IFTODT(mode);
+ badblk = dupblk = 0;
+ idesc->id_number = inumber;
+ if (DIP(dp, di_flags) & SF_SNAPSHOT)
+ idesc->id_type = SNAP;
+ else
+ idesc->id_type = ADDR;
+ (void)ckinode(dp, idesc);
+ if (sblock.fs_magic == FS_UFS2_MAGIC && dp->dp2.di_extsize > 0) {
+ idesc->id_type = ADDR;
+ ndb = howmany(dp->dp2.di_extsize, sblock.fs_bsize);
+ for (j = 0; j < NXADDR; j++) {
+ if (--ndb == 0 &&
+ (offset = blkoff(&sblock, dp->dp2.di_extsize)) != 0)
+ idesc->id_numfrags = numfrags(&sblock,
+ fragroundup(&sblock, offset));
+ else
+ idesc->id_numfrags = sblock.fs_frag;
+ if (dp->dp2.di_extb[j] == 0)
+ continue;
+ idesc->id_blkno = dp->dp2.di_extb[j];
+ ret = (*idesc->id_func)(idesc);
+ if (ret & STOP)
+ break;
+ }
+ }
+ if (sblock.fs_magic == FS_UFS2_MAGIC)
+ eascan(idesc, &dp->dp2);
+ idesc->id_entryno *= btodb(sblock.fs_fsize);
+ if (DIP(dp, di_blocks) != idesc->id_entryno) {
+ pwarn("INCORRECT BLOCK COUNT I=%lu (%ju should be %ju)",
+ (u_long)inumber, (uintmax_t)DIP(dp, di_blocks),
+ (uintmax_t)idesc->id_entryno);
+ if (preen)
+ printf(" (CORRECTED)\n");
+ else if (reply("CORRECT") == 0)
+ return (1);
+ if (bkgrdflag == 0) {
+ dp = ginode(inumber);
+ DIP_SET(dp, di_blocks, idesc->id_entryno);
+ inodirty();
+ } else {
+ cmd.value = idesc->id_number;
+ cmd.size = idesc->id_entryno - DIP(dp, di_blocks);
+ if (debug)
+ printf("adjblkcnt ino %ju amount %lld\n",
+ (uintmax_t)cmd.value, (long long)cmd.size);
+ if (sysctl(adjblkcnt, MIBSIZE, 0, 0,
+ &cmd, sizeof cmd) == -1)
+ rwerror("ADJUST INODE BLOCK COUNT", cmd.value);
+ }
+ }
+ return (1);
+unknown:
+ pfatal("UNKNOWN FILE TYPE I=%lu", (u_long)inumber);
+ inoinfo(inumber)->ino_state = FCLEAR;
+ if (reply("CLEAR") == 1) {
+ inoinfo(inumber)->ino_state = USTATE;
+ dp = ginode(inumber);
+ clearinode(dp);
+ inodirty();
+ }
+ return (1);
+}
+
+int
+pass1check(struct inodesc *idesc)
+{
+ int res = KEEPON;
+ int anyout, nfrags;
+ ufs2_daddr_t blkno = idesc->id_blkno;
+ struct dups *dlp;
+ struct dups *new;
+
+ if (idesc->id_type == SNAP) {
+ if (blkno == BLK_NOCOPY)
+ return (KEEPON);
+ if (idesc->id_number == cursnapshot) {
+ if (blkno == blkstofrags(&sblock, idesc->id_lbn))
+ return (KEEPON);
+ if (blkno == BLK_SNAP) {
+ blkno = blkstofrags(&sblock, idesc->id_lbn);
+ idesc->id_entryno -= idesc->id_numfrags;
+ }
+ } else {
+ if (blkno == BLK_SNAP)
+ return (KEEPON);
+ }
+ }
+ if ((anyout = chkrange(blkno, idesc->id_numfrags)) != 0) {
+ blkerror(idesc->id_number, "BAD", blkno);
+ if (badblk++ >= MAXBAD) {
+ pwarn("EXCESSIVE BAD BLKS I=%lu",
+ (u_long)idesc->id_number);
+ if (preen)
+ printf(" (SKIPPING)\n");
+ else if (reply("CONTINUE") == 0) {
+ ckfini(0);
+ exit(EEXIT);
+ }
+ rerun = 1;
+ return (STOP);
+ }
+ }
+ for (nfrags = idesc->id_numfrags; nfrags > 0; blkno++, nfrags--) {
+ if (anyout && chkrange(blkno, 1)) {
+ res = SKIP;
+ } else if (!testbmap(blkno)) {
+ n_blks++;
+ setbmap(blkno);
+ } else {
+ blkerror(idesc->id_number, "DUP", blkno);
+ if (dupblk++ >= MAXDUP) {
+ pwarn("EXCESSIVE DUP BLKS I=%lu",
+ (u_long)idesc->id_number);
+ if (preen)
+ printf(" (SKIPPING)\n");
+ else if (reply("CONTINUE") == 0) {
+ ckfini(0);
+ exit(EEXIT);
+ }
+ rerun = 1;
+ return (STOP);
+ }
+ new = (struct dups *)Malloc(sizeof(struct dups));
+ if (new == NULL) {
+ pfatal("DUP TABLE OVERFLOW.");
+ if (reply("CONTINUE") == 0) {
+ ckfini(0);
+ exit(EEXIT);
+ }
+ rerun = 1;
+ return (STOP);
+ }
+ new->dup = blkno;
+ if (muldup == 0) {
+ duplist = muldup = new;
+ new->next = 0;
+ } else {
+ new->next = muldup->next;
+ muldup->next = new;
+ }
+ for (dlp = duplist; dlp != muldup; dlp = dlp->next)
+ if (dlp->dup == blkno)
+ break;
+ if (dlp == muldup && dlp->dup != blkno)
+ muldup = new;
+ }
+ /*
+ * count the number of blocks found in id_entryno
+ */
+ idesc->id_entryno++;
+ }
+ return (res);
+}
diff --git a/sbin/fsck_ffs/pass1b.c b/sbin/fsck_ffs/pass1b.c
new file mode 100644
index 0000000..69a23c2
--- /dev/null
+++ b/sbin/fsck_ffs/pass1b.c
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 1980, 1986, 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.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if 0
+#ifndef lint
+static const char sccsid[] = "@(#)pass1b.c 8.4 (Berkeley) 4/28/95";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+
+#include <ufs/ufs/dinode.h>
+#include <ufs/ffs/fs.h>
+
+#include <string.h>
+
+#include "fsck.h"
+
+static struct dups *duphead;
+static int pass1bcheck(struct inodesc *);
+
+void
+pass1b(void)
+{
+ int c, i;
+ union dinode *dp;
+ struct inodesc idesc;
+ ino_t inumber;
+
+ memset(&idesc, 0, sizeof(struct inodesc));
+ idesc.id_type = ADDR;
+ idesc.id_func = pass1bcheck;
+ duphead = duplist;
+ inumber = 0;
+ for (c = 0; c < sblock.fs_ncg; c++) {
+ if (got_siginfo) {
+ printf("%s: phase 1b: cyl group %d of %d (%d%%)\n",
+ cdevname, c, sblock.fs_ncg,
+ c * 100 / sblock.fs_ncg);
+ got_siginfo = 0;
+ }
+ if (got_sigalarm) {
+ setproctitle("%s p1b %d%%", cdevname,
+ c * 100 / sblock.fs_ncg);
+ got_sigalarm = 0;
+ }
+ for (i = 0; i < sblock.fs_ipg; i++, inumber++) {
+ if (inumber < ROOTINO)
+ continue;
+ dp = ginode(inumber);
+ if (dp == NULL)
+ continue;
+ idesc.id_number = inumber;
+ if (inoinfo(inumber)->ino_state != USTATE &&
+ (ckinode(dp, &idesc) & STOP)) {
+ rerun = 1;
+ return;
+ }
+ }
+ }
+}
+
+static int
+pass1bcheck(struct inodesc *idesc)
+{
+ struct dups *dlp;
+ int nfrags, res = KEEPON;
+ ufs2_daddr_t blkno = idesc->id_blkno;
+
+ for (nfrags = idesc->id_numfrags; nfrags > 0; blkno++, nfrags--) {
+ if (chkrange(blkno, 1))
+ res = SKIP;
+ for (dlp = duphead; dlp; dlp = dlp->next) {
+ if (dlp->dup == blkno) {
+ blkerror(idesc->id_number, "DUP", blkno);
+ dlp->dup = duphead->dup;
+ duphead->dup = blkno;
+ duphead = duphead->next;
+ }
+ if (dlp == muldup)
+ break;
+ }
+ if (muldup == 0 || duphead == muldup->next) {
+ rerun = 1;
+ return (STOP);
+ }
+ }
+ return (res);
+}
diff --git a/sbin/fsck_ffs/pass2.c b/sbin/fsck_ffs/pass2.c
new file mode 100644
index 0000000..82c3b94
--- /dev/null
+++ b/sbin/fsck_ffs/pass2.c
@@ -0,0 +1,668 @@
+/*
+ * Copyright (c) 1980, 1986, 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.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if 0
+#ifndef lint
+static const char sccsid[] = "@(#)pass2.c 8.9 (Berkeley) 4/28/95";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/sysctl.h>
+
+#include <ufs/ufs/dinode.h>
+#include <ufs/ufs/dir.h>
+#include <ufs/ffs/fs.h>
+
+#include <err.h>
+#include <errno.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "fsck.h"
+
+#define MINDIRSIZE (sizeof (struct dirtemplate))
+
+static int fix_extraneous(struct inoinfo *, struct inodesc *);
+static int deleteentry(struct inodesc *);
+static int blksort(const void *, const void *);
+static int pass2check(struct inodesc *);
+
+void
+pass2(void)
+{
+ union dinode *dp;
+ struct inoinfo **inpp, *inp;
+ struct inoinfo **inpend;
+ struct inodesc curino;
+ union dinode dino;
+ int i;
+ char pathbuf[MAXPATHLEN + 1];
+
+ switch (inoinfo(ROOTINO)->ino_state) {
+
+ case USTATE:
+ pfatal("ROOT INODE UNALLOCATED");
+ if (reply("ALLOCATE") == 0) {
+ ckfini(0);
+ exit(EEXIT);
+ }
+ if (allocdir(ROOTINO, ROOTINO, 0755) != ROOTINO)
+ errx(EEXIT, "CANNOT ALLOCATE ROOT INODE");
+ break;
+
+ case DCLEAR:
+ pfatal("DUPS/BAD IN ROOT INODE");
+ if (reply("REALLOCATE")) {
+ freeino(ROOTINO);
+ if (allocdir(ROOTINO, ROOTINO, 0755) != ROOTINO)
+ errx(EEXIT, "CANNOT ALLOCATE ROOT INODE");
+ break;
+ }
+ if (reply("CONTINUE") == 0) {
+ ckfini(0);
+ exit(EEXIT);
+ }
+ break;
+
+ case FSTATE:
+ case FCLEAR:
+ case FZLINK:
+ pfatal("ROOT INODE NOT DIRECTORY");
+ if (reply("REALLOCATE")) {
+ freeino(ROOTINO);
+ if (allocdir(ROOTINO, ROOTINO, 0755) != ROOTINO)
+ errx(EEXIT, "CANNOT ALLOCATE ROOT INODE");
+ break;
+ }
+ if (reply("FIX") == 0) {
+ ckfini(0);
+ exit(EEXIT);
+ }
+ dp = ginode(ROOTINO);
+ DIP_SET(dp, di_mode, DIP(dp, di_mode) & ~IFMT);
+ DIP_SET(dp, di_mode, DIP(dp, di_mode) | IFDIR);
+ inodirty();
+ break;
+
+ case DSTATE:
+ case DZLINK:
+ break;
+
+ default:
+ errx(EEXIT, "BAD STATE %d FOR ROOT INODE",
+ inoinfo(ROOTINO)->ino_state);
+ }
+ inoinfo(ROOTINO)->ino_state = DFOUND;
+ inoinfo(WINO)->ino_state = FSTATE;
+ inoinfo(WINO)->ino_type = DT_WHT;
+ /*
+ * Sort the directory list into disk block order.
+ */
+ qsort((char *)inpsort, (size_t)inplast, sizeof *inpsort, blksort);
+ /*
+ * Check the integrity of each directory.
+ */
+ memset(&curino, 0, sizeof(struct inodesc));
+ curino.id_type = DATA;
+ curino.id_func = pass2check;
+ inpend = &inpsort[inplast];
+ for (inpp = inpsort; inpp < inpend; inpp++) {
+ if (got_siginfo) {
+ printf("%s: phase 2: dir %td of %d (%d%%)\n", cdevname,
+ inpp - inpsort, (int)inplast,
+ (int)((inpp - inpsort) * 100 / inplast));
+ got_siginfo = 0;
+ }
+ if (got_sigalarm) {
+ setproctitle("%s p2 %d%%", cdevname,
+ (int)((inpp - inpsort) * 100 / inplast));
+ got_sigalarm = 0;
+ }
+ inp = *inpp;
+ if (inp->i_isize == 0)
+ continue;
+ if (inp->i_isize < MINDIRSIZE) {
+ direrror(inp->i_number, "DIRECTORY TOO SHORT");
+ inp->i_isize = roundup(MINDIRSIZE, DIRBLKSIZ);
+ if (reply("FIX") == 1) {
+ dp = ginode(inp->i_number);
+ DIP_SET(dp, di_size, inp->i_isize);
+ inodirty();
+ }
+ } else if ((inp->i_isize & (DIRBLKSIZ - 1)) != 0) {
+ getpathname(pathbuf, inp->i_number, inp->i_number);
+ if (usedsoftdep)
+ pfatal("%s %s: LENGTH %jd NOT MULTIPLE OF %d",
+ "DIRECTORY", pathbuf,
+ (intmax_t)inp->i_isize, DIRBLKSIZ);
+ else
+ pwarn("%s %s: LENGTH %jd NOT MULTIPLE OF %d",
+ "DIRECTORY", pathbuf,
+ (intmax_t)inp->i_isize, DIRBLKSIZ);
+ if (preen)
+ printf(" (ADJUSTED)\n");
+ inp->i_isize = roundup(inp->i_isize, DIRBLKSIZ);
+ if (preen || reply("ADJUST") == 1) {
+ dp = ginode(inp->i_number);
+ DIP_SET(dp, di_size,
+ roundup(inp->i_isize, DIRBLKSIZ));
+ inodirty();
+ }
+ }
+ dp = &dino;
+ memset(dp, 0, sizeof(struct ufs2_dinode));
+ DIP_SET(dp, di_mode, IFDIR);
+ DIP_SET(dp, di_size, inp->i_isize);
+ for (i = 0;
+ i < (inp->i_numblks<NDADDR ? inp->i_numblks : NDADDR);
+ i++)
+ DIP_SET(dp, di_db[i], inp->i_blks[i]);
+ if (inp->i_numblks > NDADDR)
+ for (i = 0; i < NIADDR; i++)
+ DIP_SET(dp, di_ib[i], inp->i_blks[NDADDR + i]);
+ curino.id_number = inp->i_number;
+ curino.id_parent = inp->i_parent;
+ (void)ckinode(dp, &curino);
+ }
+ /*
+ * Now that the parents of all directories have been found,
+ * make another pass to verify the value of `..'
+ */
+ for (inpp = inpsort; inpp < inpend; inpp++) {
+ inp = *inpp;
+ if (inp->i_parent == 0 || inp->i_isize == 0)
+ continue;
+ if (inoinfo(inp->i_parent)->ino_state == DFOUND &&
+ INO_IS_DUNFOUND(inp->i_number))
+ inoinfo(inp->i_number)->ino_state = DFOUND;
+ if (inp->i_dotdot == inp->i_parent ||
+ inp->i_dotdot == (ino_t)-1)
+ continue;
+ if (inp->i_dotdot == 0) {
+ inp->i_dotdot = inp->i_parent;
+ fileerror(inp->i_parent, inp->i_number, "MISSING '..'");
+ if (reply("FIX") == 0)
+ continue;
+ (void)makeentry(inp->i_number, inp->i_parent, "..");
+ inoinfo(inp->i_parent)->ino_linkcnt--;
+ continue;
+ }
+ /*
+ * Here we have:
+ * inp->i_number is directory with bad ".." in it.
+ * inp->i_dotdot is current value of "..".
+ * inp->i_parent is directory to which ".." should point.
+ */
+ getpathname(pathbuf, inp->i_parent, inp->i_number);
+ printf("BAD INODE NUMBER FOR '..' in DIR I=%ju (%s)\n",
+ (uintmax_t)inp->i_number, pathbuf);
+ getpathname(pathbuf, inp->i_dotdot, inp->i_dotdot);
+ printf("CURRENTLY POINTS TO I=%ju (%s), ",
+ (uintmax_t)inp->i_dotdot, pathbuf);
+ getpathname(pathbuf, inp->i_parent, inp->i_parent);
+ printf("SHOULD POINT TO I=%ju (%s)",
+ (uintmax_t)inp->i_parent, pathbuf);
+ if (cursnapshot != 0) {
+ /*
+ * We need to:
+ * setcwd(inp->i_number);
+ * setdotdot(inp->i_dotdot, inp->i_parent);
+ */
+ cmd.value = inp->i_number;
+ if (sysctlbyname("vfs.ffs.setcwd", 0, 0,
+ &cmd, sizeof cmd) == -1) {
+ /* kernel lacks support for these functions */
+ printf(" (IGNORED)\n");
+ continue;
+ }
+ cmd.value = inp->i_dotdot; /* verify same value */
+ cmd.size = inp->i_parent; /* new parent */
+ if (sysctlbyname("vfs.ffs.setdotdot", 0, 0,
+ &cmd, sizeof cmd) == -1) {
+ printf(" (FIX FAILED: %s)\n", strerror(errno));
+ continue;
+ }
+ printf(" (FIXED)\n");
+ inoinfo(inp->i_parent)->ino_linkcnt--;
+ inp->i_dotdot = inp->i_parent;
+ continue;
+ }
+ if (preen)
+ printf(" (FIXED)\n");
+ else if (reply("FIX") == 0)
+ continue;
+ inoinfo(inp->i_dotdot)->ino_linkcnt++;
+ inoinfo(inp->i_parent)->ino_linkcnt--;
+ inp->i_dotdot = inp->i_parent;
+ (void)changeino(inp->i_number, "..", inp->i_parent);
+ }
+ /*
+ * Mark all the directories that can be found from the root.
+ */
+ propagate();
+}
+
+static int
+pass2check(struct inodesc *idesc)
+{
+ struct direct *dirp = idesc->id_dirp;
+ char dirname[MAXPATHLEN + 1];
+ struct inoinfo *inp;
+ int n, entrysize, ret = 0;
+ union dinode *dp;
+ const char *errmsg;
+ struct direct proto;
+
+ /*
+ * check for "."
+ */
+ if (dirp->d_ino > maxino)
+ goto chk2;
+ if (idesc->id_entryno != 0)
+ goto chk1;
+ if (dirp->d_ino != 0 && strcmp(dirp->d_name, ".") == 0) {
+ if (dirp->d_ino != idesc->id_number) {
+ direrror(idesc->id_number, "BAD INODE NUMBER FOR '.'");
+ dirp->d_ino = idesc->id_number;
+ if (reply("FIX") == 1)
+ ret |= ALTERED;
+ }
+ if (dirp->d_type != DT_DIR) {
+ direrror(idesc->id_number, "BAD TYPE VALUE FOR '.'");
+ dirp->d_type = DT_DIR;
+ if (reply("FIX") == 1)
+ ret |= ALTERED;
+ }
+ goto chk1;
+ }
+ direrror(idesc->id_number, "MISSING '.'");
+ proto.d_ino = idesc->id_number;
+ proto.d_type = DT_DIR;
+ proto.d_namlen = 1;
+ (void)strcpy(proto.d_name, ".");
+ entrysize = DIRSIZ(0, &proto);
+ if (dirp->d_ino != 0 && strcmp(dirp->d_name, "..") != 0) {
+ pfatal("CANNOT FIX, FIRST ENTRY IN DIRECTORY CONTAINS %s\n",
+ dirp->d_name);
+ } else if (dirp->d_reclen < entrysize) {
+ pfatal("CANNOT FIX, INSUFFICIENT SPACE TO ADD '.'\n");
+ } else if (dirp->d_reclen < 2 * entrysize) {
+ proto.d_reclen = dirp->d_reclen;
+ memmove(dirp, &proto, (size_t)entrysize);
+ if (reply("FIX") == 1)
+ ret |= ALTERED;
+ } else {
+ n = dirp->d_reclen - entrysize;
+ proto.d_reclen = entrysize;
+ memmove(dirp, &proto, (size_t)entrysize);
+ idesc->id_entryno++;
+ inoinfo(dirp->d_ino)->ino_linkcnt--;
+ dirp = (struct direct *)((char *)(dirp) + entrysize);
+ memset(dirp, 0, (size_t)n);
+ dirp->d_reclen = n;
+ if (reply("FIX") == 1)
+ ret |= ALTERED;
+ }
+chk1:
+ if (idesc->id_entryno > 1)
+ goto chk2;
+ inp = getinoinfo(idesc->id_number);
+ proto.d_ino = inp->i_parent;
+ proto.d_type = DT_DIR;
+ proto.d_namlen = 2;
+ (void)strcpy(proto.d_name, "..");
+ entrysize = DIRSIZ(0, &proto);
+ if (idesc->id_entryno == 0) {
+ n = DIRSIZ(0, dirp);
+ if (dirp->d_reclen < n + entrysize)
+ goto chk2;
+ proto.d_reclen = dirp->d_reclen - n;
+ dirp->d_reclen = n;
+ idesc->id_entryno++;
+ inoinfo(dirp->d_ino)->ino_linkcnt--;
+ dirp = (struct direct *)((char *)(dirp) + n);
+ memset(dirp, 0, (size_t)proto.d_reclen);
+ dirp->d_reclen = proto.d_reclen;
+ }
+ if (dirp->d_ino != 0 && strcmp(dirp->d_name, "..") == 0) {
+ inp->i_dotdot = dirp->d_ino;
+ if (dirp->d_type != DT_DIR) {
+ direrror(idesc->id_number, "BAD TYPE VALUE FOR '..'");
+ dirp->d_type = DT_DIR;
+ if (reply("FIX") == 1)
+ ret |= ALTERED;
+ }
+ goto chk2;
+ }
+ if (dirp->d_ino != 0 && strcmp(dirp->d_name, ".") != 0) {
+ fileerror(inp->i_parent, idesc->id_number, "MISSING '..'");
+ pfatal("CANNOT FIX, SECOND ENTRY IN DIRECTORY CONTAINS %s\n",
+ dirp->d_name);
+ inp->i_dotdot = (ino_t)-1;
+ } else if (dirp->d_reclen < entrysize) {
+ fileerror(inp->i_parent, idesc->id_number, "MISSING '..'");
+ pfatal("CANNOT FIX, INSUFFICIENT SPACE TO ADD '..'\n");
+ inp->i_dotdot = (ino_t)-1;
+ } else if (inp->i_parent != 0) {
+ /*
+ * We know the parent, so fix now.
+ */
+ inp->i_dotdot = inp->i_parent;
+ fileerror(inp->i_parent, idesc->id_number, "MISSING '..'");
+ proto.d_reclen = dirp->d_reclen;
+ memmove(dirp, &proto, (size_t)entrysize);
+ if (reply("FIX") == 1)
+ ret |= ALTERED;
+ }
+ idesc->id_entryno++;
+ if (dirp->d_ino != 0)
+ inoinfo(dirp->d_ino)->ino_linkcnt--;
+ return (ret|KEEPON);
+chk2:
+ if (dirp->d_ino == 0)
+ return (ret|KEEPON);
+ if (dirp->d_namlen <= 2 &&
+ dirp->d_name[0] == '.' &&
+ idesc->id_entryno >= 2) {
+ if (dirp->d_namlen == 1) {
+ direrror(idesc->id_number, "EXTRA '.' ENTRY");
+ dirp->d_ino = 0;
+ if (reply("FIX") == 1)
+ ret |= ALTERED;
+ return (KEEPON | ret);
+ }
+ if (dirp->d_name[1] == '.') {
+ direrror(idesc->id_number, "EXTRA '..' ENTRY");
+ dirp->d_ino = 0;
+ if (reply("FIX") == 1)
+ ret |= ALTERED;
+ return (KEEPON | ret);
+ }
+ }
+ idesc->id_entryno++;
+ n = 0;
+ if (dirp->d_ino > maxino) {
+ fileerror(idesc->id_number, dirp->d_ino, "I OUT OF RANGE");
+ n = reply("REMOVE");
+ } else if (((dirp->d_ino == WINO && dirp->d_type != DT_WHT) ||
+ (dirp->d_ino != WINO && dirp->d_type == DT_WHT))) {
+ fileerror(idesc->id_number, dirp->d_ino, "BAD WHITEOUT ENTRY");
+ dirp->d_ino = WINO;
+ dirp->d_type = DT_WHT;
+ if (reply("FIX") == 1)
+ ret |= ALTERED;
+ } else {
+again:
+ switch (inoinfo(dirp->d_ino)->ino_state) {
+ case USTATE:
+ if (idesc->id_entryno <= 2)
+ break;
+ fileerror(idesc->id_number, dirp->d_ino, "UNALLOCATED");
+ n = reply("REMOVE");
+ break;
+
+ case DCLEAR:
+ case FCLEAR:
+ if (idesc->id_entryno <= 2)
+ break;
+ if (inoinfo(dirp->d_ino)->ino_state == FCLEAR)
+ errmsg = "DUP/BAD";
+ else if (!preen && !usedsoftdep)
+ errmsg = "ZERO LENGTH DIRECTORY";
+ else if (cursnapshot == 0) {
+ n = 1;
+ break;
+ } else {
+ getpathname(dirname, idesc->id_number,
+ dirp->d_ino);
+ pwarn("ZERO LENGTH DIRECTORY %s I=%ju",
+ dirname, (uintmax_t)dirp->d_ino);
+ /*
+ * We need to:
+ * setcwd(idesc->id_parent);
+ * rmdir(dirp->d_name);
+ */
+ cmd.value = idesc->id_number;
+ if (sysctlbyname("vfs.ffs.setcwd", 0, 0,
+ &cmd, sizeof cmd) == -1) {
+ /* kernel lacks support */
+ printf(" (IGNORED)\n");
+ n = 1;
+ break;
+ }
+ if (rmdir(dirp->d_name) == -1) {
+ printf(" (REMOVAL FAILED: %s)\n",
+ strerror(errno));
+ n = 1;
+ break;
+ }
+ /* ".." reference to parent is removed */
+ inoinfo(idesc->id_number)->ino_linkcnt--;
+ printf(" (REMOVED)\n");
+ break;
+ }
+ fileerror(idesc->id_number, dirp->d_ino, errmsg);
+ if ((n = reply("REMOVE")) == 1)
+ break;
+ dp = ginode(dirp->d_ino);
+ inoinfo(dirp->d_ino)->ino_state =
+ (DIP(dp, di_mode) & IFMT) == IFDIR ? DSTATE : FSTATE;
+ inoinfo(dirp->d_ino)->ino_linkcnt = DIP(dp, di_nlink);
+ goto again;
+
+ case DSTATE:
+ case DZLINK:
+ if (inoinfo(idesc->id_number)->ino_state == DFOUND)
+ inoinfo(dirp->d_ino)->ino_state = DFOUND;
+ /* FALLTHROUGH */
+
+ case DFOUND:
+ inp = getinoinfo(dirp->d_ino);
+ if (idesc->id_entryno > 2) {
+ if (inp->i_parent == 0)
+ inp->i_parent = idesc->id_number;
+ else if ((n = fix_extraneous(inp, idesc)) == 1)
+ break;
+ }
+ /* FALLTHROUGH */
+
+ case FSTATE:
+ case FZLINK:
+ if (dirp->d_type != inoinfo(dirp->d_ino)->ino_type) {
+ fileerror(idesc->id_number, dirp->d_ino,
+ "BAD TYPE VALUE");
+ dirp->d_type = inoinfo(dirp->d_ino)->ino_type;
+ if (reply("FIX") == 1)
+ ret |= ALTERED;
+ }
+ inoinfo(dirp->d_ino)->ino_linkcnt--;
+ break;
+
+ default:
+ errx(EEXIT, "BAD STATE %d FOR INODE I=%ju",
+ inoinfo(dirp->d_ino)->ino_state,
+ (uintmax_t)dirp->d_ino);
+ }
+ }
+ if (n == 0)
+ return (ret|KEEPON);
+ dirp->d_ino = 0;
+ return (ret|KEEPON|ALTERED);
+}
+
+static int
+fix_extraneous(struct inoinfo *inp, struct inodesc *idesc)
+{
+ char *cp;
+ struct inodesc dotdesc;
+ char oldname[MAXPATHLEN + 1];
+ char newname[MAXPATHLEN + 1];
+
+ /*
+ * If we have not yet found "..", look it up now so we know
+ * which inode the directory itself believes is its parent.
+ */
+ if (inp->i_dotdot == 0) {
+ memset(&dotdesc, 0, sizeof(struct inodesc));
+ dotdesc.id_type = DATA;
+ dotdesc.id_number = idesc->id_dirp->d_ino;
+ dotdesc.id_func = findino;
+ dotdesc.id_name = strdup("..");
+ if ((ckinode(ginode(dotdesc.id_number), &dotdesc) & FOUND))
+ inp->i_dotdot = dotdesc.id_parent;
+ }
+ /*
+ * We have the previously found old name (inp->i_parent) and the
+ * just found new name (idesc->id_number). We have five cases:
+ * 1) ".." is missing - can remove either name, choose to delete
+ * new one and let fsck create ".." pointing to old name.
+ * 2) Both new and old are in same directory, choose to delete
+ * the new name and let fsck fix ".." if it is wrong.
+ * 3) ".." does not point to the new name, so delete it and let
+ * fsck fix ".." to point to the old one if it is wrong.
+ * 4) ".." points to the old name only, so delete the new one.
+ * 5) ".." points to the new name only, so delete the old one.
+ *
+ * For cases 1-4 we eliminate the new name;
+ * for case 5 we eliminate the old name.
+ */
+ if (inp->i_dotdot == 0 || /* Case 1 */
+ idesc->id_number == inp->i_parent || /* Case 2 */
+ inp->i_dotdot != idesc->id_number || /* Case 3 */
+ inp->i_dotdot == inp->i_parent) { /* Case 4 */
+ getpathname(newname, idesc->id_number, idesc->id_number);
+ if (strcmp(newname, "/") != 0)
+ strcat (newname, "/");
+ strcat(newname, idesc->id_dirp->d_name);
+ getpathname(oldname, inp->i_number, inp->i_number);
+ pwarn("%s IS AN EXTRANEOUS HARD LINK TO DIRECTORY %s",
+ newname, oldname);
+ if (cursnapshot != 0) {
+ /*
+ * We need to
+ * setcwd(idesc->id_number);
+ * unlink(idesc->id_dirp->d_name);
+ */
+ cmd.value = idesc->id_number;
+ if (sysctlbyname("vfs.ffs.setcwd", 0, 0,
+ &cmd, sizeof cmd) == -1) {
+ printf(" (IGNORED)\n");
+ return (0);
+ }
+ cmd.value = (intptr_t)idesc->id_dirp->d_name;
+ cmd.size = inp->i_number; /* verify same name */
+ if (sysctlbyname("vfs.ffs.unlink", 0, 0,
+ &cmd, sizeof cmd) == -1) {
+ printf(" (UNLINK FAILED: %s)\n",
+ strerror(errno));
+ return (0);
+ }
+ printf(" (REMOVED)\n");
+ return (0);
+ }
+ if (preen) {
+ printf(" (REMOVED)\n");
+ return (1);
+ }
+ return (reply("REMOVE"));
+ }
+ /*
+ * None of the first four cases above, so must be case (5).
+ * Eliminate the old name and make the new the name the parent.
+ */
+ getpathname(oldname, inp->i_parent, inp->i_number);
+ getpathname(newname, inp->i_number, inp->i_number);
+ pwarn("%s IS AN EXTRANEOUS HARD LINK TO DIRECTORY %s", oldname,
+ newname);
+ if (cursnapshot != 0) {
+ /*
+ * We need to
+ * setcwd(inp->i_parent);
+ * unlink(last component of oldname pathname);
+ */
+ cmd.value = inp->i_parent;
+ if (sysctlbyname("vfs.ffs.setcwd", 0, 0,
+ &cmd, sizeof cmd) == -1) {
+ printf(" (IGNORED)\n");
+ return (0);
+ }
+ if ((cp = strchr(oldname, '/')) == NULL) {
+ printf(" (IGNORED)\n");
+ return (0);
+ }
+ cmd.value = (intptr_t)(cp + 1);
+ cmd.size = inp->i_number; /* verify same name */
+ if (sysctlbyname("vfs.ffs.unlink", 0, 0,
+ &cmd, sizeof cmd) == -1) {
+ printf(" (UNLINK FAILED: %s)\n",
+ strerror(errno));
+ return (0);
+ }
+ printf(" (REMOVED)\n");
+ inp->i_parent = idesc->id_number; /* reparent to correct dir */
+ return (0);
+ }
+ if (!preen && !reply("REMOVE"))
+ return (0);
+ memset(&dotdesc, 0, sizeof(struct inodesc));
+ dotdesc.id_type = DATA;
+ dotdesc.id_number = inp->i_parent; /* directory in which name appears */
+ dotdesc.id_parent = inp->i_number; /* inode number in entry to delete */
+ dotdesc.id_func = deleteentry;
+ if ((ckinode(ginode(dotdesc.id_number), &dotdesc) & FOUND) && preen)
+ printf(" (REMOVED)\n");
+ inp->i_parent = idesc->id_number; /* reparent to correct directory */
+ inoinfo(inp->i_number)->ino_linkcnt++; /* name gone, return reference */
+ return (0);
+}
+
+static int
+deleteentry(struct inodesc *idesc)
+{
+ struct direct *dirp = idesc->id_dirp;
+
+ if (idesc->id_entryno++ < 2 || dirp->d_ino != idesc->id_parent)
+ return (KEEPON);
+ dirp->d_ino = 0;
+ return (ALTERED|STOP|FOUND);
+}
+
+/*
+ * Routine to sort disk blocks.
+ */
+static int
+blksort(const void *arg1, const void *arg2)
+{
+
+ return ((*(struct inoinfo * const *)arg1)->i_blks[0] -
+ (*(struct inoinfo * const *)arg2)->i_blks[0]);
+}
diff --git a/sbin/fsck_ffs/pass3.c b/sbin/fsck_ffs/pass3.c
new file mode 100644
index 0000000..22309cb
--- /dev/null
+++ b/sbin/fsck_ffs/pass3.c
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 1980, 1986, 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.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if 0
+#ifndef lint
+static const char sccsid[] = "@(#)pass3.c 8.2 (Berkeley) 4/27/95";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+
+#include <ufs/ufs/dinode.h>
+#include <ufs/ufs/dir.h>
+#include <ufs/ffs/fs.h>
+
+#include <string.h>
+
+#include "fsck.h"
+
+void
+pass3(void)
+{
+ struct inoinfo *inp;
+ int loopcnt, inpindex, state;
+ ino_t orphan;
+ struct inodesc idesc;
+ char namebuf[MAXNAMLEN+1];
+
+ for (inpindex = inplast - 1; inpindex >= 0; inpindex--) {
+ if (got_siginfo) {
+ printf("%s: phase 3: dir %d of %d (%d%%)\n", cdevname,
+ (int)(inplast - inpindex - 1), (int)inplast,
+ (int)((inplast - inpindex - 1) * 100 / inplast));
+ got_siginfo = 0;
+ }
+ if (got_sigalarm) {
+ setproctitle("%s p3 %d%%", cdevname,
+ (int)((inplast - inpindex - 1) * 100 / inplast));
+ got_sigalarm = 0;
+ }
+ inp = inpsort[inpindex];
+ state = inoinfo(inp->i_number)->ino_state;
+ if (inp->i_number == ROOTINO ||
+ (inp->i_parent != 0 && !S_IS_DUNFOUND(state)))
+ continue;
+ if (state == DCLEAR)
+ continue;
+ /*
+ * If we are running with soft updates and we come
+ * across unreferenced directories, we just leave
+ * them in DSTATE which will cause them to be pitched
+ * in pass 4.
+ */
+ if ((preen || bkgrdflag) &&
+ resolved && usedsoftdep && S_IS_DUNFOUND(state)) {
+ if (inp->i_dotdot >= ROOTINO)
+ inoinfo(inp->i_dotdot)->ino_linkcnt++;
+ continue;
+ }
+ for (loopcnt = 0; ; loopcnt++) {
+ orphan = inp->i_number;
+ if (inp->i_parent == 0 ||
+ !INO_IS_DUNFOUND(inp->i_parent) ||
+ loopcnt > countdirs)
+ break;
+ inp = getinoinfo(inp->i_parent);
+ }
+ if (loopcnt <= countdirs) {
+ if (linkup(orphan, inp->i_dotdot, NULL)) {
+ inp->i_parent = inp->i_dotdot = lfdir;
+ inoinfo(lfdir)->ino_linkcnt--;
+ }
+ inoinfo(orphan)->ino_state = DFOUND;
+ propagate();
+ continue;
+ }
+ pfatal("ORPHANED DIRECTORY LOOP DETECTED I=%lu",
+ (u_long)orphan);
+ if (reply("RECONNECT") == 0)
+ continue;
+ memset(&idesc, 0, sizeof(struct inodesc));
+ idesc.id_type = DATA;
+ idesc.id_number = inp->i_parent;
+ idesc.id_parent = orphan;
+ idesc.id_func = findname;
+ idesc.id_name = namebuf;
+ if ((ckinode(ginode(inp->i_parent), &idesc) & FOUND) == 0)
+ pfatal("COULD NOT FIND NAME IN PARENT DIRECTORY");
+ if (linkup(orphan, inp->i_parent, namebuf)) {
+ idesc.id_func = clearentry;
+ if (ckinode(ginode(inp->i_parent), &idesc) & FOUND)
+ inoinfo(orphan)->ino_linkcnt++;
+ inp->i_parent = inp->i_dotdot = lfdir;
+ inoinfo(lfdir)->ino_linkcnt--;
+ }
+ inoinfo(orphan)->ino_state = DFOUND;
+ propagate();
+ }
+}
diff --git a/sbin/fsck_ffs/pass4.c b/sbin/fsck_ffs/pass4.c
new file mode 100644
index 0000000..80a32c1
--- /dev/null
+++ b/sbin/fsck_ffs/pass4.c
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 1980, 1986, 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.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if 0
+#ifndef lint
+static const char sccsid[] = "@(#)pass4.c 8.4 (Berkeley) 4/28/95";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+
+#include <ufs/ufs/dinode.h>
+#include <ufs/ffs/fs.h>
+
+#include <err.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "fsck.h"
+
+void
+pass4(void)
+{
+ ino_t inumber;
+ union dinode *dp;
+ struct inodesc idesc;
+ int i, n, cg;
+
+ memset(&idesc, 0, sizeof(struct inodesc));
+ idesc.id_type = ADDR;
+ idesc.id_func = pass4check;
+ for (cg = 0; cg < sblock.fs_ncg; cg++) {
+ if (got_siginfo) {
+ printf("%s: phase 4: cyl group %d of %d (%d%%)\n",
+ cdevname, cg, sblock.fs_ncg,
+ cg * 100 / sblock.fs_ncg);
+ got_siginfo = 0;
+ }
+ if (got_sigalarm) {
+ setproctitle("%s p4 %d%%", cdevname,
+ cg * 100 / sblock.fs_ncg);
+ got_sigalarm = 0;
+ }
+ inumber = cg * sblock.fs_ipg;
+ for (i = 0; i < inostathead[cg].il_numalloced; i++, inumber++) {
+ if (inumber < ROOTINO)
+ continue;
+ idesc.id_number = inumber;
+ switch (inoinfo(inumber)->ino_state) {
+
+ case FZLINK:
+ case DZLINK:
+ if (inoinfo(inumber)->ino_linkcnt == 0) {
+ clri(&idesc, "UNREF", 1);
+ break;
+ }
+ /* fall through */
+
+ case FSTATE:
+ case DFOUND:
+ n = inoinfo(inumber)->ino_linkcnt;
+ if (n) {
+ adjust(&idesc, (short)n);
+ break;
+ }
+ break;
+
+ case DSTATE:
+ clri(&idesc, "UNREF", 1);
+ break;
+
+ case DCLEAR:
+ /* if on snapshot, already cleared */
+ if (cursnapshot != 0)
+ break;
+ dp = ginode(inumber);
+ if (DIP(dp, di_size) == 0) {
+ clri(&idesc, "ZERO LENGTH", 1);
+ break;
+ }
+ /* fall through */
+ case FCLEAR:
+ clri(&idesc, "BAD/DUP", 1);
+ break;
+
+ case USTATE:
+ break;
+
+ default:
+ errx(EEXIT, "BAD STATE %d FOR INODE I=%ju",
+ inoinfo(inumber)->ino_state,
+ (uintmax_t)inumber);
+ }
+ }
+ }
+}
+
+int
+pass4check(struct inodesc *idesc)
+{
+ struct dups *dlp;
+ int nfrags, res = KEEPON;
+ ufs2_daddr_t blkno = idesc->id_blkno;
+
+ for (nfrags = idesc->id_numfrags; nfrags > 0; blkno++, nfrags--) {
+ if (chkrange(blkno, 1)) {
+ res = SKIP;
+ } else if (testbmap(blkno)) {
+ for (dlp = duplist; dlp; dlp = dlp->next) {
+ if (dlp->dup != blkno)
+ continue;
+ dlp->dup = duplist->dup;
+ dlp = duplist;
+ duplist = duplist->next;
+ free((char *)dlp);
+ break;
+ }
+ if (dlp == 0) {
+ clrbmap(blkno);
+ n_blks--;
+ }
+ }
+ }
+ return (res);
+}
diff --git a/sbin/fsck_ffs/pass5.c b/sbin/fsck_ffs/pass5.c
new file mode 100644
index 0000000..13ef86d
--- /dev/null
+++ b/sbin/fsck_ffs/pass5.c
@@ -0,0 +1,596 @@
+/*
+ * Copyright (c) 1980, 1986, 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.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if 0
+#ifndef lint
+static const char sccsid[] = "@(#)pass5.c 8.9 (Berkeley) 4/28/95";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/sysctl.h>
+
+#include <ufs/ufs/dinode.h>
+#include <ufs/ffs/fs.h>
+
+#include <err.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <string.h>
+#include <libufs.h>
+
+#include "fsck.h"
+
+static void check_maps(u_char *, u_char *, int, ufs2_daddr_t, const char *,
+ int *, int, int, int);
+static void clear_blocks(ufs2_daddr_t start, ufs2_daddr_t end);
+
+void
+pass5(void)
+{
+ int c, i, j, blk, frags, basesize, mapsize;
+ int inomapsize, blkmapsize;
+ struct fs *fs = &sblock;
+ ufs2_daddr_t d, dbase, dmax, start;
+ int rewritecg = 0;
+ struct csum *cs;
+ struct csum_total cstotal;
+ struct inodesc idesc[3];
+ char buf[MAXBSIZE];
+ struct cg *cg, *newcg = (struct cg *)buf;
+ struct bufarea *cgbp;
+
+ inoinfo(WINO)->ino_state = USTATE;
+ memset(newcg, 0, (size_t)fs->fs_cgsize);
+ newcg->cg_niblk = fs->fs_ipg;
+ if (cvtlevel >= 3) {
+ if (fs->fs_maxcontig < 2 && fs->fs_contigsumsize > 0) {
+ if (preen)
+ pwarn("DELETING CLUSTERING MAPS\n");
+ if (preen || reply("DELETE CLUSTERING MAPS")) {
+ fs->fs_contigsumsize = 0;
+ rewritecg = 1;
+ sbdirty();
+ }
+ }
+ if (fs->fs_maxcontig > 1) {
+ const char *doit = 0;
+
+ if (fs->fs_contigsumsize < 1) {
+ doit = "CREAT";
+ } else if (fs->fs_contigsumsize < fs->fs_maxcontig &&
+ fs->fs_contigsumsize < FS_MAXCONTIG) {
+ doit = "EXPAND";
+ }
+ if (doit) {
+ i = fs->fs_contigsumsize;
+ fs->fs_contigsumsize =
+ MIN(fs->fs_maxcontig, FS_MAXCONTIG);
+ if (CGSIZE(fs) > (u_int)fs->fs_bsize) {
+ pwarn("CANNOT %s CLUSTER MAPS\n", doit);
+ fs->fs_contigsumsize = i;
+ } else if (preen ||
+ reply("CREATE CLUSTER MAPS")) {
+ if (preen)
+ pwarn("%sING CLUSTER MAPS\n",
+ doit);
+ fs->fs_cgsize =
+ fragroundup(fs, CGSIZE(fs));
+ rewritecg = 1;
+ sbdirty();
+ }
+ }
+ }
+ }
+ basesize = &newcg->cg_space[0] - (u_char *)(&newcg->cg_firstfield);
+ if (sblock.fs_magic == FS_UFS2_MAGIC) {
+ newcg->cg_iusedoff = basesize;
+ } else {
+ /*
+ * We reserve the space for the old rotation summary
+ * tables for the benefit of old kernels, but do not
+ * maintain them in modern kernels. In time, they can
+ * go away.
+ */
+ newcg->cg_old_btotoff = basesize;
+ newcg->cg_old_boff = newcg->cg_old_btotoff +
+ fs->fs_old_cpg * sizeof(int32_t);
+ newcg->cg_iusedoff = newcg->cg_old_boff +
+ fs->fs_old_cpg * fs->fs_old_nrpos * sizeof(u_int16_t);
+ memset(&newcg->cg_space[0], 0, newcg->cg_iusedoff - basesize);
+ }
+ inomapsize = howmany(fs->fs_ipg, CHAR_BIT);
+ newcg->cg_freeoff = newcg->cg_iusedoff + inomapsize;
+ blkmapsize = howmany(fs->fs_fpg, CHAR_BIT);
+ newcg->cg_nextfreeoff = newcg->cg_freeoff + blkmapsize;
+ if (fs->fs_contigsumsize > 0) {
+ newcg->cg_clustersumoff = newcg->cg_nextfreeoff -
+ sizeof(u_int32_t);
+ newcg->cg_clustersumoff =
+ roundup(newcg->cg_clustersumoff, sizeof(u_int32_t));
+ newcg->cg_clusteroff = newcg->cg_clustersumoff +
+ (fs->fs_contigsumsize + 1) * sizeof(u_int32_t);
+ newcg->cg_nextfreeoff = newcg->cg_clusteroff +
+ howmany(fragstoblks(fs, fs->fs_fpg), CHAR_BIT);
+ }
+ newcg->cg_magic = CG_MAGIC;
+ mapsize = newcg->cg_nextfreeoff - newcg->cg_iusedoff;
+ memset(&idesc[0], 0, sizeof idesc);
+ for (i = 0; i < 3; i++)
+ idesc[i].id_type = ADDR;
+ memset(&cstotal, 0, sizeof(struct csum_total));
+ dmax = blknum(fs, fs->fs_size + fs->fs_frag - 1);
+ for (d = fs->fs_size; d < dmax; d++)
+ setbmap(d);
+ for (c = 0; c < fs->fs_ncg; c++) {
+ if (got_siginfo) {
+ printf("%s: phase 5: cyl group %d of %d (%d%%)\n",
+ cdevname, c, sblock.fs_ncg,
+ c * 100 / sblock.fs_ncg);
+ got_siginfo = 0;
+ }
+ if (got_sigalarm) {
+ setproctitle("%s p5 %d%%", cdevname,
+ c * 100 / sblock.fs_ncg);
+ got_sigalarm = 0;
+ }
+ cgbp = cgget(c);
+ cg = cgbp->b_un.b_cg;
+ if (!cg_chkmagic(cg))
+ pfatal("CG %d: BAD MAGIC NUMBER\n", c);
+ newcg->cg_time = cg->cg_time;
+ newcg->cg_old_time = cg->cg_old_time;
+ newcg->cg_unrefs = cg->cg_unrefs;
+ newcg->cg_cgx = c;
+ dbase = cgbase(fs, c);
+ dmax = dbase + fs->fs_fpg;
+ if (dmax > fs->fs_size)
+ dmax = fs->fs_size;
+ newcg->cg_ndblk = dmax - dbase;
+ if (fs->fs_magic == FS_UFS1_MAGIC) {
+ if (c == fs->fs_ncg - 1)
+ newcg->cg_old_ncyl = howmany(newcg->cg_ndblk,
+ fs->fs_fpg / fs->fs_old_cpg);
+ else
+ newcg->cg_old_ncyl = fs->fs_old_cpg;
+ newcg->cg_old_niblk = fs->fs_ipg;
+ newcg->cg_niblk = 0;
+ }
+ if (fs->fs_contigsumsize > 0)
+ newcg->cg_nclusterblks = newcg->cg_ndblk / fs->fs_frag;
+ newcg->cg_cs.cs_ndir = 0;
+ newcg->cg_cs.cs_nffree = 0;
+ newcg->cg_cs.cs_nbfree = 0;
+ newcg->cg_cs.cs_nifree = fs->fs_ipg;
+ if (cg->cg_rotor >= 0 && cg->cg_rotor < newcg->cg_ndblk)
+ newcg->cg_rotor = cg->cg_rotor;
+ else
+ newcg->cg_rotor = 0;
+ if (cg->cg_frotor >= 0 && cg->cg_frotor < newcg->cg_ndblk)
+ newcg->cg_frotor = cg->cg_frotor;
+ else
+ newcg->cg_frotor = 0;
+ if (cg->cg_irotor >= 0 && cg->cg_irotor < fs->fs_ipg)
+ newcg->cg_irotor = cg->cg_irotor;
+ else
+ newcg->cg_irotor = 0;
+ if (fs->fs_magic == FS_UFS1_MAGIC) {
+ newcg->cg_initediblk = 0;
+ } else {
+ if ((unsigned)cg->cg_initediblk > fs->fs_ipg)
+ newcg->cg_initediblk = fs->fs_ipg;
+ else
+ newcg->cg_initediblk = cg->cg_initediblk;
+ }
+ memset(&newcg->cg_frsum[0], 0, sizeof newcg->cg_frsum);
+ memset(cg_inosused(newcg), 0, (size_t)(mapsize));
+ j = fs->fs_ipg * c;
+ for (i = 0; i < inostathead[c].il_numalloced; j++, i++) {
+ switch (inoinfo(j)->ino_state) {
+
+ case USTATE:
+ break;
+
+ case DSTATE:
+ case DCLEAR:
+ case DFOUND:
+ case DZLINK:
+ newcg->cg_cs.cs_ndir++;
+ /* FALLTHROUGH */
+
+ case FSTATE:
+ case FCLEAR:
+ case FZLINK:
+ newcg->cg_cs.cs_nifree--;
+ setbit(cg_inosused(newcg), i);
+ break;
+
+ default:
+ if (j < (int)ROOTINO)
+ break;
+ errx(EEXIT, "BAD STATE %d FOR INODE I=%d",
+ inoinfo(j)->ino_state, j);
+ }
+ }
+ if (c == 0)
+ for (i = 0; i < (int)ROOTINO; i++) {
+ setbit(cg_inosused(newcg), i);
+ newcg->cg_cs.cs_nifree--;
+ }
+ start = -1;
+ for (i = 0, d = dbase;
+ d < dmax;
+ d += fs->fs_frag, i += fs->fs_frag) {
+ frags = 0;
+ for (j = 0; j < fs->fs_frag; j++) {
+ if (testbmap(d + j)) {
+ if ((Eflag || Zflag) && start != -1) {
+ clear_blocks(start, d + j - 1);
+ start = -1;
+ }
+ continue;
+ }
+ if (start == -1)
+ start = d + j;
+ setbit(cg_blksfree(newcg), i + j);
+ frags++;
+ }
+ if (frags == fs->fs_frag) {
+ newcg->cg_cs.cs_nbfree++;
+ if (fs->fs_contigsumsize > 0)
+ setbit(cg_clustersfree(newcg),
+ i / fs->fs_frag);
+ } else if (frags > 0) {
+ newcg->cg_cs.cs_nffree += frags;
+ blk = blkmap(fs, cg_blksfree(newcg), i);
+ ffs_fragacct(fs, blk, newcg->cg_frsum, 1);
+ }
+ }
+ if ((Eflag || Zflag) && start != -1)
+ clear_blocks(start, d - 1);
+ if (fs->fs_contigsumsize > 0) {
+ int32_t *sump = cg_clustersum(newcg);
+ u_char *mapp = cg_clustersfree(newcg);
+ int map = *mapp++;
+ int bit = 1;
+ int run = 0;
+
+ for (i = 0; i < newcg->cg_nclusterblks; i++) {
+ if ((map & bit) != 0) {
+ run++;
+ } else if (run != 0) {
+ if (run > fs->fs_contigsumsize)
+ run = fs->fs_contigsumsize;
+ sump[run]++;
+ run = 0;
+ }
+ if ((i & (CHAR_BIT - 1)) != (CHAR_BIT - 1)) {
+ bit <<= 1;
+ } else {
+ map = *mapp++;
+ bit = 1;
+ }
+ }
+ if (run != 0) {
+ if (run > fs->fs_contigsumsize)
+ run = fs->fs_contigsumsize;
+ sump[run]++;
+ }
+ }
+ if (bkgrdflag != 0) {
+ cstotal.cs_nffree += cg->cg_cs.cs_nffree;
+ cstotal.cs_nbfree += cg->cg_cs.cs_nbfree;
+ cstotal.cs_nifree += cg->cg_cs.cs_nifree;
+ cstotal.cs_ndir += cg->cg_cs.cs_ndir;
+ } else {
+ cstotal.cs_nffree += newcg->cg_cs.cs_nffree;
+ cstotal.cs_nbfree += newcg->cg_cs.cs_nbfree;
+ cstotal.cs_nifree += newcg->cg_cs.cs_nifree;
+ cstotal.cs_ndir += newcg->cg_cs.cs_ndir;
+ }
+ cs = &fs->fs_cs(fs, c);
+ if (cursnapshot == 0 &&
+ memcmp(&newcg->cg_cs, cs, sizeof *cs) != 0 &&
+ dofix(&idesc[0], "FREE BLK COUNT(S) WRONG IN SUPERBLK")) {
+ memmove(cs, &newcg->cg_cs, sizeof *cs);
+ sbdirty();
+ }
+ if (rewritecg) {
+ memmove(cg, newcg, (size_t)fs->fs_cgsize);
+ dirty(cgbp);
+ continue;
+ }
+ if (cursnapshot == 0 &&
+ memcmp(newcg, cg, basesize) != 0 &&
+ dofix(&idesc[2], "SUMMARY INFORMATION BAD")) {
+ memmove(cg, newcg, (size_t)basesize);
+ dirty(cgbp);
+ }
+ if (bkgrdflag != 0 || usedsoftdep || debug)
+ update_maps(cg, newcg, bkgrdflag);
+ if (cursnapshot == 0 &&
+ memcmp(cg_inosused(newcg), cg_inosused(cg), mapsize) != 0 &&
+ dofix(&idesc[1], "BLK(S) MISSING IN BIT MAPS")) {
+ memmove(cg_inosused(cg), cg_inosused(newcg),
+ (size_t)mapsize);
+ dirty(cgbp);
+ }
+ }
+ if (cursnapshot == 0 &&
+ memcmp(&cstotal, &fs->fs_cstotal, sizeof cstotal) != 0
+ && dofix(&idesc[0], "SUMMARY BLK COUNT(S) WRONG IN SUPERBLK")) {
+ memmove(&fs->fs_cstotal, &cstotal, sizeof cstotal);
+ fs->fs_ronly = 0;
+ fs->fs_fmod = 0;
+ sbdirty();
+ }
+
+ /*
+ * When doing background fsck on a snapshot, figure out whether
+ * the superblock summary is inaccurate and correct it when
+ * necessary.
+ */
+ if (cursnapshot != 0) {
+ cmd.size = 1;
+
+ cmd.value = cstotal.cs_ndir - fs->fs_cstotal.cs_ndir;
+ if (cmd.value != 0) {
+ if (debug)
+ printf("adjndir by %+" PRIi64 "\n", cmd.value);
+ if (bkgrdsumadj == 0 || sysctl(adjndir, MIBSIZE, 0, 0,
+ &cmd, sizeof cmd) == -1)
+ rwerror("ADJUST NUMBER OF DIRECTORIES", cmd.value);
+ }
+
+ cmd.value = cstotal.cs_nbfree - fs->fs_cstotal.cs_nbfree;
+ if (cmd.value != 0) {
+ if (debug)
+ printf("adjnbfree by %+" PRIi64 "\n", cmd.value);
+ if (bkgrdsumadj == 0 || sysctl(adjnbfree, MIBSIZE, 0, 0,
+ &cmd, sizeof cmd) == -1)
+ rwerror("ADJUST NUMBER OF FREE BLOCKS", cmd.value);
+ }
+
+ cmd.value = cstotal.cs_nifree - fs->fs_cstotal.cs_nifree;
+ if (cmd.value != 0) {
+ if (debug)
+ printf("adjnifree by %+" PRIi64 "\n", cmd.value);
+ if (bkgrdsumadj == 0 || sysctl(adjnifree, MIBSIZE, 0, 0,
+ &cmd, sizeof cmd) == -1)
+ rwerror("ADJUST NUMBER OF FREE INODES", cmd.value);
+ }
+
+ cmd.value = cstotal.cs_nffree - fs->fs_cstotal.cs_nffree;
+ if (cmd.value != 0) {
+ if (debug)
+ printf("adjnffree by %+" PRIi64 "\n", cmd.value);
+ if (bkgrdsumadj == 0 || sysctl(adjnffree, MIBSIZE, 0, 0,
+ &cmd, sizeof cmd) == -1)
+ rwerror("ADJUST NUMBER OF FREE FRAGS", cmd.value);
+ }
+
+ cmd.value = cstotal.cs_numclusters - fs->fs_cstotal.cs_numclusters;
+ if (cmd.value != 0) {
+ if (debug)
+ printf("adjnumclusters by %+" PRIi64 "\n", cmd.value);
+ if (bkgrdsumadj == 0 || sysctl(adjnumclusters, MIBSIZE, 0, 0,
+ &cmd, sizeof cmd) == -1)
+ rwerror("ADJUST NUMBER OF FREE CLUSTERS", cmd.value);
+ }
+ }
+}
+
+/*
+ * Compare the original cylinder group inode and block bitmaps with the
+ * updated cylinder group inode and block bitmaps. Free inodes and blocks
+ * that have been added. Complain if any previously freed inodes blocks
+ * are now allocated.
+ */
+void
+update_maps(
+ struct cg *oldcg, /* cylinder group of claimed allocations */
+ struct cg *newcg, /* cylinder group of determined allocations */
+ int usesysctl) /* 1 => use sysctl interface to update maps */
+{
+ int inomapsize, excessdirs;
+ struct fs *fs = &sblock;
+
+ inomapsize = howmany(fs->fs_ipg, CHAR_BIT);
+ excessdirs = oldcg->cg_cs.cs_ndir - newcg->cg_cs.cs_ndir;
+ if (excessdirs < 0) {
+ pfatal("LOST %d DIRECTORIES\n", -excessdirs);
+ excessdirs = 0;
+ }
+ if (excessdirs > 0)
+ check_maps(cg_inosused(newcg), cg_inosused(oldcg), inomapsize,
+ oldcg->cg_cgx * (ufs2_daddr_t)fs->fs_ipg, "DIR", freedirs,
+ 0, excessdirs, usesysctl);
+ check_maps(cg_inosused(newcg), cg_inosused(oldcg), inomapsize,
+ oldcg->cg_cgx * (ufs2_daddr_t)fs->fs_ipg, "FILE", freefiles,
+ excessdirs, fs->fs_ipg, usesysctl);
+ check_maps(cg_blksfree(oldcg), cg_blksfree(newcg),
+ howmany(fs->fs_fpg, CHAR_BIT),
+ oldcg->cg_cgx * (ufs2_daddr_t)fs->fs_fpg, "FRAG",
+ freeblks, 0, fs->fs_fpg, usesysctl);
+}
+
+static void
+check_maps(
+ u_char *map1, /* map of claimed allocations */
+ u_char *map2, /* map of determined allocations */
+ int mapsize, /* size of above two maps */
+ ufs2_daddr_t startvalue, /* resource value for first element in map */
+ const char *name, /* name of resource found in maps */
+ int *opcode, /* sysctl opcode to free resource */
+ int skip, /* number of entries to skip before starting to free */
+ int limit, /* limit on number of entries to free */
+ int usesysctl) /* 1 => use sysctl interface to update maps */
+{
+# define BUFSIZE 16
+ char buf[BUFSIZE];
+ long i, j, k, l, m, size;
+ ufs2_daddr_t n, astart, aend, ustart, uend;
+ void (*msg)(const char *fmt, ...);
+
+ if (usesysctl)
+ msg = pfatal;
+ else
+ msg = pwarn;
+ astart = ustart = aend = uend = -1;
+ for (i = 0; i < mapsize; i++) {
+ j = *map1++;
+ k = *map2++;
+ if (j == k)
+ continue;
+ for (m = 0, l = 1; m < CHAR_BIT; m++, l <<= 1) {
+ if ((j & l) == (k & l))
+ continue;
+ n = startvalue + i * CHAR_BIT + m;
+ if ((j & l) != 0) {
+ if (astart == -1) {
+ astart = aend = n;
+ continue;
+ }
+ if (aend + 1 == n) {
+ aend = n;
+ continue;
+ }
+ if (astart == aend)
+ (*msg)("ALLOCATED %s %" PRId64
+ " MARKED FREE\n",
+ name, astart);
+ else
+ (*msg)("%s %sS %" PRId64 "-%" PRId64
+ " MARKED FREE\n",
+ "ALLOCATED", name, astart, aend);
+ astart = aend = n;
+ } else {
+ if (ustart == -1) {
+ ustart = uend = n;
+ continue;
+ }
+ if (uend + 1 == n) {
+ uend = n;
+ continue;
+ }
+ size = uend - ustart + 1;
+ if (size <= skip) {
+ skip -= size;
+ ustart = uend = n;
+ continue;
+ }
+ if (skip > 0) {
+ ustart += skip;
+ size -= skip;
+ skip = 0;
+ }
+ if (size > limit)
+ size = limit;
+ if (debug && size == 1)
+ pwarn("%s %s %" PRId64
+ " MARKED USED\n",
+ "UNALLOCATED", name, ustart);
+ else if (debug)
+ pwarn("%s %sS %" PRId64 "-%" PRId64
+ " MARKED USED\n",
+ "UNALLOCATED", name, ustart,
+ ustart + size - 1);
+ if (usesysctl != 0) {
+ cmd.value = ustart;
+ cmd.size = size;
+ if (sysctl(opcode, MIBSIZE, 0, 0,
+ &cmd, sizeof cmd) == -1) {
+ snprintf(buf, BUFSIZE,
+ "FREE %s", name);
+ rwerror(buf, cmd.value);
+ }
+ }
+ limit -= size;
+ if (limit <= 0)
+ return;
+ ustart = uend = n;
+ }
+ }
+ }
+ if (astart != -1) {
+ if (astart == aend)
+ (*msg)("ALLOCATED %s %" PRId64
+ " MARKED FREE\n", name, astart);
+ else
+ (*msg)("ALLOCATED %sS %" PRId64 "-%" PRId64
+ " MARKED FREE\n",
+ name, astart, aend);
+ }
+ if (ustart != -1) {
+ size = uend - ustart + 1;
+ if (size <= skip)
+ return;
+ if (skip > 0) {
+ ustart += skip;
+ size -= skip;
+ }
+ if (size > limit)
+ size = limit;
+ if (debug) {
+ if (size == 1)
+ pwarn("UNALLOCATED %s %" PRId64
+ " MARKED USED\n",
+ name, ustart);
+ else
+ pwarn("UNALLOCATED %sS %" PRId64 "-%" PRId64
+ " MARKED USED\n",
+ name, ustart, ustart + size - 1);
+ }
+ if (usesysctl != 0) {
+ cmd.value = ustart;
+ cmd.size = size;
+ if (sysctl(opcode, MIBSIZE, 0, 0, &cmd,
+ sizeof cmd) == -1) {
+ snprintf(buf, BUFSIZE, "FREE %s", name);
+ rwerror(buf, cmd.value);
+ }
+ }
+ }
+}
+
+static void
+clear_blocks(ufs2_daddr_t start, ufs2_daddr_t end)
+{
+
+ if (debug)
+ printf("Zero frags %jd to %jd\n", start, end);
+ if (Zflag)
+ blzero(fswritefd, fsbtodb(&sblock, start),
+ lfragtosize(&sblock, end - start + 1));
+ if (Eflag)
+ blerase(fswritefd, fsbtodb(&sblock, start),
+ lfragtosize(&sblock, end - start + 1));
+}
diff --git a/sbin/fsck_ffs/setup.c b/sbin/fsck_ffs/setup.c
new file mode 100644
index 0000000..494e503
--- /dev/null
+++ b/sbin/fsck_ffs/setup.c
@@ -0,0 +1,449 @@
+/*
+ * Copyright (c) 1980, 1986, 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.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if 0
+#ifndef lint
+static const char sccsid[] = "@(#)setup.c 8.10 (Berkeley) 5/9/95";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/stat.h>
+#define FSTYPENAMES
+#include <sys/disklabel.h>
+#include <sys/file.h>
+#include <sys/sysctl.h>
+
+#include <ufs/ufs/dinode.h>
+#include <ufs/ffs/fs.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "fsck.h"
+
+struct bufarea asblk;
+#define altsblock (*asblk.b_un.b_fs)
+#define POWEROF2(num) (((num) & ((num) - 1)) == 0)
+
+static void badsb(int listerr, const char *s);
+
+/*
+ * Read in a superblock finding an alternate if necessary.
+ * Return 1 if successful, 0 if unsuccessful, -1 if file system
+ * is already clean (ckclean and preen mode only).
+ */
+int
+setup(char *dev)
+{
+ long cg, asked, i, j;
+ long bmapsize;
+ struct stat statb;
+ struct fs proto;
+ size_t size;
+
+ havesb = 0;
+ fswritefd = -1;
+ cursnapshot = 0;
+ if (stat(dev, &statb) < 0) {
+ printf("Can't stat %s: %s\n", dev, strerror(errno));
+ if (bkgrdflag) {
+ unlink(snapname);
+ bkgrdflag = 0;
+ }
+ return (0);
+ }
+ if ((statb.st_mode & S_IFMT) != S_IFCHR &&
+ (statb.st_mode & S_IFMT) != S_IFBLK) {
+ if (bkgrdflag != 0 && (statb.st_flags & SF_SNAPSHOT) == 0) {
+ unlink(snapname);
+ printf("background fsck lacks a snapshot\n");
+ exit(EEXIT);
+ }
+ if ((statb.st_flags & SF_SNAPSHOT) != 0 && cvtlevel == 0) {
+ cursnapshot = statb.st_ino;
+ } else {
+ if (cvtlevel == 0 ||
+ (statb.st_flags & SF_SNAPSHOT) == 0) {
+ if (preen && bkgrdflag) {
+ unlink(snapname);
+ bkgrdflag = 0;
+ }
+ pfatal("%s is not a disk device", dev);
+ if (reply("CONTINUE") == 0) {
+ if (bkgrdflag) {
+ unlink(snapname);
+ bkgrdflag = 0;
+ }
+ return (0);
+ }
+ } else {
+ if (bkgrdflag) {
+ unlink(snapname);
+ bkgrdflag = 0;
+ }
+ pfatal("cannot convert a snapshot");
+ exit(EEXIT);
+ }
+ }
+ }
+ if ((fsreadfd = open(dev, O_RDONLY)) < 0) {
+ if (bkgrdflag) {
+ unlink(snapname);
+ bkgrdflag = 0;
+ }
+ printf("Can't open %s: %s\n", dev, strerror(errno));
+ return (0);
+ }
+ if (bkgrdflag) {
+ unlink(snapname);
+ size = MIBSIZE;
+ if (sysctlnametomib("vfs.ffs.adjrefcnt", adjrefcnt, &size) < 0||
+ sysctlnametomib("vfs.ffs.adjblkcnt", adjblkcnt, &size) < 0||
+ sysctlnametomib("vfs.ffs.freefiles", freefiles, &size) < 0||
+ sysctlnametomib("vfs.ffs.freedirs", freedirs, &size) < 0 ||
+ sysctlnametomib("vfs.ffs.freeblks", freeblks, &size) < 0) {
+ pfatal("kernel lacks background fsck support\n");
+ exit(EEXIT);
+ }
+ /*
+ * When kernel is lack of runtime bgfsck superblock summary
+ * adjustment functionality, it does not mean we can not
+ * continue, as old kernels will recompute the summary at
+ * mount time. However, it will be an unexpected softupdates
+ * inconsistency if it turns out that the summary is still
+ * incorrect. Set a flag so subsequent operation can know
+ * this.
+ */
+ bkgrdsumadj = 1;
+ if (sysctlnametomib("vfs.ffs.adjndir", adjndir, &size) < 0 ||
+ sysctlnametomib("vfs.ffs.adjnbfree", adjnbfree, &size) < 0 ||
+ sysctlnametomib("vfs.ffs.adjnifree", adjnifree, &size) < 0 ||
+ sysctlnametomib("vfs.ffs.adjnffree", adjnffree, &size) < 0 ||
+ sysctlnametomib("vfs.ffs.adjnumclusters", adjnumclusters, &size) < 0) {
+ bkgrdsumadj = 0;
+ pwarn("kernel lacks runtime superblock summary adjustment support");
+ }
+ cmd.version = FFS_CMD_VERSION;
+ cmd.handle = fsreadfd;
+ fswritefd = -1;
+ }
+ if (preen == 0)
+ printf("** %s", dev);
+ if (bkgrdflag == 0 &&
+ (nflag || (fswritefd = open(dev, O_WRONLY)) < 0)) {
+ fswritefd = -1;
+ if (preen)
+ pfatal("NO WRITE ACCESS");
+ printf(" (NO WRITE)");
+ }
+ if (preen == 0)
+ printf("\n");
+ /*
+ * Read in the superblock, looking for alternates if necessary
+ */
+ if (readsb(1) == 0) {
+ skipclean = 0;
+ if (bflag || preen)
+ return(0);
+ if (reply("LOOK FOR ALTERNATE SUPERBLOCKS") == 0)
+ return (0);
+ for (cg = 0; cg < proto.fs_ncg; cg++) {
+ bflag = fsbtodb(&proto, cgsblock(&proto, cg));
+ if (readsb(0) != 0)
+ break;
+ }
+ if (cg >= proto.fs_ncg) {
+ printf("%s %s\n%s %s\n%s %s\n",
+ "SEARCH FOR ALTERNATE SUPER-BLOCK",
+ "FAILED. YOU MUST USE THE",
+ "-b OPTION TO FSCK TO SPECIFY THE",
+ "LOCATION OF AN ALTERNATE",
+ "SUPER-BLOCK TO SUPPLY NEEDED",
+ "INFORMATION; SEE fsck_ffs(8).");
+ bflag = 0;
+ return(0);
+ }
+ pwarn("USING ALTERNATE SUPERBLOCK AT %d\n", bflag);
+ bflag = 0;
+ }
+ if (skipclean && ckclean && sblock.fs_clean) {
+ pwarn("FILE SYSTEM CLEAN; SKIPPING CHECKS\n");
+ return (-1);
+ }
+ maxfsblock = sblock.fs_size;
+ maxino = sblock.fs_ncg * sblock.fs_ipg;
+ /*
+ * Check and potentially fix certain fields in the super block.
+ */
+ if (sblock.fs_optim != FS_OPTTIME && sblock.fs_optim != FS_OPTSPACE) {
+ pfatal("UNDEFINED OPTIMIZATION IN SUPERBLOCK");
+ if (reply("SET TO DEFAULT") == 1) {
+ sblock.fs_optim = FS_OPTTIME;
+ sbdirty();
+ }
+ }
+ if ((sblock.fs_minfree < 0 || sblock.fs_minfree > 99)) {
+ pfatal("IMPOSSIBLE MINFREE=%d IN SUPERBLOCK",
+ sblock.fs_minfree);
+ if (reply("SET TO DEFAULT") == 1) {
+ sblock.fs_minfree = 10;
+ sbdirty();
+ }
+ }
+ if (sblock.fs_magic == FS_UFS1_MAGIC &&
+ sblock.fs_old_inodefmt < FS_44INODEFMT) {
+ pwarn("Format of file system is too old.\n");
+ pwarn("Must update to modern format using a version of fsck\n");
+ pfatal("from before 2002 with the command ``fsck -c 2''\n");
+ exit(EEXIT);
+ }
+ if (asblk.b_dirty && !bflag) {
+ memmove(&altsblock, &sblock, (size_t)sblock.fs_sbsize);
+ flush(fswritefd, &asblk);
+ }
+ /*
+ * read in the summary info.
+ */
+ asked = 0;
+ sblock.fs_csp = Calloc(1, sblock.fs_cssize);
+ if (sblock.fs_csp == NULL) {
+ printf("cannot alloc %u bytes for cg summary info\n",
+ (unsigned)sblock.fs_cssize);
+ goto badsb;
+ }
+ for (i = 0, j = 0; i < sblock.fs_cssize; i += sblock.fs_bsize, j++) {
+ size = sblock.fs_cssize - i < sblock.fs_bsize ?
+ sblock.fs_cssize - i : sblock.fs_bsize;
+ readcnt[sblk.b_type]++;
+ if (blread(fsreadfd, (char *)sblock.fs_csp + i,
+ fsbtodb(&sblock, sblock.fs_csaddr + j * sblock.fs_frag),
+ size) != 0 && !asked) {
+ pfatal("BAD SUMMARY INFORMATION");
+ if (reply("CONTINUE") == 0) {
+ ckfini(0);
+ exit(EEXIT);
+ }
+ asked++;
+ }
+ }
+ /*
+ * allocate and initialize the necessary maps
+ */
+ bmapsize = roundup(howmany(maxfsblock, CHAR_BIT), sizeof(short));
+ blockmap = Calloc((unsigned)bmapsize, sizeof (char));
+ if (blockmap == NULL) {
+ printf("cannot alloc %u bytes for blockmap\n",
+ (unsigned)bmapsize);
+ goto badsb;
+ }
+ inostathead = Calloc((unsigned)(sblock.fs_ncg),
+ sizeof(struct inostatlist));
+ if (inostathead == NULL) {
+ printf("cannot alloc %u bytes for inostathead\n",
+ (unsigned)(sizeof(struct inostatlist) * (sblock.fs_ncg)));
+ goto badsb;
+ }
+ numdirs = MAX(sblock.fs_cstotal.cs_ndir, 128);
+ dirhash = numdirs;
+ inplast = 0;
+ listmax = numdirs + 10;
+ inpsort = (struct inoinfo **)Calloc((unsigned)listmax,
+ sizeof(struct inoinfo *));
+ inphead = (struct inoinfo **)Calloc((unsigned)numdirs,
+ sizeof(struct inoinfo *));
+ if (inpsort == NULL || inphead == NULL) {
+ printf("cannot alloc %ju bytes for inphead\n",
+ (uintmax_t)numdirs * sizeof(struct inoinfo *));
+ goto badsb;
+ }
+ bufinit();
+ if (sblock.fs_flags & FS_DOSOFTDEP)
+ usedsoftdep = 1;
+ else
+ usedsoftdep = 0;
+ return (1);
+
+badsb:
+ ckfini(0);
+ return (0);
+}
+
+/*
+ * Possible superblock locations ordered from most to least likely.
+ */
+static int sblock_try[] = SBLOCKSEARCH;
+
+#define BAD_MAGIC_MSG \
+"The previous newfs operation on this volume did not complete.\n" \
+"You must complete newfs before mounting this volume.\n"
+
+/*
+ * Read in the super block and its summary info.
+ */
+int
+readsb(int listerr)
+{
+ ufs2_daddr_t super;
+ int i;
+
+ if (bflag) {
+ super = bflag;
+ readcnt[sblk.b_type]++;
+ if ((blread(fsreadfd, (char *)&sblock, super, (long)SBLOCKSIZE)))
+ return (0);
+ if (sblock.fs_magic == FS_BAD_MAGIC) {
+ fprintf(stderr, BAD_MAGIC_MSG);
+ exit(11);
+ }
+ if (sblock.fs_magic != FS_UFS1_MAGIC &&
+ sblock.fs_magic != FS_UFS2_MAGIC) {
+ fprintf(stderr, "%d is not a file system superblock\n",
+ bflag);
+ return (0);
+ }
+ } else {
+ for (i = 0; sblock_try[i] != -1; i++) {
+ super = sblock_try[i] / dev_bsize;
+ readcnt[sblk.b_type]++;
+ if ((blread(fsreadfd, (char *)&sblock, super,
+ (long)SBLOCKSIZE)))
+ return (0);
+ if (sblock.fs_magic == FS_BAD_MAGIC) {
+ fprintf(stderr, BAD_MAGIC_MSG);
+ exit(11);
+ }
+ if ((sblock.fs_magic == FS_UFS1_MAGIC ||
+ (sblock.fs_magic == FS_UFS2_MAGIC &&
+ sblock.fs_sblockloc == sblock_try[i])) &&
+ sblock.fs_ncg >= 1 &&
+ sblock.fs_bsize >= MINBSIZE &&
+ sblock.fs_sbsize >= roundup(sizeof(struct fs), dev_bsize))
+ break;
+ }
+ if (sblock_try[i] == -1) {
+ fprintf(stderr, "Cannot find file system superblock\n");
+ return (0);
+ }
+ }
+ /*
+ * Compute block size that the file system is based on,
+ * according to fsbtodb, and adjust superblock block number
+ * so we can tell if this is an alternate later.
+ */
+ super *= dev_bsize;
+ dev_bsize = sblock.fs_fsize / fsbtodb(&sblock, 1);
+ sblk.b_bno = super / dev_bsize;
+ sblk.b_size = SBLOCKSIZE;
+ if (bflag)
+ goto out;
+ /*
+ * Compare all fields that should not differ in alternate super block.
+ * When an alternate super-block is specified this check is skipped.
+ */
+ getblk(&asblk, cgsblock(&sblock, sblock.fs_ncg - 1), sblock.fs_sbsize);
+ if (asblk.b_errs)
+ return (0);
+ if (altsblock.fs_sblkno != sblock.fs_sblkno ||
+ altsblock.fs_cblkno != sblock.fs_cblkno ||
+ altsblock.fs_iblkno != sblock.fs_iblkno ||
+ altsblock.fs_dblkno != sblock.fs_dblkno ||
+ altsblock.fs_ncg != sblock.fs_ncg ||
+ altsblock.fs_bsize != sblock.fs_bsize ||
+ altsblock.fs_fsize != sblock.fs_fsize ||
+ altsblock.fs_frag != sblock.fs_frag ||
+ altsblock.fs_bmask != sblock.fs_bmask ||
+ altsblock.fs_fmask != sblock.fs_fmask ||
+ altsblock.fs_bshift != sblock.fs_bshift ||
+ altsblock.fs_fshift != sblock.fs_fshift ||
+ altsblock.fs_fragshift != sblock.fs_fragshift ||
+ altsblock.fs_fsbtodb != sblock.fs_fsbtodb ||
+ altsblock.fs_sbsize != sblock.fs_sbsize ||
+ altsblock.fs_nindir != sblock.fs_nindir ||
+ altsblock.fs_inopb != sblock.fs_inopb ||
+ altsblock.fs_cssize != sblock.fs_cssize ||
+ altsblock.fs_ipg != sblock.fs_ipg ||
+ altsblock.fs_fpg != sblock.fs_fpg ||
+ altsblock.fs_magic != sblock.fs_magic) {
+ badsb(listerr,
+ "VALUES IN SUPER BLOCK DISAGREE WITH THOSE IN FIRST ALTERNATE");
+ return (0);
+ }
+out:
+ /*
+ * If not yet done, update UFS1 superblock with new wider fields.
+ */
+ if (sblock.fs_magic == FS_UFS1_MAGIC &&
+ sblock.fs_maxbsize != sblock.fs_bsize) {
+ sblock.fs_maxbsize = sblock.fs_bsize;
+ sblock.fs_time = sblock.fs_old_time;
+ sblock.fs_size = sblock.fs_old_size;
+ sblock.fs_dsize = sblock.fs_old_dsize;
+ sblock.fs_csaddr = sblock.fs_old_csaddr;
+ sblock.fs_cstotal.cs_ndir = sblock.fs_old_cstotal.cs_ndir;
+ sblock.fs_cstotal.cs_nbfree = sblock.fs_old_cstotal.cs_nbfree;
+ sblock.fs_cstotal.cs_nifree = sblock.fs_old_cstotal.cs_nifree;
+ sblock.fs_cstotal.cs_nffree = sblock.fs_old_cstotal.cs_nffree;
+ }
+ havesb = 1;
+ return (1);
+}
+
+static void
+badsb(int listerr, const char *s)
+{
+
+ if (!listerr)
+ return;
+ if (preen)
+ printf("%s: ", cdevname);
+ pfatal("BAD SUPER BLOCK: %s\n", s);
+}
+
+void
+sblock_init(void)
+{
+
+ fswritefd = -1;
+ fsmodified = 0;
+ lfdir = 0;
+ initbarea(&sblk, BT_SUPERBLK);
+ initbarea(&asblk, BT_SUPERBLK);
+ sblk.b_un.b_buf = Malloc(SBLOCKSIZE);
+ asblk.b_un.b_buf = Malloc(SBLOCKSIZE);
+ if (sblk.b_un.b_buf == NULL || asblk.b_un.b_buf == NULL)
+ errx(EEXIT, "cannot allocate space for superblock");
+ dev_bsize = secsize = DEV_BSIZE;
+}
diff --git a/sbin/fsck_ffs/suj.c b/sbin/fsck_ffs/suj.c
new file mode 100644
index 0000000..9d6a2ec
--- /dev/null
+++ b/sbin/fsck_ffs/suj.c
@@ -0,0 +1,2791 @@
+/*-
+ * Copyright 2009, 2010 Jeffrey W. Roberson <jeff@FreeBSD.org>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/disk.h>
+#include <sys/disklabel.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+
+#include <ufs/ufs/ufsmount.h>
+#include <ufs/ufs/dinode.h>
+#include <ufs/ufs/dir.h>
+#include <ufs/ffs/fs.h>
+
+#include <assert.h>
+#include <err.h>
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <libufs.h>
+#include <string.h>
+#include <strings.h>
+#include <sysexits.h>
+#include <time.h>
+
+#include "fsck.h"
+
+#define DOTDOT_OFFSET DIRECTSIZ(1)
+#define SUJ_HASHSIZE 2048
+#define SUJ_HASHMASK (SUJ_HASHSIZE - 1)
+#define SUJ_HASH(x) ((x * 2654435761) & SUJ_HASHMASK)
+
+struct suj_seg {
+ TAILQ_ENTRY(suj_seg) ss_next;
+ struct jsegrec ss_rec;
+ uint8_t *ss_blk;
+};
+
+struct suj_rec {
+ TAILQ_ENTRY(suj_rec) sr_next;
+ union jrec *sr_rec;
+};
+TAILQ_HEAD(srechd, suj_rec);
+
+struct suj_ino {
+ LIST_ENTRY(suj_ino) si_next;
+ struct srechd si_recs;
+ struct srechd si_newrecs;
+ struct srechd si_movs;
+ struct jtrncrec *si_trunc;
+ ino_t si_ino;
+ char si_skipparent;
+ char si_hasrecs;
+ char si_blkadj;
+ char si_linkadj;
+ int si_mode;
+ nlink_t si_nlinkadj;
+ nlink_t si_nlink;
+ nlink_t si_dotlinks;
+};
+LIST_HEAD(inohd, suj_ino);
+
+struct suj_blk {
+ LIST_ENTRY(suj_blk) sb_next;
+ struct srechd sb_recs;
+ ufs2_daddr_t sb_blk;
+};
+LIST_HEAD(blkhd, suj_blk);
+
+struct data_blk {
+ LIST_ENTRY(data_blk) db_next;
+ uint8_t *db_buf;
+ ufs2_daddr_t db_blk;
+ int db_size;
+ int db_dirty;
+};
+
+struct ino_blk {
+ LIST_ENTRY(ino_blk) ib_next;
+ uint8_t *ib_buf;
+ int ib_dirty;
+ ufs2_daddr_t ib_blk;
+};
+LIST_HEAD(iblkhd, ino_blk);
+
+struct suj_cg {
+ LIST_ENTRY(suj_cg) sc_next;
+ struct blkhd sc_blkhash[SUJ_HASHSIZE];
+ struct inohd sc_inohash[SUJ_HASHSIZE];
+ struct iblkhd sc_iblkhash[SUJ_HASHSIZE];
+ struct ino_blk *sc_lastiblk;
+ struct suj_ino *sc_lastino;
+ struct suj_blk *sc_lastblk;
+ uint8_t *sc_cgbuf;
+ struct cg *sc_cgp;
+ int sc_dirty;
+ int sc_cgx;
+};
+
+static LIST_HEAD(cghd, suj_cg) cghash[SUJ_HASHSIZE];
+static LIST_HEAD(dblkhd, data_blk) dbhash[SUJ_HASHSIZE];
+static struct suj_cg *lastcg;
+static struct data_blk *lastblk;
+
+static TAILQ_HEAD(seghd, suj_seg) allsegs;
+static uint64_t oldseq;
+static struct uufsd *disk = NULL;
+static struct fs *fs = NULL;
+static ino_t sujino;
+
+/*
+ * Summary statistics.
+ */
+static uint64_t freefrags;
+static uint64_t freeblocks;
+static uint64_t freeinos;
+static uint64_t freedir;
+static uint64_t jbytes;
+static uint64_t jrecs;
+
+static jmp_buf jmpbuf;
+
+typedef void (*ino_visitor)(ino_t, ufs_lbn_t, ufs2_daddr_t, int);
+static void err_suj(const char *, ...) __dead2;
+static void ino_trunc(ino_t, off_t);
+static void ino_decr(ino_t);
+static void ino_adjust(struct suj_ino *);
+static void ino_build(struct suj_ino *);
+static int blk_isfree(ufs2_daddr_t);
+static void initsuj(void);
+
+static void *
+errmalloc(size_t n)
+{
+ void *a;
+
+ a = Malloc(n);
+ if (a == NULL)
+ err(EX_OSERR, "malloc(%zu)", n);
+ return (a);
+}
+
+/*
+ * When hit a fatal error in journalling check, print out
+ * the error and then offer to fallback to normal fsck.
+ */
+static void
+err_suj(const char * restrict fmt, ...)
+{
+ va_list ap;
+
+ if (preen)
+ (void)fprintf(stdout, "%s: ", cdevname);
+
+ va_start(ap, fmt);
+ (void)vfprintf(stdout, fmt, ap);
+ va_end(ap);
+
+ longjmp(jmpbuf, -1);
+}
+
+/*
+ * Open the given provider, load superblock.
+ */
+static void
+opendisk(const char *devnam)
+{
+ if (disk != NULL)
+ return;
+ disk = Malloc(sizeof(*disk));
+ if (disk == NULL)
+ err(EX_OSERR, "malloc(%zu)", sizeof(*disk));
+ if (ufs_disk_fillout(disk, devnam) == -1) {
+ err(EX_OSERR, "ufs_disk_fillout(%s) failed: %s", devnam,
+ disk->d_error);
+ }
+ fs = &disk->d_fs;
+ if (real_dev_bsize == 0 && ioctl(disk->d_fd, DIOCGSECTORSIZE,
+ &real_dev_bsize) == -1)
+ real_dev_bsize = secsize;
+ if (debug)
+ printf("dev_bsize %u\n", real_dev_bsize);
+}
+
+/*
+ * Mark file system as clean, write the super-block back, close the disk.
+ */
+static void
+closedisk(const char *devnam)
+{
+ struct csum *cgsum;
+ int i;
+
+ /*
+ * Recompute the fs summary info from correct cs summaries.
+ */
+ bzero(&fs->fs_cstotal, sizeof(struct csum_total));
+ for (i = 0; i < fs->fs_ncg; i++) {
+ cgsum = &fs->fs_cs(fs, i);
+ fs->fs_cstotal.cs_nffree += cgsum->cs_nffree;
+ fs->fs_cstotal.cs_nbfree += cgsum->cs_nbfree;
+ fs->fs_cstotal.cs_nifree += cgsum->cs_nifree;
+ fs->fs_cstotal.cs_ndir += cgsum->cs_ndir;
+ }
+ fs->fs_pendinginodes = 0;
+ fs->fs_pendingblocks = 0;
+ fs->fs_clean = 1;
+ fs->fs_time = time(NULL);
+ fs->fs_mtime = time(NULL);
+ if (sbwrite(disk, 0) == -1)
+ err(EX_OSERR, "sbwrite(%s)", devnam);
+ if (ufs_disk_close(disk) == -1)
+ err(EX_OSERR, "ufs_disk_close(%s)", devnam);
+ free(disk);
+ disk = NULL;
+ fs = NULL;
+}
+
+/*
+ * Lookup a cg by number in the hash so we can keep track of which cgs
+ * need stats rebuilt.
+ */
+static struct suj_cg *
+cg_lookup(int cgx)
+{
+ struct cghd *hd;
+ struct suj_cg *sc;
+
+ if (cgx < 0 || cgx >= fs->fs_ncg)
+ err_suj("Bad cg number %d\n", cgx);
+ if (lastcg && lastcg->sc_cgx == cgx)
+ return (lastcg);
+ hd = &cghash[SUJ_HASH(cgx)];
+ LIST_FOREACH(sc, hd, sc_next)
+ if (sc->sc_cgx == cgx) {
+ lastcg = sc;
+ return (sc);
+ }
+ sc = errmalloc(sizeof(*sc));
+ bzero(sc, sizeof(*sc));
+ sc->sc_cgbuf = errmalloc(fs->fs_bsize);
+ sc->sc_cgp = (struct cg *)sc->sc_cgbuf;
+ sc->sc_cgx = cgx;
+ LIST_INSERT_HEAD(hd, sc, sc_next);
+ if (bread(disk, fsbtodb(fs, cgtod(fs, sc->sc_cgx)), sc->sc_cgbuf,
+ fs->fs_bsize) == -1)
+ err_suj("Unable to read cylinder group %d\n", sc->sc_cgx);
+
+ return (sc);
+}
+
+/*
+ * Lookup an inode number in the hash and allocate a suj_ino if it does
+ * not exist.
+ */
+static struct suj_ino *
+ino_lookup(ino_t ino, int creat)
+{
+ struct suj_ino *sino;
+ struct inohd *hd;
+ struct suj_cg *sc;
+
+ sc = cg_lookup(ino_to_cg(fs, ino));
+ if (sc->sc_lastino && sc->sc_lastino->si_ino == ino)
+ return (sc->sc_lastino);
+ hd = &sc->sc_inohash[SUJ_HASH(ino)];
+ LIST_FOREACH(sino, hd, si_next)
+ if (sino->si_ino == ino)
+ return (sino);
+ if (creat == 0)
+ return (NULL);
+ sino = errmalloc(sizeof(*sino));
+ bzero(sino, sizeof(*sino));
+ sino->si_ino = ino;
+ TAILQ_INIT(&sino->si_recs);
+ TAILQ_INIT(&sino->si_newrecs);
+ TAILQ_INIT(&sino->si_movs);
+ LIST_INSERT_HEAD(hd, sino, si_next);
+
+ return (sino);
+}
+
+/*
+ * Lookup a block number in the hash and allocate a suj_blk if it does
+ * not exist.
+ */
+static struct suj_blk *
+blk_lookup(ufs2_daddr_t blk, int creat)
+{
+ struct suj_blk *sblk;
+ struct suj_cg *sc;
+ struct blkhd *hd;
+
+ sc = cg_lookup(dtog(fs, blk));
+ if (sc->sc_lastblk && sc->sc_lastblk->sb_blk == blk)
+ return (sc->sc_lastblk);
+ hd = &sc->sc_blkhash[SUJ_HASH(fragstoblks(fs, blk))];
+ LIST_FOREACH(sblk, hd, sb_next)
+ if (sblk->sb_blk == blk)
+ return (sblk);
+ if (creat == 0)
+ return (NULL);
+ sblk = errmalloc(sizeof(*sblk));
+ bzero(sblk, sizeof(*sblk));
+ sblk->sb_blk = blk;
+ TAILQ_INIT(&sblk->sb_recs);
+ LIST_INSERT_HEAD(hd, sblk, sb_next);
+
+ return (sblk);
+}
+
+static struct data_blk *
+dblk_lookup(ufs2_daddr_t blk)
+{
+ struct data_blk *dblk;
+ struct dblkhd *hd;
+
+ hd = &dbhash[SUJ_HASH(fragstoblks(fs, blk))];
+ if (lastblk && lastblk->db_blk == blk)
+ return (lastblk);
+ LIST_FOREACH(dblk, hd, db_next)
+ if (dblk->db_blk == blk)
+ return (dblk);
+ /*
+ * The inode block wasn't located, allocate a new one.
+ */
+ dblk = errmalloc(sizeof(*dblk));
+ bzero(dblk, sizeof(*dblk));
+ LIST_INSERT_HEAD(hd, dblk, db_next);
+ dblk->db_blk = blk;
+ return (dblk);
+}
+
+static uint8_t *
+dblk_read(ufs2_daddr_t blk, int size)
+{
+ struct data_blk *dblk;
+
+ dblk = dblk_lookup(blk);
+ /*
+ * I doubt size mismatches can happen in practice but it is trivial
+ * to handle.
+ */
+ if (size != dblk->db_size) {
+ if (dblk->db_buf)
+ free(dblk->db_buf);
+ dblk->db_buf = errmalloc(size);
+ dblk->db_size = size;
+ if (bread(disk, fsbtodb(fs, blk), dblk->db_buf, size) == -1)
+ err_suj("Failed to read data block %jd\n", blk);
+ }
+ return (dblk->db_buf);
+}
+
+static void
+dblk_dirty(ufs2_daddr_t blk)
+{
+ struct data_blk *dblk;
+
+ dblk = dblk_lookup(blk);
+ dblk->db_dirty = 1;
+}
+
+static void
+dblk_write(void)
+{
+ struct data_blk *dblk;
+ int i;
+
+ for (i = 0; i < SUJ_HASHSIZE; i++) {
+ LIST_FOREACH(dblk, &dbhash[i], db_next) {
+ if (dblk->db_dirty == 0 || dblk->db_size == 0)
+ continue;
+ if (bwrite(disk, fsbtodb(fs, dblk->db_blk),
+ dblk->db_buf, dblk->db_size) == -1)
+ err_suj("Unable to write block %jd\n",
+ dblk->db_blk);
+ }
+ }
+}
+
+static union dinode *
+ino_read(ino_t ino)
+{
+ struct ino_blk *iblk;
+ struct iblkhd *hd;
+ struct suj_cg *sc;
+ ufs2_daddr_t blk;
+ int off;
+
+ blk = ino_to_fsba(fs, ino);
+ sc = cg_lookup(ino_to_cg(fs, ino));
+ iblk = sc->sc_lastiblk;
+ if (iblk && iblk->ib_blk == blk)
+ goto found;
+ hd = &sc->sc_iblkhash[SUJ_HASH(fragstoblks(fs, blk))];
+ LIST_FOREACH(iblk, hd, ib_next)
+ if (iblk->ib_blk == blk)
+ goto found;
+ /*
+ * The inode block wasn't located, allocate a new one.
+ */
+ iblk = errmalloc(sizeof(*iblk));
+ bzero(iblk, sizeof(*iblk));
+ iblk->ib_buf = errmalloc(fs->fs_bsize);
+ iblk->ib_blk = blk;
+ LIST_INSERT_HEAD(hd, iblk, ib_next);
+ if (bread(disk, fsbtodb(fs, blk), iblk->ib_buf, fs->fs_bsize) == -1)
+ err_suj("Failed to read inode block %jd\n", blk);
+found:
+ sc->sc_lastiblk = iblk;
+ off = ino_to_fsbo(fs, ino);
+ if (fs->fs_magic == FS_UFS1_MAGIC)
+ return (union dinode *)&((struct ufs1_dinode *)iblk->ib_buf)[off];
+ else
+ return (union dinode *)&((struct ufs2_dinode *)iblk->ib_buf)[off];
+}
+
+static void
+ino_dirty(ino_t ino)
+{
+ struct ino_blk *iblk;
+ struct iblkhd *hd;
+ struct suj_cg *sc;
+ ufs2_daddr_t blk;
+
+ blk = ino_to_fsba(fs, ino);
+ sc = cg_lookup(ino_to_cg(fs, ino));
+ iblk = sc->sc_lastiblk;
+ if (iblk && iblk->ib_blk == blk) {
+ iblk->ib_dirty = 1;
+ return;
+ }
+ hd = &sc->sc_iblkhash[SUJ_HASH(fragstoblks(fs, blk))];
+ LIST_FOREACH(iblk, hd, ib_next) {
+ if (iblk->ib_blk == blk) {
+ iblk->ib_dirty = 1;
+ return;
+ }
+ }
+ ino_read(ino);
+ ino_dirty(ino);
+}
+
+static void
+iblk_write(struct ino_blk *iblk)
+{
+
+ if (iblk->ib_dirty == 0)
+ return;
+ if (bwrite(disk, fsbtodb(fs, iblk->ib_blk), iblk->ib_buf,
+ fs->fs_bsize) == -1)
+ err_suj("Failed to write inode block %jd\n", iblk->ib_blk);
+}
+
+static int
+blk_overlaps(struct jblkrec *brec, ufs2_daddr_t start, int frags)
+{
+ ufs2_daddr_t bstart;
+ ufs2_daddr_t bend;
+ ufs2_daddr_t end;
+
+ end = start + frags;
+ bstart = brec->jb_blkno + brec->jb_oldfrags;
+ bend = bstart + brec->jb_frags;
+ if (start < bend && end > bstart)
+ return (1);
+ return (0);
+}
+
+static int
+blk_equals(struct jblkrec *brec, ino_t ino, ufs_lbn_t lbn, ufs2_daddr_t start,
+ int frags)
+{
+
+ if (brec->jb_ino != ino || brec->jb_lbn != lbn)
+ return (0);
+ if (brec->jb_blkno + brec->jb_oldfrags != start)
+ return (0);
+ if (brec->jb_frags < frags)
+ return (0);
+ return (1);
+}
+
+static void
+blk_setmask(struct jblkrec *brec, int *mask)
+{
+ int i;
+
+ for (i = brec->jb_oldfrags; i < brec->jb_oldfrags + brec->jb_frags; i++)
+ *mask |= 1 << i;
+}
+
+/*
+ * Determine whether a given block has been reallocated to a new location.
+ * Returns a mask of overlapping bits if any frags have been reused or
+ * zero if the block has not been re-used and the contents can be trusted.
+ *
+ * This is used to ensure that an orphaned pointer due to truncate is safe
+ * to be freed. The mask value can be used to free partial blocks.
+ */
+static int
+blk_freemask(ufs2_daddr_t blk, ino_t ino, ufs_lbn_t lbn, int frags)
+{
+ struct suj_blk *sblk;
+ struct suj_rec *srec;
+ struct jblkrec *brec;
+ int mask;
+ int off;
+
+ /*
+ * To be certain we're not freeing a reallocated block we lookup
+ * this block in the blk hash and see if there is an allocation
+ * journal record that overlaps with any fragments in the block
+ * we're concerned with. If any fragments have ben reallocated
+ * the block has already been freed and re-used for another purpose.
+ */
+ mask = 0;
+ sblk = blk_lookup(blknum(fs, blk), 0);
+ if (sblk == NULL)
+ return (0);
+ off = blk - sblk->sb_blk;
+ TAILQ_FOREACH(srec, &sblk->sb_recs, sr_next) {
+ brec = (struct jblkrec *)srec->sr_rec;
+ /*
+ * If the block overlaps but does not match
+ * exactly this record refers to the current
+ * location.
+ */
+ if (blk_overlaps(brec, blk, frags) == 0)
+ continue;
+ if (blk_equals(brec, ino, lbn, blk, frags) == 1)
+ mask = 0;
+ else
+ blk_setmask(brec, &mask);
+ }
+ if (debug)
+ printf("blk_freemask: blk %jd sblk %jd off %d mask 0x%X\n",
+ blk, sblk->sb_blk, off, mask);
+ return (mask >> off);
+}
+
+/*
+ * Determine whether it is safe to follow an indirect. It is not safe
+ * if any part of the indirect has been reallocated or the last journal
+ * entry was an allocation. Just allocated indirects may not have valid
+ * pointers yet and all of their children will have their own records.
+ * It is also not safe to follow an indirect if the cg bitmap has been
+ * cleared as a new allocation may write to the block prior to the journal
+ * being written.
+ *
+ * Returns 1 if it's safe to follow the indirect and 0 otherwise.
+ */
+static int
+blk_isindir(ufs2_daddr_t blk, ino_t ino, ufs_lbn_t lbn)
+{
+ struct suj_blk *sblk;
+ struct jblkrec *brec;
+
+ sblk = blk_lookup(blk, 0);
+ if (sblk == NULL)
+ return (1);
+ if (TAILQ_EMPTY(&sblk->sb_recs))
+ return (1);
+ brec = (struct jblkrec *)TAILQ_LAST(&sblk->sb_recs, srechd)->sr_rec;
+ if (blk_equals(brec, ino, lbn, blk, fs->fs_frag))
+ if (brec->jb_op == JOP_FREEBLK)
+ return (!blk_isfree(blk));
+ return (0);
+}
+
+/*
+ * Clear an inode from the cg bitmap. If the inode was already clear return
+ * 0 so the caller knows it does not have to check the inode contents.
+ */
+static int
+ino_free(ino_t ino, int mode)
+{
+ struct suj_cg *sc;
+ uint8_t *inosused;
+ struct cg *cgp;
+ int cg;
+
+ cg = ino_to_cg(fs, ino);
+ ino = ino % fs->fs_ipg;
+ sc = cg_lookup(cg);
+ cgp = sc->sc_cgp;
+ inosused = cg_inosused(cgp);
+ /*
+ * The bitmap may never have made it to the disk so we have to
+ * conditionally clear. We can avoid writing the cg in this case.
+ */
+ if (isclr(inosused, ino))
+ return (0);
+ freeinos++;
+ clrbit(inosused, ino);
+ if (ino < cgp->cg_irotor)
+ cgp->cg_irotor = ino;
+ cgp->cg_cs.cs_nifree++;
+ if ((mode & IFMT) == IFDIR) {
+ freedir++;
+ cgp->cg_cs.cs_ndir--;
+ }
+ sc->sc_dirty = 1;
+
+ return (1);
+}
+
+/*
+ * Free 'frags' frags starting at filesystem block 'bno' skipping any frags
+ * set in the mask.
+ */
+static void
+blk_free(ufs2_daddr_t bno, int mask, int frags)
+{
+ ufs1_daddr_t fragno, cgbno;
+ struct suj_cg *sc;
+ struct cg *cgp;
+ int i, cg;
+ uint8_t *blksfree;
+
+ if (debug)
+ printf("Freeing %d frags at blk %jd mask 0x%x\n",
+ frags, bno, mask);
+ cg = dtog(fs, bno);
+ sc = cg_lookup(cg);
+ cgp = sc->sc_cgp;
+ cgbno = dtogd(fs, bno);
+ blksfree = cg_blksfree(cgp);
+
+ /*
+ * If it's not allocated we only wrote the journal entry
+ * and never the bitmaps. Here we unconditionally clear and
+ * resolve the cg summary later.
+ */
+ if (frags == fs->fs_frag && mask == 0) {
+ fragno = fragstoblks(fs, cgbno);
+ ffs_setblock(fs, blksfree, fragno);
+ freeblocks++;
+ } else {
+ /*
+ * deallocate the fragment
+ */
+ for (i = 0; i < frags; i++)
+ if ((mask & (1 << i)) == 0 && isclr(blksfree, cgbno +i)) {
+ freefrags++;
+ setbit(blksfree, cgbno + i);
+ }
+ }
+ sc->sc_dirty = 1;
+}
+
+/*
+ * Returns 1 if the whole block starting at 'bno' is marked free and 0
+ * otherwise.
+ */
+static int
+blk_isfree(ufs2_daddr_t bno)
+{
+ struct suj_cg *sc;
+
+ sc = cg_lookup(dtog(fs, bno));
+ return ffs_isblock(fs, cg_blksfree(sc->sc_cgp), dtogd(fs, bno));
+}
+
+/*
+ * Fetch an indirect block to find the block at a given lbn. The lbn
+ * may be negative to fetch a specific indirect block pointer or positive
+ * to fetch a specific block.
+ */
+static ufs2_daddr_t
+indir_blkatoff(ufs2_daddr_t blk, ino_t ino, ufs_lbn_t cur, ufs_lbn_t lbn)
+{
+ ufs2_daddr_t *bap2;
+ ufs2_daddr_t *bap1;
+ ufs_lbn_t lbnadd;
+ ufs_lbn_t base;
+ int level;
+ int i;
+
+ if (blk == 0)
+ return (0);
+ level = lbn_level(cur);
+ if (level == -1)
+ err_suj("Invalid indir lbn %jd\n", lbn);
+ if (level == 0 && lbn < 0)
+ err_suj("Invalid lbn %jd\n", lbn);
+ bap2 = (void *)dblk_read(blk, fs->fs_bsize);
+ bap1 = (void *)bap2;
+ lbnadd = 1;
+ base = -(cur + level);
+ for (i = level; i > 0; i--)
+ lbnadd *= NINDIR(fs);
+ if (lbn > 0)
+ i = (lbn - base) / lbnadd;
+ else
+ i = (-lbn - base) / lbnadd;
+ if (i < 0 || i >= NINDIR(fs))
+ err_suj("Invalid indirect index %d produced by lbn %jd\n",
+ i, lbn);
+ if (level == 0)
+ cur = base + (i * lbnadd);
+ else
+ cur = -(base + (i * lbnadd)) - (level - 1);
+ if (fs->fs_magic == FS_UFS1_MAGIC)
+ blk = bap1[i];
+ else
+ blk = bap2[i];
+ if (cur == lbn)
+ return (blk);
+ if (level == 0)
+ err_suj("Invalid lbn %jd at level 0\n", lbn);
+ return indir_blkatoff(blk, ino, cur, lbn);
+}
+
+/*
+ * Finds the disk block address at the specified lbn within the inode
+ * specified by ip. This follows the whole tree and honors di_size and
+ * di_extsize so it is a true test of reachability. The lbn may be
+ * negative if an extattr or indirect block is requested.
+ */
+static ufs2_daddr_t
+ino_blkatoff(union dinode *ip, ino_t ino, ufs_lbn_t lbn, int *frags)
+{
+ ufs_lbn_t tmpval;
+ ufs_lbn_t cur;
+ ufs_lbn_t next;
+ int i;
+
+ /*
+ * Handle extattr blocks first.
+ */
+ if (lbn < 0 && lbn >= -NXADDR) {
+ lbn = -1 - lbn;
+ if (lbn > lblkno(fs, ip->dp2.di_extsize - 1))
+ return (0);
+ *frags = numfrags(fs, sblksize(fs, ip->dp2.di_extsize, lbn));
+ return (ip->dp2.di_extb[lbn]);
+ }
+ /*
+ * Now direct and indirect.
+ */
+ if (DIP(ip, di_mode) == IFLNK &&
+ DIP(ip, di_size) < fs->fs_maxsymlinklen)
+ return (0);
+ if (lbn >= 0 && lbn < NDADDR) {
+ *frags = numfrags(fs, sblksize(fs, DIP(ip, di_size), lbn));
+ return (DIP(ip, di_db[lbn]));
+ }
+ *frags = fs->fs_frag;
+
+ for (i = 0, tmpval = NINDIR(fs), cur = NDADDR; i < NIADDR; i++,
+ tmpval *= NINDIR(fs), cur = next) {
+ next = cur + tmpval;
+ if (lbn == -cur - i)
+ return (DIP(ip, di_ib[i]));
+ /*
+ * Determine whether the lbn in question is within this tree.
+ */
+ if (lbn < 0 && -lbn >= next)
+ continue;
+ if (lbn > 0 && lbn >= next)
+ continue;
+ return indir_blkatoff(DIP(ip, di_ib[i]), ino, -cur - i, lbn);
+ }
+ err_suj("lbn %jd not in ino\n", lbn);
+ /* NOTREACHED */
+}
+
+/*
+ * Determine whether a block exists at a particular lbn in an inode.
+ * Returns 1 if found, 0 if not. lbn may be negative for indirects
+ * or ext blocks.
+ */
+static int
+blk_isat(ino_t ino, ufs_lbn_t lbn, ufs2_daddr_t blk, int *frags)
+{
+ union dinode *ip;
+ ufs2_daddr_t nblk;
+
+ ip = ino_read(ino);
+
+ if (DIP(ip, di_nlink) == 0 || DIP(ip, di_mode) == 0)
+ return (0);
+ nblk = ino_blkatoff(ip, ino, lbn, frags);
+
+ return (nblk == blk);
+}
+
+/*
+ * Clear the directory entry at diroff that should point to child. Minimal
+ * checking is done and it is assumed that this path was verified with isat.
+ */
+static void
+ino_clrat(ino_t parent, off_t diroff, ino_t child)
+{
+ union dinode *dip;
+ struct direct *dp;
+ ufs2_daddr_t blk;
+ uint8_t *block;
+ ufs_lbn_t lbn;
+ int blksize;
+ int frags;
+ int doff;
+
+ if (debug)
+ printf("Clearing inode %ju from parent %ju at offset %jd\n",
+ (uintmax_t)child, (uintmax_t)parent, diroff);
+
+ lbn = lblkno(fs, diroff);
+ doff = blkoff(fs, diroff);
+ dip = ino_read(parent);
+ blk = ino_blkatoff(dip, parent, lbn, &frags);
+ blksize = sblksize(fs, DIP(dip, di_size), lbn);
+ block = dblk_read(blk, blksize);
+ dp = (struct direct *)&block[doff];
+ if (dp->d_ino != child)
+ errx(1, "Inode %ju does not exist in %ju at %jd",
+ (uintmax_t)child, (uintmax_t)parent, diroff);
+ dp->d_ino = 0;
+ dblk_dirty(blk);
+ /*
+ * The actual .. reference count will already have been removed
+ * from the parent by the .. remref record.
+ */
+}
+
+/*
+ * Determines whether a pointer to an inode exists within a directory
+ * at a specified offset. Returns the mode of the found entry.
+ */
+static int
+ino_isat(ino_t parent, off_t diroff, ino_t child, int *mode, int *isdot)
+{
+ union dinode *dip;
+ struct direct *dp;
+ ufs2_daddr_t blk;
+ uint8_t *block;
+ ufs_lbn_t lbn;
+ int blksize;
+ int frags;
+ int dpoff;
+ int doff;
+
+ *isdot = 0;
+ dip = ino_read(parent);
+ *mode = DIP(dip, di_mode);
+ if ((*mode & IFMT) != IFDIR) {
+ if (debug) {
+ /*
+ * This can happen if the parent inode
+ * was reallocated.
+ */
+ if (*mode != 0)
+ printf("Directory %ju has bad mode %o\n",
+ (uintmax_t)parent, *mode);
+ else
+ printf("Directory %ju has zero mode\n",
+ (uintmax_t)parent);
+ }
+ return (0);
+ }
+ lbn = lblkno(fs, diroff);
+ doff = blkoff(fs, diroff);
+ blksize = sblksize(fs, DIP(dip, di_size), lbn);
+ if (diroff + DIRECTSIZ(1) > DIP(dip, di_size) || doff >= blksize) {
+ if (debug)
+ printf("ino %ju absent from %ju due to offset %jd"
+ " exceeding size %jd\n",
+ (uintmax_t)child, (uintmax_t)parent, diroff,
+ DIP(dip, di_size));
+ return (0);
+ }
+ blk = ino_blkatoff(dip, parent, lbn, &frags);
+ if (blk <= 0) {
+ if (debug)
+ printf("Sparse directory %ju", (uintmax_t)parent);
+ return (0);
+ }
+ block = dblk_read(blk, blksize);
+ /*
+ * Walk through the records from the start of the block to be
+ * certain we hit a valid record and not some junk in the middle
+ * of a file name. Stop when we reach or pass the expected offset.
+ */
+ dpoff = (doff / DIRBLKSIZ) * DIRBLKSIZ;
+ do {
+ dp = (struct direct *)&block[dpoff];
+ if (dpoff == doff)
+ break;
+ if (dp->d_reclen == 0)
+ break;
+ dpoff += dp->d_reclen;
+ } while (dpoff <= doff);
+ if (dpoff > fs->fs_bsize)
+ err_suj("Corrupt directory block in dir ino %ju\n",
+ (uintmax_t)parent);
+ /* Not found. */
+ if (dpoff != doff) {
+ if (debug)
+ printf("ino %ju not found in %ju, lbn %jd, dpoff %d\n",
+ (uintmax_t)child, (uintmax_t)parent, lbn, dpoff);
+ return (0);
+ }
+ /*
+ * We found the item in question. Record the mode and whether it's
+ * a . or .. link for the caller.
+ */
+ if (dp->d_ino == child) {
+ if (child == parent)
+ *isdot = 1;
+ else if (dp->d_namlen == 2 &&
+ dp->d_name[0] == '.' && dp->d_name[1] == '.')
+ *isdot = 1;
+ *mode = DTTOIF(dp->d_type);
+ return (1);
+ }
+ if (debug)
+ printf("ino %ju doesn't match dirent ino %ju in parent %ju\n",
+ (uintmax_t)child, (uintmax_t)dp->d_ino, (uintmax_t)parent);
+ return (0);
+}
+
+#define VISIT_INDIR 0x0001
+#define VISIT_EXT 0x0002
+#define VISIT_ROOT 0x0004 /* Operation came via root & valid pointers. */
+
+/*
+ * Read an indirect level which may or may not be linked into an inode.
+ */
+static void
+indir_visit(ino_t ino, ufs_lbn_t lbn, ufs2_daddr_t blk, uint64_t *frags,
+ ino_visitor visitor, int flags)
+{
+ ufs2_daddr_t *bap2;
+ ufs1_daddr_t *bap1;
+ ufs_lbn_t lbnadd;
+ ufs2_daddr_t nblk;
+ ufs_lbn_t nlbn;
+ int level;
+ int i;
+
+ /*
+ * Don't visit indirect blocks with contents we can't trust. This
+ * should only happen when indir_visit() is called to complete a
+ * truncate that never finished and not when a pointer is found via
+ * an inode.
+ */
+ if (blk == 0)
+ return;
+ level = lbn_level(lbn);
+ if (level == -1)
+ err_suj("Invalid level for lbn %jd\n", lbn);
+ if ((flags & VISIT_ROOT) == 0 && blk_isindir(blk, ino, lbn) == 0) {
+ if (debug)
+ printf("blk %jd ino %ju lbn %jd(%d) is not indir.\n",
+ blk, (uintmax_t)ino, lbn, level);
+ goto out;
+ }
+ lbnadd = 1;
+ for (i = level; i > 0; i--)
+ lbnadd *= NINDIR(fs);
+ bap1 = (void *)dblk_read(blk, fs->fs_bsize);
+ bap2 = (void *)bap1;
+ for (i = 0; i < NINDIR(fs); i++) {
+ if (fs->fs_magic == FS_UFS1_MAGIC)
+ nblk = *bap1++;
+ else
+ nblk = *bap2++;
+ if (nblk == 0)
+ continue;
+ if (level == 0) {
+ nlbn = -lbn + i * lbnadd;
+ (*frags) += fs->fs_frag;
+ visitor(ino, nlbn, nblk, fs->fs_frag);
+ } else {
+ nlbn = (lbn + 1) - (i * lbnadd);
+ indir_visit(ino, nlbn, nblk, frags, visitor, flags);
+ }
+ }
+out:
+ if (flags & VISIT_INDIR) {
+ (*frags) += fs->fs_frag;
+ visitor(ino, lbn, blk, fs->fs_frag);
+ }
+}
+
+/*
+ * Visit each block in an inode as specified by 'flags' and call a
+ * callback function. The callback may inspect or free blocks. The
+ * count of frags found according to the size in the file is returned.
+ * This is not valid for sparse files but may be used to determine
+ * the correct di_blocks for a file.
+ */
+static uint64_t
+ino_visit(union dinode *ip, ino_t ino, ino_visitor visitor, int flags)
+{
+ ufs_lbn_t nextlbn;
+ ufs_lbn_t tmpval;
+ ufs_lbn_t lbn;
+ uint64_t size;
+ uint64_t fragcnt;
+ int mode;
+ int frags;
+ int i;
+
+ size = DIP(ip, di_size);
+ mode = DIP(ip, di_mode) & IFMT;
+ fragcnt = 0;
+ if ((flags & VISIT_EXT) &&
+ fs->fs_magic == FS_UFS2_MAGIC && ip->dp2.di_extsize) {
+ for (i = 0; i < NXADDR; i++) {
+ if (ip->dp2.di_extb[i] == 0)
+ continue;
+ frags = sblksize(fs, ip->dp2.di_extsize, i);
+ frags = numfrags(fs, frags);
+ fragcnt += frags;
+ visitor(ino, -1 - i, ip->dp2.di_extb[i], frags);
+ }
+ }
+ /* Skip datablocks for short links and devices. */
+ if (mode == IFBLK || mode == IFCHR ||
+ (mode == IFLNK && size < fs->fs_maxsymlinklen))
+ return (fragcnt);
+ for (i = 0; i < NDADDR; i++) {
+ if (DIP(ip, di_db[i]) == 0)
+ continue;
+ frags = sblksize(fs, size, i);
+ frags = numfrags(fs, frags);
+ fragcnt += frags;
+ visitor(ino, i, DIP(ip, di_db[i]), frags);
+ }
+ /*
+ * We know the following indirects are real as we're following
+ * real pointers to them.
+ */
+ flags |= VISIT_ROOT;
+ for (i = 0, tmpval = NINDIR(fs), lbn = NDADDR; i < NIADDR; i++,
+ lbn = nextlbn) {
+ nextlbn = lbn + tmpval;
+ tmpval *= NINDIR(fs);
+ if (DIP(ip, di_ib[i]) == 0)
+ continue;
+ indir_visit(ino, -lbn - i, DIP(ip, di_ib[i]), &fragcnt, visitor,
+ flags);
+ }
+ return (fragcnt);
+}
+
+/*
+ * Null visitor function used when we just want to count blocks and
+ * record the lbn.
+ */
+ufs_lbn_t visitlbn;
+static void
+null_visit(ino_t ino, ufs_lbn_t lbn, ufs2_daddr_t blk, int frags)
+{
+ if (lbn > 0)
+ visitlbn = lbn;
+}
+
+/*
+ * Recalculate di_blocks when we discover that a block allocation or
+ * free was not successfully completed. The kernel does not roll this back
+ * because it would be too expensive to compute which indirects were
+ * reachable at the time the inode was written.
+ */
+static void
+ino_adjblks(struct suj_ino *sino)
+{
+ union dinode *ip;
+ uint64_t blocks;
+ uint64_t frags;
+ off_t isize;
+ off_t size;
+ ino_t ino;
+
+ ino = sino->si_ino;
+ ip = ino_read(ino);
+ /* No need to adjust zero'd inodes. */
+ if (DIP(ip, di_mode) == 0)
+ return;
+ /*
+ * Visit all blocks and count them as well as recording the last
+ * valid lbn in the file. If the file size doesn't agree with the
+ * last lbn we need to truncate to fix it. Otherwise just adjust
+ * the blocks count.
+ */
+ visitlbn = 0;
+ frags = ino_visit(ip, ino, null_visit, VISIT_INDIR | VISIT_EXT);
+ blocks = fsbtodb(fs, frags);
+ /*
+ * We assume the size and direct block list is kept coherent by
+ * softdep. For files that have extended into indirects we truncate
+ * to the size in the inode or the maximum size permitted by
+ * populated indirects.
+ */
+ if (visitlbn >= NDADDR) {
+ isize = DIP(ip, di_size);
+ size = lblktosize(fs, visitlbn + 1);
+ if (isize > size)
+ isize = size;
+ /* Always truncate to free any unpopulated indirects. */
+ ino_trunc(sino->si_ino, isize);
+ return;
+ }
+ if (blocks == DIP(ip, di_blocks))
+ return;
+ if (debug)
+ printf("ino %ju adjusting block count from %jd to %jd\n",
+ (uintmax_t)ino, DIP(ip, di_blocks), blocks);
+ DIP_SET(ip, di_blocks, blocks);
+ ino_dirty(ino);
+}
+
+static void
+blk_free_visit(ino_t ino, ufs_lbn_t lbn, ufs2_daddr_t blk, int frags)
+{
+
+ blk_free(blk, blk_freemask(blk, ino, lbn, frags), frags);
+}
+
+/*
+ * Free a block or tree of blocks that was previously rooted in ino at
+ * the given lbn. If the lbn is an indirect all children are freed
+ * recursively.
+ */
+static void
+blk_free_lbn(ufs2_daddr_t blk, ino_t ino, ufs_lbn_t lbn, int frags, int follow)
+{
+ uint64_t resid;
+ int mask;
+
+ mask = blk_freemask(blk, ino, lbn, frags);
+ resid = 0;
+ if (lbn <= -NDADDR && follow && mask == 0)
+ indir_visit(ino, lbn, blk, &resid, blk_free_visit, VISIT_INDIR);
+ else
+ blk_free(blk, mask, frags);
+}
+
+static void
+ino_setskip(struct suj_ino *sino, ino_t parent)
+{
+ int isdot;
+ int mode;
+
+ if (ino_isat(sino->si_ino, DOTDOT_OFFSET, parent, &mode, &isdot))
+ sino->si_skipparent = 1;
+}
+
+static void
+ino_remref(ino_t parent, ino_t child, uint64_t diroff, int isdotdot)
+{
+ struct suj_ino *sino;
+ struct suj_rec *srec;
+ struct jrefrec *rrec;
+
+ /*
+ * Lookup this inode to see if we have a record for it.
+ */
+ sino = ino_lookup(child, 0);
+ /*
+ * Tell any child directories we've already removed their
+ * parent link cnt. Don't try to adjust our link down again.
+ */
+ if (sino != NULL && isdotdot == 0)
+ ino_setskip(sino, parent);
+ /*
+ * No valid record for this inode. Just drop the on-disk
+ * link by one.
+ */
+ if (sino == NULL || sino->si_hasrecs == 0) {
+ ino_decr(child);
+ return;
+ }
+ /*
+ * Use ino_adjust() if ino_check() has already processed this
+ * child. If we lose the last non-dot reference to a
+ * directory it will be discarded.
+ */
+ if (sino->si_linkadj) {
+ sino->si_nlink--;
+ if (isdotdot)
+ sino->si_dotlinks--;
+ ino_adjust(sino);
+ return;
+ }
+ /*
+ * If we haven't yet processed this inode we need to make
+ * sure we will successfully discover the lost path. If not
+ * use nlinkadj to remember.
+ */
+ TAILQ_FOREACH(srec, &sino->si_recs, sr_next) {
+ rrec = (struct jrefrec *)srec->sr_rec;
+ if (rrec->jr_parent == parent &&
+ rrec->jr_diroff == diroff)
+ return;
+ }
+ sino->si_nlinkadj++;
+}
+
+/*
+ * Free the children of a directory when the directory is discarded.
+ */
+static void
+ino_free_children(ino_t ino, ufs_lbn_t lbn, ufs2_daddr_t blk, int frags)
+{
+ struct suj_ino *sino;
+ struct direct *dp;
+ off_t diroff;
+ uint8_t *block;
+ int skipparent;
+ int isdotdot;
+ int dpoff;
+ int size;
+
+ sino = ino_lookup(ino, 0);
+ if (sino)
+ skipparent = sino->si_skipparent;
+ else
+ skipparent = 0;
+ size = lfragtosize(fs, frags);
+ block = dblk_read(blk, size);
+ dp = (struct direct *)&block[0];
+ for (dpoff = 0; dpoff < size && dp->d_reclen; dpoff += dp->d_reclen) {
+ dp = (struct direct *)&block[dpoff];
+ if (dp->d_ino == 0 || dp->d_ino == WINO)
+ continue;
+ if (dp->d_namlen == 1 && dp->d_name[0] == '.')
+ continue;
+ isdotdot = dp->d_namlen == 2 && dp->d_name[0] == '.' &&
+ dp->d_name[1] == '.';
+ if (isdotdot && skipparent == 1)
+ continue;
+ if (debug)
+ printf("Directory %ju removing ino %ju name %s\n",
+ (uintmax_t)ino, (uintmax_t)dp->d_ino, dp->d_name);
+ diroff = lblktosize(fs, lbn) + dpoff;
+ ino_remref(ino, dp->d_ino, diroff, isdotdot);
+ }
+}
+
+/*
+ * Reclaim an inode, freeing all blocks and decrementing all children's
+ * link counts. Free the inode back to the cg.
+ */
+static void
+ino_reclaim(union dinode *ip, ino_t ino, int mode)
+{
+ uint32_t gen;
+
+ if (ino == ROOTINO)
+ err_suj("Attempting to free ROOTINO\n");
+ if (debug)
+ printf("Truncating and freeing ino %ju, nlink %d, mode %o\n",
+ (uintmax_t)ino, DIP(ip, di_nlink), DIP(ip, di_mode));
+
+ /* We are freeing an inode or directory. */
+ if ((DIP(ip, di_mode) & IFMT) == IFDIR)
+ ino_visit(ip, ino, ino_free_children, 0);
+ DIP_SET(ip, di_nlink, 0);
+ ino_visit(ip, ino, blk_free_visit, VISIT_EXT | VISIT_INDIR);
+ /* Here we have to clear the inode and release any blocks it holds. */
+ gen = DIP(ip, di_gen);
+ if (fs->fs_magic == FS_UFS1_MAGIC)
+ bzero(ip, sizeof(struct ufs1_dinode));
+ else
+ bzero(ip, sizeof(struct ufs2_dinode));
+ DIP_SET(ip, di_gen, gen);
+ ino_dirty(ino);
+ ino_free(ino, mode);
+ return;
+}
+
+/*
+ * Adjust an inode's link count down by one when a directory goes away.
+ */
+static void
+ino_decr(ino_t ino)
+{
+ union dinode *ip;
+ int reqlink;
+ int nlink;
+ int mode;
+
+ ip = ino_read(ino);
+ nlink = DIP(ip, di_nlink);
+ mode = DIP(ip, di_mode);
+ if (nlink < 1)
+ err_suj("Inode %d link count %d invalid\n", ino, nlink);
+ if (mode == 0)
+ err_suj("Inode %d has a link of %d with 0 mode\n", ino, nlink);
+ nlink--;
+ if ((mode & IFMT) == IFDIR)
+ reqlink = 2;
+ else
+ reqlink = 1;
+ if (nlink < reqlink) {
+ if (debug)
+ printf("ino %ju not enough links to live %d < %d\n",
+ (uintmax_t)ino, nlink, reqlink);
+ ino_reclaim(ip, ino, mode);
+ return;
+ }
+ DIP_SET(ip, di_nlink, nlink);
+ ino_dirty(ino);
+}
+
+/*
+ * Adjust the inode link count to 'nlink'. If the count reaches zero
+ * free it.
+ */
+static void
+ino_adjust(struct suj_ino *sino)
+{
+ struct jrefrec *rrec;
+ struct suj_rec *srec;
+ struct suj_ino *stmp;
+ union dinode *ip;
+ nlink_t nlink;
+ int recmode;
+ int reqlink;
+ int isdot;
+ int mode;
+ ino_t ino;
+
+ nlink = sino->si_nlink;
+ ino = sino->si_ino;
+ mode = sino->si_mode & IFMT;
+ /*
+ * If it's a directory with no dot links, it was truncated before
+ * the name was cleared. We need to clear the dirent that
+ * points at it.
+ */
+ if (mode == IFDIR && nlink == 1 && sino->si_dotlinks == 0) {
+ sino->si_nlink = nlink = 0;
+ TAILQ_FOREACH(srec, &sino->si_recs, sr_next) {
+ rrec = (struct jrefrec *)srec->sr_rec;
+ if (ino_isat(rrec->jr_parent, rrec->jr_diroff, ino,
+ &recmode, &isdot) == 0)
+ continue;
+ ino_clrat(rrec->jr_parent, rrec->jr_diroff, ino);
+ break;
+ }
+ if (srec == NULL)
+ errx(1, "Directory %ju name not found", (uintmax_t)ino);
+ }
+ /*
+ * If it's a directory with no real names pointing to it go ahead
+ * and truncate it. This will free any children.
+ */
+ if (mode == IFDIR && nlink - sino->si_dotlinks == 0) {
+ sino->si_nlink = nlink = 0;
+ /*
+ * Mark any .. links so they know not to free this inode
+ * when they are removed.
+ */
+ TAILQ_FOREACH(srec, &sino->si_recs, sr_next) {
+ rrec = (struct jrefrec *)srec->sr_rec;
+ if (rrec->jr_diroff == DOTDOT_OFFSET) {
+ stmp = ino_lookup(rrec->jr_parent, 0);
+ if (stmp)
+ ino_setskip(stmp, ino);
+ }
+ }
+ }
+ ip = ino_read(ino);
+ mode = DIP(ip, di_mode) & IFMT;
+ if (nlink > LINK_MAX)
+ err_suj("ino %ju nlink manipulation error, new %d, old %d\n",
+ (uintmax_t)ino, nlink, DIP(ip, di_nlink));
+ if (debug)
+ printf("Adjusting ino %ju, nlink %d, old link %d lastmode %o\n",
+ (uintmax_t)ino, nlink, DIP(ip, di_nlink), sino->si_mode);
+ if (mode == 0) {
+ if (debug)
+ printf("ino %ju, zero inode freeing bitmap\n",
+ (uintmax_t)ino);
+ ino_free(ino, sino->si_mode);
+ return;
+ }
+ /* XXX Should be an assert? */
+ if (mode != sino->si_mode && debug)
+ printf("ino %ju, mode %o != %o\n",
+ (uintmax_t)ino, mode, sino->si_mode);
+ if ((mode & IFMT) == IFDIR)
+ reqlink = 2;
+ else
+ reqlink = 1;
+ /* If the inode doesn't have enough links to live, free it. */
+ if (nlink < reqlink) {
+ if (debug)
+ printf("ino %ju not enough links to live %d < %d\n",
+ (uintmax_t)ino, nlink, reqlink);
+ ino_reclaim(ip, ino, mode);
+ return;
+ }
+ /* If required write the updated link count. */
+ if (DIP(ip, di_nlink) == nlink) {
+ if (debug)
+ printf("ino %ju, link matches, skipping.\n",
+ (uintmax_t)ino);
+ return;
+ }
+ DIP_SET(ip, di_nlink, nlink);
+ ino_dirty(ino);
+}
+
+/*
+ * Truncate some or all blocks in an indirect, freeing any that are required
+ * and zeroing the indirect.
+ */
+static void
+indir_trunc(ino_t ino, ufs_lbn_t lbn, ufs2_daddr_t blk, ufs_lbn_t lastlbn)
+{
+ ufs2_daddr_t *bap2;
+ ufs1_daddr_t *bap1;
+ ufs_lbn_t lbnadd;
+ ufs2_daddr_t nblk;
+ ufs_lbn_t next;
+ ufs_lbn_t nlbn;
+ int dirty;
+ int level;
+ int i;
+
+ if (blk == 0)
+ return;
+ dirty = 0;
+ level = lbn_level(lbn);
+ if (level == -1)
+ err_suj("Invalid level for lbn %jd\n", lbn);
+ lbnadd = 1;
+ for (i = level; i > 0; i--)
+ lbnadd *= NINDIR(fs);
+ bap1 = (void *)dblk_read(blk, fs->fs_bsize);
+ bap2 = (void *)bap1;
+ for (i = 0; i < NINDIR(fs); i++) {
+ if (fs->fs_magic == FS_UFS1_MAGIC)
+ nblk = *bap1++;
+ else
+ nblk = *bap2++;
+ if (nblk == 0)
+ continue;
+ if (level != 0) {
+ nlbn = (lbn + 1) - (i * lbnadd);
+ /*
+ * Calculate the lbn of the next indirect to
+ * determine if any of this indirect must be
+ * reclaimed.
+ */
+ next = -(lbn + level) + ((i+1) * lbnadd);
+ if (next <= lastlbn)
+ continue;
+ indir_trunc(ino, nlbn, nblk, lastlbn);
+ /* If all of this indirect was reclaimed, free it. */
+ nlbn = next - lbnadd;
+ if (nlbn < lastlbn)
+ continue;
+ } else {
+ nlbn = -lbn + i * lbnadd;
+ if (nlbn < lastlbn)
+ continue;
+ }
+ dirty = 1;
+ blk_free(nblk, 0, fs->fs_frag);
+ if (fs->fs_magic == FS_UFS1_MAGIC)
+ *(bap1 - 1) = 0;
+ else
+ *(bap2 - 1) = 0;
+ }
+ if (dirty)
+ dblk_dirty(blk);
+}
+
+/*
+ * Truncate an inode to the minimum of the given size or the last populated
+ * block after any over size have been discarded. The kernel would allocate
+ * the last block in the file but fsck does not and neither do we. This
+ * code never extends files, only shrinks them.
+ */
+static void
+ino_trunc(ino_t ino, off_t size)
+{
+ union dinode *ip;
+ ufs2_daddr_t bn;
+ uint64_t totalfrags;
+ ufs_lbn_t nextlbn;
+ ufs_lbn_t lastlbn;
+ ufs_lbn_t tmpval;
+ ufs_lbn_t lbn;
+ ufs_lbn_t i;
+ int frags;
+ off_t cursize;
+ off_t off;
+ int mode;
+
+ ip = ino_read(ino);
+ mode = DIP(ip, di_mode) & IFMT;
+ cursize = DIP(ip, di_size);
+ if (debug)
+ printf("Truncating ino %ju, mode %o to size %jd from size %jd\n",
+ (uintmax_t)ino, mode, size, cursize);
+
+ /* Skip datablocks for short links and devices. */
+ if (mode == 0 || mode == IFBLK || mode == IFCHR ||
+ (mode == IFLNK && cursize < fs->fs_maxsymlinklen))
+ return;
+ /* Don't extend. */
+ if (size > cursize)
+ size = cursize;
+ lastlbn = lblkno(fs, blkroundup(fs, size));
+ for (i = lastlbn; i < NDADDR; i++) {
+ if (DIP(ip, di_db[i]) == 0)
+ continue;
+ frags = sblksize(fs, cursize, i);
+ frags = numfrags(fs, frags);
+ blk_free(DIP(ip, di_db[i]), 0, frags);
+ DIP_SET(ip, di_db[i], 0);
+ }
+ /*
+ * Follow indirect blocks, freeing anything required.
+ */
+ for (i = 0, tmpval = NINDIR(fs), lbn = NDADDR; i < NIADDR; i++,
+ lbn = nextlbn) {
+ nextlbn = lbn + tmpval;
+ tmpval *= NINDIR(fs);
+ /* If we're not freeing any in this indirect range skip it. */
+ if (lastlbn >= nextlbn)
+ continue;
+ if (DIP(ip, di_ib[i]) == 0)
+ continue;
+ indir_trunc(ino, -lbn - i, DIP(ip, di_ib[i]), lastlbn);
+ /* If we freed everything in this indirect free the indir. */
+ if (lastlbn > lbn)
+ continue;
+ blk_free(DIP(ip, di_ib[i]), 0, frags);
+ DIP_SET(ip, di_ib[i], 0);
+ }
+ ino_dirty(ino);
+ /*
+ * Now that we've freed any whole blocks that exceed the desired
+ * truncation size, figure out how many blocks remain and what the
+ * last populated lbn is. We will set the size to this last lbn
+ * rather than worrying about allocating the final lbn as the kernel
+ * would've done. This is consistent with normal fsck behavior.
+ */
+ visitlbn = 0;
+ totalfrags = ino_visit(ip, ino, null_visit, VISIT_INDIR | VISIT_EXT);
+ if (size > lblktosize(fs, visitlbn + 1))
+ size = lblktosize(fs, visitlbn + 1);
+ /*
+ * If we're truncating direct blocks we have to adjust frags
+ * accordingly.
+ */
+ if (visitlbn < NDADDR && totalfrags) {
+ long oldspace, newspace;
+
+ bn = DIP(ip, di_db[visitlbn]);
+ if (bn == 0)
+ err_suj("Bad blk at ino %ju lbn %jd\n",
+ (uintmax_t)ino, visitlbn);
+ oldspace = sblksize(fs, cursize, visitlbn);
+ newspace = sblksize(fs, size, visitlbn);
+ if (oldspace != newspace) {
+ bn += numfrags(fs, newspace);
+ frags = numfrags(fs, oldspace - newspace);
+ blk_free(bn, 0, frags);
+ totalfrags -= frags;
+ }
+ }
+ DIP_SET(ip, di_blocks, fsbtodb(fs, totalfrags));
+ DIP_SET(ip, di_size, size);
+ /*
+ * If we've truncated into the middle of a block or frag we have
+ * to zero it here. Otherwise the file could extend into
+ * uninitialized space later.
+ */
+ off = blkoff(fs, size);
+ if (off && DIP(ip, di_mode) != IFDIR) {
+ uint8_t *buf;
+ long clrsize;
+
+ bn = ino_blkatoff(ip, ino, visitlbn, &frags);
+ if (bn == 0)
+ err_suj("Block missing from ino %ju at lbn %jd\n",
+ (uintmax_t)ino, visitlbn);
+ clrsize = frags * fs->fs_fsize;
+ buf = dblk_read(bn, clrsize);
+ clrsize -= off;
+ buf += off;
+ bzero(buf, clrsize);
+ dblk_dirty(bn);
+ }
+ return;
+}
+
+/*
+ * Process records available for one inode and determine whether the
+ * link count is correct or needs adjusting.
+ */
+static void
+ino_check(struct suj_ino *sino)
+{
+ struct suj_rec *srec;
+ struct jrefrec *rrec;
+ nlink_t dotlinks;
+ int newlinks;
+ int removes;
+ int nlink;
+ ino_t ino;
+ int isdot;
+ int isat;
+ int mode;
+
+ if (sino->si_hasrecs == 0)
+ return;
+ ino = sino->si_ino;
+ rrec = (struct jrefrec *)TAILQ_FIRST(&sino->si_recs)->sr_rec;
+ nlink = rrec->jr_nlink;
+ newlinks = 0;
+ dotlinks = 0;
+ removes = sino->si_nlinkadj;
+ TAILQ_FOREACH(srec, &sino->si_recs, sr_next) {
+ rrec = (struct jrefrec *)srec->sr_rec;
+ isat = ino_isat(rrec->jr_parent, rrec->jr_diroff,
+ rrec->jr_ino, &mode, &isdot);
+ if (isat && (mode & IFMT) != (rrec->jr_mode & IFMT))
+ err_suj("Inode mode/directory type mismatch %o != %o\n",
+ mode, rrec->jr_mode);
+ if (debug)
+ printf("jrefrec: op %d ino %ju, nlink %d, parent %d, "
+ "diroff %jd, mode %o, isat %d, isdot %d\n",
+ rrec->jr_op, (uintmax_t)rrec->jr_ino,
+ rrec->jr_nlink, rrec->jr_parent, rrec->jr_diroff,
+ rrec->jr_mode, isat, isdot);
+ mode = rrec->jr_mode & IFMT;
+ if (rrec->jr_op == JOP_REMREF)
+ removes++;
+ newlinks += isat;
+ if (isdot)
+ dotlinks += isat;
+ }
+ /*
+ * The number of links that remain are the starting link count
+ * subtracted by the total number of removes with the total
+ * links discovered back in. An incomplete remove thus
+ * makes no change to the link count but an add increases
+ * by one.
+ */
+ if (debug)
+ printf("ino %ju nlink %d newlinks %d removes %d dotlinks %d\n",
+ (uintmax_t)ino, nlink, newlinks, removes, dotlinks);
+ nlink += newlinks;
+ nlink -= removes;
+ sino->si_linkadj = 1;
+ sino->si_nlink = nlink;
+ sino->si_dotlinks = dotlinks;
+ sino->si_mode = mode;
+ ino_adjust(sino);
+}
+
+/*
+ * Process records available for one block and determine whether it is
+ * still allocated and whether the owning inode needs to be updated or
+ * a free completed.
+ */
+static void
+blk_check(struct suj_blk *sblk)
+{
+ struct suj_rec *srec;
+ struct jblkrec *brec;
+ struct suj_ino *sino;
+ ufs2_daddr_t blk;
+ int mask;
+ int frags;
+ int isat;
+
+ /*
+ * Each suj_blk actually contains records for any fragments in that
+ * block. As a result we must evaluate each record individually.
+ */
+ sino = NULL;
+ TAILQ_FOREACH(srec, &sblk->sb_recs, sr_next) {
+ brec = (struct jblkrec *)srec->sr_rec;
+ frags = brec->jb_frags;
+ blk = brec->jb_blkno + brec->jb_oldfrags;
+ isat = blk_isat(brec->jb_ino, brec->jb_lbn, blk, &frags);
+ if (sino == NULL || sino->si_ino != brec->jb_ino) {
+ sino = ino_lookup(brec->jb_ino, 1);
+ sino->si_blkadj = 1;
+ }
+ if (debug)
+ printf("op %d blk %jd ino %ju lbn %jd frags %d isat %d (%d)\n",
+ brec->jb_op, blk, (uintmax_t)brec->jb_ino,
+ brec->jb_lbn, brec->jb_frags, isat, frags);
+ /*
+ * If we found the block at this address we still have to
+ * determine if we need to free the tail end that was
+ * added by adding contiguous fragments from the same block.
+ */
+ if (isat == 1) {
+ if (frags == brec->jb_frags)
+ continue;
+ mask = blk_freemask(blk, brec->jb_ino, brec->jb_lbn,
+ brec->jb_frags);
+ mask >>= frags;
+ blk += frags;
+ frags = brec->jb_frags - frags;
+ blk_free(blk, mask, frags);
+ continue;
+ }
+ /*
+ * The block wasn't found, attempt to free it. It won't be
+ * freed if it was actually reallocated. If this was an
+ * allocation we don't want to follow indirects as they
+ * may not be written yet. Any children of the indirect will
+ * have their own records. If it's a free we need to
+ * recursively free children.
+ */
+ blk_free_lbn(blk, brec->jb_ino, brec->jb_lbn, brec->jb_frags,
+ brec->jb_op == JOP_FREEBLK);
+ }
+}
+
+/*
+ * Walk the list of inode records for this cg and resolve moved and duplicate
+ * inode references now that we have a complete picture.
+ */
+static void
+cg_build(struct suj_cg *sc)
+{
+ struct suj_ino *sino;
+ int i;
+
+ for (i = 0; i < SUJ_HASHSIZE; i++)
+ LIST_FOREACH(sino, &sc->sc_inohash[i], si_next)
+ ino_build(sino);
+}
+
+/*
+ * Handle inodes requiring truncation. This must be done prior to
+ * looking up any inodes in directories.
+ */
+static void
+cg_trunc(struct suj_cg *sc)
+{
+ struct suj_ino *sino;
+ int i;
+
+ for (i = 0; i < SUJ_HASHSIZE; i++) {
+ LIST_FOREACH(sino, &sc->sc_inohash[i], si_next) {
+ if (sino->si_trunc) {
+ ino_trunc(sino->si_ino,
+ sino->si_trunc->jt_size);
+ sino->si_blkadj = 0;
+ sino->si_trunc = NULL;
+ }
+ if (sino->si_blkadj)
+ ino_adjblks(sino);
+ }
+ }
+}
+
+static void
+cg_adj_blk(struct suj_cg *sc)
+{
+ struct suj_ino *sino;
+ int i;
+
+ for (i = 0; i < SUJ_HASHSIZE; i++) {
+ LIST_FOREACH(sino, &sc->sc_inohash[i], si_next) {
+ if (sino->si_blkadj)
+ ino_adjblks(sino);
+ }
+ }
+}
+
+/*
+ * Free any partially allocated blocks and then resolve inode block
+ * counts.
+ */
+static void
+cg_check_blk(struct suj_cg *sc)
+{
+ struct suj_blk *sblk;
+ int i;
+
+
+ for (i = 0; i < SUJ_HASHSIZE; i++)
+ LIST_FOREACH(sblk, &sc->sc_blkhash[i], sb_next)
+ blk_check(sblk);
+}
+
+/*
+ * Walk the list of inode records for this cg, recovering any
+ * changes which were not complete at the time of crash.
+ */
+static void
+cg_check_ino(struct suj_cg *sc)
+{
+ struct suj_ino *sino;
+ int i;
+
+ for (i = 0; i < SUJ_HASHSIZE; i++)
+ LIST_FOREACH(sino, &sc->sc_inohash[i], si_next)
+ ino_check(sino);
+}
+
+/*
+ * Write a potentially dirty cg. Recalculate the summary information and
+ * update the superblock summary.
+ */
+static void
+cg_write(struct suj_cg *sc)
+{
+ ufs1_daddr_t fragno, cgbno, maxbno;
+ u_int8_t *blksfree;
+ struct cg *cgp;
+ int blk;
+ int i;
+
+ if (sc->sc_dirty == 0)
+ return;
+ /*
+ * Fix the frag and cluster summary.
+ */
+ cgp = sc->sc_cgp;
+ cgp->cg_cs.cs_nbfree = 0;
+ cgp->cg_cs.cs_nffree = 0;
+ bzero(&cgp->cg_frsum, sizeof(cgp->cg_frsum));
+ maxbno = fragstoblks(fs, fs->fs_fpg);
+ if (fs->fs_contigsumsize > 0) {
+ for (i = 1; i <= fs->fs_contigsumsize; i++)
+ cg_clustersum(cgp)[i] = 0;
+ bzero(cg_clustersfree(cgp), howmany(maxbno, CHAR_BIT));
+ }
+ blksfree = cg_blksfree(cgp);
+ for (cgbno = 0; cgbno < maxbno; cgbno++) {
+ if (ffs_isfreeblock(fs, blksfree, cgbno))
+ continue;
+ if (ffs_isblock(fs, blksfree, cgbno)) {
+ ffs_clusteracct(fs, cgp, cgbno, 1);
+ cgp->cg_cs.cs_nbfree++;
+ continue;
+ }
+ fragno = blkstofrags(fs, cgbno);
+ blk = blkmap(fs, blksfree, fragno);
+ ffs_fragacct(fs, blk, cgp->cg_frsum, 1);
+ for (i = 0; i < fs->fs_frag; i++)
+ if (isset(blksfree, fragno + i))
+ cgp->cg_cs.cs_nffree++;
+ }
+ /*
+ * Update the superblock cg summary from our now correct values
+ * before writing the block.
+ */
+ fs->fs_cs(fs, sc->sc_cgx) = cgp->cg_cs;
+ if (bwrite(disk, fsbtodb(fs, cgtod(fs, sc->sc_cgx)), sc->sc_cgbuf,
+ fs->fs_bsize) == -1)
+ err_suj("Unable to write cylinder group %d\n", sc->sc_cgx);
+}
+
+/*
+ * Write out any modified inodes.
+ */
+static void
+cg_write_inos(struct suj_cg *sc)
+{
+ struct ino_blk *iblk;
+ int i;
+
+ for (i = 0; i < SUJ_HASHSIZE; i++)
+ LIST_FOREACH(iblk, &sc->sc_iblkhash[i], ib_next)
+ if (iblk->ib_dirty)
+ iblk_write(iblk);
+}
+
+static void
+cg_apply(void (*apply)(struct suj_cg *))
+{
+ struct suj_cg *scg;
+ int i;
+
+ for (i = 0; i < SUJ_HASHSIZE; i++)
+ LIST_FOREACH(scg, &cghash[i], sc_next)
+ apply(scg);
+}
+
+/*
+ * Process the unlinked but referenced file list. Freeing all inodes.
+ */
+static void
+ino_unlinked(void)
+{
+ union dinode *ip;
+ uint16_t mode;
+ ino_t inon;
+ ino_t ino;
+
+ ino = fs->fs_sujfree;
+ fs->fs_sujfree = 0;
+ while (ino != 0) {
+ ip = ino_read(ino);
+ mode = DIP(ip, di_mode) & IFMT;
+ inon = DIP(ip, di_freelink);
+ DIP_SET(ip, di_freelink, 0);
+ /*
+ * XXX Should this be an errx?
+ */
+ if (DIP(ip, di_nlink) == 0) {
+ if (debug)
+ printf("Freeing unlinked ino %ju mode %o\n",
+ (uintmax_t)ino, mode);
+ ino_reclaim(ip, ino, mode);
+ } else if (debug)
+ printf("Skipping ino %ju mode %o with link %d\n",
+ (uintmax_t)ino, mode, DIP(ip, di_nlink));
+ ino = inon;
+ }
+}
+
+/*
+ * Append a new record to the list of records requiring processing.
+ */
+static void
+ino_append(union jrec *rec)
+{
+ struct jrefrec *refrec;
+ struct jmvrec *mvrec;
+ struct suj_ino *sino;
+ struct suj_rec *srec;
+
+ mvrec = &rec->rec_jmvrec;
+ refrec = &rec->rec_jrefrec;
+ if (debug && mvrec->jm_op == JOP_MVREF)
+ printf("ino move: ino %d, parent %d, diroff %jd, oldoff %jd\n",
+ mvrec->jm_ino, mvrec->jm_parent, mvrec->jm_newoff,
+ mvrec->jm_oldoff);
+ else if (debug &&
+ (refrec->jr_op == JOP_ADDREF || refrec->jr_op == JOP_REMREF))
+ printf("ino ref: op %d, ino %d, nlink %d, "
+ "parent %d, diroff %jd\n",
+ refrec->jr_op, refrec->jr_ino, refrec->jr_nlink,
+ refrec->jr_parent, refrec->jr_diroff);
+ sino = ino_lookup(((struct jrefrec *)rec)->jr_ino, 1);
+ sino->si_hasrecs = 1;
+ srec = errmalloc(sizeof(*srec));
+ srec->sr_rec = rec;
+ TAILQ_INSERT_TAIL(&sino->si_newrecs, srec, sr_next);
+}
+
+/*
+ * Add a reference adjustment to the sino list and eliminate dups. The
+ * primary loop in ino_build_ref() checks for dups but new ones may be
+ * created as a result of offset adjustments.
+ */
+static void
+ino_add_ref(struct suj_ino *sino, struct suj_rec *srec)
+{
+ struct jrefrec *refrec;
+ struct suj_rec *srn;
+ struct jrefrec *rrn;
+
+ refrec = (struct jrefrec *)srec->sr_rec;
+ /*
+ * We walk backwards so that the oldest link count is preserved. If
+ * an add record conflicts with a remove keep the remove. Redundant
+ * removes are eliminated in ino_build_ref. Otherwise we keep the
+ * oldest record at a given location.
+ */
+ for (srn = TAILQ_LAST(&sino->si_recs, srechd); srn;
+ srn = TAILQ_PREV(srn, srechd, sr_next)) {
+ rrn = (struct jrefrec *)srn->sr_rec;
+ if (rrn->jr_parent != refrec->jr_parent ||
+ rrn->jr_diroff != refrec->jr_diroff)
+ continue;
+ if (rrn->jr_op == JOP_REMREF || refrec->jr_op == JOP_ADDREF) {
+ rrn->jr_mode = refrec->jr_mode;
+ return;
+ }
+ /*
+ * Adding a remove.
+ *
+ * Replace the record in place with the old nlink in case
+ * we replace the head of the list. Abandon srec as a dup.
+ */
+ refrec->jr_nlink = rrn->jr_nlink;
+ srn->sr_rec = srec->sr_rec;
+ return;
+ }
+ TAILQ_INSERT_TAIL(&sino->si_recs, srec, sr_next);
+}
+
+/*
+ * Create a duplicate of a reference at a previous location.
+ */
+static void
+ino_dup_ref(struct suj_ino *sino, struct jrefrec *refrec, off_t diroff)
+{
+ struct jrefrec *rrn;
+ struct suj_rec *srn;
+
+ rrn = errmalloc(sizeof(*refrec));
+ *rrn = *refrec;
+ rrn->jr_op = JOP_ADDREF;
+ rrn->jr_diroff = diroff;
+ srn = errmalloc(sizeof(*srn));
+ srn->sr_rec = (union jrec *)rrn;
+ ino_add_ref(sino, srn);
+}
+
+/*
+ * Add a reference to the list at all known locations. We follow the offset
+ * changes for a single instance and create duplicate add refs at each so
+ * that we can tolerate any version of the directory block. Eliminate
+ * removes which collide with adds that are seen in the journal. They should
+ * not adjust the link count down.
+ */
+static void
+ino_build_ref(struct suj_ino *sino, struct suj_rec *srec)
+{
+ struct jrefrec *refrec;
+ struct jmvrec *mvrec;
+ struct suj_rec *srp;
+ struct suj_rec *srn;
+ struct jrefrec *rrn;
+ off_t diroff;
+
+ refrec = (struct jrefrec *)srec->sr_rec;
+ /*
+ * Search for a mvrec that matches this offset. Whether it's an add
+ * or a remove we can delete the mvref after creating a dup record in
+ * the old location.
+ */
+ if (!TAILQ_EMPTY(&sino->si_movs)) {
+ diroff = refrec->jr_diroff;
+ for (srn = TAILQ_LAST(&sino->si_movs, srechd); srn; srn = srp) {
+ srp = TAILQ_PREV(srn, srechd, sr_next);
+ mvrec = (struct jmvrec *)srn->sr_rec;
+ if (mvrec->jm_parent != refrec->jr_parent ||
+ mvrec->jm_newoff != diroff)
+ continue;
+ diroff = mvrec->jm_oldoff;
+ TAILQ_REMOVE(&sino->si_movs, srn, sr_next);
+ free(srn);
+ ino_dup_ref(sino, refrec, diroff);
+ }
+ }
+ /*
+ * If a remove wasn't eliminated by an earlier add just append it to
+ * the list.
+ */
+ if (refrec->jr_op == JOP_REMREF) {
+ ino_add_ref(sino, srec);
+ return;
+ }
+ /*
+ * Walk the list of records waiting to be added to the list. We
+ * must check for moves that apply to our current offset and remove
+ * them from the list. Remove any duplicates to eliminate removes
+ * with corresponding adds.
+ */
+ TAILQ_FOREACH_SAFE(srn, &sino->si_newrecs, sr_next, srp) {
+ switch (srn->sr_rec->rec_jrefrec.jr_op) {
+ case JOP_ADDREF:
+ /*
+ * This should actually be an error we should
+ * have a remove for every add journaled.
+ */
+ rrn = (struct jrefrec *)srn->sr_rec;
+ if (rrn->jr_parent != refrec->jr_parent ||
+ rrn->jr_diroff != refrec->jr_diroff)
+ break;
+ TAILQ_REMOVE(&sino->si_newrecs, srn, sr_next);
+ break;
+ case JOP_REMREF:
+ /*
+ * Once we remove the current iteration of the
+ * record at this address we're done.
+ */
+ rrn = (struct jrefrec *)srn->sr_rec;
+ if (rrn->jr_parent != refrec->jr_parent ||
+ rrn->jr_diroff != refrec->jr_diroff)
+ break;
+ TAILQ_REMOVE(&sino->si_newrecs, srn, sr_next);
+ ino_add_ref(sino, srec);
+ return;
+ case JOP_MVREF:
+ /*
+ * Update our diroff based on any moves that match
+ * and remove the move.
+ */
+ mvrec = (struct jmvrec *)srn->sr_rec;
+ if (mvrec->jm_parent != refrec->jr_parent ||
+ mvrec->jm_oldoff != refrec->jr_diroff)
+ break;
+ ino_dup_ref(sino, refrec, mvrec->jm_oldoff);
+ refrec->jr_diroff = mvrec->jm_newoff;
+ TAILQ_REMOVE(&sino->si_newrecs, srn, sr_next);
+ break;
+ default:
+ err_suj("ino_build_ref: Unknown op %d\n",
+ srn->sr_rec->rec_jrefrec.jr_op);
+ }
+ }
+ ino_add_ref(sino, srec);
+}
+
+/*
+ * Walk the list of new records and add them in-order resolving any
+ * dups and adjusted offsets.
+ */
+static void
+ino_build(struct suj_ino *sino)
+{
+ struct suj_rec *srec;
+
+ while ((srec = TAILQ_FIRST(&sino->si_newrecs)) != NULL) {
+ TAILQ_REMOVE(&sino->si_newrecs, srec, sr_next);
+ switch (srec->sr_rec->rec_jrefrec.jr_op) {
+ case JOP_ADDREF:
+ case JOP_REMREF:
+ ino_build_ref(sino, srec);
+ break;
+ case JOP_MVREF:
+ /*
+ * Add this mvrec to the queue of pending mvs.
+ */
+ TAILQ_INSERT_TAIL(&sino->si_movs, srec, sr_next);
+ break;
+ default:
+ err_suj("ino_build: Unknown op %d\n",
+ srec->sr_rec->rec_jrefrec.jr_op);
+ }
+ }
+ if (TAILQ_EMPTY(&sino->si_recs))
+ sino->si_hasrecs = 0;
+}
+
+/*
+ * Modify journal records so they refer to the base block number
+ * and a start and end frag range. This is to facilitate the discovery
+ * of overlapping fragment allocations.
+ */
+static void
+blk_build(struct jblkrec *blkrec)
+{
+ struct suj_rec *srec;
+ struct suj_blk *sblk;
+ struct jblkrec *blkrn;
+ ufs2_daddr_t blk;
+ int frag;
+
+ if (debug)
+ printf("blk_build: op %d blkno %jd frags %d oldfrags %d "
+ "ino %d lbn %jd\n",
+ blkrec->jb_op, blkrec->jb_blkno, blkrec->jb_frags,
+ blkrec->jb_oldfrags, blkrec->jb_ino, blkrec->jb_lbn);
+
+ blk = blknum(fs, blkrec->jb_blkno);
+ frag = fragnum(fs, blkrec->jb_blkno);
+ sblk = blk_lookup(blk, 1);
+ /*
+ * Rewrite the record using oldfrags to indicate the offset into
+ * the block. Leave jb_frags as the actual allocated count.
+ */
+ blkrec->jb_blkno -= frag;
+ blkrec->jb_oldfrags = frag;
+ if (blkrec->jb_oldfrags + blkrec->jb_frags > fs->fs_frag)
+ err_suj("Invalid fragment count %d oldfrags %d\n",
+ blkrec->jb_frags, frag);
+ /*
+ * Detect dups. If we detect a dup we always discard the oldest
+ * record as it is superseded by the new record. This speeds up
+ * later stages but also eliminates free records which are used
+ * to indicate that the contents of indirects can be trusted.
+ */
+ TAILQ_FOREACH(srec, &sblk->sb_recs, sr_next) {
+ blkrn = (struct jblkrec *)srec->sr_rec;
+ if (blkrn->jb_ino != blkrec->jb_ino ||
+ blkrn->jb_lbn != blkrec->jb_lbn ||
+ blkrn->jb_blkno != blkrec->jb_blkno ||
+ blkrn->jb_frags != blkrec->jb_frags ||
+ blkrn->jb_oldfrags != blkrec->jb_oldfrags)
+ continue;
+ if (debug)
+ printf("Removed dup.\n");
+ /* Discard the free which is a dup with an alloc. */
+ if (blkrec->jb_op == JOP_FREEBLK)
+ return;
+ TAILQ_REMOVE(&sblk->sb_recs, srec, sr_next);
+ free(srec);
+ break;
+ }
+ srec = errmalloc(sizeof(*srec));
+ srec->sr_rec = (union jrec *)blkrec;
+ TAILQ_INSERT_TAIL(&sblk->sb_recs, srec, sr_next);
+}
+
+static void
+ino_build_trunc(struct jtrncrec *rec)
+{
+ struct suj_ino *sino;
+
+ if (debug)
+ printf("ino_build_trunc: op %d ino %d, size %jd\n",
+ rec->jt_op, rec->jt_ino, rec->jt_size);
+ sino = ino_lookup(rec->jt_ino, 1);
+ if (rec->jt_op == JOP_SYNC) {
+ sino->si_trunc = NULL;
+ return;
+ }
+ if (sino->si_trunc == NULL || sino->si_trunc->jt_size > rec->jt_size)
+ sino->si_trunc = rec;
+}
+
+/*
+ * Build up tables of the operations we need to recover.
+ */
+static void
+suj_build(void)
+{
+ struct suj_seg *seg;
+ union jrec *rec;
+ int off;
+ int i;
+
+ TAILQ_FOREACH(seg, &allsegs, ss_next) {
+ if (debug)
+ printf("seg %jd has %d records, oldseq %jd.\n",
+ seg->ss_rec.jsr_seq, seg->ss_rec.jsr_cnt,
+ seg->ss_rec.jsr_oldest);
+ off = 0;
+ rec = (union jrec *)seg->ss_blk;
+ for (i = 0; i < seg->ss_rec.jsr_cnt; off += JREC_SIZE, rec++) {
+ /* skip the segrec. */
+ if ((off % real_dev_bsize) == 0)
+ continue;
+ switch (rec->rec_jrefrec.jr_op) {
+ case JOP_ADDREF:
+ case JOP_REMREF:
+ case JOP_MVREF:
+ ino_append(rec);
+ break;
+ case JOP_NEWBLK:
+ case JOP_FREEBLK:
+ blk_build((struct jblkrec *)rec);
+ break;
+ case JOP_TRUNC:
+ case JOP_SYNC:
+ ino_build_trunc((struct jtrncrec *)rec);
+ break;
+ default:
+ err_suj("Unknown journal operation %d (%d)\n",
+ rec->rec_jrefrec.jr_op, off);
+ }
+ i++;
+ }
+ }
+}
+
+/*
+ * Prune the journal segments to those we care about based on the
+ * oldest sequence in the newest segment. Order the segment list
+ * based on sequence number.
+ */
+static void
+suj_prune(void)
+{
+ struct suj_seg *seg;
+ struct suj_seg *segn;
+ uint64_t newseq;
+ int discard;
+
+ if (debug)
+ printf("Pruning up to %jd\n", oldseq);
+ /* First free the expired segments. */
+ TAILQ_FOREACH_SAFE(seg, &allsegs, ss_next, segn) {
+ if (seg->ss_rec.jsr_seq >= oldseq)
+ continue;
+ TAILQ_REMOVE(&allsegs, seg, ss_next);
+ free(seg->ss_blk);
+ free(seg);
+ }
+ /* Next ensure that segments are ordered properly. */
+ seg = TAILQ_FIRST(&allsegs);
+ if (seg == NULL) {
+ if (debug)
+ printf("Empty journal\n");
+ return;
+ }
+ newseq = seg->ss_rec.jsr_seq;
+ for (;;) {
+ seg = TAILQ_LAST(&allsegs, seghd);
+ if (seg->ss_rec.jsr_seq >= newseq)
+ break;
+ TAILQ_REMOVE(&allsegs, seg, ss_next);
+ TAILQ_INSERT_HEAD(&allsegs, seg, ss_next);
+ newseq = seg->ss_rec.jsr_seq;
+
+ }
+ if (newseq != oldseq) {
+ TAILQ_FOREACH(seg, &allsegs, ss_next) {
+ printf("%jd, ", seg->ss_rec.jsr_seq);
+ }
+ printf("\n");
+ err_suj("Journal file sequence mismatch %jd != %jd\n",
+ newseq, oldseq);
+ }
+ /*
+ * The kernel may asynchronously write segments which can create
+ * gaps in the sequence space. Throw away any segments after the
+ * gap as the kernel guarantees only those that are contiguously
+ * reachable are marked as completed.
+ */
+ discard = 0;
+ TAILQ_FOREACH_SAFE(seg, &allsegs, ss_next, segn) {
+ if (!discard && newseq++ == seg->ss_rec.jsr_seq) {
+ jrecs += seg->ss_rec.jsr_cnt;
+ jbytes += seg->ss_rec.jsr_blocks * real_dev_bsize;
+ continue;
+ }
+ discard = 1;
+ if (debug)
+ printf("Journal order mismatch %jd != %jd pruning\n",
+ newseq-1, seg->ss_rec.jsr_seq);
+ TAILQ_REMOVE(&allsegs, seg, ss_next);
+ free(seg->ss_blk);
+ free(seg);
+ }
+ if (debug)
+ printf("Processing journal segments from %jd to %jd\n",
+ oldseq, newseq-1);
+}
+
+/*
+ * Verify the journal inode before attempting to read records.
+ */
+static int
+suj_verifyino(union dinode *ip)
+{
+
+ if (DIP(ip, di_nlink) != 1) {
+ printf("Invalid link count %d for journal inode %ju\n",
+ DIP(ip, di_nlink), (uintmax_t)sujino);
+ return (-1);
+ }
+
+ if ((DIP(ip, di_flags) & (SF_IMMUTABLE | SF_NOUNLINK)) !=
+ (SF_IMMUTABLE | SF_NOUNLINK)) {
+ printf("Invalid flags 0x%X for journal inode %ju\n",
+ DIP(ip, di_flags), (uintmax_t)sujino);
+ return (-1);
+ }
+
+ if (DIP(ip, di_mode) != (IFREG | IREAD)) {
+ printf("Invalid mode %o for journal inode %ju\n",
+ DIP(ip, di_mode), (uintmax_t)sujino);
+ return (-1);
+ }
+
+ if (DIP(ip, di_size) < SUJ_MIN) {
+ printf("Invalid size %jd for journal inode %ju\n",
+ DIP(ip, di_size), (uintmax_t)sujino);
+ return (-1);
+ }
+
+ if (DIP(ip, di_modrev) != fs->fs_mtime) {
+ printf("Journal timestamp does not match fs mount time\n");
+ return (-1);
+ }
+
+ return (0);
+}
+
+struct jblocks {
+ struct jextent *jb_extent; /* Extent array. */
+ int jb_avail; /* Available extents. */
+ int jb_used; /* Last used extent. */
+ int jb_head; /* Allocator head. */
+ int jb_off; /* Allocator extent offset. */
+};
+struct jextent {
+ ufs2_daddr_t je_daddr; /* Disk block address. */
+ int je_blocks; /* Disk block count. */
+};
+
+static struct jblocks *suj_jblocks;
+
+static struct jblocks *
+jblocks_create(void)
+{
+ struct jblocks *jblocks;
+ int size;
+
+ jblocks = errmalloc(sizeof(*jblocks));
+ jblocks->jb_avail = 10;
+ jblocks->jb_used = 0;
+ jblocks->jb_head = 0;
+ jblocks->jb_off = 0;
+ size = sizeof(struct jextent) * jblocks->jb_avail;
+ jblocks->jb_extent = errmalloc(size);
+ bzero(jblocks->jb_extent, size);
+
+ return (jblocks);
+}
+
+/*
+ * Return the next available disk block and the amount of contiguous
+ * free space it contains.
+ */
+static ufs2_daddr_t
+jblocks_next(struct jblocks *jblocks, int bytes, int *actual)
+{
+ struct jextent *jext;
+ ufs2_daddr_t daddr;
+ int freecnt;
+ int blocks;
+
+ blocks = bytes / disk->d_bsize;
+ jext = &jblocks->jb_extent[jblocks->jb_head];
+ freecnt = jext->je_blocks - jblocks->jb_off;
+ if (freecnt == 0) {
+ jblocks->jb_off = 0;
+ if (++jblocks->jb_head > jblocks->jb_used)
+ return (0);
+ jext = &jblocks->jb_extent[jblocks->jb_head];
+ freecnt = jext->je_blocks;
+ }
+ if (freecnt > blocks)
+ freecnt = blocks;
+ *actual = freecnt * disk->d_bsize;
+ daddr = jext->je_daddr + jblocks->jb_off;
+
+ return (daddr);
+}
+
+/*
+ * Advance the allocation head by a specified number of bytes, consuming
+ * one journal segment.
+ */
+static void
+jblocks_advance(struct jblocks *jblocks, int bytes)
+{
+
+ jblocks->jb_off += bytes / disk->d_bsize;
+}
+
+static void
+jblocks_destroy(struct jblocks *jblocks)
+{
+
+ free(jblocks->jb_extent);
+ free(jblocks);
+}
+
+static void
+jblocks_add(struct jblocks *jblocks, ufs2_daddr_t daddr, int blocks)
+{
+ struct jextent *jext;
+ int size;
+
+ jext = &jblocks->jb_extent[jblocks->jb_used];
+ /* Adding the first block. */
+ if (jext->je_daddr == 0) {
+ jext->je_daddr = daddr;
+ jext->je_blocks = blocks;
+ return;
+ }
+ /* Extending the last extent. */
+ if (jext->je_daddr + jext->je_blocks == daddr) {
+ jext->je_blocks += blocks;
+ return;
+ }
+ /* Adding a new extent. */
+ if (++jblocks->jb_used == jblocks->jb_avail) {
+ jblocks->jb_avail *= 2;
+ size = sizeof(struct jextent) * jblocks->jb_avail;
+ jext = errmalloc(size);
+ bzero(jext, size);
+ bcopy(jblocks->jb_extent, jext,
+ sizeof(struct jextent) * jblocks->jb_used);
+ free(jblocks->jb_extent);
+ jblocks->jb_extent = jext;
+ }
+ jext = &jblocks->jb_extent[jblocks->jb_used];
+ jext->je_daddr = daddr;
+ jext->je_blocks = blocks;
+
+ return;
+}
+
+/*
+ * Add a file block from the journal to the extent map. We can't read
+ * each file block individually because the kernel treats it as a circular
+ * buffer and segments may span mutliple contiguous blocks.
+ */
+static void
+suj_add_block(ino_t ino, ufs_lbn_t lbn, ufs2_daddr_t blk, int frags)
+{
+
+ jblocks_add(suj_jblocks, fsbtodb(fs, blk), fsbtodb(fs, frags));
+}
+
+static void
+suj_read(void)
+{
+ uint8_t block[1 * 1024 * 1024];
+ struct suj_seg *seg;
+ struct jsegrec *recn;
+ struct jsegrec *rec;
+ ufs2_daddr_t blk;
+ int readsize;
+ int blocks;
+ int recsize;
+ int size;
+ int i;
+
+ /*
+ * Read records until we exhaust the journal space. If we find
+ * an invalid record we start searching for a valid segment header
+ * at the next block. This is because we don't have a head/tail
+ * pointer and must recover the information indirectly. At the gap
+ * between the head and tail we won't necessarily have a valid
+ * segment.
+ */
+restart:
+ for (;;) {
+ size = sizeof(block);
+ blk = jblocks_next(suj_jblocks, size, &readsize);
+ if (blk == 0)
+ return;
+ size = readsize;
+ /*
+ * Read 1MB at a time and scan for records within this block.
+ */
+ if (bread(disk, blk, &block, size) == -1) {
+ err_suj("Error reading journal block %jd\n",
+ (intmax_t)blk);
+ }
+ for (rec = (void *)block; size; size -= recsize,
+ rec = (struct jsegrec *)((uintptr_t)rec + recsize)) {
+ recsize = real_dev_bsize;
+ if (rec->jsr_time != fs->fs_mtime) {
+ if (debug)
+ printf("Rec time %jd != fs mtime %jd\n",
+ rec->jsr_time, fs->fs_mtime);
+ jblocks_advance(suj_jblocks, recsize);
+ continue;
+ }
+ if (rec->jsr_cnt == 0) {
+ if (debug)
+ printf("Found illegal count %d\n",
+ rec->jsr_cnt);
+ jblocks_advance(suj_jblocks, recsize);
+ continue;
+ }
+ blocks = rec->jsr_blocks;
+ recsize = blocks * real_dev_bsize;
+ if (recsize > size) {
+ /*
+ * We may just have run out of buffer, restart
+ * the loop to re-read from this spot.
+ */
+ if (size < fs->fs_bsize &&
+ size != readsize &&
+ recsize <= fs->fs_bsize)
+ goto restart;
+ if (debug)
+ printf("Found invalid segsize %d > %d\n",
+ recsize, size);
+ recsize = real_dev_bsize;
+ jblocks_advance(suj_jblocks, recsize);
+ continue;
+ }
+ /*
+ * Verify that all blocks in the segment are present.
+ */
+ for (i = 1; i < blocks; i++) {
+ recn = (void *)((uintptr_t)rec) + i *
+ real_dev_bsize;
+ if (recn->jsr_seq == rec->jsr_seq &&
+ recn->jsr_time == rec->jsr_time)
+ continue;
+ if (debug)
+ printf("Incomplete record %jd (%d)\n",
+ rec->jsr_seq, i);
+ recsize = i * real_dev_bsize;
+ jblocks_advance(suj_jblocks, recsize);
+ goto restart;
+ }
+ seg = errmalloc(sizeof(*seg));
+ seg->ss_blk = errmalloc(recsize);
+ seg->ss_rec = *rec;
+ bcopy((void *)rec, seg->ss_blk, recsize);
+ if (rec->jsr_oldest > oldseq)
+ oldseq = rec->jsr_oldest;
+ TAILQ_INSERT_TAIL(&allsegs, seg, ss_next);
+ jblocks_advance(suj_jblocks, recsize);
+ }
+ }
+}
+
+/*
+ * Search a directory block for the SUJ_FILE.
+ */
+static void
+suj_find(ino_t ino, ufs_lbn_t lbn, ufs2_daddr_t blk, int frags)
+{
+ char block[MAXBSIZE];
+ struct direct *dp;
+ int bytes;
+ int off;
+
+ if (sujino)
+ return;
+ bytes = lfragtosize(fs, frags);
+ if (bread(disk, fsbtodb(fs, blk), block, bytes) <= 0)
+ err_suj("Failed to read ROOTINO directory block %jd\n", blk);
+ for (off = 0; off < bytes; off += dp->d_reclen) {
+ dp = (struct direct *)&block[off];
+ if (dp->d_reclen == 0)
+ break;
+ if (dp->d_ino == 0)
+ continue;
+ if (dp->d_namlen != strlen(SUJ_FILE))
+ continue;
+ if (bcmp(dp->d_name, SUJ_FILE, dp->d_namlen) != 0)
+ continue;
+ sujino = dp->d_ino;
+ return;
+ }
+}
+
+/*
+ * Orchestrate the verification of a filesystem via the softupdates journal.
+ */
+int
+suj_check(const char *filesys)
+{
+ union dinode *jip;
+ union dinode *ip;
+ uint64_t blocks;
+ int retval;
+ struct suj_seg *seg;
+ struct suj_seg *segn;
+
+ initsuj();
+ opendisk(filesys);
+
+ /*
+ * Set an exit point when SUJ check failed
+ */
+ retval = setjmp(jmpbuf);
+ if (retval != 0) {
+ pwarn("UNEXPECTED SU+J INCONSISTENCY\n");
+ TAILQ_FOREACH_SAFE(seg, &allsegs, ss_next, segn) {
+ TAILQ_REMOVE(&allsegs, seg, ss_next);
+ free(seg->ss_blk);
+ free(seg);
+ }
+ if (reply("FALLBACK TO FULL FSCK") == 0) {
+ ckfini(0);
+ exit(EEXIT);
+ } else
+ return (-1);
+ }
+
+ /*
+ * Find the journal inode.
+ */
+ ip = ino_read(ROOTINO);
+ sujino = 0;
+ ino_visit(ip, ROOTINO, suj_find, 0);
+ if (sujino == 0) {
+ printf("Journal inode removed. Use tunefs to re-create.\n");
+ sblock.fs_flags &= ~FS_SUJ;
+ sblock.fs_sujfree = 0;
+ return (-1);
+ }
+ /*
+ * Fetch the journal inode and verify it.
+ */
+ jip = ino_read(sujino);
+ printf("** SU+J Recovering %s\n", filesys);
+ if (suj_verifyino(jip) != 0)
+ return (-1);
+ /*
+ * Build a list of journal blocks in jblocks before parsing the
+ * available journal blocks in with suj_read().
+ */
+ printf("** Reading %jd byte journal from inode %ju.\n",
+ DIP(jip, di_size), (uintmax_t)sujino);
+ suj_jblocks = jblocks_create();
+ blocks = ino_visit(jip, sujino, suj_add_block, 0);
+ if (blocks != numfrags(fs, DIP(jip, di_size))) {
+ printf("Sparse journal inode %ju.\n", (uintmax_t)sujino);
+ return (-1);
+ }
+ suj_read();
+ jblocks_destroy(suj_jblocks);
+ suj_jblocks = NULL;
+ if (preen || reply("RECOVER")) {
+ printf("** Building recovery table.\n");
+ suj_prune();
+ suj_build();
+ cg_apply(cg_build);
+ printf("** Resolving unreferenced inode list.\n");
+ ino_unlinked();
+ printf("** Processing journal entries.\n");
+ cg_apply(cg_trunc);
+ cg_apply(cg_check_blk);
+ cg_apply(cg_adj_blk);
+ cg_apply(cg_check_ino);
+ }
+ if (preen == 0 && (jrecs > 0 || jbytes > 0) && reply("WRITE CHANGES") == 0)
+ return (0);
+ /*
+ * To remain idempotent with partial truncations the free bitmaps
+ * must be written followed by indirect blocks and lastly inode
+ * blocks. This preserves access to the modified pointers until
+ * they are freed.
+ */
+ cg_apply(cg_write);
+ dblk_write();
+ cg_apply(cg_write_inos);
+ /* Write back superblock. */
+ closedisk(filesys);
+ if (jrecs > 0 || jbytes > 0) {
+ printf("** %jd journal records in %jd bytes for %.2f%% utilization\n",
+ jrecs, jbytes, ((float)jrecs / (float)(jbytes / JREC_SIZE)) * 100);
+ printf("** Freed %jd inodes (%jd dirs) %jd blocks, and %jd frags.\n",
+ freeinos, freedir, freeblocks, freefrags);
+ }
+
+ return (0);
+}
+
+static void
+initsuj(void)
+{
+ int i;
+
+ for (i = 0; i < SUJ_HASHSIZE; i++) {
+ LIST_INIT(&cghash[i]);
+ LIST_INIT(&dbhash[i]);
+ }
+ lastcg = NULL;
+ lastblk = NULL;
+ TAILQ_INIT(&allsegs);
+ oldseq = 0;
+ disk = NULL;
+ fs = NULL;
+ sujino = 0;
+ freefrags = 0;
+ freeblocks = 0;
+ freeinos = 0;
+ freedir = 0;
+ jbytes = 0;
+ jrecs = 0;
+ suj_jblocks = NULL;
+}
diff --git a/sbin/fsck_ffs/utilities.c b/sbin/fsck_ffs/utilities.c
new file mode 100644
index 0000000..4e82c31
--- /dev/null
+++ b/sbin/fsck_ffs/utilities.c
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 1980, 1986, 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.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if 0
+#ifndef lint
+static const char sccsid[] = "@(#)utilities.c 8.6 (Berkeley) 5/19/95";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <ufs/ufs/dinode.h>
+#include <ufs/ufs/dir.h>
+#include <ufs/ffs/fs.h>
+
+#include <err.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+#include <fstab.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "fsck.h"
+
+
+char *
+blockcheck(char *origname)
+{
+ struct stat stblock;
+ char *newname, *cp;
+ struct fstab *fsinfo;
+ int retried = 0, len;
+ static char device[MAXPATHLEN];
+
+ newname = origname;
+ if (stat(newname, &stblock) < 0) {
+ cp = strrchr(newname, '/');
+ if (cp == 0) {
+ (void)snprintf(device, sizeof(device), "%s%s",
+ _PATH_DEV, newname);
+ newname = device;
+ }
+ }
+retry:
+ if (stat(newname, &stblock) < 0) {
+ printf("Can't stat %s: %s\n", newname, strerror(errno));
+ return (origname);
+ }
+ switch(stblock.st_mode & S_IFMT) {
+ case S_IFCHR:
+ case S_IFBLK:
+ return(newname);
+ case S_IFDIR:
+ if (retried)
+ break;
+
+ len = strlen(origname) - 1;
+ if (len > 0 && origname[len] == '/')
+ /* remove trailing slash */
+ origname[len] = '\0';
+ if ((fsinfo = getfsfile(origname)) == NULL) {
+ printf(
+ "Can't resolve %s to character special device.\n",
+ origname);
+ return (origname);
+ }
+ newname = fsinfo->fs_spec;
+ retried++;
+ goto retry;
+ }
+ /*
+ * Not a block or character device, just return name and
+ * let the user decide whether to use it.
+ */
+ return (origname);
+}
+
diff --git a/sbin/fsck_msdosfs/Makefile b/sbin/fsck_msdosfs/Makefile
new file mode 100644
index 0000000..f9dd9fa
--- /dev/null
+++ b/sbin/fsck_msdosfs/Makefile
@@ -0,0 +1,13 @@
+# $NetBSD: Makefile,v 1.6 1997/05/08 21:11:11 gwr Exp $
+# $FreeBSD$
+
+FSCK= ${.CURDIR}/../fsck
+.PATH: ${FSCK}
+
+PROG= fsck_msdosfs
+MAN= fsck_msdosfs.8
+SRCS= main.c check.c boot.c fat.c dir.c fsutil.c
+
+CFLAGS+= -I${FSCK}
+
+.include <bsd.prog.mk>
diff --git a/sbin/fsck_msdosfs/boot.c b/sbin/fsck_msdosfs/boot.c
new file mode 100644
index 0000000..b1760c5
--- /dev/null
+++ b/sbin/fsck_msdosfs/boot.c
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 1995, 1997 Wolfgang Solfrank
+ * Copyright (c) 1995 Martin Husemann
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``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 AUTHORS 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 <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: boot.c,v 1.11 2006/06/05 16:51:18 christos Exp ");
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include "ext.h"
+#include "fsutil.h"
+
+int
+readboot(int dosfs, struct bootblock *boot)
+{
+ u_char block[DOSBOOTBLOCKSIZE];
+ u_char fsinfo[2 * DOSBOOTBLOCKSIZE];
+ u_char backup[DOSBOOTBLOCKSIZE];
+ int ret = FSOK;
+ int i;
+
+ if ((size_t)read(dosfs, block, sizeof block) != sizeof block) {
+ perr("could not read boot block");
+ return FSFATAL;
+ }
+
+ if (block[510] != 0x55 || block[511] != 0xaa) {
+ pfatal("Invalid signature in boot block: %02x%02x",
+ block[511], block[510]);
+ return FSFATAL;
+ }
+
+ memset(boot, 0, sizeof *boot);
+ boot->ValidFat = -1;
+
+ /* decode bios parameter block */
+ boot->bpbBytesPerSec = block[11] + (block[12] << 8);
+ boot->bpbSecPerClust = block[13];
+ boot->bpbResSectors = block[14] + (block[15] << 8);
+ boot->bpbFATs = block[16];
+ boot->bpbRootDirEnts = block[17] + (block[18] << 8);
+ boot->bpbSectors = block[19] + (block[20] << 8);
+ boot->bpbMedia = block[21];
+ boot->bpbFATsmall = block[22] + (block[23] << 8);
+ boot->SecPerTrack = block[24] + (block[25] << 8);
+ boot->bpbHeads = block[26] + (block[27] << 8);
+ boot->bpbHiddenSecs = block[28] + (block[29] << 8) +
+ (block[30] << 16) + (block[31] << 24);
+ boot->bpbHugeSectors = block[32] + (block[33] << 8) +
+ (block[34] << 16) + (block[35] << 24);
+
+ boot->FATsecs = boot->bpbFATsmall;
+
+ if (!boot->bpbRootDirEnts)
+ boot->flags |= FAT32;
+ if (boot->flags & FAT32) {
+ boot->FATsecs = block[36] + (block[37] << 8)
+ + (block[38] << 16) + (block[39] << 24);
+ if (block[40] & 0x80)
+ boot->ValidFat = block[40] & 0x0f;
+
+ /* check version number: */
+ if (block[42] || block[43]) {
+ /* Correct? XXX */
+ pfatal("Unknown file system version: %x.%x",
+ block[43], block[42]);
+ return FSFATAL;
+ }
+ boot->bpbRootClust = block[44] + (block[45] << 8)
+ + (block[46] << 16) + (block[47] << 24);
+ boot->bpbFSInfo = block[48] + (block[49] << 8);
+ boot->bpbBackup = block[50] + (block[51] << 8);
+
+ if (lseek(dosfs, boot->bpbFSInfo * boot->bpbBytesPerSec,
+ SEEK_SET) != boot->bpbFSInfo * boot->bpbBytesPerSec
+ || read(dosfs, fsinfo, sizeof fsinfo) != sizeof fsinfo) {
+ perr("could not read fsinfo block");
+ return FSFATAL;
+ }
+ if (memcmp(fsinfo, "RRaA", 4)
+ || memcmp(fsinfo + 0x1e4, "rrAa", 4)
+ || fsinfo[0x1fc]
+ || fsinfo[0x1fd]
+ || fsinfo[0x1fe] != 0x55
+ || fsinfo[0x1ff] != 0xaa
+ || fsinfo[0x3fc]
+ || fsinfo[0x3fd]
+ || fsinfo[0x3fe] != 0x55
+ || fsinfo[0x3ff] != 0xaa) {
+ pwarn("Invalid signature in fsinfo block\n");
+ if (ask(0, "Fix")) {
+ memcpy(fsinfo, "RRaA", 4);
+ memcpy(fsinfo + 0x1e4, "rrAa", 4);
+ fsinfo[0x1fc] = fsinfo[0x1fd] = 0;
+ fsinfo[0x1fe] = 0x55;
+ fsinfo[0x1ff] = 0xaa;
+ fsinfo[0x3fc] = fsinfo[0x3fd] = 0;
+ fsinfo[0x3fe] = 0x55;
+ fsinfo[0x3ff] = 0xaa;
+ if (lseek(dosfs, boot->bpbFSInfo *
+ boot->bpbBytesPerSec, SEEK_SET)
+ != boot->bpbFSInfo * boot->bpbBytesPerSec
+ || write(dosfs, fsinfo, sizeof fsinfo)
+ != sizeof fsinfo) {
+ perr("Unable to write bpbFSInfo");
+ return FSFATAL;
+ }
+ ret = FSBOOTMOD;
+ } else
+ boot->bpbFSInfo = 0;
+ }
+ if (boot->bpbFSInfo) {
+ boot->FSFree = fsinfo[0x1e8] + (fsinfo[0x1e9] << 8)
+ + (fsinfo[0x1ea] << 16)
+ + (fsinfo[0x1eb] << 24);
+ boot->FSNext = fsinfo[0x1ec] + (fsinfo[0x1ed] << 8)
+ + (fsinfo[0x1ee] << 16)
+ + (fsinfo[0x1ef] << 24);
+ }
+
+ if (lseek(dosfs, boot->bpbBackup * boot->bpbBytesPerSec,
+ SEEK_SET)
+ != boot->bpbBackup * boot->bpbBytesPerSec
+ || read(dosfs, backup, sizeof backup) != sizeof backup) {
+ perr("could not read backup bootblock");
+ return FSFATAL;
+ }
+ backup[65] = block[65]; /* XXX */
+ if (memcmp(block + 11, backup + 11, 79)) {
+ /*
+ * XXX We require a reference that explains
+ * that these bytes need to match, or should
+ * drop the check. gdt@NetBSD has observed
+ * filesystems that work fine under Windows XP
+ * and NetBSD that do not match, so the
+ * requirement is suspect. For now, just
+ * print out useful information and continue.
+ */
+ pfatal("backup (block %d) mismatch with primary bootblock:\n",
+ boot->bpbBackup);
+ for (i = 11; i < 11 + 90; i++) {
+ if (block[i] != backup[i])
+ pfatal("\ti=%d\tprimary 0x%02x\tbackup 0x%02x\n",
+ i, block[i], backup[i]);
+ }
+ }
+ /* Check backup bpbFSInfo? XXX */
+ }
+
+ boot->ClusterOffset = (boot->bpbRootDirEnts * 32 +
+ boot->bpbBytesPerSec - 1) / boot->bpbBytesPerSec +
+ boot->bpbResSectors + boot->bpbFATs * boot->FATsecs -
+ CLUST_FIRST * boot->bpbSecPerClust;
+
+ if (boot->bpbBytesPerSec % DOSBOOTBLOCKSIZE_REAL != 0) {
+ pfatal("Invalid sector size: %u", boot->bpbBytesPerSec);
+ return FSFATAL;
+ }
+ if (boot->bpbSecPerClust == 0) {
+ pfatal("Invalid cluster size: %u", boot->bpbSecPerClust);
+ return FSFATAL;
+ }
+ if (boot->bpbSectors) {
+ boot->bpbHugeSectors = 0;
+ boot->NumSectors = boot->bpbSectors;
+ } else
+ boot->NumSectors = boot->bpbHugeSectors;
+ boot->NumClusters = (boot->NumSectors - boot->ClusterOffset) /
+ boot->bpbSecPerClust;
+
+ if (boot->flags&FAT32)
+ boot->ClustMask = CLUST32_MASK;
+ else if (boot->NumClusters < (CLUST_RSRVD&CLUST12_MASK))
+ boot->ClustMask = CLUST12_MASK;
+ else if (boot->NumClusters < (CLUST_RSRVD&CLUST16_MASK))
+ boot->ClustMask = CLUST16_MASK;
+ else {
+ pfatal("Filesystem too big (%u clusters) for non-FAT32 partition",
+ boot->NumClusters);
+ return FSFATAL;
+ }
+
+ switch (boot->ClustMask) {
+ case CLUST32_MASK:
+ boot->NumFatEntries = (boot->FATsecs * boot->bpbBytesPerSec) / 4;
+ break;
+ case CLUST16_MASK:
+ boot->NumFatEntries = (boot->FATsecs * boot->bpbBytesPerSec) / 2;
+ break;
+ default:
+ boot->NumFatEntries = (boot->FATsecs * boot->bpbBytesPerSec * 2) / 3;
+ break;
+ }
+
+ if (boot->NumFatEntries < boot->NumClusters) {
+ pfatal("FAT size too small, %u entries won't fit into %u sectors\n",
+ boot->NumClusters, boot->FATsecs);
+ return FSFATAL;
+ }
+ boot->ClusterSize = boot->bpbBytesPerSec * boot->bpbSecPerClust;
+
+ boot->NumFiles = 1;
+ boot->NumFree = 0;
+
+ return ret;
+}
+
+int
+writefsinfo(int dosfs, struct bootblock *boot)
+{
+ u_char fsinfo[2 * DOSBOOTBLOCKSIZE];
+
+ if (lseek(dosfs, boot->bpbFSInfo * boot->bpbBytesPerSec, SEEK_SET)
+ != boot->bpbFSInfo * boot->bpbBytesPerSec
+ || read(dosfs, fsinfo, sizeof fsinfo) != sizeof fsinfo) {
+ perr("could not read fsinfo block");
+ return FSFATAL;
+ }
+ fsinfo[0x1e8] = (u_char)boot->FSFree;
+ fsinfo[0x1e9] = (u_char)(boot->FSFree >> 8);
+ fsinfo[0x1ea] = (u_char)(boot->FSFree >> 16);
+ fsinfo[0x1eb] = (u_char)(boot->FSFree >> 24);
+ fsinfo[0x1ec] = (u_char)boot->FSNext;
+ fsinfo[0x1ed] = (u_char)(boot->FSNext >> 8);
+ fsinfo[0x1ee] = (u_char)(boot->FSNext >> 16);
+ fsinfo[0x1ef] = (u_char)(boot->FSNext >> 24);
+ if (lseek(dosfs, boot->bpbFSInfo * boot->bpbBytesPerSec, SEEK_SET)
+ != boot->bpbFSInfo * boot->bpbBytesPerSec
+ || write(dosfs, fsinfo, sizeof fsinfo)
+ != sizeof fsinfo) {
+ perr("Unable to write bpbFSInfo");
+ return FSFATAL;
+ }
+ /*
+ * Technically, we should return FSBOOTMOD here.
+ *
+ * However, since Win95 OSR2 (the first M$ OS that has
+ * support for FAT32) doesn't maintain the FSINFO block
+ * correctly, it has to be fixed pretty often.
+ *
+ * Therefor, we handle the FSINFO block only informally,
+ * fixing it if necessary, but otherwise ignoring the
+ * fact that it was incorrect.
+ */
+ return 0;
+}
diff --git a/sbin/fsck_msdosfs/check.c b/sbin/fsck_msdosfs/check.c
new file mode 100644
index 0000000..083389e
--- /dev/null
+++ b/sbin/fsck_msdosfs/check.c
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 1995, 1996, 1997 Wolfgang Solfrank
+ * Copyright (c) 1995 Martin Husemann
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``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 AUTHORS 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 <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: check.c,v 1.14 2006/06/05 16:51:18 christos Exp $");
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "ext.h"
+#include "fsutil.h"
+
+int
+checkfilesys(const char *fname)
+{
+ int dosfs;
+ struct bootblock boot;
+ struct fatEntry *fat = NULL;
+ int finish_dosdirsection=0;
+ u_int i;
+ int mod = 0;
+ int ret = 8;
+
+ rdonly = alwaysno;
+ if (!preen)
+ printf("** %s", fname);
+
+ dosfs = open(fname, rdonly ? O_RDONLY : O_RDWR, 0);
+ if (dosfs < 0 && !rdonly) {
+ dosfs = open(fname, O_RDONLY, 0);
+ if (dosfs >= 0)
+ pwarn(" (NO WRITE)\n");
+ else if (!preen)
+ printf("\n");
+ rdonly = 1;
+ } else if (!preen)
+ printf("\n");
+
+ if (dosfs < 0) {
+ perr("Can't open `%s'", fname);
+ printf("\n");
+ return 8;
+ }
+
+ if (readboot(dosfs, &boot) != FSOK) {
+ close(dosfs);
+ printf("\n");
+ return 8;
+ }
+
+ if (skipclean && preen && checkdirty(dosfs, &boot)) {
+ printf("%s: ", fname);
+ printf("FILESYSTEM CLEAN; SKIPPING CHECKS\n");
+ ret = 0;
+ goto out;
+ }
+
+ if (!preen) {
+ if (boot.ValidFat < 0)
+ printf("** Phase 1 - Read and Compare FATs\n");
+ else
+ printf("** Phase 1 - Read FAT\n");
+ }
+
+ mod |= readfat(dosfs, &boot, boot.ValidFat >= 0 ? boot.ValidFat : 0, &fat);
+ if (mod & FSFATAL) {
+ close(dosfs);
+ return 8;
+ }
+
+ if (boot.ValidFat < 0)
+ for (i = 1; i < boot.bpbFATs; i++) {
+ struct fatEntry *currentFat;
+
+ mod |= readfat(dosfs, &boot, i, &currentFat);
+
+ if (mod & FSFATAL)
+ goto out;
+
+ mod |= comparefat(&boot, fat, currentFat, i);
+ free(currentFat);
+ if (mod & FSFATAL)
+ goto out;
+ }
+
+ if (!preen)
+ printf("** Phase 2 - Check Cluster Chains\n");
+
+ mod |= checkfat(&boot, fat);
+ if (mod & FSFATAL)
+ goto out;
+ /* delay writing FATs */
+
+ if (!preen)
+ printf("** Phase 3 - Checking Directories\n");
+
+ mod |= resetDosDirSection(&boot, fat);
+ finish_dosdirsection = 1;
+ if (mod & FSFATAL)
+ goto out;
+ /* delay writing FATs */
+
+ mod |= handleDirTree(dosfs, &boot, fat);
+ if (mod & FSFATAL)
+ goto out;
+
+ if (!preen)
+ printf("** Phase 4 - Checking for Lost Files\n");
+
+ mod |= checklost(dosfs, &boot, fat);
+ if (mod & FSFATAL)
+ goto out;
+
+ /* now write the FATs */
+ if (mod & (FSFATMOD|FSFIXFAT)) {
+ if (ask(1, "Update FATs")) {
+ mod |= writefat(dosfs, &boot, fat, mod & FSFIXFAT);
+ if (mod & FSFATAL)
+ goto out;
+ } else
+ mod |= FSERROR;
+ }
+
+ if (boot.NumBad)
+ pwarn("%d files, %d free (%d clusters), %d bad (%d clusters)\n",
+ boot.NumFiles,
+ boot.NumFree * boot.ClusterSize / 1024, boot.NumFree,
+ boot.NumBad * boot.ClusterSize / 1024, boot.NumBad);
+ else
+ pwarn("%d files, %d free (%d clusters)\n",
+ boot.NumFiles,
+ boot.NumFree * boot.ClusterSize / 1024, boot.NumFree);
+
+ if (mod && (mod & FSERROR) == 0) {
+ if (mod & FSDIRTY) {
+ if (ask(1, "MARK FILE SYSTEM CLEAN") == 0)
+ mod &= ~FSDIRTY;
+
+ if (mod & FSDIRTY) {
+ pwarn("MARKING FILE SYSTEM CLEAN\n");
+ mod |= writefat(dosfs, &boot, fat, 1);
+ } else {
+ pwarn("\n***** FILE SYSTEM IS LEFT MARKED AS DIRTY *****\n");
+ mod |= FSERROR; /* file system not clean */
+ }
+ }
+ }
+
+ if (mod & (FSFATAL | FSERROR))
+ goto out;
+
+ ret = 0;
+
+ out:
+ if (finish_dosdirsection)
+ finishDosDirSection();
+ free(fat);
+ close(dosfs);
+
+ if (mod & (FSFATMOD|FSDIRMOD))
+ pwarn("\n***** FILE SYSTEM WAS MODIFIED *****\n");
+
+ return ret;
+}
diff --git a/sbin/fsck_msdosfs/dir.c b/sbin/fsck_msdosfs/dir.c
new file mode 100644
index 0000000..e8d6475
--- /dev/null
+++ b/sbin/fsck_msdosfs/dir.c
@@ -0,0 +1,1012 @@
+/*
+ * Copyright (C) 1995, 1996, 1997 Wolfgang Solfrank
+ * Copyright (c) 1995 Martin Husemann
+ * Some structure declaration borrowed from Paul Popelka
+ * (paulp@uts.amdahl.com), see /sys/msdosfs/ for reference.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``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 AUTHORS 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 <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: dir.c,v 1.20 2006/06/05 16:51:18 christos Exp $");
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <time.h>
+
+#include <sys/param.h>
+
+#include "ext.h"
+#include "fsutil.h"
+
+#define SLOT_EMPTY 0x00 /* slot has never been used */
+#define SLOT_E5 0x05 /* the real value is 0xe5 */
+#define SLOT_DELETED 0xe5 /* file in this slot deleted */
+
+#define ATTR_NORMAL 0x00 /* normal file */
+#define ATTR_READONLY 0x01 /* file is readonly */
+#define ATTR_HIDDEN 0x02 /* file is hidden */
+#define ATTR_SYSTEM 0x04 /* file is a system file */
+#define ATTR_VOLUME 0x08 /* entry is a volume label */
+#define ATTR_DIRECTORY 0x10 /* entry is a directory name */
+#define ATTR_ARCHIVE 0x20 /* file is new or modified */
+
+#define ATTR_WIN95 0x0f /* long name record */
+
+/*
+ * This is the format of the contents of the deTime field in the direntry
+ * structure.
+ * We don't use bitfields because we don't know how compilers for
+ * arbitrary machines will lay them out.
+ */
+#define DT_2SECONDS_MASK 0x1F /* seconds divided by 2 */
+#define DT_2SECONDS_SHIFT 0
+#define DT_MINUTES_MASK 0x7E0 /* minutes */
+#define DT_MINUTES_SHIFT 5
+#define DT_HOURS_MASK 0xF800 /* hours */
+#define DT_HOURS_SHIFT 11
+
+/*
+ * This is the format of the contents of the deDate field in the direntry
+ * structure.
+ */
+#define DD_DAY_MASK 0x1F /* day of month */
+#define DD_DAY_SHIFT 0
+#define DD_MONTH_MASK 0x1E0 /* month */
+#define DD_MONTH_SHIFT 5
+#define DD_YEAR_MASK 0xFE00 /* year - 1980 */
+#define DD_YEAR_SHIFT 9
+
+
+/* dir.c */
+static struct dosDirEntry *newDosDirEntry(void);
+static void freeDosDirEntry(struct dosDirEntry *);
+static struct dirTodoNode *newDirTodo(void);
+static void freeDirTodo(struct dirTodoNode *);
+static char *fullpath(struct dosDirEntry *);
+static u_char calcShortSum(u_char *);
+static int delete(int, struct bootblock *, struct fatEntry *, cl_t, int,
+ cl_t, int, int);
+static int removede(int, struct bootblock *, struct fatEntry *, u_char *,
+ u_char *, cl_t, cl_t, cl_t, char *, int);
+static int checksize(struct bootblock *, struct fatEntry *, u_char *,
+ struct dosDirEntry *);
+static int readDosDirSection(int, struct bootblock *, struct fatEntry *,
+ struct dosDirEntry *);
+
+/*
+ * Manage free dosDirEntry structures.
+ */
+static struct dosDirEntry *freede;
+
+static struct dosDirEntry *
+newDosDirEntry(void)
+{
+ struct dosDirEntry *de;
+
+ if (!(de = freede)) {
+ if (!(de = (struct dosDirEntry *)malloc(sizeof *de)))
+ return 0;
+ } else
+ freede = de->next;
+ return de;
+}
+
+static void
+freeDosDirEntry(struct dosDirEntry *de)
+{
+ de->next = freede;
+ freede = de;
+}
+
+/*
+ * The same for dirTodoNode structures.
+ */
+static struct dirTodoNode *freedt;
+
+static struct dirTodoNode *
+newDirTodo(void)
+{
+ struct dirTodoNode *dt;
+
+ if (!(dt = freedt)) {
+ if (!(dt = (struct dirTodoNode *)malloc(sizeof *dt)))
+ return 0;
+ } else
+ freedt = dt->next;
+ return dt;
+}
+
+static void
+freeDirTodo(struct dirTodoNode *dt)
+{
+ dt->next = freedt;
+ freedt = dt;
+}
+
+/*
+ * The stack of unread directories
+ */
+static struct dirTodoNode *pendingDirectories = NULL;
+
+/*
+ * Return the full pathname for a directory entry.
+ */
+static char *
+fullpath(struct dosDirEntry *dir)
+{
+ static char namebuf[MAXPATHLEN + 1];
+ char *cp, *np;
+ int nl;
+
+ cp = namebuf + sizeof namebuf - 1;
+ *cp = '\0';
+ do {
+ np = dir->lname[0] ? dir->lname : dir->name;
+ nl = strlen(np);
+ if ((cp -= nl) <= namebuf + 1)
+ break;
+ memcpy(cp, np, nl);
+ *--cp = '/';
+ } while ((dir = dir->parent) != NULL);
+ if (dir)
+ *--cp = '?';
+ else
+ cp++;
+ return cp;
+}
+
+/*
+ * Calculate a checksum over an 8.3 alias name
+ */
+static u_char
+calcShortSum(u_char *p)
+{
+ u_char sum = 0;
+ int i;
+
+ for (i = 0; i < 11; i++) {
+ sum = (sum << 7)|(sum >> 1); /* rotate right */
+ sum += p[i];
+ }
+
+ return sum;
+}
+
+/*
+ * Global variables temporarily used during a directory scan
+ */
+static char longName[DOSLONGNAMELEN] = "";
+static u_char *buffer = NULL;
+static u_char *delbuf = NULL;
+
+static struct dosDirEntry *rootDir;
+static struct dosDirEntry *lostDir;
+
+/*
+ * Init internal state for a new directory scan.
+ */
+int
+resetDosDirSection(struct bootblock *boot, struct fatEntry *fat)
+{
+ int b1, b2;
+ cl_t cl;
+ int ret = FSOK;
+ size_t len;
+
+ b1 = boot->bpbRootDirEnts * 32;
+ b2 = boot->bpbSecPerClust * boot->bpbBytesPerSec;
+
+ if ((buffer = malloc(len = b1 > b2 ? b1 : b2)) == NULL) {
+ perr("No space for directory buffer (%zu)", len);
+ return FSFATAL;
+ }
+
+ if ((delbuf = malloc(len = b2)) == NULL) {
+ free(buffer);
+ perr("No space for directory delbuf (%zu)", len);
+ return FSFATAL;
+ }
+
+ if ((rootDir = newDosDirEntry()) == NULL) {
+ free(buffer);
+ free(delbuf);
+ perr("No space for directory entry");
+ return FSFATAL;
+ }
+
+ memset(rootDir, 0, sizeof *rootDir);
+ if (boot->flags & FAT32) {
+ if (boot->bpbRootClust < CLUST_FIRST ||
+ boot->bpbRootClust >= boot->NumClusters) {
+ pfatal("Root directory starts with cluster out of range(%u)",
+ boot->bpbRootClust);
+ return FSFATAL;
+ }
+ cl = fat[boot->bpbRootClust].next;
+ if (cl < CLUST_FIRST
+ || (cl >= CLUST_RSRVD && cl< CLUST_EOFS)
+ || fat[boot->bpbRootClust].head != boot->bpbRootClust) {
+ if (cl == CLUST_FREE)
+ pwarn("Root directory starts with free cluster\n");
+ else if (cl >= CLUST_RSRVD)
+ pwarn("Root directory starts with cluster marked %s\n",
+ rsrvdcltype(cl));
+ else {
+ pfatal("Root directory doesn't start a cluster chain");
+ return FSFATAL;
+ }
+ if (ask(1, "Fix")) {
+ fat[boot->bpbRootClust].next = CLUST_FREE;
+ ret = FSFATMOD;
+ } else
+ ret = FSFATAL;
+ }
+
+ fat[boot->bpbRootClust].flags |= FAT_USED;
+ rootDir->head = boot->bpbRootClust;
+ }
+
+ return ret;
+}
+
+/*
+ * Cleanup after a directory scan
+ */
+void
+finishDosDirSection(void)
+{
+ struct dirTodoNode *p, *np;
+ struct dosDirEntry *d, *nd;
+
+ for (p = pendingDirectories; p; p = np) {
+ np = p->next;
+ freeDirTodo(p);
+ }
+ pendingDirectories = 0;
+ for (d = rootDir; d; d = nd) {
+ if ((nd = d->child) != NULL) {
+ d->child = 0;
+ continue;
+ }
+ if (!(nd = d->next))
+ nd = d->parent;
+ freeDosDirEntry(d);
+ }
+ rootDir = lostDir = NULL;
+ free(buffer);
+ free(delbuf);
+ buffer = NULL;
+ delbuf = NULL;
+}
+
+/*
+ * Delete directory entries between startcl, startoff and endcl, endoff.
+ */
+static int
+delete(int f, struct bootblock *boot, struct fatEntry *fat, cl_t startcl,
+ int startoff, cl_t endcl, int endoff, int notlast)
+{
+ u_char *s, *e;
+ off_t off;
+ int clsz = boot->bpbSecPerClust * boot->bpbBytesPerSec;
+
+ s = delbuf + startoff;
+ e = delbuf + clsz;
+ while (startcl >= CLUST_FIRST && startcl < boot->NumClusters) {
+ if (startcl == endcl) {
+ if (notlast)
+ break;
+ e = delbuf + endoff;
+ }
+ off = startcl * boot->bpbSecPerClust + boot->ClusterOffset;
+ off *= boot->bpbBytesPerSec;
+ if (lseek(f, off, SEEK_SET) != off
+ || read(f, delbuf, clsz) != clsz) {
+ perr("Unable to read directory");
+ return FSFATAL;
+ }
+ while (s < e) {
+ *s = SLOT_DELETED;
+ s += 32;
+ }
+ if (lseek(f, off, SEEK_SET) != off
+ || write(f, delbuf, clsz) != clsz) {
+ perr("Unable to write directory");
+ return FSFATAL;
+ }
+ if (startcl == endcl)
+ break;
+ startcl = fat[startcl].next;
+ s = delbuf;
+ }
+ return FSOK;
+}
+
+static int
+removede(int f, struct bootblock *boot, struct fatEntry *fat, u_char *start,
+ u_char *end, cl_t startcl, cl_t endcl, cl_t curcl, char *path, int type)
+{
+ switch (type) {
+ case 0:
+ pwarn("Invalid long filename entry for %s\n", path);
+ break;
+ case 1:
+ pwarn("Invalid long filename entry at end of directory %s\n",
+ path);
+ break;
+ case 2:
+ pwarn("Invalid long filename entry for volume label\n");
+ break;
+ }
+ if (ask(0, "Remove")) {
+ if (startcl != curcl) {
+ if (delete(f, boot, fat,
+ startcl, start - buffer,
+ endcl, end - buffer,
+ endcl == curcl) == FSFATAL)
+ return FSFATAL;
+ start = buffer;
+ }
+ /* startcl is < CLUST_FIRST for !fat32 root */
+ if ((endcl == curcl) || (startcl < CLUST_FIRST))
+ for (; start < end; start += 32)
+ *start = SLOT_DELETED;
+ return FSDIRMOD;
+ }
+ return FSERROR;
+}
+
+/*
+ * Check an in-memory file entry
+ */
+static int
+checksize(struct bootblock *boot, struct fatEntry *fat, u_char *p,
+ struct dosDirEntry *dir)
+{
+ /*
+ * Check size on ordinary files
+ */
+ u_int32_t physicalSize;
+
+ if (dir->head == CLUST_FREE)
+ physicalSize = 0;
+ else {
+ if (dir->head < CLUST_FIRST || dir->head >= boot->NumClusters)
+ return FSERROR;
+ physicalSize = fat[dir->head].length * boot->ClusterSize;
+ }
+ if (physicalSize < dir->size) {
+ pwarn("size of %s is %u, should at most be %u\n",
+ fullpath(dir), dir->size, physicalSize);
+ if (ask(1, "Truncate")) {
+ dir->size = physicalSize;
+ p[28] = (u_char)physicalSize;
+ p[29] = (u_char)(physicalSize >> 8);
+ p[30] = (u_char)(physicalSize >> 16);
+ p[31] = (u_char)(physicalSize >> 24);
+ return FSDIRMOD;
+ } else
+ return FSERROR;
+ } else if (physicalSize - dir->size >= boot->ClusterSize) {
+ pwarn("%s has too many clusters allocated\n",
+ fullpath(dir));
+ if (ask(1, "Drop superfluous clusters")) {
+ cl_t cl;
+ u_int32_t sz, len;
+
+ for (cl = dir->head, len = sz = 0;
+ (sz += boot->ClusterSize) < dir->size; len++)
+ cl = fat[cl].next;
+ clearchain(boot, fat, fat[cl].next);
+ fat[cl].next = CLUST_EOF;
+ fat[dir->head].length = len;
+ return FSFATMOD;
+ } else
+ return FSERROR;
+ }
+ return FSOK;
+}
+
+/*
+ * Read a directory and
+ * - resolve long name records
+ * - enter file and directory records into the parent's list
+ * - push directories onto the todo-stack
+ */
+static int
+readDosDirSection(int f, struct bootblock *boot, struct fatEntry *fat,
+ struct dosDirEntry *dir)
+{
+ struct dosDirEntry dirent, *d;
+ u_char *p, *vallfn, *invlfn, *empty;
+ off_t off;
+ int i, j, k, last;
+ cl_t cl, valcl = ~0, invcl = ~0, empcl = ~0;
+ char *t;
+ u_int lidx = 0;
+ int shortSum;
+ int mod = FSOK;
+#define THISMOD 0x8000 /* Only used within this routine */
+
+ cl = dir->head;
+ if (dir->parent && (cl < CLUST_FIRST || cl >= boot->NumClusters)) {
+ /*
+ * Already handled somewhere else.
+ */
+ return FSOK;
+ }
+ shortSum = -1;
+ vallfn = invlfn = empty = NULL;
+ do {
+ if (!(boot->flags & FAT32) && !dir->parent) {
+ last = boot->bpbRootDirEnts * 32;
+ off = boot->bpbResSectors + boot->bpbFATs *
+ boot->FATsecs;
+ } else {
+ last = boot->bpbSecPerClust * boot->bpbBytesPerSec;
+ off = cl * boot->bpbSecPerClust + boot->ClusterOffset;
+ }
+
+ off *= boot->bpbBytesPerSec;
+ if (lseek(f, off, SEEK_SET) != off
+ || read(f, buffer, last) != last) {
+ perr("Unable to read directory");
+ return FSFATAL;
+ }
+ last /= 32;
+ /*
+ * Check `.' and `..' entries here? XXX
+ */
+ for (p = buffer, i = 0; i < last; i++, p += 32) {
+ if (dir->fsckflags & DIREMPWARN) {
+ *p = SLOT_EMPTY;
+ continue;
+ }
+
+ if (*p == SLOT_EMPTY || *p == SLOT_DELETED) {
+ if (*p == SLOT_EMPTY) {
+ dir->fsckflags |= DIREMPTY;
+ empty = p;
+ empcl = cl;
+ }
+ continue;
+ }
+
+ if (dir->fsckflags & DIREMPTY) {
+ if (!(dir->fsckflags & DIREMPWARN)) {
+ pwarn("%s has entries after end of directory\n",
+ fullpath(dir));
+ if (ask(1, "Extend")) {
+ u_char *q;
+
+ dir->fsckflags &= ~DIREMPTY;
+ if (delete(f, boot, fat,
+ empcl, empty - buffer,
+ cl, p - buffer, 1) == FSFATAL)
+ return FSFATAL;
+ q = empcl == cl ? empty : buffer;
+ for (; q < p; q += 32)
+ *q = SLOT_DELETED;
+ mod |= THISMOD|FSDIRMOD;
+ } else if (ask(0, "Truncate"))
+ dir->fsckflags |= DIREMPWARN;
+ }
+ if (dir->fsckflags & DIREMPWARN) {
+ *p = SLOT_DELETED;
+ mod |= THISMOD|FSDIRMOD;
+ continue;
+ } else if (dir->fsckflags & DIREMPTY)
+ mod |= FSERROR;
+ empty = NULL;
+ }
+
+ if (p[11] == ATTR_WIN95) {
+ if (*p & LRFIRST) {
+ if (shortSum != -1) {
+ if (!invlfn) {
+ invlfn = vallfn;
+ invcl = valcl;
+ }
+ }
+ memset(longName, 0, sizeof longName);
+ shortSum = p[13];
+ vallfn = p;
+ valcl = cl;
+ } else if (shortSum != p[13]
+ || lidx != (*p & LRNOMASK)) {
+ if (!invlfn) {
+ invlfn = vallfn;
+ invcl = valcl;
+ }
+ if (!invlfn) {
+ invlfn = p;
+ invcl = cl;
+ }
+ vallfn = NULL;
+ }
+ lidx = *p & LRNOMASK;
+ t = longName + --lidx * 13;
+ for (k = 1; k < 11 && t < longName +
+ sizeof(longName); k += 2) {
+ if (!p[k] && !p[k + 1])
+ break;
+ *t++ = p[k];
+ /*
+ * Warn about those unusable chars in msdosfs here? XXX
+ */
+ if (p[k + 1])
+ t[-1] = '?';
+ }
+ if (k >= 11)
+ for (k = 14; k < 26 && t < longName + sizeof(longName); k += 2) {
+ if (!p[k] && !p[k + 1])
+ break;
+ *t++ = p[k];
+ if (p[k + 1])
+ t[-1] = '?';
+ }
+ if (k >= 26)
+ for (k = 28; k < 32 && t < longName + sizeof(longName); k += 2) {
+ if (!p[k] && !p[k + 1])
+ break;
+ *t++ = p[k];
+ if (p[k + 1])
+ t[-1] = '?';
+ }
+ if (t >= longName + sizeof(longName)) {
+ pwarn("long filename too long\n");
+ if (!invlfn) {
+ invlfn = vallfn;
+ invcl = valcl;
+ }
+ vallfn = NULL;
+ }
+ if (p[26] | (p[27] << 8)) {
+ pwarn("long filename record cluster start != 0\n");
+ if (!invlfn) {
+ invlfn = vallfn;
+ invcl = cl;
+ }
+ vallfn = NULL;
+ }
+ continue; /* long records don't carry further
+ * information */
+ }
+
+ /*
+ * This is a standard msdosfs directory entry.
+ */
+ memset(&dirent, 0, sizeof dirent);
+
+ /*
+ * it's a short name record, but we need to know
+ * more, so get the flags first.
+ */
+ dirent.flags = p[11];
+
+ /*
+ * Translate from 850 to ISO here XXX
+ */
+ for (j = 0; j < 8; j++)
+ dirent.name[j] = p[j];
+ dirent.name[8] = '\0';
+ for (k = 7; k >= 0 && dirent.name[k] == ' '; k--)
+ dirent.name[k] = '\0';
+ if (dirent.name[k] != '\0')
+ k++;
+ if (dirent.name[0] == SLOT_E5)
+ dirent.name[0] = 0xe5;
+
+ if (dirent.flags & ATTR_VOLUME) {
+ if (vallfn || invlfn) {
+ mod |= removede(f, boot, fat,
+ invlfn ? invlfn : vallfn, p,
+ invlfn ? invcl : valcl, -1, 0,
+ fullpath(dir), 2);
+ vallfn = NULL;
+ invlfn = NULL;
+ }
+ continue;
+ }
+
+ if (p[8] != ' ')
+ dirent.name[k++] = '.';
+ for (j = 0; j < 3; j++)
+ dirent.name[k++] = p[j+8];
+ dirent.name[k] = '\0';
+ for (k--; k >= 0 && dirent.name[k] == ' '; k--)
+ dirent.name[k] = '\0';
+
+ if (vallfn && shortSum != calcShortSum(p)) {
+ if (!invlfn) {
+ invlfn = vallfn;
+ invcl = valcl;
+ }
+ vallfn = NULL;
+ }
+ dirent.head = p[26] | (p[27] << 8);
+ if (boot->ClustMask == CLUST32_MASK)
+ dirent.head |= (p[20] << 16) | (p[21] << 24);
+ dirent.size = p[28] | (p[29] << 8) | (p[30] << 16) | (p[31] << 24);
+ if (vallfn) {
+ strlcpy(dirent.lname, longName,
+ sizeof(dirent.lname));
+ longName[0] = '\0';
+ shortSum = -1;
+ }
+
+ dirent.parent = dir;
+ dirent.next = dir->child;
+
+ if (invlfn) {
+ mod |= k = removede(f, boot, fat,
+ invlfn, vallfn ? vallfn : p,
+ invcl, vallfn ? valcl : cl, cl,
+ fullpath(&dirent), 0);
+ if (mod & FSFATAL)
+ return FSFATAL;
+ if (vallfn
+ ? (valcl == cl && vallfn != buffer)
+ : p != buffer)
+ if (k & FSDIRMOD)
+ mod |= THISMOD;
+ }
+
+ vallfn = NULL; /* not used any longer */
+ invlfn = NULL;
+
+ if (dirent.size == 0 && !(dirent.flags & ATTR_DIRECTORY)) {
+ if (dirent.head != 0) {
+ pwarn("%s has clusters, but size 0\n",
+ fullpath(&dirent));
+ if (ask(1, "Drop allocated clusters")) {
+ p[26] = p[27] = 0;
+ if (boot->ClustMask == CLUST32_MASK)
+ p[20] = p[21] = 0;
+ clearchain(boot, fat, dirent.head);
+ dirent.head = 0;
+ mod |= THISMOD|FSDIRMOD|FSFATMOD;
+ } else
+ mod |= FSERROR;
+ }
+ } else if (dirent.head == 0
+ && !strcmp(dirent.name, "..")
+ && dir->parent /* XXX */
+ && !dir->parent->parent) {
+ /*
+ * Do nothing, the parent is the root
+ */
+ } else if (dirent.head < CLUST_FIRST
+ || dirent.head >= boot->NumClusters
+ || fat[dirent.head].next == CLUST_FREE
+ || (fat[dirent.head].next >= CLUST_RSRVD
+ && fat[dirent.head].next < CLUST_EOFS)
+ || fat[dirent.head].head != dirent.head) {
+ if (dirent.head == 0)
+ pwarn("%s has no clusters\n",
+ fullpath(&dirent));
+ else if (dirent.head < CLUST_FIRST
+ || dirent.head >= boot->NumClusters)
+ pwarn("%s starts with cluster out of range(%u)\n",
+ fullpath(&dirent),
+ dirent.head);
+ else if (fat[dirent.head].next == CLUST_FREE)
+ pwarn("%s starts with free cluster\n",
+ fullpath(&dirent));
+ else if (fat[dirent.head].next >= CLUST_RSRVD)
+ pwarn("%s starts with cluster marked %s\n",
+ fullpath(&dirent),
+ rsrvdcltype(fat[dirent.head].next));
+ else
+ pwarn("%s doesn't start a cluster chain\n",
+ fullpath(&dirent));
+ if (dirent.flags & ATTR_DIRECTORY) {
+ if (ask(0, "Remove")) {
+ *p = SLOT_DELETED;
+ mod |= THISMOD|FSDIRMOD;
+ } else
+ mod |= FSERROR;
+ continue;
+ } else {
+ if (ask(1, "Truncate")) {
+ p[28] = p[29] = p[30] = p[31] = 0;
+ p[26] = p[27] = 0;
+ if (boot->ClustMask == CLUST32_MASK)
+ p[20] = p[21] = 0;
+ dirent.size = 0;
+ mod |= THISMOD|FSDIRMOD;
+ } else
+ mod |= FSERROR;
+ }
+ }
+
+ if (dirent.head >= CLUST_FIRST && dirent.head < boot->NumClusters)
+ fat[dirent.head].flags |= FAT_USED;
+
+ if (dirent.flags & ATTR_DIRECTORY) {
+ /*
+ * gather more info for directories
+ */
+ struct dirTodoNode *n;
+
+ if (dirent.size) {
+ pwarn("Directory %s has size != 0\n",
+ fullpath(&dirent));
+ if (ask(1, "Correct")) {
+ p[28] = p[29] = p[30] = p[31] = 0;
+ dirent.size = 0;
+ mod |= THISMOD|FSDIRMOD;
+ } else
+ mod |= FSERROR;
+ }
+ /*
+ * handle `.' and `..' specially
+ */
+ if (strcmp(dirent.name, ".") == 0) {
+ if (dirent.head != dir->head) {
+ pwarn("`.' entry in %s has incorrect start cluster\n",
+ fullpath(dir));
+ if (ask(1, "Correct")) {
+ dirent.head = dir->head;
+ p[26] = (u_char)dirent.head;
+ p[27] = (u_char)(dirent.head >> 8);
+ if (boot->ClustMask == CLUST32_MASK) {
+ p[20] = (u_char)(dirent.head >> 16);
+ p[21] = (u_char)(dirent.head >> 24);
+ }
+ mod |= THISMOD|FSDIRMOD;
+ } else
+ mod |= FSERROR;
+ }
+ continue;
+ }
+ if (strcmp(dirent.name, "..") == 0) {
+ if (dir->parent) { /* XXX */
+ if (!dir->parent->parent) {
+ if (dirent.head) {
+ pwarn("`..' entry in %s has non-zero start cluster\n",
+ fullpath(dir));
+ if (ask(1, "Correct")) {
+ dirent.head = 0;
+ p[26] = p[27] = 0;
+ if (boot->ClustMask == CLUST32_MASK)
+ p[20] = p[21] = 0;
+ mod |= THISMOD|FSDIRMOD;
+ } else
+ mod |= FSERROR;
+ }
+ } else if (dirent.head != dir->parent->head) {
+ pwarn("`..' entry in %s has incorrect start cluster\n",
+ fullpath(dir));
+ if (ask(1, "Correct")) {
+ dirent.head = dir->parent->head;
+ p[26] = (u_char)dirent.head;
+ p[27] = (u_char)(dirent.head >> 8);
+ if (boot->ClustMask == CLUST32_MASK) {
+ p[20] = (u_char)(dirent.head >> 16);
+ p[21] = (u_char)(dirent.head >> 24);
+ }
+ mod |= THISMOD|FSDIRMOD;
+ } else
+ mod |= FSERROR;
+ }
+ }
+ continue;
+ }
+
+ /* create directory tree node */
+ if (!(d = newDosDirEntry())) {
+ perr("No space for directory");
+ return FSFATAL;
+ }
+ memcpy(d, &dirent, sizeof(struct dosDirEntry));
+ /* link it into the tree */
+ dir->child = d;
+
+ /* Enter this directory into the todo list */
+ if (!(n = newDirTodo())) {
+ perr("No space for todo list");
+ return FSFATAL;
+ }
+ n->next = pendingDirectories;
+ n->dir = d;
+ pendingDirectories = n;
+ } else {
+ mod |= k = checksize(boot, fat, p, &dirent);
+ if (k & FSDIRMOD)
+ mod |= THISMOD;
+ }
+ boot->NumFiles++;
+ }
+
+ if (!(boot->flags & FAT32) && !dir->parent)
+ break;
+
+ if (mod & THISMOD) {
+ last *= 32;
+ if (lseek(f, off, SEEK_SET) != off
+ || write(f, buffer, last) != last) {
+ perr("Unable to write directory");
+ return FSFATAL;
+ }
+ mod &= ~THISMOD;
+ }
+ } while ((cl = fat[cl].next) >= CLUST_FIRST && cl < boot->NumClusters);
+ if (invlfn || vallfn)
+ mod |= removede(f, boot, fat,
+ invlfn ? invlfn : vallfn, p,
+ invlfn ? invcl : valcl, -1, 0,
+ fullpath(dir), 1);
+
+ /* The root directory of non fat32 filesystems is in a special
+ * area and may have been modified above without being written out.
+ */
+ if ((mod & FSDIRMOD) && !(boot->flags & FAT32) && !dir->parent) {
+ last *= 32;
+ if (lseek(f, off, SEEK_SET) != off
+ || write(f, buffer, last) != last) {
+ perr("Unable to write directory");
+ return FSFATAL;
+ }
+ mod &= ~THISMOD;
+ }
+ return mod & ~THISMOD;
+}
+
+int
+handleDirTree(int dosfs, struct bootblock *boot, struct fatEntry *fat)
+{
+ int mod;
+
+ mod = readDosDirSection(dosfs, boot, fat, rootDir);
+ if (mod & FSFATAL)
+ return FSFATAL;
+
+ /*
+ * process the directory todo list
+ */
+ while (pendingDirectories) {
+ struct dosDirEntry *dir = pendingDirectories->dir;
+ struct dirTodoNode *n = pendingDirectories->next;
+
+ /*
+ * remove TODO entry now, the list might change during
+ * directory reads
+ */
+ freeDirTodo(pendingDirectories);
+ pendingDirectories = n;
+
+ /*
+ * handle subdirectory
+ */
+ mod |= readDosDirSection(dosfs, boot, fat, dir);
+ if (mod & FSFATAL)
+ return FSFATAL;
+ }
+
+ return mod;
+}
+
+/*
+ * Try to reconnect a FAT chain into dir
+ */
+static u_char *lfbuf;
+static cl_t lfcl;
+static off_t lfoff;
+
+int
+reconnect(int dosfs, struct bootblock *boot, struct fatEntry *fat, cl_t head)
+{
+ struct dosDirEntry d;
+ u_char *p;
+
+ if (!ask(1, "Reconnect"))
+ return FSERROR;
+
+ if (!lostDir) {
+ for (lostDir = rootDir->child; lostDir; lostDir = lostDir->next) {
+ if (!strcmp(lostDir->name, LOSTDIR))
+ break;
+ }
+ if (!lostDir) { /* Create LOSTDIR? XXX */
+ pwarn("No %s directory\n", LOSTDIR);
+ return FSERROR;
+ }
+ }
+ if (!lfbuf) {
+ lfbuf = malloc(boot->ClusterSize);
+ if (!lfbuf) {
+ perr("No space for buffer");
+ return FSFATAL;
+ }
+ p = NULL;
+ } else
+ p = lfbuf;
+ while (1) {
+ if (p)
+ for (; p < lfbuf + boot->ClusterSize; p += 32)
+ if (*p == SLOT_EMPTY
+ || *p == SLOT_DELETED)
+ break;
+ if (p && p < lfbuf + boot->ClusterSize)
+ break;
+ lfcl = p ? fat[lfcl].next : lostDir->head;
+ if (lfcl < CLUST_FIRST || lfcl >= boot->NumClusters) {
+ /* Extend LOSTDIR? XXX */
+ pwarn("No space in %s\n", LOSTDIR);
+ return FSERROR;
+ }
+ lfoff = lfcl * boot->ClusterSize
+ + boot->ClusterOffset * boot->bpbBytesPerSec;
+ if (lseek(dosfs, lfoff, SEEK_SET) != lfoff
+ || (size_t)read(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) {
+ perr("could not read LOST.DIR");
+ return FSFATAL;
+ }
+ p = lfbuf;
+ }
+
+ boot->NumFiles++;
+ /* Ensure uniqueness of entry here! XXX */
+ memset(&d, 0, sizeof d);
+ (void)snprintf(d.name, sizeof(d.name), "%u", head);
+ d.flags = 0;
+ d.head = head;
+ d.size = fat[head].length * boot->ClusterSize;
+
+ memset(p, 0, 32);
+ memset(p, ' ', 11);
+ memcpy(p, d.name, strlen(d.name));
+ p[26] = (u_char)d.head;
+ p[27] = (u_char)(d.head >> 8);
+ if (boot->ClustMask == CLUST32_MASK) {
+ p[20] = (u_char)(d.head >> 16);
+ p[21] = (u_char)(d.head >> 24);
+ }
+ p[28] = (u_char)d.size;
+ p[29] = (u_char)(d.size >> 8);
+ p[30] = (u_char)(d.size >> 16);
+ p[31] = (u_char)(d.size >> 24);
+ fat[head].flags |= FAT_USED;
+ if (lseek(dosfs, lfoff, SEEK_SET) != lfoff
+ || (size_t)write(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) {
+ perr("could not write LOST.DIR");
+ return FSFATAL;
+ }
+ return FSDIRMOD;
+}
+
+void
+finishlf(void)
+{
+ if (lfbuf)
+ free(lfbuf);
+ lfbuf = NULL;
+}
diff --git a/sbin/fsck_msdosfs/dosfs.h b/sbin/fsck_msdosfs/dosfs.h
new file mode 100644
index 0000000..485b3a1
--- /dev/null
+++ b/sbin/fsck_msdosfs/dosfs.h
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 1995, 1996, 1997 Wolfgang Solfrank
+ * Copyright (c) 1995 Martin Husemann
+ * Some structure declaration borrowed from Paul Popelka
+ * (paulp@uts.amdahl.com), see /sys/msdosfs/ for reference.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``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 AUTHORS 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.
+ * $NetBSD: dosfs.h,v 1.4 1997/01/03 14:32:48 ws Exp $
+ * $FreeBSD$
+ */
+
+#ifndef DOSFS_H
+#define DOSFS_H
+
+/* support 4Kn disk reads */
+#define DOSBOOTBLOCKSIZE_REAL 512
+#define DOSBOOTBLOCKSIZE 4096
+
+typedef u_int32_t cl_t; /* type holding a cluster number */
+
+/*
+ * architecture independent description of all the info stored in a
+ * FAT boot block.
+ */
+struct bootblock {
+ u_int bpbBytesPerSec; /* bytes per sector */
+ u_int bpbSecPerClust; /* sectors per cluster */
+ u_int bpbResSectors; /* number of reserved sectors */
+ u_int bpbFATs; /* number of bpbFATs */
+ u_int bpbRootDirEnts; /* number of root directory entries */
+ u_int32_t bpbSectors; /* total number of sectors */
+ u_int bpbMedia; /* media descriptor */
+ u_int bpbFATsmall; /* number of sectors per FAT */
+ u_int SecPerTrack; /* sectors per track */
+ u_int bpbHeads; /* number of heads */
+ u_int32_t bpbHiddenSecs; /* # of hidden sectors */
+ u_int32_t bpbHugeSectors; /* # of sectors if bpbbpbSectors == 0 */
+ cl_t bpbRootClust; /* Start of Root Directory */
+ u_int bpbFSInfo; /* FSInfo sector */
+ u_int bpbBackup; /* Backup of Bootblocks */
+ cl_t FSFree; /* Number of free clusters acc. FSInfo */
+ cl_t FSNext; /* Next free cluster acc. FSInfo */
+
+ /* and some more calculated values */
+ u_int flags; /* some flags: */
+#define FAT32 1 /* this is a FAT32 file system */
+ /*
+ * Maybe, we should separate out
+ * various parts of FAT32? XXX
+ */
+ int ValidFat; /* valid fat if FAT32 non-mirrored */
+ cl_t ClustMask; /* mask for entries in FAT */
+ cl_t NumClusters; /* # of entries in a FAT */
+ u_int32_t NumSectors; /* how many sectors are there */
+ u_int32_t FATsecs; /* how many sectors are in FAT */
+ u_int32_t NumFatEntries; /* how many entries really are there */
+ u_int ClusterOffset; /* at what sector would sector 0 start */
+ u_int ClusterSize; /* Cluster size in bytes */
+
+ /* Now some statistics: */
+ u_int NumFiles; /* # of plain files */
+ u_int NumFree; /* # of free clusters */
+ u_int NumBad; /* # of bad clusters */
+};
+
+struct fatEntry {
+ cl_t next; /* pointer to next cluster */
+ cl_t head; /* pointer to start of chain */
+ u_int32_t length; /* number of clusters on chain */
+ int flags; /* see below */
+};
+
+#define CLUST_FREE 0 /* 0 means cluster is free */
+#define CLUST_FIRST 2 /* 2 is the minimum valid cluster number */
+#define CLUST_RSRVD 0xfffffff6 /* start of reserved clusters */
+#define CLUST_BAD 0xfffffff7 /* a cluster with a defect */
+#define CLUST_EOFS 0xfffffff8 /* start of EOF indicators */
+#define CLUST_EOF 0xffffffff /* standard value for last cluster */
+
+/*
+ * Masks for cluster values
+ */
+#define CLUST12_MASK 0xfff
+#define CLUST16_MASK 0xffff
+#define CLUST32_MASK 0xfffffff
+
+#define FAT_USED 1 /* This fat chain is used in a file */
+
+#define DOSLONGNAMELEN 256 /* long name maximal length */
+#define LRFIRST 0x40 /* first long name record */
+#define LRNOMASK 0x1f /* mask to extract long record
+ * sequence number */
+
+/*
+ * Architecture independent description of a directory entry
+ */
+struct dosDirEntry {
+ struct dosDirEntry
+ *parent, /* previous tree level */
+ *next, /* next brother */
+ *child; /* if this is a directory */
+ char name[8+1+3+1]; /* alias name first part */
+ char lname[DOSLONGNAMELEN]; /* real name */
+ uint flags; /* attributes */
+ cl_t head; /* cluster no */
+ u_int32_t size; /* filesize in bytes */
+ uint fsckflags; /* flags during fsck */
+};
+/* Flags in fsckflags: */
+#define DIREMPTY 1
+#define DIREMPWARN 2
+
+/*
+ * TODO-list of unread directories
+ */
+struct dirTodoNode {
+ struct dosDirEntry *dir;
+ struct dirTodoNode *next;
+};
+
+#endif
diff --git a/sbin/fsck_msdosfs/ext.h b/sbin/fsck_msdosfs/ext.h
new file mode 100644
index 0000000..ef9c420
--- /dev/null
+++ b/sbin/fsck_msdosfs/ext.h
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 1995, 1996, 1997 Wolfgang Solfrank
+ * Copyright (c) 1995 Martin Husemann
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``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 AUTHORS 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.
+ * $NetBSD: ext.h,v 1.6 2000/04/25 23:02:51 jdolecek Exp $
+ * $FreeBSD$
+ */
+
+#ifndef EXT_H
+#define EXT_H
+
+#include <sys/types.h>
+
+#include "dosfs.h"
+
+#define LOSTDIR "LOST.DIR"
+
+/*
+ * Options:
+ */
+extern int alwaysno; /* assume "no" for all questions */
+extern int alwaysyes; /* assume "yes" for all questions */
+extern int preen; /* we are preening */
+extern int rdonly; /* device is opened read only (supersedes above) */
+extern int skipclean; /* skip clean file systems if preening */
+
+/*
+ * function declarations
+ */
+int ask(int, const char *, ...) __printflike(2, 3);
+
+/*
+ * Check the dirty flag. If the file system is clean, then return 1.
+ * Otherwise, return 0 (this includes the case of FAT12 file systems --
+ * they have no dirty flag, so they must be assumed to be unclean).
+ */
+int checkdirty(int, struct bootblock *);
+
+/*
+ * Check file system given as arg
+ */
+int checkfilesys(const char *);
+
+/*
+ * Return values of various functions
+ */
+#define FSOK 0 /* Check was OK */
+#define FSBOOTMOD 1 /* Boot block was modified */
+#define FSDIRMOD 2 /* Some directory was modified */
+#define FSFATMOD 4 /* The FAT was modified */
+#define FSERROR 8 /* Some unrecovered error remains */
+#define FSFATAL 16 /* Some unrecoverable error occurred */
+#define FSDIRTY 32 /* File system is dirty */
+#define FSFIXFAT 64 /* Fix file system FAT */
+
+/*
+ * read a boot block in a machine independent fashion and translate
+ * it into our struct bootblock.
+ */
+int readboot(int, struct bootblock *);
+
+/*
+ * Correct the FSInfo block.
+ */
+int writefsinfo(int, struct bootblock *);
+
+/*
+ * Read one of the FAT copies and return a pointer to the new
+ * allocated array holding our description of it.
+ */
+int readfat(int, struct bootblock *, u_int, struct fatEntry **);
+
+/*
+ * Check two FAT copies for consistency and merge changes into the
+ * first if necessary.
+ */
+int comparefat(struct bootblock *, struct fatEntry *, struct fatEntry *, u_int);
+
+/*
+ * Check a FAT
+ */
+int checkfat(struct bootblock *, struct fatEntry *);
+
+/*
+ * Write back FAT entries
+ */
+int writefat(int, struct bootblock *, struct fatEntry *, int);
+
+/*
+ * Read a directory
+ */
+int resetDosDirSection(struct bootblock *, struct fatEntry *);
+void finishDosDirSection(void);
+int handleDirTree(int, struct bootblock *, struct fatEntry *);
+
+/*
+ * Cross-check routines run after everything is completely in memory
+ */
+/*
+ * Check for lost cluster chains
+ */
+int checklost(int, struct bootblock *, struct fatEntry *);
+/*
+ * Try to reconnect a lost cluster chain
+ */
+int reconnect(int, struct bootblock *, struct fatEntry *, cl_t);
+void finishlf(void);
+
+/*
+ * Small helper functions
+ */
+/*
+ * Return the type of a reserved cluster as text
+ */
+const char *rsrvdcltype(cl_t);
+
+/*
+ * Clear a cluster chain in a FAT
+ */
+void clearchain(struct bootblock *, struct fatEntry *, cl_t);
+
+#endif
diff --git a/sbin/fsck_msdosfs/fat.c b/sbin/fsck_msdosfs/fat.c
new file mode 100644
index 0000000..b509c50
--- /dev/null
+++ b/sbin/fsck_msdosfs/fat.c
@@ -0,0 +1,711 @@
+/*
+ * Copyright (C) 1995, 1996, 1997 Wolfgang Solfrank
+ * Copyright (c) 1995 Martin Husemann
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``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 AUTHORS 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 <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: fat.c,v 1.18 2006/06/05 16:51:18 christos Exp $");
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include "ext.h"
+#include "fsutil.h"
+
+static int checkclnum(struct bootblock *, u_int, cl_t, cl_t *);
+static int clustdiffer(cl_t, cl_t *, cl_t *, u_int);
+static int tryclear(struct bootblock *, struct fatEntry *, cl_t, cl_t *);
+static int _readfat(int, struct bootblock *, u_int, u_char **);
+
+/*-
+ * The first 2 FAT entries contain pseudo-cluster numbers with the following
+ * layout:
+ *
+ * 31...... ........ ........ .......0
+ * rrrr1111 11111111 11111111 mmmmmmmm FAT32 entry 0
+ * rrrrsh11 11111111 11111111 11111xxx FAT32 entry 1
+ *
+ * 11111111 mmmmmmmm FAT16 entry 0
+ * sh111111 11111xxx FAT16 entry 1
+ *
+ * r = reserved
+ * m = BPB media ID byte
+ * s = clean flag (1 = dismounted; 0 = still mounted)
+ * h = hard error flag (1 = ok; 0 = I/O error)
+ * x = any value ok
+ */
+
+int
+checkdirty(int fs, struct bootblock *boot)
+{
+ off_t off;
+ u_char *buffer;
+ int ret = 0;
+ size_t len;
+
+ if (boot->ClustMask != CLUST16_MASK && boot->ClustMask != CLUST32_MASK)
+ return 0;
+
+ off = boot->bpbResSectors;
+ off *= boot->bpbBytesPerSec;
+
+ buffer = malloc(len = boot->bpbBytesPerSec);
+ if (buffer == NULL) {
+ perr("No space for FAT sectors (%zu)", len);
+ return 1;
+ }
+
+ if (lseek(fs, off, SEEK_SET) != off) {
+ perr("Unable to read FAT");
+ goto err;
+ }
+
+ if ((size_t)read(fs, buffer, boot->bpbBytesPerSec) !=
+ boot->bpbBytesPerSec) {
+ perr("Unable to read FAT");
+ goto err;
+ }
+
+ /*
+ * If we don't understand the FAT, then the file system must be
+ * assumed to be unclean.
+ */
+ if (buffer[0] != boot->bpbMedia || buffer[1] != 0xff)
+ goto err;
+ if (boot->ClustMask == CLUST16_MASK) {
+ if ((buffer[2] & 0xf8) != 0xf8 || (buffer[3] & 0x3f) != 0x3f)
+ goto err;
+ } else {
+ if (buffer[2] != 0xff || (buffer[3] & 0x0f) != 0x0f
+ || (buffer[4] & 0xf8) != 0xf8 || buffer[5] != 0xff
+ || buffer[6] != 0xff || (buffer[7] & 0x03) != 0x03)
+ goto err;
+ }
+
+ /*
+ * Now check the actual clean flag (and the no-error flag).
+ */
+ if (boot->ClustMask == CLUST16_MASK) {
+ if ((buffer[3] & 0xc0) == 0xc0)
+ ret = 1;
+ } else {
+ if ((buffer[7] & 0x0c) == 0x0c)
+ ret = 1;
+ }
+
+err:
+ free(buffer);
+ return ret;
+}
+
+/*
+ * Check a cluster number for valid value
+ */
+static int
+checkclnum(struct bootblock *boot, u_int fat, cl_t cl, cl_t *next)
+{
+ if (*next >= (CLUST_RSRVD&boot->ClustMask))
+ *next |= ~boot->ClustMask;
+ if (*next == CLUST_FREE) {
+ boot->NumFree++;
+ return FSOK;
+ }
+ if (*next == CLUST_BAD) {
+ boot->NumBad++;
+ return FSOK;
+ }
+ if (*next < CLUST_FIRST
+ || (*next >= boot->NumClusters && *next < CLUST_EOFS)) {
+ pwarn("Cluster %u in FAT %d continues with %s cluster number %u\n",
+ cl, fat,
+ *next < CLUST_RSRVD ? "out of range" : "reserved",
+ *next&boot->ClustMask);
+ if (ask(0, "Truncate")) {
+ *next = CLUST_EOF;
+ return FSFATMOD;
+ }
+ return FSERROR;
+ }
+ return FSOK;
+}
+
+/*
+ * Read a FAT from disk. Returns 1 if successful, 0 otherwise.
+ */
+static int
+_readfat(int fs, struct bootblock *boot, u_int no, u_char **buffer)
+{
+ off_t off;
+ size_t len;
+
+ *buffer = malloc(len = boot->FATsecs * boot->bpbBytesPerSec);
+ if (*buffer == NULL) {
+ perr("No space for FAT sectors (%zu)", len);
+ return 0;
+ }
+
+ off = boot->bpbResSectors + no * boot->FATsecs;
+ off *= boot->bpbBytesPerSec;
+
+ if (lseek(fs, off, SEEK_SET) != off) {
+ perr("Unable to read FAT");
+ goto err;
+ }
+
+ if ((size_t)read(fs, *buffer, boot->FATsecs * boot->bpbBytesPerSec)
+ != boot->FATsecs * boot->bpbBytesPerSec) {
+ perr("Unable to read FAT");
+ goto err;
+ }
+
+ return 1;
+
+ err:
+ free(*buffer);
+ return 0;
+}
+
+/*
+ * Read a FAT and decode it into internal format
+ */
+int
+readfat(int fs, struct bootblock *boot, u_int no, struct fatEntry **fp)
+{
+ struct fatEntry *fat;
+ u_char *buffer, *p;
+ cl_t cl;
+ int ret = FSOK;
+ size_t len;
+
+ boot->NumFree = boot->NumBad = 0;
+
+ if (!_readfat(fs, boot, no, &buffer))
+ return FSFATAL;
+
+ fat = malloc(len = boot->NumClusters * sizeof(struct fatEntry));
+ if (fat == NULL) {
+ perr("No space for FAT clusters (%zu)", len);
+ free(buffer);
+ return FSFATAL;
+ }
+ (void)memset(fat, 0, len);
+
+ if (buffer[0] != boot->bpbMedia
+ || buffer[1] != 0xff || buffer[2] != 0xff
+ || (boot->ClustMask == CLUST16_MASK && buffer[3] != 0xff)
+ || (boot->ClustMask == CLUST32_MASK
+ && ((buffer[3]&0x0f) != 0x0f
+ || buffer[4] != 0xff || buffer[5] != 0xff
+ || buffer[6] != 0xff || (buffer[7]&0x0f) != 0x0f))) {
+
+ /* Windows 95 OSR2 (and possibly any later) changes
+ * the FAT signature to 0xXXffff7f for FAT16 and to
+ * 0xXXffff0fffffff07 for FAT32 upon boot, to know that the
+ * file system is dirty if it doesn't reboot cleanly.
+ * Check this special condition before errorring out.
+ */
+ if (buffer[0] == boot->bpbMedia && buffer[1] == 0xff
+ && buffer[2] == 0xff
+ && ((boot->ClustMask == CLUST16_MASK && buffer[3] == 0x7f)
+ || (boot->ClustMask == CLUST32_MASK
+ && buffer[3] == 0x0f && buffer[4] == 0xff
+ && buffer[5] == 0xff && buffer[6] == 0xff
+ && buffer[7] == 0x07)))
+ ret |= FSDIRTY;
+ else {
+ /* just some odd byte sequence in FAT */
+
+ switch (boot->ClustMask) {
+ case CLUST32_MASK:
+ pwarn("%s (%02x%02x%02x%02x%02x%02x%02x%02x)\n",
+ "FAT starts with odd byte sequence",
+ buffer[0], buffer[1], buffer[2], buffer[3],
+ buffer[4], buffer[5], buffer[6], buffer[7]);
+ break;
+ case CLUST16_MASK:
+ pwarn("%s (%02x%02x%02x%02x)\n",
+ "FAT starts with odd byte sequence",
+ buffer[0], buffer[1], buffer[2], buffer[3]);
+ break;
+ default:
+ pwarn("%s (%02x%02x%02x)\n",
+ "FAT starts with odd byte sequence",
+ buffer[0], buffer[1], buffer[2]);
+ break;
+ }
+
+
+ if (ask(1, "Correct"))
+ ret |= FSFIXFAT;
+ }
+ }
+ switch (boot->ClustMask) {
+ case CLUST32_MASK:
+ p = buffer + 8;
+ break;
+ case CLUST16_MASK:
+ p = buffer + 4;
+ break;
+ default:
+ p = buffer + 3;
+ break;
+ }
+ for (cl = CLUST_FIRST; cl < boot->NumClusters;) {
+ switch (boot->ClustMask) {
+ case CLUST32_MASK:
+ fat[cl].next = p[0] + (p[1] << 8)
+ + (p[2] << 16) + (p[3] << 24);
+ fat[cl].next &= boot->ClustMask;
+ ret |= checkclnum(boot, no, cl, &fat[cl].next);
+ cl++;
+ p += 4;
+ break;
+ case CLUST16_MASK:
+ fat[cl].next = p[0] + (p[1] << 8);
+ ret |= checkclnum(boot, no, cl, &fat[cl].next);
+ cl++;
+ p += 2;
+ break;
+ default:
+ fat[cl].next = (p[0] + (p[1] << 8)) & 0x0fff;
+ ret |= checkclnum(boot, no, cl, &fat[cl].next);
+ cl++;
+ if (cl >= boot->NumClusters)
+ break;
+ fat[cl].next = ((p[1] >> 4) + (p[2] << 4)) & 0x0fff;
+ ret |= checkclnum(boot, no, cl, &fat[cl].next);
+ cl++;
+ p += 3;
+ break;
+ }
+ }
+
+ free(buffer);
+ if (ret & FSFATAL) {
+ free(fat);
+ *fp = NULL;
+ } else
+ *fp = fat;
+ return ret;
+}
+
+/*
+ * Get type of reserved cluster
+ */
+const char *
+rsrvdcltype(cl_t cl)
+{
+ if (cl == CLUST_FREE)
+ return "free";
+ if (cl < CLUST_BAD)
+ return "reserved";
+ if (cl > CLUST_BAD)
+ return "as EOF";
+ return "bad";
+}
+
+static int
+clustdiffer(cl_t cl, cl_t *cp1, cl_t *cp2, u_int fatnum)
+{
+ if (*cp1 == CLUST_FREE || *cp1 >= CLUST_RSRVD) {
+ if (*cp2 == CLUST_FREE || *cp2 >= CLUST_RSRVD) {
+ if ((*cp1 != CLUST_FREE && *cp1 < CLUST_BAD
+ && *cp2 != CLUST_FREE && *cp2 < CLUST_BAD)
+ || (*cp1 > CLUST_BAD && *cp2 > CLUST_BAD)) {
+ pwarn("Cluster %u is marked %s with different indicators\n",
+ cl, rsrvdcltype(*cp1));
+ if (ask(1, "Fix")) {
+ *cp2 = *cp1;
+ return FSFATMOD;
+ }
+ return FSFATAL;
+ }
+ pwarn("Cluster %u is marked %s in FAT 0, %s in FAT %u\n",
+ cl, rsrvdcltype(*cp1), rsrvdcltype(*cp2), fatnum);
+ if (ask(0, "Use FAT 0's entry")) {
+ *cp2 = *cp1;
+ return FSFATMOD;
+ }
+ if (ask(0, "Use FAT %u's entry", fatnum)) {
+ *cp1 = *cp2;
+ return FSFATMOD;
+ }
+ return FSFATAL;
+ }
+ pwarn("Cluster %u is marked %s in FAT 0, but continues with cluster %u in FAT %d\n",
+ cl, rsrvdcltype(*cp1), *cp2, fatnum);
+ if (ask(0, "Use continuation from FAT %u", fatnum)) {
+ *cp1 = *cp2;
+ return FSFATMOD;
+ }
+ if (ask(0, "Use mark from FAT 0")) {
+ *cp2 = *cp1;
+ return FSFATMOD;
+ }
+ return FSFATAL;
+ }
+ if (*cp2 == CLUST_FREE || *cp2 >= CLUST_RSRVD) {
+ pwarn("Cluster %u continues with cluster %u in FAT 0, but is marked %s in FAT %u\n",
+ cl, *cp1, rsrvdcltype(*cp2), fatnum);
+ if (ask(0, "Use continuation from FAT 0")) {
+ *cp2 = *cp1;
+ return FSFATMOD;
+ }
+ if (ask(0, "Use mark from FAT %d", fatnum)) {
+ *cp1 = *cp2;
+ return FSFATMOD;
+ }
+ return FSERROR;
+ }
+ pwarn("Cluster %u continues with cluster %u in FAT 0, but with cluster %u in FAT %u\n",
+ cl, *cp1, *cp2, fatnum);
+ if (ask(0, "Use continuation from FAT 0")) {
+ *cp2 = *cp1;
+ return FSFATMOD;
+ }
+ if (ask(0, "Use continuation from FAT %u", fatnum)) {
+ *cp1 = *cp2;
+ return FSFATMOD;
+ }
+ return FSERROR;
+}
+
+/*
+ * Compare two FAT copies in memory. Resolve any conflicts and merge them
+ * into the first one.
+ */
+int
+comparefat(struct bootblock *boot, struct fatEntry *first,
+ struct fatEntry *second, u_int fatnum)
+{
+ cl_t cl;
+ int ret = FSOK;
+
+ for (cl = CLUST_FIRST; cl < boot->NumClusters; cl++)
+ if (first[cl].next != second[cl].next)
+ ret |= clustdiffer(cl, &first[cl].next, &second[cl].next, fatnum);
+ return ret;
+}
+
+void
+clearchain(struct bootblock *boot, struct fatEntry *fat, cl_t head)
+{
+ cl_t p, q;
+
+ for (p = head; p >= CLUST_FIRST && p < boot->NumClusters; p = q) {
+ if (fat[p].head != head)
+ break;
+ q = fat[p].next;
+ fat[p].next = fat[p].head = CLUST_FREE;
+ fat[p].length = 0;
+ }
+}
+
+int
+tryclear(struct bootblock *boot, struct fatEntry *fat, cl_t head, cl_t *truncp)
+{
+ if (ask(0, "Clear chain starting at %u", head)) {
+ clearchain(boot, fat, head);
+ return FSFATMOD;
+ } else if (ask(0, "Truncate")) {
+ uint32_t len;
+ cl_t p;
+
+ for (p = head, len = 0;
+ p >= CLUST_FIRST && p < boot->NumClusters;
+ p = fat[p].next, len++)
+ continue;
+ *truncp = CLUST_EOF;
+ fat[head].length = len;
+ return FSFATMOD;
+ } else
+ return FSERROR;
+}
+
+/*
+ * Check a complete FAT in-memory for crosslinks
+ */
+int
+checkfat(struct bootblock *boot, struct fatEntry *fat)
+{
+ cl_t head, p, h, n;
+ u_int len;
+ int ret = 0;
+ int conf;
+
+ /*
+ * pass 1: figure out the cluster chains.
+ */
+ for (head = CLUST_FIRST; head < boot->NumClusters; head++) {
+ /* find next untravelled chain */
+ if (fat[head].head != 0 /* cluster already belongs to some chain */
+ || fat[head].next == CLUST_FREE
+ || fat[head].next == CLUST_BAD)
+ continue; /* skip it. */
+
+ /* follow the chain and mark all clusters on the way */
+ for (len = 0, p = head;
+ p >= CLUST_FIRST && p < boot->NumClusters &&
+ fat[p].head != head;
+ p = fat[p].next) {
+ fat[p].head = head;
+ len++;
+ }
+
+ /* the head record gets the length */
+ fat[head].length = fat[head].next == CLUST_FREE ? 0 : len;
+ }
+
+ /*
+ * pass 2: check for crosslinked chains (we couldn't do this in pass 1 because
+ * we didn't know the real start of the chain then - would have treated partial
+ * chains as interlinked with their main chain)
+ */
+ for (head = CLUST_FIRST; head < boot->NumClusters; head++) {
+ /* find next untravelled chain */
+ if (fat[head].head != head)
+ continue;
+
+ /* follow the chain to its end (hopefully) */
+ for (len = fat[head].length, p = head;
+ (n = fat[p].next) >= CLUST_FIRST && n < boot->NumClusters;
+ p = n)
+ if (fat[n].head != head || len-- < 2)
+ break;
+ if (n >= CLUST_EOFS)
+ continue;
+
+ if (n == CLUST_FREE || n >= CLUST_RSRVD) {
+ pwarn("Cluster chain starting at %u ends with cluster marked %s\n",
+ head, rsrvdcltype(n));
+clear:
+ ret |= tryclear(boot, fat, head, &fat[p].next);
+ continue;
+ }
+ if (n < CLUST_FIRST || n >= boot->NumClusters) {
+ pwarn("Cluster chain starting at %u ends with cluster out of range (%u)\n",
+ head, n);
+ goto clear;
+ }
+ if (head == fat[n].head) {
+ pwarn("Cluster chain starting at %u loops at cluster %u\n",
+
+ head, p);
+ goto clear;
+ }
+ pwarn("Cluster chains starting at %u and %u are linked at cluster %u\n",
+ head, fat[n].head, n);
+ conf = tryclear(boot, fat, head, &fat[p].next);
+ if (ask(0, "Clear chain starting at %u", h = fat[n].head)) {
+ if (conf == FSERROR) {
+ /*
+ * Transfer the common chain to the one not cleared above.
+ */
+ for (p = n;
+ p >= CLUST_FIRST && p < boot->NumClusters;
+ p = fat[p].next) {
+ if (h != fat[p].head) {
+ /*
+ * Have to reexamine this chain.
+ */
+ head--;
+ break;
+ }
+ fat[p].head = head;
+ }
+ }
+ clearchain(boot, fat, h);
+ conf |= FSFATMOD;
+ }
+ ret |= conf;
+ }
+
+ return ret;
+}
+
+/*
+ * Write out FATs encoding them from the internal format
+ */
+int
+writefat(int fs, struct bootblock *boot, struct fatEntry *fat, int correct_fat)
+{
+ u_char *buffer, *p;
+ cl_t cl;
+ u_int i;
+ size_t fatsz;
+ off_t off;
+ int ret = FSOK;
+
+ buffer = malloc(fatsz = boot->FATsecs * boot->bpbBytesPerSec);
+ if (buffer == NULL) {
+ perr("No space for FAT sectors (%zu)", fatsz);
+ return FSFATAL;
+ }
+ memset(buffer, 0, fatsz);
+ boot->NumFree = 0;
+ p = buffer;
+ if (correct_fat) {
+ *p++ = (u_char)boot->bpbMedia;
+ *p++ = 0xff;
+ *p++ = 0xff;
+ switch (boot->ClustMask) {
+ case CLUST16_MASK:
+ *p++ = 0xff;
+ break;
+ case CLUST32_MASK:
+ *p++ = 0x0f;
+ *p++ = 0xff;
+ *p++ = 0xff;
+ *p++ = 0xff;
+ *p++ = 0x0f;
+ break;
+ }
+ } else {
+ /* use same FAT signature as the old FAT has */
+ int count;
+ u_char *old_fat;
+
+ switch (boot->ClustMask) {
+ case CLUST32_MASK:
+ count = 8;
+ break;
+ case CLUST16_MASK:
+ count = 4;
+ break;
+ default:
+ count = 3;
+ break;
+ }
+
+ if (!_readfat(fs, boot, boot->ValidFat >= 0 ? boot->ValidFat :0,
+ &old_fat)) {
+ free(buffer);
+ return FSFATAL;
+ }
+
+ memcpy(p, old_fat, count);
+ free(old_fat);
+ p += count;
+ }
+
+ for (cl = CLUST_FIRST; cl < boot->NumClusters; cl++) {
+ switch (boot->ClustMask) {
+ case CLUST32_MASK:
+ if (fat[cl].next == CLUST_FREE)
+ boot->NumFree++;
+ *p++ = (u_char)fat[cl].next;
+ *p++ = (u_char)(fat[cl].next >> 8);
+ *p++ = (u_char)(fat[cl].next >> 16);
+ *p &= 0xf0;
+ *p++ |= (fat[cl].next >> 24)&0x0f;
+ break;
+ case CLUST16_MASK:
+ if (fat[cl].next == CLUST_FREE)
+ boot->NumFree++;
+ *p++ = (u_char)fat[cl].next;
+ *p++ = (u_char)(fat[cl].next >> 8);
+ break;
+ default:
+ if (fat[cl].next == CLUST_FREE)
+ boot->NumFree++;
+ *p++ = (u_char)fat[cl].next;
+ *p = (u_char)((fat[cl].next >> 8) & 0xf);
+ cl++;
+ if (cl >= boot->NumClusters)
+ break;
+ if (fat[cl].next == CLUST_FREE)
+ boot->NumFree++;
+ *p++ |= (u_char)(fat[cl + 1].next << 4);
+ *p++ = (u_char)(fat[cl + 1].next >> 4);
+ break;
+ }
+ }
+ for (i = 0; i < boot->bpbFATs; i++) {
+ off = boot->bpbResSectors + i * boot->FATsecs;
+ off *= boot->bpbBytesPerSec;
+ if (lseek(fs, off, SEEK_SET) != off
+ || (size_t)write(fs, buffer, fatsz) != fatsz) {
+ perr("Unable to write FAT");
+ ret = FSFATAL; /* Return immediately? XXX */
+ }
+ }
+ free(buffer);
+ return ret;
+}
+
+/*
+ * Check a complete in-memory FAT for lost cluster chains
+ */
+int
+checklost(int dosfs, struct bootblock *boot, struct fatEntry *fat)
+{
+ cl_t head;
+ int mod = FSOK;
+ int ret;
+
+ for (head = CLUST_FIRST; head < boot->NumClusters; head++) {
+ /* find next untravelled chain */
+ if (fat[head].head != head
+ || fat[head].next == CLUST_FREE
+ || (fat[head].next >= CLUST_RSRVD
+ && fat[head].next < CLUST_EOFS)
+ || (fat[head].flags & FAT_USED))
+ continue;
+
+ pwarn("Lost cluster chain at cluster %u\n%d Cluster(s) lost\n",
+ head, fat[head].length);
+ mod |= ret = reconnect(dosfs, boot, fat, head);
+ if (mod & FSFATAL)
+ break;
+ if (ret == FSERROR && ask(0, "Clear")) {
+ clearchain(boot, fat, head);
+ mod |= FSFATMOD;
+ }
+ }
+ finishlf();
+
+ if (boot->bpbFSInfo) {
+ ret = 0;
+ if (boot->FSFree != 0xffffffffU &&
+ boot->FSFree != boot->NumFree) {
+ pwarn("Free space in FSInfo block (%u) not correct (%u)\n",
+ boot->FSFree, boot->NumFree);
+ if (ask(1, "Fix")) {
+ boot->FSFree = boot->NumFree;
+ ret = 1;
+ }
+ }
+ if (ret)
+ mod |= writefsinfo(dosfs, boot);
+ }
+
+ return mod;
+}
diff --git a/sbin/fsck_msdosfs/fsck_msdosfs.8 b/sbin/fsck_msdosfs/fsck_msdosfs.8
new file mode 100644
index 0000000..1c04682
--- /dev/null
+++ b/sbin/fsck_msdosfs/fsck_msdosfs.8
@@ -0,0 +1,123 @@
+.\" $NetBSD: fsck_msdos.8,v 1.9 1997/10/17 11:19:58 ws Exp $
+.\"
+.\" Copyright (C) 1995 Wolfgang Solfrank
+.\" Copyright (c) 1995 Martin Husemann
+.\"
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``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 AUTHORS 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd June 4, 2009
+.Dt FSCK_MSDOSFS 8
+.Os
+.Sh NAME
+.Nm fsck_msdosfs
+.Nd DOS/Windows (FAT) file system consistency checker
+.Sh SYNOPSIS
+.Nm
+.Fl p
+.Op Fl Cf
+.Ar filesystem ...
+.Nm
+.Op Fl Cny
+.Ar filesystem ...
+.Sh DESCRIPTION
+The
+.Nm
+utility verifies and repairs
+.Tn FAT
+file systems (more commonly known
+as
+.Tn DOS
+file systems).
+.Pp
+The first form of
+.Nm
+preens the specified file systems.
+It is normally started by
+.Xr fsck 8
+run from
+.Pa /etc/rc
+during automatic reboot, when a FAT file system is detected.
+When preening file systems,
+.Nm
+will fix common inconsistencies non-interactively.
+If more serious problems are found,
+.Nm
+does not try to fix them, indicates that it was not
+successful, and exits.
+.Pp
+The second form of
+.Nm
+checks the specified file systems and tries to repair all
+detected inconsistencies, requesting confirmation before
+making any changes.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl C
+Compatibility with the corresponding
+.Xr fsck 8
+option (skip check if clean), defined to no-op.
+.It Fl F
+Compatibility with the wrapper
+.Xr fsck 8
+which seeks to determine whether the file system needs to be cleaned
+immediately in foreground, or if its cleaning can be deferred to background.
+FAT (MS-DOS) file systems must always be cleaned in the foreground.
+A non-zero exit code is always returned for this option.
+.It Fl f
+Force
+.Nm
+to check
+.Dq clean
+file systems when preening.
+.It Fl n
+Causes
+.Nm
+to assume
+.Dq Li no
+as the answer to all operator
+questions, except
+.Dq Li CONTINUE? .
+.It Fl p
+Preen the specified file systems.
+.It Fl y
+Causes
+.Nm
+to assume
+.Dq Li yes
+as the answer to all operator questions.
+.El
+.Sh SEE ALSO
+.Xr fsck 8 ,
+.Xr fsck_ffs 8 ,
+.Xr mount_msdosfs 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 4.4 .
+.Sh BUGS
+The
+.Nm
+utility is
+.Ud
diff --git a/sbin/fsck_msdosfs/main.c b/sbin/fsck_msdosfs/main.c
new file mode 100644
index 0000000..e9baf84
--- /dev/null
+++ b/sbin/fsck_msdosfs/main.c
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 1995 Wolfgang Solfrank
+ * Copyright (c) 1995 Martin Husemann
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``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 AUTHORS 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 <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: main.c,v 1.10 1997/10/01 02:18:14 enami Exp $");
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdarg.h>
+
+#include "fsutil.h"
+#include "ext.h"
+
+int alwaysno; /* assume "no" for all questions */
+int alwaysyes; /* assume "yes" for all questions */
+int preen; /* set when preening */
+int rdonly; /* device is opened read only (supersedes above) */
+int skipclean; /* skip clean file systems if preening */
+
+static void usage(void) __dead2;
+
+static void
+usage(void)
+{
+
+ fprintf(stderr, "%s\n%s\n",
+ "usage: fsck_msdosfs -p [-f] filesystem ...",
+ " fsck_msdosfs [-ny] filesystem ...");
+ exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+ int ret = 0, erg;
+ int ch;
+
+ skipclean = 1;
+ while ((ch = getopt(argc, argv, "CfFnpy")) != -1) {
+ switch (ch) {
+ case 'C': /* for fsck_ffs compatibility */
+ break;
+ case 'f':
+ skipclean = 0;
+ break;
+ case 'F':
+ /*
+ * We can never run in the background. We must exit
+ * silently with a nonzero exit code so that fsck(8)
+ * can probe our support for -F. The exit code
+ * doesn't really matter, but we use an unusual one
+ * in case someone tries -F directly. The -F flag
+ * is intentionally left out of the usage message.
+ */
+ exit(5);
+ case 'n':
+ alwaysno = 1;
+ alwaysyes = preen = 0;
+ break;
+ case 'y':
+ alwaysyes = 1;
+ alwaysno = preen = 0;
+ break;
+
+ case 'p':
+ preen = 1;
+ alwaysyes = alwaysno = 0;
+ break;
+
+ default:
+ usage();
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (!argc)
+ usage();
+
+ while (--argc >= 0) {
+ setcdevname(*argv, preen);
+ erg = checkfilesys(*argv++);
+ if (erg > ret)
+ ret = erg;
+ }
+
+ return ret;
+}
+
+
+/*VARARGS*/
+int
+ask(int def, const char *fmt, ...)
+{
+ va_list ap;
+
+ char prompt[256];
+ int c;
+
+ if (preen) {
+ if (rdonly)
+ def = 0;
+ if (def)
+ printf("FIXED\n");
+ return def;
+ }
+
+ va_start(ap, fmt);
+ vsnprintf(prompt, sizeof(prompt), fmt, ap);
+ va_end(ap);
+ if (alwaysyes || rdonly) {
+ printf("%s? %s\n", prompt, rdonly ? "no" : "yes");
+ return !rdonly;
+ }
+ do {
+ printf("%s? [yn] ", prompt);
+ fflush(stdout);
+ c = getchar();
+ while (c != '\n' && getchar() != '\n')
+ if (feof(stdin))
+ return 0;
+ } while (c != 'y' && c != 'Y' && c != 'n' && c != 'N');
+ return c == 'y' || c == 'Y';
+}
diff --git a/sbin/fsdb/Makefile b/sbin/fsdb/Makefile
new file mode 100644
index 0000000..e0a9cc3
--- /dev/null
+++ b/sbin/fsdb/Makefile
@@ -0,0 +1,15 @@
+# $NetBSD: Makefile,v 1.1.1.1 1995/10/08 23:08:36 thorpej Exp $
+# @(#)Makefile 8.1 (Berkeley) 6/5/93
+# $FreeBSD$
+
+PROG= fsdb
+MAN= fsdb.8
+SRCS= fsdb.c fsdbutil.c \
+ dir.c ea.c fsutil.c inode.c pass1.c pass1b.c pass2.c pass3.c pass4.c \
+ pass5.c setup.c utilities.c ffs_subr.c ffs_tables.c globs.c
+CFLAGS+= -I${.CURDIR}/../fsck_ffs
+WARNS?= 2
+LIBADD= edit
+.PATH: ${.CURDIR}/../fsck_ffs ${.CURDIR}/../../sys/ufs/ffs
+
+.include <bsd.prog.mk>
diff --git a/sbin/fsdb/fsdb.8 b/sbin/fsdb/fsdb.8
new file mode 100644
index 0000000..5fc6f24
--- /dev/null
+++ b/sbin/fsdb/fsdb.8
@@ -0,0 +1,269 @@
+.\" $NetBSD: fsdb.8,v 1.2 1995/10/08 23:18:08 thorpej Exp $
+.\"
+.\" Copyright (c) 1995 John T. Kohl
+.\" 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd August 24, 2006
+.Dt FSDB 8
+.Os
+.Sh NAME
+.Nm fsdb
+.Nd FFS debugging/editing tool
+.Sh SYNOPSIS
+.Nm
+.Op Fl d
+.Op Fl f
+.Op Fl r
+.Ar fsname
+.Sh DESCRIPTION
+The
+.Nm
+utility opens
+.Ar fsname
+(usually a raw disk partition) and runs a command loop
+allowing manipulation of the file system's inode data.
+You are prompted
+to enter a command with
+.Ic "fsdb (inum X)>"
+where
+.Va X
+is the currently selected i-number.
+The initial selected inode is the
+root of the file system (i-number 2).
+The command processor uses the
+.Xr editline 3
+library, so you can use command line editing to reduce typing if desired.
+When you exit the command loop, the file system superblock is marked
+dirty and any buffered blocks are written to the file system.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl d
+Enable additional debugging output (which comes primarily from
+.Xr fsck 8 Ns -derived
+code).
+.It Fl f
+Left for historical reasons and has no meaning.
+.It Fl r
+Open the file system read/only, and disables all commands that would
+write to it.
+.El
+.Sh COMMANDS
+Besides the built-in
+.Xr editline 3
+commands,
+.Nm
+supports these commands:
+.Pp
+.Bl -tag -width indent -compact
+.It Cm help
+Print out the list of accepted commands.
+.Pp
+.It Cm inode Ar i-number
+Select inode
+.Ar i-number
+as the new current inode.
+.Pp
+.It Cm back
+Revert to the previously current inode.
+.Pp
+.It Cm clri Ar i-number
+Clear
+.Ar i-number .
+.Pp
+.It Cm lookup Ar name
+.It Cm cd Ar name
+Find
+.Ar name
+in the current directory and make its inode the current inode.
+.Ar Name
+may be a multi-component name or may begin with slash to indicate that
+the root inode should be used to start the lookup.
+If some component
+along the pathname is not found, the last valid directory encountered is
+left as the active inode.
+This command is valid only if the starting inode is a directory.
+.Pp
+.It Cm active
+.It Cm print
+Print out the active inode.
+.Pp
+.It Cm blocks
+Print out the block list of the active inode.
+Note that the printout can become long for large files, since all
+indirect block pointers will also be printed.
+.Pp
+.It Cm findblk Ar disk_block_number ...
+Find the inode(s) owning the specified disk block(s) number(s).
+Note that these are not absolute disk blocks numbers, but offsets from the
+start of the partition.
+.Pp
+.It Cm uplink
+Increment the active inode's link count.
+.Pp
+.It Cm downlink
+Decrement the active inode's link count.
+.Pp
+.It Cm linkcount Ar number
+Set the active inode's link count to
+.Ar number .
+.Pp
+.It Cm ls
+List the current inode's directory entries.
+This command is valid only
+if the current inode is a directory.
+.Pp
+.It Cm rm Ar name
+.It Cm del Ar name
+Remove the entry
+.Ar name
+from the current directory inode.
+This command is valid only
+if the current inode is a directory.
+.Pp
+.It Cm ln Ar ino Ar name
+Create a link to inode
+.Ar ino
+under the name
+.Ar name
+in the current directory inode.
+This command is valid only
+if the current inode is a directory.
+.Pp
+.It Cm chinum Ar dirslot Ar inum
+Change the i-number in directory entry
+.Ar dirslot
+to
+.Ar inum .
+.Pp
+.It Cm chname Ar dirslot Ar name
+Change the name in directory entry
+.Ar dirslot
+to
+.Ar name .
+This command cannot expand a directory entry.
+You can only rename an
+entry if the name will fit into the existing directory slot.
+.Pp
+.It Cm chtype Ar type
+Change the type of the current inode to
+.Ar type .
+.Ar Type
+may be one of:
+.Em file ,
+.Em dir ,
+.Em socket ,
+or
+.Em fifo .
+.Pp
+.It Cm chmod Ar mode
+Change the mode bits of the current inode to
+.Ar mode .
+You cannot change the file type with this subcommand; use
+.Ic chtype
+to do that.
+.Pp
+.It Cm chflags Ar flags
+Change the file flags of the current inode to
+.Ar flags .
+.Pp
+.It Cm chown Ar uid
+Change the owner of the current inode to
+.Ar uid .
+.Pp
+.It Cm chgrp Ar gid
+Change the group of the current inode to
+.Ar gid .
+.Pp
+.It Cm chgen Ar gen
+Change the generation number of the current inode to
+.Ar gen .
+.Pp
+.It Cm btime Ar time
+.It Cm mtime Ar time
+.It Cm ctime Ar time
+.It Cm atime Ar time
+Change the creation (birth), modification, change, or access
+time (respectively) on the current inode to
+.Ar time .
+.Ar Time
+should be in the format
+.Em YYYYMMDDHHMMSS[.nsec]
+where
+.Em nsec
+is an optional nanosecond specification.
+If no nanoseconds are specified, the
+.Va birthnsec ,
+.Va mtimensec ,
+.Va ctimensec ,
+or
+.Va atimensec
+field will be set to zero.
+Note that
+.Cm btime
+is available on UFS2 file systems only.
+.Pp
+.It Cm quit , q , exit , Em <EOF>
+Exit the program.
+.El
+.Sh SEE ALSO
+.Xr editline 3 ,
+.Xr fs 5 ,
+.Xr clri 8 ,
+.Xr fsck 8
+.Sh HISTORY
+The
+.Nm
+utility uses the source code for
+.Xr fsck 8
+to implement most of the file system manipulation code.
+The remainder of
+.Nm
+first appeared in
+.Nx ,
+written by
+.An John T. Kohl .
+.Pp
+.An Peter Wemm
+ported it to
+.Fx .
+.Sh BUGS
+Manipulation of ``short'' symlinks has no effect.
+In particular, one should not
+try changing a symlink's type.
+.Pp
+You must specify modes as numbers rather than symbolic names.
+.Pp
+There are a bunch of other things that you might want to do which
+.Nm
+does not implement.
+.Sh WARNING
+Use this tool with extreme caution--you can damage an FFS file system
+beyond what
+.Xr fsck 8
+can repair.
diff --git a/sbin/fsdb/fsdb.c b/sbin/fsdb/fsdb.c
new file mode 100644
index 0000000..1315aec
--- /dev/null
+++ b/sbin/fsdb/fsdb.c
@@ -0,0 +1,1210 @@
+/* $NetBSD: fsdb.c,v 1.2 1995/10/08 23:18:10 thorpej Exp $ */
+
+/*
+ * Copyright (c) 1995 John T. Kohl
+ * 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.
+ */
+
+#ifndef lint
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <ctype.h>
+#include <err.h>
+#include <grp.h>
+#include <histedit.h>
+#include <pwd.h>
+#include <stdint.h>
+#include <string.h>
+#include <time.h>
+#include <timeconv.h>
+
+#include <ufs/ufs/dinode.h>
+#include <ufs/ufs/dir.h>
+#include <ufs/ffs/fs.h>
+
+#include "fsdb.h"
+#include "fsck.h"
+
+static void usage(void) __dead2;
+int cmdloop(void);
+static int compare_blk32(uint32_t *wantedblk, uint32_t curblk);
+static int compare_blk64(uint64_t *wantedblk, uint64_t curblk);
+static int founddatablk(uint64_t blk);
+static int find_blks32(uint32_t *buf, int size, uint32_t *blknum);
+static int find_blks64(uint64_t *buf, int size, uint64_t *blknum);
+static int find_indirblks32(uint32_t blk, int ind_level, uint32_t *blknum);
+static int find_indirblks64(uint64_t blk, int ind_level, uint64_t *blknum);
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: fsdb [-d] [-f] [-r] fsname\n");
+ exit(1);
+}
+
+int returntosingle;
+char nflag;
+
+/*
+ * We suck in lots of fsck code, and just pick & choose the stuff we want.
+ *
+ * fsreadfd is set up to read from the file system, fswritefd to write to
+ * the file system.
+ */
+int
+main(int argc, char *argv[])
+{
+ int ch, rval;
+ char *fsys = NULL;
+
+ while (-1 != (ch = getopt(argc, argv, "fdr"))) {
+ switch (ch) {
+ case 'f':
+ /* The -f option is left for historical
+ * reasons and has no meaning.
+ */
+ break;
+ case 'd':
+ debug++;
+ break;
+ case 'r':
+ nflag++; /* "no" in fsck, readonly for us */
+ break;
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc != 1)
+ usage();
+ else
+ fsys = argv[0];
+
+ sblock_init();
+ if (!setup(fsys))
+ errx(1, "cannot set up file system `%s'", fsys);
+ printf("%s file system `%s'\nLast Mounted on %s\n",
+ nflag? "Examining": "Editing", fsys, sblock.fs_fsmnt);
+ rval = cmdloop();
+ if (!nflag) {
+ sblock.fs_clean = 0; /* mark it dirty */
+ sbdirty();
+ ckfini(0);
+ printf("*** FILE SYSTEM MARKED DIRTY\n");
+ printf("*** BE SURE TO RUN FSCK TO CLEAN UP ANY DAMAGE\n");
+ printf("*** IF IT WAS MOUNTED, RE-MOUNT WITH -u -o reload\n");
+ }
+ exit(rval);
+}
+
+#define CMDFUNC(func) int func(int argc, char *argv[])
+#define CMDFUNCSTART(func) int func(int argc, char *argv[])
+
+CMDFUNC(helpfn);
+CMDFUNC(focus); /* focus on inode */
+CMDFUNC(active); /* print active inode */
+CMDFUNC(blocks); /* print blocks for active inode */
+CMDFUNC(focusname); /* focus by name */
+CMDFUNC(zapi); /* clear inode */
+CMDFUNC(uplink); /* incr link */
+CMDFUNC(downlink); /* decr link */
+CMDFUNC(linkcount); /* set link count */
+CMDFUNC(quit); /* quit */
+CMDFUNC(findblk); /* find block */
+CMDFUNC(ls); /* list directory */
+CMDFUNC(rm); /* remove name */
+CMDFUNC(ln); /* add name */
+CMDFUNC(newtype); /* change type */
+CMDFUNC(chmode); /* change mode */
+CMDFUNC(chlen); /* change length */
+CMDFUNC(chaflags); /* change flags */
+CMDFUNC(chgen); /* change generation */
+CMDFUNC(chowner); /* change owner */
+CMDFUNC(chgroup); /* Change group */
+CMDFUNC(back); /* pop back to last ino */
+CMDFUNC(chbtime); /* Change btime */
+CMDFUNC(chmtime); /* Change mtime */
+CMDFUNC(chctime); /* Change ctime */
+CMDFUNC(chatime); /* Change atime */
+CMDFUNC(chinum); /* Change inode # of dirent */
+CMDFUNC(chname); /* Change dirname of dirent */
+
+struct cmdtable cmds[] = {
+ { "help", "Print out help", 1, 1, FL_RO, helpfn },
+ { "?", "Print out help", 1, 1, FL_RO, helpfn },
+ { "inode", "Set active inode to INUM", 2, 2, FL_RO, focus },
+ { "clri", "Clear inode INUM", 2, 2, FL_WR, zapi },
+ { "lookup", "Set active inode by looking up NAME", 2, 2, FL_RO | FL_ST, focusname },
+ { "cd", "Set active inode by looking up NAME", 2, 2, FL_RO | FL_ST, focusname },
+ { "back", "Go to previous active inode", 1, 1, FL_RO, back },
+ { "active", "Print active inode", 1, 1, FL_RO, active },
+ { "print", "Print active inode", 1, 1, FL_RO, active },
+ { "blocks", "Print block numbers of active inode", 1, 1, FL_RO, blocks },
+ { "uplink", "Increment link count", 1, 1, FL_WR, uplink },
+ { "downlink", "Decrement link count", 1, 1, FL_WR, downlink },
+ { "linkcount", "Set link count to COUNT", 2, 2, FL_WR, linkcount },
+ { "findblk", "Find inode owning disk block(s)", 2, 33, FL_RO, findblk},
+ { "ls", "List current inode as directory", 1, 1, FL_RO, ls },
+ { "rm", "Remove NAME from current inode directory", 2, 2, FL_WR | FL_ST, rm },
+ { "del", "Remove NAME from current inode directory", 2, 2, FL_WR | FL_ST, rm },
+ { "ln", "Hardlink INO into current inode directory as NAME", 3, 3, FL_WR | FL_ST, ln },
+ { "chinum", "Change dir entry number INDEX to INUM", 3, 3, FL_WR, chinum },
+ { "chname", "Change dir entry number INDEX to NAME", 3, 3, FL_WR | FL_ST, chname },
+ { "chtype", "Change type of current inode to TYPE", 2, 2, FL_WR, newtype },
+ { "chmod", "Change mode of current inode to MODE", 2, 2, FL_WR, chmode },
+ { "chlen", "Change length of current inode to LENGTH", 2, 2, FL_WR, chlen },
+ { "chown", "Change owner of current inode to OWNER", 2, 2, FL_WR, chowner },
+ { "chgrp", "Change group of current inode to GROUP", 2, 2, FL_WR, chgroup },
+ { "chflags", "Change flags of current inode to FLAGS", 2, 2, FL_WR, chaflags },
+ { "chgen", "Change generation number of current inode to GEN", 2, 2, FL_WR, chgen },
+ { "btime", "Change btime of current inode to BTIME", 2, 2, FL_WR, chbtime },
+ { "mtime", "Change mtime of current inode to MTIME", 2, 2, FL_WR, chmtime },
+ { "ctime", "Change ctime of current inode to CTIME", 2, 2, FL_WR, chctime },
+ { "atime", "Change atime of current inode to ATIME", 2, 2, FL_WR, chatime },
+ { "quit", "Exit", 1, 1, FL_RO, quit },
+ { "q", "Exit", 1, 1, FL_RO, quit },
+ { "exit", "Exit", 1, 1, FL_RO, quit },
+ { NULL, 0, 0, 0, 0, NULL },
+};
+
+int
+helpfn(int argc, char *argv[])
+{
+ struct cmdtable *cmdtp;
+
+ printf("Commands are:\n%-10s %5s %5s %s\n",
+ "command", "min args", "max args", "what");
+
+ for (cmdtp = cmds; cmdtp->cmd; cmdtp++)
+ printf("%-10s %5u %5u %s\n",
+ cmdtp->cmd, cmdtp->minargc-1, cmdtp->maxargc-1, cmdtp->helptxt);
+ return 0;
+}
+
+char *
+prompt(EditLine *el)
+{
+ static char pstring[64];
+ snprintf(pstring, sizeof(pstring), "fsdb (inum: %ju)> ",
+ (uintmax_t)curinum);
+ return pstring;
+}
+
+
+int
+cmdloop(void)
+{
+ char *line;
+ const char *elline;
+ int cmd_argc, rval = 0, known;
+#define scratch known
+ char **cmd_argv;
+ struct cmdtable *cmdp;
+ History *hist;
+ EditLine *elptr;
+ HistEvent he;
+
+ curinode = ginode(ROOTINO);
+ curinum = ROOTINO;
+ printactive(0);
+
+ hist = history_init();
+ history(hist, &he, H_SETSIZE, 100); /* 100 elt history buffer */
+
+ elptr = el_init("fsdb", stdin, stdout, stderr);
+ el_set(elptr, EL_EDITOR, "emacs");
+ el_set(elptr, EL_PROMPT, prompt);
+ el_set(elptr, EL_HIST, history, hist);
+ el_source(elptr, NULL);
+
+ while ((elline = el_gets(elptr, &scratch)) != NULL && scratch != 0) {
+ if (debug)
+ printf("command `%s'\n", elline);
+
+ history(hist, &he, H_ENTER, elline);
+
+ line = strdup(elline);
+ cmd_argv = crack(line, &cmd_argc);
+ /*
+ * el_parse returns -1 to signal that it's not been handled
+ * internally.
+ */
+ if (el_parse(elptr, cmd_argc, (const char **)cmd_argv) != -1)
+ continue;
+ if (cmd_argc) {
+ known = 0;
+ for (cmdp = cmds; cmdp->cmd; cmdp++) {
+ if (!strcmp(cmdp->cmd, cmd_argv[0])) {
+ if ((cmdp->flags & FL_WR) == FL_WR && nflag)
+ warnx("`%s' requires write access", cmd_argv[0]),
+ rval = 1;
+ else if (cmd_argc >= cmdp->minargc &&
+ cmd_argc <= cmdp->maxargc)
+ rval = (*cmdp->handler)(cmd_argc, cmd_argv);
+ else if (cmd_argc >= cmdp->minargc &&
+ (cmdp->flags & FL_ST) == FL_ST) {
+ strcpy(line, elline);
+ cmd_argv = recrack(line, &cmd_argc, cmdp->maxargc);
+ rval = (*cmdp->handler)(cmd_argc, cmd_argv);
+ } else
+ rval = argcount(cmdp, cmd_argc, cmd_argv);
+ known = 1;
+ break;
+ }
+ }
+ if (!known)
+ warnx("unknown command `%s'", cmd_argv[0]), rval = 1;
+ } else
+ rval = 0;
+ free(line);
+ if (rval < 0)
+ /* user typed "quit" */
+ return 0;
+ if (rval)
+ warnx("rval was %d", rval);
+ }
+ el_end(elptr);
+ history_end(hist);
+ return rval;
+}
+
+union dinode *curinode;
+ino_t curinum, ocurrent;
+
+#define GETINUM(ac,inum) inum = strtoul(argv[ac], &cp, 0); \
+ if (inum < ROOTINO || inum > maxino || cp == argv[ac] || *cp != '\0' ) { \
+ printf("inode %ju out of range; range is [%ju,%ju]\n", \
+ (uintmax_t)inum, (uintmax_t)ROOTINO, (uintmax_t)maxino); \
+ return 1; \
+ }
+
+/*
+ * Focus on given inode number
+ */
+CMDFUNCSTART(focus)
+{
+ ino_t inum;
+ char *cp;
+
+ GETINUM(1,inum);
+ curinode = ginode(inum);
+ ocurrent = curinum;
+ curinum = inum;
+ printactive(0);
+ return 0;
+}
+
+CMDFUNCSTART(back)
+{
+ curinum = ocurrent;
+ curinode = ginode(curinum);
+ printactive(0);
+ return 0;
+}
+
+CMDFUNCSTART(zapi)
+{
+ ino_t inum;
+ union dinode *dp;
+ char *cp;
+
+ GETINUM(1,inum);
+ dp = ginode(inum);
+ clearinode(dp);
+ inodirty();
+ if (curinode) /* re-set after potential change */
+ curinode = ginode(curinum);
+ return 0;
+}
+
+CMDFUNCSTART(active)
+{
+ printactive(0);
+ return 0;
+}
+
+CMDFUNCSTART(blocks)
+{
+ printactive(1);
+ return 0;
+}
+
+CMDFUNCSTART(quit)
+{
+ return -1;
+}
+
+CMDFUNCSTART(uplink)
+{
+ if (!checkactive())
+ return 1;
+ DIP_SET(curinode, di_nlink, DIP(curinode, di_nlink) + 1);
+ printf("inode %ju link count now %d\n",
+ (uintmax_t)curinum, DIP(curinode, di_nlink));
+ inodirty();
+ return 0;
+}
+
+CMDFUNCSTART(downlink)
+{
+ if (!checkactive())
+ return 1;
+ DIP_SET(curinode, di_nlink, DIP(curinode, di_nlink) - 1);
+ printf("inode %ju link count now %d\n",
+ (uintmax_t)curinum, DIP(curinode, di_nlink));
+ inodirty();
+ return 0;
+}
+
+const char *typename[] = {
+ "unknown",
+ "fifo",
+ "char special",
+ "unregistered #3",
+ "directory",
+ "unregistered #5",
+ "blk special",
+ "unregistered #7",
+ "regular",
+ "unregistered #9",
+ "symlink",
+ "unregistered #11",
+ "socket",
+ "unregistered #13",
+ "whiteout",
+};
+
+int diroff;
+int slot;
+
+int
+scannames(struct inodesc *idesc)
+{
+ struct direct *dirp = idesc->id_dirp;
+
+ printf("slot %d off %d ino %d reclen %d: %s, `%.*s'\n",
+ slot++, diroff, dirp->d_ino, dirp->d_reclen,
+ typename[dirp->d_type], dirp->d_namlen, dirp->d_name);
+ diroff += dirp->d_reclen;
+ return (KEEPON);
+}
+
+CMDFUNCSTART(ls)
+{
+ struct inodesc idesc;
+ checkactivedir(); /* let it go on anyway */
+
+ slot = 0;
+ diroff = 0;
+ idesc.id_number = curinum;
+ idesc.id_func = scannames;
+ idesc.id_type = DATA;
+ idesc.id_fix = IGNORE;
+ ckinode(curinode, &idesc);
+ curinode = ginode(curinum);
+
+ return 0;
+}
+
+static int findblk_numtofind;
+static int wantedblksize;
+
+CMDFUNCSTART(findblk)
+{
+ ino_t inum, inosused;
+ uint32_t *wantedblk32;
+ uint64_t *wantedblk64;
+ struct bufarea *cgbp;
+ struct cg *cgp;
+ int c, i, is_ufs2;
+
+ wantedblksize = (argc - 1);
+ is_ufs2 = sblock.fs_magic == FS_UFS2_MAGIC;
+ ocurrent = curinum;
+
+ if (is_ufs2) {
+ wantedblk64 = calloc(wantedblksize, sizeof(uint64_t));
+ if (wantedblk64 == NULL)
+ err(1, "malloc");
+ for (i = 1; i < argc; i++)
+ wantedblk64[i - 1] = dbtofsb(&sblock, strtoull(argv[i], NULL, 0));
+ } else {
+ wantedblk32 = calloc(wantedblksize, sizeof(uint32_t));
+ if (wantedblk32 == NULL)
+ err(1, "malloc");
+ for (i = 1; i < argc; i++)
+ wantedblk32[i - 1] = dbtofsb(&sblock, strtoull(argv[i], NULL, 0));
+ }
+ findblk_numtofind = wantedblksize;
+ /*
+ * sblock.fs_ncg holds a number of cylinder groups.
+ * Iterate over all cylinder groups.
+ */
+ for (c = 0; c < sblock.fs_ncg; c++) {
+ /*
+ * sblock.fs_ipg holds a number of inodes per cylinder group.
+ * Calculate a highest inode number for a given cylinder group.
+ */
+ inum = c * sblock.fs_ipg;
+ /* Read cylinder group. */
+ cgbp = cgget(c);
+ cgp = cgbp->b_un.b_cg;
+ /*
+ * Get a highest used inode number for a given cylinder group.
+ * For UFS1 all inodes initialized at the newfs stage.
+ */
+ if (is_ufs2)
+ inosused = cgp->cg_initediblk;
+ else
+ inosused = sblock.fs_ipg;
+
+ for (; inosused > 0; inum++, inosused--) {
+ /* Skip magic inodes: 0, WINO, ROOTINO. */
+ if (inum < ROOTINO)
+ continue;
+ /*
+ * Check if the block we are looking for is just an inode block.
+ *
+ * ino_to_fsba() - get block containing inode from its number.
+ * INOPB() - get a number of inodes in one disk block.
+ */
+ if (is_ufs2 ?
+ compare_blk64(wantedblk64, ino_to_fsba(&sblock, inum)) :
+ compare_blk32(wantedblk32, ino_to_fsba(&sblock, inum))) {
+ printf("block %llu: inode block (%ju-%ju)\n",
+ (unsigned long long)fsbtodb(&sblock,
+ ino_to_fsba(&sblock, inum)),
+ (uintmax_t)(inum / INOPB(&sblock)) * INOPB(&sblock),
+ (uintmax_t)(inum / INOPB(&sblock) + 1) * INOPB(&sblock));
+ findblk_numtofind--;
+ if (findblk_numtofind == 0)
+ goto end;
+ }
+ /* Get on-disk inode aka dinode. */
+ curinum = inum;
+ curinode = ginode(inum);
+ /* Find IFLNK dinode with allocated data blocks. */
+ switch (DIP(curinode, di_mode) & IFMT) {
+ case IFDIR:
+ case IFREG:
+ if (DIP(curinode, di_blocks) == 0)
+ continue;
+ break;
+ case IFLNK:
+ {
+ uint64_t size = DIP(curinode, di_size);
+ if (size > 0 && size < sblock.fs_maxsymlinklen &&
+ DIP(curinode, di_blocks) == 0)
+ continue;
+ else
+ break;
+ }
+ default:
+ continue;
+ }
+ /* Look through direct data blocks. */
+ if (is_ufs2 ?
+ find_blks64(curinode->dp2.di_db, NDADDR, wantedblk64) :
+ find_blks32(curinode->dp1.di_db, NDADDR, wantedblk32))
+ goto end;
+ for (i = 0; i < NIADDR; i++) {
+ /*
+ * Does the block we are looking for belongs to the
+ * indirect blocks?
+ */
+ if (is_ufs2 ?
+ compare_blk64(wantedblk64, curinode->dp2.di_ib[i]) :
+ compare_blk32(wantedblk32, curinode->dp1.di_ib[i]))
+ if (founddatablk(is_ufs2 ? curinode->dp2.di_ib[i] :
+ curinode->dp1.di_ib[i]))
+ goto end;
+ /*
+ * Search through indirect, double and triple indirect
+ * data blocks.
+ */
+ if (is_ufs2 ? (curinode->dp2.di_ib[i] != 0) :
+ (curinode->dp1.di_ib[i] != 0))
+ if (is_ufs2 ?
+ find_indirblks64(curinode->dp2.di_ib[i], i,
+ wantedblk64) :
+ find_indirblks32(curinode->dp1.di_ib[i], i,
+ wantedblk32))
+ goto end;
+ }
+ }
+ }
+end:
+ curinum = ocurrent;
+ curinode = ginode(curinum);
+ return 0;
+}
+
+static int
+compare_blk32(uint32_t *wantedblk, uint32_t curblk)
+{
+ int i;
+
+ for (i = 0; i < wantedblksize; i++) {
+ if (wantedblk[i] != 0 && wantedblk[i] == curblk) {
+ wantedblk[i] = 0;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int
+compare_blk64(uint64_t *wantedblk, uint64_t curblk)
+{
+ int i;
+
+ for (i = 0; i < wantedblksize; i++) {
+ if (wantedblk[i] != 0 && wantedblk[i] == curblk) {
+ wantedblk[i] = 0;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int
+founddatablk(uint64_t blk)
+{
+
+ printf("%llu: data block of inode %ju\n",
+ (unsigned long long)fsbtodb(&sblock, blk), (uintmax_t)curinum);
+ findblk_numtofind--;
+ if (findblk_numtofind == 0)
+ return 1;
+ return 0;
+}
+
+static int
+find_blks32(uint32_t *buf, int size, uint32_t *wantedblk)
+{
+ int blk;
+ for (blk = 0; blk < size; blk++) {
+ if (buf[blk] == 0)
+ continue;
+ if (compare_blk32(wantedblk, buf[blk])) {
+ if (founddatablk(buf[blk]))
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int
+find_indirblks32(uint32_t blk, int ind_level, uint32_t *wantedblk)
+{
+#define MAXNINDIR (MAXBSIZE / sizeof(uint32_t))
+ uint32_t idblk[MAXNINDIR];
+ int i;
+
+ blread(fsreadfd, (char *)idblk, fsbtodb(&sblock, blk), (int)sblock.fs_bsize);
+ if (ind_level <= 0) {
+ if (find_blks32(idblk, sblock.fs_bsize / sizeof(uint32_t), wantedblk))
+ return 1;
+ } else {
+ ind_level--;
+ for (i = 0; i < sblock.fs_bsize / sizeof(uint32_t); i++) {
+ if (compare_blk32(wantedblk, idblk[i])) {
+ if (founddatablk(idblk[i]))
+ return 1;
+ }
+ if (idblk[i] != 0)
+ if (find_indirblks32(idblk[i], ind_level, wantedblk))
+ return 1;
+ }
+ }
+#undef MAXNINDIR
+ return 0;
+}
+
+static int
+find_blks64(uint64_t *buf, int size, uint64_t *wantedblk)
+{
+ int blk;
+ for (blk = 0; blk < size; blk++) {
+ if (buf[blk] == 0)
+ continue;
+ if (compare_blk64(wantedblk, buf[blk])) {
+ if (founddatablk(buf[blk]))
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int
+find_indirblks64(uint64_t blk, int ind_level, uint64_t *wantedblk)
+{
+#define MAXNINDIR (MAXBSIZE / sizeof(uint64_t))
+ uint64_t idblk[MAXNINDIR];
+ int i;
+
+ blread(fsreadfd, (char *)idblk, fsbtodb(&sblock, blk), (int)sblock.fs_bsize);
+ if (ind_level <= 0) {
+ if (find_blks64(idblk, sblock.fs_bsize / sizeof(uint64_t), wantedblk))
+ return 1;
+ } else {
+ ind_level--;
+ for (i = 0; i < sblock.fs_bsize / sizeof(uint64_t); i++) {
+ if (compare_blk64(wantedblk, idblk[i])) {
+ if (founddatablk(idblk[i]))
+ return 1;
+ }
+ if (idblk[i] != 0)
+ if (find_indirblks64(idblk[i], ind_level, wantedblk))
+ return 1;
+ }
+ }
+#undef MAXNINDIR
+ return 0;
+}
+
+int findino(struct inodesc *idesc); /* from fsck */
+static int dolookup(char *name);
+
+static int
+dolookup(char *name)
+{
+ struct inodesc idesc;
+
+ if (!checkactivedir())
+ return 0;
+ idesc.id_number = curinum;
+ idesc.id_func = findino;
+ idesc.id_name = name;
+ idesc.id_type = DATA;
+ idesc.id_fix = IGNORE;
+ if (ckinode(curinode, &idesc) & FOUND) {
+ curinum = idesc.id_parent;
+ curinode = ginode(curinum);
+ printactive(0);
+ return 1;
+ } else {
+ warnx("name `%s' not found in current inode directory", name);
+ return 0;
+ }
+}
+
+CMDFUNCSTART(focusname)
+{
+ char *p, *val;
+
+ if (!checkactive())
+ return 1;
+
+ ocurrent = curinum;
+
+ if (argv[1][0] == '/') {
+ curinum = ROOTINO;
+ curinode = ginode(ROOTINO);
+ } else {
+ if (!checkactivedir())
+ return 1;
+ }
+ for (p = argv[1]; p != NULL;) {
+ while ((val = strsep(&p, "/")) != NULL && *val == '\0');
+ if (val) {
+ printf("component `%s': ", val);
+ fflush(stdout);
+ if (!dolookup(val)) {
+ curinode = ginode(curinum);
+ return(1);
+ }
+ }
+ }
+ return 0;
+}
+
+CMDFUNCSTART(ln)
+{
+ ino_t inum;
+ int rval;
+ char *cp;
+
+ GETINUM(1,inum);
+
+ if (!checkactivedir())
+ return 1;
+ rval = makeentry(curinum, inum, argv[2]);
+ if (rval)
+ printf("Ino %ju entered as `%s'\n", (uintmax_t)inum, argv[2]);
+ else
+ printf("could not enter name? weird.\n");
+ curinode = ginode(curinum);
+ return rval;
+}
+
+CMDFUNCSTART(rm)
+{
+ int rval;
+
+ if (!checkactivedir())
+ return 1;
+ rval = changeino(curinum, argv[1], 0);
+ if (rval & ALTERED) {
+ printf("Name `%s' removed\n", argv[1]);
+ return 0;
+ } else {
+ printf("could not remove name ('%s')? weird.\n", argv[1]);
+ return 1;
+ }
+}
+
+long slotcount, desired;
+
+int
+chinumfunc(struct inodesc *idesc)
+{
+ struct direct *dirp = idesc->id_dirp;
+
+ if (slotcount++ == desired) {
+ dirp->d_ino = idesc->id_parent;
+ return STOP|ALTERED|FOUND;
+ }
+ return KEEPON;
+}
+
+CMDFUNCSTART(chinum)
+{
+ char *cp;
+ ino_t inum;
+ struct inodesc idesc;
+
+ slotcount = 0;
+ if (!checkactivedir())
+ return 1;
+ GETINUM(2,inum);
+
+ desired = strtol(argv[1], &cp, 0);
+ if (cp == argv[1] || *cp != '\0' || desired < 0) {
+ printf("invalid slot number `%s'\n", argv[1]);
+ return 1;
+ }
+
+ idesc.id_number = curinum;
+ idesc.id_func = chinumfunc;
+ idesc.id_fix = IGNORE;
+ idesc.id_type = DATA;
+ idesc.id_parent = inum; /* XXX convenient hiding place */
+
+ if (ckinode(curinode, &idesc) & FOUND)
+ return 0;
+ else {
+ warnx("no %sth slot in current directory", argv[1]);
+ return 1;
+ }
+}
+
+int
+chnamefunc(struct inodesc *idesc)
+{
+ struct direct *dirp = idesc->id_dirp;
+ struct direct testdir;
+
+ if (slotcount++ == desired) {
+ /* will name fit? */
+ testdir.d_namlen = strlen(idesc->id_name);
+ if (DIRSIZ(NEWDIRFMT, &testdir) <= dirp->d_reclen) {
+ dirp->d_namlen = testdir.d_namlen;
+ strcpy(dirp->d_name, idesc->id_name);
+ return STOP|ALTERED|FOUND;
+ } else
+ return STOP|FOUND; /* won't fit, so give up */
+ }
+ return KEEPON;
+}
+
+CMDFUNCSTART(chname)
+{
+ int rval;
+ char *cp;
+ struct inodesc idesc;
+
+ slotcount = 0;
+ if (!checkactivedir())
+ return 1;
+
+ desired = strtoul(argv[1], &cp, 0);
+ if (cp == argv[1] || *cp != '\0') {
+ printf("invalid slot number `%s'\n", argv[1]);
+ return 1;
+ }
+
+ idesc.id_number = curinum;
+ idesc.id_func = chnamefunc;
+ idesc.id_fix = IGNORE;
+ idesc.id_type = DATA;
+ idesc.id_name = argv[2];
+
+ rval = ckinode(curinode, &idesc);
+ if ((rval & (FOUND|ALTERED)) == (FOUND|ALTERED))
+ return 0;
+ else if (rval & FOUND) {
+ warnx("new name `%s' does not fit in slot %s\n", argv[2], argv[1]);
+ return 1;
+ } else {
+ warnx("no %sth slot in current directory", argv[1]);
+ return 1;
+ }
+}
+
+struct typemap {
+ const char *typename;
+ int typebits;
+} typenamemap[] = {
+ {"file", IFREG},
+ {"dir", IFDIR},
+ {"socket", IFSOCK},
+ {"fifo", IFIFO},
+};
+
+CMDFUNCSTART(newtype)
+{
+ int type;
+ struct typemap *tp;
+
+ if (!checkactive())
+ return 1;
+ type = DIP(curinode, di_mode) & IFMT;
+ for (tp = typenamemap;
+ tp < &typenamemap[sizeof(typenamemap)/sizeof(*typenamemap)];
+ tp++) {
+ if (!strcmp(argv[1], tp->typename)) {
+ printf("setting type to %s\n", tp->typename);
+ type = tp->typebits;
+ break;
+ }
+ }
+ if (tp == &typenamemap[sizeof(typenamemap)/sizeof(*typenamemap)]) {
+ warnx("type `%s' not known", argv[1]);
+ warnx("try one of `file', `dir', `socket', `fifo'");
+ return 1;
+ }
+ DIP_SET(curinode, di_mode, DIP(curinode, di_mode) & ~IFMT);
+ DIP_SET(curinode, di_mode, DIP(curinode, di_mode) | type);
+ inodirty();
+ printactive(0);
+ return 0;
+}
+
+CMDFUNCSTART(chlen)
+{
+ int rval = 1;
+ long len;
+ char *cp;
+
+ if (!checkactive())
+ return 1;
+
+ len = strtol(argv[1], &cp, 0);
+ if (cp == argv[1] || *cp != '\0' || len < 0) {
+ warnx("bad length `%s'", argv[1]);
+ return 1;
+ }
+
+ DIP_SET(curinode, di_size, len);
+ inodirty();
+ printactive(0);
+ return rval;
+}
+
+CMDFUNCSTART(chmode)
+{
+ int rval = 1;
+ long modebits;
+ char *cp;
+
+ if (!checkactive())
+ return 1;
+
+ modebits = strtol(argv[1], &cp, 8);
+ if (cp == argv[1] || *cp != '\0' || (modebits & ~07777)) {
+ warnx("bad modebits `%s'", argv[1]);
+ return 1;
+ }
+
+ DIP_SET(curinode, di_mode, DIP(curinode, di_mode) & ~07777);
+ DIP_SET(curinode, di_mode, DIP(curinode, di_mode) | modebits);
+ inodirty();
+ printactive(0);
+ return rval;
+}
+
+CMDFUNCSTART(chaflags)
+{
+ int rval = 1;
+ u_long flags;
+ char *cp;
+
+ if (!checkactive())
+ return 1;
+
+ flags = strtoul(argv[1], &cp, 0);
+ if (cp == argv[1] || *cp != '\0' ) {
+ warnx("bad flags `%s'", argv[1]);
+ return 1;
+ }
+
+ if (flags > UINT_MAX) {
+ warnx("flags set beyond 32-bit range of field (%lx)\n", flags);
+ return(1);
+ }
+ DIP_SET(curinode, di_flags, flags);
+ inodirty();
+ printactive(0);
+ return rval;
+}
+
+CMDFUNCSTART(chgen)
+{
+ int rval = 1;
+ long gen;
+ char *cp;
+
+ if (!checkactive())
+ return 1;
+
+ gen = strtol(argv[1], &cp, 0);
+ if (cp == argv[1] || *cp != '\0' ) {
+ warnx("bad gen `%s'", argv[1]);
+ return 1;
+ }
+
+ if (gen > INT_MAX || gen < INT_MIN) {
+ warnx("gen set beyond 32-bit range of field (%lx)\n", gen);
+ return(1);
+ }
+ DIP_SET(curinode, di_gen, gen);
+ inodirty();
+ printactive(0);
+ return rval;
+}
+
+CMDFUNCSTART(linkcount)
+{
+ int rval = 1;
+ int lcnt;
+ char *cp;
+
+ if (!checkactive())
+ return 1;
+
+ lcnt = strtol(argv[1], &cp, 0);
+ if (cp == argv[1] || *cp != '\0' ) {
+ warnx("bad link count `%s'", argv[1]);
+ return 1;
+ }
+ if (lcnt > USHRT_MAX || lcnt < 0) {
+ warnx("max link count is %d\n", USHRT_MAX);
+ return 1;
+ }
+
+ DIP_SET(curinode, di_nlink, lcnt);
+ inodirty();
+ printactive(0);
+ return rval;
+}
+
+CMDFUNCSTART(chowner)
+{
+ int rval = 1;
+ unsigned long uid;
+ char *cp;
+ struct passwd *pwd;
+
+ if (!checkactive())
+ return 1;
+
+ uid = strtoul(argv[1], &cp, 0);
+ if (cp == argv[1] || *cp != '\0' ) {
+ /* try looking up name */
+ if ((pwd = getpwnam(argv[1]))) {
+ uid = pwd->pw_uid;
+ } else {
+ warnx("bad uid `%s'", argv[1]);
+ return 1;
+ }
+ }
+
+ DIP_SET(curinode, di_uid, uid);
+ inodirty();
+ printactive(0);
+ return rval;
+}
+
+CMDFUNCSTART(chgroup)
+{
+ int rval = 1;
+ unsigned long gid;
+ char *cp;
+ struct group *grp;
+
+ if (!checkactive())
+ return 1;
+
+ gid = strtoul(argv[1], &cp, 0);
+ if (cp == argv[1] || *cp != '\0' ) {
+ if ((grp = getgrnam(argv[1]))) {
+ gid = grp->gr_gid;
+ } else {
+ warnx("bad gid `%s'", argv[1]);
+ return 1;
+ }
+ }
+
+ DIP_SET(curinode, di_gid, gid);
+ inodirty();
+ printactive(0);
+ return rval;
+}
+
+int
+dotime(char *name, time_t *secp, int32_t *nsecp)
+{
+ char *p, *val;
+ struct tm t;
+ int32_t nsec;
+ p = strchr(name, '.');
+ if (p) {
+ *p = '\0';
+ nsec = strtoul(++p, &val, 0);
+ if (val == p || *val != '\0' || nsec >= 1000000000 || nsec < 0) {
+ warnx("invalid nanoseconds");
+ goto badformat;
+ }
+ } else
+ nsec = 0;
+ if (strlen(name) != 14) {
+badformat:
+ warnx("date format: YYYYMMDDHHMMSS[.nsec]");
+ return 1;
+ }
+ *nsecp = nsec;
+
+ for (p = name; *p; p++)
+ if (*p < '0' || *p > '9')
+ goto badformat;
+
+ p = name;
+#define VAL() ((*p++) - '0')
+ t.tm_year = VAL();
+ t.tm_year = VAL() + t.tm_year * 10;
+ t.tm_year = VAL() + t.tm_year * 10;
+ t.tm_year = VAL() + t.tm_year * 10 - 1900;
+ t.tm_mon = VAL();
+ t.tm_mon = VAL() + t.tm_mon * 10 - 1;
+ t.tm_mday = VAL();
+ t.tm_mday = VAL() + t.tm_mday * 10;
+ t.tm_hour = VAL();
+ t.tm_hour = VAL() + t.tm_hour * 10;
+ t.tm_min = VAL();
+ t.tm_min = VAL() + t.tm_min * 10;
+ t.tm_sec = VAL();
+ t.tm_sec = VAL() + t.tm_sec * 10;
+ t.tm_isdst = -1;
+
+ *secp = mktime(&t);
+ if (*secp == -1) {
+ warnx("date/time out of range");
+ return 1;
+ }
+ return 0;
+}
+
+CMDFUNCSTART(chbtime)
+{
+ time_t secs;
+ int32_t nsecs;
+
+ if (dotime(argv[1], &secs, &nsecs))
+ return 1;
+ if (sblock.fs_magic == FS_UFS1_MAGIC)
+ return 1;
+ curinode->dp2.di_birthtime = _time_to_time64(secs);
+ curinode->dp2.di_birthnsec = nsecs;
+ inodirty();
+ printactive(0);
+ return 0;
+}
+
+CMDFUNCSTART(chmtime)
+{
+ time_t secs;
+ int32_t nsecs;
+
+ if (dotime(argv[1], &secs, &nsecs))
+ return 1;
+ if (sblock.fs_magic == FS_UFS1_MAGIC)
+ curinode->dp1.di_mtime = _time_to_time32(secs);
+ else
+ curinode->dp2.di_mtime = _time_to_time64(secs);
+ DIP_SET(curinode, di_mtimensec, nsecs);
+ inodirty();
+ printactive(0);
+ return 0;
+}
+
+CMDFUNCSTART(chatime)
+{
+ time_t secs;
+ int32_t nsecs;
+
+ if (dotime(argv[1], &secs, &nsecs))
+ return 1;
+ if (sblock.fs_magic == FS_UFS1_MAGIC)
+ curinode->dp1.di_atime = _time_to_time32(secs);
+ else
+ curinode->dp2.di_atime = _time_to_time64(secs);
+ DIP_SET(curinode, di_atimensec, nsecs);
+ inodirty();
+ printactive(0);
+ return 0;
+}
+
+CMDFUNCSTART(chctime)
+{
+ time_t secs;
+ int32_t nsecs;
+
+ if (dotime(argv[1], &secs, &nsecs))
+ return 1;
+ if (sblock.fs_magic == FS_UFS1_MAGIC)
+ curinode->dp1.di_ctime = _time_to_time32(secs);
+ else
+ curinode->dp2.di_ctime = _time_to_time64(secs);
+ DIP_SET(curinode, di_ctimensec, nsecs);
+ inodirty();
+ printactive(0);
+ return 0;
+}
diff --git a/sbin/fsdb/fsdb.h b/sbin/fsdb/fsdb.h
new file mode 100644
index 0000000..ddd1515
--- /dev/null
+++ b/sbin/fsdb/fsdb.h
@@ -0,0 +1,62 @@
+/* $NetBSD: fsdb.h,v 1.2 1995/10/08 23:18:11 thorpej Exp $ */
+
+/*
+ * Copyright (c) 1995 John T. Kohl
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+extern int blread(int fd, char *buf, ufs2_daddr_t blk, long size);
+extern void rwerror(const char *mesg, ufs2_daddr_t blk);
+extern int reply(const char *question);
+
+extern long dev_bsize;
+extern long secsize;
+extern int fsmodified;
+extern int fsfd;
+
+struct cmdtable {
+ const char *cmd;
+ const char *helptxt;
+ unsigned int minargc;
+ unsigned int maxargc;
+ unsigned int flags;
+#define FL_RO 0x0000 /* for symmetry */
+#define FL_WR 0x0001 /* wants to write */
+#define FL_ST 0x0002 /* resplit final string if argc > maxargc */
+ int (*handler)(int argc, char *argv[]);
+};
+extern union dinode *curinode;
+extern ino_t curinum;
+
+int argcount(struct cmdtable *cmdp, int argc, char *argv[]);
+char **crack(char *line, int *argc);
+char **recrack(char *line, int *argc, int argc_max);
+void printstat(const char *cp, ino_t inum, union dinode *dp);
+int printactive(int doblocks);
+int checkactive(void);
+int checkactivedir(void);
diff --git a/sbin/fsdb/fsdbutil.c b/sbin/fsdb/fsdbutil.c
new file mode 100644
index 0000000..ba0b64e
--- /dev/null
+++ b/sbin/fsdb/fsdbutil.c
@@ -0,0 +1,374 @@
+/* $NetBSD: fsdbutil.c,v 1.2 1995/10/08 23:18:12 thorpej Exp $ */
+
+/*
+ * Copyright (c) 1995 John T. Kohl
+ * 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.
+ */
+
+#ifndef lint
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <ctype.h>
+#include <err.h>
+#include <grp.h>
+#include <pwd.h>
+#include <stdint.h>
+#include <string.h>
+#include <time.h>
+#include <timeconv.h>
+
+#include <ufs/ufs/dinode.h>
+#include <ufs/ffs/fs.h>
+
+#include <sys/ioctl.h>
+
+#include "fsdb.h"
+#include "fsck.h"
+
+static int charsperline(void);
+static void printindir(ufs2_daddr_t blk, int level, char *bufp);
+static void printblocks(ino_t inum, union dinode *dp);
+
+char **
+crack(char *line, int *argc)
+{
+ static char *argv[8];
+ int i;
+ char *p, *val;
+ for (p = line, i = 0; p != NULL && i < 8; i++) {
+ while ((val = strsep(&p, " \t\n")) != NULL && *val == '\0')
+ /**/;
+ if (val)
+ argv[i] = val;
+ else
+ break;
+ }
+ *argc = i;
+ return argv;
+}
+
+char **
+recrack(char *line, int *argc, int argc_max)
+{
+ static char *argv[8];
+ int i;
+ char *p, *val;
+ for (p = line, i = 0; p != NULL && i < 8 && i < argc_max - 1; i++) {
+ while ((val = strsep(&p, " \t\n")) != NULL && *val == '\0')
+ /**/;
+ if (val)
+ argv[i] = val;
+ else
+ break;
+ }
+ argv[i] = argv[i - 1] + strlen(argv[i - 1]) + 1;
+ argv[i][strcspn(argv[i], "\n")] = '\0';
+ *argc = i + 1;
+ return argv;
+}
+
+int
+argcount(struct cmdtable *cmdp, int argc, char *argv[])
+{
+ if (cmdp->minargc == cmdp->maxargc)
+ warnx("command `%s' takes %u arguments, got %u", cmdp->cmd,
+ cmdp->minargc-1, argc-1);
+ else
+ warnx("command `%s' takes from %u to %u arguments",
+ cmdp->cmd, cmdp->minargc-1, cmdp->maxargc-1);
+
+ warnx("usage: %s: %s", cmdp->cmd, cmdp->helptxt);
+ return 1;
+}
+
+void
+printstat(const char *cp, ino_t inum, union dinode *dp)
+{
+ struct group *grp;
+ struct passwd *pw;
+ ufs2_daddr_t blocks;
+ int64_t gen;
+ char *p;
+ time_t t;
+
+ printf("%s: ", cp);
+ switch (DIP(dp, di_mode) & IFMT) {
+ case IFDIR:
+ puts("directory");
+ break;
+ case IFREG:
+ puts("regular file");
+ break;
+ case IFBLK:
+ printf("block special (%#jx)", (uintmax_t)DIP(dp, di_rdev));
+ break;
+ case IFCHR:
+ printf("character special (%#jx)", DIP(dp, di_rdev));
+ break;
+ case IFLNK:
+ fputs("symlink",stdout);
+ if (DIP(dp, di_size) > 0 &&
+ DIP(dp, di_size) < sblock.fs_maxsymlinklen &&
+ DIP(dp, di_blocks) == 0) {
+ if (sblock.fs_magic == FS_UFS1_MAGIC)
+ p = (caddr_t)dp->dp1.di_db;
+ else
+ p = (caddr_t)dp->dp2.di_db;
+ printf(" to `%.*s'\n", (int) DIP(dp, di_size), p);
+ } else {
+ putchar('\n');
+ }
+ break;
+ case IFSOCK:
+ puts("socket");
+ break;
+ case IFIFO:
+ puts("fifo");
+ break;
+ }
+ printf("I=%ju MODE=%o SIZE=%ju", (uintmax_t)inum, DIP(dp, di_mode),
+ (uintmax_t)DIP(dp, di_size));
+ if (sblock.fs_magic != FS_UFS1_MAGIC) {
+ t = _time64_to_time(dp->dp2.di_birthtime);
+ p = ctime(&t);
+ printf("\n\tBTIME=%15.15s %4.4s [%d nsec]", &p[4], &p[20],
+ dp->dp2.di_birthnsec);
+ }
+ if (sblock.fs_magic == FS_UFS1_MAGIC)
+ t = _time32_to_time(dp->dp1.di_mtime);
+ else
+ t = _time64_to_time(dp->dp2.di_mtime);
+ p = ctime(&t);
+ printf("\n\tMTIME=%15.15s %4.4s [%d nsec]", &p[4], &p[20],
+ DIP(dp, di_mtimensec));
+ if (sblock.fs_magic == FS_UFS1_MAGIC)
+ t = _time32_to_time(dp->dp1.di_ctime);
+ else
+ t = _time64_to_time(dp->dp2.di_ctime);
+ p = ctime(&t);
+ printf("\n\tCTIME=%15.15s %4.4s [%d nsec]", &p[4], &p[20],
+ DIP(dp, di_ctimensec));
+ if (sblock.fs_magic == FS_UFS1_MAGIC)
+ t = _time32_to_time(dp->dp1.di_atime);
+ else
+ t = _time64_to_time(dp->dp2.di_atime);
+ p = ctime(&t);
+ printf("\n\tATIME=%15.15s %4.4s [%d nsec]\n", &p[4], &p[20],
+ DIP(dp, di_atimensec));
+
+ if ((pw = getpwuid(DIP(dp, di_uid))))
+ printf("OWNER=%s ", pw->pw_name);
+ else
+ printf("OWNUID=%u ", DIP(dp, di_uid));
+ if ((grp = getgrgid(DIP(dp, di_gid))))
+ printf("GRP=%s ", grp->gr_name);
+ else
+ printf("GID=%u ", DIP(dp, di_gid));
+
+ blocks = DIP(dp, di_blocks);
+ gen = DIP(dp, di_gen);
+ printf("LINKCNT=%d FLAGS=%#x BLKCNT=%jx GEN=%jx\n", DIP(dp, di_nlink),
+ DIP(dp, di_flags), (intmax_t)blocks, (intmax_t)gen);
+}
+
+
+/*
+ * Determine the number of characters in a
+ * single line.
+ */
+
+static int
+charsperline(void)
+{
+ int columns;
+ char *cp;
+ struct winsize ws;
+
+ columns = 0;
+ if (ioctl(0, TIOCGWINSZ, &ws) != -1)
+ columns = ws.ws_col;
+ if (columns == 0 && (cp = getenv("COLUMNS")))
+ columns = atoi(cp);
+ if (columns == 0)
+ columns = 80; /* last resort */
+ return (columns);
+}
+
+
+/*
+ * Recursively print a list of indirect blocks.
+ */
+static void
+printindir(ufs2_daddr_t blk, int level, char *bufp)
+{
+ struct bufarea buf, *bp;
+ char tempbuf[32]; /* enough to print an ufs2_daddr_t */
+ int i, j, cpl, charssofar;
+ ufs2_daddr_t blkno;
+
+ if (blk == 0)
+ return;
+ printf("%jd (%d) =>\n", (intmax_t)blk, level);
+ if (level == 0) {
+ /* for the final indirect level, don't use the cache */
+ bp = &buf;
+ bp->b_un.b_buf = bufp;
+ initbarea(bp, BT_UNKNOWN);
+
+ getblk(bp, blk, sblock.fs_bsize);
+ } else
+ bp = getdatablk(blk, sblock.fs_bsize, BT_UNKNOWN);
+
+ cpl = charsperline();
+ for (i = charssofar = 0; i < NINDIR(&sblock); i++) {
+ if (sblock.fs_magic == FS_UFS1_MAGIC)
+ blkno = bp->b_un.b_indir1[i];
+ else
+ blkno = bp->b_un.b_indir2[i];
+ if (blkno == 0)
+ continue;
+ j = sprintf(tempbuf, "%jd", (intmax_t)blkno);
+ if (level == 0) {
+ charssofar += j;
+ if (charssofar >= cpl - 2) {
+ putchar('\n');
+ charssofar = j;
+ }
+ }
+ fputs(tempbuf, stdout);
+ if (level == 0) {
+ printf(", ");
+ charssofar += 2;
+ } else {
+ printf(" =>\n");
+ printindir(blkno, level - 1, bufp);
+ printf("\n");
+ charssofar = 0;
+ }
+ }
+ if (level == 0)
+ putchar('\n');
+ return;
+}
+
+
+/*
+ * Print the block pointers for one inode.
+ */
+static void
+printblocks(ino_t inum, union dinode *dp)
+{
+ char *bufp;
+ int i, nfrags;
+ long ndb, offset;
+ ufs2_daddr_t blkno;
+
+ printf("Blocks for inode %ju:\n", (uintmax_t)inum);
+ printf("Direct blocks:\n");
+ ndb = howmany(DIP(dp, di_size), sblock.fs_bsize);
+ for (i = 0; i < NDADDR && i < ndb; i++) {
+ if (i > 0)
+ printf(", ");
+ blkno = DIP(dp, di_db[i]);
+ printf("%jd", (intmax_t)blkno);
+ }
+ if (ndb <= NDADDR) {
+ offset = blkoff(&sblock, DIP(dp, di_size));
+ if (offset != 0) {
+ nfrags = numfrags(&sblock, fragroundup(&sblock, offset));
+ printf(" (%d frag%s)", nfrags, nfrags > 1? "s": "");
+ }
+ }
+ putchar('\n');
+ if (ndb <= NDADDR)
+ return;
+
+ bufp = malloc((unsigned int)sblock.fs_bsize);
+ if (bufp == 0)
+ errx(EEXIT, "cannot allocate indirect block buffer");
+ printf("Indirect blocks:\n");
+ for (i = 0; i < NIADDR; i++)
+ printindir(DIP(dp, di_ib[i]), i, bufp);
+ free(bufp);
+}
+
+
+int
+checkactive(void)
+{
+ if (!curinode) {
+ warnx("no current inode\n");
+ return 0;
+ }
+ return 1;
+}
+
+int
+checkactivedir(void)
+{
+ if (!curinode) {
+ warnx("no current inode\n");
+ return 0;
+ }
+ if ((DIP(curinode, di_mode) & IFMT) != IFDIR) {
+ warnx("inode %ju not a directory", (uintmax_t)curinum);
+ return 0;
+ }
+ return 1;
+}
+
+int
+printactive(int doblocks)
+{
+ if (!checkactive())
+ return 1;
+ switch (DIP(curinode, di_mode) & IFMT) {
+ case IFDIR:
+ case IFREG:
+ case IFBLK:
+ case IFCHR:
+ case IFLNK:
+ case IFSOCK:
+ case IFIFO:
+ if (doblocks)
+ printblocks(curinum, curinode);
+ else
+ printstat("current inode", curinum, curinode);
+ break;
+ case 0:
+ printf("current inode %ju: unallocated inode\n", (uintmax_t)curinum);
+ break;
+ default:
+ printf("current inode %ju: screwy itype 0%o (mode 0%o)?\n",
+ (uintmax_t)curinum, DIP(curinode, di_mode) & IFMT,
+ DIP(curinode, di_mode));
+ break;
+ }
+ return 0;
+}
diff --git a/sbin/fsirand/Makefile b/sbin/fsirand/Makefile
new file mode 100644
index 0000000..2ae820a
--- /dev/null
+++ b/sbin/fsirand/Makefile
@@ -0,0 +1,8 @@
+# $OpenBSD: Makefile,v 1.1 1997/01/26 02:23:20 millert Exp $
+# $FreeBSD$
+
+PROG= fsirand
+MAN= fsirand.8
+WARNS?= 3
+
+.include <bsd.prog.mk>
diff --git a/sbin/fsirand/fsirand.8 b/sbin/fsirand/fsirand.8
new file mode 100644
index 0000000..f3fbd8d
--- /dev/null
+++ b/sbin/fsirand/fsirand.8
@@ -0,0 +1,116 @@
+.\" Copyright (c) 1997 Todd C. Miller <Todd.Miller@courtesan.com>
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. All advertising materials mentioning features or use of this software
+.\" must display the following acknowledgment:
+.\" This product includes software developed by Todd C. Miller.
+.\" 4. The name of the author may not be used to endorse or promote products
+.\" derived from this software without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED ``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.
+.\"
+.\" $OpenBSD: fsirand.8,v 1.6 1997/02/23 03:58:26 millert Exp $
+.\" $FreeBSD$
+.\"
+.Dd January 25, 1997
+.Dt FSIRAND 8
+.Os
+.Sh NAME
+.Nm fsirand
+.Nd randomize inode generation numbers
+.Sh SYNOPSIS
+.Nm
+.Op Fl b
+.Op Fl f
+.Op Fl p
+.Ar special
+.Op Ar "special ..."
+.Sh DESCRIPTION
+The
+.Nm
+utility installs random generation numbers on all the inodes for
+each file system specified on the command line by
+.Ar special .
+This increases the security of NFS-exported file systems by making
+it difficult to ``guess'' filehandles.
+.Pp
+.Em Note :
+.Xr newfs 8
+now does the equivalent of
+.Nm
+itself so it is no longer necessary to
+run
+.Nm
+by hand on a new file system.
+It is only used to
+re-randomize or report on an existing file system.
+.Pp
+The
+.Nm
+utility should only be used on an unmounted file system that
+has been checked with
+.Xr fsck 8
+or a file system that is mounted read-only.
+The
+.Nm
+utility may be used on the root file system in single-user mode
+but the system should be rebooted via ``reboot -n'' afterwards.
+.Sh OPTIONS
+The available options are as follows:
+.Bl -tag -width indent
+.It Fl b
+Use the default block size (usually 512 bytes) instead
+of the value gleaned from the disklabel.
+.It Fl f
+Force
+.Nm
+to run even if the file system on
+.Ar special
+is not marked as clean.
+.It Fl p
+Print the current generation numbers for all inodes instead of
+generating new ones.
+.El
+.Sh SEE ALSO
+.Xr fs 5 ,
+.Xr fsck 8 ,
+.Xr newfs 8
+.Sh HISTORY
+The
+.Nm
+utility appeared in SunOS 3.x.
+.Pp
+This version of
+.Nm
+first appeared in
+.Ox 2.1 .
+.Pp
+A
+.Fx
+version first appeared in
+.Fx 2.2.5 .
+.Sh AUTHORS
+.An Todd C. Miller Aq Mt Todd.Miller@courtesan.com
+.Sh CAVEATS
+Since
+.Nm
+allocates enough memory to hold all the inodes in
+a given cylinder group it may use a large amount
+of memory for large disks with few cylinder groups.
diff --git a/sbin/fsirand/fsirand.c b/sbin/fsirand/fsirand.c
new file mode 100644
index 0000000..0fea239
--- /dev/null
+++ b/sbin/fsirand/fsirand.c
@@ -0,0 +1,303 @@
+/* $OpenBSD: fsirand.c,v 1.9 1997/02/28 00:46:33 millert Exp $ */
+
+/*
+ * Copyright (c) 1997 Todd C. Miller <Todd.Miller@courtesan.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by Todd C. Miller.
+ * 4. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``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.
+ */
+
+#ifndef lint
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/resource.h>
+
+#include <ufs/ufs/dinode.h>
+#include <ufs/ffs/fs.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+static void usage(void) __dead2;
+int fsirand(char *);
+
+/*
+ * Possible superblock locations ordered from most to least likely.
+ */
+static int sblock_try[] = SBLOCKSEARCH;
+
+static int printonly = 0, force = 0, ignorelabel = 0;
+
+int
+main(int argc, char *argv[])
+{
+ int n, ex = 0;
+ struct rlimit rl;
+
+ while ((n = getopt(argc, argv, "bfp")) != -1) {
+ switch (n) {
+ case 'b':
+ ignorelabel++;
+ break;
+ case 'p':
+ printonly++;
+ break;
+ case 'f':
+ force++;
+ break;
+ default:
+ usage();
+ }
+ }
+ if (argc - optind < 1)
+ usage();
+
+ srandomdev();
+
+ /* Increase our data size to the max */
+ if (getrlimit(RLIMIT_DATA, &rl) == 0) {
+ rl.rlim_cur = rl.rlim_max;
+ if (setrlimit(RLIMIT_DATA, &rl) < 0)
+ warn("can't get resource limit to max data size");
+ } else
+ warn("can't get resource limit for data size");
+
+ for (n = optind; n < argc; n++) {
+ if (argc - optind != 1)
+ (void)puts(argv[n]);
+ ex += fsirand(argv[n]);
+ if (n < argc - 1)
+ putchar('\n');
+ }
+
+ exit(ex);
+}
+
+int
+fsirand(char *device)
+{
+ struct ufs1_dinode *dp1;
+ struct ufs2_dinode *dp2;
+ caddr_t inodebuf;
+ ssize_t ibufsize;
+ struct fs *sblock;
+ ino_t inumber;
+ ufs2_daddr_t sblockloc, dblk;
+ char sbuf[SBLOCKSIZE], sbuftmp[SBLOCKSIZE];
+ int i, devfd, n, cg;
+ u_int32_t bsize = DEV_BSIZE;
+
+ if ((devfd = open(device, printonly ? O_RDONLY : O_RDWR)) < 0) {
+ warn("can't open %s", device);
+ return (1);
+ }
+
+ dp1 = NULL;
+ dp2 = NULL;
+
+ /* Read in master superblock */
+ (void)memset(&sbuf, 0, sizeof(sbuf));
+ sblock = (struct fs *)&sbuf;
+ for (i = 0; sblock_try[i] != -1; i++) {
+ sblockloc = sblock_try[i];
+ if (lseek(devfd, sblockloc, SEEK_SET) == -1) {
+ warn("can't seek to superblock (%jd) on %s",
+ (intmax_t)sblockloc, device);
+ return (1);
+ }
+ if ((n = read(devfd, (void *)sblock, SBLOCKSIZE))!=SBLOCKSIZE) {
+ warnx("can't read superblock on %s: %s", device,
+ (n < SBLOCKSIZE) ? "short read" : strerror(errno));
+ return (1);
+ }
+ if ((sblock->fs_magic == FS_UFS1_MAGIC ||
+ (sblock->fs_magic == FS_UFS2_MAGIC &&
+ sblock->fs_sblockloc == sblock_try[i])) &&
+ sblock->fs_bsize <= MAXBSIZE &&
+ sblock->fs_bsize >= (ssize_t)sizeof(struct fs))
+ break;
+ }
+ if (sblock_try[i] == -1) {
+ fprintf(stderr, "Cannot find file system superblock\n");
+ return (1);
+ }
+
+ if (sblock->fs_magic == FS_UFS1_MAGIC &&
+ sblock->fs_old_inodefmt < FS_44INODEFMT) {
+ warnx("file system format is too old, sorry");
+ return (1);
+ }
+ if (!force && !printonly && sblock->fs_clean != 1) {
+ warnx("file system is not clean, fsck %s first", device);
+ return (1);
+ }
+
+ /* Make sure backup superblocks are sane. */
+ sblock = (struct fs *)&sbuftmp;
+ for (cg = 0; cg < (int)sblock->fs_ncg; cg++) {
+ dblk = fsbtodb(sblock, cgsblock(sblock, cg));
+ if (lseek(devfd, (off_t)dblk * bsize, SEEK_SET) < 0) {
+ warn("can't seek to %jd", (intmax_t)dblk * bsize);
+ return (1);
+ } else if ((n = write(devfd, (void *)sblock, SBLOCKSIZE)) != SBLOCKSIZE) {
+ warn("can't read backup superblock %d on %s: %s",
+ cg + 1, device, (n < SBLOCKSIZE) ? "short write"
+ : strerror(errno));
+ return (1);
+ }
+ if (sblock->fs_magic != FS_UFS1_MAGIC &&
+ sblock->fs_magic != FS_UFS2_MAGIC) {
+ warnx("bad magic number in backup superblock %d on %s",
+ cg + 1, device);
+ return (1);
+ }
+ if (sblock->fs_sbsize > SBLOCKSIZE) {
+ warnx("size of backup superblock %d on %s is preposterous",
+ cg + 1, device);
+ return (1);
+ }
+ }
+ sblock = (struct fs *)&sbuf;
+
+ /* XXX - should really cap buffer at 512kb or so */
+ if (sblock->fs_magic == FS_UFS1_MAGIC)
+ ibufsize = sizeof(struct ufs1_dinode) * sblock->fs_ipg;
+ else
+ ibufsize = sizeof(struct ufs2_dinode) * sblock->fs_ipg;
+ if ((inodebuf = malloc(ibufsize)) == NULL)
+ errx(1, "can't allocate memory for inode buffer");
+
+ if (printonly && (sblock->fs_id[0] || sblock->fs_id[1])) {
+ if (sblock->fs_id[0])
+ (void)printf("%s was randomized on %s", device,
+ ctime((void *)&(sblock->fs_id[0])));
+ (void)printf("fsid: %x %x\n", sblock->fs_id[0],
+ sblock->fs_id[1]);
+ }
+
+ /* Randomize fs_id unless old 4.2BSD file system */
+ if (!printonly) {
+ /* Randomize fs_id and write out new sblock and backups */
+ sblock->fs_id[0] = (u_int32_t)time(NULL);
+ sblock->fs_id[1] = random();
+
+ if (lseek(devfd, sblockloc, SEEK_SET) == -1) {
+ warn("can't seek to superblock (%jd) on %s",
+ (intmax_t)sblockloc, device);
+ return (1);
+ }
+ if ((n = write(devfd, (void *)sblock, SBLOCKSIZE)) !=
+ SBLOCKSIZE) {
+ warn("can't write superblock on %s: %s", device,
+ (n < SBLOCKSIZE) ? "short write" : strerror(errno));
+ return (1);
+ }
+ }
+
+ /* For each cylinder group, randomize inodes and update backup sblock */
+ for (cg = 0, inumber = 0; cg < (int)sblock->fs_ncg; cg++) {
+ /* Update superblock if appropriate */
+ if (!printonly) {
+ dblk = fsbtodb(sblock, cgsblock(sblock, cg));
+ if (lseek(devfd, (off_t)dblk * bsize, SEEK_SET) < 0) {
+ warn("can't seek to %jd",
+ (intmax_t)dblk * bsize);
+ return (1);
+ } else if ((n = write(devfd, (void *)sblock,
+ SBLOCKSIZE)) != SBLOCKSIZE) {
+ warn("can't write backup superblock %d on %s: %s",
+ cg + 1, device, (n < SBLOCKSIZE) ?
+ "short write" : strerror(errno));
+ return (1);
+ }
+ }
+
+ /* Read in inodes, then print or randomize generation nums */
+ dblk = fsbtodb(sblock, ino_to_fsba(sblock, inumber));
+ if (lseek(devfd, (off_t)dblk * bsize, SEEK_SET) < 0) {
+ warn("can't seek to %jd", (intmax_t)dblk * bsize);
+ return (1);
+ } else if ((n = read(devfd, inodebuf, ibufsize)) != ibufsize) {
+ warnx("can't read inodes: %s",
+ (n < ibufsize) ? "short read" : strerror(errno));
+ return (1);
+ }
+
+ for (n = 0; n < (int)sblock->fs_ipg; n++, inumber++) {
+ if (sblock->fs_magic == FS_UFS1_MAGIC)
+ dp1 = &((struct ufs1_dinode *)inodebuf)[n];
+ else
+ dp2 = &((struct ufs2_dinode *)inodebuf)[n];
+ if (inumber >= ROOTINO) {
+ if (printonly)
+ (void)printf("ino %ju gen %08x\n",
+ (uintmax_t)inumber,
+ sblock->fs_magic == FS_UFS1_MAGIC ?
+ dp1->di_gen : dp2->di_gen);
+ else if (sblock->fs_magic == FS_UFS1_MAGIC)
+ dp1->di_gen = random();
+ else
+ dp2->di_gen = random();
+ }
+ }
+
+ /* Write out modified inodes */
+ if (!printonly) {
+ if (lseek(devfd, (off_t)dblk * bsize, SEEK_SET) < 0) {
+ warn("can't seek to %jd",
+ (intmax_t)dblk * bsize);
+ return (1);
+ } else if ((n = write(devfd, inodebuf, ibufsize)) !=
+ ibufsize) {
+ warnx("can't write inodes: %s",
+ (n != ibufsize) ? "short write" :
+ strerror(errno));
+ return (1);
+ }
+ }
+ }
+ (void)close(devfd);
+
+ return(0);
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr,
+ "usage: fsirand [-b] [-f] [-p] special [special ...]\n");
+ exit(1);
+}
diff --git a/sbin/gbde/Makefile b/sbin/gbde/Makefile
new file mode 100644
index 0000000..c33136b
--- /dev/null
+++ b/sbin/gbde/Makefile
@@ -0,0 +1,32 @@
+# $FreeBSD$
+
+PROG= gbde
+SRCS= gbde.c template.c
+SRCS+= rijndael-alg-fst.c
+SRCS+= rijndael-api-fst.c
+SRCS+= sha2.c
+SRCS+= g_bde_lock.c
+
+# rijndael-fst.c does evil casting things which can results in warnings,
+# the test-vectors check out however, so it works right.
+NO_WCAST_ALIGN=
+NO_WMISSING_VARIABLE_DECLARATIONS=
+
+CFLAGS+= -I${.CURDIR}/../../sys
+.PATH: ${.CURDIR}/../../sys/geom/bde \
+ ${.CURDIR}/../../sys/crypto/rijndael \
+ ${.CURDIR}/../../sys/crypto/sha2
+
+CLEANFILES+= template.c
+
+MAN= gbde.8
+LIBADD= md util geom
+
+template.c: template.txt
+ file2c 'const char template[] = {' ',0};' \
+ < ${.CURDIR}/template.txt > template.c
+
+test: ${PROG}
+ sh ${.CURDIR}/test.sh ${.CURDIR}
+
+.include <bsd.prog.mk>
diff --git a/sbin/gbde/gbde.8 b/sbin/gbde/gbde.8
new file mode 100644
index 0000000..0578287
--- /dev/null
+++ b/sbin/gbde/gbde.8
@@ -0,0 +1,270 @@
+.\"
+.\" Copyright (c) 2002 Poul-Henning Kamp
+.\" Copyright (c) 2002 Networks Associates Technology, Inc.
+.\" All rights reserved.
+.\"
+.\" This software was developed for the FreeBSD Project by Poul-Henning Kamp
+.\" and NAI Labs, the Security Research Division of Network Associates, Inc.
+.\" under DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), as part of the
+.\" DARPA CHATS research program.
+.\"
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd August 27, 2014
+.Dt GBDE 8
+.Os
+.Sh NAME
+.Nm gbde
+.Nd operation and management utility for Geom Based Disk Encryption
+.Sh SYNOPSIS
+.Nm
+.Cm attach
+.Ar destination
+.Op Fl k Ar keyfile
+.Op Fl l Ar lockfile
+.Op Fl p Ar pass-phrase
+.Nm
+.Cm detach
+.Ar destination
+.Nm
+.Cm init
+.Ar destination
+.Op Fl i
+.Op Fl f Ar filename
+.Op Fl K Ar new-keyfile
+.Op Fl L Ar new-lockfile
+.Op Fl P Ar new-pass-phrase
+.Nm
+.Cm setkey
+.Ar destination
+.Op Fl n Ar key
+.Op Fl k Ar keyfile
+.Op Fl l Ar lockfile
+.Op Fl p Ar pass-phrase
+.Op Fl K Ar new-keyfile
+.Op Fl L Ar new-lockfile
+.Op Fl P Ar new-pass-phrase
+.Nm
+.Cm nuke
+.Ar destination
+.Op Fl n Ar key
+.Op Fl k Ar keyfile
+.Op Fl l Ar lockfile
+.Op Fl p Ar pass-phrase
+.Nm
+.Cm destroy
+.Ar destination
+.Op Fl k Ar keyfile
+.Op Fl l Ar lockfile
+.Op Fl p Ar pass-phrase
+.Sh DESCRIPTION
+.Bf -symbolic
+NOTICE:
+Please be aware that this code has not yet received much review
+and analysis by qualified cryptographers and therefore should be considered
+a slightly suspect experimental facility.
+.Pp
+We cannot at this point guarantee that the on-disk format will not change
+in response to reviews or bug-fixes, so potential users are advised to
+be prepared that
+.Xr dump 8 Ns / Ns
+.Xr restore 8
+based migrations may be called for in the future.
+.Ef
+.Pp
+The
+.Nm
+utility is the only official operation and management interface for the
+.Xr gbde 4
+.Tn GEOM
+based disk encryption kernel facility.
+The interaction between the
+.Nm
+utility and the kernel part is not a published interface.
+.Pp
+The operational aspect consists of two subcommands:
+one to open and attach
+a device to the in-kernel cryptographic
+.Nm
+module
+.Pq Cm attach ,
+and one to close and detach a device
+.Pq Cm detach .
+.Pp
+The management part allows initialization of the master key and lock sectors
+on a device
+.Pq Cm init ,
+initialization and replacement of pass-phrases
+.Pq Cm setkey ,
+and key invalidation
+.Pq Cm nuke
+and blackening
+.Pq Cm destroy
+functions.
+.Pp
+The
+.Fl l Ar lockfile
+argument is used to supply the lock selector data.
+If no
+.Fl l
+option is specified, the first sector is used for this purpose.
+.Pp
+The
+.Fl L Ar new-lockfile
+argument
+specifies the lock selector file for the key
+initialized with the
+.Cm init
+subcommand
+or modified with the
+.Cm setkey
+subcommand.
+.Pp
+The
+.Fl n Ar key
+argument can be used to specify to which of the four keys
+the operation applies.
+A value of 1 to 4 selects the specified key, a value of 0 (the default)
+means
+.Dq "this key"
+(i.e., the key used to gain access to the device)
+and a value of \-1 means
+.Dq "all keys" .
+.Pp
+The
+.Fl f Ar filename
+specifies an optional parameter file for use under initialization.
+.Pp
+Alternatively, the
+.Fl i
+option toggles an interactive mode where a template file with descriptions
+of the parameters can be interactively edited.
+.Pp
+The
+.Fl p Ar pass-phrase
+argument
+specifies the pass-phrase used for opening the device.
+If not specified, the controlling terminal will be used to prompt the user
+for the pass-phrase.
+Be aware that using this option may expose the pass-phrase to other
+users who happen to run
+.Xr ps 1
+or similar while the command is running.
+.Pp
+The
+.Fl P Ar new-pass-phrase
+argument
+can be used to specify the new pass-phrase to the
+.Cm init
+and
+.Cm setkey
+subcommands.
+If not specified, the user is prompted for the new pass-phrase on the
+controlling terminal.
+Be aware that using this option may expose the pass-phrase to other
+users who happen to run
+.Xr ps 1
+or similar while the command is running.
+.Pp
+The
+.Fl k Ar keyfile
+argument specifies a key file to be used in combination with the
+pass-phrase (whether the pass-phrase is specified on the command line
+or entered from the terminal) for opening the device.
+The device will only be opened if the contents of the key file and the
+pass-phrase are both correct.
+.Pp
+The
+.Fl K Ar new-keyfile
+argument can be used to specify a new key file to the
+.Cm init
+and
+.Cm setkey
+subcommands.
+If not specified, no key file will be used (even if one was previously
+used).
+.Sh EXAMPLES
+To initialize a device, using default parameters:
+.Pp
+.Dl "gbde init /dev/ada0s1f -L /etc/ada0s1f.lock"
+.Pp
+To attach an encrypted device:
+.Pp
+.Dl "gbde attach ada0s1f -l /etc/ada0s1f.lock"
+.Pp
+The encrypted device has the suffix
+.Pa .bde
+so a typical
+command to create and mount a file system would be:
+.Pp
+.Dl "newfs /dev/ada0s1f.bde"
+.Dl "mount /dev/ada0s1f.bde /secret"
+.Pp
+To detach an encrypted device:
+.Pp
+.Dl "gbde detach ada0s1f"
+.Pp
+Please notice that detaching an encrypted device corresponds to
+physically removing it, do not forget to unmount the file system first.
+.Pp
+To initialize the second key using a detached lockfile and a trivial
+pass-phrase:
+.Pp
+.Dl "gbde setkey ada0s1f -n 2 -P foo -L key2.lockfile"
+.Pp
+To invalidate your own masterkey:
+.Pp
+.Dl "gbde nuke ada0s1f"
+.Pp
+This will overwrite your masterkey sector with zeros, and results in
+a diagnostic if you try to use the key again.
+You can also destroy the other three copies of the masterkey with the
+-n argument.
+.Pp
+You can also invalidate your masterkey without leaving a tell-tale sector
+full of zeros:
+.Pp
+.Dl "gbde destroy ada0s1f"
+.Pp
+This will overwrite the information fields in your masterkey sector,
+encrypt it and write it back.
+You get a (different) diagnostic if you try to use it.
+.Sh SEE ALSO
+.Xr gbde 4 ,
+.Xr geom 4
+.Sh HISTORY
+This software was developed for the
+.Fx
+Project by
+.An Poul-Henning Kamp
+and NAI Labs, the Security Research Division of Network Associates, Inc.\&
+under DARPA/SPAWAR contract N66001-01-C-8035
+.Pq Dq CBOSS ,
+as part of the
+DARPA CHATS research program.
+.Sh AUTHORS
+.An Poul-Henning Kamp Aq Mt phk@FreeBSD.org
+.Sh BUGS
+The cryptographic algorithms and the overall design have not been
+attacked mercilessly for over 10 years by a gang of cryptoanalysts.
diff --git a/sbin/gbde/gbde.c b/sbin/gbde/gbde.c
new file mode 100644
index 0000000..3dca212
--- /dev/null
+++ b/sbin/gbde/gbde.c
@@ -0,0 +1,901 @@
+/*-
+ * Copyright (c) 2002 Poul-Henning Kamp
+ * Copyright (c) 2002 Networks Associates Technology, Inc.
+ * All rights reserved.
+ *
+ * This software was developed for the FreeBSD Project by Poul-Henning Kamp
+ * and NAI Labs, the Security Research Division of Network Associates, Inc.
+ * under DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), as part of the
+ * DARPA CHATS research program.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ *
+ * XXX: Future stuff
+ *
+ * Replace the template file options (-i & -f) with command-line variables
+ * "-v property=foo"
+ *
+ * Introduce -e, extra entropy source (XOR with /dev/random)
+ *
+ * Introduce -E, alternate entropy source (instead of /dev/random)
+ *
+ * Introduce -i take IV from keyboard or
+ *
+ * Introduce -I take IV from file/cmd
+ *
+ * Introduce -m/-M store encrypted+encoded masterkey in file
+ *
+ * Introduce -k/-K get pass-phrase part from file/cmd
+ *
+ * Introduce -d add more dest-devices to worklist.
+ *
+ * Add key-option: selfdestruct bit.
+ *
+ * New/changed verbs:
+ * "onetime" attach with onetime nonstored locksector
+ * "key"/"unkey" to blast memory copy of key without orphaning
+ * "nuke" blow away everything attached, crash/halt/power-off if possible.
+ * "blast" destroy all copies of the masterkey
+ * "destroy" destroy one copy of the masterkey
+ * "backup"/"restore" of masterkey sectors.
+ *
+ * Make all verbs work on both attached/detached devices.
+ *
+ */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/mutex.h>
+#include <md5.h>
+#include <readpassphrase.h>
+#include <string.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <strings.h>
+#include <stdlib.h>
+#include <err.h>
+#include <stdio.h>
+#include <libutil.h>
+#include <libgeom.h>
+#include <sys/errno.h>
+#include <sys/disk.h>
+#include <sys/stat.h>
+#include <crypto/rijndael/rijndael-api-fst.h>
+#include <crypto/sha2/sha2.h>
+#include <sys/param.h>
+#include <sys/linker.h>
+
+#define GBDEMOD "geom_bde"
+#define KASSERT(foo, bar) do { if(!(foo)) { warn bar ; exit (1); } } while (0)
+
+#include <geom/geom.h>
+#include <geom/bde/g_bde.h>
+
+extern const char template[];
+
+
+#if 0
+static void
+g_hexdump(void *ptr, int length)
+{
+ int i, j, k;
+ unsigned char *cp;
+
+ cp = ptr;
+ for (i = 0; i < length; i+= 16) {
+ printf("%04x ", i);
+ for (j = 0; j < 16; j++) {
+ k = i + j;
+ if (k < length)
+ printf(" %02x", cp[k]);
+ else
+ printf(" ");
+ }
+ printf(" |");
+ for (j = 0; j < 16; j++) {
+ k = i + j;
+ if (k >= length)
+ printf(" ");
+ else if (cp[k] >= ' ' && cp[k] <= '~')
+ printf("%c", cp[k]);
+ else
+ printf(".");
+ }
+ printf("|\n");
+ }
+}
+#endif
+
+static void __dead2
+usage(void)
+{
+
+ (void)fprintf(stderr,
+"usage: gbde attach destination [-k keyfile] [-l lockfile] [-p pass-phrase]\n"
+" gbde detach destination\n"
+" gbde init destination [-i] [-f filename] [-K new-keyfile]\n"
+" [-L new-lockfile] [-P new-pass-phrase]\n"
+" gbde setkey destination [-n key]\n"
+" [-k keyfile] [-l lockfile] [-p pass-phrase]\n"
+" [-K new-keyfile] [-L new-lockfile] [-P new-pass-phrase]\n"
+" gbde nuke destination [-n key]\n"
+" [-k keyfile] [-l lockfile] [-p pass-phrase]\n"
+" gbde destroy destination [-k keyfile] [-l lockfile] [-p pass-phrase]\n");
+ exit(1);
+}
+
+void *
+g_read_data(struct g_consumer *cp, off_t offset, off_t length, int *error)
+{
+ void *p;
+ int fd, i;
+ off_t o2;
+
+ p = malloc(length);
+ if (p == NULL)
+ err(1, "malloc");
+ fd = *(int *)cp;
+ o2 = lseek(fd, offset, SEEK_SET);
+ if (o2 != offset)
+ err(1, "lseek");
+ i = read(fd, p, length);
+ if (i != length)
+ err(1, "read");
+ if (error != NULL)
+ error = 0;
+ return (p);
+}
+
+static void
+random_bits(void *p, u_int len)
+{
+ static int fdr = -1;
+ int i;
+
+ if (fdr < 0) {
+ fdr = open("/dev/urandom", O_RDONLY);
+ if (fdr < 0)
+ err(1, "/dev/urandom");
+ }
+
+ i = read(fdr, p, len);
+ if (i != (int)len)
+ err(1, "read from /dev/urandom");
+}
+
+/* XXX: not nice */
+static u_char sha2[SHA512_DIGEST_LENGTH];
+
+static void
+reset_passphrase(struct g_bde_softc *sc)
+{
+
+ memcpy(sc->sha2, sha2, SHA512_DIGEST_LENGTH);
+}
+
+static void
+setup_passphrase(struct g_bde_softc *sc, int sure, const char *input,
+ const char *keyfile)
+{
+ char buf1[BUFSIZ + SHA512_DIGEST_LENGTH];
+ char buf2[BUFSIZ + SHA512_DIGEST_LENGTH];
+ char *p;
+ int kfd, klen, bpos = 0;
+
+ if (keyfile != NULL) {
+ /* Read up to BUFSIZ bytes from keyfile */
+ kfd = open(keyfile, O_RDONLY, 0);
+ if (kfd < 0)
+ err(1, "%s", keyfile);
+ klen = read(kfd, buf1, BUFSIZ);
+ if (klen == -1)
+ err(1, "%s", keyfile);
+ close(kfd);
+
+ /* Prepend the passphrase with the hash of the key read */
+ g_bde_hash_pass(sc, buf1, klen);
+ memcpy(buf1, sc->sha2, SHA512_DIGEST_LENGTH);
+ memcpy(buf2, sc->sha2, SHA512_DIGEST_LENGTH);
+ bpos = SHA512_DIGEST_LENGTH;
+ }
+
+ if (input != NULL) {
+ if (strlen(input) >= BUFSIZ)
+ errx(1, "Passphrase too long");
+ strcpy(buf1 + bpos, input);
+
+ g_bde_hash_pass(sc, buf1, strlen(buf1 + bpos) + bpos);
+ memcpy(sha2, sc->sha2, SHA512_DIGEST_LENGTH);
+ return;
+ }
+ for (;;) {
+ p = readpassphrase(
+ sure ? "Enter new passphrase:" : "Enter passphrase: ",
+ buf1 + bpos, sizeof buf1 - bpos,
+ RPP_ECHO_OFF | RPP_REQUIRE_TTY);
+ if (p == NULL)
+ err(1, "readpassphrase");
+
+ if (sure) {
+ p = readpassphrase("Reenter new passphrase: ",
+ buf2 + bpos, sizeof buf2 - bpos,
+ RPP_ECHO_OFF | RPP_REQUIRE_TTY);
+ if (p == NULL)
+ err(1, "readpassphrase");
+
+ if (strcmp(buf1 + bpos, buf2 + bpos)) {
+ printf("They didn't match.\n");
+ continue;
+ }
+ }
+ if (strlen(buf1 + bpos) < 3) {
+ printf("Too short passphrase.\n");
+ continue;
+ }
+ break;
+ }
+ g_bde_hash_pass(sc, buf1, strlen(buf1 + bpos) + bpos);
+ memcpy(sha2, sc->sha2, SHA512_DIGEST_LENGTH);
+}
+
+static void
+encrypt_sector(void *d, int len, int klen, void *key)
+{
+ keyInstance ki;
+ cipherInstance ci;
+ int error;
+
+ error = rijndael_cipherInit(&ci, MODE_CBC, NULL);
+ if (error <= 0)
+ errx(1, "rijndael_cipherInit=%d", error);
+ error = rijndael_makeKey(&ki, DIR_ENCRYPT, klen, key);
+ if (error <= 0)
+ errx(1, "rijndael_makeKeY=%d", error);
+ error = rijndael_blockEncrypt(&ci, &ki, d, len * 8, d);
+ if (error <= 0)
+ errx(1, "rijndael_blockEncrypt=%d", error);
+}
+
+static void
+cmd_attach(const struct g_bde_softc *sc, const char *dest, const char *lfile)
+{
+ int ffd;
+ u_char buf[16];
+ struct gctl_req *r;
+ const char *errstr;
+
+ r = gctl_get_handle();
+ gctl_ro_param(r, "verb", -1, "create geom");
+ gctl_ro_param(r, "class", -1, "BDE");
+ gctl_ro_param(r, "provider", -1, dest);
+ gctl_ro_param(r, "pass", SHA512_DIGEST_LENGTH, sc->sha2);
+ if (lfile != NULL) {
+ ffd = open(lfile, O_RDONLY, 0);
+ if (ffd < 0)
+ err(1, "%s", lfile);
+ read(ffd, buf, 16);
+ gctl_ro_param(r, "key", 16, buf);
+ close(ffd);
+ }
+ errstr = gctl_issue(r);
+ if (errstr != NULL)
+ errx(1, "Attach to %s failed: %s", dest, errstr);
+
+ exit (0);
+}
+
+static void
+cmd_detach(const char *dest)
+{
+ struct gctl_req *r;
+ const char *errstr;
+ char buf[BUFSIZ];
+
+ r = gctl_get_handle();
+ gctl_ro_param(r, "verb", -1, "destroy geom");
+ gctl_ro_param(r, "class", -1, "BDE");
+ sprintf(buf, "%s.bde", dest);
+ gctl_ro_param(r, "geom", -1, buf);
+ /* gctl_dump(r, stdout); */
+ errstr = gctl_issue(r);
+ if (errstr != NULL)
+ errx(1, "Detach of %s failed: %s", dest, errstr);
+ exit (0);
+}
+
+static void
+cmd_open(struct g_bde_softc *sc, int dfd , const char *l_opt, u_int *nkey)
+{
+ int error;
+ int ffd;
+ u_char keyloc[16];
+ u_int sectorsize;
+ off_t mediasize;
+ struct stat st;
+
+ error = ioctl(dfd, DIOCGSECTORSIZE, &sectorsize);
+ if (error)
+ sectorsize = 512;
+ error = ioctl(dfd, DIOCGMEDIASIZE, &mediasize);
+ if (error) {
+ error = fstat(dfd, &st);
+ if (error == 0 && S_ISREG(st.st_mode))
+ mediasize = st.st_size;
+ else
+ error = ENOENT;
+ }
+ if (error)
+ mediasize = (off_t)-1;
+ if (l_opt != NULL) {
+ ffd = open(l_opt, O_RDONLY, 0);
+ if (ffd < 0)
+ err(1, "%s", l_opt);
+ read(ffd, keyloc, sizeof keyloc);
+ close(ffd);
+ } else {
+ memset(keyloc, 0, sizeof keyloc);
+ }
+
+ error = g_bde_decrypt_lock(sc, sc->sha2, keyloc, mediasize,
+ sectorsize, nkey);
+ if (error == ENOENT)
+ errx(1, "Lock was destroyed.");
+ if (error == ESRCH)
+ errx(1, "Lock was nuked.");
+ if (error == ENOTDIR)
+ errx(1, "Lock not found");
+ if (error != 0)
+ errx(1, "Error %d decrypting lock", error);
+ if (nkey)
+ printf("Opened with key %u\n", 1 + *nkey);
+ return;
+}
+
+static void
+cmd_nuke(struct g_bde_key *gl, int dfd , int key)
+{
+ int i;
+ u_char *sbuf;
+ off_t offset, offset2;
+
+ sbuf = malloc(gl->sectorsize);
+ memset(sbuf, 0, gl->sectorsize);
+ offset = (gl->lsector[key] & ~(gl->sectorsize - 1));
+ offset2 = lseek(dfd, offset, SEEK_SET);
+ if (offset2 != offset)
+ err(1, "lseek");
+ i = write(dfd, sbuf, gl->sectorsize);
+ free(sbuf);
+ if (i != (int)gl->sectorsize)
+ err(1, "write");
+ printf("Nuked key %d\n", 1 + key);
+}
+
+static void
+cmd_write(struct g_bde_key *gl, struct g_bde_softc *sc, int dfd , int key, const char *l_opt)
+{
+ int i, ffd;
+ uint64_t off[2];
+ u_char keyloc[16];
+ u_char *sbuf, *q;
+ off_t offset, offset2;
+
+ sbuf = malloc(gl->sectorsize);
+ /*
+ * Find the byte-offset in the lock sector where we will put the lock
+ * data structure. We can put it any random place as long as the
+ * structure fits.
+ */
+ for(;;) {
+ random_bits(off, sizeof off);
+ off[0] &= (gl->sectorsize - 1);
+ if (off[0] + G_BDE_LOCKSIZE > gl->sectorsize)
+ continue;
+ break;
+ }
+
+ /* Add the sector offset in bytes */
+ off[0] += (gl->lsector[key] & ~(gl->sectorsize - 1));
+ gl->lsector[key] = off[0];
+
+ i = g_bde_keyloc_encrypt(sc->sha2, off[0], off[1], keyloc);
+ if (i)
+ errx(1, "g_bde_keyloc_encrypt()");
+ if (l_opt != NULL) {
+ ffd = open(l_opt, O_WRONLY | O_CREAT | O_TRUNC, 0600);
+ if (ffd < 0)
+ err(1, "%s", l_opt);
+ write(ffd, keyloc, sizeof keyloc);
+ close(ffd);
+ } else if (gl->flags & GBDE_F_SECT0) {
+ offset2 = lseek(dfd, 0, SEEK_SET);
+ if (offset2 != 0)
+ err(1, "lseek");
+ i = read(dfd, sbuf, gl->sectorsize);
+ if (i != (int)gl->sectorsize)
+ err(1, "read");
+ memcpy(sbuf + key * 16, keyloc, sizeof keyloc);
+ offset2 = lseek(dfd, 0, SEEK_SET);
+ if (offset2 != 0)
+ err(1, "lseek");
+ i = write(dfd, sbuf, gl->sectorsize);
+ if (i != (int)gl->sectorsize)
+ err(1, "write");
+ } else {
+ errx(1, "No -L option and no space in sector 0 for lockfile");
+ }
+
+ /* Allocate a sectorbuffer and fill it with random junk */
+ if (sbuf == NULL)
+ err(1, "malloc");
+ random_bits(sbuf, gl->sectorsize);
+
+ /* Fill random bits in the spare field */
+ random_bits(gl->spare, sizeof(gl->spare));
+
+ /* Encode the structure where we want it */
+ q = sbuf + (off[0] % gl->sectorsize);
+ i = g_bde_encode_lock(sc->sha2, gl, q);
+ if (i < 0)
+ errx(1, "programming error encoding lock");
+
+ encrypt_sector(q, G_BDE_LOCKSIZE, 256, sc->sha2 + 16);
+ offset = gl->lsector[key] & ~(gl->sectorsize - 1);
+ offset2 = lseek(dfd, offset, SEEK_SET);
+ if (offset2 != offset)
+ err(1, "lseek");
+ i = write(dfd, sbuf, gl->sectorsize);
+ if (i != (int)gl->sectorsize)
+ err(1, "write");
+ free(sbuf);
+#if 0
+ printf("Wrote key %d at %jd\n", key, (intmax_t)offset);
+ printf("s0 = %jd\n", (intmax_t)gl->sector0);
+ printf("sN = %jd\n", (intmax_t)gl->sectorN);
+ printf("l[0] = %jd\n", (intmax_t)gl->lsector[0]);
+ printf("l[1] = %jd\n", (intmax_t)gl->lsector[1]);
+ printf("l[2] = %jd\n", (intmax_t)gl->lsector[2]);
+ printf("l[3] = %jd\n", (intmax_t)gl->lsector[3]);
+ printf("k = %jd\n", (intmax_t)gl->keyoffset);
+ printf("ss = %jd\n", (intmax_t)gl->sectorsize);
+#endif
+}
+
+static void
+cmd_destroy(struct g_bde_key *gl, int nkey)
+{
+ int i;
+
+ bzero(&gl->sector0, sizeof gl->sector0);
+ bzero(&gl->sectorN, sizeof gl->sectorN);
+ bzero(&gl->keyoffset, sizeof gl->keyoffset);
+ gl->flags &= GBDE_F_SECT0;
+ bzero(gl->mkey, sizeof gl->mkey);
+ for (i = 0; i < G_BDE_MAXKEYS; i++)
+ if (i != nkey)
+ gl->lsector[i] = ~0;
+}
+
+static int
+sorthelp(const void *a, const void *b)
+{
+ const uint64_t *oa, *ob;
+
+ oa = a;
+ ob = b;
+ if (*oa > *ob)
+ return 1;
+ if (*oa < *ob)
+ return -1;
+ return 0;
+}
+
+static void
+cmd_init(struct g_bde_key *gl, int dfd, const char *f_opt, int i_opt, const char *l_opt)
+{
+ int i;
+ u_char *buf;
+ unsigned sector_size;
+ uint64_t first_sector;
+ uint64_t last_sector;
+ uint64_t total_sectors;
+ off_t off, off2;
+ unsigned nkeys;
+ const char *p;
+ char *q, cbuf[BUFSIZ];
+ unsigned u, u2;
+ uint64_t o;
+ properties params;
+
+ bzero(gl, sizeof *gl);
+ if (f_opt != NULL) {
+ i = open(f_opt, O_RDONLY);
+ if (i < 0)
+ err(1, "%s", f_opt);
+ params = properties_read(i);
+ close (i);
+ } else if (i_opt) {
+ /* XXX: Polish */
+ asprintf(&q, "%stemp.XXXXXXXXXX", _PATH_TMP);
+ if (q == NULL)
+ err(1, "asprintf");
+ i = mkstemp(q);
+ if (i < 0)
+ err(1, "%s", q);
+ write(i, template, strlen(template));
+ close (i);
+ p = getenv("EDITOR");
+ if (p == NULL)
+ p = "vi";
+ if (snprintf(cbuf, sizeof(cbuf), "%s %s\n", p, q) >=
+ (ssize_t)sizeof(cbuf)) {
+ unlink(q);
+ errx(1, "EDITOR is too long");
+ }
+ system(cbuf);
+ i = open(q, O_RDONLY);
+ if (i < 0)
+ err(1, "%s", f_opt);
+ params = properties_read(i);
+ close (i);
+ unlink(q);
+ free(q);
+ } else {
+ /* XXX: Hack */
+ i = open(_PATH_DEVNULL, O_RDONLY);
+ if (i < 0)
+ err(1, "%s", _PATH_DEVNULL);
+ params = properties_read(i);
+ close (i);
+ }
+
+ /* <sector_size> */
+ p = property_find(params, "sector_size");
+ i = ioctl(dfd, DIOCGSECTORSIZE, &u);
+ if (p != NULL) {
+ sector_size = strtoul(p, &q, 0);
+ if (!*p || *q)
+ errx(1, "sector_size not a proper number");
+ } else if (i == 0) {
+ sector_size = u;
+ } else {
+ errx(1, "Missing sector_size property");
+ }
+ if (sector_size & (sector_size - 1))
+ errx(1, "sector_size not a power of 2");
+ if (sector_size < 512)
+ errx(1, "sector_size is smaller than 512");
+ buf = malloc(sector_size);
+ if (buf == NULL)
+ err(1, "Failed to malloc sector buffer");
+ gl->sectorsize = sector_size;
+
+ i = ioctl(dfd, DIOCGMEDIASIZE, &off);
+ if (i == 0) {
+ first_sector = 0;
+ total_sectors = off / sector_size;
+ last_sector = total_sectors - 1;
+ } else {
+ first_sector = 0;
+ last_sector = 0;
+ total_sectors = 0;
+ }
+
+ /* <first_sector> */
+ p = property_find(params, "first_sector");
+ if (p != NULL) {
+ first_sector = strtoul(p, &q, 0);
+ if (!*p || *q)
+ errx(1, "first_sector not a proper number");
+ }
+
+ /* <last_sector> */
+ p = property_find(params, "last_sector");
+ if (p != NULL) {
+ last_sector = strtoul(p, &q, 0);
+ if (!*p || *q)
+ errx(1, "last_sector not a proper number");
+ if (last_sector <= first_sector)
+ errx(1, "last_sector not larger than first_sector");
+ total_sectors = last_sector + 1;
+ }
+
+ /* <total_sectors> */
+ p = property_find(params, "total_sectors");
+ if (p != NULL) {
+ total_sectors = strtoul(p, &q, 0);
+ if (!*p || *q)
+ errx(1, "total_sectors not a proper number");
+ if (last_sector == 0)
+ last_sector = first_sector + total_sectors - 1;
+ }
+
+ if (l_opt == NULL && first_sector != 0)
+ errx(1, "No -L new-lockfile argument and first_sector != 0");
+ else if (l_opt == NULL) {
+ first_sector++;
+ total_sectors--;
+ gl->flags |= GBDE_F_SECT0;
+ }
+ gl->sector0 = first_sector * gl->sectorsize;
+
+ if (total_sectors != (last_sector - first_sector) + 1)
+ errx(1, "total_sectors disagree with first_sector and last_sector");
+ if (total_sectors == 0)
+ errx(1, "missing last_sector or total_sectors");
+
+ gl->sectorN = (last_sector + 1) * gl->sectorsize;
+
+ /* Find a random keyoffset */
+ random_bits(&o, sizeof o);
+ o %= (gl->sectorN - gl->sector0);
+ o &= ~(gl->sectorsize - 1);
+ gl->keyoffset = o;
+
+ /* <number_of_keys> */
+ p = property_find(params, "number_of_keys");
+ if (p != NULL) {
+ nkeys = strtoul(p, &q, 0);
+ if (!*p || *q)
+ errx(1, "number_of_keys not a proper number");
+ if (nkeys < 1 || nkeys > G_BDE_MAXKEYS)
+ errx(1, "number_of_keys out of range");
+ } else {
+ nkeys = 4;
+ }
+ for (u = 0; u < nkeys; u++) {
+ for(;;) {
+ do {
+ random_bits(&o, sizeof o);
+ o %= gl->sectorN;
+ o &= ~(gl->sectorsize - 1);
+ } while(o < gl->sector0);
+ for (u2 = 0; u2 < u; u2++)
+ if (o == gl->lsector[u2])
+ break;
+ if (u2 < u)
+ continue;
+ break;
+ }
+ gl->lsector[u] = o;
+ }
+ for (; u < G_BDE_MAXKEYS; u++) {
+ do
+ random_bits(&o, sizeof o);
+ while (o < gl->sectorN);
+ gl->lsector[u] = o;
+ }
+ qsort(gl->lsector, G_BDE_MAXKEYS, sizeof gl->lsector[0], sorthelp);
+
+ /* Flush sector zero if we use it for lockfile data */
+ if (gl->flags & GBDE_F_SECT0) {
+ off2 = lseek(dfd, 0, SEEK_SET);
+ if (off2 != 0)
+ err(1, "lseek(2) to sector 0");
+ random_bits(buf, sector_size);
+ i = write(dfd, buf, sector_size);
+ if (i != (int)sector_size)
+ err(1, "write sector 0");
+ }
+
+ /* <random_flush> */
+ p = property_find(params, "random_flush");
+ if (p != NULL) {
+ off = first_sector * sector_size;
+ off2 = lseek(dfd, off, SEEK_SET);
+ if (off2 != off)
+ err(1, "lseek(2) to first_sector");
+ off2 = last_sector * sector_size;
+ while (off <= off2) {
+ random_bits(buf, sector_size);
+ i = write(dfd, buf, sector_size);
+ if (i != (int)sector_size)
+ err(1, "write to $device_name");
+ off += sector_size;
+ }
+ }
+
+ random_bits(gl->mkey, sizeof gl->mkey);
+ random_bits(gl->salt, sizeof gl->salt);
+
+ return;
+}
+
+static enum action {
+ ACT_HUH,
+ ACT_ATTACH, ACT_DETACH,
+ ACT_INIT, ACT_SETKEY, ACT_DESTROY, ACT_NUKE
+} action;
+
+int
+main(int argc, char **argv)
+{
+ const char *opts;
+ const char *k_opt, *K_opt;
+ const char *l_opt, *L_opt;
+ const char *p_opt, *P_opt;
+ const char *f_opt;
+ char *dest;
+ int i_opt, n_opt, ch, dfd, doopen;
+ u_int nkey;
+ int i;
+ char *q, buf[BUFSIZ];
+ struct g_bde_key *gl;
+ struct g_bde_softc sc;
+
+ if (argc < 3)
+ usage();
+
+ if (modfind("g_bde") < 0) {
+ /* need to load the gbde module */
+ if (kldload(GBDEMOD) < 0 || modfind("g_bde") < 0)
+ err(1, GBDEMOD ": Kernel module not available");
+ }
+ doopen = 0;
+ if (!strcmp(argv[1], "attach")) {
+ action = ACT_ATTACH;
+ opts = "k:l:p:";
+ } else if (!strcmp(argv[1], "detach")) {
+ action = ACT_DETACH;
+ opts = "";
+ } else if (!strcmp(argv[1], "init")) {
+ action = ACT_INIT;
+ doopen = 1;
+ opts = "f:iK:L:P:";
+ } else if (!strcmp(argv[1], "setkey")) {
+ action = ACT_SETKEY;
+ doopen = 1;
+ opts = "k:K:l:L:n:p:P:";
+ } else if (!strcmp(argv[1], "destroy")) {
+ action = ACT_DESTROY;
+ doopen = 1;
+ opts = "k:l:p:";
+ } else if (!strcmp(argv[1], "nuke")) {
+ action = ACT_NUKE;
+ doopen = 1;
+ opts = "k:l:n:p:";
+ } else {
+ usage();
+ }
+ argc--;
+ argv++;
+
+ dest = strdup(argv[1]);
+ argc--;
+ argv++;
+
+ p_opt = NULL;
+ P_opt = NULL;
+ k_opt = NULL;
+ K_opt = NULL;
+ l_opt = NULL;
+ L_opt = NULL;
+ f_opt = NULL;
+ n_opt = 0;
+ i_opt = 0;
+
+ while((ch = getopt(argc, argv, opts)) != -1)
+ switch (ch) {
+ case 'f':
+ f_opt = optarg;
+ break;
+ case 'i':
+ i_opt = !i_opt;
+ break;
+ case 'k':
+ k_opt = optarg;
+ break;
+ case 'K':
+ K_opt = optarg;
+ break;
+ case 'l':
+ l_opt = optarg;
+ break;
+ case 'L':
+ L_opt = optarg;
+ break;
+ case 'n':
+ n_opt = strtoul(optarg, &q, 0);
+ if (!*optarg || *q)
+ errx(1, "-n argument not numeric");
+ if (n_opt < -1 || n_opt > G_BDE_MAXKEYS)
+ errx(1, "-n argument out of range");
+ break;
+ case 'p':
+ p_opt = optarg;
+ break;
+ case 'P':
+ P_opt = optarg;
+ break;
+ default:
+ usage();
+ }
+
+ if (doopen) {
+ dfd = open(dest, O_RDWR);
+ if (dfd < 0 && dest[0] != '/') {
+ if (snprintf(buf, sizeof(buf), "%s%s",
+ _PATH_DEV, dest) >= (ssize_t)sizeof(buf))
+ errno = ENAMETOOLONG;
+ else
+ dfd = open(buf, O_RDWR);
+ }
+ if (dfd < 0)
+ err(1, "%s", dest);
+ } else {
+ if (!memcmp(dest, _PATH_DEV, strlen(_PATH_DEV)))
+ strcpy(dest, dest + strlen(_PATH_DEV));
+ }
+
+ memset(&sc, 0, sizeof sc);
+ sc.consumer = (void *)&dfd;
+ gl = &sc.key;
+ switch(action) {
+ case ACT_ATTACH:
+ setup_passphrase(&sc, 0, p_opt, k_opt);
+ cmd_attach(&sc, dest, l_opt);
+ break;
+ case ACT_DETACH:
+ cmd_detach(dest);
+ break;
+ case ACT_INIT:
+ cmd_init(gl, dfd, f_opt, i_opt, L_opt);
+ setup_passphrase(&sc, 1, P_opt, K_opt);
+ cmd_write(gl, &sc, dfd, 0, L_opt);
+ break;
+ case ACT_SETKEY:
+ setup_passphrase(&sc, 0, p_opt, k_opt);
+ cmd_open(&sc, dfd, l_opt, &nkey);
+ if (n_opt == 0)
+ n_opt = nkey + 1;
+ setup_passphrase(&sc, 1, P_opt, K_opt);
+ cmd_write(gl, &sc, dfd, n_opt - 1, L_opt);
+ break;
+ case ACT_DESTROY:
+ setup_passphrase(&sc, 0, p_opt, k_opt);
+ cmd_open(&sc, dfd, l_opt, &nkey);
+ cmd_destroy(gl, nkey);
+ reset_passphrase(&sc);
+ cmd_write(gl, &sc, dfd, nkey, l_opt);
+ break;
+ case ACT_NUKE:
+ setup_passphrase(&sc, 0, p_opt, k_opt);
+ cmd_open(&sc, dfd, l_opt, &nkey);
+ if (n_opt == 0)
+ n_opt = nkey + 1;
+ if (n_opt == -1) {
+ for(i = 0; i < G_BDE_MAXKEYS; i++)
+ cmd_nuke(gl, dfd, i);
+ } else {
+ cmd_nuke(gl, dfd, n_opt - 1);
+ }
+ break;
+ default:
+ errx(1, "internal error");
+ }
+
+ return(0);
+}
diff --git a/sbin/gbde/image.uu b/sbin/gbde/image.uu
new file mode 100644
index 0000000..82e6f2e
--- /dev/null
+++ b/sbin/gbde/image.uu
@@ -0,0 +1,3305 @@
+$FreeBSD$
+
+begin 644 gbde.image.bz2
+M0EIH.3%!629364"&9H\`Y1=_____________________________________
+M________X7"_"W7I7N^[5>OM;NXTY!ZK'VZ]]MS?=??>>OM=[?/I-WMSSFM;
+MO>[&RU[OMNKV=>\:^>K>]N;5WN];V*7F[O6[[[>G;WWRKZ]ZK7?7UZ7=[WN=
+M[?<5WWR[97V[M=U[>UAYYW'WNW<^^WUM??7U]UZ=>MZ?7KOOK[;WM[O>]'O;
+MKWU?;[Z^=*YZYNLNKYQ7KWO=Z]W.>OOO/HR>KV^N>L]>>Z\O;ZWWN[O=IOK*
+MT]>WUUS=V]:Y+[/OON]>[?9[GWS>][OK27KI]UK.N]]]WOJW=URR]L[[LU>\
+M77VSN]YM\]Z*]NW>S[GWONYONZ^[SI>^^YWN[6Z^YUF:Y]Z^[[=]=\NNY]Y;
+MO>^NEW>S=GGKW>^^[;?.[[7?;[?'7N.^YZ^UW/K[VY./:N\M]S:]WWV>^I>W
+M./3MVW>]GKR^]5K-=GG>^\MW'NWO>^^]?/7O7WO/?;O7UU[O?=]OO=<9@7V]
+M<\V=W5OOMS7>;[[Z^O/OO=ES[>7VON]Z^WE>]OI\^]ZE??+,[WG5FW33V;4/
+M:]O-[M:SUWWOENY[S*]>[/0]]O!=];G>MWEGOO:MW>K?7NW6NVHO6NMWOJ\^
+MOO?;K;UUM;>VV:Z>YV]T]ON9MCWGMN[WGNCWWW=[Z[5/O?5=??;WINOMG>TO
+MO+??;U?;XS=]VK;<W>UYYYSNOGWO/I[WTT=KV[JI[:]]WU[?:NFNO=W>GNZ]
+MFV]W>WGJ+S;OO>T=+Q[7UP[WKNOHR;C>M5?=SMNU==O>Y3[[?55WW;U\\U>=
+M[>^;NUU?5VNWGON^^>W3ZUKNNN[-WW>NVC4\QUO;N&77;>W>>=\Y].^^[[=]
+MZWW?;YTJ]BFW??=O3M[V[[Z^=Y?>^\L];;O?/OM\]MON[N.]Y[KSV[PWN]W=
+M+ZK=]/M]SO>]GN^[>]O??=U-Z=[OGWMQ][ZP3[[;OK[V=]W?:ZZ^]>]SO`.)
+M[?7>[=U7O>OOOEM]]W=?>VUE:-;,ON]Y][O5V^^][OK[[[V]Y]WVV]M>@Z.V
+M?7OFU\???=[[E=WM-/MRV/.9MJ)6>[[UMNWSOGQ]?/ON>]]OGE/'K;?=J[ZU
+M]?9=U\[O<T]H9OM]WJVN?7>ENZ[A[M[O.2]U[O<S-[7V^[YT^OI]W??/HO?<
+MZSXWWJ7N?>YW(^?;[?;S[)OO=][SV8O?<GS.]7OO?>WW9:WUWWWOGN>^^]/?
+M7GK>7N5[VRSYNVIWVU>YNU;KSP9=9][+;[VOMMZ^^C[WO?.GIWJ^NN^ON]WN
+M[V]OO;Z^J[UMNOGFN[WM4]>OO=UVOM]X8]MNO>\9&^N^WU]WG;CJ[NMW=O8.
+MZ5?9U?==R^;?6N]O>M:VUF]N]N[,VY'K*WSW=XZN[O>K[M\Z:?7>][IUK%[=
+MV.]N[O6O?77=OIWMFGOG>:?.^YWWS[SX5>ON^VY]3WW=[K[7O6^^]?7>M]\V
+M/<UUQ]:^WOMC>1TDZVWF[U>[IO;"[[NVR]:]GKWON^\S'>]]=O/?>W>K[[O>
+M6O7K[V^CO/NUY?=U[O/7GSVKG;[OJX^[S?=;W5TSO>[Z^USMN[O?=WWVUZWN
+MWNS[[O>];KQE>MWS:US8^[=[ZWM>G??/=>^9W=?;OO'CW;[[X'S/<YW???==
+MSNY"J?X`3``!&```IL````5/P`3$P``)@`````````3```"8``-&F@``:&@:
+M`JB%5/_`````!4_%4_P```````````F``````3```--`#$``````"8`!,3*>
+M!51*I_@`)@```-)@`$P`F`3`3``5/P`F``3`T-!IH:#30R!IH%-@!,$TP``$
+MP3`)B8)@C$P*HA53_P`$P``````````!4_#0,0#0#0T-`T``)@`!#``E3_3!
+M38`"&```F``3```542J?X`!,"8`3`F`&1HT9!IH,AB-"8`3$R,`)@$P``3)A
+M-,3)@$R8"9J4_31@3"8%/``3`3``&@*H90TR&@T#0#$:9-&A,!,3`)@`$PF`
+M$:81FB:8``F``)B8`3*>!&3`1B81IDP3``!,IX!,J?J>`":4["(B(J<C``(@
+M"(`B]!&1```$0`'41D1$`1``$0!$!L$8```!$1$0`&(1D0``!$``TD9$`1``
+M$1$1>@C``(@`"(``!)"$G*%(@"(`B`(B(B`(C\C`(@1&1`$1$``1$1$`1/OL
+MKOMIH+QS3GG]CAF%<+B]I?#`V7-_"-Y#LDSH9(@]7VLR`[HWF)TW`6KW'/:[
+M(XX%BKB][$SP'KU?K:_(1N9_H^2@JA3P.J%L@];AP_!DU0#Q+M]J`25MW8VF
+M*XL679-BSK:$@%7+58\#PQMY/2W?@M+-W)\E?]B$TIE`=%,17/[11-(;\I/#
+M3K0@:YL-M_DJH(H!9LQB\>C6A#Z#2)'V&%ST*32:^<7Z,WD!2Y^/JR(;=ORJ
+M>,<*PVQ,R&#QC8-[$9MU[)EC?'-!R@=(2:X0\CG?++DK!YO[LOU*6-G(56S[
+MML*@CU_P?SNY*I`D2CK+X+*<D33>[&5,NIT^H*%`ZF8Q<'K7)XIWZ8=1A[<'
+M;TS"6Q^U[E3BL90'SR0^'D=A@&E@R::PDR3?^0@H869KI`T;Z3$XA/LY\]1E
+M/C0"&C\V"&S3KB$?U0>XUFH\Q[N+OQNKX'*LW]</]KK<$L][=G,6P<.>*#OQ
+MPCD->$>?-13U@<R>8#8*D.'R)W#N8IFE^^*R@S%:&O"R%#-'W?!YNM3N5PZ`
+M/#!F3E9,(+E-0&:61NBQ11&;HL-7T,4#Z:MA]`E:==_$3](_^+,-FT4#KAN+
+MBE/&6*K[.Y6IUY,+C%C*7"\Z&7P!BW("$L:%`G#&<?&$\=LA)""T$O078Q1Q
+MMTR<,YL&6.H!.P$NQ4C\5:*(-AQ?@B2`II;>'-[R2,L8NQD^'8NKDO6%V-S]
+M.XJ9#YRP6=JD;F2*J'B=3QEU]OH\;!N7\Q_OE"0`"(B``(B(B`(D-T3;PKMS
+ME!5Z$Y68,"-/Q<GA,D'YFYSB^;*9@X#0S:+&?]I6A(/?N%TE=YWX'"3:")XR
+M@>>PAYR72R[0T3+!<W`!BSS#NJP"^QE)#]U8NJO;%H#8@ZFPD5^(XZ5R+S?9
+M,;7[T@)0"OI+_%3*6R$4M)._@@GW,9&OV[[;-#T`VF``;R(4:!QU[=*3AX:Y
+M]QUY1>9,F9+<[0LJ,'K!]IX"V'V63B#J53F7=OS`#2?&W!1[9G`*'&?SYV2N
+MW5W]E$!P+VH9'#BZ;8[/'<$>1S4//EG80Q8;"06GUQ=I<,<MC0,MGN:3J,A'
+M(Z")BMPGM_S&X/51E6LW*\%2%.ZY_$-(C++?8^[IC?>]Y7D(7FTGIUAK$E9@
+MG$JE)3V?>1[&:XGBU&L)E>&W&XYK%=/0]20P38X^,$3A/B+Y`![Z4:ZJ_RZ3
+M5O9QO4@7/4:,]*_@!XU8;!W>8B]V%ZATFI*&3JQU@A;8*IW`S;E&3A9=#W.J
+M`H@<XS:A&850DGDFMDVN.17D2I+*HT6*RA4%6L.8R2EE9WHTLWF.JTJ`"I18
+M<2LSCK4:D\M3#S"^@YI(Z?L(9(?EN43_C'54GG!XRLK6*(4D))8LW^RF[G,#
+MT?*CW1;UJ+,6E'GXP4F8`K^F]0@\(77%3F)+%RGE>5MZ#+DN\BXYH.2C>"<%
+MJF?>2G6_7\\@-/$FU7[5XKM.7&U+XO\E7\+V$,8:`K'Q&"(^ZA[8'4*!.B=9
+M_TQ@Z596F1WTPA:,40MQ5CS*H;5H21VQS6@9P$^U;$\D=LR-1HU^42O(>K+J
+M:L@X=VR&SWEZ=!Z.]1PX^+V[E>F>=!PSK.#KV'"G^,MUMU;(HF:<A\21.#;Q
+M>QL+H*ZR(TM"9V,/*)3X+*%=HLY<)W^`#[H(PPH(<N./[$:SQ!O7,$8?+:GE
+M$X-TB5BUFQ<W'N?X150PB5$'_%R]N?>$?;XL0)TC##3VW2WP(S!3X5H#\($,
+M%2D%>[/>W\9=))4F[$:]V]J?9X@\OUKEG;3272J_<IN69W>R(T:R##2:"L;O
+M2"SKT7P)Z9$@T"?1B4<_JD--\(C4:'3+J>($7XU]F1[G8;U!R)]'8+M/WAYV
+M=TSANI97]6K?,&AG`GA(6*M,JV:*BL<25%3%@Q?0K8E>0Y:U,MY$"J0WSD6M
+MB;C;W<?7(\=W=3J9`NQ76BR['PN-'R<W^1[DA.AE["+&3)4%U;,RMP[Z;H.]
+M*)=:2B9!)V'%47_&7_F94`TK<#7B7+,&",N*X?5/?KKN,9=PG)>[A*5#-WKZ
+M^4*GD`WKYL[5FCI)Q-KE<DFT7L<I0UHML:XC+O#5]<UP_#\+B6N26F([)0Z>
+MV\Q\2*G9'!?`BX'Q2.:J*%%LRJ6?D#\-)>B>NA7!,I0#4C-)7J6Z#7YF@L*C
+M]KEA9^]?J]LJ'.H-P>\ZY,8RA\7=_1<DN$0:^SGV<EB?.YKH=2$DL=]["KB7
+M.B<UMV0,$B)9\KS6]^:^W*A,B?[VTUP8K3K>97H'F'?-*@<??7:@&1P?V9-S
+ME68E_5XV*#)M^*Q:.L6!GK.W0I+A,;^S>VIJ4&-\)4U#613H+]^6-I6%=O:V
+MCO7^I];_E<=Q,7U\WB_KZJ>+K2SR`FAD/D$RN'675:N`\RWP\()^OKK():JT
+MB-E5L,!,=,GS;)@YE?/^^C[V1V_@"Z.P033VJ*^A!AE5Q_QVMTI'=[0Y^K5E
+MA^`0E>6=>&9X=>"XH>%8M6TT2.A_S::_D'=^LWN6.9\UAA,\J7;C//PJ]C)^
+M:\;1(O$K-4W@@=DVC2^'C'U-545^#=I*P.58$<K]L[5VSY#G-NKT_T>+T"G9
+M?LE2U\W^6`M;<IL['I;Z+FP`<W[=6R,4=U?-W\IS_E;`?JEHV#=)PAHWIJ1C
+MZ-VX4EWQ$B<OH!0]Z#C,7ND!!^1PSPQT>5C&^]2S'&$)=V502NFDJF^HF'35
+M.\%4(M+Y.VMX)OW?Q1\?-M@8V^L!?6D2M6!SI5*!^A$6L"^"^]L5Z"9E#XI1
+MA=-5"CIK7Q=2/A/V83`RIEWD-TV2!DX^H_U"@MYZC(:O=;W;L>9O=%W=>#U4
+MZ]L=2H3_W"<?"8=Z@A?T&T-$,%]NH]0_NG>N..'ZVK.^%FCDG?!F8S%$\2HO
+M?IY+618!N;^FV85%.C0.Q3A+1=ZS+U<(O&G2:!=;$'SW;Y9B$$7<<^.$P-_2
+M92+RQZM'_`>12>R<//V4^^FBAIN/K]$YH+.YZ7P&I<1&$$O3_WU,D^L@RO(:
+MT%Y4&,.+51)V5J60-&D_%2\ZER0VB;19<_Y]J[L#D'P9'9:3',PE8*BW%4KI
+MJ[&D6%X;X?O=IKY`Z$$SF>'1@N72M<?WBG?Z58T77-PEK2=6.,8&YN]J>-XF
+M`8J+I5'F*J(75:2GKH]V&^!LX--5ZXQ20I:+ZK)9>/=SC,IRG7JU#6N/C)Y<
+MS=Q(T2=4+T>T_O`JEA6JWN"8!+^))2+?432]+))3O"3T&):8C2>Q&>V&J98.
+MY9\\QVE%LKZC=O95UY)^AQW@X?!=]WC'""_B%8<[^TB\8X3#WQ!WI2\;+@L$
+M0HHMOQ)Z4X[[MP7=(>MNO`?E'K--#20D@$A&,;!(?.LDU'[7I91VV#_.BV[H
+MP5,5RU@>O)>$[<#H$8V"V0&`T1&XA*F-$Q<Q2"9``GXQL(Z].FXO"[G-46%-
+MLBX_=&$T89_0JA>:5A\`.:-[2;/P6NMJLZ:3GP`/.Q.NE(*#:LAXJ4-99LT)
+M-ID,,T/2_VS/4.TD%7-$RUDT;UFQFUKRFV7Z]C:<YLNW]L)Z:X5[KS@O9-5/
+M)%H95QON)(".;G*JE3_NO95RQ('G,#T'-:\292P&>E"-MM]6_QCH[T`7!S74
+M3YOR.*:#X;9I+O)J"G1=U].?[.YM3;VAGY&-70TI2(!G2K=RJ!>?AB'8:7_I
+M/L+4I=?6%ULTFV**N3]#K$X5:9"ZEH-,/S;H-#S,1CQ.T-PJPDB3@#Z(VD]I
+M]L("A/`:@7Q!?U>UG_@Z:/+Z#SN$E21[R')ZR,X<>",[`S'0FR["<G."Z<!R
+M@X>Y\^$\#S<!%U`OX1`LRH%-KG6)=,"V!%=WV+Q>Y""@M=04)(OCRT+42_7^
+MS&V]B&#`NB-KE\MW&),/\/DZCN(&PJ<>PL"M)$A1[GS"N<DOIUP[*WUH./@\
+M&]HP?<BT(^TTUW)GMJUK[R?`GIL=4SATC8(\Q^QL>9^)'BI&VQ-)9W?<B7.G
+M8'<D6-&'"O-/MO4\<:E1IN8O=*8EJC#.`3)R@##0-FW02,U=MH7?C86Z?KQB
+M=!:T[H\I]/?K+/K0@8S#G7I<A%51[T+`C-*>IK,$>N,[K[$)--3"Q<^7"S2V
+M5P5=BK%%/<AA=+_BH:G-8BQ0OQ9=!$B;D04@3A'9$JVVR3]$8#0![_Q^U75@
+M796@9?*^M$EY$GS!!UBK_OM"N)M<&5OG5GA?:U;ELC:XX>JC62*6F[)8%2*;
+MXTE-2&)%]LA<B$SWEZ2H6/X(`DKW3Y%C/M,.--?.$'!53K$G[[8@P52)8E/N
+M"&K*;G"'3!6;G\O/S[W,Z:J,#RPG!*QU7OQ?UR:<K0=!8'44JV<U,UX-&W8@
+MR<HJ>DS\D\_KUW/UQ+4Y3C'YJ@V0>&D?'%&B/["^4C0FWE;+:I:;MPNK:;1/
+MV6=(0V`+H%>:KN%*F>R!F3IRS8%6)8[O17E<>]-/@.$[!-4]>,)Z:>GFGU#2
+M5O_)=\5B*[(0?ZV1$EYRT^J%P'Q;-WLC^TMI[=)=$!$XY^`K0GV6@A+QGM]Z
+MQ1;,"[0'!97SB_'S/B[Y>;<50/2&/J*6_AW;0A8&RP?SG[/R`]A?7K`$P5D\
+MKC,M.O(*M3/6MB_FL\<"?II+/35,(Q+X^TJV\NJ*QZ?]^IF3JGH='PB!?H8_
+MM!3FR=HN>P1C;8%NY]W2@%&AQ"9L:9_]0E'\8KOI\7RG8/5\/FHH8N,%#4O\
+MWES>AS5&N!J@/#L:M`)C;6A'!=FC_,W#.<&^R&Z+"`GQT2?K(#KS(L+`Y?)K
+MCP#?<J4G5Z9B8EI%]IVG"XF"X<;PC0`(#E?(D.L-!4(A\H^6#$HEHY"J-[G^
+M\SND>C;'2>1&L'G=!`EMUUAH'[&J9K#BVC>6#G[E0T)#7PRV9=7O\*"Z3A;'
+M/K^8T"YBF15.'XV)EZ)%!TTY@$42I8=RY5RV.6]L<AF4UN,O"QH(HC?5%93@
+M*_B(]N93V0YW'-:3%E-#@'J#.?J5=.0,-@C@=R<;`&?><_`]T8'"9LQ1+AIV
+M_ET<\KE<XY-:468&HV-0K?I7.*1H]3NN079QL3]QR&3$-CBGFEJFO_J0:@5C
+MUG=/4JQ-BBNNC22>*G31(1NQS?#<TSR[#_$2-XAO'P+!Y0`N.#O1/ORKZ$,-
+M!"V;'=:3=Y<V.O\\H`BH]`TA16X['']L"S:_]K[QQE'_AL:S>W+0R%\EW$P+
+M(;40NK"#=5#>_2J(RP,/;`>8L.=YPK,`LJS.M7TL=`[)HJ4->?>A(,V)$1P;
+MC?&;M^C;OS"EIG3EUZRI*'ZE1[6^3G2")'U(1B9%@%O^^@%.&T`@.A,67:O$
+M?U`PSK,E%-M+JN?K/[U=_$HI.8,%8SO0=;HJW?+L^+%5PS8!S"\D<%&/@,"%
+M_3:!O6-*4)GKFTTEFN87$H1)/QB>.;=\9D(.JC#8'V55AZ'X2J\<5E#<I;X^
+MV\WKH+G,1P<17)-]OX^,:/CSSB;6PZ0'[NTX?U!_:8^P.%J>EH+-B9B/YUU[
+M_V/&"'D;YX%P6W699`7$F]`B9`'=+RR(,7E-T;S>%0C_:I;::@ZU>N!HV0C`
+M$/N33[_WS19QN$(S`U&3H'L;U>_39M[HW4=SWESHD\NZ88-9BO6]U`#0"G!-
+M_*I!W.PR<LVA7TNI&O5L>&I"*#F;;1Y`"J4:XV:S`6$>8%M9='ONF`9-L6BA
+MFD_(\4P\#V^J6\V;S'IJFU)7CIQJ?3UCD59)4THHV;4[),QJ(Y6#W8$)P-V7
+MO9^^38)Q9;U/Y`6A*?\E)05BC=M-(STFE@+^?)3(#DWZ']9CSJCL\^FYL5?,
+MN)D<=^%(!YN60H%B1I+>?T.ZL#.9(CFRMO0V?6-P*!'?VL^V/'U=2NQL36X-
+MJ0\E-_-IMVYNJ<]Y;D,F1"DX*3T=;+[/9L#=`<J0L>ER34&`^:F%-F3O#D+Q
+M$8:*^I38G_WTW]RN)H:"OPNC+.T7O32F\%G%)?+75LPM:$F4&)=![(&3&GO@
+M&FC(7;_IL@EH'Y@'K15QK<+IGBWKY=O7J\/08C6[B75=ZJXXIU233\^HS$5&
+MSWI5H9.%.!^8018ZZ5T1>Q;3X"-<_\V;C!4V8=UI%_D<'-PBE@]9Y?-K6*9)
+M?)9P(N&++8LPNNVC-:-\2&FNL>"4P#DU/TP&X')*>IL;T7$SA%R&0[PYR7P"
+MT4WG)6?6BXNDA#R)2191XTUDG;<6QM.?Z>-O9+U6V*!L-8$(`&ISZ62I]PC3
+M.LNLY4=D4IKZ9LU5DCW17KK=B+F%#D(Y<QY>%YJF$T*)&`FN=POR'"+UNF=G
+ME`B-F^ZU,6E^A1XZAA3-U?6QQ>)B1`'C%Q*<=@IM+LR>"4%)26\YRROK"S8=
+M]E08V/V<59::9ZWMS`@_PE1>7-B+_RQV(F(\'(.R;$5:503.3Z2]@$[A_MM4
+MXO#;=A7H+RE\)((TJ[T4"9,(W-GY4&Y227=&CEE_XXAJ$O>%X):'^0?]V"%-
+M"S_*HIT)3?"9QN%\LX>+5,C*!!"AKF0KGOB@H72HV<=K(+83-47Y$A9K\SC1
+M+9RD7,/]9!ADS"$&_-Y$07SVM#/Y6>]W$H@,Q_"`4`WPPHU!Y?MFGDUS@9I\
+M9SA/1=*;<RF$Z5#/E>UR<V<>-IFYBFM33W%(?]L^U>-,9H]TP+Z-JP:5ZDA+
+MNC*8)=7)SQYJ5M;*NW^)>3862XJI[''?<WX4LY.B+5@TF$<W)Q,HXH_UD!J'
+M@I'+IXVV8%6N,:@J,*0>,)'ZGS%7R@KX/RI*;\3FJ<F>3:`5-DDN?M\GQ:?`
+MM&!="%>ULO1U;(\?_8I)+S"%8B_,Z,3:F&X*.V5-55?[$ZD`7PVN80)P=U3Y
+M&@EHD0725=0VK:.,$'K!3592LBPUU#;80U7GW!\THD2-=%Y`?<UFU6>!7PQ?
+M"UV1SI]&Q:`7IL8H3W&JZ$PIYD?W>*#I:3FJ^+O:,5WQ;)RY6M0$WMB86T]-
+M)A3(#!>0BN,A?R+NW#=1P;[>!/(^F6'.=(:B]X_10T:U@X.ULJ;B1N*_->X/
+MG*#;,PE#\DUE?)=57*HC&N`M1#1;Z7@X8L949)[*!V$J_4.N*'A8L(V2KY&@
+M*?Q2'<+W^H'`L6),P90)?ZNGI<F'NNH=E90U"@?HNI`S<+U$*UGHS06.Y-S<
+MI44+GH\'\/AYEXV<KL22OQ,8TZ:+A91E\T64`B.NG?C3DF*2R!C^#TUH!YJ:
+M)GO<)"QUZ&$NLX`:N^!(KE5Y+$@%KNN+M14:[L0>7%G]!A!E7:\"ZNJDB*\J
+M!^_4^"2K3LM<ZGQA8M"5&MD=CM4N:0%#($]Z#=MM:?%;]?3R'+XB2/BTJC.^
+MK<N1CQ"G[XUDNX*FLWVT/)U:!(V9(^-48.]K)2ZMJ@R7))I8D0OOR#.$%TR`
+MA`:2&W=ZP,EVMT-DIV$&?C.R#8L"09S.^46X/=?XT_9!$`<!PN5_>'C4YNVL
+M3KK4M',5@[SKX7;,=6I8V]URL>G3+J]<3?8U1T41GAFWG=>O$6-A%%1N>Y5'
+MAA5C`OGFC&`.QO'K(!N/5TU0MTOS4!.@X6V=R<7+GPK$>/VUZ(]-<B=%N<ED
+ML`:,<EKJ*5'(G9"ZLFI1-3]78R%]B%"O'\#=[SKI/?)W<D`\2U0URSC"!>N0
+M,!NU?9'H9B34VC116\#V/%?N3Y`AH8HQ=-.,1I_RY]?L29VQEC*!N0Q)@W"V
+M<J((@0?/B+]<ZC.5(BWH,^I'$_"'2=WJZ;\?(XLI\^/BO(G9=H"=>7+ESQEL
+MX'416G$O>>UK7J9&\,5/6M.7'1;=IQ,^;$'95%XPFEBHT35LGI6<5.B;1#O[
+MM^WBEM$5*.\1<M>N5\32DU:I5LKE27E*7:9DIPU.XJ74&X3;N_U"FH[9G?:>
+MA9EXYF[3NS,32%#UL?NNQ8"Q`MY?UJF\%B??OZN_6[8*<4OG&79=;@0_I2A;
+M2A#4BWS5+!^O77#&<,BK":<5]B@VN!59?T_XU!4CR+8>N$"R/?-DT6E;>8;5
+M];61"409>??#$0J/*[R&+*>KU#1CHW%%P#/+,U;O/^[G!/C;H2)&-DWKB%?_
+MRP-7HAN8VZ=)(Y-?2MD9/WDO9BW_BI%BHE[^H6%52<6`WM[&_W^1(U/X9]`Z
+MKC!MX(^Y)9(0:SJ?ZL<(D/#0'F$P<8SIC=`@!T%E+-.;E:&.>N]K'W73"A"I
+MR/.5P"<_5,.H:%.EQ388AI2UPFI!PY1IHR^=2!A%[SJ[J<;A69C1<6RM-)_H
+M$:ZC7*@"&/N<[R:DP8,X]=H7]7,A2F5$#O&?IP#A4OM$VYQ5<\G7SL7<O=57
+M5YKQ^#/S)SZOB?QO$L'Z9<)83=HA,RS8^U11R=$S\\2S^=.RE<I'&8FX\3`;
+MJV@R(6[>6?!9):BS4GF[W>\\4`\@=5Y:\.AP?<+:_CSU-4]5CWV\I!I2Q;N>
+M,"ACP/KL,G1GT;(I^A7F%'":\X+[+A/@:++ER/\WC$G6SHSS**)ND9-B:O[<
+MW4O;4C)P1GAU6MV(JIII=C:MB?`2<2N<6P$0K<HQ6%;.U<WU.<`3$":GVL-8
+M7U'DW##L`*IVB0K!SOW#B?7;'D[N9.?,<S=GF616Y5^"3=RG1"F7P-J"1>T=
+MC`FYE>3RU3582+[^_7_3!;QDZ(6-&:]L?EU7%_9&E&UUTXG@+;CU&6H5W%2X
+M=,GAZ`VGO]>X2#BB44LM1J<^U=AJF65K\]FO1SAO'K4$4EH[:.7B*"<`<AB&
+M0OCB[CPDXW$G1?W&W9],KX*X#"I;->V70/_=UFLQ]C#B0QU<KTF@]TXF`?$"
+M&^T%Y<YN'4L%63FNY")LYQ$G,M/Z0-63"7WHQK$5WN%:CIU;6SN4:7?L\,.;
+M;!U7MOGX\=*YFK,^@![24V?*M,XB$?+5<X%4CJ'S^`C>3["TN?P2R"2-=M[(
+M%*YBR9;'=-^%_)KII?-BX(N^10+=9%]&1G<<(HY'LV4$;HGG!/NC2"K$EC%N
+M<!\S!EID>]C/=\A?'F0YA-M)O8LN6?J2P+KC6!G\L/7,Y5J4_C!?F?6?(TNA
+M1R-5<CVH](2QA;DUZ^(I0BM2L5ZA)Y@Z(P/FM,0.HW6!0A[R-;O`SU5P/SUQ
+MT"N;)Z?1?ENJA3(5E3#2YOD.];X^4[,YG)3YM4^V5]J\6$>"O8MN91<$R8HZ
+MOJ/@?&,$DOIYK5T;GF;PDI=-'>HC`F"UKP*(@/K@.M,E\,D6EC<#B:"JT_%!
+M,A-L>TQY"%[,J_YHRH<N'M*^4VI;QS62&5>O+3B=!ST^XY0.]CWT_4=4-GKF
+M=GTS/Q%3405:P0^"WJ'BFDH#%0!*Q;+^0.NIX&SD7\=G#-8GJ@XI6K]\=J`?
+ML4,6G>L$!MV_:DFC@91RW]9:B_.]LXAB+DVZ?:596RPY%D[6UQM6K?#'54GB
+M=^*F6L/&LI=V#>RYKLYTB0G5K1>7MEQSQ$3$"OI1VBK?L^LQXKQ=MMLX)E`^
+MSXQYGCY4#`-B;<6?YGX5N;=J>X&U.R1_0Z:_X>Z8G&9KQN@V\Z`.5M7CHUZ&
+M6X9-\%O1EH-#8XWHDJURS!*UL,6DQGV)I^E!S-/&GNG'[V#>=]%+`O;.\E_(
+M\5A^%VS"=O:-]EJE(:GG-Q5LA)+=%;G4.,R%GBLP]"DW<MMUY$WS%V7>"><L
+M\4%ZZFF=6O.W^\EC8O1B9J92<A1X.8)Q7GZK+VER![^H\W8!"SHL'N'TUQ/W
+M[QWHA!PA/F*:&PPW?WC]^WC6K;L?_J,9J0>Q*9#8NA>(.#L$[NN#4$[=%V6+
+MJA!"\+SH[+ZDS64G2Z:]\<4(`Z\WQV7_?5KC$,:U,*I.A93MU"5C*/3OWF^N
+MY5U\4*=B69='G@U.Z<JX<@=P+LM1[A@362.P/`RV7N40%'I(]/@3FAC8ZCKW
+M%BLO3V*[:[DHN_04+%=9)D307=.PRAV'X=@7TC^PA$++WM?-U&S?)2F@N<GE
+M>U3-F=Y(9MH;D=;DKI.T7H.LHYK(;SFH#W2TGC&^</"Z?L>^B`3?*)-]<U6Y
+M$9?+]KK<?/;4Q;EV\R:'X7.@[_IN$F=`K/EU5FJY:*=/:8+)\HRCXF8R&OON
+M9A>&XF0=FN];0\J]&`4,97O_^Y[FX\MA%0HO7ED*@!T-5KZ/='U<!#.]'H/^
+M9J0/U>\"BHX&"+Z-NO2J93U)*;0U21E=4)G(=6_*7)8/8$@;7^'@>6P5U<PB
+M-:I4>E?77EBI[7G)Z#RWQ(:GOT.2=6PM53(JK/(Q5M+M5!+N2Z>[QLE^I"3"
+M_GM*/%Q_[S?Y5NON&HH-OV<XVQ^SL"<.YK`J>)'R>YINM\W=PS'X?:Z\E![/
+MOYL=YW%4P6'[!B3&-[";FY5:/14I)DJJ^A`$W3?5M/>YVK89%I*,J9++%8[7
+M7,&!*/M<ACOTOLPQNS<.,>?Z=EH"`GH7$[Q[02J8GJ1]+L)G2B):1C:T#BK0
+M34TOI/E<)@BM-$@W1XA;*UBN+SA%AS(6^Q>,,?W4)U+Z+L2S$`W$?]F-4"QK
+MRVY!,@?+Z^7A;&:U?-025].,*NQ5ZU@HI&?1C05DM>#,%ZC<>>9C]3=K#4!!
+M'Y0=%&]K)[S24Q9%KK)D6F%4LYZ#X2KVE^N''=$O>S=(91#VXEM9ECQUU7X4
+MK^?&$M^W,:%;&V$E[96O^`5^@UG12/.'/Y>SO(NE@RZUI!&D)3^;4A5J![=9
+MX'1^$AMTNB-?;(5*(4,PD1L(!K&KF3,MA.2\@_[@#GQL\ENC>@_1R138&`Z'
+M'==(@&?K&Q`A!7J0#IE^A:\,)BONS0CT('>Y@BJL!1O6T%7;'U_V<+;$]R[V
+M&_6I6T'[4EG-YR?-@R[4)/P.L99EXE-M6CRNB54_A?L\)4]Z'..=;^_YXH5W
+MV=C0P`:34M3LSR.6,S&[F-6PB46=)PD2R_*Y+'5<T9WXMR*C_VY0*SGF.`R.
+MJ(+)UY`H(YU:U]4=SQ;&[W![`)O_AF5823/B!#-:+;/?5'3[O',I1=DBI]Q/
+M;FS'%=F]W&(#>@H?$5IZ*QVI%[S*R1];4Z@DBAN_TBK41T.KK@4J=)H\UY#V
+M,$2)3%_6+?)EJ@5M,-RTJ(O[U]!"$!5((LKP_7C;O_9"U0?T0G>\;*=F=W#2
+MFG".-87"3VM)<%>2%9F\;9L2W4L;/AN>TU#30W2?9V52QW+YV6*<E$82NR/^
+MKPK%8*P@J6F1A9?<1LUT!48SC[U6=)4.FL#D)ID:C(O0>6Q"4TRU.W/9]%YP
+M=UW030;';"VAH"P_D'Y0`FIF47UR1A9FN;IT1E#CU-,O0O9[2X"=VEF"=@TR
+MO/RO)UQS"#RW3E]LD&RM8%_Z<$"&6+MX,8J(9KI;;YSZO&+1E[!K%/I?;07G
+M6)^-X<2[+6\MS13DJUVQS*@6/!)/=?6<>XTDIBO;OG_P'43I/ICY^Q7G"K!A
+MOT9WY#C`^*EO/,[19/DR+6'`',9DN5ZWC!;$;AHK8U;0=TYXM"E(VUR>:^#2
+MI;3.(Z#KJUBXJY?;C;2+C:(`Y<G.O7&*VAC&3B>]/X0_?;/YQX$7($DML/49
+MFK[-.9VW?6=!%D]U)'+RZ@_RB&]TSY_GZE5PKEWZUJ8M7FJ0Q[C`@+OONQ1;
+M(S9Y`0&:[7#0%JP1H7C[/]QY=K;'Q58W/;Q/"=%>B/3H,2D\@0"Z9@5A;<4E
+MK&@5()-DC(_^++-%Y#E&%]*WI!Z5;F`K)/T;=I7?W`D#P"VR.V="XAQ!WM,'
+M4:N+:349^1U@8OYJ0`IU9-.PX=JYZ4S15FJ02L1XCDP%Z']O-'GIX1!1!?7^
+M]^.(G7/=FVS@GOPAD9U>O%U(#2HI<K6:C<^\IXG2:_,[(S:.=?:R)B<FQ84Z
+MKO01TY0<MTCJ$S[N\($[T*$,(W8/NJ1J^P.,=>$9FEOE\HA\D?/5ZTSO!]5(
+MPA;N]FV3*FAY0Z1\OI\+_P3RT(5T&6&*)VF<!@Q^+&H4^.*VYF)CF][UP1\:
+M@BB!#_"D?`E_#FT;M[X^1O8>5V'/%"0QPPJJ"U\-=8LEWO53^=N4!NF3NUKJ
+M[0#;QE-=]0X//Q+RD#??<^G]X1C)I)T]>_N_A$Q=U5J+I\"F&<9W-*L]_`Q)
+M'(B3*:?B5U.JPV2)[%?)<,;R2^-0+;`\.J+7!Y-YX\9!*NV8'Q9J*7'9_R6U
+M&2:T@O>J8#;L(8VFX3`WVU#`7`^>U1Z\PVGL*N1AGSK6KAGQF=$/SRV%[JF3
+M4[%NFI)56E26&-6JG1?VUV_1`AV`L'MCZ@]NG.V\7?P<<J-[.$^=PJ.BB#-H
+M&&_\&29ZT7S;F,+ZH"6<(]WL(^>I[I5)>87#&`^?`_!!5\QY9T>.XD3M>&SR
+MPX#[.^L`<)(*CF;%G-Z9)-^X;H/_N7`\5GR0Y7E6!S-L-N+M_E)Y,]GVC/OJ
+MUT/B<<D+GM%8"ST]Z"'LX(N4,(0K]?:_,R+RTYU[0F&`XT%_8S06<GR.IK`2
+M]=W,%GZ&-X!NV=?*G"&"*WJ"L1\>]A])X"H)?I+GF6`U]?1/6Q^Z-<#C[W<T
+M5W!EVU`*M-(SN[FMF;UF1SM/OR>#YN;],NK\^=^[ZY%&+Y)+%+5>W>FN4NR9
+M9<V1J!P#$=[C;<\(,B/?VPR:6)Y[P*C`)_U25(E2JR?)Y8]@Q$6"X$0AJ;U2
+M!EFL[X;4\TY^B1F2SW%<2L3F@5*F\?&-((9*0(];9$09?>=MG,5OAW0"YK?E
+M/W^3T%LN36+<YUC/SLB,YNKVI)>..5I:3Z*!Y0&T5?U&%<RY/X'Q'*<T`\#]
+M:-M"];!R^](?+F.ZK(V>*:$Q=@X>X7\,BL:9FMAZW$`*<0NH'ZJV?JU-T$6[
+MQ)>$N%)=:V6;9TDFRV:X&BZHHC2.?C&BZS@::&-<_^AXX_1)=Z!B.+N#R4/P
+MUEBK7%7YO2D)K(G+$U2J<!XSKI=8>/*V8<@+7CN2N="C*$H^<$VU_$WN^CN`
+M,%02KL,@#E2IBC&S,K.JX[;O.4]U>1Y(:++2F+73F&)1H)B]6]\BW(5-Y]`*
+M5:XOXM4&G^^&&,:4/3OMQ&(=/-RF]BJ\<T5W_\BP%;)WN:G+<:'I:RO24,EW
+MT$]PYSI\/"6+55[NDE@^"V\WB4=$@,IU*,W9PHBYT=#MI;*9WM8U6%\"%EK&
+MTNPYS)?MD+%BN$:$#)3%\`KG3KXS8'2MY_I7M.O?'XNX7V.><4L!(W&O"%<)
+M,-=H/I>5PA50PH)C(#4$&'0:8XQRH8G]9\WV]A#NO=EVA41V(3V[`G)U?Y/9
+MLE8"ES5Y+^/;$,K"YK#HPA2PDHA%)+7^%*,610#-QT/W`4_JA"/F."Z?>1OA
+M^NTC(H+H3PAY^;"O?,!\#Q6QBZVYI9MS3F3'FWQG5+QI+5RABJ':2NO&'C%@
+M^V8Y&"3H3IZPS(W%-""N-C8UNTJ2E7`XR?*>ZPC$G![S_Z<H=%0QY/P&.8TA
+MM6R#!C_D&<]\TLC8M9^*X`J%GCK)Z]&,LEKU24CW%:BY@AWSJX)E3LHT*2&O
+M,:3+%>%C[,%>[&<Z2O%2K$G.BGM[$L0_A@QF51RP%.^1&9#Y+2S<3;-I/)\G
+M/%_QXAF4@Q3$1YFT3[O;L<M0Y7WG9YP%$2V2_$3I?VINTG)4-2\<'7HZ9&G<
+M,'ULRD[&C_%PX7"34GIG[W^V%Y9RV-'=$/@*M0<+*>K),L*"IY*N58.:[_U2
+MJP8NG2LCEA=X3_9/K"^:^_`3,?1<KP-^GP*HB@1OLQ@])6WHLSM1$8:EG3O4
+M^JHSRK`8>4">[8#.QW\@NH1`F!D/(*.(JRQ\PV;VM1:6L;=?X2C[\2PA3C7=
+ML"?H+HK\2"P,;'3E?S6;W.'[H#YNE)<;,V1M<]2BD+UQ"<M*Q%Q#!J`[9-[!
+M/W?A?SN%UQ'8YA4@J`R;[<ZV\)!%LST;4,5.Z8*7>\/+5N(#SS$@C=U"_L=B
+M8F.&XEBQ[YJZ?&F%8\=?XIB57+FUIR:V.:]%;D%)5::]'*J?)84"O`BB_TH1
+MM9AW!OJK!%G\ME9';NT1J<YA"=[GK,6/S]EEL"W8"[6VNX]]:8[+;Z25$JAM
+M;LMUD)P"4MN&"X%#^!D?41*H#B:'<?WV*V-/)C<R1#A9S:7N"6.@CI*S4@&5
+M-571'-=R!.J-`K+NI"L'P2"X]5TX(2-@I7:V7=BTXE,N0N3Z4)_NQ5GQU&K_
+M%+_IIE"[.^#*AAV#[/[&#PO)M5NQ"P9YB7X_XQWAZJ=XW1U`9A`$\B=M.0[)
+MFY)Y>!(#&GIMNX1=]1\YJUQ333'M>"N)9=>%3CPBKJ_B0NFM6XD,GU@=\22)
+MOHU$`^,=6"$A';1A'#\D[.?9\7RD'LA`VJ%'H.'^2-<"22J``']V=2/-]#6X
+M)LR1*O^<P(3YHP*#IOTXU+:1+J3%]C-)WX]6I3+OM@6R6Z]_Y7_%G\`L;=)&
+MF0=3.,1=`0Z?SL5L&T]#<F]`C]FH3T;)1EPQ'6]=:]P$2P$IOU)[+1SL)X=L
+M?"M(F/=TLM,.6ZU?K_N\>267#SR1J(B5R^9Y8=SASFEE;^<F'KMH.Z([2\H!
+M"WX!P`K&!79[L]2IT71CF,ZT*AW<:$Q78=]AJ^\NK8FBW>\KP^45[RN03!!M
+M3Y)/8RBOK#='/<3I<'!/:;3/OD;^Z@UJS:!K<L(G>Y(`<+B7;3^3T\-:^!]8
+M5U'LQ+.]/UGW"S8=(9,F@)?\P+.B+E%>\7G:H'UWGZ'(JHP7,M$EZLH5L#D"
+MCDA/M56\4PREU:)VG;<>'J6`CNSHP>`,(EGN1_4:FX@,4(XE+N\R^/$=")6M
+MJY<"&/DF,(5Q3BZO4(.3G_K?R,-I9^5'R\JJ*7SC]+>?-0&B/K!(7^@S[S:D
+M'\MQ+*@6,>00<I:5M_R5!$X>,(Y5P;Z""F5`3P#.)O/JTS.J-^H9/EF&Z'YP
+M$SPIC*Y='$1DD7C;\K50P!L7)#O7DWH[/K]#6<*<(M!2=GLHP!(\56W[[4RL
+M07O2RQUS'*^U8*7,\T+F.3GFV1]6.VU?)-N%VBK.]C9([93)-ZT`WMXV;WX=
+M[))]+S#['ZY164%7/QMYI4=*!RZTUOR'PVT;Q9..FX7&?'71Z%L>H"M157&F
+M++N)3R%8.E8)K`XM(L(:?MQK1H@!`$OK\>O#/Y&!^"6/0IN2O5^8;_GCDD6U
+MOA,H"A5-.I0=2[2"?,<0?4_F[@7[LQ8J6A3U@DYXR>;.OETGH50Z@4Y=()(B
+MB:?[;!8X2LC4O.ZF[VO5V<B41%&(K[9!`U>B[.!5\V8?*9K=B5_0@*$&RN,*
+MW=F4T7=0)/;4+L=[L$SCHQKK^DE0J&<IOF5VM"<$<'QJFUA<5?V6A)T$Y,A_
+MA/T`ZRT<1']K>`63MN<*1'L8Q*2B3#""!DF:9R7:(,K2FK3:+\TPXXP\(?WH
+M=B5O554O`UD)26'W+`A<Y%9LSTDVN!NL>K(7BMG>6^.]WKX;!?-8*&1YT@Q5
+MW[<U[9KZF[22'A;UVF^=6DR)G>#LT=;%$GN0NH`:AG]L=*JQ-3D]IRHZ]U.;
+MQ@F0'+M4(UIRO!3JMU:SY,P/OH.0SWP*>>;;]M2U[9;4+A""[0;"'H9;P66J
+M<X2?LG3`8T>:'?"1J6T`%K_2?XV,LWS^LTS6212(U6LGZ`9`<4I:8QW(4@]H
+M2(?R/2/H7B46?I.L04V\=0=GU&T.F4C2+^M_+P6&?-+Y?X=(P8!J;ISZR\]_
+M8]2?"BB;MBWMN?^-O+8J!TZ]R@=1JC_0-L-E18IA3?,[VT69<%VWX.N0<O@G
+M/=CDWGWK-98^*2.`K!HKK1#>RC5#\N.:`0NZ1E$SRGT^4S&#W90:R$WWB\#6
+M`$XCSVN8NOC;\T=A4_Z]Y(HP#@/&*%2(.S2*[+PN(`(MTG<A-^`2QHV18O.8
+MN//4DQ/C@=0?'Z&($VD$[3;_<__><"9,4E>"XKF3]$E6X@T(\,%->]GG$1>#
+MC==G"7N-46<#I,XU6K<^2.\30A-5*U0)OZ2UCS3UHK1:*BH5&U_O&O4<3<F8
+M"+9V2;\:2X?.D:;QX,$`6U5'W?RYR6P2G'5FT-Q-LIJ&;Q=YUG<@"'<\VK\N
+M;F%Q(ZJ7-%EF:(YYDA&JW*;/:UN.:G;80KB>,X;A>.57+.,BP<B/_CO_<D`!
+M5K]9"A6I%2KXW391:;36H@;;WU?S=8#]/MG0VF!0E&O+?KI3KTZ(18,.?VW=
+M+,3241;,<Y7:I<(-R!UV@(OAESTI8'YSAF8DB2VR?V)Q*,<N#^AY4/G`CKJB
+M@B%.A\M35%-_8EL:")U^H.CXGQ3UH2**UY'U8X@TU/.BY/0P)''CU$D=T=/Q
+M^R:1Q9?+$W53OL)..9Z%H"6R#&I]*F\]ECJ"NE8^VS?,GW0>^//=*1T-M-K:
+M&)FU2ZC\*=%JSVY8EPB^*"49%!EG_+H$%!`UL`]@WT;-4TR]3;3O'CK0'A+X
+M$Z)X<*RZ[EQ+6$R"2.GXR0R8DUI5<QVF,V3+2+?9=.QA`_\VI!#2W&X_\]GC
+MQ09.AD&N>O"\CS^_G&P6^NR+YII6Z_!QYR_CIF8[Q8>`$6*6R8!,AR9B$&&*
+MQLR:H_?SI39Y]A@VLN6H(I$H1F[WH;4MUT\9S.[\9"_LEV7(SA7#8T84:F#]
+M1+FR3Q\&B9XGY>[/P7*S^,.,C!&V`02(H5VUEAOE6-3I1_CMI<[$&'5Q\%XH
+M/"6()*4L?.@(VQ`/9=GSVT]]P%1YD+?=84"-Z$9<INKP:.[HM#;<'4?J;`;-
+M3!E%-,0555)5]*H1U`*?K2W`185Y<4CMXAW-^#,M].K"H?%?$T>5"#=\)"[7
+M[L>9OSM#K.J/&Q-'[8WV_?E<^HX&^X-[RVHVE,:F=$V_'@(JRYLS[S##%DO9
+M0>M,%GMC/@"ZO]SZ`FV#'MI64ITLD73(;EXX'D4ZU@LN;**FXLF0L`832#8H
+MF[MQ;AN?$7K/7.,=MS1F<<G39`X3F$!`MJ_B'N1C57_;U9?G3F_,/@`#1E/"
+MR[\D>1PT:`R\'XQ(/ZOLKC'#%,8L$>.V1Q[AXOC3IS:72)'$_=2'\@,T$P7Z
+MJ+&#,>2=71]WC$+SQ[1QM:`EQQ"OBXJU)T<%J\V[6YX6YQU@WN67+?Z:UMF:
+M.,+]"GRAI_)R*-9SIPOF?-3E;8O)BMM:]ENG-0=_BFJYI.T&D$?$ST_^C%L"
+M@..TVDQ'RFV#24M0LJU:,/)D<6P7+)06[8H]&KJO#7@?`SIA4\Z!+N>*_?P`
+M</7+8^;@/5O=^P,.^\V,O"CDG:`HL_%@?(Z7&1%L>Y/!=&HJKJIAW!&(Q*]>
+MLH3S2>*IF$G^4,W_I5\<;XV1L3>'3/-6JS$_9Q_*'M!\,*A1!'>HZ&`*IR2@
+M0@H!:I(%"SS3KO`N1D9\)Q8SLJ*;(H(86B)M+!U>8M%%0F:*H+ZCDQ=RQM(9
+MK<,;A$S7_<H_?\\$)[6=5SN=8HW$9SG?JC#X2`_>0)RG+*Q3,T>!=H?M'_C`
+MW$>N1.CPT[[A[TH/N8L/`+18TO/2N^^4+Y.PKI"SK.#-D_W198URJ_+'Z8\&
+MPSP*:;7]EJ)K%9*\``.Q;Y0Z@EE]\E":^>!@CV2X-OS(<51T'*[#6,M1#F=Y
+M5A;+GA8P=0PV[?^O+N-83?P3WRNO?O(`:\C_F_OX-%:6'*F=0B:,NT8+`3?R
+M:B.)VX<X;"NW>5@R>[3K,FC'8Y]A@6@E]P1?)KC-%Z;3PJ*^@"DYZH3?5%QA
+M:["TZ1\3RM08*=IQ#D_^7'"P_$V=L!IE!;+\`!V__LEE>E>J3"9M$D):Q%6R
+M:-.,(S.,YD:+N]48XPH",7#0L)>ZBU=F4R)K^-WWH,<$&:D_,D!<K0"<;1RR
+M/[PB\J0;[6LLI=L28OM1T\9>:1?H_.8LOC>STI9^&$01AT-X4M8`W$Q6:KWE
+M`A`[$[4-R)2'6S<C1O0^%FVD\(2C=]/!KX]"N++(>?H`_`DUEO@2`$,W/OCE
+MYU/IZOR&\/PU82@):QBUD11A_G4$U&*2]4/M8_#*+?-0FY">]NAQA.3ZX6ID
+M_61H!>-FF/T@>?LL>B46G.C\%9K3DCL@XLVG4BP:M1_9_/CSL=`A1)R15(09
+M"-8302RRN/VB\.<S<X_KMZDO3#K"=DMA/6[<=S[):K@O%C/-X-(TG\;WN%O\
+MPA*:!IMO[J`I[/T$@PUN7'''H%IG]=J*N>\<^1N*IT8R-4@:BOJ*==`+*)F=
+M.)Z@+3_R5)1_DA>/A[6:47[U8*\K=O>!JM4SH.^"X-JYY2H1"6FBBV2""M,U
+M0XPKQ;UJ;Z[HTTIRRYG"US/=E*RI'ECK.Y7*)'-*R1C_10;WAID8@+3R.5B`
+M8+L#8AQ[8F.!>-\O$7J^V!T(+F6?_0=FAB?%]1^=DP[[Z^,H)10E8)3&:@5I
+MH*1\F\V^X$[S4'X033_.._PD!ZN'F`+^44/4$6VN1D2G!!\X0TGG1@]*GF:M
+M&T*F1[K\N=-9$[030!L/MO(T<(X1DPN\1T07AY>%R()KV'[,#FT6&$I1R7D<
+MJ9ST]W<CU&6_?'S%X*%NP2YIY?MC`-C#4.0/C\3LH[]3.G4S;(Z13*,%J;]4
+MX_U!A444CQ1Y;)NI0$=W`(/\7Z2P,+%-ILU.O,UV?(^C['L=4;@E'465`S:M
+M]TX[7'^TW8/\BP4PX*P"E;J!F@0&>D]H!!%F+'I&*O@U8,F*6U?+>AT67=Z]
+M'VT50EX._#LXE'7UFO1J-*B%.?&X;4=B\WB_&B?^`GR=C:<#*0<0I%(,]G=&
+MM4YR;FJ6@?.?:Y(?)6JC6R*\5UO-L_I6=I!]6%S;R:X-"^5.=:P6<03ND1V>
+MND$O'^IC(-_`!5@6_8U;[K5!8;<),K"38U3ZV3CLOI=S8H\_=?'-20QY_`%Q
+M:B68X^I&WUC5=&B8,#&VQ-LUZ>I61UUI:J_G*_\F_VR3U4%3LW$C(@9T"Q1%
+MT:O[)=J]N_'ZJ7%TWY?MJ!BP_U#89A#*#`(:$^I\C8]V%:[Y!_YG;/5R?`EW
+M[?]'".`KIVNL>WZE^R3P]C$^-MZ''^0%=VY_%SS]PH.8/M(%>4+]&?_?6+S:
+M[?B!6Z$,VQK<L+=IO<,=(U5.:87N)'WF)<$UN\=62TN!"04+)*+SOOVK3N$\
+M/E+(T;8#X1=K5OHB]@D322NZ0A%%GCR-PKF1Q/3C1Q-3=@[8BIY"Y3)&L.K$
+M0RL"OO>;_J2/H3.'LGL>I53ZG`155Q+'I"HT,FS)K*&M5K$R=R4M!V#OB/A;
+M90P1A6LG4'\+D719!H)"DP"='IQGP0F*UYH8R5#`.M7_1P/%BVS5V]>U,:]&
+MAW485;G<6-T9U1W7BV8+C^D7L#5U\3"3=1EWM6IB`'7KH&;X;;M`SJ:YW1V8
+M$,$][US]E`U[*405?'<)GQ\."LG3H-;:Z0(>[;4*%;6_>!4_!O_*)_(3$L`;
+MO0[P.]3PU=_3_]]3#]E4(M8816X-YTLXMXL0%*^]N+,/.:#$L>:DXU>JHZ6)
+MFS[T''%^L9%CL3?P"[SEY,62D'QOG"0+-:".>"6<YG>'#XWGY"8)D9_A$#DR
+M2T<'`O5I9+,LN^=#/?`-SF74RLF\LI[2./11BGVH%=$M$(KY2NI#H++9\K'=
+MF'GPQHP7NXTR+Z8TO!GD#Z'ECU[B+Q]"F;(Z0KBNM"35W(*!Y[_32J.A.:'>
+M6$5SN$8C>L\`?THWA;?'YXP%@I50:_<L"A"@DP7R&U'<N-TZ78*$%C@`[?7!
+M$>_J>I^B*IFA@GNY@V==8?K!11-`Z!&QO'Y".X;IR"Q'H1LN%1V)K&6)GID-
+MI*]W'94S<'@FHO!U61J-(IMU(/$P/+B>]^K"RJESN^GAMC@;<:W^Z';(B?2Z
+M-)XI+$!,[((MJ?&X:YFFPV;JG#=8$Y5KQH)HX/W8J1+;"E#KF9R>?:_^FD0?
+M9MYLXS8;3BQ8!)[H'._:E#;VF#YJ0&;U_HD=N)E6`>6?A:L>!;;B]>&>'%J!
+M`J^*;V>><0CQ+U9%(H!1=*W;L7+E_'=/*H1!$-I'7<T[.]:UQMB!;NP/'Z0;
+M6D#;?9>9Q!(8CT/;SM:QN(P?0DX#&0):MD.V_`7>G%$:K,^`@I(*G>70]+;T
+MS#G(N%[?&28QA8*DQ]Q>M`ZV=T)W$Y2@FZF!M5]']F,X"4.Y/>"::XS\8#WT
+M*`-=+R$J(&R7"NF:+AF>3=?ID41-+(2A3Y8AZZOD]&)+6"^7-V%I/>!CP"R-
+M$9,-1<:G-\]JBIZ&5^MZ&@17!?.>5A4%K>I>"\/N+^RE:M3'#P9?!3#<?D<<
+M`[\\Q=HA1'+IQ+\";P&XLDP)92"&M"'8.K2'4!"1=.,K">G]N8/,,!;J%CDJ
+M#C^N3K],")70:N`-86TK]D,\6C";1+=,9*<65*R-'?$,OHSZ!@?IC_W,9]+.
+MN]XC7]&,+EB"T'*#2(F=E<L^*<J&'--OXJ%+CP/RV('O)_K#`SZBI&R;1"(6
+MHFZ;DD!6-U$*/KU\H0LQ4'O:NJ[B1ESCL$)53^6<7_Z*FFER]"E)I44&:[V3
+M5.7G+IW!!BVF=:T<NA<2=&.Z)\1[99\S/XH+\6.V2`NNR[J#T\*<`[T:#]K3
+MR=E-)*,MG=,&A#F$9P^8T[]G(NA0B6\L\0#WR37T07S)W5&V(YLFDW*E5U4M
+MFU_=\`R9TB:-`Y_/T&H]"4OL$:2\G#G/-LGC]8'(74&<(#PR5TP2AQK87UD,
+M]WV#%,%:TS'5`)H>>-K.ES&LS9_O@Q)T,L3,'N8Q#)#!-#=GRR1/L,A70VMZ
+MI0E`KI;:3`.GVGD$9-L(RZOQ?]@([KT&M`?!U[E![B^)S/@\X=/'Z-&46_%^
+M$E[[-*!LBZI:5.6%8X?%/[.L/!8O<2>/AC%O4U4J5K+';H*`(W+79#LM,8?_
+M9Y4!35H=X?DUR3&/6LT?!"1-HCNV]D(L6*U0GVUKK"D4J//73](J2XQ6;1!I
+M]G\G`$2N.C2@/ZE]<'ML$6D$Z!QNV#31&QUM"O"+H!;<_1*C*M;M),\@J6@>
+M5*`\SZ^B(4Z!!>I&AZB%FAA%W&J`"85[&$C4A4BKT:T\PC&AOV!%]>Q'2^?J
+M;$#"L2OZ^$'C14@4,QQ,D,=@<@E#/*U2@^(5>W;1(&A<^;BGAO+O.3-BR]21
+M3@GDK+96PFG=3)SO.1SM.2[YY-0>`<:XBP9&>_Y\::N'^>!(F%:M%D9!(M3B
+MY@LK!AH0'3E<`=%BG,BZC2,=U/']YT;EY%DB#5@M3N<?N^V)D=''HX1[2ZQA
+MQ(.'^TNL#)4M)^$]9:X]:[N5!+5D"]O[A.MZ.GYD#2VI:C/J^8^9+9'^#O+$
+M`KLYK<D\@34;PV=Y;L2&B(UU->?-*_\`!^.[6T6<.*6&V452@2@F6&N(\]Q@
+MT;[^C@U)!&;-_;'779\QU(,,P'-;E7:2D<D>^3NX@[J$)A/I[XMC'0E"4K?"
+M7A`]D2R\/4B2IQJUSV\K!KB*K=83&)*-AQNI+AMQ_3"24_MDW&ID7[:2:5\<
+ME2M.]^AX/!9+H"XVE*EEC'\<GC0_%42ARXV$72^YUGA?+^HX[FXL+8*#@KJJ
+ME;60&A*R;4JJKR>#.P/?<X1T?7P>$(0X;I<6D9(G__$)NK--6!P3>JLP\!2/
+MC>Z'^E]D]V;]L+04,FWUVLEB*DH,46:B;"-B*Q]<CV'K%;CWT-L_WMNSH;/=
+M"9!B8;)11,.IP'*$/WG`_Z>V1"(7(MS"OP5&:L(K":?*JCOZ`A)"N)!2W3G(
+M,SO:6W;Q?+@<BOG#;%3;4>G=\YU]75;/GSCQ7C@I<BIV^49V@MOI'@);084U
+M!+;%V`-8M_N#+%/WW(YB<LB@SAHJ(_*2IL,SB6`,M9T\M4FXT$_OZ^6F?9&"
+MG<(&9&F`O@W#CB+<6FE@8D2FK@*P<`41(NU>]&^IG'@F*/HI`T#,'&U.XD5:
+MR$\KNHXU;'D?K1ZN@D]QO^J,M]M'ZC2<'?58C/[SZ]G,8?%:I]-_93][N!NE
+M1O<Z$G3N6;"2P)59XU*%[1J%;_>]@>2^C]9,TX(52]6M6+\B=JO+N.(\R<%@
+MO#I49FN/>NY[I8B<HYI,)6"@I%F'\XL>6%%SNB`R[4!NWT1I/D3C0HG=2Y.$
+M#FOPQI76=/,*Z`)*OHE,NXIMS7_<K?:T]OE"X[[+/*?TV+?\^,1XT/NKT;&M
+M7=0#YA;`$?[[!%M`0^5NIGAS;'TS;)+,5TFCYM%7%,P>YL2)SS3HB+FL5/S3
+MVD#+X_I&O7U%^6!GT[C(9&=UO*>DX6(GD9BZ=HAN<9H0^BUKXF"R0^,IO`)M
+M,,T!1%GD<!%_BQM>_+\?9HV7D:]'#_3XSI3Q6^T@*==?<9(A6N`4VC:$_)X"
+M3XO6M4S`MWUM%ZK5.'"(W!BT-6?2ZSGY%C[V,@\6L4UN+O\-ABF;YPP\H8@S
+MKV7`>:RIQIJPYJ^]\+(0_FD/B:BV&@DC68&0-K]*J&C$\JX<&EUK!ZWG@]NN
+MW#]Q+_<4Z0^JLM]$+(U.NN.[@J\BRL0-B80Z5?H(D3HML4>QW^$F)/,$NJ3:
+MFLY2YE4`J:%$D"'>^:BUWP3_HBE$/ZU")$00:=L$33J"A:?\B81*";L@7=6H
+M:\0QB7#^NEGT'%/%NIL#!:^XP3_L@:AKL62T*X9\5Z)=>"]9?IGS"!T?VFIV
+MI'+]^KYA`/2I%`Y![4%.5GW%@:Y=:!(AH4>C/3F+!,W@=,45J^QMP`^3S^T,
+M;(SMODVKP&%R+NH[M-W#XUPB*694U\!SQ1T5H/B?1:_&^,3ED%-^UTZY<=&E
+M%!][/*?&\YH@[S),F/%2NN@CII!_0KS^)7$B<J3_>JT<[N\?EK<0CU^XL$<8
+M&K``$NA033ST(P5M]!78P`=Q>\GS<E8<1N,D0`HND]KPMQTN_H5FWT;2:OW$
+M\^D1Q4Z4"QW+(RQRI?8+8AD.@99H(HKDQ0LBPL).2X+]4WU3Y`?O<SMO4FG4
+M;_S5%`EUU51&$01K>A3"T]9H&]K(0?R)21YS+K0PS/0=/]588@\.]$;:<#AU
+M@.?FPZD]ZR^0<@9#,)N(\ZS^=9:6)\-)3+KK21U]0F03I4)U-H\)'5\;VO[7
+M%#6A9!8^Y8`3F);'1U58H.4TUL3HNW0(5">D_W1N8>I62O.^BM%E^O>,U,'<
+M\$',CS-U,(+B2IC2"CF:GU7=QO)7%,3PTXU0FE?96/JLD69/U%@B!]])!HA=
+MH0,!UW$B,E6<OGC!PZ#.CMEU\Y@EC&(18WL!V'2\<?ZIXN"C/'[]8T'RQG7Q
+M06M3AI5TV[0PP69EHG<!Q'+8!:STD2[!E#?>:'=8CT`_X6]-?5EX]9-9361V
+MM>;A*U"3,4LFH60HKKT^D4Q8S66A*@\O+5&1=>/G'[Z\X53>@LK;PQS<N'>#
+M4QWPBA/WXI\J4'=_+>SXJ65/'RCE/$T,<=5.*%W=0U2>N.J[I7H3'GBSB[W:
+M`>J78=F_G`F<R2?<S"E&YT+3BGMIRM9'TJ2TT)1^WN326#?G\L.VF3Z?@LV$
+MW5C^*ZT+$*SLO`(B9CR::%^F1(8#?:IE2.2-SAT%0YLFNC;G,[01FG9]CD+E
+M9H<6AY&KP_0QX8LT>E0XT@$2U%2;U$,CZ,HISPEJU"CS(C5P/,]/Z.40Q6)-
+M1//@%(JS+P1Q,43$[5^8A]O$:2UR]8IHI]CK$KV?61_9"C^F0>S[G='Z5XAE
+M\*AQ7L$4CM/J=E'CHKT>BHGY\EM%4</"/Y)OB4)'BC_JG%LK+P.\Q*E`-Z`3
+M7=SZ\%`O3YH6=74'"1XLD6SQ#>LH=1@H2O"GSR/BZ)=*WE[S%[N*/A,^/[_0
+M24J)*RP6^B1F.FG=I\%+;$QBC<K:*8NR4(3&[O?G;OWM1L->QU!48$P/)-)+
+M\^,Z><^F]6#%=9]T?,QE"9\.^"/4E%K5U=;]M,/APJ!:F<P*S_C1*@]^_ZR?
+MXY>3@G7Y<S7Q(I\H>^B&$L_#\RFLH]^21"S#<>]WY%/5%M!(;ATDDKQS;ROA
+MR9IB"4WWF)BD\8FJ$6ZG&.W\KE'6[ZR/[HR6%MV&-3]''[0B4GUF%R>5?;A@
+M+OWT"8WKH"OU@QDDBHB^?HN7W<Z.P/>Q=8FZ/NO[Y<'*VTE?,ZC&J6F6Z^&>
+MZ$F$@5#\$/;48*/U1>8$YW<:"35D<8L';4HUMH`[E'+)J;=_FAAR=('B`,F`
+MOXK03CC\65^WV1M&2>J&X)CCZBI/QH_-K#MU=/XS0JGU9T4F`Z&WP]X/=\L6
+MGAQ:C62G"-)<*KUNDEO^]F^)FN&//I<R.3\9WS;;8&6WU(92_L1L,2_D6T^)
+ML/C>8QE5"&B1Z9\&?JPUOR50?G%EWR4%JCFEK?^F+_E`Z$7:7>WF7<X>+2CW
+MC!,6!6["I@!GSL1.]3GSGO%C'%7MCU5&?292;?3G>LOVD$+&3+TWC!.&\,W.
+M4D2XWV=4"K""*J)Z!/$<DZ8(2RV5!L.Q!0,@`W,;%&G9GI9W96?T#SK6Z5!%
+MYR&$3WX:$YEX5)J#W*+\JI;*X#O2LL#N/*W4,/2!`$R]W[GC2T$?I(].17^0
+MFZYP7OK](T+A9TE%)M<K>M)KI]@:.4TZ.&*53=.:[NB/^I69F.*3*O$UP8>>
+MI;<6(U#M1=_/Y=\\3:B5U49@F<<G%6654E_TM<,EUVIHQ_/-8UX`Z(R_*PJN
+MM1-@OSXXMAT[.ZG%:7??N/+67YOW0HK<*0CQS^RV3N%Y"5P"DFP!^A^A($Q4
+M=+:,[X9C.4ZD1%JK?K]#<<133@?87ASJ)8/737^9;J4(F-+^CT<*5Q-$L@)&
+M&&N'7R3GC4KA3>Y&E+FRX=1![GVS*H7;KI6N`[WW,4X.ZT^FSR?TH8XLOWSV
+M"K$3WV)4)1YM+2U"*?CQ#WS'&K*2F33D``_)#`$FP.XYV\F14<E?#T#RU:XX
+M/[APYS6!34N%"+?_.<%=7LS?P=Q6O_?Q.^/5T`:&,$FZ+_J((D.W9*4F3N)Q
+MK<L'M#9FQXM@M8[5#3J)9"_H7-A%\W4/K/+LI:B[8CKW96=&3A.X;D;4\:W;
+M9=R7%1NE<EKK6/O*(?Z/OMD#)9Q(XJC4N,)OD;^P342A<>;*<M\&UU>$KK\)
+MKK51[A5V/F>)P;/G+P?,(3+(M#SWXK7Z";9H5**@Z2)7><I'F1E?Y>Q8B1!R
+MA^@2A)*/Y"CWI3Z3KSE^!NN2=V-Y_B[:/N/I*$T^YWKJNVB,+'-/`1:59"<I
+M^!CFUKB&OL]UE5"XR+=8W'A898=$&9CW-OH?3.RSSJHF=WN+<BU/YV[04`(2
+M`ST!%M(@L'0:KQ].CN^>9Z$=I7`IMUT2[I?M#\>2A8%EE2K1VKD/`E^.[:AZ
+M!R/[HJ+`ISZE1A"`HM@U+Q&V8'P4L9!!RF+%M_+9@?+K(NU9\K=:9F4"T<D<
+MKS;?+@>(J>M\LT-IWH7_`7M$JO+RRB:^RHD*V42O8ZGRBGJ+C3T"2W&_?`T7
+M/HDMHJ8)9.]LB=$!G<K(JW0B:<%^.QX_T8/6I\&PN%N:N\XZO>EH#4()[AQO
+M+C#CKP;FL;VD''(NU$KX4'.0'"G_RQB``[E@CGA,<PAC;M#<SDB(GGDW4'35
+M[D0=OU%KD'<7D6&;'OS@2GH,8K392(1LQ%V!K,ZZ!;O4-]RR:"]%E="^6I3V
+M$ILF4HJ5/$O'5BS:45@7-:*%%A"LC020\Y6&<>++<*-S;V;#:4R=I+,!-YD%
+M8%@.U%+VG?YG!M8BV1H>Q@E@R'!JT<67U[S1<TH%SH4*8+`0+CV_!6<$0I"H
+M)-JU]5%\&[NS77<AT##M#/)'%9*1NN.]&_8S87!J7KH)V)['YC>J^)Y40T-3
+MUG^9,7G^-?USVF53)`/QWIJ*(%HKG?9K]8*]9DAHJ#.,GN6R/[5Z'\VP`KYP
+MO`2@1F6S'WI+.IP[CF%2>8`@O#X<`6](CGE&-WBD$&!O&?3/2>HR27Q[(%<4
+MM=$Q\L>=&`&L)Y?DU`/)*&[`PBG_?Y3@YG8KF<G!#$0J"KV>],2U2(<RO>="
+M7G"&?7JT;TFMN)%H,!?+ET.OY%D%2"BUAJHN[/D*V"USN!PQN1JH;/>\%(Q3
+M.CB&7H_L5#ZD_(/@FKP<@Q43XZEZPS4=H`U%@9/9HB"8&LA0:8KE]?`$4E2<
+MO&KS]OJ$^V1.5U5^:1SDI`_-OSTZY?>ZJQA!#'52*\-.^SI?OUZ,Z:O1MT]:
+M@T2*VL(7&:CD?R6V28)_^B%(&I^?!5WWOHZ^Z&0D4OK6:/ZT.#<0BA6V=%B$
+M`QB&R4T_T_?E8XWD^7]5RO&RP>98EXCNL&.<^N]YH3XUUZ'KA4M:[4F'O!GM
+MTBZ1O/0@AXDPF\;SZ\K`G[(JF;8D&1S4V3R2?\O)4".R/JFXV-0SP;]JCI6+
+MM'8X\6O]:1V0KW;<B!<3+8K)4[5-4(N&(9)2)?VZ@FX\$&?B#/Q$LNB2MQ<7
+M2ZS-(T4K">;_BQPRMQ3IV2RP`B7MFS76'=2]#VKN`.S=+X>.E9][EP?RG@TB
+M*<.DYIXA@.=^IR?RH^_D#'UZRF+(;G(HPYGJ=1N+-\J9VE3UD?A2((";4VON
+M;??QR(3P<4;WITO^DA\3'Q>1"]8)MD(V4],ANDOW_,VK`$<G$?MZ<?*%8,*X
+M[UKR,00R]5<]2X?XMPD196-G%\<AE\+F9EQ^H0)V28K]ZLX"!"5E(=08YD1^
+M,?<.,,23!F:.?+H9#4E3^:UFK^D8O'A<"CW\Y"]9$O7$$;*U][W2"`>$MCY@
+M,JKU85+PAX/Y^E:XE&0$"*4"D_A28GXJW8HB(5VEHUS7*2\_K?J@X5N)=/]N
+MH8AXN!+EW!(&6?$)ZWKW>[>HD*H^CEJI[_?>AS>^1<6/>P%BHWHHWF'LQX9#
+MJH?P<3L4*@QWMIB.]<25^M6LKKAJSMEC[#WMX_9PA9.L3ALT>D;ST(9;0[YS
+M"O9DFEFU73OB+=^,Q%CC7(8X<"4W_?5Y;,S0@0IH6)W\?UQG*+[EL;^-8I\<
+M0Y<NV.50#(,TIH8,7L4\W`5#.Y\QL$Z*YP?'1Y1(3*@F,*_RC0[-E82F&G-2
+MG,R!2*.VMKXY.D2KO%:Y=*&%ZOL/1NM<B0$JC5'=MF`]O9>(\V-`75V4TA$$
+MXP\";2;D99CI'@XO2?=3`3T`61'2<3*`</?;K`P'CH9>A6&L.OE?6T3[*N!,
+M?GQ'7Y+GCY86WG:2C24(&-%D]]8NG%IV.+!]#(2/E>UJ?X75QU0>'V.R'2/K
+MF@XP5DNP5E!3@BTLSA&<B62#XD*D7.A^W%LY)-<Q@_5S,ZEVWJCKVWS#T.S"
+M0G*OVS.WM""_943:9AV+P_$+A&IT>0H*OU*DXXT/ZZVK7[7=F7O8Q6-/%IRY
+MW*0Z]'"WG@``S0F^329`![>X/U.\)&1GIJBP^U)IRV;S.O<,?,/,I9IMTR?I
+M)5,_:WC.%4$[QMM&-N*^:W1^7*U<BFV8\1R-HGW!3NQJ.[_O?ZQF)%U].3ZC
+MEY8O<YL!:K<SM0(DJD](BP&#V?CD$]GM]N^W-Q>W&'A=<>6S4ZM'K^[SP9<K
+MJ:^BY)?`E(.N??[KF=5J\^YZ2-4=@>7'DZ24>\/!2&S-"BNZ*K?M5D]GA[M3
+MW[M^M'ZT,VW4UCSC?#F>Q\F2OS[#B5+]?8;0>EYTD+5%#->ZN\DIZ]:A@H59
+M01I5;")$NPAA)X>@5Y]2%-A9H,?+OS/AI9QA=3[73]L&/IJU8?5D((BHLD[X
+M4>0$$E@K14YAAH:GM;AR>_LB7&**'Y?E"4W(M#E1O;%A)_'NY^@4/#RH+?[_
+M.ATT8WG+=O`Y^7O),BYTOMD,0#''L=%9O2[CNUG)>$!O.?^%O%:BM93IJL[,
+M)P@)#A82J<+=#&3TY313J&2L.7$\82AI]$^K/VU-%G4KX]AQSS,69=3]&NO>
+MR9FWA!M\<E-P*._RVS8.I"NPD)/@0]>M&G?-_:>PVO@(V=-%;EQ*)Y<@9$P"
+MM\<0G_@,D5+;;!`=HQEQ67K+(6*$NC!$:4&.#<`9=>_:(^ES[#BMS*A7Q^*?
+M`3H*8$J>IP\KOUQ!>M[U`"#$0@M/4E`-WO]\A$667TJ"C-VQ9_H0@5+/";9]
+M!@,+!J8C@*](L4`TZ63[EQH\DG<IG18U&Z/T'[(>RDCPG4OSW%,1)%\1>ZW`
+MJ?*?=H[]42+R9=E4!9Q\0QHK;#F[![^3Q9^D#?@+-N7R-AYB,/,+Q#]I2+@A
+M,__/IC)HO_G!?I1NPOE\QEGJK=H4]6JTJ"X9^IPT`]755TNU%\_`,8$[B+N:
+MU6>;UM*QZC$/RHM&*QU'8_!U?/A;J_I4OHA,!C(9,\IC<'<T`<6H9<OO7TWO
+M;/+H-"V-QIG\@=4=2J=0;Y<JEH:;+2!.<MN5M^4NN]`'.5OJ&'!-[97(]!D:
+M+YX'M%J@;X]+41)/<3M`^Z8BZ["&S-<.CP0L]*GQ;FKH4@\EM<:$$^5]WLA$
+M8_"ZJN0/&PZW1S2";H0332J!OYDLQ,O=&**&.G'C(;\3O(YW:*IK;+O4(R5N
+MB]&;<'?S;BSA%6)G*G\?1^"X`*5G(HIWX!<S\VG&(ZO5L0#7JC\BG$3*@@AP
+M%%NU(#U_<W>XL&;,<(T1A;MGT?_%+X8<J^BJ!:[FR7&>V#(O;I8AI)MO[X/J
+MJ$LQ`QF/RHH@X!LLV?AXH.MJ]!1X?PR5];!+9M,/AM])\Z.SN`*);A_6J[0`
+M2QK>:CEDB=MLS9A"[12&PZ3.N::;+:Q@J82.1,?5AN8[(4>6=]J'6RK%J"2H
+MY?)^^M"L*.0(=H,41[=XXX/ZPX0X4Q%H)P24\=B$:;V)Z78;?0#\<;`U9B[I
+MV5"':![WFYJ\?J%+*,PAMPIO!DC)DD:VL8/[X;RST)<EN?O638"N)W?%M-RA
+M2XBNWN'6DM?)8H&5&[>_JX`9W"U5A>0Z<\3`DY"<WM_QO)Q1MY%!0Q6(]YA[
+MXDW@],A!8H<D(>6)75US7(-)7G&X4HBSBK&64+(6R#$SPD;M]K[YL#*W5[(*
+M9^<]ASK#;Z/?#>^#1)_!*]E]K3;B'-;46D+`BG0^;<5D@R2NGA[!$D`MD;Q.
+M8.8=5)UQ(LR6!B)F.!7N&W;<P&G,/BQ*803:?+1L]O[WJ"P_`HUT;4KZN7#K
+M!%><Y9Y0#OJV]$>C":+#UP03UGWM4Z\I*-KPV'J=_>-!&RVK6!TJF[R$PHGR
+M,)0C=*[>\5';F$55"U=#YBJ4<\0$,9"A^O7H>3,T=N2/XD)1%]!X3Q)?7I:M
+M]>7KKCJWKBF:.OJ$HDLT#0\L2?&,2?%19G#'QHZ1\ORA<4IN:6L#5[[VBH&V
+MGJ/4+VWB"&NP9F-C@G2P$D2A_NJ"`H+4ZA4F9WG!'7`&97Z]:E/WJL`\Z^,<
+M!$;D=3_NPA>)4_*!=@P?D#1/@B($<FB.#N\DAQ3G"FGMT/X][+5[:<XZ4ZYD
+M&_5@Q.WK2*A6GRL>9R%![7WD(O^4B/`F=L^`YT@LJ\Q4_6%4[8MP6:0Z53X&
+MFQ5C9#IK$E.OVWW;L,YNS^9)`_\7(#59++:*_AW>2)D@/HC5K37@"4P$0FMG
+MD^M^O4_+7Y30O8XQ*D,LDS>F5X"D@)[Z:'-''?V(QH%A_QL@X*O\NY*4C9BW
+M#08MUN/K`S"D?</VIY'\:MDY3T$.72^A1]96Z=,FL-+64H&)2D@$^7;PQ`G2
+MVOX.^NA'NPGD^4:^0X/[0F!X*)7>"QC"S@[)8B&Y:N$<E<2VSW&;3X&J$YFA
+MMDXJ9\D<:I(77'7,7VUIK%GY;^Z6SS%!&D0;Q8&A??&)QO8R5EX^$*,-+Q!=
+MT@Z59C9\HY9H*!>-%]STH6O_UB=,FGZP)<F9?)%U:YOIY-([\6":TN>2WWZP
+M,7UFN:@K65I&RJ5SH+LG?,*TL/?8+OABF@/G'X+?PUV;[L^!B"MP8?1RJDL;
+M&,\BUED-QP'30J(!0!Z-'5CJ#\003QOW34521J.TY@8JUKP;9\'FH\.0\?#`
+M7WP/=9AP9G9GM?CWD?A5I?#@_K*#L+#EK'%KX"7M5(F@R;?RV6T!Z,J"!2,O
+M>M$X3$)U`]4I7,6]M'*_M*G(Y>9G1F+M+M'W:='6@])G]D`#`=I:_F('4%B>
+MP,#A7F.%]#57"8O;A9;KK[&(S#<?4^7$'$0%J<C?QA-$5:&`+5^D/'#!X\\X
+MK%.F$632$]-;9#M9H1F\^U"+[^%?)+RI7>M^Q)O1V%3\:O*%1>^JHV_7?UK"
+M/06($V"0V83$IBF)2V[LF1HDX],/=1]8>\G*N_A'J[L.V$!N)#O9?C-44^^W
+MH-RZ]MP8L0.PU#"6=+YI2"JZ6MP&2E^)'6>O'8![6)TD,]BQ\L7JEN.:Z&%#
+MNO.X)9,*$5Z$@455>J7B&P7KG+A9HWEVB7'.F#;"MQF6@H%Q'O\XI[!%#%I"
+M1/.W5)(XS!1C[O/6)3RF.G5=80@#3IUI7"K>BLI;5<R&YIQ8?O'GW3]Y^<N=
+MDGZ-(O#W?-SZ;<:8X7#.'"""R8_H30.DNW`NGD2`)F)JU+F!PV'=5/[R`<K_
+M<3O\+$>*B`MC2Z9W&)13ZRIO4%QI`B7V;I%=$PJM3SL#LW90(4@_)UQ-3SCG
+M``Q_I^+[JX4JKXCXS,`L.)48M-7;P&-.?SD:;/8O\[8"1\@/6PCC;>$O5Q'3
+M`AG=.IHGZEWE;!J%1D^)R(DE%D#\(K@2YSZ:#C'$))&AA!%`.P6[YOV^R&C;
+M(K*23U6,5Z\:2_*$G@VI#R0!K@6P<I77C$$EPJ)`MS@PST#N3P&9+`&?FZ8S
+MY]M@>SWKN\2]Z!"S;L^FV3287)[UY9UAV'KX^"<*-FR</7.$Z'I+G;#@F*H7
+MV[=]48,9RY,G9(D<FL@^<B?S-#RV;EWX:3F[/A!,NPJP?7@%X_$AVB"1X>KL
+M^%,>M?RP/R&PJ0>:GOJ:*S]@0[7XT]8!6W123XY`U4<P,]2V9DS79U`TS88>
+MHF;M,$%628**O""TX9;R5>5:Y6^)/\6Q()G8-/@+RW@VO)U--#K.#910,7R^
+M?+<.+]>P:$/GU)$"Q0O<2OA$]>BJ+#>=KO]I)0^J#Q*!%4%!D$0B?;$G&9(*
+MM[X+O,DDP-C--@/!5A@=\3:/PZ>C'TE[444,!-,3J&_L=K25IYE+^1(PK=25
+MF%Z2M'/G/K.Y[>@UX5-CD4\8B<R.>=E2HSP.,"P2(_)<_`LR)-!>=4YT1'QN
+MG0`LPSE/UTO:N0^%"\RR(!G,14^.:.B%S:5R.@TM;@JW*I3>T36`4HX6>5M7
+M10]6X8?(-+FA3)$3!/F5/-&LG*""]`6^Y0YGT<DP=?NN>%<UQP^=<KDS[_Z-
+M+R^%DMC#-9`1,VT*8[F-QMS.4:-AI!B6<Z)9O3UV^:Z2;3IQ9TC7AT?BVD<%
+M3NAG3#L'(HAA"3=(I,G-.K-/MW5UW)6N!M#AG[(O@S]A1]H/'COY0`:]L`QF
+MFD4.(01;@=[2M/GNT*A>KI;P6`^U'H,?C%A87"YGD1>UY)*E@.P_RD]RQ92=
+M\A?>\-*\$'$>X4LS0C0*$@(],ZU[J$H`R?3\;/3+*P;334509?J!T".5)RY>
+MKS(V:8^=597C4[BYT+<_OFTH1A*"@Q<OD)!CX'4@VO;%JO=Q]M(F1P3/7DGQ
+MR2$]D41S^C37@N?TIOX'Y2[;6<VT8)LT:UK,;)U]#(O"&6?.+<[U0R%E("8]
+M;_/])Y+)Z4_4NA3:^GV339F`/G5JQ^&<HHYZ=',F[*4RT?JK!:?WGM$37E\H
+M`BZ)L.#:KD;%,*7F(-%9TU8J5Q"7]P)I,'32SZ4,IDIH$[>X0:+_[1;DS$$"
+MT5D,!I3'G#OO"+KR@*&M)G#R8(C@.VLS)G]0IVT1[IHX:^=9PXO.IA3!>.R>
+MJ'S$)0>90Z?CP'JIY:2WBWAHQ9<E2)ZON-O2AM_RH"NY3NM-.$>L8V0_83*)
+MCK'P$U2NN%3=B5FBH7#D@BH3[P[60?^Q,CH$VP'`?'I:8+AXZ-;!H^%G%X=[
+M<QC4FP:-H='KE('U2F"@\(56]Z46+71]Z1Y0E@8J9Z08OS=K+O:1?L8N1/PN
+MY_`;8F`-AF>`($#,"4NT*G#VYQ>QU%#Z_X2/S68>;;!)9,[FN(S40S$E@_=P
+MEEC$U/YV#]MCM1"[%K"?[9X<Z'E-OAP-@0E%.X\8%[L`SX/Y7@AVHQX<<9NW
+M,8D>!Y#AI?U4B!:*X/)(WR0(LFT$6:/ZTWT//:-^'/C825Z#J^@ZJU8#K9P"
+M96B:W#;N1FA;)&#ZE<XEN6J7X`(V/]QTG!*-*3@[;5SG.HN]H$W^U^B3V"]-
+M?Y#"F@!,RCO05__GK44U8IO1^OX8GHWQA!L'G4:4C\N/2I6;')-"-GGN]X(J
+MN;%F1OEL8=F.V5<.RO\>H*)CI<>;5+B`M@>P;@)FE1,9$(T:L:16KN>R559Q
+MM.B2S;[\R$D)T)EL`Y=X14X:-*^AP';5(8:9T>>K_WI5K=M,N.""+=_P:52$
+MR5H6K4]5SYC([PZ>5>+][#58J5+Q.BCQ4;$R"3H\7RQK;VKG4LWW156@Y3CZ
+MOXD.G]?`11B$2GD##F_T14H,RM>3A.L$9UH;V^J@I_06;N%0;R55=\5J9Z=F
+M,[,Y\,F<A8Q]/(4,E'&/!H5U1S-CC+^K"[=,J7BD0E=2;D:+A(Z(OD:@]"=!
+M9+[KBG?#U>IRGE&_#LV$"Y7D%^LHP;C+HN>!ULT!J%#V/;'>YM_[,<3=[C.F
+M)D;B\`'F)UI.N!P<D;\![O#,3KO`';P]<>!8KKC*[&0NK0)OU!1)\M.:6M9+
+M>5I._2@]F]/<1:QV$V!,5$2J@DZFK2#3%AE/&658W?M!L/E:!G5S:)C@Z!.6
+MT;5[M.K/``;KSS"WU&)LT)JO<SX["Y&JD0VNP\DB;\OW6T"].++VYRB>J%DN
+M5240N!4;3\JFF+51;(2MW:SM$9:'`B!33//F@@1?O.8S/^L-E"X5^\T#[9)$
+M1RJ)SHSE)*X9YJEYH.T][.+FC:8K\QR^VP;J8]W8E&.U7()&H\Z%JX%[4)%H
+MUEOH:XJ!P%'ZD$[</?28H$XJX:(F(D?GE!_^EV>P9)`IYD[@=+CM6FE<5AU3
+M!QQ]*9C9\P=L?O`^UWH94%M_/XW:W/\S',9HJAUSR`Z@V-9B^&LC\:(H[,[=
+M\5!LSQ.T1J45%:IU#6&[WQ=`^9*(:M4O2PWI^>/@"TAM)=F-VK)/;^NXFRQ5
+MH7J_:&VC/ABF%9LEIZ&0#JZH)D$]!%UZ,8U$(SE;^BVX\U6B^0`:KSFD:(`0
+M7[9^<VA^GWD*)(U*A/:,;L].J%X!_E4-XHWE/BR<@Y2=9)?29P&K';<<9C[G
+M@A5W&C;_D>MA28F8]`'5'CQ>*UE/K9_P5APLFK`$V):]EM,>7C6Q-&VY$L9;
+M];85C%<;)AFIXRBIU&`?#0=-(^00@4A7_8/=7/]>C>V++ON2*#K3D6"4R1>D
+MX-F4W5H!.&#IM`ZSJ0#L(>4Y]Q^-S9]OOZ"JC:#DA`T:#HQ)3A+<J**`@&C:
+MH$99RK8F@[`.*L,F+BT<.CKBY+)SRS?_S-748/_;_1Z0/4O\.I&EW#NP,W<)
+M\47C"_O(NK!W6XCP./@VA^G5?V<A65CZSHYGU;@ALL'*A?2V^="H]^]5#V9>
+M4QI8+,9+=@CGS=R)11>Z)P[9+2.,M,XK3U]F9%EC-PM3_)76L7SZ;8=D?\&,
+MK_D$00UH_]R^DS[^0L'@?WQDS]9U`!]U^]\`D#B[NNG99*ZIHX?`A[$5CE9Y
+M=$E(FSXB>Z;""]HA>>C^T&I_AWVA=@3RB`W3NT_Z'*+V#*DFQ.+#UP/@[YM>
+M&S4`AHF7CCF527\V;-0=&!S.<J<8RD'BK^?P.G\,?)MA7F\S,\8,RHAY-[;8
+MU'U030RI]P!?&$>WUG`LB9V-2)8TT)A(&QQGBL^'O>VB6-B,R=8EGR4XT):-
+M9EMRYJ^.=J7J70F5UCWT5)%6Q@6!9#1PG1^V>KEX1_SS_]49)99`^>K^NYJR
+MR?`8*(*A*;0^L_&'`U0H"#!N?%8KT2S0[/:*]F7\):1]7CW0&ISY8C2F;YJU
+MB,GR#L<?^%2$[[)9=OYD&YVD5^Q);5P7YX;EQ']S2#"^Z9I;$=-KP/![R'!B
+M!OM#\JU)II;:V=U5((Q&O6(U,UL4_;Z+(,)(DR#]%43Y47!Q9F`5K!.)-5SN
+M#7F<&#3,>%]L9XM5J-VLUH,WR_%:0"%]-"(EFA\O/-R0J"R0ZLUVZQYS0PE]
+M6!QLBC<HNXK@7C"Q5@T]J=UDFS`3(D2-79MMN;%84UN,%8OJRRYG:7T?]_8D
+MB`W/?A47L/7;PF16W"W3$#H5+A8QPIQUP")%#I@2,(76QX:G'S_EZV.^;5VG
+M.FKA]QD!/>&EYI]!I-^CGN-+$M$YO1P/KS=;UGB+1\>4_P*V_UA;9ZI)A?$A
+M!V:`"0S4YC@<F]?EP2UEKH68Z,4ZOCNHG(E]:GN7!&OW2QD]7<WI^F^W^B,"
+M5X5$[91P`']_G;/!-+K56YW#0^:J.)6Y2I:4P(RJ9<`9:^L>"$7[CGCW"0FN
+M%"D<4!@M-$ORVG46CBPHVT!J1>$++KK"P3$,1,7E-UE#8)WT$?!];&J:IL+)
+M;V9$49/\*T&8E!F\8\)8"U1I3KE=KZ_>%O5O^"6F=T(SV;^D>PT'!F(VGW?\
+M]52V3:M/K+-P2K;)75XBL-(99:\>;YY2CQ=UC3T_QP1=EB.[/Q6H5W,:1\ON
+M9>0D%1C`@-6G$U]WG+7SA@J'YG+>7U):"W499H8?#Y^J<0"_7/K<0T89MYE<
+M+ABPHE[O1C=2.O\9_7Q22JHVP20NNV(M6O'#=CK%,ZL131_D\P$8(L"HM?O9
+M5ZV)!O,4^;+>-]>;&"UW*R^FR=8889)EP;A*P!OQ<,!_4:$.VM7^;%<LA.A;
+M(T+XT"_T,NOSS\F"I<M,L&1)].@=(X.\A2(<%O:P\A-#FNS,:/#=F4TWMLV]
+MW@4ME`73I($\;ZNMHI*0T8*,#(>MK`:?5T`$E?7#++_&/.2CHU"A+RNI/D]<
+M2R4Z8S4F!J\AF`-9(']L6=9T-X.1@NI9CSMG]2&>/VEV4-P.:42>7ET//_/$
+M&$@)T_I`3(W@=H?*3TD`/6@N)V916PZ,Z6B_CZD0L\;"IJ-+.XW2WEU-W05N
+MTC$]"_#*RP`XR($CKX=]])6"<'/S.*W=LY<\J"5:P=<QR.O";M=K5T,VQ0T;
+MVKQ1"8CGJ-"\3>$>F-<1NSOF$:;Z=W?Z:J#6J2UT_8V$AMFA@'5QY\52Y)30
+MAWJ2$6@9D_[`7,0)7/O@?SA-3FA%[!TOLQ4"#$C?.7#R59SGF[$$R]BZSC)3
+M/L11.K1^16X\C\MO14D)8-68NM."H;'=AQ%/L506;NM+2B&C>+(A94OQ\:G7
+MQ]&+&QO0:AG:Z-4IDB<`C&W^@SQ7DJ5$4U>E>[.F8NK(T3!CGI#PPKJ^<H4:
+MT-212<U/XG<95A@4U/'E"0I\;=./WYF@`E]W]@T$TK<Q*YHBQNXZ79`/N-PF
+M$FD$\Y5&\[5TDI4-_3FBD@`K0IQ#'^&AO=*1G+24"MA7]O1)?E*J])N(.L_I
+MO82H`EM/%\$D@/%)3#6?(.X-)8*E'X%/`(4`.#[<@V`26:Y9):(`;U5*QOD'
+M7AO_D7>N#1==<-<%E,NHTA3@%LYBT&$8"=I,L$#Q[:5Y24O?.><]CGM[0[[V
+MUU@&C]'9K$Q1]R'C$TCJAFR^A(I;G?1&:-`Q&_%>1GI5^,,T&>PKHYU<W.MC
+ML3#?I,''?.'E#[M#C]IDG>X"H'"G2R[Z`N[`;6<HB-ZXO21(*TA4;KN40]NT
+M&QOM(J@&_,H9N$J=($6KP#(DG>8VW('44=X2^HSW,S/#E)&;7L37',0Q5YZ?
+M`=_\X7WU<&:$*.._+S!_#(MEZ;48E2]XEC[;9BNU<#`1"2#]\9?"71=G%D;T
+MVE1`-?Z)S$R6UBD&W):N#ZRPRK4YT'H(,JUE6M9X"86,Z\!_Y\1`QR7ZT:J9
+M0]S>6;]X[B?2!,%8%.BJ>(8$9A7DL>\$(JMN4F=J3M#Z7AR1EJP-_&I!#!`"
+MF<2(FHD?G8>E;?;0U8-Z#"P<$SI21+IBTO%=)Q[Q<'-?,W+YI0^L>Y2L4@^9
+MK%E_2%8?>P6TXI4P9-PJ1`VN7*[_5+?Z<=\^3&HJ^>X8`+'BM+=^U)8[$S>)
+M,A!C^"Z7<TK$<K@1_SXH(X=G7'G1\`I347V/@]49;8,JOO#O?*!$C=4C3DGF
+M1$\I>I>;GXZG'XOY%Q`'^.5G:-1R(S2][RG4S^?X'1EXY*T"6,#=]DE<G=Y<
+M75KBC9HH7F=#9:6*<3!6=`P*+0+7A@Z7_=[O$#X)VK9`7;.W;)B*39UXU:8>
+M+D)U"U4BG&GW_EW28CR[\9X/--A<(D*"-OBI0;P^[U-HT$1[&HB>8;EF;PI:
+M`-B23\]2G5)<(K(MHF_0%/ZFC\G[SO+)_.0U#9DG<=@LX*285`G)IQK.-"I<
+M4/P-TN($:7QWQ`MM;5&S3,["?PQI%/O[^,:W)V4GL(,RAFUU14$*K#^*$SZS
+MBB:91AN1*Y.0A`=/$A*`#=9+KC:3I#+S*A:^6P4HF?RV%X_>!<'/>$`G*9/-
+M7*0K/WHU(:VA\AXI4<Q[&!C1=QF\90_,)KG%9K+I*NHJ[0FZ%E`75S>88#^-
+MB80#A&)]0O<[R9'Y)V4!T>N,E+\<DJ)%"92-Q>L#^WP73C]]^6";V_E5`:(J
+M$PQVM7>:-REY;E$7=G0$&&(EI.K>28`<*6P@&#7:H=&A>65\FIJYMXBFKGIU
+MWZ5X31;/`3;1[08G'4#YBQPY>H-SH&*#KD@_^HP)#P^0R_ES\GF_7LL.!^(G
+M9\'OC/$3=@_EGSNU%+/'OT`Q;)4=D7*:03.5>@NPATG!U8L"_?-:7F>F>B.(
+MJ$:+CF+@$XG:'=%W*W]!Z(&/$'&Q&-B$U8OD;[\#&`-&[I17=,*HOT]/HC@3
+MUYF&+&N&1]3?0I"L5^,/X5^K1E:3>AM6,9(FU-7B2$A0?3[%%`N?:[C[-RZ^
+M5T-#NH=O!G^C7;1'LR?<$C<L>0FZ2[]-K&#^G)KI$S2]LD0O[5IIU^D+-7UE
+M5"1N`<^P;9`D=XFVXT?]*>3&,QS"XDH!\[#*L/-S)<PJ;#"H`^19;]Q%#W8N
+M`LSI[G,BO4C-P4EA)+/A7+L6W;T/2X\.B&[-R%:F5F49/;A"TA6D%:<)I+IF
+M$<>D.NF^UD>#"MR!#V^67H2XX5WQ(/K35J$16PMKRZFVDP1:QY]6WA:>2]EV
+MAB@HJR^!<SK^9H^IV7S7NA&KO0\OQG@-Y)356Z\,\XR^D0GJZWO4AR6'F:LW
+M6PW^%(S[AEW-./@VGAR1"/9^.M>$6'W`6B,U5'?=&_RWRA&MY0;E*+L:O^O1
+M-^?>O#%N;NI4VF9D'#M_*72T9CCA;2N7\FC4DT?VV&D;2%EB1P8,?SO!Q9H\
+M.7TX[@J0#TVT2A4+6H'2GIU(NO+T7#B_[>VHO`S!R5XQ'*96F"/'/;B;P#P>
+M[@90&37?((%B7N\\<<B54)/$ROL&,$79RL="O(ASRU+Q<R"@M5]`9:#G"?6)
+M%'9?K97!6>;V/JHD,)03YBF(HL#;BRZ$/GJ+;]_,_`*7N1P4S8[AE%-84UY=
+M:\281;W&'6B]U/LO[1;;8"?(S4J@XZ?0V4;Q^1%M-ZR)"/+QPHV_XE9E<'=E
+M?R2B3T+DK*WMSA0;]J-D!MK"V>Z#_"6V\I64L>FZ[_PFO'3J?/&4N&4]<P.S
+M[[O2QF^4Y[5O-=`4V?\14'&D,3!Q^TF"X]\PCFZ='&+HY]?NJ@%K3%>?R+[\
+M@02Q'=FN`%Q>D2@0\@M6ZD7^!S!R'?68Y$TS:&ZK3-!KO+_BM+T9?*FV\]=V
+M87Q7)O'RF@-<CG=SYLY`XV8$9#B@NO0_K=\S<C*OG]^&E84+"Q)'\_C>)V?]
+M*^1*MH<4^YC6;^T3T7*WQ)$-..F,DUV_O,^PU-B7&+==(D!L38%=ZZFHBAA0
+MV^/2,VL+:/QY^XS;TS@9/1@\0UBLY"'-2=.AH-6K?'81`7\X?&J^N.R3LCRM
+M@88Y.N2O=M.T]I>P\NY%F?<C@[AZI]AFV^&0[.VQP`2[A.?$8MW>1GV./=\H
+M=AS:/%@;N@+6[2Y-=X_)GI#YM@[:72K]^ST\5JYJ3F\F-!NND;4<7U\:U6TU
+M8,E.6<^3H6#@&+SN(/NTP?!;"L]_J@L.<5R<E/#D!^$T8<RH/)%K,]P,!]B-
+M@R+=Q/":C`5A7CN976L#5KW6H!M59@;HC\>`ETD49>:X_5$I3P\4+QQ-14"D
+M!M5O4@7[QDQ2WE_(_S#1^.69Y)=/#(1S2S$B<X]$D\[+'E<":7SS.*D@17PS
+MJ'BV\K"W21T_^ST8II`V=7Y'X=PB<Y]<<YP!0>AZIVTCI]WME)>JYN+=W46B
+M+D)*1/*(ET5C8\YZ_OX2<L$BVS"T*U-;]1+Q`&E1$BXDKSN%[_YNJ=<ZMP<=
+MT(:K)F=R$`^43L#B\77>=S?`<\\:&7&$U+N+KG`)T2R']K#81&-K&AI-Y"IM
+M7<8-J!H)XZG.F&"X`G07$10Q_,K`F$D2TSZ8M]>3CCTCI]DQN@^Y-UQH?Y>&
+M)_FK(J<S5",J-SI&R,6\"D3I=1EVJN6)Y\E$2G(F8H<L!7"#]^\TRM&"DFXX
+M6`\,@I(%@-0X/W&2JK_PE+>IBEE3^3,[@TU5`$0,%Y'FV"2RHW"BC6+K`<RE
+M:N2-V$\FZB78=YEA@K5O]L`.A^QQ6^UFXA;M(XGY"MK`RC:>>C4S,.Z)V$OY
+MT[ZP<4!?!$'&G\[CZ,A[(\KHQ.FCI[-Y=#,[DL!-?($BBLJ42EN)-@MCCET;
+MW>W0NGV3:S5BD6/<->,K/8UH#"&17S71_F2&P]T-:D6-!JO;<WK3ETCQ%GW9
+MV,:&<7-]EJ>=.\X6+WWKW0+.P\`[+:SN[HO)\JL96PJ5/EYQ,Y[K**=TC*GM
+M.E]@T=Z@QL)!M#?/-AA.?14=/+8@#1I!'QU`VYG1GT>DK&I&0!J\G7>NB_\_
+MC'@;"2A%UO=0@`+[N95Y71P/<%NLE.+L_90^UZ#V<.V#RC*+M"HQS5A9L?*S
+MF8R7%QD,2.7B!G0_V:X^_KI5/\>(.;D]_>$_Z-V;$["-U76W7N3SHOCRT!KK
+M@TA8MDX4<E3*1G90HLWO[EOW'#SL]*"_`063V1;`$S[=T;&NL;#.0$!JP6!,
+M:`;[2ZDNC1FN]?DK:FF&RE0DSAG);SU[O1<41/MK1)ZZ1XR?>MM=.]_1N?4K
+MB.K>J'-K(Q#2E\4T;J%Q?>_$>GSD$"1]3D\!R4Q">E&7]._&ZW/.XAJ/!3%G
+MC%!U25A8=D(B(8_'7:K2DE5GQIK.4FW7-5)XPUA;O>II1N9R!M]6UO1<O+%_
+M#*J\<<1]N7.=!IVD`Z?"A=U[`%PN3+5>^V(2__'*>T_"8R(4#>C9,/*!IL02
+MN.0=T-N38M%4<ZJL8VAU-TCI!F)^<ERS`?'4(VYS<DZJ%][J\\\"#7!U`QG?
+M@!TXOATP!&@(WE4]AB;,.!P1"]Z^ZLQ[1%F>9^)4L1!%9O!A\__VB;-?Y?E:
+MYQJ,)"V[S1CCO"=`KI@T)5I#/%`]BP)E3>[B`/P%WGB[JFACJ.OKLEM7!B>Q
+M\#%_LL<,%LN'5D)]7A]L%I*E$#4DI)%`TQ3<RM^.+C]G]Y"1^DF4NWOL$`WK
+MOSVY>(HKL5/9LE/9$^>7B^)AGS3P,*79%7NK@B$I676LY08(KO\MT$!BPY/C
+M,$6K,+A3R!WV:*GXH-4XQ`RZAX]QJHV37(UTMW)MCOI.PZX&P0Q#%Z&+M![8
+M!F3K>F-W:WT]4/?)?VYL3\?<87I$>TB5-I(![3;)*&.HH1.85"^6K-P-PT;D
+M;&G$E%AJ>_2!ZG;@,7@\_R!T)*+-C<Q_,E^@F"[NH<QRA-U="\I\HM@8N;AC
+M3QDIAKM#2-R$RJB_0.B.?!2!$^7F^C*QO#\V73LH;]*6#B2L\`A6@KT&!B0Q
+M'E!WK(307O0DSW<HPRN:54$9,!+NOL<-JV61TLG(HEKK$#8'::YZW$^+.4'$
+MS:[L[YS>\KM?)@OS?=FVLMGIO0<3LQ9^K@$`>;X!WFGG,MI5<.<>X"11+QG.
+M>Z>:56XUC_O$W!U]\`"N@ELIJ<G^J"P]L_M':CJBQ:[4!8+(S$'O%"L?"\Q`
+MO(5%":XJQ:93%"MH".6T[EKC;^W$PII+?/)65.0O@(X??F&I^C@LB70'OPS9
+MF0:<U847T^5?*.<V?@E,+/401A2@JEU3[Z*@PUQ<9V*)OL>M'_.2FEJE%&W;
+M'[45&,.+?@>32;'?;WP!G[^R1'O$GDM6+ZPAC>X`@\1O1D%W!J_#_<'AXH_A
+M*20%W(\Y&RV_,ST4EJ*6ETJ$:MJ'N%K8O>@66"O^:&JYNN'5?V%<NKZ-&F$T
+ML16(V_2C,LA?)FZ%6AG83\3I8W;>!@&>/U(+$.TV&Q/M-D891*@!FI&BWJA4
+MI6\-DMI4[!Q]M_?QI5V276FKU*F<?`("V!F7:E=1XAD>,"O4)CP-@:3;=3?B
+MZ[51**V"+GZX5<[-=C7F*7!^@ULB/.I$^%63Y(T)F)7?C-P3[HNPQC_6X'2Z
+MC$B1:?FINX>SHHK@"L]N":J?'"#O2EJ/N?M(;"H`1!NY+.("J>S%'*ZL:#V/
+M5"YP;'(-N$SN9'CFT;?7KW3H$0?<VMK7#P*8FKSU(PGW78"Y\1_9X>*CVU\V
+MZ-C:(X77K=G#,21W/5:$K,[2<N\L-"U`J>HWO/RYIH4HJ("4^&1M:'J:[ET[
+M*)C#PIBHH`:&4M*6VQY/4?/&)=SKJDI&+COPZA:`"^I\>1Q"$Q,[CW29=+4)
+MB)A8S16L6B2$&HU1<]2HR/!L0WTDG4S+/77;FDO/GW#ULN6F*QK[_'U.X*]C
+M7@TVN\_(A"]LJLKSFZD-_\ER@+8R206#H$-4-CP@$9<##+0'+]]LIMW]*5(6
+M$U/WKJT[04KH0>_7^N@1?M9FV.5X5\P17)`,<<>'I#&\!7UB6YMBS577[O9$
+MG3&I4,R/M$/8T2<ZS5&\K=#6R:#7YM%!GV91QAXQ%^:MZ=HY+6FO-N]&DM*`
+M6J!@QN2[T9E9)4BD+/0$*;?B59,*[=YGOEZY(+'"P]<F:-]JT+KU01E<PK?C
+M&GKW^BS%+9`H+!!-WBRGB[@LYQP0#0.GV>3O.0^+U"G1'^8(_A$&9/]MH+_3
+M2/0'V[[V\_N=8!+OU&DO"QR#\45I,:5\,#>`]L]Z`UH`(*=[^>(\+06=&QR?
+M4?*P*S`XXS+/G$V]?;P,D0XRY)EBVT6>.)/!E12)`'5%#L&H"V.Z9MJ.A.;<
+MF*I6'!MLS&M7U<$A]US^2[F_(6(1GTV//F#:($=XF#6)!P[@Z#LM^8#PB8GG
+M,],G_X_[9;RM_UV76S95+04"H$`<5.TPD<^N.OJ&4B^"5.3!DF8EE`A%W5RD
+M58TW'8+T7"4C_V`NN[\H$B$_V7..$NDRR?ZA^[SR"68?%%\8OLB"F"QAFK(4
+M@/:*J<E/Q<U2IYR`M(P;O0,/Z?;-&"7-UH:4*UYY[PLZI<"@$\OUR62"_-B=
+MNFOW]I;K7":>129H"E+V*88S-<\)7H4+>-JMFJN'0*N71S/R0C3>V\0R=2I$
+MIL"XZ\8_ILS2++N*^/F<?;SW$LB^`_7`:Y?75L1MQNNPW]]K4E"YP]B.MG5B
+M?[SA^*.'3BVA6;^R#XR6,D+V_CB&I9T?4,N$AF<2C71)9]$"W+1H@K`:\8!,
+M"Y_YVC&%Y9\`<UB=U^%[N?&6#2L]T33TB=0?HAJ9C^C(E_1E'$Y$_$P&+_1X
+M>WK+QPF3@8LJTPG=)>_G2A,8[/8^"U(94@NA*;=F6;KK&9O/DB\9'"A%N2D7
+MO\F!UAW@"B^_RX-_@>`1(14NF[>J7F[;Y:O^T_]=?-T&[%;"72NVK/'%BA$I
+M[X009*7`GY:.M0N!*&>+'`\63G(.^\X2X^FL#`8+G8*0Y!*;-?GHZ7#7Z6/.
+M]A0>.53O2P^6K-H3$NH`/W(*`BK'61_@B"9_.+V''D0!>KHXORG@#FCT?Z7F
+MEKBW;@.4RE0F:MS\37:LEQF0*\W<V2HW4I#`F<#S('$P8:^#:=AHBAMR^2O*
+M&O9>Q?#HAROEWJ3F9]+7!Z`%!S/Z3?)ZC5TW.7/(KDO$]>!"/&7?<-;1.6O`
+M/4&=`&\D%#WR1T$GH36>.XX(0?Z?;.6OMB*8[4[+0I.:.OVS3V#"2[%HF*!T
+M%N0NBI#>I[V\I1!5N5R)=/?W7_>BS3?"KR3@0+OI!YS?3H@RG9#K#G7YW*F?
+MF^SR5/W'G/HK$VW*OH_')OR!X%;>52]J@7M8O(WFS!'8Y(%XL0*6>7!\O:"\
+M"CZ4LEJ"1[YQ%^D!R4EO+%\[#>=--],V.KL</+=*[/XXCV^(W2`!B."(^'[O
+MX^(7<@4`6MNPKJV2YYKJ@B<Y^BR"[_H&?R6*YQWZ;M<1RICFHW`T\1GJB03/
+M1';[7=Y%[IOY+*DM<1A:="52D@Y?G/!M,,6ZX<\8^\X+7%?YV@E(N!5:FM%C
+MK&`.G:1$CG>!7<T_`%@/C@R4F7[;WA6"QOUGQL-_0H.DB)C-XRS71DUYL-'T
+M=UI?GGYI!IYA5UIU&1P^;H_.<\D3?GU2;=K0N$=5BE0]9.WQTT>XDPO7!'M-
+MRKWETO^HV*Z2#WV"*XUN?RCQHX:G+D+HZX2725D(6I0<X.X7@,RV,]:S?IQI
+MHUHJ?G?@XV&_LZ<CJG4R2Q^YA"LW8MWASP>C%D#K_CP_W^H5>B>LW(9T)<X0
+MTL9I,J&.0L<P*=SXM0I]'TIW5&>.(+<!>O=-$3MY.0N;Z_;^C32)9#"$YW_9
+MF?R^_8/=$#7A*P(BS"4A[+^8S#5\92D,4*/_RPR!C5^[H-RQ@I2*!VI@P5R8
+MHZ-E)EYV4-VM4)Z?GOUQIX"SM3(R]_!1'@Y/D']]Z2[^6M^1YT3_7M3.U/C/
+MK6,@.6PGO9LO`E*E.01?!RI91543UW1(6,EC>4C94D$5NY/L.M8="U)CWX-%
+MYT.</?W<6F-RO_-_+X<.4#[0%7-ITSXQSCX+%,(.0:3GTEO<$MPJ+HMA19#5
+M^0'4?<<LV>G"+Z6)D&UYAJ3J'<LTB*A6GIR3K/K4G']2=YXGEZU0PX8>)<J/
+M97UH?L;F-YD-3JBYYZUP1DE8Q*[$8"<0MP6QV=,,>\F7%,F:I_K61.1OS2*0
+MT`?VIE,XCM1V7M6SC(I(-(+1(_7N$<U4<E301\QEM/(5\?*\(2FQIVST"J4N
+MV</!W#,?B0JN7MUG;%%7%EU"%8E"`M3!/4?N;82(V6&?F9V0@>_LR9MSW"2"
+M_#LZE9T&/"/[2.^RK%I1+-XO@7W1S*:W!ST$#U>.)<4WM&IG`5_5)(+R=0R^
+MQ?Z\<6;H;J7@VD8<Z;`KBN43;%F&C*0"GF)&[.$,+CM@]?!"E`L@G^$LHSBL
+MO)Y`"NX"$*]WBGI[ZWS4UXYPS:R'RZK-"F2:][L;<BS?52QM"/?-D<!$>2T$
+MI5WB"X<=H[^D8`TCH/L7/!;&`$[0HFZD./1CV(.9AK8X<R+M^IK!]'^JSA\2
+MN/6?^P6C#;-N)KVBH%EK\"X52!F7@8K+$?3"?VG??:8\;5W07_HFRN]+IRX@
+MJI+4:H:B[OGR8:$J&BP7A@JJVSD_VW+L;(SI0'7G,+_KX">YGJ>#Y,LR&SY<
+M"+@BEY^T&Y;A#W='!6T#.:#C+08?),B+-:/%1;Q`\K0K(PQ)"+A<DU:5"<EV
+M`(OUDJ!+`X3T=XQ*T.?)9`?J(#?2._`>7>'/0[]"8<&5AR'408F*_'K>\EFW
+MN;7NVJCI-[G/TJ4?H@:_LY*(+G*H`>L$%KR!8#E\B3J&,`DRP3D6TS?/7(UC
+M4WQW'SW?$^II3\J9HBH=A!]=9PMS/>_L=Y_173#31^['%A^:XV$<&WPT-B::
+MPAB*@,OY#,&>H+9%.<=180U.N=:J?:*EB"(X,@8"U%$8].61$"8F[ZL>`IQR
+MQ_].ET,K7ZF!9-X<3;4HY85NO2T8S7#;AAT9"#3"B2/;J?X)EZBM#+G[0D96
+MZFLX9[IHYF=G0\JB-4:S70=)TH7L0+DK5?I1`.&J[UE?OC,%'R6:JP+1X"+Y
+M6&\:?Q:..WX01//[2CN6,GU[^,!MH!.@E>#4@CI5OB2E,'X76-*JK6$DI+KV
+M45C5F.'?%!G`/T6Z3E%4-'<C`7SVD`ZC]!>GMSF_@5&I5-2;F_"^(LT)!L69
+ME08^2K)2U^R5[G1E\#XORXW@)^Q/NG(&;%EWD<C).AU53Q=]>@%Z3Q)?PX&"
+MZR=W#4XMF.V^0TSRBLI!\8T.A4E^HT]\E._Z,?)T3QH-7Y:A?\]F]9TN.G6)
+MJ3&OAL/&F70ZR.U%D`D-:J_L:/E75E3?H_GF*F8$:)P?^!A$+>'<+G\N.OA$
+M17&?@1@:$=YUO?Y+W'G5`++\-E+`B3"<5)AY1S+1W=7>ET8@-3[1=C8\[_</
+MABDG':Q68CS00"6^=?VS:)4#^.;JT5:+2W(]\O;/&Z&KX0SECM3`JP,?=W64
+M.L:\70`#KCU#AZ+5PHEY4C)3^-^SU[Q4TST/#!H9T]!OJGG!QGTU$96$44GJ
+M]T\9;M!J:I5]$F*0*HO>MDQJR$3+RFX4X+P["LNO]2WYJDUA=6E5)=0DL8$E
+MAD28,%+:S%B_BICW3V*87VBP%'7QC]17_594]0FGS3ME4V7Q(8XB.'*MY<W]
+MV*4#1(:5W__*L9V.\[><UX:_PXG)*V1N$.209=(UX\BR!Q;1I."-GR#$TC_=
+MM[6@ZV54M?$BY&?%IPUQ%OP]*XB/`QHG68F:%B:ZTLGY3LY3%/GC+7!)\G)L
+MN3YZ^\F<O4!+'$1X$"YY6S-4!9\[=PYZ:N`]2;*0G@WR>/VTA"5PG4(M-4WE
+MCX)*FO=2-G27(_.IV:/KB>N_+=RV&AV\T8?R<,@@H"<,4\$4[)A4U4$Y!D4T
+M5>D[OMP(LZ`_"H+9+W.N8XYS^/`?V^@9R2&"O:,D;,`/1V4V6GM.D'U+1J77
+MU.;./3=90[U\N.0-J+_O:,8:L[;VG0I6%AD[((V!LSS6%DB$TKRZI)*4(Z)%
+M@@SB2WDJ*5;=9(_'WDK..:[_W;^'*S!1>9#*N2_R43*L&:VDKM%D""D-"LXF
+MPD*6NO):9O2F']P2&?BI<'CK4^R&3EGMR/C&[:"W@M32P`2O;<#)-(+YOI5[
+M,1RG3=G<6M^[<_7BQD@^THSO5K0YZHB]SO1N-UQ;8ES'3[-7WAT-5C6Y\?6#
+MV4"!^_;Y;;8T^P*.U7.*(*RS9!Z%F13>G='?L>9H5U"F:A/'':.^&Y_+=I"V
+M9]V]ZW15V8[(^W!8:[POE)_\L@G3=\9%9VA'U5:C$JRP"`?VYU@\>-P)I:0#
+M]]#;E&T0\0_7I\36B*_:9`7W[O0JX+X*D0*$Q=H$8[<>L15V2E>H[*V`92_(
+M.+5+Z3@@![!.R/WV&*-Y>33FA?JX_I_UO@=%>-9`L<&:,@?]&T&V2H<%.Z:^
+M=O-:T?1J``[5&=50RWD/\^A^N64\Q:@`!!T!#>=JBH=??O_V69VTO/+`Y?,7
+M(U;":RTV^]HEA`^6TM)/.;XH3=<$@(L!4SHA96>=!(VV6(WU@XV@H$124Y<J
+M0^$,YXZ7)"AO_4+K0;$"%#HG%26T9\\>B>AR9:#M:(,K_`/*,N/0/EO,IJI?
+M>K@YJ.;_7(2^A67_<`)5H/F2\\.2M^1BI1EVZ%8OB$./Q`K$Y6A56LC'/J!+
+MI?8->T7O+IT,75&L\!T?91?Z-)@PMU-P85NV6Y0-[ZDEYM6O7*_6ZE@AH^^?
+M3!LN!:22.)W+-FM($U.W0==O+HWKGYBL,4TQ2AI@JE8(.PZ0LM8QI++E8%GW
+MDO/)DGCN8[BD)(/B$*-$B=V(X\-?9?<1)M+K)1+F.9#[MB8S/'OULMNG*W<M
+MJC_/JZQ/&3C'Z")WJT'+'+U]W/]O!LL[#)UN8HB,?F`A?0O-ZU,PJ7@#[=;1
+M+3&`BBA/=L!76>9#,_^JZ^NN@V<O%FZ-V;#M)Z<XMAW30$.[&I42J&_0R>U%
+M$02Y64%4$O4PATHVX*Q2(JD2.T6#WZ<',N.K<^'C@$L=;7.+^G-38XE/*&*I
+M;).4?,KOJ5>]RO,NU1F]KW\.CNIN<@H.%P_NS&`@[+8]>#G/KGWENP]U$/&-
+M+1YY`7;(VYVZY:8PB]5M;'%=N3:^,,;CF(_L_E@M7I)9NCHEFVBG=)AX:H,6
+M(CZ&8^$K\_8GI8H>CG<ML'%+-@>UXABE%[D,I#<4WCU$#E!@/2E_P@11+*:P
+M89V0V#"!^'IB.PL`6G1>E?UCC/N0=`FR+.<8%CG>)^<'2)=DR5D7;1D<M4L)
+MJ>KH\3LDL\,!(<]$LFQ"?*U3O%I$,=+#*CR,)6PZ!O+D5M^<MT7D??R<H?8_
+MYHQDIW+<6M':-"O)4:8?\PC6&9$[:KOB['<[H@G85E-1WC5J=!LE+F=N>]IH
+M[R*^;P>;:K%T::>&>;JSL=R74/'#1^P`BPR60J.;$(PIE>E7S"D>2^]-NR?S
+M%@294U>H1+VNEDZF)OLLFT+>WWA[.E7_G)D^,ZW,94=/@HHF!DX"+8T$P#VL
+MX8T!G5B<&QV9Z]:PX\JZ:!QCO3;5^3;S:JUK`PK,FHMLV*#<<BYB[(I5^*JT
+M6OSXO5MVQ61Y:/6A;K>C:JRQ0?_I(O`4EXIAJ5U>#(,Z**=#9OOFN=NQ8]=:
+M9+<1#ICA]*_F*\GOA_@QCS4&+4U@5OLY7UG%1W(?OT]@H=L2A;&ADR]1T9,2
+M;_IZT%@`M]B(ZA!]NN69B[/6O,H!TQT7RNE46T^`2.NFJ;%-QLX5J@5A5?H'
+MG71K_<L<KBZX@/8]<0AO^GNU8MOF^.%\5/0A(&'"'0\=FV,_7P3+!SC3)<'^
+M9\,?*%I=K7'H/?OE@TZE>U;7VR$==_1L%8F`/9,\H#YYM$D9(:T[-=Z58Y>Q
+MU78QJB];YGWT1(BJD5]^&0U0-I?([-;HO'7^"?:/7<85[?V>.)-[.J"=^:B2
+MP;H*J$:_3UT>#?.#V!1OTSW>0/39NV7^CQ'(G-!QDPB;)E6H7%FLR2X]2%V*
+MUGQ'1?:GK&3A=AR894`(J)OUU[9:D:V;1&>7RT0[>2M'?AWX2-#Y\5_LTZ1B
+M43!&4/&_8FXQO>.K!WT5XP??PK]BR8')CH2AX*G[LG5^*Q@M:%-N+9+CL1E@
+MIY`J0?;RVI./EL%IAM>2#/[?93H0@]K7O)O"Z"9[?C:=4I]_*TX;H47^;HH,
+M[#1AY@`GUT?1*@T-R2#9[6$W+<L;YK9:O[X_,;<>7?=\I=.</6_=S0'YKP::
+MJKRDP@`P;_R@WQUNM#@]R*\K"<H,EV5>%^2D4Z8TZ;,8KM=BUZYH!L<(.8K1
+M%@`TVY4*J:EI%5`'V\1G;.1F$D:ZC/X'WK)79]ZF(JQ1_QIQ(!'@PN]SGSN\
+M/JY8JR3RFD*'M5;=&O>Z4&#3#D!P>/UC$G#*I"ZY2A-$'3-'_@UK8UPC]7*Q
+MU_=LBJU.GVQC#M>4,TLPJIUZ<I]EWST/"$[R/[CK;/IQK5]2B!8Z[F+3Z#69
+M#'$KW>*'XD3$4_SW+@9V[G:9@Z8)AL$!<UHLKY:HML`;OT/2E?[*@PNGR^?/
+M1/.GA0%U1K;(Z_Z3CXY:BG?FEEP9`Q>C=^1$(3LG%#TI9.XM>KE,G*P?:N?%
+MXB"D<\K+%)@=YL?P1]XW2W@JZ715[."FV[O37S/<7Z'\8T+1RJ0IVI&[?NR)
+M@/R\IR\D.8)W@ZW/Q2V<0FYOY,O5N/!ZC+#(,NE_B>B9F*EFC]R>XU&XX(D]
+M6B999P@GOG>V/V+2:ZV3.;E3\V6O;&`77TI00AYUFY."+@:RZ(:.4MNUI9!L
+MW<T8+)(KASBL\Q2P4CVQ09Q;U<$94?FO\58:_@,_,W=*!A%%"B]T@ADZVB4.
+M*G6TS]FMI\A&)9DJ>3WA]3@L)$-!)%UHD`YYEZ?J3IFNJ`U6L":MTBR"L-Z"
+MJE^_``7NC@U\C!N,F4`W,_N2DP.E*HHARZ6P9\[3"PEJ*ZQ0[K^4_\BJ:>F]
+MYS#@?[\YKGN;GVXF5.YDS,O6\5ZM`MK6%5/?<$`F8#+UE6YF=.A*$`9]7>'#
+MF].GR><Y\LX"I[K?6LJT`;&)4T#L6#0:3$E$^+6%0(\2TDEC0)7\A04P?.IZ
+M*'>H#]L]9\-[9L,,SF%6/8.-?7EWO@).N![=EW.;[%,/M7"Y30INML(OZ``H
+M!`PT5711PO]($A3C-9%M+[XYEJ([WHS6[1W%DJ67%F[OU3$5/@YO::W$:9];
+MX:.KIM"GR`+A_$<G.8L1U6LRXA+'NZZ(6-[YKLA%=+CU`/E7A""8!>,G0FQ3
+M>>G)I'OQR;6F0K)-]>:H^0]<C+V=UL%ZE4;H3ZDUQ:UX?IG'P%#8S97*Z<"L
+M>;R"!.SD+3/A.D*WQ*.'M5Z+>U_*YXP9.,`[Z]:'1GA%+$S<WFROO6EPNN$@
+MCY!Y!1YD($@5O"WPDE>OO<1\?6FNQ/\+XCP-$+4ZN+6_'7_F5%M"V9V&9@_=
+MUV3>G15A'BV=<M"IGTOG')(GT8&R`9'/Y]AD2$:G%4*/H;4`WE,&-"[FO+HA
+M5[[TO@4W/#RTH4+'PZK%RP:8\TJGC#+4H:Y/'3<N&2?4RG84/+Z&=S6^N:@`
+MV9L&*J:J4O]8_\^BB4=HT]?@F/KY=VVN^Y.E2L"_0!3K84#W@;MEK1\W#&1G
+MF/XMD^CU=<GR75[G2WSEEXH/4NI94Y^>V'0ITXYR2!VP,OKM0#$E3>PZX9`>
+M-MW^*OGQ200ZX93D^O&!%\5%[!K`]XWIO9PO,B7CSB\5@3Y5*92:S;GMEL#K
+M2D1/ZD$)BOO25KOW*K32`Z=*]YQP`7H_9Y"`C$>Y$Q<M^8NW(8EJH6M2E5%_
+MK-N)=^%B![^_`=U7P6*-O_KGYCX.`&+"=7"FY044/L8(U<G,(Y'V>>X*4/])
+MVZD/9VOOZS>'$`?%\]AR%4!C.>!V@"PQL<':J9]T$9G6SLGN)5"XUD<G?B2(
+M<Q.@TG+4F7GG?+(V%*JNT?4TWR[.!^1%!4&D_.9.E6\PA$WQ[3Y;\&>,KA#!
+M[$R,:-425')$#HH&R+/62*M%VKSAY1V;-$U51VMDN6K`7%=9DJ'S><%"/#KL
+M9>\R%"S6$"2O]O,!4)$Z?2$C687"@ZEFBT_R@-X0NNN.RS*RSQCFIW]+@(&^
+MVB[;VJJQMS-A9[-N<;</#7O>0[*S2=_?UH@'OX.`K?OP7*)7WC,@I0*',N-U
+M/K`J[[C]0RZA3NF,SF)^I%WDZ!1UVR6L9D4_[G)NAU(WG[6V?PJ"JKP4'[C6
+M]E2QAV9#%A0^[>X[:)'A%D1^9N;EYCHJ@H]'W]9Q76O<70_><C$F0(MR:(@"
+MFI2EHD8J'*?SBYN,N'&_E:M%_+W;F.9'7RHAXL!/L_&JEP$R[CY][0?-J5U(
+M`6'U./GNR44Y;Y_:=(S<,X5M6"(ZH/[P%T#-XP8"3U<H*[L`[E?CRCM=OTN.
+MLQN[PAD/OT[:W?,V#S"SI&/:MGU0/B9;:^OL1ONMO#4*2T=IKW+8B16)(7*9
+MQU)OOIKYZ&]-#%*K8PGOG_7H,V3(UZL9G5]1CR3['LY6E0R8%TG67`A*C]K:
+M`K$^=?VC(S=K/>YEY'I,.O0WV['A::#+/CVJ:HI923I7KW6H5'PO"2U(_.1?
+MM/B(9KA,ENJU$U+$&Y#XQF6(!7<^F(J$IK;J/",K?([GDR/&4`'*F96+J.^T
+MI.!1Z</$(J+5*`6ZS"3^',4::E[N5C@W2]O!)VS4[\`N:Q#]'P^W=$",EJ+(
+M+H^<X\GNC":Z3@`GI/[$L-;:$`9196.ADS%X-)(@>6!10?KD^'^*AIVA%\"R
+M[.\L%4,\5.DLMBUI=!G85V<Z[S/DJ(DQP';WI&=_'2W.=>=IK>2''TKPP5R=
+M.^DP$]NT#.'ELQ6':CMT^>:(*O/9C`-<=Z+B>YO?G+<QY/>ZXF2EB;79Q7K+
+M):3783]I>$7N7=UA)_7-UU8/J^;(=O?REI!<\SG`TT^]*D`E]I^P4W;MR,05
+MU*G@=H4U`IORK),KC@25Y!9>;6O26C)0OR\-,-\\-(EDPGXNL,0CT2M/_(ZK
+MI^/J.SR[DGKOD38=:2.Q\S'%(#6XPK8W0#1J[9:DD.>O0VV>S"$IZD5YS1%$
+MQO6@!7S4?[.@`T)#60_[O*T*L6A]*6ZYX9+B#F8R+9M0Q-,E3F(59G\XFD%3
+M<`VG_LMIU2(3V>R:_@Q&:QLJ@V)5=(X9S:Z`X>EH6_34*,G7_\*YJ04Y*Z9V
+M^.=W[Q\W%NRA">3'^>CLTNPM>;#5L)D@V8XG*T&#9SKHUH@"S?<#8NE`A'3A
+M[-):69.:MC(\4(M_SK0P\:KNT+0KIWVBQ[PR4I=I%0$'K4E$'\SAASPRO3(;
+MS2RJ/HPF9KW?XY=:PL`5ET\K1$<SW-T!'_[H:56;C68X>^/<Y#=VL4&N>GVM
+M,/'WA6C:00;K&#X(?'!X_R_I<H7HCGS*8*6)LAE<?:4VYV+`4L8!'<^546#;
+M/8M]Q&:UGFO"9T0H[SG'D;*+@.;_V$3(CVZ/BLLH'B.*;Z>!F&48:PNP\69C
+M"MU4U4*0)KB_WP65`+07(*UG/HQQS*?6'7:;&5RI#6'ZCK:&9YP%B.ZRV1T6
+MY4Y2=VY`"3$G_ZK!$*E"<TV.B\\]".5IYU-11(RM`H5MZ>_1H6C?I)`3G822
+M<:M$KJ$QD%JGH&+UV0'ZVZ&):GK[KVC3\K;HJD-E*C^P)KG,&+W51Q=<)OOF
+M12]DK\P:1FI^0;C@0R>,<&T/'=`"&_#"R;EA]4-TS:`B]*9L#.GV*^8]EJ.M
+M^(U&>:#]O\O?5,_,`W]C$13`3Y+DAZJ."49^"&4T,3*-?[<?'9^0B\^$KBCH
+MAUA2[%=N8,RG4E!-#P>X4Z:57+T#@3(AC\:Z"8G$>MWJ6%?$;GBNRH6WEWK]
+M#LRP;.05S$9:5F=^3#AVJ/(4-^-5X9>F@X3C+`74XLI41SP2<H>*6UXD(\I5
+MOF2,03C;QYY`MOE&`CY<;UF[D,JGK:("+IM*UR]^T$VO/J)KY_AH5"2)1$S`
+MCJ)G)3#+!HL[DCX=C4F8YMGEB?3T@,LQHY>/I&#G>94YWB+CM8&=IBC._*7T
+MK%7%TZ^D\LJC(25)JA(-7_\W\#'UAXHZ%V\L=(YJJ+Q.S?F37:RZH.UI^^:B
+MHP4U\Z_#.L^=//A$W47)7`<-P!=Z%&WH\2,B/JR3>>ST]YH"<36'T9,R'F=9
+MC794=_H*648U\H>]916[I$$Y"XK(&N\V-C-,\7)*R+ECA,\S0`B("JO9(R-G
+M7(L8/JW2R(&%71OT@G[5_["<U'$!('4TGZ4V37)3S^'8:_(4BXJ[>R;2%,)/
+M37LC/FQGS*EZ@`@/$=M!?A=+K:T.<6Y)?X^@>!IPI-DK,>85J1C1I[XTCD+0
+M1(Y"F>26S5SSN%6:\*7D*A8J;1N'('Y_"F_([N=/;@SXR8,89SX^<*OV10D:
+MM.)?;!(C6HQNU9`0E1OZD84])W0B(H^CW,:UG?X;H%\%]#XJ<+KZBJ!<0QB#
+M@0_+6GWU>.U^%GJ0D]%?A5=&ON#>#XQT,%FDR"=!-G1,E]U;)&I/Y^I6PD6T
+MF^LLV#YWY842K/;D\K5'8^NM\WMAJ.S550IP0)(*\<"&<^)1>WZ+6-JACA.*
+M@1XF\Q#.XUGQ8_>3B74;V)+]M@QNX=)I631^9P*#!5A:M1_^%FF!I^1[U?BQ
+M@.7R95/7)FZ9NHPKUNIWJ6HL)"$_:1<4T?;T:1K7?I>R3OS'OH53X-"SP"L-
+M`?/X/9/),;^G/OS$/&\#*)?2-PF>,;/5`8I(7DF.Q*,@S+C#(ICR<YR;$)8R
+M5/!))C++&P\W.-I,S4P/(6K2E$"_!*6]/&M%_AT2Z,5:UM5V%MJ2O.QP_2Y]
+M9B2:[_'>1ZYU(>8K;4G,&]C?!?!L.#$TJ3$MIRO@V9X*)]MSOAJ!;[3K=O(C
+M7(Z!Y4UK!@*/`Y[8.S-8FE26D4\RCR%SGOS(T9TP&%VCSTN1@/N)%LX[X"[#
+M1?)_N#G!EC)=),,&BB6/9_9!<\-MO+9IJI*Q=\F\=J^(;3TH:,C,YTT'?^]H
+M1/#.</)X"R([<GC@HFN-BU<QMI0&8<+D>4\J%G_T0O*3Z/'^H0/#JP/4.,V5
+M/I2PU^9='9S!1HHMK74X9DCG3Q=8<`2#FZP#B"Z_Y'M9;"(8;`Q!A5<:W`?7
+MRG[GR:\3U&)G:V>ML3?[*J':PP8`?R3TOL(M4+STK;2=[^WIX&"')EW-BX3=
+MKW=/=3`*[?N:44JFW<V'K9[RD9K]]4%&V.?82_:LXM4W9[D\3+8$^%#8HSX@
+MADN08`+H9^N,D."WYVP/Y'Q,**-/E(C@RSU<W<)-M-%?VGHOH2R4#^HZ:$#_
+M?$6.IU[X:$#5D1&2<>QT>-2_`/84*,FU"V'+;)BMEBY5:3+(J,8/T6C%[/_!
+M*L;L.TZCT/3HZ^@/#_TC)_3L"1.[Z+Q(FA,,1VLT:2KUQ*#442*=9Y0X<>S]
+M>RLSP/3!%K-0YW/>_M&8HW^8C=CG:.,LI>V$P#MT*B)]^8B8[3B_[DHS#X+-
+MOQR3>](1H])E)$BTL5*X-_FVM\M*ZTS5;8>+-=^L\<O`8NY!E+ZI[$+^EV<Y
+M[D[HT)XU&SIA`S]SXB?O%'2,E#]3[M?_-SP>;#"N3O\8@-@`$JS[#CC,(ON<
+MI<*3?DRS.&$@*>@PZ]9EX""F@^[?,/2@?\%`3&?>"#:(.L:)$`7_0`/">3W:
+M+_,Q8?Z-1U:CR#2]M&OP->&AM9%2UV2?M895?3NHH34>_#/$HA&VW&>M+ZV@
+M99YH6]["E.38OULK44>G8_-!'_"LZ1[J58F!IWQ;9NS?)9*II2Q4U>$(D>T9
+M&K9X!MFGX@\$$Z[OH<(%G8=UM,LLJ]$ULV%,8&'_1Q_1?&X0/'>V!NN*^*F?
+M84X`=I+@F%$8$04YHA;N,7&/T`[)30/!F86:85?07\BWHVCW6WG<$ZC86BS3
+M5@'O<_0W3LDL5%R4;U[U-S.E?R/\#+$8V/<NQW)3D&FA#+K9VB9_G'M^?<8$
+M+Z.3C&?403O]NHC>SDBF?ZD[AVT-2S4^2UB>['<Y$R+ZOA6=*LWXA,=;H'Y]
+M/#SV=VEVPKYP2-=)R)1C#450IP<ZK6S_V"J%H.2'S`^UO]BB^4`\:="PB2E%
+MLF?2328%6RM=6QP\-&"UN:,"E30@V)T5*C_/V(0TM(J),=31505`'>0CR,@<
+MZT6*&,U/M\[T8X(3JUU^I4-S]E8%L/VVMJI.2@EMW>O:<##A(5?`S7>CO&>8
+MVLJ&C+@A`"G?5P-6K09+L</N,CA40J\)L<HGWQVQ$<J*FSJ+/^&_<LB]'5P4
+M@+U3$SWY/'6D--<]?LAQUZ"\X=E-3R<1#QA<HYUAS:>65I$>=K7..0)HN/4O
+ML+>POI?S#CS\("$2R=>"<IJH-UON7I@RJE3I58'0",6/N=YX2_])W'^_V4M!
+M8*/P8:WMX>3LIFK$#@L<]2'(98F,,5C^[!`2_+S%7&`L+=E.0/$?NO\+K'6T
+M!"#K)DN3R&N4@N*M%!?M'L9>Y1W.?'IJ$?AP->\K"X5Q.'0(&7?P<=JH^-&#
+M5+^I;*K@/S>>KT87)W=^RSOPH)"B4'P9"'JJZHN-FMO^/VL1"?4@4Q(CRY$>
+M!`ZSW.#^@><8;HG?+HD-2'3\O_4-5!N'%(CKRE5=^6AG@N@DM$O*V4.8E*@M
+MUO,HGT_6\87H-6'][[TE0J*N1Y4^9E#WVQOR,^Q;,V/BL(.Z04'VRD,,RF/8
+MFA&+T@[U7@]I;C_ZPT_<(3CLO!)X-ZS_M%H)=P4]4%.`Y4#[<`S2%C[<<#JJ
+M]6HH>B97X9U\D==`L8KBS`;$VODE[+NC_%<WS2].][EDGMB'-V(?#08LY%K)
+M'ZX%OX2@DZ_;)<-G)KSG!&OR7Y8$`=F*D)DQE*#*9@$4%E&\XL8[RH!#/7"?
+M8(CO4W>*]-2YW=_ZPU*^G@O3(4&KV9.Z1,>RD'GM=Z'LGA94:*$U<YT84K),
+M9]T`O9<=/:$F;#7JRA*]6X>PG=A&Y\[1FG/JBLRV8KV49RY'>[T!!3M7)W<U
+ME(UF'+45\HFZ`^R/(BM-)/,N3:XT:V3Y[>$W+5W?"%J73+]#LF#Q2"\('IN?
+M"LB*9/RXKGP>'C36;8.^0KGQ\@6'/R*4^#I]'O'@HM!/9<>V4C3E23CB$Q";
+M>[37(7@IKR#AN(R'+!:(#"F[4<,XJR2E?TL>A7\4[D$NPGW+QENJ\XA@S,6-
+MF*G<3W\KP&[XKIMJI<8.1=SL>5ZUAH5LNCGO)G"<CK8HJEA4LRZWG$"NJJSW
+MK.ZH7OZMBQ[8I+;X?QU26;,FG^VR1KSO8@BUPS<4DP-2-QBXN(A1CA-4PB(Q
+ME8$5T<@EG,`R?\`@S<S'#,#IE4Y$'W!T0#6<6$V"=/8:M8/`WQ6ZE%P?O;LZ
+MM+[A8*4"4H)S[U/^T,,S0FH`BHJQ81V,FL<85',>TUWUPG)KZIOWRO2TQD%/
+M&V(I2H)T;G9YOQT]I<7.G`Q,)JHWQY))^2M7F&V`QL3A0DH][%KA(E77]-`L
+M6A!,BB3LR5E8_J"[>[[XI9FG%T`A+=H8J?;3%"R![DE?%E/VCV]+2:CX;5(J
+MQ/4&(1`&,$?"\CK79=(4&/WA#DG&**S%,+:+\\YJBY+)FTFOBASLJ_\`V+WS
+MJ3!74[Y)3B=<KYHT"CCOZIDL^J*K[Z\+&XR!`G&SXSEV/V"1D2?(P>!4ZJ*/
+MUR5W;Z=>%^/F(?'WV<[I>2.M,7^84`\MD*J?;24J@'\\+*YY">Z7[J;XD,ZA
+M=S,<9S2][?2O[Q//'J+_C$Q76R6DF\/_#)SE'80'YW_=-[F.SY59TLQ:-MHF
+M(U7EAGS]/!CEP^9.Y5Z=0K`F6;_.('2R`\@GG:_WXTJ\.=,S`@7KQ5%HTAG9
+M3HTO,'NKUTPQWA3R(O8$YU>C>BF.X.W%E;MT>QBM,/L#^?B*+R5W:S%8#@P_
+M\XWFZ2%`)(<Q@`41U[;0HHF48/P;N;/!>@8E2T?YWPE#KVHO>V1_:VUF-'`&
+M3P<?A9$13+7<WMHP5H>ZK5B;%"3EA0!W,`]JJ#,.C@"7`$P83#B1=-K_(PXK
+M$J#28M*H2D`0)'&S/Z5\&HIQ[[2\A!("M5G7+JJILMDAYIM.*=M"S/)>=TV7
+MF`<8>1Q@,-_<RZE:=JAETL'H_I[8`^Z6Q7T:]WO\Q2'!F3IT2"M64'S\ZB]!
+M>U?D:&%T+`;#%PJK)AIL^56N2/@]U&O*:*,PV-JA2I$>EB'!"7KS?AFJP02%
+M++(UN>*(8N"XBY8&A/7QX_F6!9<@;7K,6>=T/^?F!\H>Z>W9HD7/<6?BUM\:
+MC*)3@/@8GU;$R@G,*:S7-P+H(E+5,G0',RK=MCP%/R32MSN'PULI(RMV;@Z"
+M;7)2=/&I/&E-3YBM(PE4E)\6GDQ3(R``_E?"^T6!VE`E^3GJ#7$P,HL['^>^
+M%H5YZ1LT_+NQ:0^N%:MA%A:K^!"GJ]I6_=WHT\Y-&9(N9GMIU8X]H^5[M^DK
+M/?<M)1(FQN$6P>'8S$0H<E!4/(?3RBA$7>1X'=MB?NI"5)3OG00DQ,TZ6('R
+M<;(S79.#4)NT0%;O5+031-=:JQ1K$N4X#N>E?D^WY%J1M#(K#7=H:6;OC1+;
+MVE%?Z_%K%YYO;_2;YZ@FJ-9W%:LF0FZ0=W7I+&6O'NX%,_E.@$8>UR@Y\[<Y
+ME<7!AC;&`X,\O2,:3X7H5!F6721MPG;PZO(1D+O*(#J#0^&<\>,[S-;#?&EZ
+MB+4_AH%Q>A='PFZ:4YN(-A)ASN_@EB=Y"(O%]C]B6Q,UUP3[>'K\;!<3;]5Q
+M*_1K^$TFZ<BYH48W-C;6!/\Q+KM))?%7S_U`=#[C^5.K;:6R:O(!N@&V)(_X
+MD2]9K6U8%1=*^.)'U^H6)?1HA<DL^=UK%2BS`AAE#:&`L2D/%GGU7\X8Q/P+
+M5&@7NAO'@"Y\[?*=L!?(:W.1A[X8[.\J+L]-'%<%64B'78JJBT[D[UY.`=F2
+M;IM[OD;(C,EC2-Q'QJ+762A/;$#4KZ7VW1$C<=,H!X?`3SF<WF%!3)^:SK\'
+MBJC.M2^X*90^@`6&S=6,XD1B.(:=317GZI>BXF(?KO`Q='\16))\5I^$2OA7
+MR9;!2!3>E#7R)`ZMR?W;F'-',J!?<DZK6JB`>YHW=H7!H$K+/U/JJKY\NGP9
+M:&=P7[Z7[D+#6&4N8<II,GVO9P?8TT(6-$<XQ)QQ?:'QC.MN2VCV\$=?:H.W
+MD3(6(W=WDS)Q2=YCD;ND1E0NK$OMV&Y,)$)C&TOE%GLJ3$YC_3Q*`[;R<Y>T
+M^@9?J(YB],Z52^2,HW+^ZS7">.5)?&\;!./"5M4[>O+9LX\G0\6&:C^A)2@*
+M2$BXG\6KJ%Q[LIKTVMBXBI#ST!@V7Y``22HW?VWQ*I"9[E<JP2&DAK@7+5\'
+MQ%T8B"!2_0C)[TD:6'8PUU/LZ&V^IY"L$Q'QYE3?2;(9K]DPZ)$Y)H(20W?P
+M3N=J*G3."T52V6-1#E(47)#9N`/0.R&<!-`ZKMH^&GI^.]B-2,FE-AO8PC5N
+MF0&+M*:@^NF1?5IGH'T>RJ$X+)Z\$F:^EF#Y]VX!,H3U"D;;)V&$=^4`%,-A
+M05PKYH1",%%]5`Y;NVZ3J?/IV6/Q_7?KY"<3OHLPQ-JDTND7#4'27^TQUGX"
+M#&AM<HLB3STG]1@Y*!-K+^#>_'ET@&9US((#:!TSM]20*M?GHH2FJ@./-SV5
+M-XKD]+:J[]I0J!#$]^G=(_;FU%Y`_P%BR=P:X"3-XK3OF9#9,)I>)J5*O7BT
+M3`3S$)^959$V^+G4KG_ECRS+#OK_,NH^@<]L\6QY"G3(XRR+"V1AP7X;VM#'
+M_/;H%P/6@F>58-IAN6X0#O\"R5#3<WZ]^3RQ_[E^[T5F-[V"!Z-6Y`'NVR,(
+M0:/<SA>BU6!<6W(DEB\=`KCW7-F;DGE6G9^=R?_M0@24^2!`:%XI-(#F4<T=
+MK.MK[FI*B8FH3(L@&]27B=9K#T:"A/JC@)D42O[XY:@S8+,B??(R@O<?]#K_
+M`850IPNK24SY(W70;)O&O;>T[J%@2&6\J&&K'G-.5*@_"ZY>MP6IHV912].-
+M?9IDL.2/71/2U]A.QY+/)%ASZ$#L_D:&%48F.)(<DFRC>UJ,I5"0"Y,7%4FC
+M$$,8XQI60,YE'R6D##)/:2F+E=5#H-V"/G7DB+;WHA9CMUJJW_M=IOP^Y+WU
+MZPH<2YQ&L_,Z\D]]@(%FH4JM1!3GN1AXB'<[SVJ'@$04<_2+N<MK.?+LKJ$A
+M`'7Y8N-*_G@RF3VZ.[HDI]*8[=?\Y/]?JSH^QQG#:\[_H_-,]:_I:%;@9U)P
+M4W<0J!`^?1-9+QX\C`:M7T82L1EFG5Y27_05*CMD`FWKU)WS0D)/[#&SD7K0
+ML5]SY`LBAC._.H,O7C6:YF$/!*M1SF4"H%5F(T-=OGFX<1'Y*RV;UQ89+Y_X
+MXY`UP"NL^3Y0[(K&K-_D6&00SQM7<"[V%64?;BGCF,0GE(81AFN6MPOJPHMD
+M94L^XK/2,9>;.\)PF#.&1&U792N_\:GBL0U["T]"F:+?9:14W]*F1RZK.]V?
+ME,EQJ!%2I36/E-)?=K9%C.O$6!N=B-1M+%)XP1%Y\=FFTQW[_@+>='5&!O!]
+ML;9'38D[\N9K4F7>.FD7G+MOB.#GI[]CB6V<<M62^N*\;36KG)94N6=(URN/
+M7D$*(4.E/*2.\0#YKDE-L\WQBZI<'#DXHTH3=\[[[NVW>&8N619!&1(_+B]8
+MOXM<79A/B,2WJ;+W;_=5<'##@LX?H?I1!YANJBHX^CP#SF?8.Z_/LM)J-/SM
+M6U7\PK5KT>KYO-!YOY9/("3M4XY_Q'#D;2(03Y-_N!>=N@8A;\(G<)30(%:G
+MM<W9/'V@GQ^7"-_T=,2I7IDRZ4CS0N,E?0S%,ACO"R):VZ'_;D,'-PJBR5_'
+MZ%`HPHV*%1@X%\Y:A8+A0[1:=6W]EWEZ=/#8UY;.)<GOMV1[(5G37OC4R,A*
+MC5Y!=-S788:B@7YDIV24QC!,$';9\:D_JMR]93HU";WGIENWKD)DL92L6]F[
+MP#BA'%1-LGJJ\GVJ/8BKCPZ*S#>(MNCMD'3K<U^5@99$0])=;FMPO?2]F5*8
+M?$(K^Q,/4,87\8"^!#@U>3[KK`G'!%J,#U$V^NX9U@FE7W>>3J]7Y'(X0%PO
+MF;2A='"4%N@ES"1`CSJ0"-`/[2M6"+VN\6W/^J7X[/-4F'\Q`R,ZSQQ5(-6Q
+M8B"JW?(=O[WK/4Z+37$48H6N'K?4S)`ZM2L[RN^W]FVRS$0*_YV[O5A/3GVM
+M=XR-8N6PG"SML[0ZE!T]?9'"`Q`-1R.4%-@OJV$I#7`H[W<E)8+^2.KW3L=G
+M5'YOH?`&/C3<].`$5>%U@DM`1Z_^(C/S&)LN)-_#DD05+"_>)7K[8F2,*=F^
+M!GU_@MM0V&(95MQZ/V;H!8"MM>S4P'F`CK#YO_&_O#5POEQB&?T)8C+P)'BC
+M:VQVJ`O+PAA+R<"88VZ62X_1?-'3OJBG-6/GI,'4'S6I!ZT;Q;JR<2Z8=EO7
+M=Z0HDBE;0R=_<M5G$LK\\.V'WXE0YTE9*Q8TEQW$K-4N;"<#T@=`?%N_P%-R
+MM@NW3<;'T0!O?;^P_#Y8VT#\"U*P[^,_YGJQRDS?<K/^!0(<"2T,YO852CPZ
+M7`[Q\@&&C=[W+*;_X[+4<B0<E!$Z.I`$RL:,;P(!X=1GD"74,L'8:.]+;$M^
+MZC-=8X^*L@?!$R3$.#7'GC#_5X[S2E>B]-A:&72#5>1,^0'7V.QS0^&7\ZL*
+M-Q8.;[N8DRT:E-1_JTS?GA/\@20)T2U?W`]=EW$(,U[PM@B<4!,+(Z1P`PNT
+M=VR?.1RBH/][YP=D[VTVV\VRQ5JB\G%8$4W5.W'M#'T`GXK_H2\MP+A]62;K
+MPME!%4C/WJ4W1<PV7[061T1?S]R&5%:(8W]NQH']&N.`2#H[@3@KK8KM,M3_
+M%,U3U#!F=+V>OF#0JM#N.UUD'W'&2R3AI$H%2XD,7!;>DG.]3$><GG7&+HTU
+MXQE(AN:C(T@_74KCFXHDH,Z-&!O-5IR9LX^RQ4Q17#SKF_WV]F7CJ.$2-CR=
+M1\G]2M'IP+'WR6,_2!<!?#'*XU[I2O3G1Q8NT?:!4'ZF!(?.&@\1A@H1VD+I
+M'@OTVW^YS,PCW!0YE0:%633KPN!-YBU<9_TM!&@"3L$F.YH)P"L8AX65SLA#
+MC=QMW;.=WNEOMSH)H94TWW9(-QS^I,?IIDIW^;0(0A+'?F$K%G>,N6CFY$UY
+M=V!P2DL!6CV6.&8+'P[\_Y>!<\/!9"';;/CO]%MWJ8$'*Y(>*-7;M][IEFY%
+MZRWQ%8!E1_"'>MW#.N@@2Z)(A;/,F!7GCQK"+/Q;B[L)?Q6O'M/31:S+5'OF
+MR2O\J3Q6$@*>B[$V#@R>'*Q9MQ78]\<<E5-C4Y$<P%`@8J@?AR[V>385L34F
+MTFH:R?4'I$`6'5HB/47:;VO4(/!W:C?D47CK"/G?(L82W*/9#7&2<'E[$2^O
+MUX6,:S2)&`3,OO'@[1&V&B=-LG4:A]3E%&R464MXG8/LH"]P"R5N=Z`HYJ>[
+M-KCZ#&.R3ID.-R:)YA#/)LVV'74-`2[EZ7%0T@#SOTD07*R#.)+2K^J_7':Q
+M@9BSF",15B@VW0U@?"0?T#]=Y';,J2:KK7#X\T[8C]O<GM'.+&/,VX.O7BRD
+M#<?9R.Y)1YN8+@QU(F,>(/CA5VW3%NRWC6LT4)W9KE7O1CWFC8#="B%4/H1K
+MYS5BH47=%`QFFW7%*'%74:)8V]4**A,#1V)WR8S*70,N$ZSWWU]H*^.N61X>
+M;]A3]SJ3)/47MI350ZX=1L,W,ZVK_;H+VP4]I4[G>_^/NM$4#%A2IKK26YJ&
+M+`4">/+.':]Q>,\Z:WS#MN7+U;M.,6L\597"7NOI#/I3[GGP6#05F9Z@[1E*
+MD!KQH$D2\DH,-&WUH3X!JMK9X9J4,BIVBZ!5SY>W#,X*3X]G$L7-_M5K\IOH
+M?(HMP1"5G^,,.@KM"!]1=?ZJ&.G(6;*O&^02J$:AQ'IF<)]O!)A5>1DOH'?W
+M:")&[T$>B?`U"7/F12N.YF,G)(BG>+6`@OH[UH/KLY44IM'G,/@%(SO#C4.N
+M\Y"QYG*<_`BYE`V2H6K3F/IM'(H-QB+,7843T.T;PRE+6/L_$7[2D'G-*H3!
+M?Z=KRTDRLU8@79&`/VI+_O8BNZ[C)B&Q!9RU;(5,XC/'".\_%4Y0MG0X;N<;
+MPYF.A10G;KX6OQ&S7$0$VQMLC`0ZK/M4+TT'9/8^!RYOJ$S0S5YT"!><Y_[Y
+M:<+3N]/UCV<2U)XB&PJNZ"]UR0H]$GQTH?^;&^21F&,RN,HD5.).EYNICC+<
+M&NL!AXDG_C^];R91XDF2G$BVS/#JJ$3@U,=B<CNT-`R,N\RVVV8SA,KOIOP=
+M!YU>M))T98:YTFHAU=,\C6?/#,(!]RHH[-Q2M9ED9ZLR?H8):P3SP.6JOCQ*
+M#T*'N$6K!=`]-%\*CV,>E]9?T\1+#@;#5<=.D!T(PU"T?'6Z;%/"IF#Q<D"R
+M#I[XM()75.J`?1F&NV^CM$O-L)!WJZ#+R^4P0>[//R2]!$81YAAC?_7Q6:L:
+M$J_.%S^3W1_,\>4V81*12;%\0>@2/,*OV,9%O9+M26Q=_%.!(A!J3X<#D>RS
+M>.]F*A<%PY.8`PC+B!^%D[F'J-G>,DH1Y098!K^$;!7M\-=PAYJSA86CDO;;
+M-0M0[R(ZD@/D97$\4XD?--S]20*6FJ)@LOX@,#<R1/SB3>CFF+9NK$I&[GV3
+MC\,W01`E/VW+U,+R2AI)/B_1.[T4R+V&S"BA;9NPZ<@,H_QY0%*AYQR9QE@H
+M$*V<?=%$?(](/+B?M4Z_6MZZ]:-F*RA=Q09Y=/$YD)E&?RIG%<P3$,/B2]L_
+ML!0F*7]OV,YC7379>V52"=F)8=_^AD>G9DV-0V7CIX;18!@6`+6XWORG;BVC
+M3<@9B*!:]&>P2UX)6`E7<@9*6\IKD;%*V^45"7+LZ18G"VA*7_PM,FR2HJ'8
+MD,VO0!=?I47D,'D!(L",.J]K3R,$:B@TBO#]M#DGG;_\*/:YSA)`XOOD(Q(#
+M&88F^Q34CYKU:@0.:)1E9'R2_CY5"+;,.530_#LA+[*>-Z=+!WN?!>'9*7`R
+ME;F/F&>E,SLJY*6S0SOF(4*_Z<@S9^\53WQS7)#8LZQ/VWS>?ND*K.;E@SP=
+MN;1)Q"S_:0GW3V':"4R+A(]8:R[=T^4F)%B.%'QJ1D_W6S]-!T(K55+@&19P
+MN'Y4>QY;6"HO4^"%_9;;N8>9&!-5K"B1?^+K)LZ$8@UJPDE94HYL8MV_K@EK
+M>%9(G1D.'9@]-5#(G[*[?-"M/7X_MK/=WD5"`4PAKB58Z3EQ<9T/SV=4V<;<
+MQI-.0V$9`DG@Z8@@%"I'$\M>.\6&*Y=U/=[(CIZV#P:$P*9W[MTBV9%$TOY<
+M,OXP-(%E_S4O/DJR=)N$'!,KB^@=6557^`IEW&!'7\=7WMA!?G@M\^0]#5YA
+MM"1T-)_U<'1&1NWY[4&(K_$.'2?1B+;E%Z4>%`N(IV9Y*K7BVY_Z@8A6TW[*
+M7H5I\%]AZ1^X+>J/W/*`7:+%VMY#O>:.JSX&!\[2)G-T;0UWVB,@OH:E>^7O
+MBFX`:7]:7.=7Y]$8GR"83--'<4T*9'>4L-S2._0#B..[6V%AU2AT`FRD$=:"
+MM7<20UE+\^*:\?&7VY92(^=P+5L>&*'U,>(1M<&(%1N^2/<MN3E8N:J]E]TA
+MEJ1^GK&+#`_R2A*]<V;O]"4C,&:\83DS]:?MTU30+P"Q*2*P;ORX50&:4U[/
+M*40QDJ7Z*"U7@2&`'1_C_&81+"!71Y>,"22-[P95;=WQVA(>RR%B5[S.\9X:
+M6;;%R)N".K(B38[@5'=PT.-"%LGY&IG70OF(WAI%*^-"-$&Y=[-ZW^ZRAY2R
+MDO0-5>"H#/N_'W*ZG"\U;"(A'WZJIBT%K*G5'W8G<8C<F'I%R[*N,DXY@O:+
+M;9>#-GQ89^^CQS_4J#!'FHNB0"_;53Q(GAW]<`"IVB>IJ[/WB"J6LEE:=6+Q
+M*V,'4M%L:/HX.#$N&PDX->-H9<`3:48T_M58I^R#*O7F?($L/D`3)1L]85O4
+M@,WN\DEBT&L6[O&)CW^^2*'?[L1)?F_-I,!ROVMGTV%#,E=.-Z.P@CI3NV%^
+M?0P\:>P$RD8JR50/&7.YN1\P>U-6%$/\A9U6B&F3,MH/EH%FX$D1BCP\[.3&
+M#1ZU>D`NJ">_A\^AK=3/T:80WAE:O_=1<(/+F\K>RW>:5G>.D,P51</V5F_F
+MXB9JVKK#OPP*)P;3IBL]/1C%-X!K5W3%;ACG+FSXMH)OG-O2XTCFO171:SQ7
+M;_U#F=4=-G%?#G.-*5&`\:.3Q!@D:EQYZNQ$.,/OG"8**B?ZSAL0)AJ<-27)
+MT=9_LNNPMSJI3J7/T#]AYNFT48C9-HT0+)C2"N.?D_P(_Q9IZ'$^7HA"T)*]
+M_YH!Y1NA=CWN\U'V;.&I\(X>[K*E#!DE&_'VJP)]9%\501([L-:K!^KO%JDI
+M[I-COIMZ'D70A<IT,?HVGJ/A/2/HF7T-O?N9-[8W#X)03`$?^JURH65?:BO0
+M!_0\A#A'K&-T9+"C]5)?#)"DK6E$2M2=;-/>;VK(-*#^2]-QS"S_SNLBHPDQ
+MRH`$#.!D3?4-KB^5D4=J`'0^Z,TB:I^78ZW*+OW*BGL1CCRZ(5!J..&JD^Q<
+MXS!W17U&J7*]"E5C)`C@,7H($!1ESTG9R8L))>6_/[Z>G;BGMU@2$Q8=RD$_
+M'Y9/<Q:SWT8@$='^S;%@964-*PSYE5.-RO'YXJ2UD:Z=VC33F;MFBKZ*4=ZY
+M/B*4`,5E*<\'Y3X?C<[S-=1=@$#\P+LN=D?J?58C\,]Y`M(?NK5_YN*9/GN.
+M@?^Z=<PIP<(UP+6(+F3<KG<A[X2+F9$L?W81)"'V!9V,ZRI;@6O'_6!KD)!?
+M>K:IUA0$N:--)AR;T=3X0NK%_.25"O[D\J'/OWY&>$-F'87Z:,KU*/KN$]8Y
+MRAS_8BU'LY/:H4>VHQ"SG`U>;6QLVDMDBSFS#XLLO88\>BDDOXW/EC_'UF:6
+MZDD-P56Y5UG(O6[>!E3M_*98R[;ZDB<,+<_)G2ZR')LY`<[+]L4HX5$N`N-\
+MAM\#SM/LS48'&&J6U3=.2FJ:_AP<Q4IZ,-YE.P)@WQ^*FU?=I%:IXU.9;!/L
+M)!*_>:8:&_7G180)1.7-)*:5&9#;7%O^.H;XNSX*V&G+^PO=Y10^+'BUISY9
+MT[X78E%50C=4M?#Z-Y<ZN9&/=P_7L#SI.T2_=:=UP*3\T%`<`R>M>4?RO+7M
+MQM5?J^DOSCK1_B(OXF(9[!^WIX@VQ)`^>Y10ICYGAV:`N[R;]/D^<]:`@UP-
+M<A8^K9AU^E77,:<"L0J%[H.^L^0[%!+TYTE?[6N^3[FG7QJ*90;9JIJ6OQ[V
+MJ9ME`GQ*-BSN0MWYX9JSL<_R$X<"?@<TP6)NK!PGPQ)U[\[`&FG^_03>:DX]
+M!;KG36`94`H=JWD65.;^A[H#4*<ZBVGS&^W#E3)VJ0E!;RB7G_Z:J94J`;Q^
+M6+)1<B!=G'!DCQ$-%FB;0\*ZO37PQ#`C49F<82E6<'PHEKZX$[F^Y6S\(JM%
+M%[O21_T_ZQ)Q1IX_$)AXJ59JSA'SI@:ZE)],(^35-@`Z:?&]0_&D?PG>!R<"
+M&X<0?/H(L@OP@""%1)3:T.=8U?*(7[J1WZE6LT:CYF=!DX>1]<K@&=:I=T8B
+MK`MO*OMH(H8@YI9ROTOC8RY"4F))F6CT1/#V5K^:0T*I)F1<"W<>%J>OW8I"
+MGP)4'8INX*:E54P9S^3_UL0OZE73$ES$6G_FZMN'9QR2F!$;4Z[-Z@N,##S[
+M=J_^5/8F<R"8M-MVD!0B`@B'%+UG<$+"(!9_N]$3IXKWL`^*^!^=Q@M_8/D<
+M_/UL@,KX!4'9%*S/S.N.2XC:!6R1H$L!,"T?#4(U#`IEC5_?Z(#*K?X5NQN_
+M\5WE>C>!LD&`?R][OR*FRL:_H%"5C!&"M8+%/Z)YG-YM\2G]:P#2IUL-JGQ5
+M5>:>YCNP^!V1HZ.PX_(,;R;14(]-J0F:%4FVSPUDH'MVG[]7IK=:_^Q?ZLOY
+M4O\$H2.`<-2J'$PK&I?`*_^.>(`H[<117@S]'GZ`K^(WIM5I44/2BGTE/PI,
+MW31U?1HW6K14EG9W?IT>*419,5#-N\(H+:^13C6LVQ(L@]J&/EZ^85S:%_KW
+M$$_[8''Q\K?B5UV89Q37[MA/Y$A%:>""E^Q9U5325Q"IS4OJ27H)$1Z=G"H2
+MBY<H.M/H@5*U#^A&*ZO>@I[21%Q1.`/?945EB,<W`;?_4V_`,GQ8/G0!X"+[
+M-D@](0'4%6Z<\>/5=;UC2>>&>B`>`D^>RRK`@[FP/9SP?_O!][3EB%[Y$9UK
+M3>@VOH:[5]#M?38L>$Y2CFN/ES/,7["%,9M-&R7R5$@`XYXUE$L(1>2,&@YY
+M2(9?Q+N^76(BOGZ@;G[C=]I7HZ)AD+CLR25G8J;9@FJDRJ(#9VA^37F1.+_@
+M8`(;<$GG#YM34V+?=%N>SL?-;416UOU]XRP^<87/7QS+XX&1I..=D*=<Y&!^
+M_1!A9#=XN<]?_[C)-#).W5VDA1P\]?5/Q!YE"G?,OE:?W9Y<J31=*L4IMM]2
+ME!9V-<MI7!NO6)PL7V1\IN\"YM]_89YAY^";281(V<@V<;L.-?WJTKS)J9"(
+M33,*LD<\X9.UC:Q3;?[%17T_.EI/K%DP1Z\!00P]*Z)_=NAAS1S!D(A4QK*K
+M:W;"4P']NSB]G5L84FW:Y?:Q($^*V'A+&Z=HJNOY/Y&[913`O0*FL;U*C-/$
+M@52+,:H&M7#.N%DWQ;YVU:_YW.0"D*A,`1+XWB/$`RGB(8>@WAVZ/"3C@?;W
+M:/$Q#-.%>Y6`0_J.R;V]-P/>&?^<SI8UH2X:#K!0[CGG"/_<[^BTC?"8E.(`
+M8F<U^#B,#A&FM.[<:+3+GS7)\3Y9"/KVZ(&/_4R8QW7L\RMO>PF:R[&H?&H`
+M*`C[G`)AN2;NIVP[J$5#/3<<!CVY^M;NFTG[RD9=(&Z"(8K;\5)>4+D:KPVF
+M<73KSWAXZ!*TU,CLI^46,C*P-S%ZV'H6JCM1'-?-DU,GEAQM797_I%$J]D#B
+MI"W(.,^L5"H6C-CMLQ*JZB\-6S'>R%E]`%=)/$\OI4M*<%I87?CS=K&4O8N$
+MJA."LPUD+0VG;>\]!*Y_U:6;@P`)Q#B5)=.J^:?HV_);X*5R#1J%O.2.\<)!
+MTD0@4D43IT[,&&&O.TGJG-RCTAG174KP/$JZ;WOOS/48^I,$NMZ(%`@V]V`3
+MPAY4OS(0/L]1B/67786TXR:CO!L'O_M7UST'W:C>U3FOTKQQI[0FWU]$Z+Z#
+MBN(1>D-V6MZFZ_A-:4VD$OK>QU\/\94B`^,A/2-CE2(WZ[DFAYC>9%8%M=[?
+MF&0]5(OA_P:?G)`EIS8@&7OE<[W,1DJSV;LJBGG7.+'(JH4^YP==+D_+&/,?
+MJ*;\*[J)AT9M-5,\1E7AZE'XN0?/=)UR`('0'P+.:/IEYUA\UB&78T#V!XIG
+M''8/J^EQ22E56A>*7JO4KBWEQD8-B8/W7$$#QP,Y:FZW:MK`-Y/G0K`=/"=M
+M<3X!Z!U"UNIA!XK;)5BH24*5IP8T\P2+]XD6*9?R^5""*?I'U.F-XNUE/Y+Y
+M(=<AXK7COA`J3.!?TWRH-<];KEE*]V+GM.+>%XGTSAD,-22NX2'M,;[[O#,Z
+M2ANUP%M#H+\Y4S#26N^09,"BT\<@'+JU"\4@#.2I?&4'I-@-N"TVDE=;GAJ^
+MMQ?''*K^-@10VEHJ^:LJUT^E2MZAE/T5_@/")KVX[KHZ6;>%"85WA+XG&L,4
+M#N$7"2>QN;GM%X]M+<YD.VR)D2+XS1R1WE[II?G6)Z$I=YA\$%03!K*^("!0
+M_?`YGL\%_`P*;'/QD)6B\3CJF(1$)+@3*XQ(YOV]>H`F.,S3(8QF1B"<V!P$
+MB]%;(N-(RM7"NH^E&<3>TR0Q.XPF-*=?O[#!_N97%$/K-"EZE\KU"\ZL,Q1R
+M0;(LW'YN:.&DLTR4&<!YH0W"-X2>^L4/5>E-]Y]@PDM93)L1-3HO%UPD&<2B
+M,&<7I6`%[:]/7%87M74T<<$W+J=T5+%SUP&"/KI_6FPB+[5ST5#]S:5,CX#:
+ME"4P2H4X[-8>[,0;T3AI8WJ%E;;A.%527A608M]Y;MWD;Y>-:7.]@&CX3)!L
+MTH(M]#-\2PSU'T'1(OHL@8?P&6MX](#PFT\YOO&(*`6#R[UUD`P=1.]I<:3C
+MQ#R@'G`&WQ9;O=TO'(]G5C3K;`T[<DA)##NWXB,"=,E#_*!CK$%`\XM#H(7W
+M[H*Z&[GBQYBH:#!WH-03L:1Q9-F^].6;!3";A!H9<U=30]8"KP8<[/=3G9R3
+MX,ZH4WPWL#N:U(L&'7#K^8,AF#4S=JN-CXM"[_XVU:V>B\[I*KND&7V(RKIS
+M3..2Y8)(-0,43)M79C*EQ.]/;\J,XQ5[/'W-=H+*Z&*_OCJ_1^5&`3U2Y&A0
+M4F"%,ZFI%;"6]_YT[ZS76H^1P9[&+7E^E\=ON!GA?0,9<PEX8CL-W8N0:U[/
+M:$E<SB57(<Q=_Z@F)\"%[DV+`P;OO>I@H0%5$%F_[R+NRC_9/86X6V.FQK;C
+MZF:W)QAM@N/#10*69+<=KM&TK#;6GK5JS^G6<C<.$;I`[W;,FJY)1T2C\Q<O
+M%HA\QR9")PKB9O`SC%$WB.X-30V,;T*,N6"N-_);HCI4_II/@/?<UIK3`.0/
+MZ'$D]5<QE5X\MZ$`6=9<,FPR8HH2_LL-)I)&Q/ETA@V]?<R7L[@^&/TS,[&K
+MI$6LX]Q71N\>>`T9'LR\UCWO.N##D4(8P=KRV2Q2]JX]\,"M"U'YFWP0%AV5
+M)`5?>RHO]UU_Y[NYH5ZOYPUHR7J2]K4X"^U6_4R/+^_U'J$[0,:OWY&R8Z3/
+M`!K_X<8#`A`/2ZLK1I*_,7/K`WY9FE7]:;Z+`P)WH^#)^FNLN7I$DR9WGS2W
+MFMA\#TJ0A,(Q1S5.,HH&24/&U1)=505B/K&4]XMMC7@\X0(AY/$'W'+QPQ[B
+M+H9-V:8+'S$GL^_;SL`GW.+GU.A(D]GRO\HE^)/9RP?>\'W2OZW!->_A;/\K
+MU:T<#+>#XSUR.D(L#(-CMF(GO";DEQ98.\R7OLU+Y[KXBX7!>$92U-<HQZ.T
+M/(5=GF9_OP_(E<[G;?2`8P3XC![XU#0?=-99M]+R(XCU08'MRRN$(12(3?D*
+M!X(M_ZN7\($K(*[].IK8_U&)P%%KN;O4M&X+F>>&^5O>,M$GOAEH5]@Y\-U0
+M\]GBOA`&H4,G-J[.GKDZ[ZC4Z=G+"7T)L!J[?A>`:#X)W1U^6N"LOF;.8I=&
+MLH+BLQP:4L`B00W6M+]K)0/;22*EO&64IKDRL[X]=2E@.N3I4T67^/"UO+W8
+M#Q77K(18U]GC0M.MM4$)."(%]R@FZ.^D9Y'':-+MR8=MLOZCK.,/YLU7<7YC
+M/`R>9$NCGL!R!&[WQR.5Y7_,8TS"^-CH6QNL%&*_L=,03,.G&_DK(&1H'L6;
+M?.&>I,#Q.I*O*G61<SD(P.]*<:>7.=O"OJ`JL(];`)3D[3#H=5/M9/$@#:(\
+MY+=3QS4(%J)$/;R.\-8F@Y]DV65^;7MW#Q8IMHN"[;KI:Y+<$[=>!H>1;N`B
+M$'K@X;NU^PNJUM-"(M,XD59P,.=4>&0S90ZBHKPHF-VNUNDTT$#'34*=<3#8
+MH3\YV_Q,SS2[AV)=L;+E693>TZIO^IZG1"4:-ZEO-,M8$8"PH9(27Z$$]_TN
+M*"7)]AO?2H.K*V(R)\C#3A`P";,=5@3_GK,#H,CQ_1#5C@G-G[NKQC2#WX6=
+MNMB&QA@.+>CK?FVU!G*.9SUH".<EF:-Z2\B8]H*_G?0`SK^RK`H^<F"W(&K!
+M*[-M#%1^0H&$H].$ZUN/(KJFZ-[!5QIUE*?7+/)?(JZ;YN]XE,O_+M*<G:VL
+M,X7GH1'0!J)KT;%_Z#9[<[\'"MUG-YIO?;CT^*D$R5-NNT@/C')L;NM*_T]_
+MJGJH(58$KL1EP4D'`!ENQ-&&*1_@ZR:6@EUPB=3[T]NW,4LI^-1FHU><+MJ+
+M!2D:)7,*!C]#O9*:A%O#QJR&=RGC^(5\3GY=C6`YAJQ-X*'*6?'_5=,_WGLL
+M3>M\*%8D%]<-)N?!<^<T=F01L.,`9!8J-W*^].S0]GJDF3$6[-91JQ"A&X;H
+M??GQ/NG"<_LX!D[R7V^M^T9S;OW+BKBRVNG5'51YK61/YG#\T3N3UQZ]TG,%
+M+8$YS.Z\@;3^"MBA*MW3;E`?I/BP+LM9$O2O!EYCQBE*H%&+6/R8^9LF9!;)
+MZ[)GBKM-0VPTUVY9G9".H+N-%$5E.X"XK*)(XTA]",P<6[[OT@!/1%10B(4%
+MW?*#@#\620KAB9R!PB-5=+:*?9_PY1O(5ZI.]ETO.<6&S<UY"/.7#HFR%+W:
+ME^X-\^QE:W>"#GB9U+8'<,H<B69-N<-"\/>Y*GT3U')@4`H]\(GD'3QS!K6X
+M-*H[$-".5'*>]P;1)3T/*DOPPEVS=.Y7&"_JRK_(@4%Q]<W5%VNF<&0Y]:R:
+M'8!PGC$/"L5Q09G)WA:`)2&GG3#QXW.9M$`$(@SOVTT@MY[JE0F`".:%Z8#"
+M^O>D99QF$,8NA,):#D'9!P>6N)PL4%$DK/``"@`,&<LL,[AI)BB.U!!O:A44
+M)VO6D6H.-\;+LMW1[6$.IJY@(T(_9J'[^21`_\#>1!]:P$1Y-J!?K-G?&NYC
+MD?GEPYJ#.A-"VS]VU"3)P2KJ'T4K<[`0*Z_AV*TK+UC[*7KN.U>%*I^OZ1.?
+MY1LK6,3JZ82H[8*G,8W5W/-3'1+8`%91>W3_H:];:G',D&;.+6U$"8Z7G_N%
+M)T*A9PT@S2/B.KOMUTD">+'FKX=<?>BT!$^;P9M3J%;CVG+[C3-YJ74,=R*U
+M3RD2;7SJX$+Y`L8S)W]#*%'^SQZ7;UFU[(ISN2B-2/)"D;V8<<:&<#_I!<PJ
+M+KB%-E_X#MZ<]R'-M_[W98J=V6"2WP3/:[P7\?Y<Y?C-?+U,+)<1.TB%BB3\
+ML4$,WF/:4P!JXBE:(S-]QB7@#QG;#L;/86S[*Z8BIK'__P'U($?=H.`,G3+?
+MZ9!6_B"-!B`JX+#RV,B'KE][`^P0-URH<G!'UJ/5,&W[V,63;/4J!>:>(%*@
+MWZ=T6F6?=1%\QZ16"-,.[=*D\D=%TAER<%'8"]#V>/P[:(X+%TK15,/"@''.
+MLRS,(9>6'2*/R[BEV,ELSZ"S^SJ6C`T+@@8V3N5&>7AJF^ZI'^)M9)I$NX:Q
+MEMUGSUB0&*ZY'J<4'[.(51F:DF$P3)Y<)+DUA@;05M9K"^C9\RU-*#4P+LY%
+MQYSY9M=5<$%3(`-G=*`.:_G^<#AA!Q$2&7TJ]A;MBC&2_M:#$#(S6+?+3GK9
+MP\<1/ZGXE>B!K:+@AETBQVH]@X^=]4]164@EPM,46#L)&1=1LE6Y%X$XQ*U9
+MK(X'<@K!ED'#=93HJA<%CTLVM29`8S=U\RCFRPU'!$`FAX^?@YUQ^-34/E)V
+M?FUT[WMFY$P:XB9JMXWK/1#6<9#F9U9@">@[2*\4Z7#(8N_Q\HL*O8!H[B9]
+MYS97;)QSEJR2Z,R)T\#9N=(?5^1IY*4)G.N8C4;;'%`D8#&@3'C>6&R-;LRN
+M.!6]SDCZWAEO%MR]0<<C?TX1+T+L%70-ZD);_$PH*XK0M]O8I6QM07<[`)!Q
+M[I->4`#$@>7?L3UN<U3S=V^1=\$ONO3JF$.]@8'PL!F#]C'6\E!R:858(?R<
+M'-<K>95&.Z8$K?UMG.V3"(JWZ*`A,V=NG>O9&<KDN&@WV@IN9#KWHVE^WX;&
+M3RX<:!B2*HZSFYR">ANB2/C%%=O?[NU01`>QFEE9)YD:SCC,M%B;%[W/LEWC
+MX4?8\&_ZMQPQH<B]UH1+O<",@;;4@P_FQ02G9,\BNOM$_?YR"7@U2;I<$&$G
+MS5D4KG;5\6^^Y<H/L?^2N[@5KN<+6YV*P2-7OX4`K=U:%1`JE>"N$<Z2228C
+M).;;F:9/1M94D"8T"\_@R?I05B<I#_=TMJ(0Y$R'J5*Y(%/G6`YBK1'M^ZQ"
+MSH!=YL:F35ZBZWAA;$)7+W3>K@0FO[CFS_17G`_GS)+R0$?\O6:6^I%C&$U`
+MX)Q&KF+]&U5U9:^[&E/!C@4.P=R'HR]Q9@-TWYLMP6%MZ]PW0'=NA@P!K,J8
+M_IO^]LEK8]\8K:O1(*$7FCBC>+VT.;7,8.#>>'^RJEJ=X_D@=;JB9?BB>4-F
+MW>8'A=9J4C.0#XJX<'H+$S:_B/M;)E/-,FXG8$A62@_<TVQT^L3%_'G'!P8J
+M!JSV_KA%,S')9R5#5W^4L18FO17;+J,4/.NRM&*9*05"M+'RZVUA;,M&5XT[
+MM_;9D9KG<:L@-ZU_.")UTOS=SA/4,^L[)*]2BDV5EFD;.=WFAK#\1*2-ZO/)
+M:XSD76FH%(4GH,P(0A]"?Q4B^8)HFZ4PQK5L\2&YQ8ZE4RJG_&GQO>;5#1%D
+M)<AOSP"O0V@S48A5D+@:OH<?CBND9G<-:M)NS>20<ASP,:;EU9_0VG*'O5Q@
+M"G#!(SE^6+Z5I)E%WK??#%5N:X<(%//Y[4\4SWXJ%&C.J5AA134_6G8K)2^^
+M@R$DU8;_:'8WF#9#'I%V^HY6L;14`[XRT[!P<V,2)S!7[X>N4E"MA]`[?/OU
+M[-3ZV,FRL,`_&\X&Z3C0X58LUQ7_%PO,(Z-&VZR'5X'[(@R7.'J00"8MAMUM
+M'CO;M7CUSZ.'<RFTZ_,-O<.Y9ET5D!,![=C*.1-,DXB60[%<=<V1[O%U&(L+
+M_P2_.X4/.P2#/SC$BF.&B!DN!0$G`'_TWM\5YBA'PC\AM;OMGM->J>KU+*("
+MT]^D0EN]\YK>=2%$QN5W\@``MW2];(,+,Q_HJ(`Q`P;@'"E$QODV>/2<01)F
+M6T_=7^1^E:TA'#B\W`+7<:=0HL]AI#2O::_[FH$6CUC7X3H/"W_I[T8R7\06
+MB?=N!#"4DK-'?ONGUU^OP*45=G^08KQ;Y]ULV6Y%==X$3:E=@LUK3N8LL18@
+M7E`0][#62YIFA$BH>X0^A9-RMB#$$D8T]/OWM;JDS+`IOQI==05";!XL[_CK
+MVA$0T5V!E4Y1\Y1!6WEAG@?$C`FJ>8)A$^J&Z/:L\^'AI11EU^\2F,Z+&`\7
+M+#DB+.*;]Q>33!]+*9TY?61P;]$8:G-DZ__,]#FI76>HGD,R"#5/#=NEU&FL
+M7OYWRBR$TP-;(Q3EA&V"2UPD*2775Z%(]1X%"2BY4>Y"*='P)[WO#*P&<,#W
+M]X-T77R*DB80/#"`BE(PIY$!4;1U&S]%@2WWV>2MFE5W'@&YU\F,0TU!YM_@
+ME3J[Y@,1[*A=;4B&@8H7ELH5]LT;.+V6)V@6=B7CHA4Y#1"6`>M_P,C<SSA;
+MS9HBIQ?NJ!GO7KD)["%98C/A]SP9S*BVGO%*K!WTJ*6)L2\,\-QW$"],X/Q]
+M",]+%\;/<7++.O2.5^!,8=D];Q=`(C0TCM\A^P%N2E3PL1-=9M1`C#U63Y7C
+ME)7AKDR>$#TQIA-.<17]ZZZM874%&EZX"JA**6Y]1F%-T^TVXQ4@W1I8(*ZW
+MVK#!CICY-ABSGHQ?72M%+/!`0N3J-5<;XGN\@Z1`1$IP1F>S<SF[`9+%RI,'
+M'4`C(FF8\\XQ!\\7:3)K?K?X!I7]R@(@QZNTDX2Q-0[2ICNM05K.L-$*"M40
+M,#)!BBD]N\`K#`7;1G!Z(/01"&KR6Z5)H;<3I6JNC5\GI92)&2@]-BO8*05$
+MT&>_D0W63>NO\#>21>J<8JC+,N_,&[*7I,K[\;\=[B#"4]3O5J<;YY8K1HA/
+ML_8!JEH'@U)+:/Z!TFRGH[J44U&%M!EIH$5ZA$L\L:``@27'?:UK89-EZ>,O
+M0KHG*O(UXIR-ZOZ0'Y38+FTW425PL+%>!R[1VS_F4F$%0K_O'8UGDZ:D[V'[
+M\UTV10?8R\6M?;^P7Q(75RZ=XA?E7$_/.$^:D:,S)7W95(C[=R-R[4EF+!5E
+M,HUVA//CS:&VUW/8\9-"H\04LD7M)^2_>#&!TB$Q@0CE'`1[4N=-EY%VYKCK
+M2Y3*&8E_"'FV#8"8V]WVLKA#C?<"&B<%5PH#*;#CUS*QZS)%!LA"=%OE)C.G
+M^Y`DM)!NN`+6(7U#14RK1#1C96*;MW3BD2X@S,VY?+D+_VK)_RSY\"TG/LLD
+MT*&[,[!6&;?:B$*H'>@`@+Q3DR$)Z/*$2V?_;V[G\:(I6AE#>82U2O=)*TP-
+MSJY>+0_FI(A3G/6(K5CO*R1(G<;(J0(.'\K-[';]/N('6$<+2J=X2U^X!_D`
+MH(??L?VPDD$W$`O?[5(OCH4D%SJ5D+6$([B#/A][4.P^Q!CG8G$82I,T_`_?
+MIH"V7L'(/I2(S#!WTHL_?VIAC#'BH<EJD<Q/O=9:.L"@\U?)X;G(/&$]>%Q7
+M\.O6Q"2Y>#O!5(;;#TH=^9/\D)0%KRNP3?E_U&`Z.K-CG>!]6KK8PY=8A7AF
+M\%XQ+MXIT)7R:8!!/@)DG>#<4EC-46$_=7`J<C_RIAXGF<+>OZCZ20+)Y.*'
+MM%O"=KL`!T-1H0\&*F)`KEVK2_:,([/$>XT+M9]P3OGQ`O2J6AI==8Y8\$_;
+MD40D+'1$IPD$\BRY-O49>S^S&BC0_1&.Z:5KC?F[C3;A"X^J7-V$;=#45O94
+M&/O+/9'?H]*B^.X7)?^BW+XA34\[#B^)S,&MFI:/T?:7^+\'_)<>\,;/+NR%
+MD@0ZV38LVLC>+,-K0[GQ@D$]P?VIJ+R_QRE'QPV7R>R\D4>O3IF(,EHS+O/V
+M3O!I((?&D=1XSS`@R!%7V`[!AQ=<L_D>\LRW0;K')=$`G,T9!V88GJZ/+%-<
+M$*SY%T4'3*-1)/FO$4JUDJ,=I#'Q>_L5<1G8>@REOP`],+G,_%69_@]*N5*X
+M:O4H^.MDXVWOS/U`^ZQHPD37!G)UU`AE:5,5E??'GY@;G[?CK^')\A9Z^-CX
+M"&(E#P',R%"_:^^&8]1+Z$><VC%@UY4/P;I#*B^[GLZ<ER](5)V,;J/1L5K^
+MT0OJV`PEX.D4N*U"IS+F"<",_IW!F8/>4(%I[`8C\\@XVW7BIR&EI,`9%FV6
+M;&ZQ7!@$J0P7%5XZX#Y?\T!%]G]TN==$LIRZQGE*:Y]V%B?^$W21@Q(YP'%\
+M6X)U!R\@!YRTHO_/#6+$ZNNR4(;=4W5V!>@"%T-#F?]LH*RG<8M2(2SE>LJT
+MJN>;AAHK47VWF;XJ.,#T$Q=_9N9?3LJ_FYN,DG-O*P/1ASF@VQG5,Z#<VKP`
+MM47K[+VWR!>P*"*$99*"6PBIFW]95HFR]*T6FZ38DW&@GLBZ,;S3+AA1$P6?
+M0RYF@+:HZ1VD],KKR;8PQPQ)A;LMURJ7Z'U;\*O\ZW%*W!>&DFABOP-?R*]6
+M\Q'%00RMAC;\W6Z+!M)N0HPHB%&6I9)AV>/(<9.KD56UZ6Y/KU_31W1-%0H]
+M\Q2F=*:%Z4%*<(;S:CJF8NPHB'*!:&HG+*N:3Z`\)J6_L/'GW8;;NWX%.5K[
+M)1CIUMK=H!R3P)DSM^%!.%4<Q568OT<!1XNV6I;5G.O):KHR1@C]^P8K@E=^
+M(4BFHCI(WJ0C9'+=XN7V9"QUQ.E?J$?.<'_V\LS/"Q!'V_2%_L&-+L8$W5MZ
+MAQC89F?3J/#)6G.L?6S"[/R`$45M,L`D]80+Q@IBBNH^%6%Q)T!AC)/_[YG*
+MP,.<HA9TO=!5E8(WU-#O/?:;^%_F4C]0[/WI?0Q<X^V9\:W.6HT*7FU-B$5E
+MI61$R`]4EM@$$.4I9Q*8W34$9?H)*>"1>=VS@SST/**G[-R<2F_I<Z"2ZV"7
+M&T9WE!.N`6,8O3@-9TQ-^],'/Z.M#7/F`8NK?%C^0P\CM.[?=PE-;$!A7[)T
+M6Z2Y^?0#_Y<9^<>/E`#$E`5TD!T;&1`^I+,I=WP$Z*0>WD?Z!2G*7L1PSM?\
+M**NC#SIL6B<4UV%_RFZX>7),GS8B%:X$00%#39?#X6G8Y%^/U`J<==8XBG1X
+MNF:&/,)7B*;MQO=\!CMJF4F487=$&)6#^RCQI9<[K<I+$3/%7SI><C=/?A<9
+M>:H=ER7#Z?HTQRL;(*A3O24<KH6M5ODE-)9$GC@%*^YE5V.QR0V^1=F9:>O<
+M<#OH.)`D:#V26C#A^._9BS;H'[?#964+RO043[$EWBR01VOFK>Q4ZJPH..5D
+MDCH\W?I:'V+I9KIPL4Y>LC@HT(R^GE]?MH6T)0Q[.!^Z&441Q10;HI6P!Y@W
+M+/'(L.:.`HS4[L-V`PI1V/`I+TXAU]5D7_&+^R+?RY31:%M^&=4.Y#9"G>PQ
+MS*H?50QJ',(1-'H[.*JN)/;G-4:6@6Z_(6Q#Q)7>A6``=#U!?,8`#L;/1MS-
+M!EO0M4<X!+:/ZCD2H0GQO#PXS+0]?N7N?P*I&FPZU&"]@.!4.(Q[D>SP7%./
+MU`IHW.,=<_4>><^4/Q+K"=G\'[MRD,MRS^V4`#/BS])8SK+#*%/@3$0S\+5'
+M9Z&UF](VO!*\L$S]!PMX3TCSY%L$W3;\1QU\P$MI3/!>XV.`QH2OV;?O255*
+MZ6;YPPMA<S`W%S4<=3F8N3P\@BC#M(P#Y?W-O1MCVA(G0I8*8?#67B4M1$[9
+MGE+3M99:]:1LW4"'F$Q)OVOI:\&:#GJ+/0OP?96=D1X/<G\/;]("9RD9A4DR
+M7]GZ+_9$U==:R=IU/Q(IK)=-(^U)>^D:7I`ESE)BN>6B=K>/KA!?;Z0#.&%.
+M_]F.N/KYJ6P!!DRSK.:T!)8WP%'.TO9&)1LD";IG`KJ(!D^L=XX\6>Y[<W\]
+M#HSO'0E!S,S=#V>EXI][C:1@AN?8W$1Z>5QY`0)0:6-Y5S&P9"F8H?C;:L\B
+MRQ4@PVVCZ[U^!?#R=Q.\IG>0X>+K&OY?G2Y0H=#P9V9^!V=Q-8-\,`289CCO
+M*)EX1'3=.=98&MRS,O&(JCR^^J1[9;`Z%KO\>OSQ^W2YB+/8,;C:#4EI09U9
+MY]#\Z_+/&%_1HZ&!L-%Y<8IPE%[`X9^9A(Q!*S'3N]ZV[&&,*QL7"EKFY6Q[
+M8I@F;C0CJSC_7$*;2\4![)$]JPB9DG;X<&`\\][W_@I5VC+&O)Z/G00U*&?N
+M("[)/C/>R*/+3E$_*9`Y<Y)U]5SI?@P%#_VRM2*EBG&"\K"K,IH-RET5SZ%U
+MH/&(3N(]MB$8W>DZQ>NRHN-HF%HS:8Z<]W(VPTR=BZM(6*^CPRSM(B*4E&*3
+M5Y6UQ3KKW2)Q-PMW+Q2,'XC;Y):T,#1,M<AP<)/-7:WL14/4UN*5?O(5Y.RT
+M;MCM#4E=-/GVW@/,B^"JU.,;<!)"N#M_D@V?!)C#[E:UNIU)2>'^\B.#$I@3
+M_RJKJ0E%"+3*6_.X$:UT&MX5U`J-B<;X6U-[36T'&!.SGKF@$[;P1KW0I/H$
+MPKXRX;L/SS?1$OE3HV!O!$>S+.*CT;NVS`RLXS9_\O>_`RC5IR``<)U$BPG7
+MNJ-C!%%[`^[6W(,8J/*7A\*75G$EEI$E,6-ZZD/SNW<\5!+/K66^*1U7G%S<
+MYB'T61&&ZKZ3%WO:]Q0(SG..[RE3<$2"CE457HR84UEKPSA8&(\MHW29CT75
+M>;SPA!9FLWD2A2B*JT,W((8XS\]1,V3BG5L@YXK!JS<6Y(,$NVL332ZXQ*SE
+M@/&[A^)1>T(J\E^!E_U?S8-QU8K5BF2.`YO$G18R(96;([T;0>SNM4R.UF*2
+M2(LKS#V8Y=B`XNY-:2<G4C)`,%>'J7H`'N4ZWI%),3[IT/*8Q"9+VQS2(Z5Q
+MX0MZ](%N)9#"_O"WI(J_8J23'MS1\.UNJ)0:^*OA6>,:%YC^,&"_!/B&%/Z^
+M6$,+#(2J"IZ@4RBM6$!TP*K$-]6[XSUL5VQ>)S>U++XWN.$1PMEZ0<*L:FB5
+MNX,N`4S'DF>`V*HM*/SK+PO<[<*4)JA$Z#<>D+G#%X(8-0O;].LE/\&@L.NO
+MOSS)*LM/F#UHYEFEP[XT;JZ;H'6OQX]IUXN]Q2'@-^R0N`K'$92!9<W&)G%U
+M++`=]0B@P3O37)L4<9,9DQ,F1(0^2;S'@6B@WIJ$.6C*\@EAJ_JE#2#=(S97
+MDXR`9#+<9\X(363=_OS9Z"=^J_NP8(RRKDI3!W_Q40FW`WLM\=]`/I'QW-*5
+M!R3!%%/X3]R29KT*3_64AFE)G&AM'U+,M])W;!>X$FUYR)KKJ^*$B%"3+YQU
+MB\"^.":MIN*%RO-G6M9YN.DWU8_U(BU+$O#I2MR%4E37.&?$%N^$/#'<NNV#
+M2YQUNN68N.W8;C=8QMN5P]2YT^PE0ZF[*(C":&<'D]G7?(@'M-O1C)U_JAF/
+M:(L17VD!.XO&4*%`V-ZETNS[^-^QTY0S[#\QO6O^&`._3N1&NIUXVW/S'18(
+M)YF/J8U:+.\('B]9AM6R@%`HUDZ-^\"QEQ*N./0T!Y2*I$)2&!1^BSR/U"!R
+M38QE)A0;@(VF#</3+;.)%N<B4QX]5BT9H;U8X+LJK@9V([[4=.S+6SM3KO1W
+M`>5"@3GZKW$*TON"$7KBBO:AW+K%VARVX7P_Z2.DSEJSO[7X=>MKK-:J%[$;
+M1,8$GM;NL+Y^GM&%K_H^'BNVXHS97OSHEZ;UVVAX,"J3F1GY`P*/T5$KK1%@
+M+]U8_/,[X3TO<2N2'8\S*%+^&S,',*@F[K([GQT16^!C?8C-WT`>V[K-$D)/
+M8OCAAJH\=1=5A2K$Q/Q$XS6H43#&EH$`?`M^\O;4CU"WIVV/GNMYNH<M*VMD
+MO$GBGY+YL8*NVO3P.?@6D6-TRYL_W#7K@UR-Q]<U@^-%((M**19,WX6MI(EW
+M?U*@VY=:%OUK*H"JDGQI/YO;]"\^KH$2XFS+4FXPL[$S,*3%H\8,QMZV2*F-
+M76\S520ZJPUAUYP32(B-5N#<LPD8P@-##>PL'6,J>$MEC/_!XC'N^R3)'>A)
+M:+#J!877OWH8%LM67^]X^RG6!@.4M(-?'85R43'V>ONR9O!=A(=C;>T5FUB4
+M?"`?[/H3ZV*[@WN?*E\;V/54,3>W#4=\G&0@4&@N(Z'DYHSK*0MSRJMV-DO5
+M)3I`%T0<&G<2G[,EMXE"HP?GPR!5QPW*R9Z*M[!;#CE4/Z?8;Q!A[#"\2;!S
+M$\QO&1\:NE!_ZB,C-1S$5?<.,K)ENXN2/)]>X[QJ6G`,CUVYZ_FX1`/(&A7_
+MMVK^'&SN`YYZL@)/H`FY]Q2<MPNFJ=+ZR8!$VBT\IN[,2HA$>NQNP!BF'HPK
+M7J<IT#Q91V_H9R4R4:NYVDTP(>1+)AQ#DSLR.2VXR+D=^R5JCW[FW:,*='[4
+M5<_)D@$EEG+=TX'97"N06;!&6XO3_SL4!BJ@;C<5DN85:S@WM!?O,BJY#/)W
+MW"Z$)C#<SIM.NXQU^%_#9L[PIK/G@3DBC"$!L],I^Y_MS\SB-#:/59RF!C0C
+M:[4YXEB(]^J@C__4`H&UQ`?YN.)L!!@PY@E;GYDV^'X_]1P'@!(\$7D3IL[>
+M^<KM9T<AC0F9D\)GL$=.`6,PI0V>E=R06ZI/;GS6?E_6PQ*XCP/]'L8]OJSI
+MB*3A(E7#(&%KV^-"OBV:NN5#<^0*LX8GRF@@R:V^>[<6ANRR)LQJ`4,4]FI]
+M&)+"8:@;+ZQJN+W7!=T\F.MD[GH%A@W]=\"`SC].(S#86@1)'RFI_+[D/#X`
+M#[,`-B=:IV:>X+59^Z3;:GQC-J7=N?_;&P@_3X+$['JLSCP?/T,`5R<4X+^!
+MSX(C@$B%M\=$.OMQQHGDPPJ<MIG$0D\4BL7P1[$(X*?[2WQK/[_;R9+0!V,E
+M=FQ75<I](B&$I-97(N/QW]1L=9D9?A,-ZW^>LH^W[B/@-PB)*)Z!Z;]3O+GB
+MZS;8NM)13N[=_NB/3[V*"MD)QYD:WV`XII'<]`YN6/6T6F"%D]Q%.J54?:Z#
+MVYIM#%6%D7)G-C,LG2H>XZ!W_[^D#MV6W7_3LA8P3T4YB64':;ZT"/+E.^,@
+M<^TW;^UIXJ;(?&ET"4H'+Y/7,0$`/RL!D/"X4DU;3/A3+M-ZLOQJ,7'RGZO2
+MSB5.<MZ]]D+B4:D%R#N(M6O*]>3>9IZ7]BE!`T+ZR<W1U+;HH5^/H[,7TVZ=
+M$GW)QV:E^TO-2Y(P*+9U#%Q:')/W)V[$XMW^V#"OI2-H1J%:4,Y2K9_2;=\3
+M7KI=(^?#O$P>L[74W>,PI`$UW=IT0)EKTD55D9J)AUE7V,NOSHVR+R0(M;UE
+MM8LRCG%!;<U_^"3J:)#3#&AT&;FVPNT%:%)`O%*[Z*Y;OM28ILNW1:5E[N]=
+M3FM5^J6A^)^L#;^'Y#'R(U$JCS<4VGH5NGQ#[`C(7,128F9"VH=LB7?QN;'T
+MWBR[+I\@J>K6U@)!EEIDM![.C6]^>IDVKC700S^P(&JP+`H-*[;QI%,W?J-F
+MG1M'/@H<U9>;"98[<1D:QJPB/,<R_2_-%R^0(`+[(D?_JC8./;*BNK$CRAZP
+M`IKR#(_P24"-HR:UX4MXS6V[!>'[CQ+9%UHW4^K%GP551%Q%JECG2)11JX-N
+M58I*]L94+1>Y@(A$E/.QD&T1/J6>^FNR0\;08+L?C#%M)#T->9M=^;CQ@E88
+M07T=HEF=TI.ZUFK#V*'>%4Q?X:V,HH2ZZJ?\CXLHN^><A0%>G]X1IE--@YSV
+M1G[[M(R^M'F+C$BGY`/<`58(M`A`+0F=@SV]L4Q&`,S51U&_D,/*2A_41>OV
+MW)I`4;E2>^#8^UL[QB5VKQI%,%?L<)Q'&S(WGGP<U%G34(E;;='Z';PMR[69
+M<LR[]*<-;AQH@7&?.(T4--%?RG\OG25^XM'\1JQ!+#Q^L&.V^3&)$]O@HFC-
+M":T5*E5!'#:`86QR+U-17!MQ%./FN8W=/54<W:./G&\C+ZM#N3XO9%'0Y.WV
+MC@,_V0(GQ`KL35!$E5D<N"97(H664;T5>:_%4:ZR;8R2A<%#D;S]4A,'ON7>
+ML\T?P-)6PF;!?3H&C&=IH)N3]G6S)MM*0[2.PWM6UH.-P-:!AH)-9^5*6@S`
+M31IBGSEFKKUNO:PB9;L8%>]H_XQ]%,0N'/.,$*:T^U+=!A;H3\/Y(,T#X)LA
+M,/@[E"4%</*Y/ABHM1%W.I-C$-%;^U6WGXB_Y1>Q(4V#[F=_ZURVC'\&QF%H
+M(=5/+O"XOX?^H^ZG[<^?9C*G:&FRE-E%TSE'AB6OCKD*8H48A!R(776+A(53
+MJ5=\`A%]NVJ-P0@J0?LB[]-*6Z"FEQ*OZ]1LSE\CU!%(D>7QE9=EUZ3F.M13
+MU)J@?DPY^U\>%$>N]83_`A"0LSQ6D26`9<J`%;UZ$*V8[F7Z93>@C"SKT.2"
+M3R)98I\O?3I>_:+O-*[G+?6,-_K]9E[*:S0R>MT@_OI\^P>B9\SM=7$N<=.5
+M2_?I6+5):\M]Y%C0ZF.F0?2=CE]6'`#XO17,'QJ]&4(2K9`EQN\NEL7HJ+0L
+MKE`V:AE\A3?,8THE:K>8Z0?_1/\T04H4>SW,Z0UAG0G?XYPAP9SC%T!N@WQA
+MFG1&HL&(&MU&<!XF`0-Z6]O'KC@3\ZB]"8NV8YC5=_;GXZ@D0.<)T'!3K\9-
+M^N3<+[-;M`#7RTKPUG0)[0QG,ULWKO>@*.C%PY<E6B-IR9[[;/%SGA%K)5YZ
+M&.CL`-H^*MD,X/6-5#KHG)U6,C^1[HRFW.YG;[`&F#EWJMD!M*=*/+#$_E6@
+MLNN*G3&?&G>FR]U5E&:U&"5;9!&LB;K:KE1(;DZ7=IO*9LF#<D[SCQ.G^HR&
+M@\JI"<"JT^?L"K&QG&U'^-/![A;F\Q&E<+--#K1:6D]W+@):(JYI&>:KPM5>
+M8^Q#,\"('4UCN]*)B4"N"KU@^GO",CRFJU)>N_Y7,`2/T'=CR17?5@=0\6L-
+M&,C3/N0_)3VH(6UDS[]=#X>^B!SBEL^/LI).,HW8VJC'X,$.^GFL]`YT]QO5
+MQK1XEU:X\?:2K!WBI[*SCVJ63I>8E'>Y61'LX.(XL1LV7N$9-0*U:EN>U'VS
+M6=Q2.A$501UPZC0"C?+UCZ\+G-W!:8IQ>_UIH++QJ_7^@P(6G((+E["ND>=5
+M%3XL1,YV/@NYU@2?!7%*5.J1G;*&)8U=#$FRZE[PLF(M-*ONHD&KTFSD1(6R
+MB4BA*EF#*1:\;J>X"WU5ONB?$"F^;9L#+YTOOU1X4W?L-*/K1A6<;F\<0.O\
+M_6,JM_]`.7I(B`#D>4?'ZZ_;RCBG=8#6C)K(H35E(LA/>AV)ZRN/UC;2PP],
+MT4J&IMD1(+Z)N!).^M8E*LF\(<P:R[?'AF([O#IU<D\/3H'>.#)"A\27*^!E
+M8H64E)-'C0ML7X3]=LG49YZ7A]5ZG5/M&!R&ZD4&_\P,%'?QF"FV:&0@[<,=
+M/0DNJJ3I\=&5@4=N'DXA-:R:O#-D./1<^U[60PE-B5ZCH,27B@P6TC^(3BEI
+M6_.9)"_(HT.!LO]4#SUY/,]G&_#ST;+6U\>3UU;=):^+8)YKOA6=AQR5$?NE
+ME[[&'2E\V)^Y>?826Q>,'`%N<BK:5L+EVN5,/:M-Y;*>[<V#F+T+")Y402HZ
+MK2Q"+WFX=NS0@3*P>&NFL3TW*YUWFP`X(T0A5G9;9-8SY/6-)/P)/^(24V:2
+M>[:#4>L4KD?Y^D9[5/X`N7Z;+6<>$T(WPTAV?^Y'B&E,OP_;VF?HL9/,2#GG
+M*49!*I\&214-E]M(+/O6,NMJCV=FHG+=7%4IC;6-N8DJ!J<!\WYVW<-@5B-A
+M)Y/@9YLM&RU^AGHCM]8\9+N=@U26<=D5#WW^0GY4R%GW/FZ5H`F_B9*Y$<>H
+MC_ZST/4_5/W=Y5I9/$2>'K&*.T_L=H8`BD^TQY)%7D%X(B658VWEB]G\\L+,
+MOPKRIJ@69H3H\HT-+O]@MRQ<!!1=)@6-\^\)/1$?5]:FK*S&IW=&TLP/:0I4
+M6<\<ZD@6N6.2@.DJ4"$?K:02X?>Q-H)5#]5!??BM)^>6@<A;8`WF@('G.J2_
+M$5&]7=$J'8ABE9%K8@3K<B#^\.II>_!8^V\K$Z,.]*WEX95?H8P'_).M?G%<
+MW`M0&O2,@@"F@<\P%(`5M8+.5@P+5#ANRPV^/[J_RE`2@?<7NT5]H8A9>&>'
+M1DV9]C*!^HJ;5L[]DI:U_4<54Y:S;_K#/K-JU"-5)O;L+OCUH1L+R\3R-UAD
+ML%A7A0-P#_E&,M3HE;%G^`]B(K&YL7SX5PNOZB,7?_20KZS`1']4)HWZ/Y(^
+M<JAXMLM"TPR]ABPF<8._G<3JY$KB=:%D82?#!MA')ZEW&W.I0$%966_WJ#AT
+MS7AY\DH"QW7>MOV>!A_E\OMI&;KN7S[IN2!;1YI\/MO%;LO"LSLAX0\>6]NN
+M]-@VU_7JE:5*C`9HKPF*WQN)U$;V]-MPT(^A>7U$#K=KFEFIF^/3?NM+8.7+
+MUB`K[?!+WN+'Z2#NM#KG0M$JZ.\N`5JE(HH/Y#?EA(=N]T3E]#!6X>SXB-N8
+MA-ZW0`1JX&I@^4,@--7?B?]X'Z_.S<.RD&05G,OT-:$!23UOV).&)TOE$;OB
+ML^*9_06SMQV$74?B885J9B64W]'E,OH%8&CAO]U1BVBD]GML`:5E.GA`'#:M
+MM'LQSQ6E'TBWQ)F'^<<*C3[:MLY>?RY:_&Z_T9!GF[W7\O]66<Z):+DL7NB0
+M1,I4&@4C<`CQ0-!J.O#R,]]FTJ04C'=3[_%0QA=MEN''I3_5XURC;J"YL\=#
+ML0]Y1=2,JTFKX*MW>E0N+&_4(9WJM'+X(/D=&Q;D&_J3*"(G(4P]Y0=;;+E6
+M'VG[$>JF[?Q]#>"8>YY1V=I'EM'E+9B""^S=W6/IC`SN@<,K/'1C2_XS3R:"
+M1!M#)H:N8'<:S?2<QS.5K2`#);X,7[;6FS.NT9B-?]_Z+AC3#`'Q6N?W70B4
+M)$;7ETBDD%&L/V$/\*^2OII3&B<NC:J<QV(W\0-;W@%Z(6_QPC2^0S8%L[,M
+M4?"6ZV&N:.X#/TO!4GI[2&83;=5;&-`R;SN_Z7>)Y^=@U11KE3QXRX3AF^A@
+M3"!XR39%EWZC-%!JVGB&6)616+O\D\MQ%3;MP(M%Z3&)A#^+!#N#Y4)F0"8(
+M@`$E;5E#40_#3R?//9[%;_P#Z%3OQ0Y(#WF19W:G.HHTO^WUDG?EL-/DG*LK
+M<,`.6Z=!G7/M.^".)36YKC-;/@6&Z."&W81><98I%4KI;`+TN##V%`!D_5_&
+M-,+M-2'?5`%%P^LY=VFN)-LOKQJMZ,"HCB$9IGX`BO\GO;DQ[(/W\8HR,58G
+M-WJI."B,32"0FE1-+W@4+=V>LIJ6`SX<(R-JU?K(46#B<XM:6O6[Y&IQ^;?=
+M9&IE>0Y0IH14:Z9;MOH/3"JB6"N_GD%H/%L[,@JYU#];5*MK@*HLJCQN5V*@
+M?NG-JC%($L^?#@;4M"T`3&4`@\B#859J]>O$_:H0<HPS4K<SA5:<?Z7Y-.W*
+MCBFRFJ2N9-4*(:*=J.<H!G?#S6I.,S^F`,9>(C_(DK,YF(Y54$WFURJ%+'UR
+M(#=#F.223EU"V.Y_,5OS[E&V?$WF>^&O><*XE#D0_-JS4<:B*%\!7#;CLDNY
+M=0Y$A0O$2*9;Z]^"QH.>-3.FN=6@F\RYI^VH&DU?'&N(#<Y%NVH[\J0X&WSQ
+MN+SN5Q56<ITS4Z;Y-=P"W1*K7P)-.ZDD<-]ES^8RC/,2NFABQ;/=$#(<6G;:
+M43GE\?@QDWE`:M3=M]7RJ1,.)JV/^+O7FYW&+[80,.:A=',#PO-,YSZ#[)"$
+M`Q)7JZ5NGIPQ?>*&*V"R'):W'9"NU_U)Z7!O'.3".>%%WJN,L74>SQM(4I.@
+M_[%<,)^.S;_K,A8?K*Q$8ZH`F>E!HTZS$9KY-7$VQ!GR/Y.:_:"@606+YWK%
+MIU@/XTT\DPF<_Y"(@OIS@34,G/&O78N]@T\IKT1N&#XT03PEID(1@3J<3U64
+MOY5$983R?>#T]Z!=X$=*Y\R:Z*_G;1Z%!VLVH[5E)DS3;2H31#NGZ;!",,[G
+M:L5.^$K5N%[=B7;`53)T$G/H_&MSJ=(6#\H6;72!RM+MM1_`A%L@446Z6G3#
+M(0D6/+I>K"]B,AH21IX`2-"W6=GY1(M"%U>F_AMJW:H!#TN]F69"LPL#SK-7
+M<^E6@SN3#(GF3TK6G-9.;N@M^91*Y/0<KQ43-KR$U;.LS9S.5H2@46U,ELDL
+M-O[B!,AJBV-!*M3H4+?6?->""N!.Y>S?P@S*"KE0O9RUU?NI>W2ZOVHP0W8B
+MWZJGE6`^,F6N?VHO@*HJV[:]ZW6Z)27Y3H3>CJ0I]4HF)JJAI<;2<#J$0NPQ
+MM7`MP@9OJBS)^)+#NF7!B(`LS?SG)Z<*/T,++KGM_K4YORU/UW^P2&_U8C(N
+MH!)3T'F"`L9RF%WLF3",NOJHEJ:U"GVA(J4N!^^%*PMA)'"<?JDK94._4@,F
+MN.>\5PY76<C3^48<18ONZ#;+1*$-MHJ\8(RDH^S.A7>#,5)*[IQG\>:)L?F/
+MM9>^5M+M<SL'=[8(%.\B\?NH=`GA-$PN31F8Q/BM4]$_B6!$3UA=@TE0>6A`
+M9:-_L9?7!9N(IME!&^V;;(2Y@A#*Q[,=_,7M=6Q]9MM\!^;D$I*<JIOP5G>%
+MJCZ,B,*[?"1`@,_7BS&I#KY2<T=`F,'3T:]K6K+I*/IJO!@SSL14F-XN6LJ%
+M%GD@_5>@%LAH$;O;(R-9'7W^JZ4M#L@)F@>).2(L2S0'9&PU/">+MAOGHHH:
+MZM-B0`5<AW*:^$*>_+?R_OH3R=9*%A<F@F)[6,E&6VH>\JV:?G;_?Z,I;(`=
+M!S5RN.>9_!("#*AY:-]VIE:J=/5P^$I4D1?\OSSQ"MR;.*-O2N%>4.V?'<2B
+MB8[4M[L?]#:.+H'T0%<LB*3M%.-`RMRW#]GM;-ZZ1@\6'QZX4`^FO!8`<0!N
+M=S8\`UOB7":]F2&%!DZ+S_J.<L-F7$(KZX=(6'HIL_QCC+O$0;>!$JO-:E.\
+M,G'4W8V+.<[/:UR<26*Q9C,-^*1U0C[04/:=SD:)K^HYY8P@9&'$-N0O?`"%
+MB\=#3!(IJ]G)UMC!C5U;K`Y3]P<9VH/9!+HT><[O;@'XG,_TV!(.:OY\&>/#
+M,+0F!P`M+V'0Z<VA0_DJ!&?2-DJ+#C*U#W<.>\H_;M*VUZHT39:EL.[63D.:
+M=_=$1N#CBCTA,4]4]9D4*NV4N'*G#BM&V8OD^_Q>::-YV(I9^WW9/@PMQ$K'
+M27L_S,T,O>P`<\9+.-D4#71YQ@E5XZ-80J3>UU5H!-K5K1&-W#]^T&.'>E(D
+MC7K6J\UU&*#'IBR0&-/G/8+VHPHG$R:`972%6^X4B_4N7'B;:F(?JP_!<X%M
+MO=P<U\<+N-V)+$90\WJ;486YF.M8'R;?C2=E)*'FSW>/!\@YI=-JN8%X7UW*
+ML/07="&0N+B7L2[C"W#&#GFF'+(GZG2;T74?3$%V@_`?M/Z+6=^4LA<*/5S?
+M1^62WTG*O3\YZ-C^F3]*6\],QR4D/Q1T1X(>!Y4#-"]!A%?#_*8;?G`18`_(
+ME@RNFJ):@N^-50R^C_5%&+$>5VHZ@9U?-)G$"%065!J::(R7[;JI3R_PD*MX
+MF64>JLJU=4OETX?[]M$-=&;RS)W;O^SB.,*%4@Y$!U3DNOLO[`K%Q/I/!]GK
+M8X5P20=_#QZR;^8B!0Q(Q/$=#>KW/KY"G!7C0<Y>"`:56N-2'CV=6KM&[^D0
+MHBS8^`>IW:N_N.E\!@Z'.R73/P;]SKF*HNRF&<WT_L.=N@P=9%AYHHK;"_R7
+MHE`9MW]B:>%/X8I4$2)9+W'HBWKYW_QGI`8``I\S+AJOBIBSGK/2R^:;?GLF
+M\3B"G-$-@VKGU%34@WQ*<(F`"Y!J@%`V6=%8U'A4^@;^OT/#(D@[6$4@US+6
+MML[%&1A>_`W\_U@E1?D!PB_N2Z3I1H:%3&OB]NRKTZI)%V+LM.[-_E[!L9TS
+M/-DQL6D]]UW3QC=\/'D4A=/6:&%?F7YM'2UKN]9">C/ZT'D1?M`:1O'D_[1W
+MWW=N6:\2SA7HQ*X@=)FNBM^^+X=Q6U1&8-ZN&C]>U66#O7FW@*%)NN4:5.H*
+M8&"5?&-D1\=HY*>G?NP:=6]1X+\`]=+!&`R#FL5*V=78\Z`U)DS=G\=E$Z@-
+M/;Y36O5M+@V@>I53FPK\+J)@;AY_EL\,=;.:)&"$-NXGNP7(3*=RBP>S%5"L
+MOVBT`OP''VW?Y78S($S<AD\VQBK2&V7V1+5K)^Q';6E]N@?>,)_$>;^S`,O`
+MEX67DL`;*L^-BJ]0&I;^5C9M!'/6E26=P*U.S\-!W+8:0V7/>%2'FCOYK/8K
+M[`:E6%=S`"C5[]Y$1>=>_@_7[)%K7XSN<N^2&%:=<FH+*4J1A1*D'"*+SB4/
+M[JM/5-CKTDUG\YRKI$32VP3++\#GC3-&G>_SK02&TXEBX4TBR89!H5Y0/"1_
+MI53.!0O8/DN:A(]:%3B]"*`%Z-6+^H4&\A_'AB?P+4TR\8J=O-1&4?_2`D!+
+MB*+M7(7FYIJ<B&*W4DSR]+>^D)82)3$((W46%>;G_:JXXI;;O<\WS0NQ+7P:
+MMM]8>W%_*14`KW,H8_@Q;\[9<LQ1R%W"8CATS],R&^.B[:"8M=;W>/HEF7J6
+M/][(8K-,70M0(678W==RCYI(""XFN-KYWYTD@.U>K>)B(O)&;J08#">"F#;,
+M9?4R'6"V$M:QQ[1XV]+B*2KD9WGK@3^S]E"'0^7>;X$D7R&^4[Q[AA(QN._,
+MZ.&R^R97WGQD14X!0&=*&\+FQ35W%B9&Q:\X)E3(*\$GH5?!.=,Q.@1E$W7^
+M4>`)=[[&,_\8AAC%CI[T0&ML[@7EI3(Q+";/X]7#[S&MQ/8X/]HAB$Y0?@A(
+M;*#2S4Q+P"])+V%NADDYU[#P!0I"B-,Y^1DM[*6K'^9T+%4H9-1XL4G$0C`)
+M9VA/TR$#C=""N9W17X6V4@L8;6D>1@\6-Y+@M_6M*J6<<MR4`5S9X_Z(1/`6
+MHV^U)_$!8,L!1?!P/L9N30`37M0]:^%-=RDQ!`!M!W%""RG26N>7"F:JZ@KR
+M-<G&K/(=:W>(>UQA#(3V]+[:/4ZS&?P+:"=M#S1D4:.G_!1<ITP3EZ"RT;O4
+M<M,&O!/S1?@5/U-3P`G76&+JUXY)&,G>B@R*7#GW3.'I_+;B>*&5G&XC&IQ1
+M&K3(?^IHPO=W06S&$JR-,'!3'!?1O^%94(8[*5$7[:A?+_]&8R#]@!PH=E!S
+MP&7!JDIJN:^S].5UTUBHTJ_=5"M95[)DX!G-9UR*%WK!BJ:"LMU<<47E)82>
+MTOMJ.L1]-]]PDZ_`MKQ6A,,7V&3E-P/Y^%93=#VD!Z`JXQD#07%L%ZSN0+.X
+MG>J'E-%4^'SVTQ7?[<U3D5A^<C;,E"\^/CH.ZR+T#R$H06:\RY$#FJ]VD)"5
+M`(;6!=]2LQ0U&=TW:=A"ZK[)#+"5F44;LU(O"*2='S%'S]"<[K7G8X4SI9QG
+MK'!'_BB/9D/)Y1JFU$;C'%ZN.GBU4V8+IU;GOL3]U'5$8"0@X3>5'_M+"+LA
+M<`_V@7M'_8$@([&4VF,K+@C_#@4Q.9,(7GK%S:B/2F/:JD?.=PK.<;A;N21=
+M1XRO>.'`DP8;FT^LR_%%("X]7WT-_B*7ZFR==V4BS7A_%W$!*LZ/9>@)5\,F
+M992CVBJW*LW?I@;P@[JJJN6OC17<J,TG3=Y+DVG#N"-J*56%5-L=9WK_1:']
+M;]UJ?1.87:S]V=XHH9ZYK=FL3#<2JW1=X&YQ;%66K`D?AQJ)?<RQLXHHS!@*
+M\)5..NN:`UR,8;H/<\C[\<0D[Y:^X7@0K8Z5W'-MCHI^:(<CH#(BUL7^W_L8
+M,<6HE2QPXN&J.]0SPE_2#Y2IUH\X;I@)Y*((Q?5?=)".OBK9=>:G&;:).R"E
+M8QQ[^&Q.8FHY5!A,0[J96"H+#I:SV3`??L)E"!?B]6F`4@.]?`%LST0@%(5.
+M6"@LC%Q!P]R/V\E&+X:NW")56]R6IS+*W4V,.^D9[*#0*<47*AZ.\_F[@7E$
+MR/QSHDU?9AX6S2AA)H!+P<=4A)=_2:BO!"78"1I/?4U]](F/$T)VR\N]@7^:
+M_0%87E3P^1KWD4US7[7_M7/[Z,EP3'A_E*\DMJ#`F*X5,M6!N@FL6:"2>&,X
+M"<$QG;*UIA89VJ:9Q$5<(T5^5\7WMOT)A76CNMF_:2^RW'%'<S0(`CLEM'WL
+M*/-:J/D1[F4FCS1I560K(78)GMYE`T4M#X68ELS1O\)46T^/=I,92@79G=^(
+M/C2SDK3VSE%E<<J2"3O`"#Y@#W1T3^N"Q\3-=;SUB\R_URNEY01N=DCQ85(]
+MW?EK[XN!"C)%J.,+6LL0LE58>5L-==AX.9>XK-AME:RYY">)+^N;0K(6L?@Y
+M#889RL>W/F08&XX9KU51TRBPONQ+=4<EL/9+VQ;@8V?<>8RS!^[#%T5[`(;T
+M/DG\8_Y$"2F-PM@1,KH.T6?*"?F]?**V!B05:%9$752Z*KH^U^(P7E\H3"F#
+M!;@4:R;8N.Z"`MZP'GMDA7TG!00!VE*_`4P(G,8,@)*$79FDGOMB]4+01^>N
+M;:NR7>:V[4JC8G#2W_>"7BEN#I@&+7<%+#H_AKN<X[?]`_6TGJ5;S0.9N=^U
+M`R8U:JC:<C2VY`"7`\`]],:*/20LK#*<!(P9!^[`@JVJ(8"GS;W=',Z(850]
+M,D$#MFE"O0HE?''>#Q1-$!MMARND-$/Q[3TV>;CGU"+Q(\JF&,/OV\W-M3-U
+MO0.M0J26"HDO^99Z<I.^ZYTWI90M>:$9,:H/ODIP0P!GHU1K,AQ3WZW?R/RV
+MKKK39U6)"%N.O@/I(#Z$(U"KMA4&&,FI(>_(69*5(]%[4O`O-U>_0^BMFP@F
+M'"B)]*!/W"OHY#VV%XM\P-O);!42`(C`1`J3&VJ.76,(R[(WKJN7I5#H2)5&
+M+V%_<OQ:P7MAQ:RAQ7_B7H?D&M#5+4&<3*'E63WR&5-\SZI?EL@U$:JR^%$I
+M1XZD]BA.EYV79KV>5+J<Q$HEQ@@$MYY5P(;BL8(Z03JTBG$2W8VT<)M=B&G#
+M1'#8[KWK+$2>'9\V"98!F&SB(*Q--<BTF#18%"[V5=`%]IZ62]43F/J33>(C
+MF@0=]:CPAK"L*1??)M/FFE<=`ADQB7G/O?9M&]3`WV/N[[_9#HF,V$/5%!"%
+M@YOCOL-T]3P(%,=>$%KQ=U?IJUPO@C7_:H&K$F![7$]7W_?/67F`]=9(YPZ/
+MHQZ#I&%S./9YV+:(0AW5F^UHTM1]A`@&[6K:.0D')85`PYJRW'BLD+/-JGC=
+MP#1'+HVLG*U>17ZA:*:(N(BL@@'K>)OK"'Y=?PORYH(3)]^AB:20B"/I:E?Y
+M-QLQFM$&MO#8D'SK:=BZ6DEL>05H47'LA=FA)\]>>FC_3O7GLVWO%`-F]9KZ
+M"-DC$@K%;NJD41V8G'H/R*"E*MA!TK:?%0"P]I2"Y/&1G:"D8)D35&R+_$C_
+MNEF;F2_'/4^("B@^9"]FSR/7V>N\?7L<+$U!YQ@H>)Z`/**Y017;7W.;GJ^8
+M.R;L"XQ`OR=R(6;K]<:=5O*:::->-?R]\91(^^@R!>//`>"NP1-`^:5MMBJ,
+M81LR54_WE_0Z$2F&OZ$-J9Y"Q-BG6I(G4%YJ8HTPIWD;,WF)/I;LM&8.I9ZD
+M_XEG+U6!F<*LFID\)8QHM9B.N<P,-F)N,LR"JR&EF54Z>".8ZU7*KQAK+V&#
+M78E\FL!,XJ-=U!N:VQ$Q$9!W;O=*6!-I0BB8ZW"T<K"ROD=Z[.<$*/]T9<_L
+M`+#S`$K(JS;(PMF343>("FF5Q^D[WV[QIN/Q:VY!HK%)H?2:%L*M)Z^;%PU-
+M;+VYML'(\H1"?M+;O[>B4J<'+FI"0+:"7XO%[;JO2G(?7WDE%&1UI.N[+[SQ
+M%5=7<_C^_FVL`8VORR(;9)P>ZMX<F12DQ&:"*W-!#<+]B%7O<4M_D*L3R,\*
+M<MB:[*2-G)]7XY06<N5[ITA1<:)@K%7;L2!B1=>"\N.2`F"F/]4)5VA$6`&K
+M#<$B#!R-9'5>!P*W/7IYPX$>=JN>Y:=F2&NXY],EC&@OBB<.S\J$&OG?9.^L
+MA@._I=1]>R>]4)"*$U]XJ9N>&[%/L-&`Z6'/1(3@?T+P*H<;Y.5@=S.MVO==
+M<U*%%ZG:6QR[:!F,C<J6E+E8:7]]CX%G2GVLTB\8^#H%E79'-,I`X.4R[P]Y
+M!)7\OL/IM_,'V'!<YX"6E@W?K#F7G46$U?=<I*7Z0IVA9,P$8W2`HS$:7G7^
+MIBPH$GF'\Z3E8GUT0F'MJJ.7E['^@'TH<U@8*X&C%CV7F:3?'2-&O+T0`/J=
+M]N=9VSG.*Y$B1>%22,P-Q?.[QLP$I;1<K[>Y_#=Z(WD:%U*X2251'W%GT`6S
+M+=JXW?$0QOMKZ&"[>1[X^-J)',K]-^.:5LX:2]H;Y.!7'#?>Z_&\Z-'AP^)G
+MFHX3B$Q^`9:8DY.DZ:_PIQ:-X29\RB2KU*=N&J*%L/;T'>%_K+=BHN?DI-$Q
+MIWNY@FM"_\Q3?X%K\$@)ML'X@&ZD-OTO<^O1,2YAO=)%US=%'J#\M'<N$U_X
+M?81-?^D.['3%!O,L3>'MOQHX<QD$GX9TI^G$U890&(#<]QJ?DD`!U+[>JN=7
+MCJVK3J8I4=^MT:7/E$Z@GJ-F@`:]&;0[4VL]%M>"BN:)8`;B/@1,^*_8A#G=
+MIZPUYIZCN/>`T3R*N:T0S'7##35&HY[7U/\BCA"[1/NIC<$5!5O>&RC/FYPS
+MDTH5]74*KSMQ5,\RIY@SF_"W+^?5"D<C0:*)&3"@+->$G9+VI:@*$$E:#1TG
+MYL1])T29I/?2\O5+I8G0!F==W,658@!`6J1GV\!"J4]HL$BHX-$A[A$/],/!
+MEZ[IIFTB=$^X<A2'-/X^1>][C1_<TSX!#>E``"SX(8QJQ\^`BYK5+Z6W@A_*
+MH&>].-?.F8@C"U3-/>12;2.<-%6N82G<4EEE?AP7YR#:?<KAR<9@SAG!XYY'
+M!^[3ZF.6J2`9KE]R[-'TZUC'CL*+%`BDX_BR<F7?LS)5=#C]R\YPV":N"<*!
+MT7>)Q8Y<2%*:<^6G4R[(/ZFB(:R"]LEGVO@+B$T=.6A8.ZM=IQ&.'K=9M?W"
+M0FH*LDQ;!?U.DJ=(`(/N&?HX+`B[,[A]].&O1`Z!+F6W6P1)\V5A/*P3NR<-
+M//AT_+"5F/+5H_A9QW!$MKW^4=#?F1=9*<ZAC^O^@I]P&7GRF`E]PG"J:U<<
+MXL(=GRRC,H22&$GC^0G:0=VV<A?**HA<3Z#Y3M'@P^L>;[-\&^A,:Z`Z.N'\
+MPZ;1,"3%M!F&1:;'$EC&OK_4FR&^?PRP'>+4N;MM@"FJ-![5.9G,[82OY6GN
+M(NC_I$2"+2:!FO?Y)R^!141&`_`Q"DU%45)^/O;6H'4M7(HR<SKDIOBA<9%,
+M$^WK<GYAGJBO$YJ$B3=!%[4[#BHF9(PURVBA=.W*WR(Y]U&_ED!FLI7F6D4I
+MVK\D:5M+&\@M&R9'\4=*T.OU`"BL[0N^[FTZE)'@EJ>C?7.67%;+Z)1,PI*1
+MLIGF\=T)`"33!_)(@4F-#\;.#0"H>F%J23$/$AG0JLF$1]G>8G#6E=AREW-\
+M)L?>`T`?(&B_<4T6&)?1"K^#FOM+:'/:!E+SV.*E.)X"64T+Z>5A"D;L@+]G
+MDY%J;QH*!<*@>3%G=1)5^"-"QW8Q=FTJN)6/LBJ[>GH[!MO/O[1]0&QU(RIP
+M3+T(XZMS2\7(!M?>]>!R8=K_`.V.9<"#6ZF68?'"7%2(6H>!/V1^.S#MCPA,
+MLXTU8-\/:=;)APY?57'\7K(Y@\"EPB8RN2N&G&CS,_^I$4%$TX1FQV?\&XA>
+M5M0TUA1,S>,UXHY!*'H950578Z;P*V(;-&+$35NI8U'1UF[HIU#.C#25=T(0
+M=$T,P_LVO-:[ZJ]A0Z38E\0.01>@$5B+0)(V,5NYO"F/EN4-F:9\8G#R[8<B
+M&C),R-#WW76;4#1!:-C*3`K:Y+N<%Y#-?_NI^!]!0;2-UDED;S.WUGP>S&T<
+M.(]EB,-L>O=;KB&.F:5EF>HG8A>IUO^+,,"JJ4EPSG9`PT[X;>)IM@7/O5ZU
+MDYT_TB</$!@MX*.`E%T3*=P`A,805XL?,TG8A%,K_&XYH5*LQ;E3Q71XU<_[
+MPK4NE0U64I`X<W;F\EV>KS<JF?J<#L/^K`J%DNON8Y5!9=WOF,,9#>Y=XEJ;
+M&H*7.\]V>*Y*++7"(5&`::.A6R)MC0$*WGW84POHLAFQ%1YP+81H'/^)BE?]
+M19?*=][INL]*\^>%H"K\YT^5@1;IIC-F2&G6>9:#!2X&=D@Q30X(WJN&?RQK
+M_0[":`GBF*N=EVQ\83Z%X>!;Z<#91BF1U!X>:Q1.`=NA-!B(M29QZ]%!1@$T
+M7F0:JAEKD_Q@=PR&LWM/X?UB\CS<?>-WP,$0<.2X:^33S"67\[U[*!WG4Y5L
+MCO/\)ZO4KRE9R'G3E$;O#,VR^>'CQWG/VI<18M-=#@2V`47[4^6'M97CM9-:
+MCKD30!4VK<$I,$%[AQR8K7_,LA340I^&),.9PMTW![@7DQY1E`XN`DRV`)IX
+M,)JO58-;A<^"JR&S@G*7NX43!5"0).1(3F'!?P+T?XFA:F9V03A_32^CJ3Y:
+M30_O82&1@X:R?2U*)4!2ODQZ'NY);VF1OM@<E5W&-VMC?F1VX\K@-D"]\E:E
+M$XEU:;#CQCZE,S0(O7>*9`R1Q]&FC\K?.!KNVY.%:O!U*FT5-$&"^?(E4])#
+M[_7"]U?1?A.9+1BSX`?K*%'USHK?>/UF>C=6Y*9O<>'KR)9'BSJ75EY'\+.(
+M9E<L2SZ2"Q>:D/J(>M#\M5(AQP09H=Z!3V<&NFPIV)VD*[UC?])F8!8;Z\-G
+M2*!O:PRVSG-=1);R+;M`(MLIZY2)9A!>)$7(V7$MMU,0RI>9IO"LZ3PN[+ES
+M[V:=]J=$>M\_+/.^PRYMOH0\^=4S=->`Q'BXQY3C#$"-A@:T7J%`';)*<&>(
+M`=EID0O4&X9ZGV(@S'G`W[&C&-N7TH%6!T<[9ZT8/TY)^3QV&@G<'VGO""Z2
+MU/!=9V:TY!FJH;Y4ZUC@6W5`3O/X>-<S-5T(SR*]LXD`]9_U':<K"AU;YDHQ
+M==1RS77_,J<;ZS'\-C=[/8'Y;),9.=HZ(^6HPJ!P=PK[8`D\J`A9"RHE\W&=
+M,_8V4+FI`E#0T*?^?+-Q5]--UC,S(RJF9+/^@>2(77RC5O_B+,+W[-U))O3H
+M.K.XUW@"MB5(KL5R=$-`8A"D"S#>;I5\)<,??D4]4YPG]],,IA<!%G]L.TPD
+MD$4N1?;8:#MO%\9'_$8R^:;%QWXEP\Q$U\+#-YL0>%#IU*]C*>)CQV=^]\/&
+M[J`EQS9'RIU@QX'HD!F6%;8I<[!Q/ES2&UR=)/L+>E_)NFI&T%JT_9G6\4Q#
+M&_6MN!:7.-F-F_88$H</D*>R"$1:LQ@4?[`X?2TW0(JT]1]+3/)]\V`"@NWI
+MN'PMM^)%`.Y84PX+\J][>U2`Y([$TG8G/WLS+E^O()2B<7:*3JE^5-]PC#4!
+M5,W&T]<*<B=2MDE'Z*A)<FRJ,>L':*3T;-.0A!L4;#?U=&W"C`MCF`7BH!=%
+M<[X/W4>:,^_Q>FB]Z91F)_3&(%71F6!\AI18M_4V<I7O?$PW4.(3-&UZU5R;
+M<IE&A]R]<,W+YHAE2@5>:UX%,R\CTN[!?IUSI?5U:C0&WG_??-TR&K$?;3#F
+MV#8Y0.@DDJ)E)+U!$5`K+3@XM-0\Q?3-&*CS%8K$8&"G`A=#((,'/7^O4W'W
+M:<,DM]<(=5L08_-';B[?P7Q:FM&?W&A/-\=@WZ672`K:9/S?GZ,]'![]XX(O
+M:9RD;H4ACDE'B5+Z1#JG[0_\.&LVO*;IDH^I^/7O&W0+IJN^9@4]JO'N[Y:]
+M+4?)8*DDZ#S_T@2V76$+NZQIC(<AI1K"I^-\+J\"2U5[X=A!1H=<0,$+_&,E
+M!W'8^^%X$B_2F56./K3T^(3&1)*2N6!-^5R+1@5L?S^A;^>#`$MTZ,6RNUL:
+M::F3$2EQ`1\AKD5_"?J#ZTC-TVW?YF2_G*`<<>=15@MQ3B@F.F4`(>CUH5C+
+M?[UK[D_<5GU0]LJ6;RENWT0VQQ$OJ9$"<L;9D$R?E_I]AV'E6`RX%==%PF.F
+M8LX&=HB)FN4P*2Y9"B'OA3V*[O23D\%_::-PW=TA?=T3[L4?&.XJ9N.T'"8^
+M[#F2V'0AN:.,0'Y0JERS/Q"%`0,LN]XBS_Z<J4R=*6TBJA!"$>]U_.5F&<3"
+M2?A"24HRK>,`4OJ2"HH1(;D4:T*JQTEU_8.T8!68``_,;N?]Y0T:Z?:@6F'>
+MZ6".?Z!S35Z`UD1I[>12_O@92ZFD3$D^Q@%@C%TJ"6'_)'Q=U+.PH7L$:8Z8
+MG'W\4959=XT+&>S!`\X.:6#'?3*I_RSMWGB[:0,N_G=[SA/&BBEU-H)0Q0]\
+M(H1P>2Y'8']D)6&`::Z,E,'%\UN5#_T`/#>1QYC_:&38(P);Q3)?H359=M"*
+MQ+OG++/TN:HQJQNDR/Z&-TS1C-:A=:H%&+U?>IVIJ^*IFP<U;>SQ(QS637MJ
+M'2,#"X5(-D`A!,+EZ2Y3K_GQ9$UZ1IR\G$<#G\!7^+8#J()%%J>Q,74Z]%S5
+MIB)KH83_PP?;YBK\UJK[5K]2>AJ2Y,XK"24N8*^2^8J&JG"4"Q#K!UVJALB4
+M:QC_[M'?VLQ@ZT6BQ:5SRZWU;K6PWIWADOVZ;NRI.FJ*G01,P`<(ME88(P-1
+M[9:/SJ\ZX>KT?KJ74I5ED*3%4:RHC93'()B_H+L?(%R"^YEC)K(;**S&AR)=
+ME9=A$E%U^MH!^[]U0CX4R'''VA<9(1U1><;%1%Q.(][!#0EC8[B:J0<S@UW$
+M3\Y%S^_.*Q`)"DR-V,$@!W.?RV'-C\?$)H,GL2``+\:ZMRZ,9P9O.TLU]3NK
+MPL=(]'.V>PY$>U_=S^#5+PVF>;ZY9O5(R([\23_YXT>/@LT?M>#0V6<6D:1\
+M%M:D:(%.+A,:J/>77':B.`708[S;==^M59B[9<PW]2[71^D@FM-LI7#U5/8L
+MI"/K):+S%H&8I3(O?X2YPI84U!0.6-U\)YSXFD*K]M`MN+YB9:*R0&J2ZO;)
+MDGVRG\4\7]''(JJ;5J</:.)2NL7V39A:F3FRX'E5QF$GELJ63P?\5$VKN$<R
+M6/I7S.B87^6DV:'F.5)1P3AS$](^15NRLBYB>0$U+M/TJ'HOWR$L0BV%EI_A
+M'<X8345&;MGC[)2.+D`,YIQTM6JKQ<*TP,>@+_E2.\F:S8=6&A[3(@?,#&V%
+MG$%[5F]M$E)Y#CU\S%'U$^[\8O\W3$YF^/@W80MC#EL<"_Q55DCT9Z06>N4V
+M?U]?Y655KE@TU@R"G04W8Q!/.$$\Y5>K8ZD-IZOSE3K'"M;@\6&W91S/YFF6
+MEOV=,PS?1B:DWS""H$_C(NP1KGUB:MM#2L3L.,(_JI\\<+FRRG2A\E&^TOW6
+MP:YX+KY//H7N*23OBK[/(-J&?4X+<?SEE@_4$O3<*9E':P3_&7X6U!L^Q^3(
+M@ROTH*O[EZ2MJG7R3RO'$.W$^OSDE?,K1JX:ZYF-JPD\/2M*N+R^QXF'.Y@Q
+M:O+'U]ZB6&'Y4MX`Z(>VQ*,*D[)'5).Y*IXP<^[%?"`>BQ+YXQ^/,LR@E(48
+M8)UI#\80`NHV96<[?3<PVX8_B!:_$KUE;"T`W67=Z)MN`ROR%.9%/,OIHC5#
+M-A9YZ(V2M//_)WHOHC+8S*`*"REUBW1*$%(\BR3A@A>S&$^8ZIO$(4X9@)%;
+M353VOW>%3#W[E)U$VLEL.0>M+'L^)UYS\,&;PIK71D@3K_7<L&C)XB6ZJ7;M
+MP?5T5MD5YJ-5PG[><?5P8#F']RY1H_]6'Y@Z40ZUX$IAQR(5J@`3D?3>+\>@
+M^H)%N1AB6AL./F'JXDA+'Q=,\Y%BC"_JXZA<6='OF'TO?1#>DCWI=I/5AYK/
+M%P'!,Y+Z$<N&=!!^UO]G%$/S!S;9[Z5X1NRSC3/&*%[M:%L3J3D/=*$#V$F;
+MBH17^4MP@M8-:]X?EXP0;S303F3#5ZOC@*%U8D3B-2/O*G36QS<8U\+IE*4C
+MUQ'=S?>7CV!VDR\I\7_9>KG6@&XKM+0-<W,X8V;23)_^)T8ZTY_9)F.23Y#0
+M.&@-J,/BX]33CSS@5]+%_4]PUK\3"(UJ7F4#I8!AV20XF*9,1['GGE^U3M<5
+M)J[$R<^M=?OW^DNR-GN?;#C3B#0NP)^13\'-/&N2)9\K#+J]?2NJ;SIF-72M
+MP+DSIR%2$_K#Z7NWIJ5\X5V._3YKE4$)ST=/%HB`AFMC#KE(JZK*;6EM%?``
+M&TC_0-I]GL!]M&L<LLY+H0;'%&&;M5J"C<#F+,2==1=:.5>E2><H>[%X:9K6
+MAX;W>JMLF[.BAT(EI,-_7:,)[Y81OD[1F/&<#BJ#,?O[?\Y%OS9$;4>0V;J(
+MLKW5K6D=)H,J?O$XH@$?-79,A,30*!VJJY[*EH)0RE]M`-\]3:>\BK60.<)7
+MTJB]\8"8.]RC)5%@@^NA^)W`7VO*6_-',(!2&1.&?^N,#+]-W]$C+CZ;4!&*
+M.BLUAT.22/EU*WYAAI_D">2V&%RSCF3*58355CKW<I'3SA>^DJ#HWK\T0NBQ
+MRCY3%)>._^?JCOI<8ZCN,5C&^,E8AXS\8ST5:*=6,OOS?(QD/3,"?9!'.G/1
+MS3I`D([J`G"6AFM<0)T5<10Z(PLFO[4)"6A]\TZ^7,!_EA/'S*TT\.?'(^FA
+M?K>VY#[(83*;!1LUBT>5U,+HJ3X,'C&,.,]RH6AE6/_^0]D\/-%F((SXM-V(
+MJ0_F9[W=1@N/S#(/MDUV!?S^4@3K;W.JH#VD8\@I',889A2^#+:\/6KO$M^D
+M%TAF,PZIO^&/E]9CN<-`FS]8CHRF]ASLZ`R8R*F!70'(`\)=/)B1T'?O[:4D
+M;5>#\&^-0O4J">O@K+6?CMR43C(FLE3:J:2))"?!GZMRSG1EV$TFRHAT_/.Y
+MZH-$@UP,GTZ3IFUIX?HYB_E[."Q(9_FQ<YRT/`SW,&5GD>G!#K@<9"`"1\FW
+M$F0S+%;(?/G\*K;6_NK"(+8T&ZT!.*!V_">#/D54:5FDO:03SNU8X(*F6"#V
+M].!.$!`ONSH:/W;6*?1NJH]P4O#J.E,1B;<G(F<BQ"8E%4:T4ONT!F&9SX3_
+M\81U@]VW2_$'ZEI"H&Q(RFK*AU-HN%8OH#;!AM!)9N":7H96L[D7P$_-F70;
+M]H.85GE.L)KXG:(_/=_#[%H`B]B\V[!'VB;_34!26ML9]?==4_L9P[QQ+V"T
+MW5+ZDB;D/_%K>IP9E.#M)Y7LRT"P&9HXTG7NP]^*+9<7;$!G&?$8I'ZU+N9.
+MS=TAT:(>QLM?Y]CG&I!XG\3@,*OJ5)7*]HB/C_D>6YC&JE`,]<!"A3>.$"!?
+M(3'\^XB![%:7LA,IV2E(ZJS6^0<JL%*LO_.@8;28P6=$T<,_!!]D3)&RDN-'
+M\WH_O#&/3WP"=SMN!K\M@^+1>RQ2E"U/'5L3!O$/#\E,[0"<$Q@S:XVQ86OS
+M%W2EPR):8C_J("&<R+2VU/63B=H!:_U2&2XU26W:)&.A,D@_7JC`=7L:4_3/
+M^)3@5VQB5;H*$Y0XO\H&!C4>O;&.?2E+9%"*N>7G%0LF!"!.T'7YYLN!F2@`
+M*]RW_%Q`!3$"O9,\]4V5S=29/UH(R'LNKL\JK*ALRW8P,,X=[;OYSQP-W3G)
+MLZPZ!3W+8#I/]G"\Q;E\(9"3JWR6@&"D;$,\"P[9$4H!=5'S5TPY[W;O&G;K
+MX\"EA/NDLK$TJQ0A+LF-E8HK17^+PP\KWND[FP_S+T."\2;(._MLM<BVORTU
+M;;JJ1Y;(]7\KSIP&Y)F>VYU@]^3II7.2^E*^;!\)F55N[$R2HD!A'$TA^($@
+MUGZM_,A:R"#(%/SI&:@3G6;B7CT)-XMX1\>VW@`9KN"2U+)JS54P;"WSMN^+
+ME'[H?1U_TUJ"F.<#B5OG5F)\B5,&2W'D"9>=*3CVF`*_SKGN&GG'\]+JR.L@
+M23\,,`DT(X(\*L]%3@H712.,N["*XEP`+#ZJ8(#6M-\)9!V&8<>DP"OFVFC8
+M[^&(XJ@7QS52Z>@IT08<PR73_*'C\EME'6?=OZ.%,NHFCYX"$H":V?5DNL#%
+M#W7B&X7WLZ[:5:SJM[&::"YY*5/'I[3\CRUH*\X0"*]*7KD&F?4OVQVH.]=.
+M234RBB",HN?CIEF$2*'=4#U.%N^(\Y:B,>MN>D+FWBJ]=+SGE^BW?:`=.8(M
+MCAW;M:0XZ'-68O-OX8<.XXDO^5)31&^QX`P0=)<R6\X3!-T_ZTJ1727GL.FV
+M83T!]RQ7LXN_X-$NE00H9?9",FP$?D=SDG(Q5`/R`J%I*#&_G0A$."+_Q"),
+MUKIDZ1!0_)K['W9-_%A%>3.I(L0KMH#]A`.<:NAE2O#/]MGE=%0]K)5(-GVZ
+M2XS3MJA$]7*DKF[,^`FQMR"5FF8*A?R5Z\XY&\LE67N5I5D"$2#!X,_U(41K
+M[[>0!4HT#S!TX$8=$T&\'KCC%&+?B7^MMCIOL1F/E%DA:H!U,U?2L(]ZAO*W
+M!67?:4_SEV#]D-5?`;^YH'K$#F))1@9K4\;&E`[5*OLO!'V'C?4%>0Y#P],R
+MI,?Q3NT=KS2?O1N=`U[-"?!1.]6@LWYN0,&%9^4C/UK[A+`Y[4IYNR#W@%1%
+MTY,?M,JCS%(N4OB_JI5SB"N??K7O3&UZ:*JS.,)E//4&SP:73<ZYT9=GHAA+
+M.\7]F^B*IPM+T\*M>%@L/;:"/'NI<L5ENI3G1M;,?H.Y"/I3&`-AZTE1J.I+
+M%K18$G9\UNCI<+<VO6&M;[U)1KS9%Z52/&!M'BQ_"QXOG+E)<T@J&F2H)7[X
+M)W&9J`<2;%8S?FS1ADQ39'K/M#^Q5B.!Y#*`4X13>/.N!@DD"T*'Q#/$QJP.
+M9V6Y3SB\.)--!>\!MGFZ?K(7``UB>8R>V1X1BWDR_S$^MY6QD!Y/HR12UMTI
+M@U<O,1INS*7YW!7JD_!USQ)A-`RC#PZ?+@E64B5//.6OGE+'<'\7O+'6#D*E
+MDC[:185^:J=QI`Z,0W8@_T<DNYP,!!V!VHG>G9'EWI.8\I].;>44]7C>A2AA
+M3X5I;/V*NIC'DU.ITRQR?,*(TX$';TM,&?TIZTOD*^^,;2N1!'%"RV]*0R\C
+M)+)/3<:]M2("H6*71A*8F2IZO\KX`*VG_AW`30IX:C%S+N!?M59)?GQ^*!</
+MMKGZF]!?C=;A4S(/#L!&XTCWA15SFL&T5>BC^Y(,ZIK+?J6IDQ3__<`2@,,,
+MT1PL@_ENYV@[G(<%&IA1*N'[?`:")J.IKA,"!T<8[W_>H$G\]FPEE!-QS2<K
+M/BR-(.*WNCI+80"#M=`V\M)/]TASVNH%N\/5&ZF:OWRA9,;+.-/S;<R4L7*>
+M]?/U$]2X9<5+"4'O-O!\W46_HX9J%U$3VDX#8@/TJI-?1JG'.O>D#7?RPO\J
+MUQ8*I+-C9LN^#9"8WF'\)>E^6H`]/Y>5?&3ZDQUDCR<5XIWE8\Z'I>W747Z>
+M<)9K/(N/<G-?Q$YR?WU^[Z7/R.F)<YIZ1!E=E[&P[?ULQ+SGOV%[I+*T6Q/;
+M[:&*$M:/L.P%V4B%+?1@"ZZ$`P5'N1?_[SB__0XFO1:Y+3P,[:[$_(DCILG,
+M9*8W133/\@;U5RQR;'$P"<Q.U8K<\V6S!ON%X6W?C?/.KD7*)Z\-E"_0X5\>
+MU,6U$K)%%%&`OCBMTYL+*JM%:88K9R8G`+275Q!!W<`]%-WNE7K@>?HM9)+E
+MO#A/7!8X)P7,41W"+\V6<W6B`;VBQ.V$2,O6(4#;[T+OTN\S_/[J*D_Y["5O
+M\519ZBKALKRC)Q-P'*DE,3(#+VR+L:6[,(E[M^=@;<#677J%%RK!QQ:<@6O(
+M<T^Y&NYJ5%U-$Q%.?5(RB\F0_+$"QM=`^TZ+T30>*"7UHP79FT+`]XZG'6L\
+MW'3<*[8QRQUU_BL#$<3CBL[E(@]M")K3M['@V'_!"[?M(#I"3_,U%[)[(5,X
+MCQ?4X)`0K&W+WU'$MNY\^BETX(,E$T_>>O@\3+N\@XWN,(<$U),8FGFQC`#"
+M6NG&##NJ^WE7A0/#\O6!IL)._$8.82-4A"L"1;]/,"R[+2/#U(QK"G"=]*V3
+M+'P)*%W=K8WU>5J4I(JT<!T[CQ>B:I40XX5-:"'7!TZ5%P<B(I%RUQ((F$@^
+M(YU,GN7N0"6XBU%*\<DS'LV&%EWB<I'%[E`T9Z*&-]%%I^\-M/K+RR&;Q&I%
+MR$Q3"V^EO%=?Z<H<<G@?SX4XF_D&VY=6AH0BD&PN?6NI!3OG^@'JXRUXL[-S
+MZ1SY:([%]%5KRXPZ]9.<D/`N,K<?"+S87];PIG\S@4=J8&>'Y'X8X,@T@T'2
+M"'4/5X^"])."F)G:7BO0Y!RO(T,3D"_BLU6XC-446ED8O_'4JJDY,=@\.=9H
+MS[66=;_YS;>3VS]4RY;)U_HEN/.MA"I.)2&B&ZN*1DCH3$IA40*?Q1\E]JE,
+ME7E]&QLK^35FW)MZV"%2"V<EC<?I5MT2'6B+MHV9"!T75!$#4QZ==;QVUX=H
+M;)<LGS@Y[B-7>"#L9HT$VN31HD##Z'@M*+^4$.]6FFXISR>DPJ60E#%Z7Y#1
+MR!@X9^E*",\B@BK+#8C;L[!/D%"\CC7*4[XF>:C*V^9:!ON`QG^_]Z7KPM^1
+M=3IZ#@CSN$1X%L?VK9`=0EV8<"9U]?D0TVT0BBDR+D(/KU!7J;EO0=LKCX-Y
+M7EP<J:WRLJ]#"-6@%G`G#34@D-)&$`(:XQ4R\;"*+]V`V(Y2F.#LU3_OW,!5
+M,,Q88^VIZ*M^D3H,N05P"@RTF**#IF6NR*.#F,CK&G4``(0-`TY1*HQ`(!Q5
+MCD'8NGH0)7MF=:VKU7=>KY5KZ-G;OK\7+,\;E+,#(N^4PW)=[<@PK*V`9/KS
+MH>O/]IUG[/6EM";_'MWGIIM_-`Z5@\6'<K/&,KG:#%S].05=DQJ<)4GIG)'2
+MW0B:@O<M=N0A94G:LX;J5=\HUN&9L1&=.N2:(JJOV#1%QMPX%:C7$P9\DUNH
+MY;$[IU,.CRW$<UHY_GTUJB+JVW=X`K8U"_":*^,$S`6LYBOA^Z+7?DZ*KC`8
+M4')'#K;;0&#$S5%?Y>1]AS7CB;]9O^?.:&+]YL@0D@P"PI,15J`8F'H=]R:'
+M5N,D%$=RQW630!I(OD7#7'!I7XI'U:*9L&1:JS>Q2OYV%\N@:.9JJ@87&<Z:
+MPT_J>35+32$-HV>Y10AV&6M<:JTT5ZK-T?X_6.W$V1^#6.T'WAFZ<YLH>1=<
+M3BUBW<2!8J:NNPA<.:C!=%M2,C=E34G^+]]T>X'WG/K!0)?:8+ZP.#%`?/>=
+M)4#0DN-67PM2DW"D^,*N)O$KNC6W=.[U6LCGT-](</Q&IN&M'#1H*\#E9MO^
+MDXLF'KYA_6KN9[G&T9_:HKR6+_1-A@R,,?&%GG[9B;.HRQ@!L;5PH!-I4O%B
+M(Z^0SXZ-QBGB!2#7QTMU.CL.'/V@_(WI.`K0M&.L],/:P!\RC@S14O0UVU&0
+MKJN6U7SREZ*!5I13:[4+ZHT,VZD:@!TGS7;[PVQBL!_-)C+<W5?MXV%H:C77
+M6MB5US5O&,`.2$TPWP8BM_(PVSX<^+&SKYU;0&[QAN8;ZZB*K0,_W#0ZJ%BS
+MI%"O0,9I<&'1D_(8_^2_,KI,@S+(HDHAI4R$O>V<I@+)N,P%9W&[:ZRT&V-$
+M1(YJ9#P?L>WL1:D*]9J47F<**0LS3[F'1KXV!P8Z+^^1J09A,M7U4N)D9DCY
+M>IM"V*NW-:VB(L#'?D0$?@J!EZ/MK+)KSK,!RS^<G8Z,$\9^ODA)QX)$PQ\X
+M7O\5FAR=)(_Y/6H($"3K&K$7+B#VSM]N2<WXAC1"U[?YF6UW%T)A*EUB1R>G
+M"IJK'?.[1XXR+*W\"W>EQ$-E=!PF/Y"6Q_SQ*8%BAM2F!P)JL)0"NG-DQ+/N
+M)MZR1=$N3;"N>ISLJ,!Q&+%-#EBWV2EKF5U%SY$7B0OW?FGB)[.H4!=\T/HG
+MGXP<N!>M8I2;:+@C]MFTN4NZ[;S"^NDMY!N@]%*GE7@QX,W0VO`$DGLVZ.'*
+MOV_/*,[Q#$9A,2PXSEMIPW$.NM60I'`G'BS!.85#UPN+W[41[7-G+Z.%HV>,
+M[.=#4XU*8F_0;USPCP?15-W`'#3IPE:$5J^%,EP<2I]^RW:4G!88VP'$D`PW
+M&W;/M87]\`,'US%NWFA6N`%:_Z58J'MU.\HBJY^$6^C/188>S2ONO=BFIU'B
+M1\76?<PZ1&K6JM4<4@"YEA?^#KI'2GEG9_/+"(,45B/XJ-(]J]PDR9U]:L?F
+M/OFDR2J8,A?CX;L9;)D$4FNL\L/>@RF6)\,EF^R8ED982Y.=_XX>`B'&@"+@
+M[;*`/M\7\5`*-,M&1VP]?,F93;L+!?P]2.SV59&3OM&H`+FL14H'F6Q&T\<7
+M@).B)SSBDHYQY.BZ!<:I6+.9^*\+J/OV@UD[\0YU!5Z/B5;&(26([R6F6[N8
+M7P3LY;<%"5-"'*Y;PYTS-8V`SMIK!V2%`(85).TBG:C+&7V7B,TV80[Z@"J[
+M3PMCQNV!\D18A3\`L&3V%\1!_[59RGVY7YK4&8,;53YA_DQIG39*%!_EZK5[
+MZH4A/O'I"I7+;@XKH-3VE'%IPJN4X%I=]#9>_X4P_+WR5<O\(\D,ZC"WUGE#
+M!]=T*B9)!KK#SX3G)XHQ[VE[G@];M"1<X/%\/Z&F&!`:O-^/9>/AT2DJ6.BU
+MAN2TV0Z#A&1F8P$HU+QG]B8+8])+C;I9RB"[*O+<6EQVUMO>E%?W61MKGE"H
+MTHH@[WJ%F3/+7O^_A2U,^*@Q[?4U0DLY:PNKK>K[H$E.%*2HFBC8+S'V#I=;
+M>I%9CQ:9B]FX,FCQV'MPS4B?,7(15R-)&R)]VPWJ@>K]$QDWB2#X9#1])KW]
+M>,4(DFJ(X2V<^>U$\[U5YQC@+GZ34WD&>;2FDL3JT;!PO.%62^T;?K=XT4<D
+MA.].J,LJGN^KM2QV*LFR_GRYYTZ7!^OC)-;-`E&19\@'K)3<(]6K0G_\U=E1
+M(\7GD[XSN0H6P079`.SH1EK,IH]H*UI9'E1PUYCD(NW,!^^AE_JT]*XJQ4->
+M(3A69J?02H31=[["IH'UJ<GE]Z(*C@.Z_`XL`=-D.PO`+J4PXN[(S;>8C7!]
+MXG$\CO?727&8[FEJO6BY5^K;-]3NI60;/6>&KY\HD;TS03MJFY/ZJ,HP'['G
+M@JCY=,!_7&YU#"<C87!N29/J4A.3QM7/(,XP.AS)\LV7;'^?8-!.8+9M_E1H
+MR?7.!9)JXX&X`%4\/]<_I;92Q>!-TGKE_[S*1&=CO0BK/0W`#]GY,H+F-:S;
+M'=G-'`_'^Q>G+7PA@Z;XM:E_8[G<9NKCMY.3J\OE=L[584Q#GCT5O-_<55?8
+M&>IUCBH-$L;C$7RECH;UZY6$\="<QR$DYI=@Q'9TWBIQV"@TMWGC[`W>9O4.
+M;ES%=S>&FL:V[">3Z9U0S%L6_Q4W7W$,*1G$TWX\YR%"=NU88*17MM-`EJ2=
+M!S@C0&@Y]-SG(63"%W&4>A!=4*#B`Q.SG@WJO]IIE*'9BSA,EE*VO!+B?U&$
+MP10(30E5Y7>ZXX*M&5P_:.\#D"0R5#M95]>X\/LR-GHA0UF*+-^`+JF:&@HQ
+M_'L6KUX1#$-F!:8NI]SE"3IZ_&H+<GV\V:9!RCI$"S]8\/OX!7K$ENA2.X41
+M")(NP6E=[Q6*4B(ZQUZ=BVEC#&X!K:Q$*OQI#"&7-`0S4=*X5%_5RX"5S#K`
+MRJTOX!JK6M"CC5EIO=,_A=.HMLO2-6RM@ZBFH),SY=!<=8B4M-'4;T$4TW/>
+M:GXO#-CNH&F@C5<BVE?;ZDN;T1PG[%\#I_,`+$IRSIL9WAWA1C+;`D?6!C`*
+M,)'JZ(RY"=9L?Z%/'BWKLFS&AGFQ-=H'`8!-D_W2>$:?3Z+PO<9-LTJ/M57*
+ME.^Z#?H-B9E9)3KP:.@):/ZRIC5T=L!KN/!X*)<%A*G^%';?FI4MP'3GRGIW
+M+Y6/+S+!??P(6!39!?,Z.)J#II9GY'*J^G'XJ@!/J(O!C$%'?3,A@GX#$KW?
+M,D=/2"W9&[3\%^C,EL'R[`AT(E3(OM@7E"]"5";J9,R>JECFI]8CIU<EW_T"
+M,JZ7RW6YR0,V+BV&HS87-F>`H_`O3'31&W$S4W9-/U,J^!33Z4WU,:_Y3QUK
+MT)%A%"DQ)H[<],??J9O-_I*?8MJUV&*)PY;?5="%AMJUU>.+Z@!>)ZM3N59H
+MNH7-;7X(!D4(+K\:/2^`V).DZ]NV?#[:*5)[/G2Z;40\#?%^^Q%]?(&4U,J%
+M4/KW1):YRSQO2H!HRU1YOW+CDCYPD/8R:]B2@)SSUY__OGGN4;D#GX6*UM.N
+MAC*MXK3AW?@_60D;43.&.AC`*/`F5U`8"0"T=9B@5VL1SH-S5CY[LNRT5]L;
+M'+K;:-#'FHQJOK30^>#<&K'DH!]WM\9;DF!E]C2JW)-8(17@2(7KAFQ8D<V7
+MHFP[@`C?]]9V^^N8!QP;ZL126N"^Y.K5"B6@Y9R=9+FSU6@/P,W]FP`%><%>
+M%52.#9?JQ\9*A`OU(=:SVRI'S.S)40RPS;UDEO\4F6QX[B>T7X!RC[?Y[:%Z
+MU3Q+"XM.9N@3`_$A9^/FPO+$3P,;XW<H"MG3RTLIX8M?`E_PJ4=!TZ:/ZU62
+MUAQ^Y,"R[J7CG'^;NCP!8E)@^I:S-/FZLY[27G<KSO7KWF;_K+4Y)WL/Y2C1
+M,#%4ISJC!3#&;.U+V=]N[JQ-(H,[R^VZ0*S.*L]FNF=`M[T3(BP'O$`D:ID,
+MCPX7Y9RACGGO`PYQE\XNKG?4!IT_&7(14\,#.%FZ!F#>$8?_(-RX08.4;AP>
+M(KXY+I%AQO_0<>A;-)WET]E7&8,:?2Z+J2[$0+SD\IO_F<=5&K%5D./A&-5S
+M#U5*:KNK?G`ZD\>(+=ZJWPT>&QHOZ`X>2A^7)ZE4?XLMF!EW`WZDM.T2OZ8D
+M[B($2EPY<)WP[X@BWYU`^C(JH/,%:A*_,<T(A#L\ACB7\U&OW:S%TUV!^X]O
+MP6J7_Q/CKL>L1*-C(=KDL9'2X]?APTQ7A1:&,'O6*4"#2;WK;YMX+41O,X+#
+M&%TW<T_.F:SC8UV>DN]GL4N%6ZXCNA$H79Q(86LF]QZC4[!\/>TI@VKAGPT4
+MGTNXN!SM%]69(@-$I>**I%&^+O3F<5C9O7=I:QV5<P>>X.V0#CCQ9RJSVZ@%
+M2H>ZJR3?`CHEA"A?=/]#-1[19+C]MT*?^&X*EP#3;LPA.3OIL'=:_JB.MFI@
+M;XKC]W>AC^$=C\(SL!]TG$NKX3K6JU$;@:]L25^=O,HMAPO$DMC@\6V)/X^F
+M=[R?YV`NZV!:/^$:ZYBGEL!]C7S+>.(S+')K35+;?<2V)SJN"96*;I]IZ8A)
+M8M/4U3'HSY#]SH>#Q31<WUQ-O_N<!UX"O/%5HG='!W(G[7]>4-@(LZI.,T/;
+M,].V32O]A5_MO./]Z5'Q`AZ<==K[,P[)N?JD^-R<A]_4@><DPI`$X>)G.D\D
+M'K;CI9"*Y!`S2.Q$?N)95;`\+/?HNXBI2C8H1P@7]N>='&KH)2Y'BH_F>E$:
+M7U5Y4`%6W-\.:<O'AZZ^!7UN!3DOT+KZB_0W-"L"+$4ASO@76FCR`U.!LW<G
+MX+T='K^ZG*+[IK$MQ50:/KCJT+_`\H[>SE$3[]0JM)$GF=',!EHS<+/N!PP^
+MG=[&S3]_DT`B`OO$,=Z3_@'W>0<.;),\<2<IK/T:KT<R]AF'Y?E^WF>D^14%
+MT<@]DZ\0JT;%="#86$S.9%GNRZ&<E`]PVA=M=GN/:)_B7>,#`J8#?,*,:X&I
+MO"[])6*#S-S9T;?)(9V:32Z!!`*UX#B1P&MGIYD!1MX*H92BKUVY&.>OY=O&
+MK:@BA36$H,!Q>RJ$/<2-'_,%*/-L\:<R\WA^\=;;Z%U'T7UZ))_;T$AG[H#F
+M*.:D[CTY4OXP<4;8UO(3L*W&Q">&4]EU,VI;6Y<"U%)_V3YXU[F*QBU.'P1&
+M..HF/*L`QX8L@6(V,S-5D<W'P>KZDN1KW[:D7L,MUE*[1:QOD-=;\-[9^%R5
+M$LOQ?#/8R969?BH)@"*8<QC+".%5R6B1;=#P6'1G=2;[RR_]4!L^N<;FEWH4
+M@<JZRF]W[.<@U+(A+>*^9C-C]].B=BALQR8[XF(9GX,H&I\_0[++0!W*53(#
+M]S6QE[T)HBFE?2)!!QC)#V\I<4.&+CS[<Y4R)4N*-#QV--\?\!2446`%JHH)
+MSE:KG":P:+FK3*TRWW+)?5*PM](QL7Q7QE^_U]<,UTL3DN*Y.*Q/GV![6#*V
+M\8"R5:-B,]8(R#F=OV$TLP`',D+7%W]V]^P'6?^XR1IPOHA\A$KLM&HI>:BQ
+M#7"S/R[1MWESGQE`&*!\6>,K"2AU;)?.M=,;>G_VXT1FE#:#>3/;C[<`!*"]
+MO!\$N'53DY6<L`\)YZ3<4*72H@''J*OA+--*\2C@<.+\9HF0`M03*'YY"E>]
+MX%%`<.<E38#Z/$<JO5PZMM/O@=V!O<;S=(0`S[6"F5-PW4SD'?Q+'UD-/O/=
+M_]E?CI2*APP[@5&(1GHB^8%XI+SY:*D-A@8ZT'DMPJ@ZJ+N\VK1F7<6Y7+,V
+M=DBLW\U=<E6)0G?`GSR<&?:]@W7-]73-328::,YMU`=,<?;XJ]\S<Y2\-M)Q
+MG>J\"A?'M4KVZED\$!&Z:8,C(_*"^]*:39%)Q77E2I2V$E>PR!$7^%A]=&]S
+M*=[Y>O>4@.-B\&P]W45?1*L4?KM$8"=@^.1PT:WM<$!"3,&]K=XE:?F;"5U)
+M)V<+?:LR^'RU7FPD%0OQ:O(;`WM5>?"7;FRM>%9"I=I!H"X?>PVBXV4[TCJO
+M$'^PG+6C-I,)+7NZ6J%.D8]BI&NI@J2GMM2L5='^_I-H"VF/G%2(67^[C9]`
+M$`.#%5",:\X+`H*?#?O$%VZV=\_%B&(J!H6^;E41_K6VFM32]C&7I-Z%FV?:
+M\^!O.\L.]`$9\?.%XZRY);F[Q>D9#;5F1.:^:&D']K4>@\V&WN^L$%S?&#^C
+M[(%#7HJ.5NOTU6@18AW"U-Q*.M$\$<N!,#NK`39)F!OSJHZSK[Y/]BZ@1T$Y
+MVOH`H%^6>))X2-*9S9/.&I`ZC_L![O70LMKV*WX`N^*GJ]G9<S\L0@S5H1F7
+M1?97.?$#E1!Z)6PA5NZZ[*U_=33TM$0]I)L8FQE]68@6'T=[+$KC6:NRI\>E
+MD3B?M:L>T+XAD0B5R3A<\SXMI7HGG6N;+>[O,28F7]&;2@&T"E7DUV^@:*K[
+M&T2/]C>)F"O;RR=HQ>OZ/(4*\]+&.A#.9^4$)56_&C@.-YC7%NJ#U.FW#<,9
+MU49+NB(Q,E%C`"T0D)^[+4/6F3C`&H7X0"8N$=Z!,%?)<81$58!Z@G\I&62F
+MW;2@NDNR7^ZZ\LPE1WR3*,7#E&BD1.^`/+M5<@$B*,&N'S*'R1'-A#,[A=%M
+M5:>F__E60*WP)PR>9M,>W7GH2O`G@K2.U8'3!=DUUFYLH8^0C9QKI0TXZ^F_
+M1U-S^K_70\A04UZVF$4,PF0*DAI=9SJA.'ZS/ZAY&N0W4/ND#@&D<TO(;7.@
+M#::%(@]`,@N_\P,$D76LZP,))D2QT)3,Q17IO]$43<B-A>V??YV0E>ZV%'8W
+M-6[/G(0W9L`&;29!]?]#&,H4GGE\/TC@D1_/2`=/P(Y!7!M#B$X\.!XD$9`=
+MB:3\>?E*;-4)AWH+R):O]%`2(A/4M@M_9XF#%^D;B-%=J3G".GV4R:A8GU76
+MSW2I^AY]<)"9I6'KF0K>M]0^!G;+FZS1[SQSA\>JRWG.8TUG]X\-P/^-[02]
+MBVCC&WE-&?ICHDO%D]T@^U)@,/(H^K$P,M._8A$0=0J*'H9UT3.Z'D+`A6%W
+M,KSZ,V;=,#F#CY*E&#89=]R_Q,EF\S\N4NS6<6'DNBPM^DTZPF+L1S^=X`.'
+MFHYR%Z[A^6].J2;PC,^?S721*XCU60+?WOX-\E#7>WK%Z\MS$ABG5=SFFF7K
+M/?[WYDLAR8*XMH<D8_ND-S:>;LX7SN]'TD68]B)O7>;/-#KR<"=HA)YV/:<O
+M-L'+;D\#YWR;R!U!Y5-;M;&8^3ED?R)X(C)>V\:SJ+8S_A#8:V,?\9^W_G/`
+M5*Y-=52U2"*@4TR5TP`;#%2Z3M"F$B-M`H+V_1!WV%O9#41-C'>A+OQ)3;:N
+M*4:;&"(!B.V1-(X<*0H6F<M..MS6O@]^UGY.K/'0^[$?$QMLL`-)?[MNZ(A1
+M."Y/DR!LF$%+(AW2D$[>_6BI.RF(E&*%ZE:([WC';/$OS.&$HME@9$!F@?@H
+M`1MIA[GE+P.O<;KL,)#:J'JE9V1P^DU!_:MU9]QQPHOV//5\2T!K([5P@P(D
+MJT@9N9HO\/-R-,@JU!K=<2H+<\DK8TI]O),FM\BX6Y3$E[6[<OSDKR4QZB\`
+M%!6SDYVR5E#&U;=&;)'._NC.(J>;!)$A=PG/TH963H=\3O_JX[.B;XQ;QLUG
+M&5,(JE2Q0/O,CP32()\L&IQTBPG.9:6W`*PL1Y@"_Z_E@[D74@#U"S\M]X\L
+M'_PY4J2^=!8(RT;N'#\?RTJ%Q::4-0-*`^>`NR>Z562:+I"D7[+/6CQ>E.?2
+M#IW?:`"''D>%[G5;94G9,#77U=4(BTIG:*.U?Q`DC,=?QG1B/<7E6MQ/2,C;
+M[08G?DJE%1RE$+S%>0R&*C]<\W7K^\IF[=M*X>!(S,3CW&]GXCIU?P,F+.*9
+M(*[F.C_@=\<Z>RZNPY?*2W?\[+X1RW_J$(8'?R_M[:+V=X>@3Q2LYP5(:/&[
+MV1*:E3M6N:.;V%I=#(78.1BCA/[P^.3_W=R:.1J$F"=2LT2]_U#6"YXM=D#L
+M.U8')FGFK8::;)K$^_B8?YJ6+NVZU$NA'G4Z0QS\I:P(C0TR]<NU7-&O5SU!
+M'3D(-RP-H8:@L?D4ENU>WRQ7TY8<;!T_5M\5!\"6NO(KB5LO&ES9VB-N`\,\
+M,(KUM\:0]@O03_&UWYO>>:6*VHI'S>TG,_GUMAB\:<&&!:)1X[):N[@9E)3^
+M%/Z1/T3K^6S0JT4'3PG1N>SE%>D)];*&!Q%S6(OEATA9&2&I_Z-G^`#\]%R?
+M@,)SFL(`/QPH:<8K`R\6=6+/=B]7#NTJ,DP9_,IME!.KFR>U'AQVX-.BJ?P;
+M.8Q([0<`/0@EU8CN(UM6?W[(:FH8,KU:/P2OPTJ&F2RJ!ZBYM@C@0]NIFA,`
+M:6EH6W;1-Q1>@\F774_[O8W8Z$Z.*@1%CQ`/`_K'M^?7,W:!.F]D8"O)CGXS
+MCM2WG!LL!)00MD=TF*/8CD;G?E@=3Y6/SHACY44=KF8+8TDKL3NWLMB$&Y,F
+M>Z;O5`PMDC3V8</)*Y$S4/!16H@TPFP1T1D1K&Y5?5)O002CKQ#+`54NH.FV
+M:=5<#6K2KG/5S['KEZ+(@7RGL7X(O[]^Y%>,X0)7PR`36'H6P7`;;&:,A%#J
+M#+Z@2W27*?_YA<8S8E6JN,ZX1QOE&ZQ@"]2&2%F60919.5'AEA;8#Q&0[Q"T
+M#-%:]35Z6HI01;C5#NS9J+HGK#>H9856L]^I<*(GRXO6.:P+'+93_-=BY'.;
+ME`_VSUTEWF+UH:DTKPP#>^1MSAZ!F.K_B$].DOM<K%DUC*V`Z/Z#&"YHZ\9'
+M#7H8A-=:\.?K(U"\HZM+8"`H+>8;BO0#L^&*9>]VM(@T0O*7H@K;VNG'ZK%?
+MD?W9.76U$F1`F`^M'S>1;#C\N;E\;"B8W;%2<!1#=F`R!XGXBZ#,,,:IG)!]
+MFV(9PC!MTQ7N/0-)^CMP'*FB%]]LZ_K$,P^CCHP)NR[$48]T-4PUT:_RC00>
+MI+I42BZ`*([527G8/!C0FUF%NAIRZ+7.`L@%8&_>!3%0>/@@ICB<3(*[OHF&
+MHO4A://B9J,@Z//U)"G?Q&-8[AM7PXL,JIK-,D;ER$(B89SR4?"LX_>M+1-I
+M_($%_%*F<(FX1@!-H+.Q7"T<,=EE#")H?`2\:]R_9G@K(=5>Y%$JH.\)VJ;;
+M.?<Q=&>Y>H$[=NP3PA!^G59[$8L%4L^G?6N1X8L*F<U;>`8#:Y;M43*TEHZ,
+M4YWU2+K;Y9CC^[_I$]L0H35PR/8TJV#J:WECGQ>OHXIK_>",;A`S>OJT2X"7
+MKN)/32%73;ZVX>YC]XCX0A:7-GVO94V)0G#[61%3DB?`H1X=9CDS<6O:1,:&
+M_X1R:V33`$69*&&GN#D8K5K`O%HA+!0%*)F:*/ZMD-F\$'C0GG?Y3JK;%+`6
+M>X0>)911F4YQ(^;Y32W)"%H01CF@^F)Y5876/QQT(N%U110PFB#\\'-)3E,Z
+M?J(G*2:XS""995"]%W?HYN14\=,E/2@C\A/^9K'23P]"OQE*OO]ZY,_#_*0G
+M"023)FAL=;D#!R0*MV#CNE3^+!A*K*X.>T_4ZW=(MVD22^#'@%Z31=:<3%/W
+M:!,8C6WALTC0>\,`H@*X\'96,79A.0A1&CO;C&1.A@[YA%:QEO?3D^K1O\(<
+MCGOZD.F^>%C\TI[@3F_?SQ[!>BA,"\7,IG/.LJ2_98!H:AQR,1AYH]>/9W^)
+MW,M"S7O(3Z)B/\;W,NH]X!L/-:6/FDQRDRZT"(EI39XK^O0"H:(Z8ZKIA!TX
+MY0BT:L(F*.?J=5(FWM.5+7.6F(>2$N.76+<>,/BZ<1ZD=?T^6N,V*2-_<C2J
+M)H:#BM55F+HLU2&S7MM+9N3/MDGCOH:V#ES0'2I!_UC0R_3A?";NI[\I%R_8
+MS?7?'T6)=/TM.`N/11J<4*E:7>!0+6YH&ZMY_>^O;Q:;36\+5LG.!`>K]]8T
+M8!Q2_(C902?[>G$$M672E5]0WA6[P&X8J1X^=35W?MKZ"2F%AEM."JA1Q,X=
+M1/D0PL#G,<OK@4F5Z3S#_9^UHE6<J3P#EP$%'5`278QE&F**!Z^'(C*U"4HQ
+M4&!!`'BM$:^7F\/A!:3W74:MA:ME-D\+FA7!]F_&Y/)Y="T;@\FZVS$BS.8:
+M2U53L91\Z-S=3-`-F$C-U]5,2RXJ.0;5"Q'DKW%DX;AN35SJF4_Z#V:L.SZ-
+M:#-2*'T9$&P@CF%]*M?C71HM9E[<27L01]8A:,HY$Z8AY[(=V<+P^:'&5*BS
+MDX#2R$^(JBK7T8@029H:DJ6`,W-];%:G)W37*%P0.ZR66Q_KA#]J`5>X7#BG
+M9*I!,"_"<PP[^85:]FZ.H9Q26)(Q]8'_*RV>*8HZ&H$'Y"0:U=[]'+L"<`Y"
+M#+:@XO81E,5.Q12'K(6QA1Q-1Y_PCFLI!]<HWML-Z1Y03`^Q&3>%E3_`>E4[
+MLL\H*7_/=L^R22QK/*:T'$;Q";H./?'6*:W8FK2/VCW(=5N]E`%LD2LPC?XW
+MR?]WOI@N4VU8P&?J?6=AK4V4UARN??A>89(`S#<$"#EI+].$KG0T`N!9)2I3
+MPWR\\/R72!ES7;]4^R7MW]\U-2V'!RX<P%'&F2,K'*KIQA?_5X]"O4^GE\!?
+M[$+EDYE4+5YR@%%"*,3O\9CW.:%8;Y$56@$N,D?3J`2++?VTZ,&I+)2B<9#P
+M+#^6\(:#Y,WR]<XB#,O.V6*@0?`Q&=2BA^^I3V;4P(W>;%D1HN*6"\;'M]C4
+MA,Q0%ZGC'88I0+3/M_KT7NR_+]"TX"&XLR/Q6>B'VMWDNS-L(P;3/X5'ORC8
+M(Y.8M=FJ/37<N6>)QW)3AJV-0=:&?C/]Y:RZ>'BX3L%R]]_%"[TG/4<+V-1/
+MLH^Y)$BXGFE`MPEKUSQO7N+*&>'42YAJ0LNY,$#%TD+?YZTIU/UY@A:_)4Q*
+MM,3#L!2O:L?/+<`,2$0'1VZ]3T3&5:E@Z4-/X#F5>K]%>;Y(S3;G@=LV%)5P
+MX]6F?9=:):W>)%-7>3[<NVV>H9X;T=LR\UH<RQQ'])W9U\6>GL-9K1P\+^C-
+MG)C97J`D>M3WL\I^,6AT&RMZ18PYQM?0,2`GY8U^AP0@9?QJ1)?0</UJI7#=
+M/K<J!/8U<C"30E`7>/XG-?R[^7\/!UBD?]EP.9(E6DN,=?U-A4`AIO\[/\GC
+MB*<A6-(%QK&O:0]KVKZ4AKEW/.6DC(Z,T%"!4YH"GD!*QI]9U.MJ\T2[$.]^
+MN"Y#*E8%3ZK1!6*F,Q%%V;7A'<7,!FZC2VY<WS`$O0\]\5&^;]3XD-4<03G<
+MOA4$Z"A%(2V:6/KRK`@C%&K/)')ZJ'\O;W.53L6KK,*B/)=ZVY/",VC(X:R[
+MPX(F$,W'L<_.*/6<S8X"7$1@&(,S5W"=CLC_.I%Z2.,\PW%RFZ+3N*;T&-A.
+M2<>QR(C<LTSO!`&Z`\'G\D\SPX@;=C2%]CMOOWIV"$4J"OX*N4X!=U22?ZL(
+MF0U`!.TCFG/A@P6!<&(+C\J9K!0;D9LDP;7V%VVP%V"2/7&ZP&8V'%2;/-E&
+MZSSM\GVZ.4LA88GP!*;M+9&]*'87'&&"V=#USRT[&%36@.1NJO.OSG&0%=OQ
+M?+!2-)-`]1EBI+4M]"NS;V1-_`!_6E`+OV5NPAQE%X!L[9`^7`/VXZ?%E;S@
+M;H2Z>EAF[_,OW._I<<^L9&6.GB:059O]CEA#LW490-D_()!YIIEN5IX-GP,D
+M]F/@U416>X*/7DSL2"O<]1#XL9[2VO<!]_=NZXP\F@*13X:6Y(EP[K4PY$-;
+MZUU8TS<!?I0BYRG"MM:+[4'E[-@\S$B1-4<$9/`QQMC=RFK(?;1IBATRRV1N
+MA]Y*9!5E;A]_K+PNE!?F0-P]&#WDQ[&,65%MMMAVH9R!7!I;'(CS^VNZ[T$;
+M9DV##XL:ROD-<1'S[^.O&.I<ZS:9+8W%LX6JR;JS1];3/)*SW)E1R3Y*E.KP
+M(,1.)Z9AA"^YM\<CN,]`*\5:M'"2.=FIA!9*>A2/$?Q]*/\27D#%)Q:/R<:[
+M5^A(7]=/>>6K\R)_3D]XH/BC/AGI!F$G("D^LM]PCOHSXIZ#IO+P'!'1;$,]
+M@P.'KHR\@<H7C@U).&PM(-90FJ>PVY/88)LW,^.Q^O7,FV,9D"6O),!HL-5$
+M5<;NX*G!0SG%Z<"!T(OT,F`E+M9_(`CBEUT+P$*IPS;3&\;H:7VC.T/D9*W"
+M7H<D89M#3F&MUY>,,C896@]'0)^QG`0R5^"`T7W3,L=WHRB$02[2P%_:(F<#
+MSVN;!NZKAK$XOD)D\I90$Y0[T;NM">L1V&^Y<X8E[1KTIQL)8FN]]'H/4V`I
+M1N*ZWI&(8Q%(SQE_B]P;AUKY>A[*\7T75/OBV1+D%9:X'4;"WD1X+7>9.J:=
+M?*J^:K-Q3S"')=XW(1Y<ZK\?"[&7=<@0@&RT[EN_X9*A#)(,S$`*P8>:?^,1
+M5492*U;*_7P?`>/'Y2:/:#)DQ&$R:@<:_Q^+_#2<W^B`K&3.?>59BF(TW(GF
+MO`"]&G'$AQ[-9=AI7U+0&+Y_]ZK6IQWM?09N/8S`-*H[W?;6Y`+KD$8EC?_H
+MJ>ZNDM`*P/XBD^_&!9R&]IHAC,>8E?L*[52\QMIP-X;U(?QBM]6S\4TOSJKV
+M.XM",?]7_.JY3MM,Y.@)OBJ-4OI^E:&0+ZYHB\1I=<`QG/G%B:<^J`;N5-SW
+MO_K'JR@27)-IMM<=:@-9XZK^<XGDT<5'\_M;XJ3RU)KZOTH8'5:$$ZAV:U+%
+MM-'7CX$Z*$IB=Q.M7["OQYB\L;P/^U_G[W6#9'*UV\RUEEC&TWB"FJB23Q[;
+M(NMZWP.(3@]";H:H%;LOE)7>Z^W0@^^I9+V,R;4.IRV=C$':)]:2;!Z-BJJ)
+M?BMUJ`SU@:N7CMBELW,F@P<!6<88\%3D'MB"K.[>]7!$OK27Z(;!V\O&^L[:
+M#%Q]J4#3^39%_SUB8QV^4[>*-S9@]J.?^CV`N"_8#^_P>_3OL6$"-[-*$BV'
+MF\%'#5BC[/\'5>#RQNMG0Y6=28646]QE3MAE5%%9)R.`>>Q7KV>&D"DA$-]8
+MBE$>[PJDGZSA2QC=LV'X_'75]3QP(VYW*3">P$;QSX00(ER2Q=L9HO7:0@S.
+M0/?:_Y(M9B,1'39[V,?/<GM8YL!KK'MCP@.MJDIME-?FJ3I14G=1O5SQ<4;E
+M`/FC&O>+!Y^#,.3/"0$U.4_ZYE/%B<Z/+RJ^,!DR==NR^G.`KB)9`S^N\NXR
+MFNVFA$T_3Y"?JP""PH1US3X9D#8K9TKVMVL??$4:_?2%4ODOTK1;>=76B4U(
+M/+ZL4Q!O_W=9;8/J+,)8H#77SG5RYIM]E]!BO<BB5G3=:GC_H?`%"4IHW\4?
+M'YXG6DZ?9Y'JP,'8Q;':M9&<^RWDQUO6^>\U2<A"R]@HMNA3P!=4-,GF5`,`
+MN]0!D^)#N#Z_T"/O:82K/PN7\$-<=)NC0,XM/IEQ,'Q9L&*>4*]=(+W"?LMH
+MJB&S]4G3Q"OJ7NG91"#^48"80WX#4GP&"LZ-F5H_GA#8YSG&NC*U5(DKQQSS
+MHWU'&?_8=YZ5[M2Q_%T:]V&J6L<72@U)+?6WRY(.@%3&E$;,TC'@AXU&6=GB
+MP_M\R'<)=.G(MC.MFL2\R'W<*T:F@SPTN47L+:\\[GQB5I^S4_,"F-Z.%7]S
+MR;[H;S)\3LQ335<KTMEJ7DT.>4*DYD]@;JJ1U&<NB['@]^G;>!=A@9M(>PSY
+M+[V?#6S259;<O9^AV:7_&ULGF-G/A>X&%"\<D8!"WV"[)T896N^5YH>3$+%9
+MZ_AG[OJI7%&0X3S0-"+CL=`?P.$$Y4@V&-S5/9L!%=1<4RU0H@82P=%\;C?$
+ML&?0X?SZ3H-ZEE*=2BC89\(7E'?,#JNG//S]FF28+_+D',>+^-%C,0W1U%B@
+MJO:YY`HX,CVO?[3$`HBIKLEM$N(0]5WJ[1!5-8>AI4]BEAX.N?7@E2-ZCW9W
+M:Q6"(5V#$_.P1E71HC/%N/W$/FQ&L/TV^I29C#):16>(U*Z-2=S!X5V#S#)M
+M=]AUV.JD/A/1<Y&;61UAUKU/S,'J,X:5+?7XF>NG;IDJ^Y9F8^D\U"0W+3\8
+M=U@[(L-6A=_,T-B3/#ZR^U.Z//=-++B.,%0"*-UZVCIX6H6M+HF7P-'3!AUV
+M>MVVU\]!B08.4`EI0B;KV$J)_?S2!!84FU,\EK]3A3&A-E';93K/+,\?2<HR
+MA,X\)[/0`;[[@!*\Y-S-`+_)EI49K2Q(?KW(#3YU%@.2IPR6*7*[^$ZCMKS8
+M^.V5Q`5Z>E9V/S\Y:YFW=Q+6X#83`MDIQD47L9B6E&,.UPC`5B77/`#!VCU%
+M#Z[%CT>:X!\]0(0--I9N=-1O@SATO&MU<;SOH7<EK3Q2##)J+-<6U@HN&.O<
+MT;?U\:WM2W&7@U(/(QZ*E5RZRG?UARY77S'G=\0H92RV%.>CRTVVK_=^WXP!
+MRP3,W97]]HK;NQ41=WW69MNOZ_-0Q<)IGZ70X<;9`G=MW394R>F<CB9:'@.)
+M!6'K77&:\)0]]-\ED4"_,DI/>SM";NW?>MY]J8H3X!VFXT55S82OX+LV-%HE
+M.Z$?3/`U<,40/H/,)D<_%%_C"$<KQRI1-(%P>.<Y2ZCY(S\>_P\1&*6^YSM#
+ME2J7P#&P32@8JPFIZ2*J?H=>9Z.8*0R5`"P0*J%F/J'[ZYKYF2\;7+RZQ4!Z
+MQQ"M+:G9(C!&/28NBJ[N'^YX0*Y(`D/&E(8A0"9R1>PN?GF_FQKVDVC:YM36
+ML%D7%L>E_4E>IH"UX19-[>],397X[M3:O@;M/\E*93&D"NDO&5<<25;U]H7-
+MZ\.Z&*D\]5I;`$\@;_#58#7&*88$E^KA4Q6GC2BXD]-J_"%3'VX29$<:Y-4]
+M[NO-O)G>CN?\QSMZ*VQ.E3GXI@Q(.ZOR\&]11H1-KWJ6*I;5(@YMCA(=+Q'_
+MF$)`>DV=DCT]K$A2T=[F>B[G`_+KOJ,/Z#[P&>E#F4"5X>[:_XBWF'V]/4O*
+M2@^)_,D:PUD.&4_+NOQM;N!_PINQM.^2+H`$=.[R#._#5O4C!C&;(Z^:>TAR
+M[X"(*\([;$)0TX(G^O$HHZ@,D)5[&#4VOI)`&_2L.&^")B=IQEL&,VD.&)_7
+M1'^OPJX-KU#ZGS):`[-.-76*M()-E/+QGEE4T[M54*N_[KH;F4:#P6O1B>:?
+M[XPH>QQQ*G:BJ7W=IN2^.GC]LB*%UR(M$6M#/6]*H?[0G+F6#:ZQ6HYSKTP`
+M_+/INF4H2=@K3U3:D3=U)U*=CEZ!C&K_Z*OFEJHB0T:`Z=413)X]C^G%H$RV
+MF1]8/EE1A%:&C#K(W:N\("UOUW<E45EF=.)#65$VP@?CB"?GE-JH\#E:[?YS
+MF+KD+R@??-Z.?I_.TQUX!7>C+E(6->9P=*!EV!\$EX[U1B:)A'ETN-QCXT8Z
+M<::?N!1:B$E>IA,(NZOO7!E1<,P6W$Q5V_I-_-W2V^!Q3*EW2GM/)G5"3TGE
+M'B.=,<=TCX[:O"ZLVJ%[H*P1%=\L0\_\/%8V)PW.\'B6S#=YX/=SB:/^&PWC
+M3SALM\!0>T#'AE);\@L%+FC0X]7/O.+T>8ZO(U&\CP+51B]7\^\"^!*\5CYV
+MU[=>G[Q5\F`&DM;U$ZHU4I")J,+\"M@+?IR:C0Q1H%3+P9`<=147H!>RH4-V
+M,9Z9>R'BA$2>4755]WH:!0CTPJYUKB.^^6B%FI8'M>V.W*R$9_A/)?:1P^#D
+MJZP*H'7NP(,NR6!)L?#1X&F5%I9<*)8M@#SD*`/-RT5CFT9(]#G")L7\:"?'
+M,Z-+:B*3]>(;8_25[?*?X-/XC,`MSLTCC_^^I]P=U$A#2:P$*T'?SVR*984E
+MX+AJ3%'P.J&`VA/,0UG2O@D%A:3YTBV&6R89.H[.R=<#T;HX%;X]X05X2Z>2
+M-Z2PX0!G40ZW&,V,/'YI+$XDVYF^[2LDZ"IT*SR&:]TF91#`0T3/=E3Q.;KU
+MX`4(+3+=$=4SCJ-=MZ[Q<LL=D8U^3+NM7\6Z[TR4I^UTRK;_<_FFWO&-1;)/
+M!V3X-41U"7URG[6HR&DH+@P3:XN!"4'>28,0$LEZ:`.VX,Y4PRZIL(9](P?.
+M;,:*OB1V+0-8%QG;59'*_)/BUD\-984@VSK@*.=],^VDB51GI8K1XV$,'",E
+MN!(%S7\$E@P_"**.6H^WH$C%6<^LO`U>4O?'06(0-BF_XBA-]3=M?Q;'>XI=
+MT8ZW'Q<E(;/<YU.:]\[CJ2N-_"2_3<94>QT219:&QP?3C`!IZ>X$607%?[X3
+MDO?J&:`_;!7V$&*W3A_.2"7?(Z/)P_E)!E7&>>'!L):D%:%.D4IS.9TWS[T<
+M==877;;=#`&LKU*-C^02UL\>B]W#MDLU^JCG&E79';9F4*_R5KE,O6X/DRD-
+M61U2J;<EWRM\?*VPVU)^?VSYAO3;9UFQM5U>LAZ-/7JVW<)9B0&DF_TF'8=J
+M-Q,03T\NJ7F;FRP-SW_>4KXXBI)UY?KJ)W$]O^./<=%"+M$*I,[!+U$`,TLH
+M6O!_-68GK9SI3E^NS&5[5V44A.IFD%[-DFI^;Y$%^NL,T_"6,B>>1^48P<[M
+M67]5N&&/<EC[//-VMXE=4R`]G#]7*U+-=`DV"$TQ0]?;\?1V7O,B+T8#RM;I
+MUG_"#]#4"_*-T:'-MA0U>H=!&6UNXSQ3LJ0PKI6H0QQV6RGGA1,K3F4^Z;R+
+M`R3^GQL,2(I=#&5&C2<$G>XUF;7[*-'IN^*"20@,><_IA0)GB0>%_)V2?O(+
+MEE*GI*C<*=]B:]"@\KZPM#PW=T-I0!GV6'[KOE,(?^:KEL#K%>-0TZ?(8\S^
+M4\;T$=$C@)S347:@U'=V,=9_?O<+L2MQA^QR]5O0M@K)%*!E4IMCTSC8I_`\
+MT8+1#A7D2NF8]@#-4`@$_STA++KVA&0J$5Q0JWOR'N"%0H7?((U%57NUP.K2
+MSO:[V?^D&1KURD7`D/%,=)8<GGDLH(8C<^KR6"+@X\<T`5'Z437"%JS.8G$E
+M]WFUC*#-\.TRG@,/N5JS"TT?E-?ZYFO;*\^KZPI_1WBN7$#,L"$8\'M=CLH1
+M&TB]<(,TZ53]RU-&K,*3/B]\93"8'7PIZP3IY*#HK!JU(6G'E=>,&>WN3_5)
+M#AD7[GLC1,K^D!/M`XEE(A=3L@"W1%73&-&ES479KP>VW(C1^M!++O'IX)'6
+M&OY#7S<4)6C8D=_[/`\W,PYLTOQ3[KA.\O(</-9C^7R>G4H-IRC4,0T0:6E7
+MI\LB!%?,4$A`J?B_)NC(6))1<W+_L[]JZX!82VSJ,LHD/UQNUJ-K;XG^-'2+
+MQ^QI>%T0?CSJ>=[2UO[W^8H&S?AWXW>`!>9`AT9'%^]%5IM9D-Z=@?`@6<Z)
+M^SEX<)1"0UU86*[U0?49I2_T%58LB(&2>#ZXNL\-6(2>==F[K-./?J^%2_;Y
+M=H*1^H9G+4%I_O@4)WLB4=+1/&7J,1B6A/G^T*3]X4FL39G9:RX*.VZNR#_=
+M#,:^Y375/*P&V'*EG.[']0L9.0[O-3:U\3HXMGR//42GUWF(+8A5V&W![=B$
+M8QR&C:0^2VC?C;.3RSZUFI7\#QQ-W[6CQ,K2A@61L)1DIS30NX(_-PM%J/+^
+MFCX'29+@#A%:"#.F:.8L+`EQ9P#4@60!@?0TU*R/`S=*UM7W3#FT4H4:2:'F
+M\+>^SE,CN.+*SUQ;.]W\UW:T<X.KRI9W[I=PU#=^N<@/##&\L871C\H&A7]4
+MGI^47IRV_88!LJ]3>R#+M;;V&O.2E_1YU*%CP:J`,2RF-<QS$>'=.?"85EV.
+M;PU0)O,]UC_QW);MF7<XAITV+.K[,%9[8K&;Q&\CXJ.\-JQ^#2D/FZ%);>DT
+MI3]2$3LZ69\+6\XMZ$453&LR++,D!C_-K9_$Q'/IMRW#`W&^K+X'/<Y!V^%>
+M%$[1I!(5M/\J$@P';[3*6(KA?LLTS:##K;I22!<D%S7_O_VT__I/8Q,R>0(<
+M4-@'@R@IH'OGP6*RX6:V=U28I,_*K6E7D5[/<>=_(WCU3(L<4$9`IR"^Y-R2
+M-JW;.L3B$Q/-"?HOA-_O5G>2`&I<T%MF_B1I'\=]32L'!NS2>;;\(AXSXRH.
+M)9W4;08ZN'%:>5SKLG&L2U7Z47-18%+JN!-\::C.]\#?<X+R"\DU.*YGU8AW
+MA1U!.WP;J15_F()H@H-0BZL29R(^#IHK[PA@F%-.'QLEB']G,?1O4Y'0K5OU
+MNIDE\3D2'JU_XI.$B-?AJHVCXJ,G.:JFP;<X7S.W(DB9B#O&<@!+M)YJ%KA`
+M8TS"!`6R#L!+G;&;$P;'UK"T2I%JQ]W[#_K&-$1;C'9;%,3Q>,_MH35$CY.%
+M*&*(//=200`CMV3NEMH&!><?E+QH#>)A;X=K=V!9@P[;&`4FCZ2,>XKLW#EA
+M*NQLY5>!5`5N@>@810+8/JFU^VI4BZ<L&P.TRY)"S:,,J;VK,]Y\WX7]<,5?
+M";5.+[B:DYA3;8_5"KS)3+`&ROM0_A31J4[12!\K@XJS=YM6*^B,1NVWT$Q[
+M2[260TC6.\I7[_+G9*W-]Q5<A/'A++N0;$$?+YS72J:7'.5$'*05$:'##G4V
+M(,B@T?9V(MH:)>9H+N_Z!)TDO%M7NKC*]*Q;>A2.WQ1I-"R(K*I0OQD/(-3%
+MWU#L9*O*\D<?Z<]KIB*]#[";0XOB:B.N<_\\-U(Q\.-H]^C]U#M0JRR5D:>]
+M-T7_"(A\^`^6#@E403_=U\?E$X6M^((0&'TD0-+C2%-%:>%0SN[FS5TENQI6
+MXV?-#6.5%J*F#TYRUF$Q<F2RZ.3US?6`*^JSD:=32A9K4(>;C@&+`AJX['2V
+M_RO7#9'C6H6%_Z[D$&,!*PI5]$KB$UGWPB=-%-3FC^SX1L)IO$+4FB.'1K*;
+M]27D%Y^C`LEJ@C^UKB7FD/33GJZBE9INV6V0DDHL:WCW%"Y&5$S\+T@HKS^+
+MCN9_UWKPR:#8N:"A]9D]*48]>!BH^N6.A$,L>OWVM3G_Y$O7E=E0L0V=K!H/
+MVXD)=:?*,+[+GSFCA/P*N_.H6`1`]MFP_W*KAR2[A2O(SPNIV.]L_^(MD`TP
+MLPHS=*3^)%YR6SS,K"M#5RF4:K^(;NPW1B*8;5G#L!@G91X&"]1(%ZDFYF\I
+M<;Q6#.Z61GT/M)1O(A8^@K^Q1#ODZC.FZSF-)=1MFR82S3*@%M!-I**63;YN
+M;A7LBZWQYFGD&_OU]ISU<$QYHP5IIPG;!.,23)-C0BX-,U7AU?;>F`W87AHH
+M-''[GNK<^O@IHE7:A:I<YO#CRJ`G#(+5!X62_6<=T[0]PQTW'<BTYOCGQN;(
+MF7;TW[??G5X[M_QYR`ZD$G72AKZASKUG4\'1!SKL']GD+;9P9$A2+3+!\PH3
+MY]TARBW#!L#O\N\5!W2YZ2\:^=?)')\>H`I+OT'I=!?=]Y1+EDXX@[*1MM<9
+MEJJ/W(!"?WG1ZM'ZX1A?M)EZ(8C$7K?5[8#5L"@CY'01'UWU9%4G0AEJNMDA
+M`2C:C(PO,W-I`+6B8;6ESV)R_DQ.@\AA.1Z$]]CHE;RR%UBZ"A$H-E98]H,<
+MI0L79NFGV<Z2#\+PR^NHT#G7?/RTDT%[236?#`^HDB3>!\2T)&0T\49H,M7R
+M9);_V<'P;B7[4AR7#2'-Y-[KXKLHA933U!C5S..PCDI)T^2NHQ=(\72`4L?C
+M3-YG;N<S7+/61Z?!HQJA_M^;VW1O0\D#<N^_<:(HLEAS&I;WH<=_O2#)U0TE
+M:*F3F[R^BSV%72LBMD"C5C.`H>J-1.>]X);:+B^Q<91>,3PDIBPKE0T:$#5T
+M"]!R),7/L?><<!=$IG#8U!YV@S>&Y]<1\%?Q8CZ$,[OZMD_">%F'&M"]].6O
+M*8XME=?8H[+/B-^RF#CW'7D;`;RCO+4C?L4:R@]<?O$14+Z#TCCN\<`(!M6J
+M[A/YY]3B&UOKBD]CMRG(A2G"0+[IU`GR<%EW-H,8YQG>/H`*:+?MC$*!QSO4
+MJ]4;:KCJ^3TBP_B=3^NJ@0O8M^=ZTU72'.6HWO.N<=#6M'5[*W1RM*1+/_MX
+MLM<3D2;/W&ER#\T#3IINR*+U6RN32-V)Y8*N+=&?0)P#I6A]*^B7^.&B!$<#
+MC'!`2:\@T)I@EAM2<I?430)^HH9&U7]X!'87UY3,JY7PX"$%1N9Z^SUV#C?J
+M]B9E(>!=QWRFSZ1Y/MQXSA-0]69?T6BH$Z>=J@#.XE('O(IO5KEF`@-+4U1%
+M9$Z\'1[V>K=4CZVH`-J^_<'/,>R%=R$%P7!X?N%]=[2C?Y)<9TVRPLO"$^C/
+M<W8D_QAPHF1Q@GJYE%B02:>@<VZGVGD#6"P<RL8CL!CN4M(LXZUW!'%W,W)'
+M>W>T^660O8_WSRQ*-_S`.+1`V2TRX"'40L$SC8Q+)K1V0DJB02Q(E_X?#=/*
+M#4JRL*[FQ'I.T$;Z"T[&%#2R5*R3<X4UT%+C1/2,X1>T+)NEL26E:('EQL-'
+MUK7GT38>SG7-4N$J?Q.JDYLG%7=/6(&GWN#=:EV6V/793#L53=O9;8B&+3!U
+M,@?6`KZ<)!=R8^M!V?L7ZYNH&I;&6F=^AE8:E?+G:)04;**Q2T9($Q_1=M]O
+MA2.GRO:HZ]V&Y#HX2FFS.M&R).QM@:AY15VU[7+T).Q5USU:5WCXL=]+T>[0
+M`1]Y^I_WB5.+L'G>HV\7:EQ*0L/7A^"+;$$L:&I\#9+H4#9NFH6#4S?$E;D'
+MWBJQ)WOTD;[C7SFEB:PPN9K7J0:L@XM,A3=8)4HJ71069[^>=RO2%1U7B*,1
+M:5"W))L5?MZ7'=K9:CO&#FT6:]5$#(286L,BB:>8H\&`+&[!B;^HFVD$$C8[
+M<CYY?*1*GB;R8#MX$^E;4K/"^W17]2UCKD\_ES&79B1GPIJ)=50X[,@4AW6\
+MHMRU_BM]D=GJ/A8H`,S&"S&\>GQUHD3=5>?ZL>VLXSHS0/G^B\['4D4>7G(J
+M%H:P65E?%++?3[>GLY+1BU?*N9K4%9-'B5(WFPPA>5$D5OXA#8*9V&0>I7*S
+M>D/%*<+&56+FQR_%*KF+&.A5'[]#GS;IC]3>7&\-*0UXI;8A7F%C)@7V4@1Y
+M>TO>;;S]XLK<5(]ER"GE?_]O@_[\`P';T]9IAV<)6":V?_R7K2WS9^5GS"L1
+MF8'<=ND@&&L"N"AV5GH]$!LHI0,,A'/"X<8Q(.X4VBE.\ZND(AS5`=2,.<X?
+M3-,Q'V<F^"X)7]%QSU>C=QS@\>/;ZE6!5C@J-.Y#:IO]CRYE?/8BG%7RA<4E
+M?$=5S/TOL>I8WU97_Q:<80;N=TE&!_1('AEDYZ]]8.>0ME',W_-]%\]0)E[5
+M;R7=IN[X'S=U?/CFYT^$.K:BP5\^]ZFD.2GK4?621PAQEU<#W$X8K`-S$(=K
+MMPLM$6YQQPM#'MI@=DQA8%W(EKH[Q33Y/<'_D##4E]NM(+5+G743?KV8+0M:
+M#*0*Q<&4-36V/;]C\N!)L<:E2&8U<!,]-M;1"->&[95VK8[#VF&T?.NCA;R1
+M6'E*&5QY@2Y`(N=1=2>$A_V^/XB1^96J$I$_#U5?Z#7'*]$=L34S=@Z+IVNO
+M=!)=35;RQ,!\UI9;2"=F0_CYD9,M=(Q2$0\42:*(];-3=\BCPD0SQT*X7?M&
+MNERJ[B!-V.)DH<@;&;UV1J-S/U@I.[$+IUTI,:9_S7`.%FN3.NN.K]&[Y=SH
+MUAI+W5'FU\]B;NX\^C$,W0"I">O)(C+&,!T>69\0/7>4V(Q_">.`\LRKP;8@
+M#'W+I%/I#D,_+9EN5^M1;YQ+O(F!!<H6/OK=/5[WZMA],3C@[#316X:$2RX9
+MKXAD=:!%`%A&T>>7@IB('Q,/-?"FZ9!+>!LS\15Y>1NHJE0P;ZTK]%7/J`Q$
+M1GC2-:+XH=%3F`'G5ID1N`<IS8$GA5]VKP\*.TE9!(SLX=GRF`!OU\JH<'>"
+MJ4RRT!H6LAT_B8(K/,,S5?9F@\TXSIYQ81]6MF3ECT+7;Q0O*GT)$#\A@6KP
+M:Y.T>QSKN0#%+CM1!XY\U&J<A27`BI`]4'R$6-VDR2M(J0S8,5-(89--Y.K_
+MM,4\)J"XO=5([-=@8@H$275^_WA<6:ZXW>[.]EL&V?SG0UCV8M-DPV$3/6V]
+M[*=//@&:K&#_DF#0P--LS@9UR0F91KW+4F/U"1R05!+^:2/F0E+\^XOVLQE"
+MD+OWI5$WVM?]Q,]V1YB-%3Y-@[WE"<WGF:[@!'RQ<$..RRK[X+$GVOL#A3\2
+M=6,7!(M6N\]PT$*EN2:@W`-L3GS&(:KA?K7@@_:4&1K/JG\$]`=1D^#VM^(^
+MAM3Z%\7XH%=AMVZV0*\3/P*-S#X\>D6GV8/$E*::[/:T1HV8V*_8A`M0ZJ+J
+M*+I"MR4E-XFND"[A3L6I,T&S-',WO]\3>LR#UX9MQ^;FI7J0&>-*235%GI15
+M($,;68<1\G$/?$<#)LC:KU&BI-Y[`5+MLK\#Z0GNRWZZ42M6^N@0P-/WC2"9
+M_D08,[P.7\(`[4**=C&:%(>";1PFR2>E[$90'@Q9<ISW:#2\0=W8O^S/A:W;
+MW#AM*QV>T-4K/;DVQ2=J+4MC_/6G]F0L^\8>1P9ZRQK%]V&)F,->_0_QT:9R
+MGVLYZ/)!(4J%ZU5N)"]+2Y`&*HCNHV;`Q[CN?8$K^F6-/4'><(Z7\O,**C>E
+MR(^D(Q[-X-ON(KQ0A&A3R8];,?]`J9(SXG%`&:^)/\:\J3D9:A+.V4E0':Z\
+ML\N].)8SRSN43%/!R0+4GD1M9F4U"K<6XZ$RK\G+I''Z*;"XY3Z]W+<PW6!K
+M-.M6'5SCU-93L?+(=9U?]5&II2!!/%3Z@^%%GT9,8FY\DR0V3L`<3:[_:AOP
+M038/<$,&">Y/GY*$9"^=.1B'5PZDJ;]O,L!;"<AH<\KU_$HT%ZR\LQ((866M
+MGH:*.]J8P^X9!5'?-[$TJTOI;&C$`DVJAK@G'+N<,42^M$B;SC1WQPNW#\]?
+M"-CTIWH/V""G;S2.0>-*^TY=4=BS?7"-HE'6(#8W1)Z:M?`I8#=R\"X4J:62
+M$%$A._[#8?`/.PZ"%D^=H;<HL-VDU%:I`\0)BE_P^D:;@[;QMA?]LNKR_RN`
+M\HSN3PU/C86<K*FT;U5,)`\?+)(YJIHIRO2#YN6KENY0`C#S9?5.7T47.%PH
+MSCB'+V>(?8WP=EC-Q<PDRV-(]\R&KF)346=7_"-6O_4F0F/X]MTT@G=S^;[0
+MYI/7;<"B[4JF7%SFFC15$Z3'806:J7EU'ONM.!T\>[-8LSNQ":WO)]J?)0J(
+MMQ*;=0EA`4.,;(BGA05B1!N<;[7?H0=H1ZUAJ=9][8?[FZ)L)`G..4D@!O+S
+M9#+NC&JWAZAEI>^)ZP/[+^,S6PC&T)7:AKRLB_HWC<VO'QEZ[C<QC;D`DXY+
+M"N@$OF-"'Q9Y*%JR3S':<B1OJ%W$P(<6\A6Q>&LL"L`'9*8]^.9ER4]0*:G9
+M">Q[@UK"<-20^!PM8#35I6TTK/L4P2U]W#XCE'4(=4I!!KV"N\;$YUMOF*$/
+M#!_F9AM&U(<J!@0+WT+61#C9WIQ&#YQM`I+<Y$$H:@M>6GU^@[_8JTHH7:\D
+MS5CK*30D;)!K-/P"Y7S+1-15EUCH(]7M^_&?/\R:&[NY1Z)_8?UDCK8P6B&6
+MXNEVC@@%DD@V<[BL#S"_]E5+31CM6DBF)*XH%D7K:76^*AK.;\FO]$1T-%75
+M2#A&KPR98IME.M37+16!EYM(B'N5A]ML)3!?:9G@G"-J]R^,O89#VS"XQT1V
+MGRPC,PEO7K&5TCK24,QA^'XIB3BR!$;&Z.':.'JN0/@]^1@99Y#K'LR9`F9\
+M=L:^U/SQY=FZE\"3P;1Q8<BL"-LS+13\"H*-U*/,F*3?AP92/]I4'W?EV:UG
+MOK/9>W2=4YF+T"*/]RCP^R`;WHB@YGK&8-C."W?L"42CJZPG?^,`N"EVE#JQ
+M<*E+]Q@D=6Q&$*-55OK<$EN'XFEHPLVB+;%_H(M4HTCR\2@)<[2^S58,9\CZ
+M/Q%PC]=-3P?/.A5L'VZB:TGL+*/K)F3%<ZECNF\E3'MN8D^85-6;=N*7.K\3
+MJ*&P*.L88A7"#X\-LKX5>-?Q4*')6,5X:5UJ4G>52,\5^-,)ZFD2JA!\#/"U
+MA>%EIW:.N@9<'V)[<HOE2/,QQ(Y8?CNWOX$@!?\-0J>LF3Z7PJ.^ON*PB4$!
+M+N=DD=+[F\AV7E=@,Z_#/CJ40SW>.[?/Z8R41&=2),4=/+[\%M6NVUF!F(@O
+MLUXI>'@QO\.S])+XD9#R*K5V2CE?8N</<+7O:Y<_\IR!%$>S!'_B+QP5XVBV
+MG10N"G:,_%S^'KE<;"!V%0:=UK@I3TZBM6FZC&N>3U\=GYUWTW(1NK>7?(<I
+M-HES?L?;.BI^][,?C??];+:8S5$U2R0:,Z(LN/RX!=DWDSI4X72H/)B$INI'
+M8FL:LK2?%K^5%>/.W_(:_GT*3J$DB2\O!KZ26]+8=_'I,:-0Q;O!C-$'I)#?
+MT/?EU_RZK+NDX!G.Z40,\9:>R3X^4"!Y?ZRQO8#!ZSX)1;+(9AQ&8GFF9YNA
+M!6$1KKDM6&?)O\P8[P(`L1&A_(4ZEV`?'U40U79.4_1GS\Y0V,O+Y*P5'BST
+MR@Z/NT]6@1L+4B4NR_:>L`&EWR+U7IO=532Y$ZD(@GEED@UWW]I^A9X.;8E4
+MV>6OS>):>]?:R]DY\;7=4\\V\^J8-H@_J;FEBMV?`8<E27)&.U<9]351.>"0
+M@Z@R3WC-3^\0+_+B[<+C-`RO>/T#8>)CZ,$*!812^J?F.4N9L$9Q#7?<DXJ_
+M8#1"'A1++/.0I4&4&J7+<DB`+Y.5M"$^0<)\M*UW0VP#V('</[&$'M2W*@;K
+M($4,G)<K)M2Y<1O&>VA)W>L\O.P#OUR_L=R*FL<.DF"`^ZA;>Z((%B@%.Y1)
+M?MCIOY1,)K^"X3N6O1Q6$:T0\:.\:23D?-+AODEYM(SMZ@R5E^[@52N?$XH7
+M>E@!HH$6X@/@H%`X#Z"=%&NB3*Z)M+C^N>;'@,O&FHH5,9EA$X]"J975$6RS
+M/Z(:IVX/C7@R7V9QTOX*%7@=^$*V_#V/\F/8\9GD]IM+[A5OR&P6&P+=/?&^
+MXCYPU(V*?N']7"OZN!*R9G'XX0P(+UT-?!7**!Z8[;X*L!BCB&[LZTC[V0F+
+MA9[42Q3$KY2P/2\X0^82TT<,;*W4`U)FG^$MS+L8#=>6N*VSQ9@GC&,0-H.R
+MW^8SU3X<*^9"\7J'@"FF&\)5/8EN]-TF@SR(AV%V?8-=,:+VVEEPM(\[-N3*
+MF[LT+X8%-0^"PIO%89O>:/Q8KC7$/2A48*N.UD#!>W7_DF*&6U#R]ZF-]#:-
+ML:R<<!1C5X]V3>#>!`572E^LF)*\V/(Y*'9=\"4)J4^N?#P7MRE[AE8$87_K
+M-.')B=$O8R=5A6SBO1,PIA"/EG/5^M(DV3H"L=:/R/G-L^:]Y7OFW4@?/E33
+M;+)#ZAPNAP"0./>KC9&UBY(@FNZMTXNVS+N&CO!&:RVE3865#IH3];33!_4F
+MYF;?+H`)MZ<,3^R-IY%122A`@`"$EF.U9NH)M3Y[AG:#E8Q8_,F>^%!3A/C<
+MUFN=?@N&$H^ASPHJMR(!P-,:*@[.46ZJ]&^.1[K4<DU:JQ0EEO__<.MP-'^>
+M@1^LPK:4F]ZO)NXK6A'^'T3,5/??_+KY/1VE(SOJLE7HWD5)K`BV%CS>7/F-
+MG:*<,YR2ZO!#8"F<32YVA@^_]S*`D:/=,%3$^DK_2VL/`36T;L+,\E+ZWQ5+
+M/T5?>`<[+('6Q92VX='C*%V0$(\KQV'Q]9^9C#1DY!O<E19<E/NN&TWWK8A;
+M#A;L.(]`HR(8@[OZ)FM`I00.9@J]D_W%J"#E(C8*2IC+_2SB,M8VX@W,^UB_
+M9_DZ]Y(]]=MF0K/F/\>6XI\#_TY>20GG3#9&&L03[[/-`\_@R7?V/EMN0EID
+MQ$],*S(.`Q@N'R#S=!$\WI+TN$!K%U*W*%8J57]*%55L%](&*#_2Q>4\ZZ.5
+M-C=$EZ)(7`-IP8.;`0E,XE[LQ:+,.0@IP&7,8S#&P/:\F+A@=7U`BAV6SB1-
+M]UCU.:>1LEKYT5E7S-8;M`=LVYX>0!_8D8O#]F=46`=Z;TUD?M'?=.MA9(Z8
+M;_E*-YE:XZ@T[8-P&Y!^`@/XS65PM*-`T(_Q:.8*9T(TETP4LX?0JSIXOAG+
+M:+M9FW/PT16G#-#=.9DHR'DV\A:A&Z8_5;N;2VZ=.C2DSY4B#\RU'N4*A_<!
+MHV[+Q.?S:K+8KOP]:+)H.EL/PF$RMQ/H.'RV"I'8.1%Y8M%GT\;FL/:FVP0^
+MWX5A.-127/&ST2Q1'JGHTM0RGMAT7\8^SVH7),.W61ERU2H&>M\9QT1>IFXL
+M:($I20P'0NIW@W@-K.L$]/7+>94-@%?'J*<_[1KEN[=#9L@OT$:LTV2$)WJ3
+MG5GZ/>#PCF&@Q6.-+T#]Q&M+#M`3D'F+`2U>6>6K_R^D(H/6R0HH>?1_#6+N
+MD8A\+Y/HO7AB/G+`DCGQJ6Q#_:9?MFI:EN5KED.8Z/?M$`AVM;1U[?R]3R4T
+M9L#?67RZ5"RP=N``E0QI,W-#YS@$-S>"TM)P6.Z'%+6^T4-0TBID2KM,&[O"
+M=JEK?&H>]MI]3IMDXB!PX"6F=S6',3V`8VM$:S8J!,A/A<O"8(KO_SCU&7N0
+MZP$A:[!^B>-(F8J;I2^]WSC[$??TWX^6<'E$VD_[EA<U7Z`82*WWDHLM0%*O
+M$/0DT1.=#ZD!J?AZ<1170=767E4[G$-J\440@>A4A%6Q)E;U_GIE*K8.!;I0
+MG_IZ2VA&KQJ08)/AW"]#H*=_TPI^,#7@/!K%[,%`]ROGQS416WJ(2<X`^#?/
+M#_4_%/Q`'CHVW&?IB5]XW(EF]T-GU_RXBFLN-5HS_E?*IWO[[#.-E'_3K5M[
+MN<L`2,H=L"0IT,JCF/-D$F._1]*)1AQ>Q^Y"\@TN_3BAI=#=&`?J[Q=`%3:Z
+M?G5*_XT?BIXR(6A]"=0XL>197%@@7>`!A1D6IA@M8GFO]IZ8W(JWCG"QK=>Q
+M@FCSOZGZF/Z+8!9^!AFQ9I1W@8`SLI,"_@9G*9I>(P'9);FL?1HG9H[T25$@
+M*D[9@U*1E:,);(IY2QQ9,'_&3=HQEX'IK<@2)(1^5XX`D,5G)0&8"8F.C3T>
+M2FY*WIH%'1?!F&+I'Z@&'OR&;XNYBN8EA]WO822ZB)R_)VX\PZ]_&.+K_UMS
+M-_+/@A7N8K.Q0&C,&VND_E.)1E^D<GL65*IA=Q%`Q6V=O17U=V#L*T?6!=H!
+M!?:>N?U3GJC0D$6U/M1LO1QC0U#FZ@^(]>XM'4W-SC\XO\3)65YXM7PF4%CA
+MH,C`';DJCAUFR*G:RS[2JHBVK'S<+C1QM+_$2GG>LCOW(0)A6GOBX._G]88:
+MY#Q@F_MB:S=MF`S,(B66CU&@AKC^[UT8.J@KG2YXYN)5FF+PWQ*9QZ0F;R`&
+MQT0#)]O4X@O>9W8-PKM88M"/NET:88E9*1KNO<D.2G#G(T#;XC`5]A['=VT@
+MU7[J[3X.I^3+@/]3C105#.2D]/.T<?Y]/W4'=K0]/LM!;*80GO%\^R'#LQQY
+M"/MHM)JQJX,_31<GVJ1+'IE>$WLNY7;NO[&NTQY:5SV0>LP8P`3L!?EP?)YP
+MJ5DGNXEO25E7`R:E\]&DSF/`R&O2OM_/Q9&>A`JAT%8,_!5@LP(3;+\7_V@)
+M;ZM;T^<U,V9(X%8T.5N8#.03C;Z9"^??(,?1%?4?KK?M2&8!E'YGO%'#X],%
+M+"E*]'XMIMGE$=G/]T.:ON9CM5T65$+$_,['9R]4&+FI2;9?JW+9_R1OMV/(
+MQRRYD<M'XM(5U%3)'(H6/I-2I@DRX(M%&P2A`M#$4[.%=DGLHI$DK,EG&E1Q
+MZFT#-#R&/SY.6X*G(3SM/US$CDW$I$F4)+^H.THNK<Q.Q;*M'H[_:U1<O37W
+M8TMSK^Q1=[C;A+BL'X1^/RABII2'-*8S2)WXP/1D*7FFK?OM2<]&VN=$GTML
+M4H-:>-V"HMI'JCP>TH`DC$\%BH-+E89H#IX@):3%!-'-6T5F%DX8/S_!`3?_
+MO*F(.D\'P!;76>20^$1>6`S->K^1GAZ86K;WT%==OM^;^_QKDAJ($LS#OWKB
+M*%SK+A!.0#FEJ/TE:&RA-D@BA'>XZ<(7F)&>;=<84!6S4X#F&<T?QDDF#.S2
+M/TK[!DQ+1SZD=N/<"5().[.<0A9?4$]ZA<?G^A[[XP`?."R!7K.@U@:S_.<C
+MIB#B9JH+-%4A(+/?P?SUB0IP&%R'&'1::-14]L7I6<3"K]39*N\47RT7:]*B
+MVQPU'/',\7,1[PG[;SAYK+W2Q#;C!MZ.&TQI'H_9]#X5>((WSWI<=Q#:W*NV
+M+R2`]-O?A^5A?`EQK9GTZ.ULZX@#:E!Z1[1\J;6.+'72_([6>3Q*9"`J+^76
+M6!=_#GY<9)+E1[WR;)I7R-QG;IZO+F?%]PI5Q-;Q-H]2UYGYD$(6C.$H('.E
+M=DS/=_#*FI(Y<YZX"5'K2Q-+[7_62%Z2I'O=1E<Z53FD:5=9?Z,9AE.)I]W3
+M,MG5@$DP6&$R%SZ_<M>U5A@5`O:#A?9YCHWC9!1*WFF[?,["PL"UI8_/U"F4
+M0WK/B;$(<3689#F&:>'E.8#]S"M&,@RC"/RYV[G^U9':SB$[\CF^N+V/!;HJ
+M*EQX+\71BU4GR&\1GRAU7B0-8]5&?M&5_/==/D_GB,!AMP*4R,%[`T6_X\I1
+M@SI/["ES:S<$*DJ2_`UZ!^@><-I+_:!+<)O*9JTHS*0A=Z`3S`5CF_JE\6V6
+M\KPUGM"_$'7+2''FG:[+9D@,N3Q'-U^&5*^T:0-J4=&<5D/*T*Q?2G9"XG_F
+M^2X)F+9F@P3XI>##N5,;O$/&>=<TGD!V`0&`5K?@KK0_!Q;UK)%_#/IF`D*F
+M0`*+CM'SJ<#V_&+"VTVA#NUY6M4".I;AS#G2,"`!&M@/1D1HD4\3WN+JI/?9
+M;HS>_Z13&_4,IA"'OLI+?0HG)N'[U>9S`5\M?0@`=_E0S#,\H$IBJ:7J,5WG
+M$\/;P&\[->G/%2/?+VN!$76;T%+"TP38:.!Q/(/)2M?)5,"/HE$L'AI4<$<H
+M'D^<]+$5X+UQ%#V"I/F2"_)%JLCJ-/RXML]&;I:0S5P8!F,W1MCNAAQ)4^(<
+M+T7ST6Q=RB1?*Q'/?34G9E\HB>5$7'3TGQB]@\5.="J-47WSO*`8W\FR(-TK
+MN1#44(^V9R2DKNDRXH&E+N[YJH<"@W5`5"K;-;*&HC8:'"1$?84]D:\RO\EF
+M9M'=!$H:GT[W->G'\;/JWKD,IJURPTUV@KX)Q62[`Y8[1QHX%Y4GI.VXJR%O
+M#'"\C(WUA!:0IR(R,S0^!"FSLSEVD6T^4(8B-U%,NGOZ/DHTOD*,:'Q#[C0'
+M#A53.FF_<]S%96#8SLW%+5@50ZG3^S.<=,2<3@0&?1+AT@37'LR`I,^V]=F:
+M]Q;B@18UR\]EATL0/U7<LYANKE:@;>,+3Q.J2)0;_F\86$V_X(=`N6U$88+<
+M/5TYN*0R@X$$RQUM&8WV\,K;IW+"YYW+OC[,6(""\2Y`+P>SC:$P.YF$NWD\
+M4LY76R:NINW%%_RCZ@HT<GTR;?I)I>M0'-!L]0@Z-G>0HTD;3VPSCO?>9?)M
+MQRD9;9%5J:[N76I4+AC*_5MO2IM)1#40"I9!<N!Z1&/R$^Q^IN(N\P8F>&#M
+ML3A:&<("5>#K85DM7=C"]C20H)$+?'V/(6"N?ZT4%CL7W7K[>);E:J8:R+W6
+MA\^*".N9L.;!S:)Z#GY&#.OS$B#CO,'I<[29K3"#2SXJAI$8#,)>0&."HR[(
+MJ3?B+8_PRFH<`&):V22,L7H-I1TQ=,%DM_BMNP'3ZJF9_%*>2%>`O80A$6M^
+MOZ-Q*<?[5J74X8/V=4M8MW3BDGG$V3[IX#D9)8_E;`<8:(26TS7,>FEV#T;'
+M/[#[S")5SEVOT<J^E<ZQ@]/CK_@^,SI8FW?&M*>:JKNRC!0.[KQ\Z&Z[_,0O
+M$O)@Y%`+YOQG2^0(F7U>HP2>26)>)ND`<PX+:1CF`LU%,%<YLW71(LO7C0L^
+M+IP<L-D?92<DGRS5FSO"<)'&+$\7MND`_6V,E=\,2.N=6M&S4'TV#@X:O[Z/
+M[5A2-CQ>3:7B$;I9PZA%-I<B[0U=K25+`T<)AZ*E'9/'G<N2=-N*\7W^[)YL
+MKCY8AD=6^5J++;-VZ![+<=Z\"!H(CI/-_`R,8S/UV)`N7P`N5I6F][[<+F++
+MZ)'Z2(?ZJVU.N<Y5-_\B<0(97:`?CT8@YC:+5>NYJ-FG6+9`$U(MAKH+CL0(
+ML3E[@4%XM>WTM0:.)3;Z:7+-CZ6^]\.\JCQ+-?WTP@'CUWW]Y!HFBC+76!ZK
+M6>!TW_:U,$MNX)JV:IX[:4$P?G#&P**KMO4_ZO.7,]=F["^%!DX#?*>H++O2
+M>&5">P_]4-"/*"#-%8J`\_U;OR$;7ZX`FM6UA'FG-7CT:6`/E%>)(87P4Q.F
+M>/VL(E_8OO8:E_$\VSGP<3"KE6)LG[RTI&"SRF_Q\>@TSXHN52^Q"=8JNK5#
+MRT/'#:F'PKN'FA\UM<7R)6D0$\=F'JIKV3!I[68\^NB_6I[@*T3$=W=\/_06
+M/9DH[9/KSX7H_\X3W_*O6-/8VEI$Y]J!PZN8<5.6^:F;K2;QL;7`T(Z8]0?Q
+MPRR7*\E!@"(WV[]A"?LC@D$[<*HD>,W]?!C\&(XV':O\N'_RPJ*F=`![E1#2
+MI7]D^>"EU@=><1*55VBV1<3[S?NBL=.(VR]/:J,=+M77O2<<+/8[9,QZK%#U
+MV)L.`4+)G'V#@<*`.@?EP&^L$Q(N/]MZXR!KU^M;0B%17>J;60["AR!RB<U6
+MH`]6T<L;"[U?8X%XNU1EG:L/ON#WIENG]2P0+K=6;0./`Q;`X7+#2\^JV-C3
+MN>PZ!.2YWPS`(O+*CJ2:G%ZD,Q<9Q%35>->:6[IW0:G/*HR$[Q:RA&0NP.C(
+MX&.,OA!Y:C5X\4*43=+E*N5O0&$4T_9M"7A\O*BV@.-HEALG-[(/PA#<+MIX
+MO.8"1\E=_#&&L`:HRI\L>==->YMDU,+^TWF'$8XT]:&.1YS0;W5DFD32&FYW
+MZ=FK>^%IL`ADNQ20RL-ZQ*\1"\V_3=WK!BU&T@_-FV@+.%=(<<HPF95\PB*,
+MD$_+TR@K`3X!]9A@6O3`91-:'_]=&3+GH?$QGQ:C)^?!*B80;LF]>;L11JH&
+M8B0GNZ?,@@'N=7'^@V59PI;>H#A1>D?\U[/VYCD\(*"/^;G[!@G"2,F>[^X7
+MH]^I?3?O;+PU[N>V3('5V:V!P\[M^&+-,F(4?K,OZ72R$V1/FOWS!<BV3'E!
+M#A17#&FI$3Z@/`*.Q".Y3\39#0O[D-/).>@37`.U[(-!E=:>R>;&LK-ZO`8B
+MGP1DCXM0<"6&(KZD3^CJ3KST]H?YT@JR]3Q_%[GJ17OOI?%$54JJIS[\4V9?
+MSM_"H\[0:[<0%0_6S20WLGVR44<%??5'Q9NT=B[<*_1=Z665O.%:.N0$QV%(
+M2O,Z/-5".^+^V38!E3:/)=O+>C4//+;?70RM]N+?ZE05TA\$@&HD$@J<=*FN
+M5\JO$7DCL\O-[N7"EX1G4/DZN:$'B2\-9)>$1A'?]^,UI*$/%WQP20C[9=X:
+MYTQEM"CJ(B80D-+-"<J2,,22!/1`B?_1".6?A*/*M-7R)#?)#E*:C%=2+1X`
+M'6\S<BQ]V',VQ6TT\YV<_PYP#_Y95WZAP-LS.4%<<N-A;!8[6V*V-Z5KA["U
+MP1)9_YK2N,HCKD%'L`H(W^!V$%]LJ;/._"$`?K-P:>^,)V!D&NCXRHW!>@__
+MS(9;H=]>@@G>T).AP;83HN0RX-'?;E1.RQWN5D+"ZS9RC:2__B?MW$BRF/^'
+M>3KB50,\"[J]<T+ZK*7G^ZG%6.RG?B*=5<5@K[4RM$5CV>_UB+1FO7>F=WN`
+M#T=XUCDBG2WW8F#1#=XU%Q<0\$CD,APLD%R!KE6`69V]$3`8@QF;=>O?VV#;
+M:CS$"^%'>8K8G[.&OWR'17S;*[^G:[(L]#OR.(J,/8H<6DA]T'T[<RDX2&`N
+M'0^5P2C,E8PQGN)]U6BD1[\?[`WQ/;SJ.'&8,2YD=<:P;-JOC/ZM/KL/)],5
+M)`8^7"])!K#J7%B7%$[Q$&:R^C7'$WZU0Y<SS0UTLY3=\VCK\=SJXWL9WKIL
+M>*'##2QF=/7.<\/8X+JO2PC]7ITH\74L%DQ#)K!YK]2,ZB17<[Q4**N5LK(M
+M6L\F_7(96#YJ2Y2N?(W:F]"=<'`"+4D)C8[*%,:0-H\7`Q%(W&-$IT)9_C]/
+M2AN*;I][)#0R0"DG@=)?JO>'N<=F8<AE<2*!<0Y=(LEC^%T*@7KOFQ&T[%LE
+MT^CPQS7@2[(Q>YK+%J,*C7;$RX[Q!'MS7#I_MI49XLB3F1?6'Z]K]QC.-$0:
+M9HQ2@4[WH*0&(XI$\21VQ@#I163\!VAZUR%<ER,G3.T^2S>ZNRA0NJ\&<WR=
+MJQBSFA_$;TW1]M#TT$O:1N`PY?6]7GYKR6$YJ!5)SP&LF<L"#8(.-I.(J/*Z
+MTN!X19R\+J0P/^+<JWU5:[NK7+!)-9]!DH"^MAU9M<:`RUE6*IQ78;BW=,N1
+MUYDYVY$HW4@0'5]_I,O;-(_LZ>+MU!#M60_,FTY.;VC`*Q(>"TP-UP6#8PQL
+ME>,5U%&/,S#64&N&:!PK85RWW;@8*"<_=OR)?%^GU1@>\L:D-%.+ZX.+--2W
+MHU`_"[,6?$T.941^_;9"Y`U&8L3,=3X5NQ;\02.*K$Q=+0`CF[2NIN6E&<5Q
+M9%6NM9,`#O&/O7J9QT3Y$3"L5>,JY+BJ`%'B!Z^GKD5S0.M^E.1/U[)RJ/0$
+M<_%12N.>Z[1<`1.`1:1J]B1Z1+<@6,PRPVW?P\<JR-0(#XB(N!<RPC!M7%$W
+M)'(9I0I<QTXP!;&:NQ;@S1T`TO>@TX"=XC)1H&L4\9]W^_)CQ5(*^EZ,YI+D
+MI.O2("R?)!#1;F!-`EY@?3JI(\<NXO%I;(-N*X7-^@#B_DL\28#;R\TFL"`+
+ML\?$4XW)+_)(.K7-:E/S*TKI\)H@_=FU*I05>O[B*^#E&U-!>X&LZMOUOCAD
+M>Z"(R3%`"GUKH5\>][="7C]^$@\^,-'?($YHQ.4-Q\#.%,CA`4)#TU4-BW\:
+M9\G4<NTFNN78%%+S.:<S'EW5I!/L[<%=S+"!V%7BTKGKG)/I":($GS7"@UVQ
+M,:L>;3X/6\-\$;'^29_<SY6N50;Q#4@#F/!E_QSSP91Y$4"W`:?PSEHRB0+?
+MG/+V=2"F[^'[Q9*UMZ[O]OS^LGZOGMJ1V.;QLU:>"^@O5*0N5IC@D'M/F_15
+M'(TOV$*/DGN*[0=V[`V8CK)5]H`$J`;F)^EU[^K>&<K]GG,QA5;I0F%AR0]L
+M4LD/5A'GM8#-?.I>M_<H%3D#OX-;7TY%5IS5C@:OU#T@_8DD[AE43RH3BYJ(
+M60F`<I$-H*DSB!H1#V$RRKP`FFNZE`+L?FI@CF8!!#20=,D8O8;8NLR7<:'.
+M@TR8@KP)B;<C=W81JL>&JQ6"(^F3YP0Q_AQ6S^8-E5S'K@WH^7J^546#`_17
+MU=_G4ME>@"=5+"X3PU4K@:#!!6S>%@56FX<J?HUL!;DE=<]"<DVQ?,8C=!*\
+M)\<!HR\,.\;YNPJ4BQP_+?QZ1TA"-.^W9*\G5D=Z>/M6C.33N-LIN&^Y<-T"
+M(W>Q-\J%U&2*.W8F#,1L.:`U5T7G)+&+Z>8J"0EJ@C4RCC"\M'SJYF=.RWTX
+M7[HPO0!E<A0=4<G32_KU:[4'9^(*C_:S75:RS@`2]SKEB[=OHX?(>&)UK31.
+M5E1$;(WA^^A!JT<[:F]&B5EI2;!6TE"G'/J^G^&24"C.^4\04GK@RE.)=FP8
+M20*7L7*RMS#U!JPY1_Y%8WB(Y)/C-]R;M\F`#XQ\@W]0L7J\32=B?X;@D!RK
+M(#XE[YISQ'\,R(1>,N]=4/EBH1LH==-N@1@L.%V>+7MLI:/`E.?IY+_]6D(+
+MZ1GCDDK5K-TIF=6A(PV8)="XWX,`>P5I<$%Y(\)DK244[`\D1K=ACR/":I\.
+M-=/GP<:C:P1U>3A=1-2T=7SAB<XYVV34Q&1F(D0N%`N)I"VPN$^LF(N7-O'O
+M@[BJEP,<>J)2PI&R*0A__,97HSL&IR+G_X!YX'3UGEKC&K>P78FVN6V+D"_C
+M3"A'3:!ME2M%T_KOYPGZH%0RL"PT#`+`B>#[)GA+,MT3/5],F8I,$*:8(3A2
+MD<K]YRW/YRI/`<J5F<E5U*?!)U(0-3Y%<RPRQ'*'MM&Q&_(BL/@^RIIJ+ZEP
+M>JP!E^G!,5,2$%6R2MV3"[<<BUO_.EW-$?;[G#V%@GLE0"^8,'JT:XSI*I@0
+MRM/]`9<NF?C.JAWGR]EG><C<B,#$/7#?J#0%@$16F:J0S!4Q0M*/_I.O.;*$
+M)Q8:JH)_<L`G-_]`R=(P-D+9/$;J*"$*X%@=+I]9DUI#GL=8W.8O2R<@+VM#
+MW:6!`H%HV59!V=$=A>;2^1_OD5)O?@.TUNB0ZQE)(+],T.50<NMA!K!$(X&@
+MA!S$B+FNJ>-TH&D9*932%47KC7JUMTC>15I[4BM^<("J$/#KFK?Z*2RK%#]Y
+M/<@\+F4*SHF(NQP!0JW:T:[U-C4-B%7VMPM[QLK#W?T`8HBX*/.:BMVP85+=
+M`B$WWP]`,6646CK?5I?I#7A2O#?\+'2W[]@*P\TN4\.8V]AZ5(",C:%?;[ED
+MUMB;.JG[.JM\F.P8HD9:Q*C,3!<J>[H!7G"E@,P;RO%NBYV`L[9A*;U+8'8>
+M$=",<W*3Y5%HV,<,NS+H_KUEMGH<"<LM_$%&2*9N!<!N$P-D6%V%O-]:U.,.
+M]LJ)?&)]UCTD6GQM2;17D78>A6="3BPM.=)FKCE,GF]/Z0$>CF<;2W#MMP-?
+MXE:RCNI"3PUM?BJF@S'/-Q&DQ4LO2`&]OV7`NG89G6'*<Z=L4W%XM%5F`ZWN
+M'(Z^5LN#H-^&50LFXB%<<(%.1P_T_AO`\N@=&"K@0.<G%"_W$^*L^6(CI;`0
+M2&@U%#^8E>!%@QPX_8M&TTOU0O#6X%@X*9YN*\`0\]C3;VEO5?R7+-;,"/?1
+M3!V9+$OIHRF)%@4JVKI>MF2IK!&%#/BYK6,4"OR<*((*V!@;SKRS[5'4'UM1
+M^*X[*I@8*4H5!.LXW7ZRW0ZUL`NFT/]_"43_,+A[SY"-U+?_215PLV7W%B')
+M/?X20SA_(J$)'/OA]RM?66IW$O0&E/HV^RZQRX[3D4X&YDV<8?<M:0NLK#)Y
+M@VRPT&60"U[CT.]9F15>[8U(=?.R1)O'GS+(?[?[/>79V(2I4%M>[\B;XI(;
+M>UOH<$=:F?^K^G!)`%.^$T*R(*Q98&#;\)8LY%&KJLM;`&)9%$MF+_;\@&/_
+MZS"<^?+284P..5,KF3XW]',>>_'/=)FHMA:DUQKPWQ3ACM]%ZN12O`.,F1"B
+M%L]IZ:--PI;]C1N]DE8!W.2/.5`N49MZ/X0#LC?IY6>1Y)="8+/6U-W^E.:Q
+MP<N@='[Z,$(AYG+BC&?L`/41TH*2W--IT,&-7J1R8^V_%<GJ"GE]G.E_JS?,
+MQB9E[2XF1U`NF7T/;N!FQ[X#)9U%>$?4;L2J9E[CL<0O/?&\=W^"UZ*>\8YJ
+M1=R[JS)41[%2[N7-\(<VNZ58L%4RY+]Z/43`C4O,STW2]S[=\H4.H-Q6!5[4
+M#_QR!O=L4J?A-#=?Z^_>NBCTK41F)C=!\OC#=TG)_4%?"UZX(6Z9!^=%[!#M
+M_Y&,9\HU?"+M:LI;M/KQIRY93)J@(@5[-OY`6@DN/@5T/=QN!9X`,IM03BLZ
+M;B<@4\2VF""-2O`,:8L)Y#'QQ(:E&X]IRWB2D=`'P24^))O+/U8W9T>]Q;V?
+M0WQ2[.!DF4*'0_=()*+3W0*$I,#/])_O.X`1<9#)$!NY<SFJ[Z<,B%Z75XG;
+M/=ED0LC8J;?VO(Y&);BG7<^KS7NC)PL9B?'KX0-,,9F&9V!7.0VC&,J?B^0O
+MB`"XT8>DL<@GA=VH9(V2XG)^^U8)IL!3V64)K!^69&C8P'0<+X_9G%*`(5R9
+MP,@$.:VGB8G+S4/THHW@50$5E[[8M2J9?#N@:[YC=('E#AK/-;ZE'!/105)[
+MA3;W`^S_FXUF+G"C&RK'MO&Z@OLZ?MQ3-6\TX.O)9I:K^\)Z>"%-&>"W0^:G
+M_6K<Q&,NFR<G53QE>2ZO3S]<T[#&`LB$@1UABS&9AM0.W2_]7YJ`X)P.70_'
+MIRN8]).A+7-B/2IM!Z8^\;7!?MP`S-"BF;3/+K`5SQEH+\/AAA]X3"=BJ"SW
+M2\N[6&QMKWFB]>#B<VT>CY!YHHNT^D;"5WV?^*3H?Y;&ZT^?+#W5DFXZV>H%
+MK3N(QA?NV`2/IH,G"!T?#J0OF.),$R]C1%\644RQGU<F@C5!CZ1VBT=/RQJ%
+M&^O5Z[F0J99<2@9Y=6(^G,W+4V4UU9;P.2'V!?$/4*(S@AGG<F:\8,"J-V&'
+MS;O_`N:""3=IN$%9MO`W`(DZ$VT/V(UQC9HK].=_GOJ;IAT"IS"WS:+4@M4S
+MX174X`I57\`.?,7^$@0@_<_(VA`:T7STT54>V-)N!98\1@@BHXZX_R,"/PF6
+MWZRCBJ6S%<!50F,8`6>TU97]$PP\I_EPVNCF`=@H#N&.S:MVS&*Z^KO\5Q>C
+MAN5V0:;7-329D;ARPF6V2!Z6]N>(_WF%WKD;>%U8\&_7`9;YXP-M+#"^_Q3B
+M?8WLDM6;DI`HQLLAR!I>#IJ18V9C.EE6Y,C7_DT\;;E8,J"^^<L>8QTA0"*:
+M#24CSL<WOT:67S43;8RB%TJ^UCP6696T\Q@-T)18_27N#LH0M%"2\'DX9[(H
+M44G8!H/>EW*30NOW;(Q!`.^Z/8XUXN5$)4.P@%J]-GY<7YX7X;-8)G[E,P:&
+M"X;?TIASUU.0!W\J&R>IAV29$;:JR(=??Q`AR/^F@OP:#5075."V$:9:STB5
+MJ33+S@5HU^@M,[3A=E(B*W@VY6_*B',K+YB>1EF_AYM'+#O-+LR2@F)M7*YS
+MFZF7P*3S@9NW;U)X/:U_G7-E):['85.!$>?T,M[5B</2Q$Y?7`]`]P-*+.U*
+MOSE.GZ^*H=;4]D(#$G_1U:M+CE"%TE]%[N,7Q+)#WNC-UM+\D;GN%M->TT\D
+M*^S%><=W71IC;>;T;WB8K<R?F4:3N[!\*C[$3AJ4[Z+B;K7G=5EX"!8]?;1M
+MZGWTHH"W,IM+WWR3UI^8M)?%RW28R,_6W1W$K&YE0-8ZC7O:L:DM`JNE+16T
+MNI;F=L3+P0C9;;VV@K5H7"A-6=S/H%V*X\7P1B%VZ<&OD(O8:1@\8E]`1:C)
+MVW9"->OG`/_,%`&_".+,6[Q-;QV1PFHM4IZU[/V8"QCQ)/_P=@K\ANJ]Z!IJ
+M36&\\O7?&Y5VU,IL7LF3IZ/JW]))@FWLTQF6FNR@[8Z,#/P/&M_ICGK?+7`&
+MO71'@>&>'LL>$Z[HSQT'NE1E,/!&BMBHY,`#&6QHKX),+&I@E]68-!'4.KD^
+MB2+Q$-),>#T[@M+\:Q(T8'[,I9^CPW14-[^@VKN?L*CO%]K@0&2^G;_5".S[
+M9/F]F$!F*]3XH$$A.02;F=,?*1<#!S@]461'WP`_GFH!X`5AC*A-9>1?_')3
+MWMA\R<>?HYDHC@7[^S\0)8PD0$9PI<'7YRH<9F?@=PVC$B@-;";QZEMH_K-%
+M?=2SM@<<C1WS7>YO?\"3(6AE[`$35<&_NDS)>?NN"4#$F78>>.^E\T9K,G%6
+M((S3T\'RL+<ILE"69J2($U*$=UU(7N8+[9:\29%EOV]0Z=EH&(+-?CENEIXV
+MT\O=&O@UD+V[I;*LN`&-`^RR?>($WNUT4$5^H=?DC$3(9$B.WOMJ**U'08Y)
+M[(R3]!&,+6"/D&FI?(49Q8RT#%06P4N@V-:+K&8;!#0>B-J'_),>#*OF4ZE/
+MHO$+6_GSKPRCEK@<_]CW;.NNPP7+3@UT0YPG?=TX]$TR6CN"N\N:29NOR@+3
+MF@GH%S-*/;BVJ--4'E-GV%)0"[Q9NL*(``!AFHP7="J+:\CI^H7ZU#B<;-0<
+MI43VI06;UBE_K\SR$3J5E@HU/:$,Q;:06("EUL?II)K`$F408L1\_>IC7CJI
+M//EYLFU`YFW/>R$G8C.P/TVC<YDU-`ZR3$<1'FJG&[DZPEK1:'DYX?/V%8]?
+MGW1R[['AR3E](9PG4LPR17)RQ)<[YF0FA0('Q#/\JU?Y57]">.!R<RPS@*(.
+M7G8K6K+C)D4MQQ=54QD:<8-NN[C?(=D&YOK65UN%+=(3CC9P(X\[@,#C]2LC
+MID[&HT^JN)MS##OWA!4V0U+`%/%6O"@F9IG[!H=GPICE7\W3]4PA=DZC&R(O
+MTML#8]V#]`UYJ,(_IW;E%0.%X'*4D6:\9[K<ST&;"=V9>>[^K<]9S.4@MK9;
+MAM`YFRC^7`_O0;&9V<0D)]W)XJ@G3MXK[M@/Y4\@1DAG7@C<6RUAYP82!S9[
+MMMD<(RBB:?^JBXN&9XE3ZG@8C`W$HM\!L3K/?G\5A.M;U[K7K\<&9;*S<CK6
+M#(T!^1&-)??RE]ZA7!^JWX+.60YHRQKN<I3!WO[@EG?3@-L*?D@I\7XXI*8(
+M&G^#"#[B%IPG$#G:4G%S*B<I.4:#?E2^5@GAP+4]5@V(C%NJ>,T^]JL)A8@C
+M,O3W<.[D*=^VERR'"#!9NK=W2T@CFE,*>2>J#OO+)C3\.#(0Y(<+;EP6C7(B
+M1">.+SON/',;>Z7;MZ&]U._.YJU(&)ZX)5PB9=2JU:7GE.4/G3VL.,)6Y\;+
+M2'B^%!^LE=?/6'[."2FK?$'<2#4`S7>/NK,9GM`Q]K:6G9*ZM'PT`:USGTL:
+MCG'::2>[.(0`ORUR^U6\.Z479\P*31-O)"B4=:O@0-OE99U]R)KP'2?.11H'
+MDR%).*6*.8SQM\9[;SP\*++8Y$W"SL,_M^?*&D>7*W/6D0"0I:#R?$7'Q1:O
+M5/^N@U)V%/?Z<LR?AKI%X(FP<J^/<E%_4&!.(#*A-HEUDMYYG(+/!I@@P:O4
+M4Y9KC!MD"^]Y?6P:V5L,81Y/X!P8.-%Z\-2]9B\K-153H[#7O%E2-`:$=Q>\
+MJE<$8^ZQU.6*;5_!Y*8;D(!3A#>%P3^S?/LF%7KSJ<>6Q^#]:6.R77X*KV[.
+M063OZ^SOP(C@X<E!7U=^^"Q8,B:B);*',*(2CE*(=-Y_]3GO6M6=F7I%!SI\
+M_"GI>GX))<3;)*D<97G?/B=APU.1MN4D'\Y.P_;DI(FD@78=P(W*-$E?2S?3
+M=?1LL6Z)MJ]EW/1$K%H55C%WA[2"*(']T[]&QJ;$='7"-\Y!8N`'RNX-6+AC
+MQ(4NEHR!&E$HWFN7,W1Z$*;A40:L>29)3K;?U[5#$?8[Z7]D4JYLTJ('S/32
+M>O[5I_/9!=$8V$0>CJV3DCMD<V:BK:H_Q>S^<&\!Z\CM%5_S^\O$%C76\_5Y
+M+ZN+$2=2#))A0`,FTOZG-3)\VNT'*(ITTK;GV2Y6,CJ2WH]H.;5NX0MPMB*E
+M0D4;YGCBREB@&0K2-]'D2/<E3.RJ+(=`=HO7Z@!RVTM3@D/`N"*K4+U!V468
+M`WIAG:?Q+YIX7?O0#G67217VA[93)T]C7?Y#ZH-9"8:5&_L;<"##T[II`H$E
+M+GQ.S[%I1IB>5/C&*@47!N,6T4?OU/>?2[FV,VT;YR_E`OP[E$Z65XB<W:3Z
+MD_FE'D88*Q;]&M>EE#"6L5(NK')ILDG9\S.%JYNS(:)0PV<"6\*]%?9MZ1_+
+M)8UVQPGE@[20;R8DJZR[!S7-,PJ(&=&X?P6M.R/"GX^UU_[+C:Y:JZK?1[,G
+M5LU'3,U4N3\7)3Z)'/PP4-1N(+X5QJ(O/.?O[#DED'JI<H9:O9!-Y1B_=Z=1
+M1J$HT^R-@Y*N.0*=;#^N9,?(\\0J2I*H\IM'IJPEC2=?!#V,51GK5KWOC^(<
+M;+R1J]AY%E.G+.<I(;,HG4/\CPQEQE;C-FEWWO[M%LOC&(>K](5,+$B/NPVT
+MSB5.R\.+]\%?T#_>&O_:>)6P)A;.*0:*\$)/[`2XP1X>"$D>B94<$DT+'$*/
+MHK6B[INHZP5-U/N!E.,(M-4*D"^<G0\[Y&^UIJA9R07()'.52#X-DNDMX?@;
+M(?JWW7PO[!-\]^$\MS)V#>KTSZEN/4LF\(F!$A6Y"A!5?IH'^1-Q5$.3;`9P
+ML=E>JZB0G!/0W01R@NP<L4T`X>&)JBBVTP6&^%I+;=FP#UH&LRNNLATUY#U%
+M7S[>$OG%FQT$).%%>H(SPG+QO-<,W39(BE3@Q[MP*CK04.A*3'].KEJ0"P0H
+MZ$Z%UJF!AC@'=^N,KB.`"PAN$3IR>SRN6='Z61#OVA,\RCC4P!:BXL20V]FV
+M069A;?]+K???AT5J0AB1/78:![V2VIXAKW*[(0'S/2Y1].W]?%6LZ$:.F)@D
+M.@2HN6?$3>VO\2NG?!&;JY26;;@[4)`%N=(3QM,O@YC]4Y)!_?Q\EI=&"A#&
+M3?B4M$6;:[?0[ZYBFJ>!-574*[$(8_8^,[R@8??F^%2+[AC]2`2R:%1CLAC>
+MVB9-2:3USYY68?L0O#JCS$B'SKHB5BP^CB#Q)S2=O9`H/)<8UOU"O&1.VBJ/
+MB6,W+5`KY"6$]LIJL!M*R!MZ>LN\SRE)H0,@/)<@K9#-`?:01;4!`JY/F*^0
+M%TL),OV[5#TU2E#ZBOSP[*2X0AT8;$WP]UL_BB'<JOD+RAX/Z2Y#E;H]5C#8
+M913:N<A3!HGVE-'\01N>\K\,$L?>:-)7?_\]Z-$%+T*#B7L%K16W*3Q@KX\R
+M[=&RL[$HP*&0<'@XVX8N88`*T&9"<TW\LS/9=.VAM"#VC01STV`*8)ERPCE[
+M\=ICVJCNU/\'("G=%U=<8STI&EX0[AO`*B>DT=N"!$]9)9[+7+_QL:NB0IH,
+M7D.1X&C/6YW\`;<%HWJ(4`?0G!LXOVE,$I6P85#GIBD%M5B7\S56WQ24'VGP
+M,I%%4#P+LOL9UEM8OD;%">45A$0]"?<)=N>C<2Z3E@@>.:XJ^8SG7K)Z3?$3
+M!1$D%))Q`AI^5RD"'32JG#OR?L%EK?M2>11*LJ]O7T89/@7]%`(OAD@K#DS$
+M0Y\8MHJW4H@<K&)$3[C`W:6$4#VI1:[R'CI1,_S1PH^-F_$C\+,\F_$M)K5'
+M->]"KU,5WRP#^?J=XRJOFDGO(GWS(F`JHVMN%2,[]O0)0L\*EMM#6T9`X9+G
+MPLL@/T#LS+(EYHTO$8PI1%#O;PT07^XT,=QE0$J>^DBRK]7+^>F>@`S*W'*;
+MA)L)4D0%\[%DGPC5(`<%,_*X3J/JT1UK5*AX);JWV:WE!8CY>!H427/RG0[A
+M)A;J7^;_1/CZ:XA+.*#&BKSAGDGR`FY:4/()"GMTVR"3(LX-"A[/#:O$BM'N
+M;P$!!8,TAEHM/0*;ZX)2(@P^H)GNCS_C47C'+2<.##^Q-7+1LA9@IWB9$RQ7
+MH?EM%*=/='DIU\/!ELP]ZZ!._Y;R5U:+M+.P\*ET1Q.D(+D0@:-\R,9QN/CB
+M_#V:Y))'*25-#,2"=\H,;VR\K/EVCSG#$CT!^^=0*B,@\;$_.B+(6#A8C-QJ
+M13",G3^N+'"0EX"H8.K!;?)H[?+B+FY^?AT)BAS;'9J.V&.O&-NZ'&&<7NU6
+M0!R"=(YJ,%[%OC5;Q(;YS`V3@_K/%>[&%=VZ$=2^"-`I/Q[D@*SQZ<M!C3?+
+MYF<(P&4-@UOI!/J0'#K\D2H)A:N@Y$QKJIK'^(LN-:_A6LA_A1<+/G_J4^0T
+M*KT"X1O5>`S'L-E;5QJE,WF]?2*?J<XAH80A!`_[9'U2_*738T`,!-F(UZ!P
+MK</'M\0LO&*I@=HA+I\W7!.XT-Z3H,""A)M88XXZN_)SNG]KF6B2=<BP%A1P
+M"Z^BT(]_AU@./2O]Q::>ZAHQ.;P4@I)*"X6A<.?5'2L/F/.U>60U1^95<O-=
+M_Y4O1-Y@&?08P^P\^\D%0Z5',AU6G66,Q`O@X\)",^S9ADLCYEGLF34<UDKW
+M3(E$18C-5ECSI0VYM6[V0>SK*##L%1&,C$=0*"&W53<8F5OW*+=8NW&2<.81
+M_B#WP!A4;V1N=V$7!KKC1,EY^1U#A?HX!J1HC=3!1Q1I(1-]\57L+[>S3G0U
+MP[.9>/SN\2LW%EF/3+-;I=P.Z0;53RN&L4U$-A<*_XO6FV'2M%4H$WOU.("%
+MR>^/(5./$N$EF(;G,D1.I+5-6DG";Q1*G.L(>R+U@[-OVJP?/G;T:ZGI!-T)
+M^9L<Q=QYU?.'&`-C5)V'+8Y9:ED^K'.L5(3/EJC.S,:N)S^BRX65G?):\<_$
+MTCZ(1)473^V728!D$KCV?C%JN&TV//;]$E'QN`=]\TD(Z]>+)PRXVC]'.-+N
+M":Z&YO.^YG_@MD>^$`K"E)L5Y(O"OE--1I[QQY,,;)L7P4UIJ]QZW-VIN\O_
+MOH9AJ,DE4E";LLQN'Y>P<8'1OVXA./]1*R0W0Q>?82E-VKJI7A![FL*_MAW\
+M.^&X%2"B40I[Z5C+>5X>,@J:!?=#U98#MW*ZG&=&HW(^<X>G5$L]#7-Q"0K6
+M:Y,TKSFNN$*<ZGD@Q#FA\*^\`O+AM^2+3]%/27*K81K+UKBF#2K9$60Q@ZF2
+M^FP`%UC&]Q'76K(VAC*6)(K+8DSOZ,5?\ROZ7"R9Z'AVQN[46^G&O9Z>-K'=
+M^\905W+ZSK<,L".@#=RZT(4J-%D()85747/N\I*_SA5<H3N,32F;N\2;$[EM
+M^D*5\Y@.R0\DR8-#*)N^FG"\!WJ?IO?W9C#=NF1=+(!T15=:PUDRSZY3UP)7
+M#<&/]U[,,Y_<P9XQ<("AIR*2/-%V3$\]Y#CW2GZI'7_1"`[<XB:RZLPF2^2M
+M]WI^VK[?/.+43@1)QJ3]M$D<M$T1-0\(T']&Y!D/71^I&TRJEFMJ0NGY+Q,F
+M&DPO-[%P/E)H%OS\4EYQ^UHL6_L#%K!"8S_\=L['C4ARI9`QK-1'K8L@RZH;
+M94P[9:&HB'NVW*(<JZ00)0PF&U,>TG=51T.0Z%@[`=0'=5*CZCG@>\B'24(B
+MO+AT'&_:&CAU7C9QS<EU(_1P+AA7)@=;U,(:V`'#`1"QO.X[L)V+2R'\0^JW
+ME?K+#VX!_(W@1;K1-G`;*(:@NZ-,S7).68(?3)5R5*1\&Y1\6/TMD</VY3A*
+MRR'"TUP&:W$C:?)FW69.N)@P+MK=U1#8X311,X;JT(-&,+Z$&SX?@STP&3>>
+MCAUFI!&'YH"&LLK`#O[)1>/R>(CB3Z(Y!=]IMDZ6)D0><P%CN]@\A0<?CZ:O
+M"B;C^F"ADWFKE$I-ZMMJ/J9M&AR>)X*A\!B"#D,[TVJ1<M+6>W*P<Q*$U3'J
+M"^>)XRE*Z#"]-NW(O(/;\96:^L9WG7'U`>S4EY5)BNF46"3?#_]U$'E)M^SY
+M'Z6#DZY+.!O!VBSP,U7')F2*('I"G.E[#M#M^;M%@%;ULD['(FAKUL1%V!2<
+MPEP?6(1U<*%SZ+,B+2;("V&T!*BL7B=,!F;XZ&A,D7B7IS'N-Y*>WC8_OQ[:
+M[_I@-J*>%LIZ7>"!TV4O2GR-E@;%F;&I;).9'Q23DGYT)E*D^_J=:TWN?`ED
+M=ZV%*Y8DHVN0.#L'K52/.<':N>>%NO"#RB`VZI=PY]-7DH'0:4,/P,CHS;;]
+M1^.?:RZ_SI%]]=8MJV@$R-2"[>=B+X\`\X:B@_&S7S:]$5S*@]E:R%*$3E^U
+MW/&.FMP;EVN.P;[1>Q81YMI-!Y(_.&B,.G*J8V6D<NG07PZO1GL<H.^36FG)
+M2-<NTF"ST9`9@.?_1(E.M_G/))TF@AKD`EI5;W?89B\M&-U<#_N_XC\"Z4/&
+MEU,'MT*[@@U'#'OY">/V@I#>W^T0&#+#=1NE/V4%-135B=\J=>_;UP.Y^CMV
+MIY\J#;&84,1:R0#A!"3(AQJ^=ZYZ0(Y`?!&QJ)0$AO/Y7AA$(Q[>J9^TQ94=
+M0I.$&<>`@`)#<!GNP;4S`(@R_'L^I[13@H$MI=XF&V@O<,^_(?R'L!X.RW\H
+M3[@6K:!O!&2-\3J>T3)7K!M4&<ODGI(0/<$YWIS_=3>G#"_)?F42M&.JEA9;
+M/@E@%B;LU;.T-1DV31Z*]<E:]Y#NS/]ZVF?P1;+!LR%"H4CZCE#]B)H&]ABP
+MCF^][.&E1F'>/'(;T`YML=8^E7E:&(AA/8[`JVWB:FKN1ZRQ#]T]%,2M+)OS
+M2&"?(Y<R:$Q/.*]JGFI"<Y,T!XU,85A,.CN"&W/<OS'>GP[@7I0T+LQK9E/T
+M0'R<6*$"S[CCF*@!ADWK*?2<=I&O*$[BBCWM(5H=Y$,F<J>J&LP?C0`1LA,H
+M)I\L]H?@VIS<KWB'3!;](IBJV&,4U6-*/9$#`012#V*91S`X9BP7&4%6'MDE
+M:E8Q^GCPS=!>-7RNZN6R/UUS@:]#4YP8AV2P4HH]-'E$'J_*1S`(,GU))CVD
+M`^M,L>.\L1#A)Z?LH'3-+O@1P*TQPG%P1Q.)D6*=7515G.?OFO5G:'F=TR](
+M%B6OVS1YJ^\F+:FSB!(RU>.PK+4^V*6(;O^;<;&.^DZRJM279=B[/?KN0[0]
+M23?J`2\@;Z:Y!Z9&ITH*IBH);VUI&-J$8HUW.]DD[_BQ=)MZ.KE0B%W=%VY$
+M06X0\:L84:KY?NFR#:R%B8\KT7JC+P:GDET1+[I;F<'OERXTKD^>2!FY+P.0
+MS&O2&`^A)F9C5Q'A_FG+BC:VWML$WNA>H"]NTQ2J2T*C2%RXU_6K@ISB8E+`
+M@3V1(&%!W@@D2V]MRM.RL2E./J!(2R3;2".Y$MFVOB&A(WVN#.1I3MP3&ZHK
+M:/,<H4V/FYDDE5-):,?,_;^9BY^C.2(EY]RR(I&X<(!8"[?DT[7)')5KH)(;
+M^-?WNL_#AP[^7%N>D`6;^UZ22UV>XI9)C]7B&-A?2@\K5O6PGY1%61B]7U0?
+M9KX;>)#`@%1NY'E#@D?RGL&G@F81?!3HZDI*/./8"WQ=?"HT-`+CM`?^LD<U
+MIO0ZJU?'YI/IF4PLU*492$C(KY($/+/J=U(]"*^ELG,WIF.M!M?"96FIO0R/
+MZA[3<Q,YQR5WC\YD_=A/8+\H`P"#F.#W#>M)I@:+#6Z+-G=OF>,[PJ8*=8).
+M2V3HL/S3"T*(E>%EDH)[B.:&3]SO:*7.^.;3`(I.["/07%NQ[X`9=//R=H+Y
+M,$SDW3'4#//OEC\Q8)W8TIG^A![8H-<CV\=MDS]O1S]F<_:RL?S2;H4=+_>.
+MAP*T4[U+/?&68+@(X/_0HP1AC5_X+WB+N@0PC$-DJ%_NO*0^@CQ.MJ[-BDSX
+M_>K]WX'7QF&A8'&FX0K_94/Z@#*MZJ[M,Z(%O#'GXJ-2BC5QDOS)*C3!#=7!
+MUD)]N0K4/=J2[`WA,,>*_9Z<?J.R$_/8=-FAG*9T7\/@Q+;[G.?_N<,:X.=V
+M;"665>G#[SO(B2-7C-!;=P3?0O<P/VB3*:C:2:`'^;Z<X'W='UBGT\50@+%@
+MSD@^G3Q_RD\&NI>4/H\1)O.2R;DX?"T)NJ.,D9D"F'-'#JOTE3&HK>QL+@FY
+MAZC:08VT=@E]-A4<]<&+1/[T7!/W&T9C\`)9S/^O>K(DSQLI9OLM7E73:EJJ
+M`A^U7+J^:I;B5&MQSAXWA[[A3R&TR6Y-%TRPSM&C$,8C(;R&`-3P,ARFNR>%
+M['<!KOO[XIZ2"U<<W.Q@Y?0QS!X?N-^F=-I($$B.9;(D)<CD=&KBX@Y5`2NO
+M6>C>@%=1A&WA;(]6;;NXD>DTJ7FT]?Y-,9':4]M[`!_\YQ#E4NKBSO5I--*$
+MSJ`'UQ'8_??-^)XF<'1,K=<E"Q3B:\)VZNJ'P*'L4M?U?@<-^'K64*^^#RYM
+M&M<'9LUM(`3A-,33)#;O9>1AE,`KS/90:H)&5T.5A"*>?'`@AWB<6UC5KS%F
+M"R`)BS>1CU!\L6U1CE*+>_'JK^!+'R>IDS\CC-Q\G&>9V3)"BT0KU]3\2']E
+MT`O!A"1PL7Z8=NR67TE_J.^8=B:R7G!IQIZLT7Q/OM81H!>6%!A!.XG<0VJK
+M[<7^%4M6$>;#;_K/YD@?YK0<RBR8HBB)7K5F*65$6U;NR:U#)7FK++7/KAO/
+MU+^B`JTG9.:SIK+_;>\:0E,/9\R_C>.E05R#Y2^%PON@C2BUPWK5$7-1_8<Z
+MZX0<0Z5O>A#&Z%32OQE7*:6Z9_7PP[]/`ZY4)[K;BQ+S*D09)<O_"6`[T9G?
+MC3@Q"P.HS,)_RK:8\:`XGRH$4<%H>QR)IZ`.,4+#OY'Z!(7ZB_JQA\7O9@$A
+MGWOX)PM4$K_E!B'=<,X6&8<#J_R4.\8?SH<,H"P''&$M$/1:@*?/$D:!,M=Q
+M*J>59-/VB+Q>9RM)-0(W.L$ALXT_D+$K46'T%>:@#D8+TNGQT9]G0R]@7'H7
+MW[AHE.G\H=UAQZJG]J/\S3Y,7M+T%@99X""$-FP"<HG[8\?(I8L0S=V#*4<,
+M8X.Z[I1?>6_2@L+DIU^!:#)Q-/7TD*ME!VI*(VG`$"E_F)S:AO$P8AU,KE0M
+M*V)&!B\8A`3,;($;)@Y*]?12`;H%TLJD\)81!ZB.'%C_X4R;B[4PL%\B3)"<
+MA/VEC)5*4U6D^]$'>(2QM6>;F[$@(S=X7]-B*S5^0>)O3=*)RQ.JBXXW/>W8
+M(_G'7U'@?Y>XL3W1*7@5\"GL%?^&IDE"W8NKI@-F$?FMOI@&1%+P4#19YIF&
+M&MCKFM<W3B@4X>SYF)V97V0D<Y0-NZ`\PDZJ<+4X3D`FHQQVA&FT>E[>F'ZT
+MO-ZNO3JPA7/-$'80?R>*)^LBB]I_E9EPY2'/'=Y4H\.SHK.6T23<AQ\8:^(D
+M:LHL^][PA)F,4O4=I69&4[M+3OO:7W(TR;P)HO<!T%+$5#M:@>SD-A)?L[T%
+M)B(A_JOL*G]*J>I8,^ZI2PT\.4=8<N4BYQKO\LJZ@VLG$3?E$.!Q(]F-[I<C
+MSGLA+JM%&7%6L6/M%([/(@DTSXH:=`<\GR*-JM.>GO*?K-P]%P^+H&\.$V+K
+MA<2)%Z"ZFC%NT7G25G)P9X*%U7$CK&RE<]**F\:FDR7HB?^[7A(PV6NL']N\
+M\\TCV3L-#IM&YFP^RFX7LL[ON!AB%6ZX6K<]#A0K_!TT(EJ]!R-F&`R'+X)N
+M%M&V&)1WX4&8OJ<4NBU?62R63<M8$/K*V`PT.4`K8<W==!WRO3V;\7)W-6Z,
+MJF_&!>YGHP*8.0L<6!#?[')QR<$YZM()?;#&9'KC:<\<^YT&[\BY7WT@5)@5
+M&KXE%O@,4U_C`UEROV2PF@/:%MXWB7>0M:(^_IAY?CE"BR8>M_>>0*;M-_LR
+M%@.!&I/]O$1FV\"^+XJC!5,A'QC4NCD!DKLZ0,DV48A::&C8FR*+8*9$;JPJ
+MPTOQMAM'=X"=CF="QE&032%W0&NAJZ68VJTM9G:2A_Q>=PYD3R^U9H+>#EH'
+MW$_3XK5%MO/U5CF(\PA!8V2^;P,0:VT(#59+\7HZ3!73OG3^@EY][M>_*K3.
+M9J\,9.I,3U;SPR;O/)U;)$=N#XLSEX2%.+HWLD\L<9G5LWH6)<C3+,B#;AY>
+M#6/$)=%?%S<G%$68XR2>%9E=$FXN,MZ)#MER1T)$:.I/^$](E5BI!4->*J`<
+MZYE!E:)-Y1<24JP^(7H7X24=W,BH?(A@PM$\%6.3C9/I!_<C@^"1-B7=.PG0
+M4*V^SS0O0#+7A*ETP@E3GV$3QV:6&U"92?U.NAXR9,";Y-*[;I,W#$Y1R@&'
+M;)BF@!VI/-#^T`^$&0G2M;F[7!/[K)HW9$`KEGQ#E;@J2D_KY!\]SM<`:97)
+M_-:7^]D5@S3G?1])US8"4B>D0EDR<X]0`D84K--ZR*AJ_8QBL*#=A5QYHV+=
+M<;G/@^:"BOH4;88-(FS"$E(NPRKPO[R_/CI$1*W,O/AV096X]>Y_8L/Q<,3^
+MT,3\WK-@[I:BRL%SC&O12WO0%ZWD`C9KY_%>(1DIN[`:**V3K>%+MIRA]9CP
+M\2?%2.#ZU-E/$.*#)D5(BA(O0.+QV%Q9_L9[\8IY3_\-51XB$S98P[W12,N.
+MC'(=!NQX==7T%N,28.!W#3:;][J+%Z(?^>W)G8/&EBX$=#D%LU4*?4EXWYR=
+MI-J^?K;*I;)9^.;=/3BV3"LFM>-/A5'_(GV-[P#:;3YQ0?O<]>?A7#A+8!VV
+M?K":T;%M\=@$.K/HUO`0;7I=%",7&A\$??>;-@TJQS4K\4[]>6(XQ3@W(M)0
+MV1QO^781R7\*7F_Y7#C.9<\Z31G,.=XN2GV5A"?-.'\[LJ(>#+(VE=GZ0O,=
+M*'5(ZQ)6M(Z]_X\_-]2^7W57K2V*O!M6CEO5,2/.D`TXJ=)M>:C!BNU9BV,"
+M7@D(@JHJP/W;R'8YI>NZ)8`D^V6>4-TAF_?W53**LWW-_AO/6@#H&/]%7Z)K
+M(M]>M.^4-`BI\='%ZI"/<;K4-9LW@#ZQ?\=7S@TJA9#OR1C.>;!NB)X^S$2)
+M)N7Y@VDSGZ'=S2#\JYEZAVU.%=D$`N)E8>\*!TY>LO9TT:^I>O?,,62.%-U>
+M2Q_=<",1;9GG7Z$`,4R^\ZUZY$`%G%\<V2Z0[B*-LDDCHVUP#UVP16*S&:FX
+MLZLN#P<\<W=/<OC?;61X;T7<K5>76!/P1_<>VKUR48+X\>`F:.:0#14"27Y@
+M"%LYX^'%3[:'XQC70IC"Y,OOTK+B/NLJ2ALOJ((6:7GR:;<A)R*N7GP["0B?
+M81V^YJR6#400=LAK'XD`9GND=P1(7@X59WUWKZ1&ZN(FT<[9Q([FDJ'["LBJ
+MK:GK]1IBI`0M&;Y#O(*VMN;?4(O0IT;`+\?[;/"GXZS]QQ5;")_=91,[-E9?
+ME[;S7ZQ(I.?WE.BF]IT,P@RPVO1/.\#YIRQALW$B3QE6R,0,'^?Q*Y_%54IX
+MJ(15>2I)PGL"IE_0<IL=A6.M7Z"64H-OUSZ5M6>=#89V#;U.$WBT'+5!3A\W
+MFV:80&1R3M`&Y@3G(,`H!-]Q:3,[BN9JZ$6Z5)M.%V!L.?4YSH_OZ$(\2'[!
+MUF[WA:26DCKLP'7Z,L&!"TB4=U7Y2EYS:#C/40BS("*G0*BA@W;SMH5Y!W>/
+M&.2^%[B:#!*^VYU'6'@C7%X7&5]@1@86,_TO])ZW>^A_ZAEO4(-^2##A3R1$
+MR9PE1@7E2&K9/$Y,C?'P6N3(^,XPF"E'++\R0.53UJ<B^^Z-&%[XH$D`V#_9
+M3L.,3_]HUV&2QH7`Z^C`"OF:1>#C)#;I8&5<71SXTA$2*U0J6@4#.%@LM:.?
+M6C<:W'-Q$A_6`6?"*Q#F<N?1/L]C[:IG`M3.<"_QWTF/U2@&6U2J!RD)-('S
+M*!J9U!-F]$AXK3?'J9DX,NUL7I/'QL*&*K%4!JWWIT<`L_9+PK>O.C%<:`0[
+MR!C;F_SX/.>L:,&G?\?'-Y[M5]G$*TQ9Y4*ZTG8\^=-9%[QHD1E(MT@<8>T.
+M'G+'G5:@PC78'2DJ?8@Q1EZA;G"+ZTIUK'^HV.8DFOD7J&AP4>TG/V=`@KEV
+M^_:.7A#SQS*".LIK=[)LEK;J+'KU'>4^9IXV3V&1UA,QI&;GV,"\%RS'5F%%
+M:TXP/V%U+1-V(<*^/'R?-EA/D%;KR<)X!=MTK5L/-5=-[Y:IG"KG.]\8&P-W
+M$/?^NHM'-*O#36ZVG^E&X]&)JY:;8V>@22]"OK@G:VL;6-RCR_?6[5HD7Q+F
+MRD(.6ND*NK"KMF:"V<@)>^W<`:NW2%<!,KUWLCB9*>B'V+T2^XO4Y$9HH*`F
+M%$CDT)_H!R26<Q1'PHUX)JS_R*3C-F\K<:"L54G@'Y.P1F_N]M&C8@?J*?\]
+MH;\K5O;D\"'REFOW6=%K5EF`^G.YHG%^E!U%@MODA:@AQW<SB"2K[6]RE%41
+MG^]=M72S?$4HBK;\YNJ^KN'ID`&=H#IX>VRZ83(_&(MW"E9$HRI>[RH>C@D&
+MN&@O'&0XAH5G^16_"Z5=Z\8-?G[(JJTSNP4[3/+:B.FC=VHB.TN5T`JU85;%
+M5W\(0!C->T+*.$A,;@/Y7GPH@``B*O8\N3)UEC_K"HJA.1*A4F(A84+,X'GU
+MW)M#C$;*M\V&;+[HE-[P:9D%%O>&%Y`5H,UQ=C#GMYR=YT.++;2S8O1@/Z/L
+MC%2D(QEH$7R#:>"(45>R8T3'OLI7=9&Z=KJS!->T*7T>'<CU!DG%JG:=A?NV
+M=_N<8NBB_9?D@JE[=_&XM/(C;B%E_``)X@%"I/GQ>XQD3#95QUD,ED"W'*KE
+ML2Q5'6(8G<@_(PEW[JS$HQ-E*4P=D9(G5#V_B+/512LGFDQY0S_3.9&A4=Y,
+MM\)YDA2*]$7MA-96'\?<?N7.:>%K:KK?`=#[;FW8>P\.Y>?DD1O/J%6$<PE.
+M`"I+8DR7^*;YCW(B.E3_J@J[SVYD/'\Q7R03^%I$);W'#/7CGJ-1F.0I<F%G
+ME!QLN5Q+8+K8H6FO5(#<BE@\LD;X]4,D,FYH+(7/\ZO3@;2F>;%%4$YG*[:N
+MB]LY+ZD[=CN%GFI>3U_.Y\M8]MBV95Q(^F!3./B([&VSRXV4K</PO&X<$K,'
+M6,+&!F*%+S0Y^5#7E++[;YFJG38N^]3'=1(U5OK]F5(JX"<;>O]%-VF6RH3X
+M;P.`AAS<_X1YMRY!ZCL!.^U'-!A";Z[+=$&7!A-/'@:N\/%8G'/@CP,2(OG:
+MTQHCMI7&%!7I!Q]JKYO[G:9G64Y_->0OT?/M?<]P,,F^F1=!EQXD>/FL2Y:/
+MX:I6)Z8(/N.D1K<RD;=9T'A9084ZMIL$:+\H?/@11J]2P-*RX\AX@;,33/5)
+M*,'+F7"PZT]`1^=5%:?A%C@";^+T<5-Z4^5'JB20I4Q?76$L1987R>G%\3L.
+M-^(]GW4\.LI07S))_E\";.TQ_K%]#[WQM?^/<L;;*^'TR+VQDL^V-2I`4GLK
+M.XBD7#=*Z0PUQLL^#NZYIP9KSVZ0\LWZ:J9^5/>)Y`2<-#?,])SX0_@&A?'P
+MOA.:`X+ZMQ2T,HIR+YRT&_@.7^8T\*$[VI8[PYG1PW3JB:J,H_\>2F1+)^:9
+MJ+I2QJ/+>H'U1UN'H:&0$I$31L_XPUN-_;X]B$/>*.U3*`$FF'85I,DK3'Z4
+M(#=V(O->FQY.3A@K$U!T8[)M^/;&<3LA[_$6\O?$;??1+&<2_HH]4J3H<+A/
+MI@#MA\3]9`8TR"5NY#HNF\WVP.\,5K1)2(Y:RA+P]I!H0&9O9GCL_<<MP71C
+M<]>$Y5#MQ56!+!US!;SY_7L,'Q+/@&N:5X[(A@HOL^-GWV0@%Y/KHLI_TS&D
+MOQDKVU%3-69(2X;FH!/+J+MRY02L]#6/-%9,EPWYJ3MR'#'4JOHCM`,/;DKE
+MA4T@M-`1SD/MIR0'024^7`1O\,VK9]M61(X,.N\@;K'56L)_4=D[9L`-=^=<
+M.40.1C&.D#>P[??J$]2RP)?-GYSEX$G#XRQ=\_5I<U%F>+UO'%I_P(BS.R<S
+M*D*3;W:]9_G!UKX[Y&QG%ZI)-A$Y0%[>(WDB@A@!+.@#D[P[EH+\$N+S!B1Y
+M_#87K?-,SSDYC6HU<'`-;W\%@+]T-<5^>@&T-Y$N3*CEOBP&_7+`,7A!&'7@
+MNBI`3S/#@"LSD@JC.41(M&V6!DJ5Y93WS'>C9DS7%]%AQ2+C.!Y)JF/%\IPS
+MS*;ZNV"E>GCQ>6%6HJH/WV,;!T%I_ZD$:;4\([%E4EAGX$:3*:EX``R'K+,.
+M75,`4\GCJ8$+P&969,>R_%&8S0^O,NS6V\\[_E@=W"*6K]8UO#3%<AW=+37'
+MA5G3.Y:?T=J4T3S!;6_:02Y`GTK8U>WWX"V9`D\"]^)9".509G:&TYHDUQ#+
+M68I2LLUA5T=U,'7/JA^$4T<O`;S2#CLR'M)(/\E4L6Q5'V%N+J8]*EO!_\W[
+M#W8*OI7O+V:`G`Q].2TF9TGM[<]>7:N>@JC0MJ2(8B!TMGWTC]%*R3'1JV>7
+ME#^7DS%XVN1OY:ZIK;6)?GBCQY`3MM[GO.ZLIU$)Y:"1Q4$F]0N+H$A!ZZD%
+M0J[&CK>">H-&$6UJUU(;:YMN<N9L08LFO;).:V@R%@_MO]X6A;B_`IQ].>-X
+M;N8<+T<GOTS^],Y8@&L]9^H%T[+9DRC!^0#X+G+JOZG\?2?AK8XH->U$QS%A
+M&[Q@TY<CW<T#G6%IJ$P4RH0<%(0=H?M,W67#M5>3"NM@MYO!.(\T,R.;&J+#
+M^UO6D&KG0&VW8AG346=;/O`OP3I*:7Y44VTY2ZHPN#YJ_3A.B6B8SNQ_T,RZ
+M0)[^/*&1A\?#3;1_9`&2#D_?!K0C=J?@W;YI%)3Q2U5F[*H%NA9F=0)^7,CA
+M5]6VOO:%S#N0;T:0FK;'#H/!<1CKQ[7T7PK":A.I4A.L0X<#!+Z`RR?=:^X(
+MXA@3>>K]976/^]+<2;5$5>/B5/8N(`H2PW2]51UZ_MMM\72HLH'3IF9B>W9J
+MJXE8+J&Q66<7:QAFA"^23YL@22,;-42U31+QFM_%8[E09?(POX'3Q9>EW-;D
+MRD:C^N@W8.2N2Q,1W+<(8RPS>&;_Z5;U+6N((AQL!]K@G>GX=^E2$6_OY.S;
+M.YK79C$&A#62,+JK7+>+?SB$(6].6N.):#Y):Z?ZV=#/7K$;WOB49+<T^^H(
+M6P3DHG.CK[&OX%T#B,TS7LSXO/@/I%/Q>A">>-JJMX;7&(HD`:ZMBM(FC8F>
+M3(G$?DC/G:D<IN0B6R;J%J]@KJTQ4\E$Q@[-*MW5!9*:H8\S>P%/N_K5N>T7
+M0'TC2P[[+$J3>!<YG'S#HT1#&:B.US]#\\`)WO%!H-?JJ/(.X9F<5C%%9%\%
+M>./)/031#N%8T0UM-WD=#(38H';\*&)K(;(:/4A\*H:WU;[[[/>0P6!^$SZ=
+MBU4B\3COU,+.OQYH#RZ/O^$2@VERP><4=R!A,+ORVNN*_67WBYTI`^8O.3-^
+M4+Z?W?4(;:_+4`,BVE&*U]FYTLR:;2<6$L,VPZ)G]?@>\0QZ@HS,OQINQZ5:
+M3637F5E&]#?)[>[?X5,)L-+^[L=D&3N0>#'C>KIC`<</JIFWU=-W,F2M\-74
+M%<*51Y+[%OK@-:V9W(W:)O<TP;3@J(K^(9-[)0%)]K70T3(V`T&3^IZP=)RL
+M\%&$N*'7WQ3+/`$8\F,+4A4/'#SJPM8M+GH-OKVFQT@I3R]D?#M_)6]\,UN^
+M-I1I3IQ0QE[N413SQ07()8B-/#\<O'@.W/2,)Y,$<K2W:)/%H^E2/]'C&:PR
+MLUS9-&,;K>A4`BU#MD*X7E?Z^1,+UX5H\,UH@O[D?81E#P!PQ7#J#Y+3C@.2
+M<C+)7Q(,2SU:T^Z'_ZK9X+:MH2725LXFAU/S(?*ZJ_5`I=GE@M=M?(8V(4A@
+MD_@`1(W91*MCK&H=1=%H.('D-B<B<C"I*3,IFIVC`Z)G+Q!NRU,<_A?YB_H0
+M=87-D6C_?811W#$[I#1O3EL>*JNZ17!U&]HJZ45+^@J+K_)IH!HA^O&"W"=/
+MC-?VMUWI5TW8`:A!HU-&DE>G>T9;2AI<Z)#-9,VO]9F/59&%S2O`1EF!F"NG
+M&&X%DO0S!Y2VW`WLF:J8$`Z8(S;.HF7/_"38F]5%EQ<^(I4\O9\18<3YO;1`
+MUZP9F;PKB?93I0I`6-)N6YC"IHP,Z<%50S:MZ^S@T#CM4,L=\T>DL7EJ?=8N
+M9?0STL$XD7C"*I+!@_[&G1?9DP(`$)]+)8,7L5N\PMUZ(1DZE]&9L6`G]Y&,
+MAGS)'*SRC.Q<,HE&@5AI)U7@4]]>OXE>R7.")N+J%Q[)<Z]?3>E9(J.40-%Q
+MYX[3(&5.*<7R#:1=U1;,/4-7'Z%3*-6WM2*RJ:RWMU['VZ/"5\&=I4@MA22(
+MS1IC,A-*9./T&=!2TUCI#Q4,MS4;Z"T5(5+UF.7-OO.9$,/%\B66^2.OR"(E
+M7,Q\$?O4Y)>>G67-K"DNE^DGJ+H#,_V09^'MAA7=-H/\!>*\2_VMFXE3BZ"+
+MQ?53B$F@:<.`-D(<D"YN\QGDGRQ'V(H:=W?[;X(?TC)S>.T;4NRCX-`P2UF6
+ME&KP!Y/Q!=0Z*/'KOB$0'2Y2U0/Y]`B:W7:C+-JY\Y1<;CP,NU5`1OD%\IM.
+MC@IB8E*9M:BAW0JY&YNISC<<K8/]2'PKS?5!Q*Z;=*X<>^MJHAXF9LHC<Z##
+M<*0-U`)^$-0J`TEG]2J7WK[XH$1C$9'\+CSNIR;`*VU>J\0`Y%]X.<CX_4QJ
+M_VFTL,:C?/F]<*:NW]MY!6+B9NKBW`_J6AW%QG0[MOTV?)+U@AHIOVS*G5AQ
+M/F^D]YV*\2D21)@KCD/=>GOR]YI!D/?SG]YQ:D6R_%ISD(#((<YT`9RYLV4!
+MKIBP^\5,C8]F.[NXACO5+SP_!+.MFZ5U'IY*:3>QW/!EII;\LB>:(8W'I'#R
+MA[J-\A+M9C!`DNL0SEW!,@^X2`PER<EEVQO5FO=-<U([>CA=HDA,3O0+0@6V
+M*JJF.86W+<`?&^[7R<W=49E)<=BO[.EVJYWJA$"(2/"Z&J?JWGH-A,&]#$?R
+M[TZ9W(IIRE,]Z"Z76=NRX6&+=0,-0UVX!]>:W>KH[E5^IZ'[^Z-GXK'FR"@]
+M83;T.>**0M/.?"VM2^V[9:\I[D[<^^K-L"".F9K5`V6,M1ICG([*SK0@^P+4
+M\+25$)KACNIG#>K%SV13B@>JY"FX990[XQ<U[XH,G$_\,P]*JG;S3U'\S[SG
+M?"#EI+4T^7%10XA2&`1$/J2SPMZ2$;PM)]A5`K[W:U")8].IO#]2+HHTDP&9
+MW3)&M@2F-"*_?1)>\0?\CS/*HED;ZQU0EEG1V+!H2[`2V]R_TF8GZHWUH<4-
+M_@$F2B<9L&[$<,*G5CA"'S%,])-/,)"P"OOP7JIK&2BH;%V)`@*<1P]"#_MO
+MZ^+=?WIZ@3R;9C6Q'%T0;.[P0*%V?0YB*_VV2OW'7NU]8U[F=]IUU,DVQIL8
+M5V-;^L_QVH9X=P^ZKB7_HVYN@=Z,!A&E*YG0$YA^LR>$FU5U6@5TEQCP>4>:
+M+1KYNR%F,<IVG,C8Y@5A"P/;K,%)U$A8\.2#RG-/3';R*^I44PA>(\=%.*9$
+ML-'.GD3Q[WY0RQPGH[>>6^+34A#JSZD;OG>&0'W37.@:%_.2`^HZW+PR:E6I
+MC8P`BK%EQ)$(\DS8]72O*,WV:9P%ES^,T\>RE22Q3,SZP9.O219)E7R%9\3W
+M-JGOG%$WL+S#^0D^T^<:7/<SYKN%$BD=#2K^&*#I(_F"ZZ&G%INJ&3>A7N%,
+M$3/OK8A)7OR(/.&E@9#>C$9<J<>C':DGF#!5UAUXPQ1;:&K6V=)/1[5R8V3%
+M<JRH'$+F:TG&RQ_D1`T6VHD@=AB=?'H?DFV]>V-'VS>6\@NQEB#;+6Z1]2<+
+MTCN$408(\F2I069U-5;\QO!&^"P60JY;')WZMF9K')J$)>H5D)05.(,5A`Y#
+M@K^STGC#E09>"D3@3WS=@4@V%3]^:K=*099J]@5_ML)(2)5R%C9LR?C1."C&
+M;5>654=R$E]&>5ETG'^GNN`F_F'U,"C6;#T:?2ID7^+\ZILJ^#+]%(9G>Q.8
+M)\4@.%M&+8;Y>0"['R4ZBXPJWR[4K'[/=YL]/MFH$[RJRM16Z_VU.SXE]JQ<
+M\F[>0Y$LT0K7[<=]QY9ES#VOPS'G0[P&B%AE%91SK*XQ+5VXO`@S57^2=PP8
+M_,JJCC0VMJAYMV`2@<Q^'(!C:[YDFBJ@B+(S=V<!_2(PG@+P'=..F,=>3>%>
+M/&75%:U6=J<*8+='XB77(T%`02AU`&X3(X)V0JLH\:#+!EGN+V%7PCO223K&
+M\\<FR)^[X/E_CG[?,VHDFP+@P'-Y8'SUZO_`AJCT/]*1[9[ED.&5*J`]3D'B
+MRA.\;EG!O8"L'U(29M+*Y8O$7?]TXM[A9AIB!<BN?U*LOI)0\Q4Q4DT'#K(P
+M)PGYU7#++Q.F6MN=QS8]W#9G)V%XR-BSBG4CM]ME;)GCRZ[*L[RJ<+@-MV,^
+M_E?#H]>JHI*C*Y?QMM7J^;457+JJ"_X6GPS(8W%?K&L`_#/,/\`!NJ1)9W.U
+M@J0IZ2JBJ[IQ5J(T2)IK.<&*Q$,3\9P502-.5#@PQE'@0P&K3G(GR!O*:"<?
+MI'AG%OX8/G=6PK=Y>*&$_M'V=4>5]:>WL0B%=IBA0VW,DOQV(!`XO$&56+A%
+M-'@(UJ[#X\-%?W:Y/:-KQT?H4RU+TD$*Z0R)>L`YVI-<^_;[G@YK3UM->H"5
+M+X*'P-6R3N%1M:7SP?X);#M\3)^#[+7!(-HR(/,F*!%&*P$/)XD'`GCZ$Q)?
+MCT]9M`+^B9&M2W-3'AZ]A?BMHCUK+2^\]2,(^9&-IV3F"OS>\V7"V'E^W-J2
+M$MG4(XVPQ5/)"T[$A7E)A'GXV$#]C-YU##`-\,QX)DXMX1!ULG95@'3WLY9Y
+MPI``B$/PX6^%V)49,>^3VN9:\=23IZP?D)P&QKVC(EQT-'MTV8<HU[489=>"
+MP!J#C9R\8U5'<:R>4;('\*!2;5O'[KWXI*B6B+%2Y+@T&Q';/:?SD6VRV&E@
+M8L_OJK](9*2)%N9D-K7M`+@3$1)9)%#()WSKC.6KBMWVA+P>_\;'#@6W9-01
+M$]A]N?*&`M&EJ5WMW41!_'7?C<H85YZ-8/7EG!-[EAK1$1?,S4@^K"ZI3P6@
+MU_QNC'%Q^,<=YJHOW6Q6KR)*]`V:3]CRE(.>B6$L>IPH0TDEIC&W8$:"4/AD
+M;WE'2=7ZK8:L/]2U(31ZY!8/T;-JG#"3;(ZY'T-4:831*027\ALKKI[J.WZ`
+M<T!;:Y7\DA]SCFG;["D%MNOZ/L=U926E6),*K2>?3S$YQ?`J4LUF>^5.2-&2
+MY''&AU>5C_'7(EY66S$/UI:S'X#%UJMZBM7<L*QR/$Y$II\*^Z]]DMO&G/GT
+M;6!%H@AD^\[>X^4@)%SB'V7=04UN0'>NS4Y#Y+V;Y!08J<93JOD^MNLO_>?1
+M9I&]?(@6C9!46X9EGW`OF*()ZYQ)@.S[J=KA(_YN)GAFLU&'J+WJ-/TTWOXC
+M0UC(JA.'[U;(X]`CQ`TU]_OX9/ZGR/U,[6!V(!B4QV?HJO/-=O<*#+:7&!"6
+M"4?O6(0VY'6K/K2VXP>?SR(Y@-QUV>8BUHT^;!,3&WY(7^)_1[G^*D"-0"I0
+MC5N)?03KW9\T2K3O%5#$0[N9>0$PRQ'BC3%R)K$:JZ#S$M%$6Z*R._]:D6_6
+MD@U*%7IN\MIQK"#BY>Y;X7['"QU;ISL&-"3!#KV2]N>5J]4CBLA6S$WMO<'U
+MA]8LS7MUH?+V'`2_6L`?@Z>ALILBA(;:9=-0O7=EZ3<7^OU)1B_G,UY:_WVE
+M!J.3BW>JQW!A0!%<ZJQ<8[L<<_4-Y-A`;J+X:)4H@SJ-A4`%=(PEELL(9TH!
+MV&S-F.$5RTS=M.+K1B[+$T'F:WB7Z;H[57H'UT.UMZ@AB^T97Z.W-DGCUKD-
+MUT@76$HQ;9U_YD#*?G6LK2X!B-SK#-GN>SE''<<4.A-U-?URW/&(9R/U,(8S
+MW9H2U/*T[0VP:XAQX,&1EZA>B6E_/J$+#TBE\T$D'9H:07+X8"']M?]>]0AM
+M_$TK38WWNO%!\C?M,7,ZE;XY@:8D_C>^`L,),#@U4>5'JVL+$<,'&3F.:TN.
+M?^/3L&1S5\M6/N,QQ']`?"@4SYEP#ZN`T5RT27W"F.C+.Q]^.K;*`.]^K%)3
+M26MBJ%O[!:\@^,JTR@^OI*Q,IH$6J2*GUD-XBCNE=U4N?Z$YP!EPBM0BZ,_9
+MRC.6-=RW%<[=D111A2<A;#X*%367!!1;5"O-=WZ"GU;UQ*/FV=-)5(5FI0;L
+MN1B.!\`G[;#>UYA"V(UQ.0:)<)P_7W:O"JYV-O/W%,XY.+6;,KF1/4TM:@PR
+M&.LB[J]]5A^`XKYX='X;PV:K2^[T]!^1I1/+&*LTTY1",]X\/)[5`$'46"$0
+M-GQ!U7\SC&<<E8HXY=AH.IC:1']C2VKXZ;V_I*_&8S+?%A6DXN[DMS$U1!1&
+M*ELYNAF:L=E!TG-^&1TP'!]QO:3UTVZ<.`F_9SOO#EX#0B+S7:9S:3,CV63M
+M.I=+[+NM4A%AI1=5:7`O(*;Z<(&T12X1;("IB#-EIK8J+EK[JT[=SL[JSP)M
+M[4+OU:,'*NZ24(+&J\6^^(IW:LEYK>&F3C]VQ'FFA(OPHCYL55*NZ!JKG9NS
+M8OM#GLVQN[FL'@`E@#_7!<8*HNK*2MXU/,D!WFXD^3C<2\.GP8KSA6JW[)JY
+M>+=5(],!U76-/X">Q./TOXW"KY7A]*GI>F%[V9.@WO@UEQK].'/0#9#3QI84
+M?`?KZY.B;+[+=-53DZ58:<->"_'0MU2\%0`#K<<-=--!K/[$99-LK-F-)_PX
+MP]-N9V6=2BNL,$>H^PTPKMI'2"-T)M"WET_+AU,IK^O!O_(_AY)GP#PAW)6-
+M\KU"A)=OFIK%,-FW2G+US6^1T]7GEZA:'R#>\&VM,=V/M:Q&<S(E.$H)RQJ<
+M()A%VUA?2B&-4H%(ZLI5=`#[;+H7+Z'UW,J]:$5`]%C.$.,_W_;KIRJ*/JX^
+MG8BH_+)7/ZF*O0@G%0GSGWBWQZ?BZR$E+GIS[FE8K9T_G;#;`/X=H_1W==A7
+MPF7OXP4IAT512Q"2=+<&UX(6A]M6(I2]&'N:"G%I;M._'(>K8X")@Q+!B.(+
+M;5%AJ+/?_V.N?G83%V%7+?RR4@7*H/C>3O%3+>"#E-G[IQ1X;6^/N9.Q0YO\
+M_IAO1FP+^0>ZBSB"Z@F/?V/UQ+)3^&)^_#)LK-K-,-`PZ:$+AXV[BFO(S!;H
+M%V=B.1`YARLHR?+"C8(ZR.S0?3?\+T-6[<Z?'ROB@;3"3DH:$KDLW2OT/5%N
+M,46(PVFSG-3.;A)VKUS3G<QQ3^\"].,["3=&8,&_P`TK"C<C9?Z/X'\]KKUH
+MKHT5P>S#`644/VYJT7C+`76T93U)=^<JE4VL`/=:99$QKK)%V$2ZK/9V4N@)
+M.Z/(!G>8&WRE*BMT@O77X"!Y+$N3"I9Q0D\K%''4Z%6'&,^FPB/^J6)<0Q'U
+MQ<)?.W/>+P*9Q1RNX2WEZ&WXU>Y)S7Z=@9FR60-WCM4<F=3<YX@!BF$(S)X,
+MJS/#>><>V/G9QH7Q)&&I>ZV'<]E'7-_69GIQ)6_>/AMN_-+L1"P2+RM?3HDO
+M6A/+VO.27J92PJ1$P%^KE6OJ">3AE/Q.`J#5[<%=J<//]*IW6[XG#@$C+&CT
+M#?_0UE3$HY$GZCG_24']C1W2=9'%^78L[U:/;?4-PZ4K,VT.+:C#!R'`$^1W
+M*<"Q\(\92>\V*K[SU^0,^LL!5'NH]W39N.84')"E2H83:;\OG::'@@5%FB6"
+MRD5DZ4:::]9ZW-A7\=7&K"*K^'^D,T(Z.,=?"OUNE:_KS_C+PTI;):FV6ACY
+M@UQ68A`';(UHWY4?KQEGHA&!0LU!DGATXJ\,2??I0T<=>8N[DROXK^BKY/@Z
+MA2#_+1MZ+]7YB\^)PY)Y%M7`3"89N:&'1KO;#H]:T,%.1]X%%"7?U`S@PFA'
+MA(+@]K$;GKAD6RR3)A^-2IL-5XJIN+C837'3F:NJ0,MI/&^AF2GB71"CJ;?8
+M*15-0>DSVM3WJ]+>BMU9)KF$=D9R=GJV8&$\'ETDAR-EMC85S=8\F90%L[22
+M[H\\[7.UXIP&56XC_$*\N4N1!Q2/>\_VZ88XOL2GIN:Y4NRL0#2Q>J$-!W1-
+M#=*\#&H\0^=<U`A56[(=7*&ORY%[-IL^1XV\_,+##,^%A:<3_CI\#!84SN/O
+M"IK/`?L2.LI*II!UZ-/J)0?^`-$+DZ"K-M/'1ULAE_9GH:^QJER'C?^0`IT*
+MS04H:FKP!<$*()P:R+^L5*CX>8`I%?T)E@]@W^X(MHIDC$83E;'<TD.;-$)*
+MOWB[,R=!E!5>T6(0MYA(RZ8(C$ID)7H\B-2AQ[8&>(N(X5`ZOVN<F7%'#8V'
+MJUVZ_WU[\7")<4X8%A"TQSE?^]>[^)X/KGGC6R)9XL`K`S:O:T:V3^J`UFZZ
+M"@CS#@1OOM_1?61Q8EZ9]IHV]13+7!`<FF,B9-$G/2J1N0<SGO)96T0W'Q4=
+MX`1#B@R_1PSLYR45JS,0(FLD(QI.$L_5=QN)(5'6=8):T)DMK\I/(GFX+`^-
+M$)Q8OFU\.-9:[FUHN(.<D]*$@NG)^+:MZFDMN5M3%@!-.XQ5<Q)AZ<\4LJH'
+M\L?'L8A31D&CX0KV*R$TA'R#(R2U";H0:)8R9K4H@F<$H2_D,Y<?D^#6X^B^
+M'(U^7%=QKRM2Z(;FL6R.WLP;@@)+[V/KHZ/\UZ6'QK]^>J@37X%5C''&VD@W
+MY3CX?45HY0<MQ,2!5]2?QG>B!#^9.%"DH@46U/N-_2JRE39#+(&AIQI9U_0\
+M9Z?V("HS"R``]Q(B_YR[R,.GU9#4U=1CN6`U4=;7@;A20HVT^R68B$\.=,"5
+M0\UT,;#'$P5X3Q'3#!(A1?`=-OY37%%>`-*[V2841UCY)7L&X;2;!E`?>>==
+MFF?1[CYI<"L:"9*ZYS9?[TXPY'3^O@CE0O&D!)'3KM)?=Q.71RQ1!L&&AI,<
+MSC[[VH\!+[(R;PV^#"),)]"`VLC9@NFVZ7>C<IA\G'R:V9.R_UHA+V\VU(SF
+MBC9V#3C[0+13262VVTL`D9A+`529%Y`O[-7R(+6O)6`$D[2QKQNZ\0[Y@M=S
+MZ"XGS[[\K"?X;7X&T@O6-G!4]5?#R=UC&$RSQY*;X>G19=4D\OT)/@U8P5MR
+M=[/>[:_]I66'W+VGHV`GH)I$_',U(`\RLD(EK4N!A;:NO#VWC_4N,Z<OU+NI
+MJ0%7.@Z+>S]*83LGAZ7(BLTYPH8$6*&9P%<^DS;D6V7T:.<;/40^7RH+CREB
+M2:$W[+R#:I>!2G=&N<P?^Z5Z(X#,-P!O>@#6N.<)NC0"(9))::H[R0C03L%I
+M)A<L"%B+@FKU>@R0C#*:-ST*-0@3'ZH6R*N4L4^\5\OED]?9TJ'Y'L<6;'O<
+MLG^%'`=$W#<^R",A=$LJ7=1"3KH]UZ"^>Z?V`-5UDL_M;R^R^"UCKH3#<F>[
+MV0HGI5CV"OWF'66V5YK)/?0#U^0]MA@7ET5S(`S='DOM'0UB_2;1A'4XJD@B
+MT*N\X*$T\C!*VA!IC4WBL>M"UEP-_;S?%0J6?8(YK%IW_36Y`4J_D9G<PFD-
+M-1#T/N0M5I^U];NI((MMH'A)L(QH%W2K1;P8+MWLW,84HHB!LJ;&N<13AQB<
+MY-K#(Q?\2G>$E@Y*(F<(R?Y^H#GLEPZA_F,/#9#<U#4#N@?@/W:#RQ4<H6:S
+MA:32R1>89QOR+C<DTQ>A_GH@[:7"1#(9:`3J91G!??HL/+CP2+U:W9PE80DY
+M</6#?GKN)6GB0AE,__4L4K..$M/#(Y<=M++7ML-K%&+7`&U]GBODNT@,G6%7
+M!>O=$5$^ZIK<,MB5=5]L=)7D4"*DC&]<50;V%UKI4$U*K.Z77T!+>W<2$HG/
+M)M[?^Q@>\7-\$-B>!.#4^U^_>Z"C]N:.?,POL]ULLC>9--4;M<Y'XWV38KIU
+M`WY7J0Q.`J6L*V,N8.'[8HYZ;9A/K`*:C`!.UG05@"N;)6V0:Y4-8Y._L7;:
+M-05ZIOE^]<0,HHOYU]J/'.9`>VG?#`>*44E*EQ>Y\6.MMM8<K'U$$)F30*L+
+M@-,F&'>>^#S>35D](AA%V`M,QF,DBRTE\@ILWS46)?_(!L:!EB=BHIG@)+XJ
+M`H%;T[K[QD'ZY2FI^QIG3,8#:%)HUU[Q*R:SM[<1$CS>1<:-WM=HH#I.W'/F
+MSY[M,>F(-K2/SOB&M?`,:\GJS]A99R4$!IKX<J:[ILJ3T)QV4FD-F6<Y6W(I
+MN^RBYE(P5MO7%.6-M6LF&&-WH'N&*LNW!,2I0W_^L]^ML&O/S"4=%67)KMF=
+MK<61<^T?J1X[?FOM"V+,S_SC3NI0OW<W#7\#6=4$17-]7",2?:O**U/>0I$^
+M>Y5MO%>`XR&@HP9LIUB'!`W=JRR;;]+FP2+[*`4V2)X*2%/A8V,6F[,#-,-X
+MUNI&EY&,_:?M1'FM!Y`?I(6A,)8):X`^5:Q9[\/)'$7#EB*4&:A-:XD#'F[F
+M>:GT/XAH8=!_$8VZ::B5T(^J+%C3>];#?=%QZ>O(A8&TKGI?2->]<V@>O[2%
+MBCU?4\X."`R7/3K7`N2+NJA=+\6AV]IH@0A=I4G,"2HLEQ5*+4PC0"E\2:-%
+MQ:@X2?N@&'&CQ_OII(D'=`'7&"O+0T&=(?H)&64Y%7KQL@^^ZS8).TB;8:HN
+M$K@2;R[EP%5[<8M::3#7#_2E)@XNPS"@N7;"PU7EC12^9?/-:+Z@F3))JO8'
+MW^PGAX/)M&PX$%?#^M\V,%4$J)2EXE5,F61TOJ&Z"MIA3I+21MI8+/,WVT:,
+M4U,-\6]14&&4,]='>T+;8UZ+:?B]#X8Q_YYQ2Q6L<_?ON%'<LD%7%`1G?P+,
+MYQ%/<=4J=M>_3EU6Z4COD!7FHGZI*H7%FSC&)*QO.6AV6GYD=UE2B</HTY[4
+M9V]U2>A=J:JFFIOAM)O&RG68OA>4X2'4J21'28,VA="`J&G`L`''_RS0_;&-
+MGQAXYB?'^_=*=BQY!IR\,AQ+?SM)3R'$^=DHWE23XGJ$7!*Y9(N>Y8RSY\2-
+MW&4<P(/F;8394KC<+$@9[>'%#4,M^)-6,-$T,O9P*(8MF=%!??\T<G,J1Z<W
+M:>(M_SL9^?OZ824<['@5H-5DBKM8]N4I*'NVW$3*(54R*""+5/6<3WGNN=6[
+M>7#7FX7TH,%54GCC1.)HJYG`Y-+\*#AJ(Y&A:&&C.F&D%VE1QJ4T^,]@F&1G
+M;`<9'&#Y=MC%!O,NGNE`<_?EO577STG:Z9[^@7#5='Z4$3ZYB\SQA**X2ASN
+M68;AUZYHBK&"PW4\3UP?CU$M)]=MW4>AD"/^UG[07EGT:F>'2D8`KS74"M[?
+MZ\DL%4!5GLA0_#Y([??Q3LYWM\YX*6!&='$,O\"&*N1EQJ\F_D0VE)&"#"J+
+MZNITF6'WH1\E_\%B6=,&7ZT:&1U=V#)DHG1.H[NA[Q2)@B7<J'W7_\O.O:?X
+MEZ"./DP-\\:#ARA@%0CR(NBM3V^?)T3Y>XJ\7A9#R4SN6R$1(#H:8MZD(M;S
+MH3YI5$]L&4R!)Y=N12-[(Y*1"BW#FLERNJ-1NZ$PPEKT\&NXIE+B3](.7EO+
+M'R*^Z"R#/5L\AZ)GV#+;&1EXLXK=1ITQ'S9PXSL8Q!J8DH32W&_\5%+U0U\L
+M#"-*U"!=W9J?OBODB41O;<MX*'MX`Q)M^VY:FR/U6?,=%:FJ+NOM+_3?25V0
+MU\&U09AN(+:12_R4@$NQ,9)5!#X80S%0I)-ZE^W?D*'\C6Z+WZO9E@.L^.W'
+MW0=.<UEV+=(2V,V#GHA.!.Y.HS%Q#IZS!MTVBT"?Z(6RS69Q73#T^=V,O!DH
+M`PAOFO!1'.TJI[=0I<$Y;GGS3.=_<0@N[_7/3FP-_<HCR":KL,8.GYU'H.7?
+M#\Q6"@:YF"S]!W0R[:0;#L1#!8-VT([`<5R;P=EZ?L`C4F*\/<.."%QZ5N=A
+M(^1'*/QC=U8\1Q>+8<4S`./IB\W:N'0!WK5%'7-Q?!G*:I\('SG`,:!D_AK;
+M;]3$6>&R4DUBOSEC-H"%8B:T;$)@D#M$P3#ZB?9<TEG?*'*[3P?!W]8GX94F
+MAH/$^7C]N0$\.!]]:\'N)PQQAJ3$Z*/=C!$P31&VL(CI@,6M9Y_[$B5%WET9
+MIV;)$7[:65(MP8,#A*W03<3&&T9I1IYA?*^SE+V4#?$LH.VU9$U>OH/A@PAO
+M!?J75;``+*RG6X2*;JL@=WI>(_WL$DYNRN+X)D3.7*B<[TD+/[77?O*EZ7QC
+M-!4A]R@&Z3Q_`YX64GZ;#,33,.?6:*:65>`(KSP4'<(*'^:Q/2;+;D;+>_5Y
+MI3\\BP7F'8J[X_Y6>8(*V>O$BJ(U@ZO+<N7]L=K2TM2%:VX1T.;NFNH3^.;%
+MI21W7?I^&@9V^_@-R/7OESYG(6.IQH4L4T%7K2*\4;J/,.[BEZ?>&67C4$S>
+M0A8@JO8)&Y4B64[1P@P8D?]I:C18^F/O\:D^>>TH=I`'.6`\K#$Q_",O.ISB
+M+"BKT2?+`XSJ'4!\_+"O?8ZB_^?7<%24:,)K&\6C7>)<76_.SELA#ZV$Z\$D
+M+:`8MGW_W/'U<EC2/$C-?3OO7`@L1W?:9YN7C\BKPV`H'EW*IW'>][&R!Y9)
+M<G50XN&]36#$2+,`<^Q8$"E'>$6>U_RA']J\+`Y30+%:-G9RPW?&1#4S<[3+
+MQW,_CY+))LE3^%IJ]SE$<!*O'-9&[?U-6;UR;?N&,*`F(.^/H,\UI-11_.KE
+M,F_-RBSR\&<<9C%EQJ@*ABUA'GX36DZ0L^<;`7E6K85A2N[T&$?-"4UQ$$ET
+M.X/,YHH2I`?PEG2V5X61!1Q`9EILBG`)1NEY&9=TXOTV<J.U>E2H[&82N`%*
+M?K?]!"QH&HV#'"SW8Y=-%[>CG6O'V%"M.XLFLJ]PLSV05)ALT]3@Q.9RPE$?
+M[WP3\H=<:5PU2Z]AOLII4-<%4UQP3K0IAQ98SX4>ZN!U8C62++0:?X2`5'>8
+M6RY))A49;3?9;X;:=D\$.F/64Z_>X`7R*FKB_)$ZU$ID/P&L_UV1\_/VDR46
+MA:5,GD=[QR9^.@F._A*RS_H5NN9P\:#T(BY;348QA(2\<^6G%G36(-3^YH?G
+ML-.U3NOA-6;/N&+'3NV?H(H4H$"B8"6^-/OIVO.H<%A/I;IW!%Z[>&BR-6-J
+MF]+TGD5J;E,^,KR%G+CZ^S._3<3J1E9F>AK&)9Y=#DB;@\6M&;3P?A2X3:@S
+MP.4!N*A9>WY.W:9G;<LEVABF1L)N:_M.B6EHS2JG'W@\WMS"G/D1!Q4[+6=(
+M>';1&V"VYF]-\_M1V$L>A'#A5_1W_JH%\\%"@L"VUJ]\!HU*I3J#<74UT*OX
+MVLH^EDR#<4T\OS"%$D!Q?&EF!E:_5#=2?-YUY:_'$NZ#G:<<GP/N[*%R#=^S
+ML*PSZG6MF^NIRFJ&!UJF+PIF^F*;5#_\"EOC'AHBJ=#+$F$L0W:[109F@[(M
+M:DV\,7]1E9,Y\Y1@XL/00&>96Z?GN`.\50?ZP4Q1O=?0`S3S_'&]<3]A'QR1
+M!NV*:-9LS(D4\_5SG]G\_K<9YU;=F.T,0JZPE.4`SFADM!7H7'P(*JZ0YFO@
+MN='@-5EJ\_019!"I:8PLGDJ@\2\#.X7==PY:%8LS'R9L-<LRT1#1*1>F9M6\
+M"G\?T.X:DZM=1EI<.5-E_>!['J%>*%S-_K])EI<42[L!?N,'8(@$G'9(.Q:F
+MX;(*#8B'89B2O:P/?2KB&\M->S;L?"5['+0_#A8PJ33<.95C%3C:E4<=2>1`
+MXVO-T[/\L6D8H'U*PK@N4,T>"6:".W\LR82ZAJEX@7R/ANA-VRNX@Q[0M95D
+M8-!]J"27<3U>]C"5.*)$]-VV7M^>6E_&Q<-<.6HHEC:%?EK[B0SV1,WQ&7<*
+M3VH)=YN0NW=,OJ<1`6\B']=+7V(E4DZSAA;9.I2E98Z!:19/3;*/J]&:A0<"
+MKQ7-:L-B^T5D96E9@Q*RY6V<S7.)3&*P*2E?CAC?!X(EFUN9RF7$TP/B/3MN
+M&Q@LL"-NL$0;,Q]-=*R%*9'_VGB3D@6Y6QM!W$^2J#KR!6OI!"%.>'V!3@O`
+M?VB=>_TY_'4%:![G@].98=':@)U[EC>66`,90-YP^FX9IHR,D,7U8+A'?I?2
+MU6[SG"!HZ53-#SEO..E];]P&1?*PX\^YC,U0$NXR1K=2$:(`Q+S=FC\1!-1H
+MYAK_XTQC7OU,9V@ZSB*P5KY4E1&52=M8-'N%[8%JY^:H4:+4SHU.,PU]]LIO
+MA5DR[/+NHE*<SU-(:H%O+LT-*X5.X?"&4(4S?',+N>,&A0'&TQ]*<8",*IXK
+MS&49@WZB4J3*_W$__CB+EOQ]"!6]$2-T=-[*Q4SVFE1AST26FEW[!YT0+AQ9
+M3\S%I659Y-QXG[?BZ'C)_NY/C7(RJ;.`(4JQF%YY!;H8V7;.>'@FARV$ZU\N
+M!E4/M6<>A2**A[.\E.VBFF/^B>6DZZ-`PY_VU[08[2("U7`#4CX8QV1RVZ"S
+M\VT:?W2;6$HC<&8E[XWTG']5CN1PQ]5,46@]Y]!W4IAG2=9K=A4L52$->6!M
+MCLS,+%*(*S^6Y2UOUOA/G4-&VY;:$IHPS%8$:G'VNI07C]/I$S@H>C/,@-5L
+M?F:$\C.)Q9^DSI16U6DUP1(*12MRU<@9H0?':C2:#:H&\R-!SY)/OV//L:R\
+MAJY2%E>[&HC*DVZVS@UNF<Y?6;?*HNV+^;PMF/\D.8O"(B6B`@7XNA>-V`&]
+MN!-)(,(KP:X[X_3\*>^D!J;:IZV'GU)8U#T&IQ-*\$CVQM>BCXCO[[_IXY*7
+M7V$^V"&Y>_HJ>*3=\T`<[,NL9LIK@#8=8?@(+VK5".IE.XDIKYAH1/3!QT6Q
+MF8STG?>/NR3OZHVN3I^HC<H4?%[F^S,0"S84?,!;&R*79RF#F+W\M5>DRZ]@
+M-`RV\,1-4`<'"1GA]!<^6E0FG.[^B%:"TE1R1C"./&:XX[X9Y8\',)?!2-65
+MHTPNYJH1\)$DJG[RQ!,?$84>_JFWHS5G25[/.#05I@.PN:^F'=G/QZY&]GB)
+M"^=2\(WD_2M3:0RC'-?'P(I)`=O+)3::!OM)I+`%GYFC0OT-X7GH)?/03Z*9
+MO-.%3:DN0UVI$TG,\./DC&4R3S\#&8(N8XIXB"P-7I1IFS9FB^4B+*_)`[WT
+M7:./XKDDS`$54D"Q![])F_:;AC;:B7S>RI.]H0R3`_0.\\.1C';DOI(BETU[
+MTMEH04]Y9<SJLNQI!D8\)'^T0PMU8.]$U1XTR9TFZ!PN\*:D8'X-Z5=8^6I[
+MC7]Y&J!/ZEJGPG<[+.\(W?JGR3O-"]##UN0I7+!PF!',&VG1.[*G(+HX.G5W
+M1IT_%I3DIDS"6T&YS#!TZ\]/RZ:=_<6!IB!D,,3TU*R)D,Z"1([Z%ABNAF(!
+M#^UO\\4YBRK;.S(YFS]6G*#?:S?-"Q6/5^%^CN;4)';G$*WAQ1J!2Q#$\(Y3
+M2!N*8N^HQ%EVO,=*9)KG0<>?ZLC7;>%IZVRP9&NC:-J29#8Z8'"N2R;*N;]+
+M:!G:=M/5M`3<'">)CO1:W%'V>&=L:"J91O\TQS5,:XCLBT_XW..AD_=3V>NQ
+M`?E'JMYE<\HR![N>IIT/?W-=R_G&I#S0#PT)D%B`EXO,>&D:4BX9'7!J,PEA
+M8;X-S^L.6?8"YN3^7W:C"(L,>X\V(^HM!7SG08F#5#(=A[N';F&S85;,^WCU
+M>`/#2:_`"^7&-46<>;$N:/>IZ[%Q\[\E)9#B=VR+7']<9XLHJ;(QY$@5"O"$
+M_$<F;'F^%[+'4#ZG(^[G,D.F3D9K0"5O3273S+O'#!(W;@I9TI991WO2<<9J
+MA`1C%]AVC*2L>Q\@*-9^[19_JCA52?4I#<V_!1STJ>H:PU]LM#7O,D_".^9[
+M5/_?P]EVUEU6!5OUOHR2+VIS:1PF!D^;'9$@G*ET2\U3<3MR11!&'RME'EL"
+M6F?Y(*S2XO3VO3@J9F;N(-(.[7`]6_Z))((K#96D0=V8ZCDA3&N!L^EV<Z@#
+MH8,CIC1?-[I<VQ2L1I6G'+/%%;W=CFI$I@K&'?[V(,HZQH3X`MLQ:1ZSG#2C
+M0I]4`J<[;&450P1V,7'%&M*ZNZ[@12DR*.5*O!Y;_6CC(>.#4>.FJ'VEE*?2
+MREJ61.3PEB>#8APQJ==!F].=$(7%@:&^$IUUX>8EF)/`5+]UJ]T%GQH5YI&P
+MAF#Q3'T;^/$\3A+,SG8GNA[:LL(L#=#N;&U$Z,A_4LV]*K-,]\X`8TK5;)H%
+M$[MLS'KTT]KZ(-B22FQ!2P^QH29G-L9=\!TUSD#FI!&TR+6B/E9L8AFO1.CB
+M42%3:5H6G,0A,@T*9-[OYK:2M.-J'WR(`C#`@8CE635>7&$G7-B])KNZHD\A
+M,9R,-N#:+#?PO19:"$?3I*)-*(74\2T/0!%T#\4X3JH/C=>VJNS@W\<R5>M2
+MTF+G<Z>L8:O;-5!>6K-6NF*_L?#S=*+YC;=Q:ME9GD_7,]##K'^Z']2APP\C
+M@04C?<PEY-`6D?OS#/E%4MPBI?X80`M,HCG5&]@V[@^!"$/W]\!/3Q&#00JB
+MZTZ1,;NF[9?"9@<,/6?=QXT;^.=SD3`VSFW9LR"C.WQ75N\HYB-DR^!;,@P7
+MN3QPO/2L"6%\"RQ:L?WT-<+,ITNH#U.>O*[0_*Y#5Z+.A&S&RO`P.2.\Q2`5
+M=%VL<]41RRLKDK5(\`V/Y7ZK1__O@+WET-:!L6^Y?.+UP:5,U977F';)35B"
+M91><<`H:5^:VF:RS-8QN[N@LK)(8/KV2;%G/^-?'X3C+'7<)F3ZIQ1#^)F'A
+MIW'+P`9UVOTIB/SGJ6TO22U;ZQY2;HZNE2`2YI4"G@H$M$E9P21-*J@ZQH2N
+MPW8;,>9^[?ONGH#%.N3"(#)LQ+8ZK-?IAVC5O5]N(RUW?YU?$T;*;!EA,-4"
+MW='@</<R>(6:MR:BK/!00W\,FK'668^*/11SI[=Q8VVU]I/AS6R*K]UU[RS(
+MV0C)2J[OZVC*RHOD0+YY:NH6(07?1J6KU^-N)!K<I_"LB'\499[[L^%0HKU[
+M^P'\MYHEQ]?%+&([?DQ%766!U+9_W'[CX:(@D/^_CQ?7-)LJ?VB],H"RM^SE
+M7731B?!+MT)!')_5#^>ZC$%^!2[2I0_<6&=XG).7?54GN.7M#6?=+U#(*E,O
+MWPJSF39;698N2<Q^B4F*>7?[ML>=ES`:2^,Q=O"W14E?`_"+]=B==`I%>#SP
+MH-^Q'XBJ=QA\V9.6FC,.C_T'4H)E3!`DXPZP?8DC:O_,]B5CK'_#&,T&8,';
+M%>"HV!!@]Y;.Y=/Z/"U9M$U'&_K&[IT9TJ6ES6O@FF?*`<U*(.@+$-S=*]R?
+M9\=^]A[+PM>+\'CRX)<+\?N^8&BAVCQ9,:E.@BPX7IZXE%U.`M'[1C$5_<5Z
+M:**MMA*,8N`)C\B(6"[A.>IFBZAJ3\()D\\VI5E^9Y97@"W^R2R>VN2!R-!]
+M34Q6W`*IUV3QH[$X-'R@;S,5^(C.T<;'HWFZP]&<]K),;D#K1;$I,.)8!7&#
+M/."5X.HD$G;_)A\GVNL<'NB>/HVPZ;3X.1ZDW8]!!Q?*E`'S@XW*(PK]D73)
+M07@)M8*L@#!@1K=T'!%*[2LM\01/?@]UW:R022=.\+,)%'8>@+:SZ']B:4;5
+MR@M92A:7_.B^E7Y*/KML%B;.Q_3/.V00_"30BCU;`@:M,3&Q1RG5A3D_2>=!
+MANJ<K&IO`J08%;81G"QI"^&3ZK!XFKRW[B,\],28HR6B&>F=L&H"?3Z0*7Q<
+M*R?$:ZU?+DY4O%Y$<K/Z.+*@Z.A2I98GV3QQD(K[P&I]"#F2:%ZGPYMR61,[
+M),?9U`ZJ]V0&72V(?R,[72^MB$:"O@R-7]OE"MRDP?7\UV^U:_9F-F>V7PLA
+M%:31!!VX<1B\V?E&R*F?[BL\B?;9S*9N3AM!.AKP%0NO,K/,%E&VJB.7WEN<
+M1ODQ^%J(NQ+LZ37Y=T:0K6TH>\VK(X1661[XS5D8-3[U5#@8.,6GGS&3GSF&
+M]87!J<&D.\'8\O4TN:(&:@N07LEPQV6<<6+A[SX@FW)E",[[W%D2Y+>Z>QK6
+MS+@G?29H\*L]=M4<$R'RW>8)IPS?[%!/R='ARV6]:B;L%T@_(\[2%*ZHV=9>
+MYX5#;C)[KQ";Q=[?/E=B).ROQ-OW2$A"V34MJK0$2)=/B$M@$2(N271<'&8/
+M/!OVI:/\BLO$;(+UF]CO0]>_9;U:"Z^I\A9==]K)Q]A<%$K1P2B\75;,_<6H
+M`'1G]/#!8\C"F3DJ=C^(*Q"ZTITB!1N-ZVH\M#>$Q)MD,WYJO>24<W1*(XS*
+MF'9,K@29V%]]GQ1^2U<+W#-FZZ`N%VB]F1;\=O(83$;E#78BYC6KLXR4DH!Y
+MS,=2OU+#MN]NJ4D5<-61`8.O@RZK(Q:LL'%"')MQ?/7\ODM2ML+,E<E\;_DT
+M1Z+`VPC\Y>#^&.@HGB.RP#'(UU[0K[WWPX?24_OJ]3$C:*R+4`^'/#)*&_DC
+MWKY<Y!Q29?<<8C#5L&/7!;\13EL7PM3"3^#_2X;P4*:)!=&!J#6>K"T#[V.T
+MY,QSPD6C1*K).`)/P;/&#M@.(@^K;4FA22YK981X5@<U1OL\0'@F!#D"%8&,
+M$#"&VK=QC4+OQ^^7PU3Q//VR/FQ:2SBOYXJ"*[$8:UW4:`=P3JXG.99!$$HT
+MHXE,6Z0P#]E_Q!^KHO)N3D.D9]2;EC.O&1!^J?EW@5IZ5Q]CZ&\!-%-9NY9P
+M7'&F@,3U.79Z5$=D<G.7&Z3G]E6Y@4T_?'8<@8"W#2S(]6O]>$=>VAX@N[NZ
+M2NQ!TR$,P^Y8ZADZ>N@Z<2:WD'J)-C99IZCVBL1I/RD)(A3+N,*54KS"RTD<
+M]A6X-:1@P$4(1%E2B6!-J=M`KL,V3<#WI:8N6!_":RU@')NMZ=BR\AN=RYS`
+MDC@=_\!2W">Q.@TA!']#]^7LEA*"T.RYFT\!P'M8=(#8[Z"VGV6.H-W)Q$V+
+M<HG@<@J"ZZ`WA\8D(?BF,]@LM3*`VJS"T:`%?9-C(VTA&R_50]?=SKF,[F`O
+M"*"R?$M!`'+"\^[T0$+KV^QF]$$0CF8]_E<M9Z6!H"*'H3O2W*95DN?='(@O
+MN,KXF:.I38KU3S1$&@8@X@/XEO-F^<:318<&E=)3C+U\B*30E!*2H>HBMB:X
+M\Z288+ZIV]V>LU/5+9$//2_@SVM=Y>UST(M#]O.\M0@.!SKRKG-?K,8&_0N?
+M;$:S%0WR'@!&T#L0>C0!VB\^KH39\N5RO@!;_V(P=N^?XCO&_98\6O\^0Y82
+M5EM*H9]%2,/-'GM7%>2D7#"I!@+T"#0&[UYDL3`A]RI:=)1\ORT<.S+?"%=?
+M1X;J-KR?TK]B\!=_FO(M8YS<YQ2%<V)C6*\]`CXI>[5E/)_8_7\-:F'""SYP
+M)U![V!F^PPKD.>&*4[O:,5"OT')=UZ?C62FI*4U?#&`2_@U>KK:"GT/!.INN
+MJLY.3'Z%<L%D4J1O)YD*_LG6'X="LW<>0RYW'XS!2@JC!,#\<J,\'7J#QL]X
+M<L2(@F%VM_:8#2#][IOY@L/>)W2-Z_XA1SL53[ZFZ8UMDS+APY`;XLMI7Q8F
+M2PD8@_9TX4_(`DZ-X:9`=82#K1K!8^FCKQHC2$.!AE'D4L(1-ZEEM+_`?IG@
+M`;YN-]=M+WSYY35Y$+X#1!9H[$;@QD)\;E5.;5-4+C>:7S,##-%OYOWLA)"_
+M<BX>23U0JNZ,"\ZPL]6<VD'86@N;(,B<S94"DRV6[@K*GG9X^V&$LQKVJ+K'
+M#'4T#!<7I]'_Y:XH?'!G=H3Q'MILB^<E5,4TI"&^-Y=*&&-]V!(<29EW(H8X
+MN[M&1&"D<\J[XD=[\4,GJ@=,EHPQP-`[0''?\#Q"E_!'S(Q^!HI:;X5_E2FL
+M4F"+F8J(LS:JRV6-SYYD@LE]V)NQY.$F1;HTB3B!O`>HQDPV;7I=/-XASP2:
+M4<T=Z["4^BB\V7Q*94/&P30;E)2+CO,VGZ'CBP4QV)Q#IE6'_E5*^+?/T%NR
+M&^8$.U1*&0[G`FD:R%D29;,&/\HZ"`*5OWSNB)>FS`\P&:[A23S+]/O@V]Y7
+M+0\W(E?=&T3IC_QK^#-/X[+^;:(XR0O)DN8TN+X0CG>_:H%>^C5(>S7C?0JZ
+M]LU,[.>Y;+"2:GYJ227P0.5"'D:[-VZNRRE*3,))37(RMA8G#1R-T7T=!`D7
+M)Y<R*ARY2V/K_FF^<=!\YQ(P)[+AC`>IHU7FD5<YN>J(A*1;LFCE$[FUA?C;
+M9EF5KZMESOV>;B$7<?791I>HYEBX+OO`2$#[`I$B2.[=;>W'AAI4:%],K7Y2
+M<^!.VX`_-")B'&OM>_':]5VR.03>-##N9ON0WF@1[Q93+(JF0Y%^0O^U^S6S
+M*V]=QB7]7@8L1>C)59[Y#^"B+)[8_@5O1;N!/G:51L3#J)CFDI#ZBTDXHB5]
+MK3"67RM/F$&9,HB&![K\3<(0WY:O\GY`SOP+TBB6OG?]A]JQY_*4?1\*W->K
+MRMP:#($,#:C1"!D=Y]X-\KSQ%89U_7]LON<];'I!L0E/!K>@IUY0S"KQQ5)L
+M4_V:@RX)Z,6GSW?BP6Z^!(S,W_W<[S<FNV)#7E'1G1;=N0>:[A&N61T_P)5Y
+MJE1/;/M&NFH^=>[]LA-Z%Y[1Z3CXM9(_00&\%SMR@?E<LI%+'!'(J)^<\9O`
+MW4#0R7ORA*,1[9R1V6ME=Q#4!DHMTA6+>--]B5DHZ'"94HPPVQ[BIR92E*^<
+M43X5=A2T*B<KH:,2^-7:X1>[[6]^6B:Y6+=Y\>#12%-15FYW*5Y+^#"01O?"
+M6A6>#389L0<Y=IOTM2")3K+K<3]Y79`/)HXB<4J<M\VXS>!YA<&FNJY!L_`"
+MG9TQ<N\I6&/5N41>]JU"QZ9$0D\[I1G1X9+%Q.OW4=\/ODXJ4>88V%W#U>,@
+MY41ECU^PNA!'2GEV1H`X/_BAKG-_?0H_:E8?HOTF5>$ZOYC@$CA=2^:G_U5D
+MFN1_D4R,@@;2B1E1(*RMUV'D(PC#(RY/Y2%,M]%C<DVE%G="=[ES3NF_T59$
+M_TD3@!VCF[@%6LWM53DQ>GRU^W_-`4V;F6B9WI=N%Z)_#P$I/D1:W?ZJ%FI<
+M\O9!G&%F-*OE>-KBT\('_L#["P'N]E*647.-OI*55%/VH]GRMF('%/C"]J6:
+M(3W@TK<U=(TU$)#WHRG.3IUDLYDGXR?D6!GW+:!BU*O7L/Z26D.INU+'+X//
+MQU9?XI]3[#2\)&Q,]+[`MY"/5#H<#R>V-$O$QS1\Z]O_<A*7*BA4_J&&BWH[
+MU"?!I@<&HY4(*Y<F8AJLI?]YMIDZ\\V5(\W"G&H!O*+.::=`_==02O5$V3BW
+M$.@09ISL9^@W*@",'P*\OSVUO]=56CZ!1?(6.`/#SG2)1(=I8Y8QV?4A_].2
+M1Z//3&OS17P/VKO.S]V=K-CW[1B<%3FOIC&8FX>#!N:G%;QVNTP>=B;+9+R(
+MT--A2DDH`=HDFL+S>S'9:U=:QJ><?^0T:>5)IW7>.>1^":NP)88%YP=IY`F2
+MB4GX6X?M!D^>AI6D0T2S`CY7M#J[TDZ62><+'-:D+N@'HT.;FC9UR"@27^JC
+M]W(>?MIB(;>-^4.)-B6GKM+R[),#AYXKHM)PHB*CJ!%R)%W*"`8,;'&-O^^#
+MDWW363H).%R_AYDSI*%8*K%*B1&7Q<'H:$CDAP+G>*[FA>D8_E#2?TV=9ZVR
+MADI-IQ4P'Q+W(78W:<;.*A+CZ82:HH\T%"5_8K,$S:5^.S[+=G(SM7@]:1;#
+M)[=54<[8+EOOT]NE6U/$4_5U]9#NJ.9*H*3[R&=FRIVK/QY:R,,R:5Z*G5WN
+M6I_(Y!<6696SO1;,1:R^S:M)729`BA+SZ+)1'Y@VSQGIL02=68!\;/%SG#^+
+MX;OI?L7@5K8;24ME)!;IFVSZ79U6E_;)IO2,4JXQ#H;)+UCSE>-4"V+,89.U
+M?%0F>?[CEEC[NQX'J.KSX$-`VP=%QRY-3%C"+@D\;F$J=WQX%:YEQ<P7AZ,M
+MLZ8'_$ORJN,\S$*<X''=,`O8A/YRJG'YWQ?@88GX\R!:YM'X42T'B2=)'K-.
+M*+_;%:)N1FFY5"MC06[^[];%`RT'.FGJF<W][BG;MV-_?4^C8")*IR#`>U&G
+MP3_1K+,HV?LU']#I)HHF)GO92YJ:RL>XCVKUMBT_H3QNJ9^332,LG>*T[-_"
+MJWC?[0,QV$C<@E3C-SJN$:O)+_O?=]A?#W+K$O)"Q(?.0<[:OHE0PT+73>)@
+M"WGL(R:.,%>IJ>PVPPZPN81CK`ZM_`HTM^'L7%8W3\VTIFRZPP"EXOMYW#C9
+M!#BQ246Q:"Z^N&S=/Q)R(V]T_0W]\>4`/^`TYOYKT<U'5@J_)D+-BZ^.>OA[
+MC#H:_SMTUP>_`B7GROE9(MOXTS?7>^D;Z/D3$+>JI('UYJ#Z*RXJJ5%5!0HG
+MR-2C>*(:`P.0DA!.9MB"3U]^LQ]\]1493]PQ4,2SG$ZI@NI2!I)5F52R"370
+M_L13(H'>.0P"]3[4NHH"NUQ(+]9S2VW(0%@KN_O-RE!<?NBJW?18\XILVU/L
+MUO$&\A-.$2O))]);&6]G*/]M2=X>=]*[+FI]HX5QJ03L[7.7/#S-,=HQNI'\
+MRE7Z$.8!"R7I6K?H9.!90R5T9;24H^38X9='V!"^+!5@3%,-Y9>)T+;AQ1[^
+M",FK\!+KGKF4+BZ.T&+E1Y4%WYRNFR,"QN#-LW*6)U>NY9V!R<8A=NU)Y[67
+M;]?-&YAF;=9(!R@_>;DND7@\B1CFF<<FEQ8YSXZ`Y"!!.[^]E?.N6T.NGBTL
+M@C9`'8]-M,'0O\`1\_?%#M%\R*CD/>B=JG^*Y6*_*O9DNV"<I:WG81U)+74N
+MQ7XG^Z-9MDF1FVFSDZ3<*ZZT)L*ATBX[8WQ_3""@V$H6<D)6#6A9<$G^30Z"
+M0DJ035_W7?:[9G=6'IRY<=T=W*LKWR%!LK40U('>A2/-H%\2C*C!R]K`=5]>
+MN"J$?C330VMB2G:?795%%L!;R_$%O;I'Q5<QB()RL@094:S3,0V95+X7,$4#
+M%7-S-H5L$)XO]B#C,G>4?Z+6S-=6>1@(:-B"GX+78B-ISZ&IP4(51E+@$5I.
+MWT*#0ZRT8$SJ@)PQ.)6@HNE;ODQ4C@9`B)_FZ9Q_Y\,[,J.B!?IL!_DN<7*D
+MJOUP4!M[F4"EK@)D(KN@_T!<)\(+/KGC`<(56V.:[V^<,N3Q-^$R/"`L?9?U
+MXFB8<1>-Q7`9(0*=_0<IX"96ETH72SPZ_RS&.89YEC1U(^[*U<:OPIQS<8F*
+M#-M?[,!7+6X_BRL^RDOUFO"MPH**";Y.-3:D'6E3PUR["0U)]H@=H-?':>6=
+MTGM_(SA+!:$I(8[?]L6Q63@"UF#5(XWM$%C,IP4Y;]@S5@Y3S=-'$0GB'.B?
+MWQ+CS7OCL'W/MV7E6']C=([_SQC0ZW-MS5DV,\ST0SZ9=&/IQ].8YS6:0T0U
+M*3T=@;J"I^U5&F2&*^Y7'`K'*1,A5:PMEG92O^*XLU8$<]T!2(HUY%06)I)&
+M7,TN0P+*D:8($O;SI-"6+EY:@.#Q'W3CP_U`ETK[D3%R(=.HZW9C2HJ9)=Z@
+M^B_;!B<?2)U=U'GAY\7$XW]\/YCEE(9K#>Y/O5F#)1,%I2'1-1O$+-B11)>J
+M+R@'LDK@Y_ZE"A*,H2S_V5Y(IEOEAQ@M;ROXS7G')"221L,BI*`6QVCWC`T.
+MXA0^HI^=YA[^;'.6!T\VC87$I`^-15^]=84#,CKOV[%9.)/0Q`K5\!(E(GT2
+M9%XX,W&GSU0:B$J`FBPB/<$<!&5K"LRB[1?P]A)R</2$,=*;\M08CU)F$HF*
+MJ3ANH@9[VH0=`75J84[RKB6\B.HJY(FO%>&G`;J$EY;Z];W(Y)_'U=8!?5FD
+M6=0'0^L..T6VDI%H[S7<(NE9(/DV0XV3VNA/5\/%`ZO:&)(K>,88(W[_K*O>
+M<JD#7=`CYV&%?3V@BAHQ4+\'+2XG,D-0ODKV)8!NBW6X4B\(__&)9"Q22-[W
+M,UAD5+1>A2/'UYLK[X_[^*<U`.0'^_XR&>GV=W@P^U!DF%,M#0C9':>S`?PO
+MZJK/4$':&B148:UOF9*6?;-5E_-58Y^&1RXW*-)#%<GS^>6F90W/"&^WXD].
+M:2OL]F+7S_"?TOG;D\#X?K70QJ\\83R*FOCUJ6^FCL]N1'TV/J]KUU)5X&DM
+M^Y4P_'R#C+:;O$<1CA>4Y8Q.'Z=W.?L)%[HYJ"!$R2`6T<A^]E]7:0QKL.^"
+M8^<G^TOO1P=7_BHR@B<6:/LR&7SZ,%N[Q*424F!?5P.9R2=)F#:TDOO<+K#U
+M:=FZ0$;ESE%9*DP)HW*3C`?[:EGYG^(_%@QIR."<,7%;'YU-"J\6>-"W,:C7
+MWN*1M0.P3D(8UFG=I\6HG)$8+V+C7F'XVH)^XEI^)D"AI!]'A6B(U*\Y1Z(L
+MP7@U+(3I:9ZMOTAG8A?(9H):193*M6^TL?'W'>USC(VO*J/89=<\\@Z9R58Z
+MN16K_.A.`R+RMG[)<VX4HXF43,#!3U#$-<CK[VG;XH2DJZ*.$;&(4AB.\H?B
+MWXCOIZCK$35GS]I+YF:D]IB#//N?##%>E(M8\@"@?J.\F\G?%S.MRD*0#=Y/
+M,([Q;:WF`E7D7`+E"0*26-;7U%@AMY(J>5T*!QJYW_XJX""4V^(`]AZ$DAK@
+MF`.E'(-<(.16>XE$V1H<&/YE>$W,/]E8Q)9A!,JYLI2"?/S^/A[@YD%;5@K8
+M[]O60`(#)^%C).[@OJL_%,W@+*S>-\D0.Q<D2%T.7DU)HYS80Y6W:=^0T.%:
+MT+(0*E!=)_LBF6;;?ZS%<[6U0GM=`U'*4W<7Q:]MXK;R^[=41IM9C(S*M&#P
+M5(\?0Y7.9OZ-^"`1E!B`T&&<<,<3\-HW?Q<+;ZY/6#^E>5=1XKX)[$SFL]I]
+MB^DO*D&APRZWF"TV:KU6'6_.5E`E]T!=H98`%!]'[G#*T&S%%/U[][MBGZNK
+MY/:(]L3OU"#/!E./M:N'J9'+''CB:1G,$AQOM;.[9DL")S^*$$:.9X1$XAW,
+MJ"GEIFW2";NJ?`U($DJ?`L#5E1_-3NO;GXD9"N.=J;1[W/N@Y`#&3Z`1]DAL
+MFP'VE;E^D,(AZ:\Y`N60NB;FFH@`6W&@BN9:+?[NJQ9RG#O(W14A,0-3Q*GA
+MX1%O]I#-_<'[0?XS!EGB<FLMWDP#`:=M$*XS<<7.GJ$M]70,^Y\N&J`U`[QQ
+M%KCF!>%5-F_?7Z!^V^=5'\QDTR(5[\B"]N0B['5E%XT\+RL,:]=MCE_>W^'`
+M[2/4EQ<#CRRSJ?VX\1H!_&9BH;7AIQ_RI&ZM'8BMM;BOHAE!U&HX;145<"[9
+MUH]:[4+\^S-U"^,(0+U@-.<*L52")%!X\?U\VZ*#\(2T@K/_?$C#-_*($(AC
+MBR@UY+1V<U.H_!DI692?ICZ98]FC;&,_AO3!8S204$68K+0#OF?SA*\ZRP6.
+MV!8$#`HXWE2_#O@+N=><!97QV>5]F9$#T+3E"WS.G7ZGK_%0L[)VADS%:%V@
+M,I._<&:YFOR4#P*"*WT[N:RBT`VA6'FU&8@T6UU<B5#`JNG-6B,F;L./+0A"
+M/!A`&FK."U)^)F0?<LOO<MOXWNF5,O8FC248UX]"<07O;RPFZ8U%!0H9='LO
+M$8-.-`4.QB>*E-^&W_.1?"(#9AT0,]^=V^_J+4W!U6%\[,*4MU'-12.`,=`=
+MO&YYV8%84/DO8$6L!-CY@&BY07TX==D>ZF#>?#R"=0ZC[)P1&T6+(B2@7ZGX
+M]AIMQQ!95_+O71G>,(#!0!J:&YE8V!Z-B280@W-W_B0D@IRH(%1B<^"W!>U_
+MVA_OQM3?#_W29"=RF.W@FVXE5_V(9\`L=0L$NCQ&D-=,[PE$85E+ESO+@<.J
+M!:^#+,<W23$#,R2.$F;96W+X\BP:<];5%8!I,:X!#A26-^6BH)"X<:%.'EGI
+M;:+.<;?<UFX?X>LBQU;=&=`!'UN/NJ*A%@<C`]II^^LF.#:\7D1LAG7`,K^S
+M/=TZ`WR8U#C'_4VAFH[H,U%`[5)]/$ZRV%9C._DH\9UB*A>9+`7]KV9,-$_-
+MQ./-P#Z&W@,7W;N)G'FS5'-_D0[AP,[,9KA-BE&H.ZBKKB9AE6)IX^S!8@\=
+MQVAA]AD^)+C/5?6;=?<0*X=I`KJV:3B]FP.KVI&!GF:[Q[@)8#H-A4>]5M)+
+M47Z7[@4=?A+H%9OWORZL5!6S0XN2-^X4)VSHY,].\M:J)9%]#_(@1D%7B<UX
+M--'P\4)'/);DY28/O1I623:UIH.*`YXN4XKA;="!7FU>S%!@7K/B"6E7?7P*
+M6CY['4,N\M39@WN]DZF6,1^*]`FZ>F`F739L9CCSJ_MFMF,A;]A4!A8-/?^J
+MB1!WU$:-1[UW>3%38_VY7R$IP(27LVTHXC?^&O;UCCYD":`XP"35T$K0+=&(
+M3%:C$?]"DXQG-=T<K7$3_P2F/=7?S`H>%]L&"NOV/#M>FGD_@23B6EP;:6[T
+M>%.]6,`'!(^M>:A<7\)GMG[!8450Q0D9ND/E\[U@IQ-H7-9X%\W,.E;*5&G^
+MR:3B(XYD#6UU!\](S5NH<WC"-&3/-@CQ_TY;Q!OS$9+_\,"H,UMZ-V[.B29<
+MD/$$S1KA0'[6]#,P=X1?F`N]\<U"ZK^$1;+/MFOW7B$U:-@XHGK4HQ$=$=,@
+M_,*//_<H2CS[6"^!O30M^-B`S:>M2]W0SU?$PYP!S#,NV7";"&#<JK8^P,F-
+M=)_%$[7GW2SM@?DAUTG33"!BI2;P.@FHC%CEXDLY6JW'])]XPH17Z"2OUIMI
+M!T-P`=.@FEI"YLVDWO_BZ)C]>0"&_!(QL^.(H3^MYSG'?JF2O0<,VIA#-.]@
+M63-R+#".9$OY'OCF48'7WAL.YUY`YB0ML.3(@SD#D>HTU_WWG4!S_=O-+M(&
+MVY_[TK'?80K6MH[&"QY3<*A@<-03`QZ#V0Z!H,'E]WMN-VHK/'L\XL<H6^$O
+M6F35(?#1(`R<_X-@D+``F6!?`Z7BN$W+&APH]SPQ^)5:68^FI#`X?4?L3W4.
+M6%E7&8VH!3(?U3FO.GB=X^(9E[Z"$T^XECV8PB+7>]S3!RKW&M2C^ZVA>[D]
+MM.7J\(9;(2D=\8<.-2GOC33GYT-7,&G%;/X\L@G5-'E=^/HQ+U['B5EHH=)P
+M]G/#8P1+S,&FBZ<3"MA)HJ.CU9@R@I7?/7XYRG$OV'&5+86"G0(`*';VT]QW
+MN9**GP1RP2"[LC'-+OMN'/O2OS3VPF?=)[%5P9G$AT&)8N=+J8J'DF($M&T,
+M"_7\(O#WG._NOG[6A`/@AB!'/>>"G-#+T(:-UD27V*FV(^_/#<.+1H(PM!J_
+M/B^6D6>C-_2><,)>U':E$JA27TSZR\P.W_Y\S=4%_U&?R\&\I(>(%3\<\9';
+M_KL4!ZO7'?<\U=!1PB]F_C!'U\?F&(*IK'[#)SC#I5QR[+#(.&K8\TD$]Y)2
+M/*$O5,(#Y$$9&^OV-0P]Z*4K8P=N:EPKM>A1K?3_R._+`8O:#'8E`KUV'>.L
+M<;"Z9$\V:*YS1>CXZ2M)[P'7ZUIA+?&MT/M2*8JD3YC53]\W2=;[:4"^.\R9
+M77[NFA*H39'UV[2_3>/.H*UEPF.9XV8_9KJ7`MG*QV4>'Y8E\ZW569RSD[O;
+M`(02H.!^`?I!J8B`3&ODME/V$KSRSFXY)RB/:JA-$&3M+%+JI/C`"OYQ#"ZR
+M?'DR./?\T""$3H'JB;1IV=2F(U_8B"+UQN4G>=VK,8&EK*1854LNB9E',:WM
+M]ZEH.^&B7KP,PDA)#>T7<8+!KUG0Q3!V?5%L04=/\TEZ79RT=U!C8(O>+2XJ
+M$F@%!)M*NZ%XB8E]3RL=9U!?W9>T^<7RL:S[QM,B\=.3$-M-,`GF?G\ITAF<
+M:)(%47T&CD>D/=:NYORN]`=VJ=/"CY.9]9Y#Q`\<G2`/W9(;S#A[J==&*>"[
+MD*O<%G`;X9VQR0=!67L>>M7.B-.%EE%>ERBKD"<>^T'E=L#/(-F`-^?E]X3U
+M3SE]4NZ<QP/8=BEFAZ&*3_Q'N"7]R_;#7CL/.EI3V6[)VM:M?(8-AO"<5+@Y
+MJN4W[$=!.*67"Y0=W_R\90L=TS&[R:<J,4JJ.W!(8=E7>Y&$O54-/B[.2M*$
+M-$>-!UBY;ID<E(9=+DGCN['[L5^1FS9O#*'D3:Y+MTH%%E5&U#@?<2"]\]`!
+MEW,3/*72=;<&DA;DPXV7.!#4#4FHLP:=DI>!8JM5JDTAK(7L<-BJWF!VG=YD
+M*9.&,_)0$D5\1<&(,JYJ[[5_O$_?[*Q@:]4(:8D<$65QANG.B/!3M)NYZ<G)
+M?DVYFRD+@FJA#807R-:E]^FRV!PST99-0O0O\42&EJPL#Y\]JPQG*0OLG*^\
+MV'\57Z`T)8`S#M:ER25<5?`A8;PF9M:3&I,M[/G3(BB8_[YUICE^USZ#]!-I
+M_%W3#VW_F.46FP.231/2<;D?4*:-Z_N%^..3<>57=TA598NS^#_60J!]]??X
+M5P@3SLFX_)//[A'QY0=&_?G(*(FY^C$G"$[Z/:,_39?Y7"'.+',Z[+L/-^;$
+M=O;L5\82BEW[0&<[,I##(Z;8"K%;'&60X&O>53E\7BTG!A+@TV?.V/4T3X+)
+M;BKSZS:<`;O$CMM3C,N@*\(VE-B>]`0)7\,9=X8^W1PT\QF9P&J42YGQ[J.#
+MKOV[Z]C`U(DJILZ\-)CTTJ#)"7\6K8G5'5$5SZXDW4DXY)JV>W2!<<;T@$:?
+M,'A=/\9-GU];Q&(XYOG?SJE]HQ353V@QG-WP#=!3*C=>+67U"B2:8\F_N7ID
+M8;RFN;`^=M'T[_(C?IM.I:>R:(MR!<I+14Z/1GX90=XS;\ITT>;@+Z+QG5@`
+M2"G"=//C^%C"D\^W1E:75S+H*3,GCU>^W>P0;&7BWEM0%Y>SM*)'K_6>[XE4
+MG^`9J5I!&DI>29K9.;D0EC'+D"@C%TI4#RTMB%H,NVN$Q4J;%:>>I"GC#:><
+M:^JK$O-(U)`W?:,L?5@0\J'UY$(?&I>2#11:"`'Q&?[!4+`%]?,=%/^2'KJ4
+M(FAUR57H2!/PH7I$HV@9)FK)<RSKBIQ3@FBP\E@[5+=Y:OJUN!DL#,E5)&!4
+M_RIMZ+8?![)R00@7$.BJTG24Q_;R,]*4'F3.H.UA#")`:QJ@*]7PE8^N!2)E
+M2_.S=NBZ6%=*"L<%?N^%,A%F[%$L%T3<OH'",5<9*QQS*2$:0,*QYMYJ7MO=
+MXNL>1_`2C`@-/`@=L`FHM0"R\B;J9_PA>U4%`>DN3#WR,M3V[2E+XZ,DN^[0
+MV2;%M=^.1A%0;Z.7%>.JB0W1]?5V!2G@*UCV9XHJH$8.XNW<(O`9RA\AU/F+
+M]R6$IVKB&-CN3=DL9<C70*!D1#:=V](WU!.IY9U3><YX>A6(5022%(STL2=H
+ME<N:AY4/OR!T579`BMAX>7O8I>>]-3]IYD+G09K,JZTEQ7MH-0\LPN<78SE=
+M8`J9)L3+N9G*--`:7,P.I:B-8PWMCDKZ4#)WE(FK5X<M,5L)6E'B>HG)HXH3
+MC+P<N4I.\UTMOD?0O3E&;^FL@NZ7+?B!;PD1#9?YWK1(!9AIK>-;OY=%M/?*
+M,4"ER?VIUVQ4)K)15@YW@FZUED\'7A6E/^#KOD7=>5O\R,1`],$4/[5=E82M
+M.C`^HB^S.@>;O>9R.(Z^SP#^,3]E,HJP_Z'<UTQ+#*W5^U]),BM"FB<GS:07
+M*"+7\KOOO!(3N#M+[CP(,>-S1%[!]Y=-SA]X+XB",?-3=*11<]:__<Z6&21M
+MTE9MC4[[HB>$UOUW9B(X*%^EO=4,[!3!&Y?(<GE?)K$DFU8TL3K<<'A?8GPJ
+M-0R_PV\N8\@,L#@ECZE?F;%M2HPJL_89P::)ZI?-&UAP!.BUCU1)UGAR_KPA
+MT%I/^'ZA>F>!5*KPY-LS"Q5MT[)FSCZ$XE0(WO6[FGR*MR0YW&C;T,H2K?X9
+M'J\!C[>,!,[S*:@B&VR;-7%`V7_8XQKJ93FG)]Z;ZG"RK&T,Q)_N==IB=>JI
+MRFWG'6_3-:N?O@`_D@*;\%1@5VAF\*,ZQWM*PQAZMCMB,?;,N@4UI#1'B$CW
+MT$ZPX8%"[=UE(:GL-$IRAA?&;A$3B_!E_R;)7U[K2TJ2#OAY).W4OP%HXHK[
+M1#!I*V:GL-2"DZSD-)P";+UW\P(@0B:DJ[#&D$@I,TNE`HL&Y"+*[O$OUY_K
+M=/8A6QIIZX^HKUBH7>]+".=`S'$(\7<P`[AN2[*[6V?:#';!8<&9])6-IO(W
+M4UL$U>2%34HGD[B4;5K3O(;!GO0E\8I-SNQ^F&FO@J0MT,FZ/;362U^"/0%Z
+MU>T#*:B5X]4<(A=%AT08%'H.P1[\T_QL@TUS9@^1EF;)Q)UFMMN?=B33S:-M
+MNU<DTU[^H;W("K3M.,.N2)C#UG+#6W(]0$-TS!OQ,.U/4"TPE5V;2@?.U4Q1
+M^Y@K.D[DT56<OZ\6+:HW4):A+M9(I)S^C_$#O>,AK>E;YJLSKV`)[S4TEZ36
+MR4A1U^\^:TP$*MW$)L<$\W>Q`?E),O3MZ76YY`T"K7V25@^"A>UQ"H\P[]VQ
+M'>UZ98I;]%/@F``WO`J`TMAC-]\5:ERE+LTLHM@U4S:;AKX,`;8O_OL52<1B
+M<8W`E;S65N'^BR\.O=\H8_/?!I)=>`_+$`TRCZ::#BD(<6B2>6F4IY&C[BJ:
+MZL$3J<3X'B]T#\[@'HH7H<@Q-ONM`1YVHRZ^HO0W_C.+LAD'E0C$XR>Q#I-<
+M9750\QN.,W3G-D12.[:8Z^A+>L1@B92.OD9VC_?0P:",[O2[^IO0&/G2ANRD
+MBG'M!2U<X/<8I)%!V><K[-LOL8;BY2&Y1+=?S;+'K576;P9GG@%.3E$GO4TS
+MT>F\<?P*MY>WH4.?PE*E4`+$S("Y4!M(O?JQLWE-N4==254UTWZWC_MS`*O%
+MD[)%P@`;B&=%4#6,R@%J\V5L_#@F3PNS"X%]D\$AJ%[5X\-6-G4>0EV80CXQ
+MK]=:+!IVEYC.YS#?Q>S2RO'*[?+/`MCPPU./FC8)X)JC>AT/FQBR7;,A='\V
+MVJ()]!"+:CV4!/$Y-K:P4*1"04[/SR(/RH>"F^YU2Z^:T1[9%]D%Z5<&8!P)
+MBI&<\=JF'YW\0)J"3I+UR.M%B-<EZN73QZK#N3AD&K`RN?)PF*1=0:U=F!,!
+M@<(&B7G+?RFX7?%/@6B0'](\X.26-YY:9^W.O)\=.ZNB,C5(M=<1[\M?36)T
+M]"<F=85,'@7^+E3F!@?:/R*/37T9"D%P:;H:=1?8>AK:\]^;-S#^!(WCOQ[\
+MQ/JWY`=_#F:U/8EO(LRBC],WZ*]GFPBS.,*NSAW2@L/2T(CWGZSQ84H5#=6Y
+MPCO'VHN+?=<(AU!@+J3L42<FY9Q!!KP[BY$GI3<CS5T0[O)5_L?Y(AGF''TM
+MF]H<4,F9KC6"KLVA:C:$RJ&3`#>(3<;D:'FI7PZ-($CZXK?(M2J;N=+(V]0B
+ME5=AB2VK/;B\.C/T]]"T#DPVR9[=Z[7/5#EYD#J<]<%O.:9OED.N&L?H9XP4
+METB`N4@CJRU2ZH]X[TG>R\SY<]M[L.R]O^UB.GZ&M\3TZ'E2(4-C1O:A*`,1
+M44C+G/?G8N8W!0F$$9_J,PW-R8UPM\N+STX1N`70A%6[%M:/N<QUQV%^`%2!
+M<M'ENZ*+:T]AFP+.>W#]/3.(I/`3WQFH`7B3=$R\T,9F1"M)8C5NJL-GOTTZ
+MHM+M5K][2B(-K]\IKF$VH/)&2*?I(H(E!/R6B(L_,J;2WV(D3;DTZX8R]\M"
+M2/M:8MJ"[-@;BR"HH;SDS&WJ7=A-VIS9=0/H1,]'QZWT!G/IPP?V>-B,2$KL
+M3)MR@K3;BET&'G)G9KC/595+GYN'Z;3JMPX-ZW<OCC6-LQUR[WX9LSW7^QSA
+MB!L)G7:^T9VEO6[WC]2ES*>FZG!`U_.^-25C8DE/\J7TR.+^VA,$:B"E][6S
+M,-KN);=USVNKCX^T\HWSYKR?6I3YC'^&,?#CL]M(+BQ%)>CR)E)X.IGS(K##
+M,,WC#8>,O*P-6Q3?UU^(22&>1MM.U$E10B22_1CW_4TT%YY#J0WI9^CBN_/"
+M:B*$R'OV:O@SC8E>@>`.8O9-ZF(_3:DK#@[ZPE2BC$A,PT@TX!_M(RN=!"I^
+MSN*U3_"L":<6G^XVX_J\&N.R"`%&JKF7]K$O)[3S6R_*,-I=MJ3[J:T\ZH-(
+M_MS.E-[A%I44U,%2T/1/L]T$HSAK>,`8M\&.,DS=%,LX$\1:G[I\+@^>62J!
+M'VTXDXE\R6^"SW7"312`J.6+_V[/SK#"6"-A<^\<RB]E(V6&?L`("HL1Y@:6
+M<:AO2JQZF15RV2OZ4IRDK<<-.W[%5`>)7@","G9(^!Z`A'QJF0]?YZ&J9<K5
+M6<[U(=Y=JQ=#7-K<^O-F/]BF\D[6?@"9&U!DV6:@X/1X4Q^N,&\PFO[K4JOZ
+M2QS)-;MB=.WZ85C_G"'<Q1D<KMEAH8OH.WPZARSV675A#@GB)(,#/1RKFT["
+MGHEL$KB\&VKWDSXDO2$B%+B&\J'8KI'7C\]&58D9534/$AS-8@/GA]N%KP>3
+MDP&#ML,HX/`#[_+W>_TF<V)J3VOY"@*Q`B-)!BW5/]E6:5_4P>$Z9P5G]$`/
+M+4#^#JM).YMA<:.*J'D$J?ONKF<DA"C!=^NCTIY@.+>??*<7L2QT!..'D1B&
+MI*LZ-@G_N;/@"RQ%198\^25R4\B-TE_Q5\U7`)YW%;_6]U-U)??57HP4WX&D
+M!O6=`!]?]FMKB3J5;9)TVG-).]TTR:<\7]?,<<EGP>3&S-/VWH^^;"`AZ`>?
+M4M\7!$?7GRKZO<[R0NB8:EX69@M&WXA8?E>)C!&M5.^!H<_L1V,JD91>"L7$
+M,I]5``LA0<!MA(>/T40IO9EAWII&4"ZM^>S\"V!>\EBL4\\DZK.?V7,9A$+5
+M6-?\0W@/UM!7`163NV[Y,QKS*/KO!('R9W)H[`A&)SHG_TM[&`A)6R\:Y>]=
+MB!25:/9M5.I2F[3738,_N3D_!Z#Y9?`J-#ZRO!0'43J*#3*FFA>OZPD!WL[?
+M\#?3G[`*/0`^52;!J":^],.$_..5MBEA.P/(`]$=F[-N-D10<B.$I;79JG%2
+M.RQP6)07FM=KCJ#3#T1O,YVP'.?.2F!*K:=:=:)!C?`7X)@%JK$:^<4AGPQ2
+MG.TL4F,9&7R7^<J>I@"_?""V2M0?E6K$)'V`[8"L4_^[;0O=S?*R"%5;-+%%
+MQ5E^UWM`N:-(B\*L\=#Q_T@4_[Y1&Q.7I^FY*!(4M*6A%U57$8DPM$>'Y)=^
+M.?<^1"\$&!&JM^4&KC7""5+1J&V5O*\<F#&,&DXH*[N<]$1O!1F$80[7G8@M
+M"'(UE]G6O^A$1@4<<@FL[[GC=6(</(V>TQRH/_%J.9?@)?S+)1*W9INP5R>V
+MX\:/!V811YV@ZI&T<";%U;#;/4%G!5%F4;$M^'^.-J8!SR;!/9WM%NT[A"18
+M6`/\X((+B2`6S.*E![/4&MPN:#9I[DG#(Q^D0%Z;YLQA1`6C-VY[-YS:I>/K
+M+0\*QIPWBVQG"K?D^J]^-\"([N5.$OS?I6I$WFL0_P^#J^<K4M@'CEQ/(BFR
+MK=X;X%8/QV_-J_>.'=E!3H:UBB-K;GP3&B8MFR]5<J8M5.,BBH;WIM]R#63\
+M`,WZ46PN-%FB+3%4QI1]WSDR"FCH<X,_+E<_>N*Q_H!6ZU(7@8:>Y!D`VWQ_
+M<&7@@)3DKDBAAM3]1)FJ\Y!Y;@#3]Q5N]+#WUDS7K9YCR)R"(X7<?QTVYXC8
+M(R-2JDD!%9-,`XK%68:!H-A8L8=%%AP\%AAOK?4ZH#KIIRY$B&L47#YC(K.+
+MZKP<'0#%+]JUQ]7&ZGEYA)S5SZ--SB&HW_E+"`_[72?0H<=,X=S\<*KGH3,J
+MK0I$U^]PDY8,`[G)C@,P?L%2]6+6%!+Q)7?CIW?K2.\#.4V+KF9\F0YVL4B#
+M.:(1-1CF0,X:=UP52N[.>@8!M6G$\K<>L+R]-IIUC&/J5=4F#YJG@<E[5)+]
+MG+@#\<+2L0B5!F%5WBH;M$-7'TBV@**)A69I*^!UEF+$+/:S_I>EF\=,;;X`
+M-3;%-8;PD:*T83G;2;DAP2S.8X9815&@R_`B<&=Y/G)T[-%RKL.JY&\/6V_N
+MN&D7%B2S,7!,3W?-5DIT"`$<$V.4!;+3MDIK!NXF:':@:XHK]@!-0PLGUA=5
+M/6UV1@6F@>!>^O[^,,.,-(Z][-XSU,M_JMJ($FL!8WJ3@DQ=YR.->CTJP7`2
+M\^G`32I:8AO(?S7IO&8='X!IH/_7;!V%YPA452Q_LJ=AC?,?-,\?%\<`'&[Z
+M5<X>?'E8=O&Q?B1TM0CYR.\+24$SX=%%-X!.C9)/N?W54ELW&UO96.EX-Y0[
+MI\46:ZQWV9B%PCM^OG:KL0J@RN57,.[+^LD<>T/NME.H[:$_>A<L1P#?;*X_
+MW3&YY3Q[R4;*$F$-Z6+P+4Q"=`>=?S?:!",QYV+.KG?U].40BE\\Y4RE;3*/
+M#N/ZFU,'):10@3C\65\?IQU12CI\@N2+;DV@R"*3<'--7B\R3[EX9IT31Q3R
+M:70RL/N<O7*R?`U9V\RNVP=D(27H8+FF1K0O=3MR!.M@X#0>DK"&RNMAXV("
+MZ<-Z@F<0(E%@[IE/6#*X>C[P-IE#QONK>M*"&7Z3J'E9O=K^BW<M</M]1R=.
+M-L1>C!>K7#,.D#5D5Y"95I`D-I:1(NC6L2*E(5DN_R_MS/0U9&QPWFG)G`#P
+M:T]5IS5RB.+\IAYS0XX&P5B3*452W)K21`10(J(]3P.MON5-1B#JZ"*#>L?`
+M'WH9E%*I7#H,[4*W9,,'W6T*[+NEBN`!!&T`Q.&3^H:1]HL[F%.DGH>[T>'/
+M:QV->LMHX>;U.CDN1U8I&>HYZAIV<>3\*'@0D:G96FP$4GJ8O'*]AA-N'##W
+M+8Q`9E<NP'#]#ZX&TO;KPW*C<0[>DFL/O<6L=Y(UOY0PZSI]A@104,Y]#;?"
+M$NMLAOF33.K6M'H=;=3QL"!+?NS9G/G5O/F7?$B$.++O&O)LEU/>QS[*ZD':
+ME*$KFX-=2].4J*J)FU3@$'W>%]4PQ_:B_2#:%[%KP=&G43BYX_NZ;'<K,&L9
+MAW-@PZETR51,WAMRU6J05)N&(58]_)K(`UG8R1I-K#T%XVE+R0W-N90YX.Q`
+MX[^-`8@=9$,;O*,"QM<1E;)(?[IEW7*[(`<-=1FT2&&5W61#NASD$13.X+%3
+MNH!>E1BG/`6KW;_9E/CT>;8%N/5HYS]I(+B63C/Z$.#\#6\>:]F$VS1.;;B.
+M!6B5Z,Z*?0MO3[-?''WY.Q7J645#D!O>_^\/B$E3+TFF%;V"=)!>-;Y<^:J+
+MUB&^B5.K2P!*Y&WQRG$,3T5T(_O:[MKOV&_K@W(?.GPE?E3S;8;5J/$+_)6\
+M8^P=L>YEO@Q@GJ],EPR.._N?;7?T+Y<OQ_I'5[>R35#K2U/OJ.@G!2O%N#E[
+M-CQN8[?I"C(O[B<W+5S4[5IFO,XG>R0W,;F-#8"V`1C!0I<G+TH6D\43I+;I
+MSD%M#=+P]JKB(3>**#YQG?!S96K?8&;VW79E&UVSDO3UJC8H[,K_J:0G<9BN
+MGR+SN<+>D-O-/V(,-2Q&>5;0OIA("W!W6/*3*#CV')^?A\-!-*#B+^QC!T<H
+MXK]MJ5?=OH_.(@`/=*FJ'$A'+QAMX.?5@%E,12ZUTP:T4S33EBU4%B9>32^%
+M-^^AQH'D@6>QWM6Z)\!7%?FFG<O)F#^\1D[0A$DUC9=,OEZ^<:G2K%<&D+?M
+MMEWZ\MK4;RREHIQRY>&BVQH8'>QBGIW0TI`#$*(!KE?R%G^S#Z'G6,T/8`U6
+M^ZGLWG4<]5DIUM6D4^3R.OI<L=LCQ7TNFP-+_$X_,[8$)+A:E-W\]<-17R.Y
+M61'8;]7(T`T"T3O%>OOT,)(=(C?IE_B5`-4CP4(S0.#ZU,$EW`QIG%%N[16@
+MC2D^W'5ZO7ZLGST37!A#A29!]1/_WS^M2%AN.T*^)8Z)VY&%$A1MVF)@OS;5
+M?$8A%94<?4,W/PT%HBA\!!==US=9>QP07U%8?E^`BK6!LT&61R]NF5A2,4Q<
+MJD4>H&3PH*J[;<&WA(6EWA!,Q#`8CY["\D9F`XUQ&,D$M,1/M$]K:'G:4S@7
+MI7QT.'I.D;F(&&],_')#C7-.`5FU4P9&]"V!BMG_&,7DT5:MKDJ9L#]Y]L#.
+MNPQ]7Y5#FS+6#[+K:3M)_B#A#?7+VY`VEXWWR%&>8=Y-(?MXK1-KU6*&U?J^
+M?!>L&E<.4)9=VZ/Q_6NY4S;70979!+[@C(H9QYH""ZO,#GP+LD-PP'3*>K;%
+MD&4_G+-IV78$1TW&Y#1R1ROEM,-4#ZQ8T>\-,H;=P1(#V>*5;@&9D[)(4?V/
+ME02\2T"0@[B]*?\PTK'M)"#OE4L.1\];@5YZ?'G44N4,7;]E.(H`)8\C\&Q_
+MKOUCT!Y?-K-&>`RGU4Y&%>`I%DC"<4#05E-+^^Q>/5HMH4LAX])^M8\P"#[R
+MN!.5"*&K48=DNF,2@34*FN1(IEP#W:(8S3#*E%.WTAUP/H!A`'X`]BUHA,!%
+M<+O"05/'@DY.!Q6#[UTF2OE`$0*^A%(0!]\AU<3]->?(_]M&XUY<T9T_5,I;
+MC,F7'IDADYIN)??P`AK4+9_-0*Y`@.DC>3/-D3N%'3@$B-B$[888)CO%PC=?
+M<+D5_U57<_[,$H/<KGVEBQ`6160!G2(BH.D^BGDQAT<<G9AU?_J7.%3L>(ZP
+MM`.V(J6*C*!Z(B`;("FF$EF)1TE;\@](SP)[0JW>8KG;X5<N^[G5>1X0];$G
+METI8+;!U0J(PV.,6'L>W-V%SWNQS/\M:X3/PO?A>L\CZ'K'N_*\M>==RG_GU
+MH.:;R:2[O-'+AAG@KD[&PX2!^&R,KJ:_!=+,NDK*-]-;>?!>7=OE$1DX+K59
+M\1O`M2JHR]@9(&%LO@R"A[$OSI0JP5V`40HW(]B*K]&,>7J2>4U7GP=[]PN<
+M]'P^T9O^;FP"$8Z6E8?J;O7&3N3!BESQ1X$8=BM\I",#7):`?=-G+4DMKJ!K
+M@F9QEOS5+DU71EX9%2D@TW#0T7>LL9B)=B+D7/%3@IL]+!]?JK$T567P=,.$
+M\03QC'2).RUWEUU6.G@X;CNL]ZKNL-NN@;-50Y,MBMWO7T65R3B=H-U+5`CU
+ML[EY0!Z<N'(<<,LOH?[#`8R4#"J2D^N$`NO5,(V-J.[47,OHX-"PNH^\UXJ1
+MKI=K.L4YW4[8O4'>7-'4H72'ZUVO03[@L;]5'D7G>/U1VQ5:W%6,12_JL-5)
+M_AC'PUF7=6Z+<QKZMK;3=(A_6L^(YP6[6^>X/@E]UM9'@U.\E`^C'U0(@2K-
+M_N%M7H[D7Y1342`9>=%'HY"#&1NFVE6/+V'O\3<4R5E\I5KP)Z_B?"-SWV\Z
+M-`K0*4#`<)"F"WG9^7GH1G7P:NDU/FJW+-7EOU0@F!6"8\`!Y>62JE%:SB#3
+M4=L.@4XN+;BFZG\)^[Y?P7U%/!7J*]L:@Y-<S9H9U[\PN['G6(R9=U&V\]LH
+MPDA`^%Z+S8:7J"YS;HU$J?G!>IHF]J(JQU*A""7L53#3`>(Y*D6T*JB1G[XL
+M'@%FDI:6`(DTU,LXI1^Y`56F%/%O';682K;=6K,=_)#];ASLVDV>6%/KD[1A
+MBS('5AOW8!TP`<YG'#RO$42R,)4#(WMSA*E97?/[UAZK!O=T_.J@=U+'=>^L
+MB<-&$6_CQC;BVPV5+/XYJ2I#5M["5[ZS*SH3`7)?OWAZG25?C)N,/DFDS'(X
+M3.Y=\^JV4IUM%>J*8#ADHM^*<C`/L2B8FQ5L-(O5ZG?'C!(_)`9>5TZ(PV!J
+M&VIDPY2Z[)*3NOX._P_/@1-AGT'[1>W(X+-"D*1B+`XRQ(82P7B6CE_33T?P
+M*0@:(THTL3_;5>$@UDA)<V^GTA7#L-WNHXYYW_'L=!E,S#];S>G#4^KE\"/E
+MXJAY>V#1WIY"#^1-+0D.+4(T$)(XYP0?&\7N#F[!KK7X`>PU5Y:YG>)\[XG4
+M"N#K;PEB@FH>CW`J+3VV'_.YPJUM=(;PR?T6W7Z;`C<GG(2E@4'8G._Z#T`_
+M)J7YK%O>+>7#^T="5,T"42%@'GQ3R[&D)15$`5DBO4X;P-1UM"\E%]=4@^9%
+M(GPRGW"+/X%UC"SK+[>T]Y2DPB*1;W!\9O;B32T.^.>!N-TDN*N!1MI\:N7P
+M<K*9<Y]=L6O?X62PI/GA]J+4R\L173,6QP1Z8ETOK-B@A>&6X#W7\M,MU]2)
+MLC=RZ"G)=DF<:JBFF2U]!_DB^QT@8)`KS>2P7"4Z,$G$<G+D)"_QC*U^W*,I
+MW@23@2QJ*RR@>+'F<F/,K,A&+?]68EG>YQ[U-!)P/C0`A0NG[2D^T6+H3X74
+MIX-"J\'653J/#)`I]$5C^M00M[9^L/=5+/O>T6,];.!%+#+T9%HDP%)^#TFC
+M_Y>UZK#-]_.`X8+1#>8J'JULUE0"R7D>SH-F.`6L+FLFGFDIZJO_:_92HI:`
+M[V3?"=0.5+>J;J&;8C:]/WAVOHC\XBS7YS%)E@I22V2:NKPF;WA]M,=95EZ)
+M&S-2KP8/B[0CZO9<:C$N]*AC;B95.0[TA,)`_ROE)$3-/P-!_\3'G,M=(++K
+M'=U#:.A.#1OBAO%BRK3DO+V<U"PF#9P6#'3,2HD[@PFI&</?BEJ%H#9D/%ES
+M$Y`[+6M1EJ[784NG7M\*'LN5;(4Y>LC!I\N\XC;NQ"H_-,^HJ6]1'8#_N_L.
+MK&N"J2J[NRTXH[XU66V1^)=BT_ODUD38[-C#'DZ*&B9>&+WM?4(*&A'#13C?
+MN4N^3N)0AN52(:%FJL4#+NH%!=[%XCU6.-\+^:I_!E-!KCV7,Y#CKM<*3$KY
+M-^YNH,K74'^<K\:><=J<+-.K"DMWQAY3`BG65W0TI?FB4C"B#;N=H5K!^.B@
+MI\!NM^R7.0(3@_VW<J0CG%H'!0AQ'L#60;<5N0K@[4*BTF+TKN(W\B'BQGG4
+M'DBF<19`$75U2^=W^CE..V_11$E86-^6V:H.ZVJ-Z)/:8X7@9TFYRN]?E!N$
+M:"8^8]]+#]*(6E<7T)$V.#/G:"/QYZK$@$4\`#94G/9?WD@LV:@BW<LMTYM'
+M08>WX78[+?RL_;NN@,E?]"``;+/-=ZA.WM4+J#X7$?3FR.+):$WZHS\_VF70
+MI_4@_P43CV%V'9Z.L%E_RG_:I"ZB(NYAEJ"Q!$5\H`_RDVT3BD,9VN>RL4>9
+M@T-:"?]!D0`SWVE?L!E1WUP(/SON(IILHQ7-C3$?@<!:"ZH#=Q(YMPMZ;,_R
+M[5L%&U(1T0ZU7:YS8[D:Z`Q3`C=AK"'?;/YSMZJNNZLG%*[\F!<+E4U433VF
+MWCQ3IV(""1&_)5&IFX'K85&-B9G=W2D$/\H^5[DSAQ>H-6&J45-6D6Y[3,[G
+M-LM-I!T36W]"!PSC`J(4&---W*RMM>+VC7U>W?=XUQ=7GF0;OPP&>>V+8UIX
+M6>=/&J84@KT#,NBIGR,?;0P_W(PJ_.LI6"Y\S^@/RMA]NEN"0B+^E[Z679=G
+ME$D]\<,ITD[WQ(I%3XQ]/%'+0U*4'-O>*5BXE:DX0<]E=+DO&,[]%VM`D;%C
+M:9B`ZTTFNKSME,K*#+$%8%W:A*F]P6@*^A3Z>Y<D2&^QR9]%T1_M)&<!VBWB
+MTYA'Q'A/D]L=LE%_0X0[-Q@0?]_(@PF;^-`VIVI/@#>,::YU?:->H%L+LR>(
+M3743+1"?98<,Z71Y=87$3KHF.,-\,815Y*:F2`+YR^R7YW9/#4+`R%?7Q+]@
+M&%R6(%S<\PL:2XN2#V_=G<"%D"BJCK3H.42M=M=+R$L^I7_UT*ZNYN1(8"&T
+MM@V[7F[65C0`RPGV!03$W>-II057<]_6IH>T#XEA/"[LP)RM@O1#/"O$2N-!
+M4\VS.&'M_7X:+L)6,;RC9U8%'>0QC_Y<QCH1A-D*0E*&##C?+Y2#HQ%PV.N?
+M)<ZYRC[K&K)$3B74(<MT)B2V2+",U\YO#X-H(M$7.4H)QUPI9L+^8.QNQ34<
+M9DWUR2^)8102C]F.+`RQRPH9/%2"&QDXE&49KN5-$9_"MYG*C_D=$Z+#0O9:
+M@V5\V.3X:R\[@2VC4K"J@E'[S.?ZC4UF!=ZKJ>N$1B'#3$6P`$&MRM:]%B0P
+MI4)9X1MC=_L1#T>XA/L6MWD^<U),3'<-5NNX2JY&*[A5$>.]DJ*OBU<!RYP*
+M7IA\&64FXV\^.($V;MZU*.;:7XS7"0@7(*I%>3]]OH/1CLRFO(TVCGUE`K17
+MA;[=OKO!I9?"=3>&D!`:@:!])5C?"%/VEYEC'A9VR*F`K2<GLS+"1)[[=X_D
+M;+>&3O;=J%%'?FU*6>:NJEW)4U*`(G7UI5/(B;:G*3.S(;__>_M'EVW=)P\6
+MN<..Z?.^\]WEIM1G="0WP3B$JY8R>3!L1M;<II@2BW5,G5]U$W@Z\%MOHP(9
+M;?I'#4Y5AFV3)#KJXA,8WN0B\%^/"$#%.^[I<&?>U\[""R!B35K!GX:'V)LY
+MNX"?O0KL"6^2^8!:E9O4;0#'0<.,B5RJ,$LP)T<C$*.=5&IWN?]EX$/Q[U?_
+MMG6N*Z7W(VW:R\UTT6@@%P;?*C1#2'^6RR%H12:R&Q"G>'>&B\MKYS)<73/V
+M7`-&,;(=$JIUD-A"[UBOY0'T\8JQH8;I*/%/9\_#NF=YB;CY(XZ>?++EU"B'
+M/NMO0NWU@D=QGF6XHU8C%VD&5H%6VBP?Y[D1'&BHS1R$B?/R@.LH4!%#:]:4
+M.B+RUT,8`S7XXDV:_1F/DP?\Q)LX#/]$*JHR(>F]0WE3*'/\?Q96S\9T!(Y1
+MG!5KNIJDM0_2O\E%`OF7_D&Z',`P$Z)\7Y+\\F/I/.ZUG)I0![2^1+H9[\7P
+M5+W5-$(.C-B%SQ6:Y$X9']3]Y?X?L(3G;<^@?*;FT,UX-ELEKH-*=Y)!:RXS
+MM@'6HOD@_>\URN?MV@*L0=4W!`I5@-1/0E?21%U`SI#>U!\;:0C,VC\R8Y^V
+M&*/Q7'7%S2+RO@$3X/=)*P7Y`L`]Y3D513V#:^#7\O^'$NA[_>0-I//L)3],
+M?6&?A*=\/,Q*)N(&=Q4`*MP8H66'`)(X*D"S/<4@BX$;_<GD#<C_M,I\H%Q>
+M$QR?5PF'TTLN>3>`P(Q)J%+TPW'N$2M.Z1E.*N5Z@B1V6]'YQ$]Q79F-"G9O
+M\K77QQ+$\)]T]9.7B/V:P=>(_CG`)9!=SAR"N5A2^[>1Y(512+%?K*]`^Y03
+M(G5+/@Z(?G-Z6D.EC;4]C]+`:."U8TVK8V-=?>+Y+\2I4-ZU9[`&KW+.#VRA
+MW'9`U/+`WY'X&[ZIVZ3;@1'D@*MQ=AVZM245'SFG(GLT(`T6\Z(%FT^/LA1(
+ML%JC%9(MC6.XC)NK;3[)_:78+YD)-<2T<JQJ%D%XZM>8Y+#8K8+L[YKLW.;L
+M#\_>0>47TBG>1W#U>6*=9-+>*XJ(@1:N3[[!AD45M,TCVF:/%)!,&S'T^C/I
+MZ<KFLW-;:CQ\:-'1+"[>3]=0?6.%D5<,77M[+K&W;"46[#;=W%*A-`EH9AS!
+MT-1XQ.)R5).>FW>0*V'>.;5*H`BPS?1@QIC-^1'*^6N`<PA%>=G.T6[9-?PR
+M*10DFZ5Z`XC!6L9K'6810=RISK9NG1,VTT@.L!\&"JJ8NB]NK5^^I_3KI8M^
+M]D%7=[_X+F%.<9][Y%/^[-I50_L3-QEE`P9HMQ5'3-I2&64.Y!,;;VZ9&UI8
+M*K((WW)QK:7]M7G!R\6'X7>\8AMG)%<!=,OZTWA=69!M_KRMW=4A?Y&%.\6D
+M>D078"1C6(%C\N)_B^)L(Q&BD+X-BM9Q9V9?<:+$M"(>,,\P]$!#11>G6ZJ0
+MG7%>I_"1`N_J3+Y8Y][(>$GMY=4M>)P:0A]!W/ZBKJ?P&?@<-">3S,]R_^4L
+MTQ+A4H8BHQ[+,HP!Y#<HB+FM&]LDR-B+Q:>#)%=NN&Q^6_)S^5^0$<L,70P^
+MN'`60BTMI[1!3&M]^#A?,/W_H!E70)#[I`$%ON8@+X$P_*"ON!4.TIK'KKXQ
+M&RX6U<FC\A3*`2;?:.GS4\@REI&:CY?FTLU,`WY'M>X1>+T*SO$]Y$09--E6
+MWY9\Q=31^A`J6;Q\!['XR01MPTF(LKDM!D49'NQ).D=ZS/TVJ84$Q(K6=?DR
+MAF.L0)GR*82(9NC.N!&;7S5QD>%,6ZA+R,)P8<\7XGV?[>UN98#QOQM&'40_
+MJ!&Z5ETFM:LEUWR&@S^;B>/L.7-DE(Z>\B[S2D.BJ/'QIJ4G,#5P0%G>:97L
+M9."_:KH87F_!"<Z*T8<NZCG`L*\=10`Q&C`!E8]L@T!$AVZ$0;>?MC3:RW,N
+M?O'1TE\8.W6.DAV4?'\.OGI@7];/H\3:@`-\OX_M474]*?U.T+>#H$6DV4V?
+MXO]",$7+'`V%H0:V;DA#<M$"'7/DL.*-^1?1':#R\:WWOU^$]HK?CZ$&;28%
+MF/`0!G]87L;L",\,9\C$YC#OK=,]T/GL_#]TBUAZ/:29SY"WN3V9HI5)*Y3V
+MQ3'^H@*>V;E9I$T"CA&F$1YFV>234);XZOG>*<`.6QX[QR6O2Y4Y"=G_W7:1
+M0]-H$/[C&KK8%T2?=V55]_=!,S+17/0)L%!?[.TFG/1([EMU=;T=:MP[SO`9
+M.D0^]Y?S0>QF!>B@X^@GK,>0!Q$H50"G"H6O*FIP>%=E$U,#JPWK78AX-238
+MBW]V)3)B9[(Z3-^U55Y$,Y??5MBKL/5O]U2@QW>X""RA?843K:!Q(!&-XB[R
+MSZ*Y.8VBDP-%VZN]4,$O+@^1J'S1P'%TY5K^#\RQ%+`9,WVQFB/^O<B4D[]`
+M5>Q>&9<_\YID51L)8R@TQ@-2G]<PZ3P]'1>94[Y#QR``(B(``B(B(`B48CWT
+M#:9+`.KIL\&<+Z#?_KT56D^=!)6:W\?`5>"9Q3M^O=Y+B0\A4NW&$US#F@OX
+M?BW*3PIQ0WSUGU[HB?$6=J:C1ROH'FL7M6JB[6?N:;O1]63P]FC]DM<7'K,L
+MW#*%*PF/T"W.K9`0_E($^CS$`^1E2Z,U$81U8'#01]E^KBBL?ZKV8<7.X[]/
+M]!8<5Y0-+@'L\$$KC"J<;V_9@\9",AW,%M5O+3(<Z2A-@$Z6#;!-3%%SB:Q,
+M_.58QW]72#"I0P_^4?UN,ZBMVW(U[G$79U>TS68>R14:$9!F),WC"VA`G;+I
+MA:_NS*TQ:@V'(.X.*Z053HI;K\8GH%/QD?BEH*F^Y*BV.#/3]Q2MH<8HTZI*
+M%J5WL\3YB+=!E2H5"=XXLVT=%-"N%7CNQ]6NK9[[/%^E1JVTUJBDI;U^[I2S
+MZMH?HE\7+,=L6F-@,QZ[Z"%;N_0`=4N+KW0TR^;2S`DX8G$`,=#-"W26`CBJ
+M]*WBXFL[^;OH0GR*@<W$T=-KE9H.1KR$8N^BJ^5KZ=JF?H!K([7I&8=L]9ST
+M8F24X"G=3Z,=+]-%4(9U>.SOEZ_^&I[?LB4-G(ZW;LS<U/;@C[8.5^O$X!?_
+M%49<LS0;JQ3&X\(H'X*[0?A6`\@GPW%&'#Y4/A1X;`7UM7LJ-[KEAC1,)T7X
+MWSUIX[(I\2]C+QVR:D\=59H/=XWNE/L/&:&0O<Z@_Q;4WI0+R*AS2(Z,=AJP
+M@^JJE'V$KX2(TR`11_L38][YVP]-J+C"M-<3>Q`(<*862`6E<LI-X.BZ-\)=
+M%B_;U+3E-IOA2J/"B&+C;"4]3WTZ%ZO?0Q!9W9/9@ZNPC`S<S3'6`1@OT<QW
+MFA#C>Z*#NUL`R3716+IKE>45,`!DV96SH0J=W#6>5)U@M>AX*0!U2[.N0NW$
+M*0#=@LPHJO2V#G%2VBGIVK?`"7<E&&@0IS33&`8:/19CE]9$;4!^>>%,:$)F
+M*1\>BPP!:5_*4[FBZ(<O[J$F/ZS><W0HJ4TCU.IUC,_!4D%)M_0ZJR(1IKZ'
+ME2JJ=/L@."]HE&_1)^!G0C34=(0['`>,#2H(O-&;)]_(U+"!-)<Z-U`%`,O*
+ML-.]4_PO[R]F\#;I1*2"7_IK'XW:C!R5$IG?CB&+A]2%^T5C$:4#3I<O^0V8
+M&=$!JLOFP&`29]U,J#IMN]N]2F\C)HA--%Q[()];IBD*.$'DEK?][W3QDY^J
+MX[Y&G^V=;<'A3&+L=+0$X,SATI`XP0KS]<RBX6*X(Q?^P56HQ\G;QVM<B$3G
+MP+TB%]T=<\N03SIDZ&`LVS!&$P:=/9Z=7+J&00+:&5>%1R%V1)FA0H2G7R[*
+MM^&^WZQP&PUY+00*`A>39^'))`5<0>2:PAE+R[,A'#O5-SY0@:;8#]$6LP)*
+MR7[>Z\=<FY2(/$3_M0+FP!13$-T/!_%'7#E/*)@RNQ8=A=[/;XDV;AV26A(8
+MB#%,Z#6'"^T^0MBA)'R9.^'?V#KTA19($>%LR7KZ-*T@:WVFM&KQJ-P:>$NS
+M$EO%'."?.,_ENBAV:%ZYJRM`OWU[)">!(AB3<E+>&>/JX[1+@&^3410##33;
+M-Y`FMV?X?REOA6H*0I+@,K54R&@R,[NHCB7<-3OH&QB_F,;KU7_T%7SC@S>^
+MN07.>B@B%[*\T!$B.?*FR%K/TH9@S2^&!'+OBEB5^[17P1<YL=[;4:?VA8CS
+MAQ=#D`NRBL@\]K>@P-NK&RB0PUIT)OE%H0!=.Z-?ZJ2/,J>FK7Z#HK5J&5R'
+M?&M%W;=S%8XM273[BT>&;X)U%A5YI=R+OS5IY=9+2O8"A,F]70:F$EP[B@I`
+M=_U`,<W$2RZ8&!U:%>"L=?:-`J+6DC)O3PH%%[Y^:KAE'5^^*DT-Y9/-K,:N
+M`V=:B[PY;/'WIX%<PPE5D=$U*:,R&#S=Q,I9GF'1L&D8W)*:^Q<47!XW>=\D
+MTZ1/Y3G?A:_\NN6/OR7V1Q9Q3KF.>""FU"P'8<;6S9,NWD)C59=J_XW;A9KF
+M99IP,F!<Q!A7\KI#U`M^]#(<05M%AM:'J<09`-<:$K;$"DU=SF/H[$PE/'-W
+MZ';2K*\97,$(M&/<J76*POK7[75!Z!S,/?QDGXEFU2-1T7CV?(*#0P3TPX_Q
+M]8PH0FA_8D67T&0U`4K#<2=LUPA7I>2>5;T*]W$"8ENZ^A$8+2[Y[L"@4F0_
+M"<^,C]]&\!7V5)=^>&S5__X<=S/H`"`$:*'+(K).WAZKWC4%*W]O+G"3%ESI
+M).7VFUQDMR*2GRH#VN:M3XL]]H3#BG,7ZUNI9(8JA&NY4\>(>F;]KSJ\529X
+MY_0K;8)/(GT0!,B%&7=',L#%$T;]G363YNE31U#7S\'V7^A%E*@%Z-.+9XTO
+MO`IK'Z`@.*_R7R7DY?7\5GA4ZY8&//YA7(B*XSL=T]MD]B@@E'>;S?@N3M3>
+M*A]XGP\+-%`92I(BUR>?"6\)6U[4?)1DNH\R+#BG4;L?-D2)P4W'/'W)2$C7
+M5>H9EOG13%VST,<QSE1,#8\Y,>,K/G`UPP)B6LYU\M+6\.PL<;+_Y"88^'FX
+M5R?XZ,X@@*7Q3B(.IGC=MV7YG+1>=<O-IC+X.,-P\?YL)SQ_0@4UG>1)I'$.
+MZA]R@5KY=YFN&Q(N7["&>C-=+DOI:9/,_L#NU240B+_HI4&$N2\!J"M4?JJ(
+MNHI:67WD&7K*=UY]R;%(BEN!8NU/X'6IA,*Y7'GC;<'--G;K5#%_TEV7;]:)
+M7''T7D1<11W@YOVYRE!8LV`0\?R9VAXKF!<22L@EP)H*K_@!X>M9S]V??C%U
+M(X<F#+:V/^'PB,YLE_[XSX'RE"])I[28P]L\YLM>"JY5[4#-54QK^T1@L)%B
+MQ+L$S%1\&"-#DE2N!.K#;9"?-4GSA'7`0THA/YM;/`PZ9&+@(-&9K#7Q12!F
+MZOQ-M<QM,`U<DNEH8_PD[$J6IUP4RDGIM/)E?+9D?+'?8LR4-ZZ5SNT%+@6,
+MV_`^;NF(3Z#"K-0!*>=_0G'IG=#35>+J2XE0$#2Z^.P?_*!%_<&U8$_V/RZM
+M\F`!ZACID'V(;F%1PZ'3+VF,R))&D+[+EFWI#>H>W<#)E/*V)+@J&;_:*!34
+M[@=G&[?#@_H6(N4R'5)4]3>A'Q^&772N3.=ZOVP(=)AC`'&=[R#(_&3D8?_)
+MO<FK3+4ZB<$!XU=]KM5R!"9-MOZOF&7&.;Z0L33.T-0_E%H>Z9R+$T>O(VC>
+M>N!JA@!88!.+,J_<_!;J$X<0Z8])HC3?9MK63CEZ=+OK5-T-&+*:#=#4.ZU1
+MUQ/4*RNCZABL^@?32Q$^W$M[.OKD4FG@S>Y]Z_D[$>74B*!X]4COZ%R5E6U<
+M*+A8^M+K@3-!^E`QA-B\=ZKD")GI\LI)>C^#]R>@?$X8F^.IIL%K8Z@"$E=!
+MA-1#L'H[N`N"Q4JD2E_C2\6Z.;S,!.Y?.8<>VH63RN1,%'S8VIS7R3-"WS72
+M(M^"`@"+-X<PC'JISJ3`KCQ0OZT9"^<UL*>MWJ,H/#DF?V*BVNLA@2(NG6_W
+MLX^4@^-QY*>%N6/IW`&%J<E/XO#.<MUVH6/EDS;$9,LPE/N-*.^K4E(L/*\S
+M%"0[-H1<[L;&$-SO4R2@E]!$E&W@E0<,]PJ/U\L&+OFL;2"&7T-JF1F$$3\P
+M-INM#<G"95%YA\^85H<L-&1'L=TA6`3)PV%4]=KAMCC9X"6^QN4\AH"W(I:1
+M17"WFS6ZXN902EUKB+0`DA^?`R'5Z%G80KKPP>^YS"7K^];J4)P7XPW'HNGP
+-^K_XNY(IPH2"!#,T>```
+`
+end
diff --git a/sbin/gbde/template.txt b/sbin/gbde/template.txt
new file mode 100644
index 0000000..3d22007
--- /dev/null
+++ b/sbin/gbde/template.txt
@@ -0,0 +1,32 @@
+# $FreeBSD$
+#
+# Sector size is the smallest unit of data which can be read or written.
+# Making it too small decreases performance and decreases available space.
+# Making it too large may prevent filesystems from working. 512 is the
+# minimum and always safe. For UFS, use the fragment size
+#
+sector_size = 512
+
+#
+# Start and end of the encrypted section of the partition. Specify in
+# sector numbers. If none specified, "all" will be assumed, to the
+# extent the value of this can be established.
+#
+#first_sector = 0
+#last_sector = 2879
+#total_sectors = 2880
+
+#
+# An encrypted partition can have more than one key. It may be a good idea
+# to make at least two keys, and save one of them for "just in case" use.
+# The minimum is obviously one and the maximum is 4.
+#
+number_of_keys = 4
+
+#
+# Flushing the partition with random bytes prevents a brute-force attack
+# from skipping sectors which obviously contains un-encrypted data.
+# NB: This variable is boolean, if it is present it means "yes" even if
+# you set it to the value "no"
+#
+#random_flush =
diff --git a/sbin/gbde/test.sh b/sbin/gbde/test.sh
new file mode 100644
index 0000000..0aeb05c
--- /dev/null
+++ b/sbin/gbde/test.sh
@@ -0,0 +1,67 @@
+#!/bin/sh
+# $FreeBSD$
+
+set -e
+
+MD=99
+mdconfig -d -u $MD > /dev/null 2>&1 || true
+
+mdconfig -a -t malloc -s 1m -u $MD
+
+D=/dev/md$MD
+
+./gbde init $D -P foo -L /tmp/_l1
+./gbde setkey $D -p foo -l /tmp/_l1 -P bar -L /tmp/_l1
+./gbde setkey $D -p bar -l /tmp/_l1 -P foo -L /tmp/_l1
+
+./gbde setkey $D -p foo -l /tmp/_l1 -n 2 -P foo2 -L /tmp/_l2
+./gbde setkey $D -p foo2 -l /tmp/_l2 -n 3 -P foo3 -L /tmp/_l3
+./gbde setkey $D -p foo3 -l /tmp/_l3 -n 4 -P foo4 -L /tmp/_l4
+./gbde setkey $D -p foo4 -l /tmp/_l4 -n 1 -P foo1 -L /tmp/_l1
+
+./gbde nuke $D -p foo1 -l /tmp/_l1 -n 4
+if ./gbde nuke $D -p foo4 -l /tmp/_l4 -n 3 ; then false ; fi
+./gbde destroy $D -p foo2 -l /tmp/_l2
+if ./gbde destroy $D -p foo2 -l /tmp/_l2 ; then false ; fi
+
+./gbde nuke $D -p foo1 -l /tmp/_l1 -n -1
+if ./gbde nuke $D -p foo1 -l /tmp/_l1 -n -1 ; then false ; fi
+if ./gbde nuke $D -p foo2 -l /tmp/_l2 -n -1 ; then false ; fi
+if ./gbde nuke $D -p foo3 -l /tmp/_l3 -n -1 ; then false ; fi
+if ./gbde nuke $D -p foo4 -l /tmp/_l4 -n -1 ; then false ; fi
+
+rm -f /tmp/_l1 /tmp/_l2 /tmp/_l3 /tmp/_l4
+
+./gbde init $D -P foo
+./gbde setkey $D -p foo -P bar
+./gbde setkey $D -p bar -P foo
+
+./gbde setkey $D -p foo -n 2 -P foo2
+./gbde setkey $D -p foo2 -n 3 -P foo3
+./gbde setkey $D -p foo3 -n 4 -P foo4
+./gbde setkey $D -p foo4 -n 1 -P foo1
+
+mdconfig -d -u $MD
+
+mdconfig -a -t malloc -s 1m -u $MD
+if [ -f image.uu ] ; then
+ uudecode -p image.uu | bzcat > $D
+else
+ uudecode -p ${1}/image.uu | bzcat > $D
+fi
+
+if [ `md5 < $D` != "a4066a739338d451b919e63f9ee4a12c" ] ; then
+ echo "Failed to set up md(4) device correctly"
+ exit 2
+fi
+
+./gbde attach $D -p foo
+fsck_ffs ${D}.bde
+./gbde detach $D
+mdconfig -d -u $MD
+
+
+echo "***********"
+echo "Test passed"
+echo "***********"
+exit 0
diff --git a/sbin/geom/Makefile b/sbin/geom/Makefile
new file mode 100644
index 0000000..19a5636
--- /dev/null
+++ b/sbin/geom/Makefile
@@ -0,0 +1,27 @@
+# $FreeBSD$
+
+.if defined(RESCUE) || defined(RELEASE_CRUNCH)
+
+.PATH: ${.CURDIR}/class/part \
+ ${.CURDIR}/class/label \
+ ${.CURDIR}/core \
+ ${.CURDIR}/misc
+
+PROG= geom
+SRCS= geom.c geom_label.c geom_part.c subr.c
+MAN=
+
+WARNS?= 2
+CFLAGS+=-I${.CURDIR} -I${.CURDIR}/core -DSTATIC_GEOM_CLASSES
+
+LIBADD= geom util
+
+.include <bsd.prog.mk>
+
+.else
+
+SUBDIR= core class
+
+.include <bsd.subdir.mk>
+
+.endif
diff --git a/sbin/geom/Makefile.inc b/sbin/geom/Makefile.inc
new file mode 100644
index 0000000..0b1f991
--- /dev/null
+++ b/sbin/geom/Makefile.inc
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+GEOM_CLASS_DIR?=/lib/geom
+
+.include "../Makefile.inc"
diff --git a/sbin/geom/class/Makefile b/sbin/geom/class/Makefile
new file mode 100644
index 0000000..a7ed1b9
--- /dev/null
+++ b/sbin/geom/class/Makefile
@@ -0,0 +1,24 @@
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+SUBDIR= cache
+SUBDIR+=concat
+.if ${MK_OPENSSL} != "no"
+SUBDIR+=eli
+.endif
+SUBDIR+=journal
+SUBDIR+=label
+SUBDIR+=mirror
+SUBDIR+=mountver
+SUBDIR+=multipath
+SUBDIR+=nop
+SUBDIR+=part
+SUBDIR+=raid
+SUBDIR+=raid3
+SUBDIR+=sched
+SUBDIR+=shsec
+SUBDIR+=stripe
+SUBDIR+=virstor
+
+.include <bsd.subdir.mk>
diff --git a/sbin/geom/class/Makefile.inc b/sbin/geom/class/Makefile.inc
new file mode 100644
index 0000000..06b733f
--- /dev/null
+++ b/sbin/geom/class/Makefile.inc
@@ -0,0 +1,13 @@
+# $FreeBSD$
+
+SHLIBDIR?=${GEOM_CLASS_DIR}
+SHLIB_NAME?=geom_${GEOM_CLASS}.so
+LINKS= ${BINDIR}/geom ${BINDIR}/g${GEOM_CLASS}
+MAN= g${GEOM_CLASS}.8
+SRCS+= geom_${GEOM_CLASS}.c subr.c
+
+NO_WMISSING_VARIABLE_DECLARATIONS=
+
+CFLAGS+= -I${.CURDIR}/../..
+
+.include "../Makefile.inc"
diff --git a/sbin/geom/class/cache/Makefile b/sbin/geom/class/cache/Makefile
new file mode 100644
index 0000000..35f81d8
--- /dev/null
+++ b/sbin/geom/class/cache/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../../misc
+
+GEOM_CLASS= cache
+
+.include <bsd.lib.mk>
diff --git a/sbin/geom/class/cache/gcache.8 b/sbin/geom/class/cache/gcache.8
new file mode 100644
index 0000000..b0f1c7a
--- /dev/null
+++ b/sbin/geom/class/cache/gcache.8
@@ -0,0 +1,192 @@
+.\"-
+.\" Copyright (c) 2010 Edward Tomasz Napierala
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd January 3, 2010
+.Dt GCACHE 8
+.Os
+.Sh NAME
+.Nm gcache
+.Nd "control utility for CACHE GEOM class"
+.Sh SYNOPSIS
+.Nm
+.Cm create
+.Op Fl v
+.Op Fl b Ar blocksize
+.Op Fl s Ar size
+.Ar name
+.Ar prov
+.Nm
+.Cm configure
+.Op Fl v
+.Op Fl b Ar blocksize
+.Op Fl s Ar size
+.Ar name
+.Nm
+.Cm destroy
+.Op Fl fv
+.Ar name
+.Nm
+.Cm label
+.Op Fl v
+.Op Fl b Ar blocksize
+.Op Fl s Ar size
+.Ar name
+.Ar prov
+.Nm
+.Cm stop
+.Op Fl fv
+.Ar name ...
+.Nm
+.Cm clear
+.Op Fl v
+.Ar prov ...
+.Nm
+.Cm dump
+.Ar prov ...
+.Nm
+.Cm list
+.Nm
+.Cm status
+.Op Fl s Ar name
+.Nm
+.Cm load
+.Op Fl v
+.Nm
+.Cm unload
+.Op Fl v
+.Sh DESCRIPTION
+The
+.Nm
+utility is used to control GEOM cache, which can
+speed up read performance by sending fixed size
+read requests to its consumer. It has been developed to address
+the problem of a horrible read performance of a 64k blocksize FS
+residing on a RAID3 array with 8 data components, where a single
+disk component would only get 8k read requests, thus effectively
+killing disk performance under high load.
+.Pp
+Caching can be configured using two different methods:
+.Dq manual
+or
+.Dq automatic .
+When using the
+.Dq manual
+method, no metadata are stored on the devices, so the cached
+device has to be configured by hand every time it is needed.
+The
+.Dq automatic
+method uses on-disk metadata to detect devices.
+Once devices are labeled, they will be automatically detected and
+configured.
+.Pp
+The first argument to
+.Nm
+indicates an action to be performed:
+.Bl -tag -width ".Cm destroy"
+.It Cm create
+Cache the given devices with specified
+.Ar name .
+This is the
+.Dq manual
+method.
+The kernel module
+.Pa geom_cache.ko
+will be loaded if it is not loaded already.
+.It Cm label
+Cache the given devices with the specified
+.Ar name .
+This is the
+.Dq automatic
+method, where metadata are stored in every device's last sector.
+The kernel module
+.Pa geom_cache.ko
+will be loaded if it is not loaded already.
+.It Cm stop
+Turn off existing cache device by its
+.Ar name .
+This command does not touch on-disk metadata!
+.It Cm destroy
+Same as
+.Cm stop .
+.It Cm clear
+Clear metadata on the given devices.
+.It Cm dump
+Dump metadata stored on the given devices.
+.It Cm list
+See
+.Xr geom 8 .
+.It Cm status
+See
+.Xr geom 8 .
+.It Cm load
+See
+.Xr geom 8 .
+.It Cm unload
+See
+.Xr geom 8 .
+.El
+.Pp
+Additional options:
+.Bl -tag -width indent
+.It Fl f
+Force the removal of the specified cache device.
+.It Fl v
+Be more verbose.
+.El
+.Sh SYSCTL VARIABLES
+The following
+.Xr sysctl 8
+variables can be used to control the behavior of the
+.Nm CACHE
+GEOM class.
+The default value is shown next to each variable.
+.Bl -tag -width indent
+.It Va kern.geom.cache.used_hi : No 20
+.It Va kern.geom.cache.used_lo : No 5
+.It Va kern.geom.cache.idletime : No 5
+.It Va kern.geom.cache.timeout : No 10
+.It Va kern.geom.cache.enable : No 1
+.It Va kern.geom.cache.debug : No 0
+Debug level of the
+.Nm CACHE
+GEOM class.
+This can be set to a number between 0 and 3 inclusive.
+If set to 0 minimal debug information is printed, and if set to 3 the
+maximum amount of debug information is printed.
+.El
+.Sh EXIT STATUS
+Exit status is 0 on success, and 1 if the command fails.
+.Sh SEE ALSO
+.Xr geom 4 ,
+.Xr geom 8
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Fx 7.0 .
+.Sh AUTHORS
+.An Ruslan Ermilov Aq Mt ru@FreeBSD.org
diff --git a/sbin/geom/class/cache/geom_cache.c b/sbin/geom/class/cache/geom_cache.c
new file mode 100644
index 0000000..4b18e67
--- /dev/null
+++ b/sbin/geom/class/cache/geom_cache.c
@@ -0,0 +1,239 @@
+/*-
+ * Copyright (c) 2006 Ruslan Ermilov <ru@FreeBSD.org>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <strings.h>
+#include <libgeom.h>
+#include <geom/cache/g_cache.h>
+
+#include "core/geom.h"
+#include "misc/subr.h"
+
+
+uint32_t lib_version = G_LIB_VERSION;
+uint32_t version = G_CACHE_VERSION;
+
+#define GCACHE_BLOCKSIZE "65536"
+#define GCACHE_SIZE "100"
+
+static void cache_main(struct gctl_req *req, unsigned flags);
+static void cache_clear(struct gctl_req *req);
+static void cache_dump(struct gctl_req *req);
+static void cache_label(struct gctl_req *req);
+
+struct g_command class_commands[] = {
+ { "clear", G_FLAG_VERBOSE, cache_main, G_NULL_OPTS,
+ "[-v] prov ..."
+ },
+ { "configure", G_FLAG_VERBOSE, NULL,
+ {
+ { 'b', "blocksize", "0", G_TYPE_NUMBER },
+ { 's', "size", "0", G_TYPE_NUMBER },
+ G_OPT_SENTINEL
+ },
+ "[-v] [-b blocksize] [-s size] name"
+ },
+ { "create", G_FLAG_VERBOSE | G_FLAG_LOADKLD, NULL,
+ {
+ { 'b', "blocksize", GCACHE_BLOCKSIZE, G_TYPE_NUMBER },
+ { 's', "size", GCACHE_SIZE, G_TYPE_NUMBER },
+ G_OPT_SENTINEL
+ },
+ "[-v] [-b blocksize] [-s size] name prov"
+ },
+ { "destroy", G_FLAG_VERBOSE, NULL,
+ {
+ { 'f', "force", NULL, G_TYPE_BOOL },
+ G_OPT_SENTINEL
+ },
+ "[-fv] name ..."
+ },
+ { "dump", 0, cache_main, G_NULL_OPTS,
+ "prov ..."
+ },
+ { "label", G_FLAG_VERBOSE | G_FLAG_LOADKLD, cache_main,
+ {
+ { 'b', "blocksize", GCACHE_BLOCKSIZE, G_TYPE_NUMBER },
+ { 's', "size", GCACHE_SIZE, G_TYPE_NUMBER },
+ G_OPT_SENTINEL
+ },
+ "[-v] [-b blocksize] [-s size] name prov"
+ },
+ { "reset", G_FLAG_VERBOSE, NULL, G_NULL_OPTS,
+ "[-v] name ..."
+ },
+ { "stop", G_FLAG_VERBOSE, NULL,
+ {
+ { 'f', "force", NULL, G_TYPE_BOOL },
+ G_OPT_SENTINEL
+ },
+ "[-fv] name ..."
+ },
+ G_CMD_SENTINEL
+};
+
+static int verbose = 0;
+
+static void
+cache_main(struct gctl_req *req, unsigned flags)
+{
+ const char *name;
+
+ if ((flags & G_FLAG_VERBOSE) != 0)
+ verbose = 1;
+
+ name = gctl_get_ascii(req, "verb");
+ if (name == NULL) {
+ gctl_error(req, "No '%s' argument.", "verb");
+ return;
+ }
+ if (strcmp(name, "label") == 0)
+ cache_label(req);
+ else if (strcmp(name, "clear") == 0)
+ cache_clear(req);
+ else if (strcmp(name, "dump") == 0)
+ cache_dump(req);
+ else
+ gctl_error(req, "Unknown command: %s.", name);
+}
+
+static void
+cache_label(struct gctl_req *req)
+{
+ struct g_cache_metadata md;
+ u_char sector[512];
+ const char *name;
+ int error, nargs;
+ intmax_t val;
+
+ nargs = gctl_get_int(req, "nargs");
+ if (nargs != 2) {
+ gctl_error(req, "Invalid number of arguments.");
+ return;
+ }
+
+ strlcpy(md.md_magic, G_CACHE_MAGIC, sizeof(md.md_magic));
+ md.md_version = G_CACHE_VERSION;
+ name = gctl_get_ascii(req, "arg0");
+ strlcpy(md.md_name, name, sizeof(md.md_name));
+ val = gctl_get_intmax(req, "blocksize");
+ md.md_bsize = val;
+ val = gctl_get_intmax(req, "size");
+ md.md_size = val;
+
+ name = gctl_get_ascii(req, "arg1");
+ md.md_provsize = g_get_mediasize(name);
+ if (md.md_provsize == 0) {
+ fprintf(stderr, "Can't get mediasize of %s: %s.\n",
+ name, strerror(errno));
+ gctl_error(req, "Not fully done.");
+ return;
+ }
+ cache_metadata_encode(&md, sector);
+ error = g_metadata_store(name, sector, sizeof(sector));
+ if (error != 0) {
+ fprintf(stderr, "Can't store metadata on %s: %s.\n",
+ name, strerror(error));
+ gctl_error(req, "Not fully done.");
+ return;
+ }
+ if (verbose)
+ printf("Metadata value stored on %s.\n", name);
+}
+
+static void
+cache_clear(struct gctl_req *req)
+{
+ const char *name;
+ int error, i, nargs;
+
+ nargs = gctl_get_int(req, "nargs");
+ if (nargs < 1) {
+ gctl_error(req, "Too few arguments.");
+ return;
+ }
+
+ for (i = 0; i < nargs; i++) {
+ name = gctl_get_ascii(req, "arg%d", i);
+ error = g_metadata_clear(name, G_CACHE_MAGIC);
+ if (error != 0) {
+ fprintf(stderr, "Can't clear metadata on %s: %s.\n",
+ name, strerror(error));
+ gctl_error(req, "Not fully done.");
+ continue;
+ }
+ if (verbose)
+ printf("Metadata cleared on %s.\n", name);
+ }
+}
+
+static void
+cache_metadata_dump(const struct g_cache_metadata *md)
+{
+
+ printf(" Magic string: %s\n", md->md_magic);
+ printf(" Metadata version: %u\n", (u_int)md->md_version);
+ printf(" Device name: %s\n", md->md_name);
+ printf(" Block size: %u\n", (u_int)md->md_bsize);
+ printf(" Cache size: %u\n", (u_int)md->md_size);
+ printf(" Provider size: %ju\n", (uintmax_t)md->md_provsize);
+}
+
+static void
+cache_dump(struct gctl_req *req)
+{
+ struct g_cache_metadata md, tmpmd;
+ const char *name;
+ int error, i, nargs;
+
+ nargs = gctl_get_int(req, "nargs");
+ if (nargs < 1) {
+ gctl_error(req, "Too few arguments.");
+ return;
+ }
+
+ for (i = 0; i < nargs; i++) {
+ name = gctl_get_ascii(req, "arg%d", i);
+ error = g_metadata_read(name, (u_char *)&tmpmd, sizeof(tmpmd),
+ G_CACHE_MAGIC);
+ if (error != 0) {
+ fprintf(stderr, "Can't read metadata from %s: %s.\n",
+ name, strerror(error));
+ gctl_error(req, "Not fully done.");
+ continue;
+ }
+ cache_metadata_decode((u_char *)&tmpmd, &md);
+ printf("Metadata on %s:\n", name);
+ cache_metadata_dump(&md);
+ printf("\n");
+ }
+}
diff --git a/sbin/geom/class/concat/Makefile b/sbin/geom/class/concat/Makefile
new file mode 100644
index 0000000..34e2c28
--- /dev/null
+++ b/sbin/geom/class/concat/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../../misc
+
+GEOM_CLASS= concat
+
+.include <bsd.lib.mk>
diff --git a/sbin/geom/class/concat/gconcat.8 b/sbin/geom/class/concat/gconcat.8
new file mode 100644
index 0000000..d874b08
--- /dev/null
+++ b/sbin/geom/class/concat/gconcat.8
@@ -0,0 +1,197 @@
+.\" Copyright (c) 2004-2005 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd May 21, 2004
+.Dt GCONCAT 8
+.Os
+.Sh NAME
+.Nm gconcat
+.Nd "disk concatenation control utility"
+.Sh SYNOPSIS
+.Nm
+.Cm create
+.Op Fl v
+.Ar name
+.Ar prov ...
+.Nm
+.Cm destroy
+.Op Fl fv
+.Ar name ...
+.Nm
+.Cm label
+.Op Fl hv
+.Ar name
+.Ar prov ...
+.Nm
+.Cm stop
+.Op Fl fv
+.Ar name ...
+.Nm
+.Cm clear
+.Op Fl v
+.Ar prov ...
+.Nm
+.Cm dump
+.Ar prov ...
+.Nm
+.Cm list
+.Nm
+.Cm status
+.Nm
+.Cm load
+.Nm
+.Cm unload
+.Sh DESCRIPTION
+The
+.Nm
+utility is used for device concatenation configuration.
+The concatenation can be configured using two different methods:
+.Dq manual
+or
+.Dq automatic .
+When using the
+.Dq manual
+method, no metadata are stored on the devices, so the concatenated
+device has to be configured by hand every time it is needed.
+The
+.Dq automatic
+method uses on-disk metadata to detect devices.
+Once devices are labeled, they will be automatically detected and
+configured.
+.Pp
+The first argument to
+.Nm
+indicates an action to be performed:
+.Bl -tag -width ".Cm destroy"
+.It Cm create
+Concatenate the given devices with specified
+.Ar name .
+This is the
+.Dq manual
+method.
+The kernel module
+.Pa geom_concat.ko
+will be loaded if it is not loaded already.
+.It Cm label
+Concatenate the given devices with the specified
+.Ar name .
+This is the
+.Dq automatic
+method, where metadata are stored in every device's last sector.
+The kernel module
+.Pa geom_concat.ko
+will be loaded if it is not loaded already.
+.It Cm stop
+Turn off existing concatenate device by its
+.Ar name .
+This command does not touch on-disk metadata!
+.It Cm destroy
+Same as
+.Cm stop .
+.It Cm clear
+Clear metadata on the given devices.
+.It Cm dump
+Dump metadata stored on the given devices.
+.It Cm list
+See
+.Xr geom 8 .
+.It Cm status
+See
+.Xr geom 8 .
+.It Cm load
+See
+.Xr geom 8 .
+.It Cm unload
+See
+.Xr geom 8 .
+.El
+.Pp
+Additional options:
+.Bl -tag -width indent
+.It Fl f
+Force the removal of the specified concatenated device.
+.It Fl h
+Hardcode providers' names in metadata.
+.It Fl v
+Be more verbose.
+.El
+.Sh SYSCTL VARIABLES
+The following
+.Xr sysctl 8
+variables can be used to control the behavior of the
+.Nm CONCAT
+GEOM class.
+The default value is shown next to each variable.
+.Bl -tag -width indent
+.It Va kern.geom.concat.debug : No 0
+Debug level of the
+.Nm CONCAT
+GEOM class.
+This can be set to a number between 0 and 3 inclusive.
+If set to 0 minimal debug information is printed, and if set to 3 the
+maximum amount of debug information is printed.
+.El
+.Sh EXIT STATUS
+Exit status is 0 on success, and 1 if the command fails.
+.Sh EXAMPLES
+The following example shows how to configure four disks for automatic
+concatenation, create a file system on it, and mount it:
+.Bd -literal -offset indent
+gconcat label -v data /dev/da0 /dev/da1 /dev/da2 /dev/da3
+newfs /dev/concat/data
+mount /dev/concat/data /mnt
+[...]
+umount /mnt
+gconcat stop data
+gconcat unload
+.Ed
+.Pp
+Configure concatenated provider on one disk only.
+Create file system.
+Add two more disks and extend existing file system.
+.Bd -literal -offset indent
+gconcat label data /dev/da0
+newfs /dev/concat/data
+gconcat label data /dev/da0 /dev/da1 /dev/da2
+growfs /dev/concat/data
+.Ed
+.Sh SEE ALSO
+.Xr geom 4 ,
+.Xr loader.conf 5 ,
+.Xr geom 8 ,
+.Xr growfs 8 ,
+.Xr gvinum 8 ,
+.Xr mount 8 ,
+.Xr newfs 8 ,
+.Xr sysctl 8 ,
+.Xr umount 8
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Fx 5.3 .
+.Sh AUTHORS
+.An Pawel Jakub Dawidek Aq Mt pjd@FreeBSD.org
diff --git a/sbin/geom/class/concat/geom_concat.c b/sbin/geom/class/concat/geom_concat.c
new file mode 100644
index 0000000..d4f3645
--- /dev/null
+++ b/sbin/geom/class/concat/geom_concat.c
@@ -0,0 +1,247 @@
+/*-
+ * Copyright (c) 2004-2005 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <errno.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <assert.h>
+#include <libgeom.h>
+#include <geom/concat/g_concat.h>
+
+#include "core/geom.h"
+#include "misc/subr.h"
+
+
+uint32_t lib_version = G_LIB_VERSION;
+uint32_t version = G_CONCAT_VERSION;
+
+static void concat_main(struct gctl_req *req, unsigned flags);
+static void concat_clear(struct gctl_req *req);
+static void concat_dump(struct gctl_req *req);
+static void concat_label(struct gctl_req *req);
+
+struct g_command class_commands[] = {
+ { "clear", G_FLAG_VERBOSE, concat_main, G_NULL_OPTS,
+ "[-v] prov ..."
+ },
+ { "create", G_FLAG_VERBOSE | G_FLAG_LOADKLD, NULL, G_NULL_OPTS,
+ "[-v] name prov ..."
+ },
+ { "destroy", G_FLAG_VERBOSE, NULL,
+ {
+ { 'f', "force", NULL, G_TYPE_BOOL },
+ G_OPT_SENTINEL
+ },
+ "[-fv] name ..."
+ },
+ { "dump", 0, concat_main, G_NULL_OPTS,
+ "prov ..."
+ },
+ { "label", G_FLAG_VERBOSE | G_FLAG_LOADKLD, concat_main,
+ {
+ { 'h', "hardcode", NULL, G_TYPE_BOOL },
+ G_OPT_SENTINEL
+ },
+ "[-hv] name prov ..."
+ },
+ { "stop", G_FLAG_VERBOSE, NULL,
+ {
+ { 'f', "force", NULL, G_TYPE_BOOL },
+ G_OPT_SENTINEL
+ },
+ "[-fv] name ..."
+ },
+ G_CMD_SENTINEL
+};
+
+static int verbose = 0;
+
+static void
+concat_main(struct gctl_req *req, unsigned flags)
+{
+ const char *name;
+
+ if ((flags & G_FLAG_VERBOSE) != 0)
+ verbose = 1;
+
+ name = gctl_get_ascii(req, "verb");
+ if (name == NULL) {
+ gctl_error(req, "No '%s' argument.", "verb");
+ return;
+ }
+ if (strcmp(name, "label") == 0)
+ concat_label(req);
+ else if (strcmp(name, "clear") == 0)
+ concat_clear(req);
+ else if (strcmp(name, "dump") == 0)
+ concat_dump(req);
+ else
+ gctl_error(req, "Unknown command: %s.", name);
+}
+
+static void
+concat_label(struct gctl_req *req)
+{
+ struct g_concat_metadata md;
+ u_char sector[512];
+ const char *name;
+ int error, i, hardcode, nargs;
+
+ nargs = gctl_get_int(req, "nargs");
+ if (nargs < 2) {
+ gctl_error(req, "Too few arguments.");
+ return;
+ }
+ hardcode = gctl_get_int(req, "hardcode");
+
+ /*
+ * Clear last sector first to spoil all components if device exists.
+ */
+ for (i = 1; i < nargs; i++) {
+ name = gctl_get_ascii(req, "arg%d", i);
+ error = g_metadata_clear(name, NULL);
+ if (error != 0) {
+ gctl_error(req, "Can't store metadata on %s: %s.", name,
+ strerror(error));
+ return;
+ }
+ }
+
+ strlcpy(md.md_magic, G_CONCAT_MAGIC, sizeof(md.md_magic));
+ md.md_version = G_CONCAT_VERSION;
+ name = gctl_get_ascii(req, "arg0");
+ strlcpy(md.md_name, name, sizeof(md.md_name));
+ md.md_id = arc4random();
+ md.md_all = nargs - 1;
+
+ /*
+ * Ok, store metadata.
+ */
+ for (i = 1; i < nargs; i++) {
+ name = gctl_get_ascii(req, "arg%d", i);
+ md.md_no = i - 1;
+ if (!hardcode)
+ bzero(md.md_provider, sizeof(md.md_provider));
+ else {
+ if (strncmp(name, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
+ name += sizeof(_PATH_DEV) - 1;
+ strlcpy(md.md_provider, name, sizeof(md.md_provider));
+ }
+ md.md_provsize = g_get_mediasize(name);
+ if (md.md_provsize == 0) {
+ fprintf(stderr, "Can't get mediasize of %s: %s.\n",
+ name, strerror(errno));
+ gctl_error(req, "Not fully done.");
+ continue;
+ }
+ concat_metadata_encode(&md, sector);
+ error = g_metadata_store(name, sector, sizeof(sector));
+ if (error != 0) {
+ fprintf(stderr, "Can't store metadata on %s: %s.\n",
+ name, strerror(error));
+ gctl_error(req, "Not fully done.");
+ continue;
+ }
+ if (verbose)
+ printf("Metadata value stored on %s.\n", name);
+ }
+}
+
+static void
+concat_clear(struct gctl_req *req)
+{
+ const char *name;
+ int error, i, nargs;
+
+ nargs = gctl_get_int(req, "nargs");
+ if (nargs < 1) {
+ gctl_error(req, "Too few arguments.");
+ return;
+ }
+
+ for (i = 0; i < nargs; i++) {
+ name = gctl_get_ascii(req, "arg%d", i);
+ error = g_metadata_clear(name, G_CONCAT_MAGIC);
+ if (error != 0) {
+ fprintf(stderr, "Can't clear metadata on %s: %s.\n",
+ name, strerror(error));
+ gctl_error(req, "Not fully done.");
+ continue;
+ }
+ if (verbose)
+ printf("Metadata cleared on %s.\n", name);
+ }
+}
+
+static void
+concat_metadata_dump(const struct g_concat_metadata *md)
+{
+
+ printf(" Magic string: %s\n", md->md_magic);
+ printf(" Metadata version: %u\n", (u_int)md->md_version);
+ printf(" Device name: %s\n", md->md_name);
+ printf(" Device ID: %u\n", (u_int)md->md_id);
+ printf(" Disk number: %u\n", (u_int)md->md_no);
+ printf("Total number of disks: %u\n", (u_int)md->md_all);
+ printf(" Hardcoded provider: %s\n", md->md_provider);
+}
+
+static void
+concat_dump(struct gctl_req *req)
+{
+ struct g_concat_metadata md, tmpmd;
+ const char *name;
+ int error, i, nargs;
+
+ nargs = gctl_get_int(req, "nargs");
+ if (nargs < 1) {
+ gctl_error(req, "Too few arguments.");
+ return;
+ }
+
+ for (i = 0; i < nargs; i++) {
+ name = gctl_get_ascii(req, "arg%d", i);
+ error = g_metadata_read(name, (u_char *)&tmpmd, sizeof(tmpmd),
+ G_CONCAT_MAGIC);
+ if (error != 0) {
+ fprintf(stderr, "Can't read metadata from %s: %s.\n",
+ name, strerror(error));
+ gctl_error(req, "Not fully done.");
+ continue;
+ }
+ concat_metadata_decode((u_char *)&tmpmd, &md);
+ printf("Metadata on %s:\n", name);
+ concat_metadata_dump(&md);
+ printf("\n");
+ }
+}
diff --git a/sbin/geom/class/eli/Makefile b/sbin/geom/class/eli/Makefile
new file mode 100644
index 0000000..f8e453d
--- /dev/null
+++ b/sbin/geom/class/eli/Makefile
@@ -0,0 +1,17 @@
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../../misc ${.CURDIR}/../../../../sys/geom/eli ${.CURDIR}/../../../../sys/crypto/sha2
+
+GEOM_CLASS= eli
+SRCS= g_eli_crypto.c
+SRCS+= g_eli_key.c
+SRCS+= pkcs5v2.c
+SRCS+= sha2.c
+
+LIBADD= md crypto
+
+WARNS?= 3
+
+CFLAGS+=-I${.CURDIR}/../../../../sys
+
+.include <bsd.lib.mk>
diff --git a/sbin/geom/class/eli/geli.8 b/sbin/geom/class/eli/geli.8
new file mode 100644
index 0000000..c435859
--- /dev/null
+++ b/sbin/geom/class/eli/geli.8
@@ -0,0 +1,1060 @@
+.\" Copyright (c) 2005-2011 Pawel Jakub Dawidek <pawel@dawidek.net>
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd June 18, 2014
+.Dt GELI 8
+.Os
+.Sh NAME
+.Nm geli
+.Nd "control utility for the cryptographic GEOM class"
+.Sh SYNOPSIS
+To compile GEOM_ELI into your kernel, add the following lines to your kernel
+configuration file:
+.Bd -ragged -offset indent
+.Cd "device crypto"
+.Cd "options GEOM_ELI"
+.Ed
+.Pp
+Alternatively, to load the GEOM_ELI module at boot time, add the following line
+to your
+.Xr loader.conf 5 :
+.Bd -literal -offset indent
+geom_eli_load="YES"
+.Ed
+.Pp
+Usage of the
+.Nm
+utility:
+.Pp
+.Nm
+.Cm init
+.Op Fl bPv
+.Op Fl a Ar aalgo
+.Op Fl B Ar backupfile
+.Op Fl e Ar ealgo
+.Op Fl i Ar iterations
+.Op Fl J Ar newpassfile
+.Op Fl K Ar newkeyfile
+.Op Fl l Ar keylen
+.Op Fl s Ar sectorsize
+.Op Fl V Ar version
+.Ar prov
+.Nm
+.Cm label - an alias for
+.Cm init
+.Nm
+.Cm attach
+.Op Fl dprv
+.Op Fl j Ar passfile
+.Op Fl k Ar keyfile
+.Ar prov
+.Nm
+.Cm detach
+.Op Fl fl
+.Ar prov ...
+.Nm
+.Cm stop - an alias for
+.Cm detach
+.Nm
+.Cm onetime
+.Op Fl d
+.Op Fl a Ar aalgo
+.Op Fl e Ar ealgo
+.Op Fl l Ar keylen
+.Op Fl s Ar sectorsize
+.Ar prov
+.Nm
+.Cm configure
+.Op Fl bB
+.Ar prov ...
+.Nm
+.Cm setkey
+.Op Fl pPv
+.Op Fl i Ar iterations
+.Op Fl j Ar passfile
+.Op Fl J Ar newpassfile
+.Op Fl k Ar keyfile
+.Op Fl K Ar newkeyfile
+.Op Fl n Ar keyno
+.Ar prov
+.Nm
+.Cm delkey
+.Op Fl afv
+.Op Fl n Ar keyno
+.Ar prov
+.Nm
+.Cm kill
+.Op Fl av
+.Op Ar prov ...
+.Nm
+.Cm backup
+.Op Fl v
+.Ar prov
+.Ar file
+.Nm
+.Cm restore
+.Op Fl fv
+.Ar file
+.Ar prov
+.Nm
+.Cm suspend
+.Op Fl v
+.Fl a | Ar prov ...
+.Nm
+.Cm resume
+.Op Fl pv
+.Op Fl j Ar passfile
+.Op Fl k Ar keyfile
+.Ar prov
+.Nm
+.Cm resize
+.Op Fl v
+.Fl s Ar oldsize
+.Ar prov
+.Nm
+.Cm version
+.Op Ar prov ...
+.Nm
+.Cm clear
+.Op Fl v
+.Ar prov ...
+.Nm
+.Cm dump
+.Op Fl v
+.Ar prov ...
+.Nm
+.Cm list
+.Nm
+.Cm status
+.Nm
+.Cm load
+.Nm
+.Cm unload
+.Sh DESCRIPTION
+The
+.Nm
+utility is used to configure encryption on GEOM providers.
+.Pp
+The following is a list of the most important features:
+.Pp
+.Bl -bullet -offset indent -compact
+.It
+Utilizes the
+.Xr crypto 9
+framework, so when there is crypto hardware available,
+.Nm
+will make use of it automatically.
+.It
+Supports many cryptographic algorithms (currently
+.Nm AES-XTS ,
+.Nm AES-CBC ,
+.Nm Blowfish-CBC ,
+.Nm Camellia-CBC
+and
+.Nm 3DES-CBC ) .
+.It
+Can optionally perform data authentication (integrity verification) utilizing
+one of the following algorithms:
+.Nm HMAC/MD5 ,
+.Nm HMAC/SHA1 ,
+.Nm HMAC/RIPEMD160 ,
+.Nm HMAC/SHA256 ,
+.Nm HMAC/SHA384
+or
+.Nm HMAC/SHA512 .
+.It
+Can create a User Key from up to two, piecewise components: a passphrase
+entered via prompt or read from one or more passfiles; a keyfile read from
+one or more files.
+.It
+Allows encryption of the root partition.
+The user will be asked for the
+passphrase before the root file system is mounted.
+.It
+Strengthens the passphrase component of the User Key with:
+.Rs
+.%A B. Kaliski
+.%T "PKCS #5: Password-Based Cryptography Specification, Version 2.0."
+.%R RFC
+.%N 2898
+.Re
+.It
+Allows the use of two independent User Keys (e.g., a
+.Qq "user key"
+and a
+.Qq "company key" ) .
+.It
+It is fast -
+.Nm
+performs simple sector-to-sector encryption.
+.It
+Allows the encrypted Master Key to be backed up and restored,
+so that if a user has to quickly destroy key material,
+it is possible to get the data back by restoring keys from
+backup.
+.It
+Providers can be configured to automatically detach on last close
+(so users do not have to remember to detach providers after unmounting
+the file systems).
+.It
+Allows attaching a provider with a random, one-time Master Key -
+useful for swap partitions and temporary file systems.
+.It
+Allows verification of data integrity (data authentication).
+.It
+Allows suspending and resuming encrypted devices.
+.El
+.Pp
+The first argument to
+.Nm
+indicates an action to be performed:
+.Bl -tag -width ".Cm configure"
+.It Cm init
+Initialize the provider which needs to be encrypted.
+Here you can set up the cryptographic algorithm to use, Data Key length,
+etc.
+The last sector of the provider is used to store metadata.
+The
+.Cm init
+subcommand also automatically writes metadata backups to
+.Pa /var/backups/<prov>.eli
+file.
+The metadata can be recovered with the
+.Cm restore
+subcommand described below.
+.Pp
+Additional options include:
+.Bl -tag -width ".Fl J Ar newpassfile"
+.It Fl a Ar aalgo
+Enable data integrity verification (authentication) using the given algorithm.
+This will reduce the size of storage available and also reduce speed.
+For example, when using 4096 bytes sector and
+.Nm HMAC/SHA256
+algorithm, 89% of the original provider storage will be available for use.
+Currently supported algorithms are:
+.Nm HMAC/MD5 ,
+.Nm HMAC/SHA1 ,
+.Nm HMAC/RIPEMD160 ,
+.Nm HMAC/SHA256 ,
+.Nm HMAC/SHA384
+and
+.Nm HMAC/SHA512 .
+If the option is not given, there will be no authentication, only encryption.
+The recommended algorithm is
+.Nm HMAC/SHA256 .
+.It Fl b
+Ask for the passphrase on boot, before the root partition is mounted.
+This makes it possible to use an encrypted root partition.
+One will still need bootable unencrypted storage with a
+.Pa /boot/
+directory, which can be a CD-ROM disc or USB pen-drive, that can be removed
+after boot.
+.It Fl B Ar backupfile
+File name to use for metadata backup instead of the default
+.Pa /var/backups/<prov>.eli .
+To inhibit backups, you can use
+.Pa none
+as the
+.Ar backupfile .
+.It Fl e Ar ealgo
+Encryption algorithm to use.
+Currently supported algorithms are:
+.Nm AES-XTS ,
+.Nm AES-CBC ,
+.Nm Blowfish-CBC ,
+.Nm Camellia-CBC ,
+.Nm 3DES-CBC ,
+and
+.Nm NULL .
+The default and recommended algorithm is
+.Nm AES-XTS .
+.Nm NULL
+is unencrypted.
+.It Fl i Ar iterations
+Number of iterations to use with PKCS#5v2 when processing User Key
+passphrase component.
+If this option is not specified,
+.Nm
+will find the number of iterations which is equal to 2 seconds of crypto work.
+If 0 is given, PKCS#5v2 will not be used.
+PKCS#5v2 processing is performed once, after all parts of the passphrase
+component have been read.
+.It Fl J Ar newpassfile
+Specifies a file which contains the passphrase component of the User Key
+(or part of it).
+If
+.Ar newpassfile
+is given as -, standard input will be used.
+Only the first line (excluding new-line character) is taken from the given file.
+This argument can be specified multiple times, which has the effect of
+reassembling a single passphrase split across multiple files.
+Cannot be combined with the
+.Fl P
+option.
+.It Fl K Ar newkeyfile
+Specifies a file which contains the keyfile component of the User Key
+(or part of it).
+If
+.Ar newkeyfile
+is given as -, standard input will be used.
+This argument can be specified multiple times, which has the effect of
+reassembling a single keyfile split across multiple keyfile parts.
+.It Fl l Ar keylen
+Data Key length to use with the given cryptographic algorithm.
+If the length is not specified, the selected algorithm uses its
+.Em default
+key length.
+.Bl -ohang -offset indent
+.It Nm AES-XTS
+.Em 128 ,
+256
+.It Nm AES-CBC , Nm Camellia-CBC
+.Em 128 ,
+192,
+256
+.It Nm Blowfish-CBC
+.Em 128
++ n * 32, for n=[0..10]
+.It Nm 3DES-CBC
+.Em 192
+.El
+.It Fl P
+Do not use a passphrase as a component of the User Key.
+Cannot be combined with the
+.Fl J
+option.
+.It Fl s Ar sectorsize
+Change decrypted provider's sector size.
+Increasing the sector size allows increased performance,
+because encryption/decryption which requires an initialization vector
+is done per sector; fewer sectors means less computational work.
+.It Fl V Ar version
+Metadata version to use.
+This option is helpful when creating a provider that may be used by older
+.Nm FreeBSD/GELI
+versions.
+Consult the
+.Sx HISTORY
+section to find which metadata version is supported by which FreeBSD version.
+Note that using an older version of metadata may limit the number of
+features available.
+.El
+.It Cm attach
+Attach the given provider.
+The encrypted Master Key will be loaded from the metadata and decrypted
+using the given passphrase/keyfile and a new GEOM provider will be created
+using the given provider's name with an
+.Qq .eli
+suffix.
+.Pp
+Additional options include:
+.Bl -tag -width ".Fl j Ar passfile"
+.It Fl d
+If specified, a decrypted provider will be detached automatically on last close.
+This can help with scarce memory so the user does not have to remember to detach the
+provider after unmounting the file system.
+It only works when the provider was opened for writing, so it will not work if
+the file system on the provider is mounted read-only.
+Probably a better choice is the
+.Fl l
+option for the
+.Cm detach
+subcommand.
+.It Fl j Ar passfile
+Specifies a file which contains the passphrase component of the User Key
+(or part of it).
+For more information see the description of the
+.Fl J
+option for the
+.Cm init
+subcommand.
+.It Fl k Ar keyfile
+Specifies a file which contains the keyfile component of the User Key
+(or part of it).
+For more information see the description of the
+.Fl K
+option for the
+.Cm init
+subcommand.
+.It Fl p
+Do not use a passphrase as a component of the User Key.
+Cannot be combined with the
+.Fl j
+option.
+.It Fl r
+Attach read-only provider.
+It will not be opened for writing.
+.El
+.It Cm detach
+Detach the given providers, which means remove the devfs entry
+and clear the Master Key and Data Keys from memory.
+.Pp
+Additional options include:
+.Bl -tag -width ".Fl f"
+.It Fl f
+Force detach - detach even if the provider is open.
+.It Fl l
+Mark provider to detach on last close.
+If this option is specified, the provider will not be detached
+while it is open, but will be automatically detached when it is closed for the
+last time even if it was only opened for reading.
+.El
+.It Cm onetime
+Attach the given providers with a random, one-time (ephemeral) Master Key.
+The command can be used to encrypt swap partitions or temporary file systems.
+.Pp
+Additional options include:
+.Bl -tag -width ".Fl a Ar sectorsize"
+.It Fl a Ar aalgo
+Enable data integrity verification (authentication).
+For more information, see the description of the
+.Cm init
+subcommand.
+.It Fl e Ar ealgo
+Encryption algorithm to use.
+For more information, see the description of the
+.Cm init
+subcommand.
+.It Fl d
+Detach on last close.
+Note: this option is not usable for temporary file systems as the provider will
+be detached after creating the file system on it.
+It still can (and should be) used for swap partitions.
+For more information, see the description of the
+.Cm attach
+subcommand.
+.It Fl l Ar keylen
+Data Key length to use with the given cryptographic algorithm.
+For more information, see the description of the
+.Cm init
+subcommand.
+.It Fl s Ar sectorsize
+Change decrypted provider's sector size.
+For more information, see the description of the
+.Cm init
+subcommand.
+.El
+.It Cm configure
+Change configuration of the given providers.
+.Pp
+Additional options include:
+.Bl -tag -width ".Fl b"
+.It Fl b
+Set the BOOT flag on the given providers.
+For more information, see the description of the
+.Cm init
+subcommand.
+.It Fl B
+Remove the BOOT flag from the given providers.
+.El
+.It Cm setkey
+Install a copy of the Master Key into the selected slot, encrypted with
+a new User Key.
+If the selected slot is populated, replace the existing copy.
+A provider has one Master Key, which can be stored in one or both slots,
+each encrypted with an independent User Key.
+With the
+.Cm init
+subcommand, only key number 0 is initialized.
+The User Key can be changed at any time: for an attached provider,
+for a detached provider, or on the backup file.
+When a provider is attached, the user does not have to provide
+an existing passphrase/keyfile.
+.Pp
+Additional options include:
+.Bl -tag -width ".Fl J Ar newpassfile"
+.It Fl i Ar iterations
+Number of iterations to use with PKCS#5v2.
+If 0 is given, PKCS#5v2 will not be used.
+To be able to use this option with the
+.Cm setkey
+subcommand, only one key has to be defined and this key must be changed.
+.It Fl j Ar passfile
+Specifies a file which contains the passphrase component of a current User Key
+(or part of it).
+.It Fl J Ar newpassfile
+Specifies a file which contains the passphrase component of the new User Key
+(or part of it).
+.It Fl k Ar keyfile
+Specifies a file which contains the keyfile component of a current User Key
+(or part of it).
+.It Fl K Ar newkeyfile
+Specifies a file which contains the keyfile component of the new User Key
+(or part of it).
+.It Fl n Ar keyno
+Specifies the index number of the Master Key copy to change (could be 0 or 1).
+If the provider is attached and no key number is given, the key
+used for attaching the provider will be changed.
+If the provider is detached (or we are operating on a backup file)
+and no key number is given, the first Master Key copy to be successfully
+decrypted with the provided User Key passphrase/keyfile will be changed.
+.It Fl p
+Do not use a passphrase as a component of the current User Key.
+Cannot be combined with the
+.Fl j
+option.
+.It Fl P
+Do not use a passphrase as a component of the new User Key.
+Cannot be combined with the
+.Fl J
+option.
+.El
+.It Cm delkey
+Destroy (overwrite with random data) the selected Master Key copy.
+If one is destroying keys for an attached provider, the provider
+will not be detached even if all copies of the Master Key are destroyed.
+It can even be rescued with the
+.Cm setkey
+subcommand because the Master Key is still in memory.
+.Pp
+Additional options include:
+.Bl -tag -width ".Fl a Ar keyno"
+.It Fl a
+Destroy all copies of the Master Key (does not need
+.Fl f
+option).
+.It Fl f
+Force key destruction.
+This option is needed to destroy the last copy of the Master Key.
+.It Fl n Ar keyno
+Specifies the index number of the Master Key copy.
+If the provider is attached and no key number is given, the key
+used for attaching the provider will be destroyed.
+If provider is detached (or we are operating on a backup file) the key number
+has to be given.
+.El
+.It Cm kill
+This command should be used only in emergency situations.
+It will destroy all copies of the Master Key on a given provider and will
+detach it forcibly (if it is attached).
+This is absolutely a one-way command - if you do not have a metadata
+backup, your data is gone for good.
+In case the provider was attached with the
+.Fl r
+flag, the keys will not be destroyed, only the provider will be detached.
+.Pp
+Additional options include:
+.Bl -tag -width ".Fl a"
+.It Fl a
+If specified, all currently attached providers will be killed.
+.El
+.It Cm backup
+Backup metadata from the given provider to the given file.
+.It Cm restore
+Restore metadata from the given file to the given provider.
+.Pp
+Additional options include:
+.Bl -tag -width ".Fl f"
+.It Fl f
+Metadata contains the size of the provider to ensure that the correct
+partition or slice is attached.
+If an attempt is made to restore metadata to a provider that has a different
+size,
+.Nm
+will refuse to restore the data unless the
+.Fl f
+switch is used.
+If the partition or slice has been grown, the
+.Cm resize
+subcommand should be used rather than attempting to relocate the metadata
+through
+.Cm backup
+and
+.Cm restore .
+.El
+.It Cm suspend
+Suspend device by waiting for all inflight requests to finish, clearing all
+sensitive information (like the Master Key and Data Keys) from kernel memory,
+and blocking all further I/O requests until the
+.Cm resume
+subcommand is executed.
+This functionality is useful for laptops: when one wants to suspend a
+laptop, one does not want to leave an encrypted device attached.
+Instead of closing all files and directories opened from a file system located
+on an encrypted device, unmounting the file system, and detaching the device,
+the
+.Cm suspend
+subcommand can be used.
+Any access to the encrypted device will be blocked until the Master Key is
+reloaded through the
+.Cm resume
+subcommand.
+Thus there is no need to close nor unmount anything.
+The
+.Cm suspend
+subcommand does not work with devices created with the
+.Cm onetime
+subcommand.
+Please note that sensitive data might still be present in memory after
+suspending an encrypted device due to the file system cache, etc.
+.Pp
+Additional options include:
+.Bl -tag -width ".Fl a"
+.It Fl a
+Suspend all
+.Nm
+devices.
+.El
+.It Cm resume
+Resume previously suspended device.
+The caller must ensure that executing this subcommand does not access the
+suspended device, leading to a deadlock.
+For example suspending a device which contains the file system where the
+.Nm
+utility is stored is bad idea.
+.Pp
+Additional options include:
+.Bl -tag -width ".Fl j Ar passfile"
+.It Fl j Ar passfile
+Specifies a file which contains the passphrase component of the User Key
+(or part of it).
+For more information see the description of the
+.Fl J
+option for the
+.Cm init
+subcommand.
+.It Fl k Ar keyfile
+Specifies a file which contains the keyfile component of the User Key
+(or part of it).
+For more information see the description of the
+.Fl K
+option for the
+.Cm init
+subcommand.
+.It Fl p
+Do not use a passphrase as a component of the User Key.
+Cannot be combined with the
+.Fl j
+option.
+.El
+.It Cm resize
+Inform
+.Nm
+that the provider has been resized.
+The old metadata block is relocated to the correct position at the end of the
+provider and the provider size is updated.
+.Pp
+Additional options include:
+.Bl -tag -width ".Fl s Ar oldsize"
+.It Fl s Ar oldsize
+The size of the provider before it was resized.
+.El
+.It Cm version
+If no arguments are given, the
+.Cm version
+subcommand will print the version of
+.Nm
+userland utility as well as the version of the
+.Nm ELI
+GEOM class.
+.Pp
+If GEOM providers are specified, the
+.Cm version
+subcommand will print metadata version used by each of them.
+.It Cm clear
+Clear metadata from the given providers.
+.Em WARNING :
+This will erase with zeros the encrypted Master Key copies stored in the
+metadata.
+.It Cm dump
+Dump metadata stored on the given providers.
+.It Cm list
+See
+.Xr geom 8 .
+.It Cm status
+See
+.Xr geom 8 .
+.It Cm load
+See
+.Xr geom 8 .
+.It Cm unload
+See
+.Xr geom 8 .
+.El
+.Pp
+Additional options include:
+.Bl -tag -width ".Fl v"
+.It Fl v
+Be more verbose.
+.El
+.Sh KEY SUMMARY
+.Ss Master Key
+Upon
+.Cm init ,
+the
+.Nm
+utility generates a random Master Key for the provider.
+The Master Key never changes during the lifetime of the provider.
+Each copy of the provider metadata, active or backed up to a file, can store
+up to two, independently-encrypted copies of the Master Key.
+.Ss User Key
+Each stored copy of the Master Key is encrypted with a User Key, which
+is generated by the
+.Nm
+utility from a passphrase and/or a keyfile.
+The
+.Nm
+utility first reads all parts of the keyfile in the order specified on the
+command line, then reads all parts of the stored passphrase in the order
+specified on the command line.
+If no passphrase parts are specified, the system prompts the user to enter
+the passphrase.
+The passphrase is optionally strengthened by PKCS#5v2.
+The User Key is a digest computed over the concatenated keyfile and passphrase.
+.Ss Data Key
+During operation, one or more Data Keys are deterministically derived by
+the kernel from the Master Key and cached in memory.
+The number of Data Keys used by a given provider, and the way they are
+derived, depend on the GELI version and whether the provider is configured to
+use data authentication.
+.Sh SYSCTL VARIABLES
+The following
+.Xr sysctl 8
+variables can be used to control the behavior of the
+.Nm ELI
+GEOM class.
+The default value is shown next to each variable.
+Some variables can also be set in
+.Pa /boot/loader.conf .
+.Bl -tag -width indent
+.It Va kern.geom.eli.version
+Version number of the
+.Nm ELI
+GEOM class.
+.It Va kern.geom.eli.debug : No 0
+Debug level of the
+.Nm ELI
+GEOM class.
+This can be set to a number between 0 and 3 inclusive.
+If set to 0, minimal debug information is printed.
+If set to 3, the
+maximum amount of debug information is printed.
+.It Va kern.geom.eli.tries : No 3
+Number of times a user is asked for the passphrase.
+This is only used for providers which are attached on boot
+(before the root file system is mounted).
+If set to 0, attaching providers on boot will be disabled.
+This variable should be set in
+.Pa /boot/loader.conf .
+.It Va kern.geom.eli.overwrites : No 5
+Specifies how many times the Master Key will be overwritten
+with random values when it is destroyed.
+After this operation it is filled with zeros.
+.It Va kern.geom.eli.visible_passphrase : No 0
+If set to 1, the passphrase entered on boot (before the root
+file system is mounted) will be visible.
+This alternative should be used with caution as the entered
+passphrase can be logged and exposed via
+.Xr dmesg 8 .
+This variable should be set in
+.Pa /boot/loader.conf .
+.It Va kern.geom.eli.threads : No 0
+Specifies how many kernel threads should be used for doing software
+cryptography.
+Its purpose is to increase performance on SMP systems.
+If set to 0, a CPU-pinned thread will be started for every active CPU.
+.It Va kern.geom.eli.batch : No 0
+When set to 1, can speed-up crypto operations by using batching.
+Batching reduces the number of interrupts by responding to a group of
+crypto requests with one interrupt.
+The crypto card and the driver has to support this feature.
+.It Va kern.geom.eli.key_cache_limit : No 8192
+Specifies how many Data Keys to cache.
+The default limit
+(8192 keys) will allow caching of all keys for a 4TB provider with 512 byte
+sectors and will take around 1MB of memory.
+.It Va kern.geom.eli.key_cache_hits
+Reports how many times we were looking up a Data Key and it was already in
+cache.
+This sysctl is not updated for providers that need fewer Data Keys than
+the limit specified in
+.Va kern.geom.eli.key_cache_limit .
+.It Va kern.geom.eli.key_cache_misses
+Reports how many times we were looking up a Data Key and it was not in cache.
+This sysctl is not updated for providers that need fewer Data Keys than the limit
+specified in
+.Va kern.geom.eli.key_cache_limit .
+.El
+.Sh EXIT STATUS
+Exit status is 0 on success, and 1 if the command fails.
+.Sh EXAMPLES
+Initialize a provider which is going to be encrypted with a
+passphrase and random data from a file on the user's pen drive.
+Use 4kB sector size.
+Attach the provider, create a file system, and mount it.
+Do the work.
+Unmount the provider and detach it:
+.Bd -literal -offset indent
+# dd if=/dev/random of=/mnt/pendrive/da2.key bs=64 count=1
+# geli init -s 4096 -K /mnt/pendrive/da2.key /dev/da2
+Enter new passphrase:
+Reenter new passphrase:
+# geli attach -k /mnt/pendrive/da2.key /dev/da2
+Enter passphrase:
+# dd if=/dev/random of=/dev/da2.eli bs=1m
+# newfs /dev/da2.eli
+# mount /dev/da2.eli /mnt/secret
+\&...
+# umount /mnt/secret
+# geli detach da2.eli
+.Ed
+.Pp
+Create an encrypted provider, but use two User Keys:
+one for your employee and one for you as the company's security officer
+(so it is not a tragedy if the employee
+.Qq accidentally
+forgets his passphrase):
+.Bd -literal -offset indent
+# geli init /dev/da2
+Enter new passphrase: (enter security officer's passphrase)
+Reenter new passphrase:
+# geli setkey -n 1 /dev/da2
+Enter passphrase: (enter security officer's passphrase)
+Enter new passphrase: (let your employee enter his passphrase ...)
+Reenter new passphrase: (... twice)
+.Ed
+.Pp
+You are the security officer in your company.
+Create an encrypted provider for use by the user, but remember that users
+forget their passphrases, so backup the Master Key with your own random key:
+.Bd -literal -offset indent
+# dd if=/dev/random of=/mnt/pendrive/keys/`hostname` bs=64 count=1
+# geli init -P -K /mnt/pendrive/keys/`hostname` /dev/ada0s1e
+# geli backup /dev/ada0s1e /mnt/pendrive/backups/`hostname`
+(use key number 0, so the encrypted Master Key will be re-encrypted by this)
+# geli setkey -n 0 -k /mnt/pendrive/keys/`hostname` /dev/ada0s1e
+(allow the user to enter his passphrase)
+Enter new passphrase:
+Reenter new passphrase:
+.Ed
+.Pp
+Encrypted swap partition setup:
+.Bd -literal -offset indent
+# dd if=/dev/random of=/dev/ada0s1b bs=1m
+# geli onetime -d -e 3des ada0s1b
+# swapon /dev/ada0s1b.eli
+.Ed
+.Pp
+The example below shows how to configure two providers which will be attached
+on boot (before the root file system is mounted).
+One of them is using passphrase and three keyfile parts and the other is
+using only a keyfile in one part:
+.Bd -literal -offset indent
+# dd if=/dev/random of=/dev/da0 bs=1m
+# dd if=/dev/random of=/boot/keys/da0.key0 bs=32k count=1
+# dd if=/dev/random of=/boot/keys/da0.key1 bs=32k count=1
+# dd if=/dev/random of=/boot/keys/da0.key2 bs=32k count=1
+# geli init -b -K /boot/keys/da0.key0 -K /boot/keys/da0.key1 -K /boot/keys/da0.key2 da0
+Enter new passphrase:
+Reenter new passphrase:
+# dd if=/dev/random of=/dev/da1s3a bs=1m
+# dd if=/dev/random of=/boot/keys/da1s3a.key bs=128k count=1
+# geli init -b -P -K /boot/keys/da1s3a.key da1s3a
+.Ed
+.Pp
+The providers are initialized, now we have to add these lines to
+.Pa /boot/loader.conf :
+.Bd -literal -offset indent
+geli_da0_keyfile0_load="YES"
+geli_da0_keyfile0_type="da0:geli_keyfile0"
+geli_da0_keyfile0_name="/boot/keys/da0.key0"
+geli_da0_keyfile1_load="YES"
+geli_da0_keyfile1_type="da0:geli_keyfile1"
+geli_da0_keyfile1_name="/boot/keys/da0.key1"
+geli_da0_keyfile2_load="YES"
+geli_da0_keyfile2_type="da0:geli_keyfile2"
+geli_da0_keyfile2_name="/boot/keys/da0.key2"
+
+geli_da1s3a_keyfile0_load="YES"
+geli_da1s3a_keyfile0_type="da1s3a:geli_keyfile0"
+geli_da1s3a_keyfile0_name="/boot/keys/da1s3a.key"
+.Ed
+.Pp
+Not only configure encryption, but also data integrity verification using
+.Nm HMAC/SHA256 .
+.Bd -literal -offset indent
+# geli init -a hmac/sha256 -s 4096 /dev/da0
+Enter new passphrase:
+Reenter new passphrase:
+# geli attach /dev/da0
+Enter passphrase:
+# dd if=/dev/random of=/dev/da0.eli bs=1m
+# newfs /dev/da0.eli
+# mount /dev/da0.eli /mnt/secret
+.Ed
+.Pp
+.Cm geli
+writes the metadata backup by default to the
+.Pa /var/backups/<prov>.eli
+file.
+If the metadata is lost in any way (e.g., by accidental overwrite), it can be restored.
+Consider the following situation:
+.Bd -literal -offset indent
+# geli init /dev/da0
+Enter new passphrase:
+Reenter new passphrase:
+
+Metadata backup can be found in /var/backups/da0.eli and
+can be restored with the following command:
+
+ # geli restore /var/backups/da0.eli /dev/da0
+
+# geli clear /dev/da0
+# geli attach /dev/da0
+geli: Cannot read metadata from /dev/da0: Invalid argument.
+# geli restore /var/backups/da0.eli /dev/da0
+# geli attach /dev/da0
+Enter passphrase:
+.Ed
+.Pp
+If an encrypted file system is extended, it is necessary to relocate and
+update the metadata:
+.Bd -literal -offset indent
+# gpart create -s GPT ada0
+# gpart add -s 1g -t freebsd-ufs -i 1 ada0
+# geli init -K keyfile -P ada0p1
+# gpart resize -s 2g -i 1 ada0
+# geli resize -s 1g ada0p1
+# geli attach -k keyfile -p ada0p1
+.Ed
+.Pp
+Initialize provider with the passphrase split into two files.
+The provider can be attached using those two files or by entering
+.Dq foobar
+as the passphrase at the
+.Nm
+prompt:
+.Bd -literal -offset indent
+# echo foo > da0.pass0
+# echo bar > da0.pass1
+# geli init -J da0.pass0 -J da0.pass1 da0
+# geli attach -j da0.pass0 -j da0.pass1 da0
+# geli detach da0
+# geli attach da0
+Enter passphrase: foobar
+.Ed
+.Pp
+Suspend all
+.Nm
+devices on a laptop, suspend the laptop, then resume devices one by one after
+resuming the laptop:
+.Bd -literal -offset indent
+# geli suspend -a
+# zzz
+<resume your laptop>
+# geli resume -p -k keyfile gpt/secret
+# geli resume gpt/private
+Enter passphrase:
+.Ed
+.Sh ENCRYPTION MODES
+.Nm
+supports two encryption modes:
+.Nm XTS ,
+which was standardized as
+.Nm IEEE P1619
+and
+.Nm CBC
+with unpredictable IV.
+The
+.Nm CBC
+mode used by
+.Nm
+is very similar to the mode
+.Nm ESSIV .
+.Sh DATA AUTHENTICATION
+.Nm
+can verify data integrity when an authentication algorithm is specified.
+When data corruption/modification is detected,
+.Nm
+will not return any data, but instead will return an error
+.Pq Er EINVAL .
+The offset and size of the corrupted data will be printed on the console.
+It is important to know against which attacks
+.Nm
+provides protection for your data.
+If data is modified in-place or copied from one place on the disk
+to another even without modification,
+.Nm
+should be able to detect such a change.
+If an attacker can remember the encrypted data, he can overwrite any future
+changes with the data he owns without it being noticed.
+In other words
+.Nm
+will not protect your data against replay attacks.
+.Pp
+It is recommended to write to the whole provider before first use,
+in order to make sure that all sectors and their corresponding
+checksums are properly initialized into a consistent state.
+One can safely ignore data authentication errors that occur immediately
+after the first time a provider is attached and before it is
+initialized in this way.
+.Sh SEE ALSO
+.Xr crypto 4 ,
+.Xr gbde 4 ,
+.Xr geom 4 ,
+.Xr loader.conf 5 ,
+.Xr gbde 8 ,
+.Xr geom 8 ,
+.Xr crypto 9
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Fx 6.0 .
+Support for the
+.Nm Camellia
+block cipher is implemented by Yoshisato Yanagisawa in
+.Fx 7.0 .
+.Pp
+Highest
+.Nm GELI
+metadata version supported by the given FreeBSD version:
+.Bl -column -offset indent ".Sy FreeBSD" ".Sy version"
+.It Sy FreeBSD Ta Sy GELI
+.It Sy version Ta Sy version
+.Pp
+.It Li 6.0 Ta 0
+.It Li 6.1 Ta 0
+.It Li 6.2 Ta 3
+.It Li 6.3 Ta 3
+.It Li 6.4 Ta 3
+.Pp
+.It Li 7.0 Ta 3
+.It Li 7.1 Ta 3
+.It Li 7.2 Ta 3
+.It Li 7.3 Ta 3
+.It Li 7.4 Ta 3
+.Pp
+.It Li 8.0 Ta 3
+.It Li 8.1 Ta 3
+.It Li 8.2 Ta 5
+.Pp
+.It Li 9.0 Ta 6
+.Pp
+.It Li 10.0 Ta 7
+.El
+.Sh AUTHORS
+.An Pawel Jakub Dawidek Aq Mt pjd@FreeBSD.org
diff --git a/sbin/geom/class/eli/geom_eli.c b/sbin/geom/class/eli/geom_eli.c
new file mode 100644
index 0000000..7df4d90
--- /dev/null
+++ b/sbin/geom/class/eli/geom_eli.c
@@ -0,0 +1,1650 @@
+/*-
+ * Copyright (c) 2004-2010 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/mman.h>
+#include <sys/sysctl.h>
+#include <sys/resource.h>
+#include <opencrypto/cryptodev.h>
+
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgeom.h>
+#include <paths.h>
+#include <readpassphrase.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+
+#include <geom/eli/g_eli.h>
+#include <geom/eli/pkcs5v2.h>
+
+#include "core/geom.h"
+#include "misc/subr.h"
+
+
+uint32_t lib_version = G_LIB_VERSION;
+uint32_t version = G_ELI_VERSION;
+
+#define GELI_BACKUP_DIR "/var/backups/"
+#define GELI_ENC_ALGO "aes"
+
+static void eli_main(struct gctl_req *req, unsigned flags);
+static void eli_init(struct gctl_req *req);
+static void eli_attach(struct gctl_req *req);
+static void eli_configure(struct gctl_req *req);
+static void eli_setkey(struct gctl_req *req);
+static void eli_delkey(struct gctl_req *req);
+static void eli_resume(struct gctl_req *req);
+static void eli_kill(struct gctl_req *req);
+static void eli_backup(struct gctl_req *req);
+static void eli_restore(struct gctl_req *req);
+static void eli_resize(struct gctl_req *req);
+static void eli_version(struct gctl_req *req);
+static void eli_clear(struct gctl_req *req);
+static void eli_dump(struct gctl_req *req);
+
+static int eli_backup_create(struct gctl_req *req, const char *prov,
+ const char *file);
+
+/*
+ * Available commands:
+ *
+ * init [-bhPv] [-a aalgo] [-B backupfile] [-e ealgo] [-i iterations] [-l keylen] [-J newpassfile] [-K newkeyfile] [-V version] prov
+ * label - alias for 'init'
+ * attach [-dprv] [-j passfile] [-k keyfile] prov
+ * detach [-fl] prov ...
+ * stop - alias for 'detach'
+ * onetime [-d] [-a aalgo] [-e ealgo] [-l keylen] prov
+ * configure [-bB] prov ...
+ * setkey [-pPv] [-n keyno] [-j passfile] [-J newpassfile] [-k keyfile] [-K newkeyfile] prov
+ * delkey [-afv] [-n keyno] prov
+ * suspend [-v] -a | prov ...
+ * resume [-pv] [-j passfile] [-k keyfile] prov
+ * kill [-av] [prov ...]
+ * backup [-v] prov file
+ * restore [-fv] file prov
+ * resize [-v] -s oldsize prov
+ * version [prov ...]
+ * clear [-v] prov ...
+ * dump [-v] prov ...
+ */
+struct g_command class_commands[] = {
+ { "init", G_FLAG_VERBOSE, eli_main,
+ {
+ { 'a', "aalgo", "", G_TYPE_STRING },
+ { 'b', "boot", NULL, G_TYPE_BOOL },
+ { 'B', "backupfile", "", G_TYPE_STRING },
+ { 'e', "ealgo", "", G_TYPE_STRING },
+ { 'i', "iterations", "-1", G_TYPE_NUMBER },
+ { 'J', "newpassfile", G_VAL_OPTIONAL, G_TYPE_STRING | G_TYPE_MULTI },
+ { 'K', "newkeyfile", G_VAL_OPTIONAL, G_TYPE_STRING | G_TYPE_MULTI },
+ { 'l', "keylen", "0", G_TYPE_NUMBER },
+ { 'P', "nonewpassphrase", NULL, G_TYPE_BOOL },
+ { 's', "sectorsize", "0", G_TYPE_NUMBER },
+ { 'V', "mdversion", "-1", G_TYPE_NUMBER },
+ G_OPT_SENTINEL
+ },
+ "[-bPv] [-a aalgo] [-B backupfile] [-e ealgo] [-i iterations] [-l keylen] [-J newpassfile] [-K newkeyfile] [-s sectorsize] [-V version] prov"
+ },
+ { "label", G_FLAG_VERBOSE, eli_main,
+ {
+ { 'a', "aalgo", "", G_TYPE_STRING },
+ { 'b', "boot", NULL, G_TYPE_BOOL },
+ { 'B', "backupfile", "", G_TYPE_STRING },
+ { 'e', "ealgo", "", G_TYPE_STRING },
+ { 'i', "iterations", "-1", G_TYPE_NUMBER },
+ { 'J', "newpassfile", G_VAL_OPTIONAL, G_TYPE_STRING | G_TYPE_MULTI },
+ { 'K', "newkeyfile", G_VAL_OPTIONAL, G_TYPE_STRING | G_TYPE_MULTI },
+ { 'l', "keylen", "0", G_TYPE_NUMBER },
+ { 'P', "nonewpassphrase", NULL, G_TYPE_BOOL },
+ { 's', "sectorsize", "0", G_TYPE_NUMBER },
+ { 'V', "mdversion", "-1", G_TYPE_NUMBER },
+ G_OPT_SENTINEL
+ },
+ "- an alias for 'init'"
+ },
+ { "attach", G_FLAG_VERBOSE | G_FLAG_LOADKLD, eli_main,
+ {
+ { 'd', "detach", NULL, G_TYPE_BOOL },
+ { 'j', "passfile", G_VAL_OPTIONAL, G_TYPE_STRING | G_TYPE_MULTI },
+ { 'k', "keyfile", G_VAL_OPTIONAL, G_TYPE_STRING | G_TYPE_MULTI },
+ { 'p', "nopassphrase", NULL, G_TYPE_BOOL },
+ { 'r', "readonly", NULL, G_TYPE_BOOL },
+ G_OPT_SENTINEL
+ },
+ "[-dprv] [-j passfile] [-k keyfile] prov"
+ },
+ { "detach", 0, NULL,
+ {
+ { 'f', "force", NULL, G_TYPE_BOOL },
+ { 'l', "last", NULL, G_TYPE_BOOL },
+ G_OPT_SENTINEL
+ },
+ "[-fl] prov ..."
+ },
+ { "stop", 0, NULL,
+ {
+ { 'f', "force", NULL, G_TYPE_BOOL },
+ { 'l', "last", NULL, G_TYPE_BOOL },
+ G_OPT_SENTINEL
+ },
+ "- an alias for 'detach'"
+ },
+ { "onetime", G_FLAG_VERBOSE | G_FLAG_LOADKLD, NULL,
+ {
+ { 'a', "aalgo", "", G_TYPE_STRING },
+ { 'd', "detach", NULL, G_TYPE_BOOL },
+ { 'e', "ealgo", GELI_ENC_ALGO, G_TYPE_STRING },
+ { 'l', "keylen", "0", G_TYPE_NUMBER },
+ { 's', "sectorsize", "0", G_TYPE_NUMBER },
+ G_OPT_SENTINEL
+ },
+ "[-d] [-a aalgo] [-e ealgo] [-l keylen] [-s sectorsize] prov"
+ },
+ { "configure", G_FLAG_VERBOSE, eli_main,
+ {
+ { 'b', "boot", NULL, G_TYPE_BOOL },
+ { 'B', "noboot", NULL, G_TYPE_BOOL },
+ G_OPT_SENTINEL
+ },
+ "[-bB] prov ..."
+ },
+ { "setkey", G_FLAG_VERBOSE, eli_main,
+ {
+ { 'i', "iterations", "-1", G_TYPE_NUMBER },
+ { 'j', "passfile", G_VAL_OPTIONAL, G_TYPE_STRING | G_TYPE_MULTI },
+ { 'J', "newpassfile", G_VAL_OPTIONAL, G_TYPE_STRING | G_TYPE_MULTI },
+ { 'k', "keyfile", G_VAL_OPTIONAL, G_TYPE_STRING | G_TYPE_MULTI },
+ { 'K', "newkeyfile", G_VAL_OPTIONAL, G_TYPE_STRING | G_TYPE_MULTI },
+ { 'n', "keyno", "-1", G_TYPE_NUMBER },
+ { 'p', "nopassphrase", NULL, G_TYPE_BOOL },
+ { 'P', "nonewpassphrase", NULL, G_TYPE_BOOL },
+ G_OPT_SENTINEL
+ },
+ "[-pPv] [-n keyno] [-i iterations] [-j passfile] [-J newpassfile] [-k keyfile] [-K newkeyfile] prov"
+ },
+ { "delkey", G_FLAG_VERBOSE, eli_main,
+ {
+ { 'a', "all", NULL, G_TYPE_BOOL },
+ { 'f', "force", NULL, G_TYPE_BOOL },
+ { 'n', "keyno", "-1", G_TYPE_NUMBER },
+ G_OPT_SENTINEL
+ },
+ "[-afv] [-n keyno] prov"
+ },
+ { "suspend", G_FLAG_VERBOSE, NULL,
+ {
+ { 'a', "all", NULL, G_TYPE_BOOL },
+ G_OPT_SENTINEL
+ },
+ "[-v] -a | prov ..."
+ },
+ { "resume", G_FLAG_VERBOSE, eli_main,
+ {
+ { 'j', "passfile", G_VAL_OPTIONAL, G_TYPE_STRING | G_TYPE_MULTI },
+ { 'k', "keyfile", G_VAL_OPTIONAL, G_TYPE_STRING | G_TYPE_MULTI },
+ { 'p', "nopassphrase", NULL, G_TYPE_BOOL },
+ G_OPT_SENTINEL
+ },
+ "[-pv] [-j passfile] [-k keyfile] prov"
+ },
+ { "kill", G_FLAG_VERBOSE, eli_main,
+ {
+ { 'a', "all", NULL, G_TYPE_BOOL },
+ G_OPT_SENTINEL
+ },
+ "[-av] [prov ...]"
+ },
+ { "backup", G_FLAG_VERBOSE, eli_main, G_NULL_OPTS,
+ "[-v] prov file"
+ },
+ { "restore", G_FLAG_VERBOSE, eli_main,
+ {
+ { 'f', "force", NULL, G_TYPE_BOOL },
+ G_OPT_SENTINEL
+ },
+ "[-fv] file prov"
+ },
+ { "resize", G_FLAG_VERBOSE, eli_main,
+ {
+ { 's', "oldsize", NULL, G_TYPE_NUMBER },
+ G_OPT_SENTINEL
+ },
+ "[-v] -s oldsize prov"
+ },
+ { "version", G_FLAG_LOADKLD, eli_main, G_NULL_OPTS,
+ "[prov ...]"
+ },
+ { "clear", G_FLAG_VERBOSE, eli_main, G_NULL_OPTS,
+ "[-v] prov ..."
+ },
+ { "dump", G_FLAG_VERBOSE, eli_main, G_NULL_OPTS,
+ "[-v] prov ..."
+ },
+ G_CMD_SENTINEL
+};
+
+static int verbose = 0;
+
+#define BUFSIZE 1024
+
+static int
+eli_protect(struct gctl_req *req)
+{
+ struct rlimit rl;
+
+ /* Disable core dumps. */
+ rl.rlim_cur = 0;
+ rl.rlim_max = 0;
+ if (setrlimit(RLIMIT_CORE, &rl) == -1) {
+ gctl_error(req, "Cannot disable core dumps: %s.",
+ strerror(errno));
+ return (-1);
+ }
+ /* Disable swapping. */
+ if (mlockall(MCL_FUTURE) == -1) {
+ gctl_error(req, "Cannot lock memory: %s.", strerror(errno));
+ return (-1);
+ }
+ return (0);
+}
+
+static void
+eli_main(struct gctl_req *req, unsigned int flags)
+{
+ const char *name;
+
+ if (eli_protect(req) == -1)
+ return;
+
+ if ((flags & G_FLAG_VERBOSE) != 0)
+ verbose = 1;
+
+ name = gctl_get_ascii(req, "verb");
+ if (name == NULL) {
+ gctl_error(req, "No '%s' argument.", "verb");
+ return;
+ }
+ if (strcmp(name, "init") == 0 || strcmp(name, "label") == 0)
+ eli_init(req);
+ else if (strcmp(name, "attach") == 0)
+ eli_attach(req);
+ else if (strcmp(name, "configure") == 0)
+ eli_configure(req);
+ else if (strcmp(name, "setkey") == 0)
+ eli_setkey(req);
+ else if (strcmp(name, "delkey") == 0)
+ eli_delkey(req);
+ else if (strcmp(name, "resume") == 0)
+ eli_resume(req);
+ else if (strcmp(name, "kill") == 0)
+ eli_kill(req);
+ else if (strcmp(name, "backup") == 0)
+ eli_backup(req);
+ else if (strcmp(name, "restore") == 0)
+ eli_restore(req);
+ else if (strcmp(name, "resize") == 0)
+ eli_resize(req);
+ else if (strcmp(name, "version") == 0)
+ eli_version(req);
+ else if (strcmp(name, "dump") == 0)
+ eli_dump(req);
+ else if (strcmp(name, "clear") == 0)
+ eli_clear(req);
+ else
+ gctl_error(req, "Unknown command: %s.", name);
+}
+
+static bool
+eli_is_attached(const char *prov)
+{
+ char name[MAXPATHLEN];
+
+ /*
+ * Not the best way to do it, but the easiest.
+ * We try to open provider and check if it is a GEOM provider
+ * by asking about its sectorsize.
+ */
+ snprintf(name, sizeof(name), "%s%s", prov, G_ELI_SUFFIX);
+ return (g_get_sectorsize(name) > 0);
+}
+
+static int
+eli_genkey_files(struct gctl_req *req, bool new, const char *type,
+ struct hmac_ctx *ctxp, char *passbuf, size_t passbufsize)
+{
+ char *p, buf[BUFSIZE], argname[16];
+ const char *file;
+ int error, fd, i;
+ ssize_t done;
+
+ assert((strcmp(type, "keyfile") == 0 && ctxp != NULL &&
+ passbuf == NULL && passbufsize == 0) ||
+ (strcmp(type, "passfile") == 0 && ctxp == NULL &&
+ passbuf != NULL && passbufsize > 0));
+ assert(strcmp(type, "keyfile") == 0 || passbuf[0] == '\0');
+
+ for (i = 0; ; i++) {
+ snprintf(argname, sizeof(argname), "%s%s%d",
+ new ? "new" : "", type, i);
+
+ /* No more {key,pass}files? */
+ if (!gctl_has_param(req, argname))
+ return (i);
+
+ file = gctl_get_ascii(req, "%s", argname);
+ assert(file != NULL);
+
+ if (strcmp(file, "-") == 0)
+ fd = STDIN_FILENO;
+ else {
+ fd = open(file, O_RDONLY);
+ if (fd == -1) {
+ gctl_error(req, "Cannot open %s %s: %s.",
+ type, file, strerror(errno));
+ return (-1);
+ }
+ }
+ if (strcmp(type, "keyfile") == 0) {
+ while ((done = read(fd, buf, sizeof(buf))) > 0)
+ g_eli_crypto_hmac_update(ctxp, buf, done);
+ } else /* if (strcmp(type, "passfile") == 0) */ {
+ assert(strcmp(type, "passfile") == 0);
+
+ while ((done = read(fd, buf, sizeof(buf) - 1)) > 0) {
+ buf[done] = '\0';
+ p = strchr(buf, '\n');
+ if (p != NULL) {
+ *p = '\0';
+ done = p - buf;
+ }
+ if (strlcat(passbuf, buf, passbufsize) >=
+ passbufsize) {
+ gctl_error(req,
+ "Passphrase in %s too long.", file);
+ bzero(buf, sizeof(buf));
+ return (-1);
+ }
+ if (p != NULL)
+ break;
+ }
+ }
+ error = errno;
+ if (strcmp(file, "-") != 0)
+ close(fd);
+ bzero(buf, sizeof(buf));
+ if (done == -1) {
+ gctl_error(req, "Cannot read %s %s: %s.",
+ type, file, strerror(error));
+ return (-1);
+ }
+ }
+ /* NOTREACHED */
+}
+
+static int
+eli_genkey_passphrase_prompt(struct gctl_req *req, bool new, char *passbuf,
+ size_t passbufsize)
+{
+ char *p;
+
+ for (;;) {
+ p = readpassphrase(
+ new ? "Enter new passphrase:" : "Enter passphrase:",
+ passbuf, passbufsize, RPP_ECHO_OFF | RPP_REQUIRE_TTY);
+ if (p == NULL) {
+ bzero(passbuf, passbufsize);
+ gctl_error(req, "Cannot read passphrase: %s.",
+ strerror(errno));
+ return (-1);
+ }
+
+ if (new) {
+ char tmpbuf[BUFSIZE];
+
+ p = readpassphrase("Reenter new passphrase: ",
+ tmpbuf, sizeof(tmpbuf),
+ RPP_ECHO_OFF | RPP_REQUIRE_TTY);
+ if (p == NULL) {
+ bzero(passbuf, passbufsize);
+ gctl_error(req,
+ "Cannot read passphrase: %s.",
+ strerror(errno));
+ return (-1);
+ }
+
+ if (strcmp(passbuf, tmpbuf) != 0) {
+ bzero(passbuf, passbufsize);
+ fprintf(stderr, "They didn't match.\n");
+ continue;
+ }
+ bzero(tmpbuf, sizeof(tmpbuf));
+ }
+ return (0);
+ }
+ /* NOTREACHED */
+}
+
+static int
+eli_genkey_passphrase(struct gctl_req *req, struct g_eli_metadata *md, bool new,
+ struct hmac_ctx *ctxp)
+{
+ char passbuf[BUFSIZE];
+ bool nopassphrase;
+ int nfiles;
+
+ nopassphrase =
+ gctl_get_int(req, new ? "nonewpassphrase" : "nopassphrase");
+ if (nopassphrase) {
+ if (gctl_has_param(req, new ? "newpassfile0" : "passfile0")) {
+ gctl_error(req,
+ "Options -%c and -%c are mutually exclusive.",
+ new ? 'J' : 'j', new ? 'P' : 'p');
+ return (-1);
+ }
+ return (0);
+ }
+
+ if (!new && md->md_iterations == -1) {
+ gctl_error(req, "Missing -p flag.");
+ return (-1);
+ }
+ passbuf[0] = '\0';
+ nfiles = eli_genkey_files(req, new, "passfile", NULL, passbuf,
+ sizeof(passbuf));
+ if (nfiles == -1)
+ return (-1);
+ else if (nfiles == 0) {
+ if (eli_genkey_passphrase_prompt(req, new, passbuf,
+ sizeof(passbuf)) == -1) {
+ return (-1);
+ }
+ }
+ /*
+ * Field md_iterations equal to -1 means "choose some sane
+ * value for me".
+ */
+ if (md->md_iterations == -1) {
+ assert(new);
+ if (verbose)
+ printf("Calculating number of iterations...\n");
+ md->md_iterations = pkcs5v2_calculate(2000000);
+ assert(md->md_iterations > 0);
+ if (verbose) {
+ printf("Done, using %d iterations.\n",
+ md->md_iterations);
+ }
+ }
+ /*
+ * If md_iterations is equal to 0, user doesn't want PKCS#5v2.
+ */
+ if (md->md_iterations == 0) {
+ g_eli_crypto_hmac_update(ctxp, md->md_salt,
+ sizeof(md->md_salt));
+ g_eli_crypto_hmac_update(ctxp, passbuf, strlen(passbuf));
+ } else /* if (md->md_iterations > 0) */ {
+ unsigned char dkey[G_ELI_USERKEYLEN];
+
+ pkcs5v2_genkey(dkey, sizeof(dkey), md->md_salt,
+ sizeof(md->md_salt), passbuf, md->md_iterations);
+ g_eli_crypto_hmac_update(ctxp, dkey, sizeof(dkey));
+ bzero(dkey, sizeof(dkey));
+ }
+ bzero(passbuf, sizeof(passbuf));
+
+ return (0);
+}
+
+static unsigned char *
+eli_genkey(struct gctl_req *req, struct g_eli_metadata *md, unsigned char *key,
+ bool new)
+{
+ struct hmac_ctx ctx;
+ bool nopassphrase;
+ int nfiles;
+
+ nopassphrase =
+ gctl_get_int(req, new ? "nonewpassphrase" : "nopassphrase");
+
+ g_eli_crypto_hmac_init(&ctx, NULL, 0);
+
+ nfiles = eli_genkey_files(req, new, "keyfile", &ctx, NULL, 0);
+ if (nfiles == -1)
+ return (NULL);
+ else if (nfiles == 0 && nopassphrase) {
+ gctl_error(req, "No key components given.");
+ return (NULL);
+ }
+
+ if (eli_genkey_passphrase(req, md, new, &ctx) == -1)
+ return (NULL);
+
+ g_eli_crypto_hmac_final(&ctx, key, 0);
+
+ return (key);
+}
+
+static int
+eli_metadata_read(struct gctl_req *req, const char *prov,
+ struct g_eli_metadata *md)
+{
+ unsigned char sector[sizeof(struct g_eli_metadata)];
+ int error;
+
+ if (g_get_sectorsize(prov) == 0) {
+ int fd;
+
+ /* This is a file probably. */
+ fd = open(prov, O_RDONLY);
+ if (fd == -1) {
+ gctl_error(req, "Cannot open %s: %s.", prov,
+ strerror(errno));
+ return (-1);
+ }
+ if (read(fd, sector, sizeof(sector)) != sizeof(sector)) {
+ gctl_error(req, "Cannot read metadata from %s: %s.",
+ prov, strerror(errno));
+ close(fd);
+ return (-1);
+ }
+ close(fd);
+ } else {
+ /* This is a GEOM provider. */
+ error = g_metadata_read(prov, sector, sizeof(sector),
+ G_ELI_MAGIC);
+ if (error != 0) {
+ gctl_error(req, "Cannot read metadata from %s: %s.",
+ prov, strerror(error));
+ return (-1);
+ }
+ }
+ error = eli_metadata_decode(sector, md);
+ switch (error) {
+ case 0:
+ break;
+ case EOPNOTSUPP:
+ gctl_error(req,
+ "Provider's %s metadata version %u is too new.\n"
+ "geli: The highest supported version is %u.",
+ prov, (unsigned int)md->md_version, G_ELI_VERSION);
+ return (-1);
+ case EINVAL:
+ gctl_error(req, "Inconsistent provider's %s metadata.", prov);
+ return (-1);
+ default:
+ gctl_error(req,
+ "Unexpected error while decoding provider's %s metadata: %s.",
+ prov, strerror(error));
+ return (-1);
+ }
+ return (0);
+}
+
+static int
+eli_metadata_store(struct gctl_req *req, const char *prov,
+ struct g_eli_metadata *md)
+{
+ unsigned char sector[sizeof(struct g_eli_metadata)];
+ int error;
+
+ eli_metadata_encode(md, sector);
+ if (g_get_sectorsize(prov) == 0) {
+ int fd;
+
+ /* This is a file probably. */
+ fd = open(prov, O_WRONLY | O_TRUNC);
+ if (fd == -1) {
+ gctl_error(req, "Cannot open %s: %s.", prov,
+ strerror(errno));
+ bzero(sector, sizeof(sector));
+ return (-1);
+ }
+ if (write(fd, sector, sizeof(sector)) != sizeof(sector)) {
+ gctl_error(req, "Cannot write metadata to %s: %s.",
+ prov, strerror(errno));
+ bzero(sector, sizeof(sector));
+ close(fd);
+ return (-1);
+ }
+ close(fd);
+ } else {
+ /* This is a GEOM provider. */
+ error = g_metadata_store(prov, sector, sizeof(sector));
+ if (error != 0) {
+ gctl_error(req, "Cannot write metadata to %s: %s.",
+ prov, strerror(errno));
+ bzero(sector, sizeof(sector));
+ return (-1);
+ }
+ }
+ bzero(sector, sizeof(sector));
+ return (0);
+}
+
+static void
+eli_init(struct gctl_req *req)
+{
+ struct g_eli_metadata md;
+ unsigned char sector[sizeof(struct g_eli_metadata)];
+ unsigned char key[G_ELI_USERKEYLEN];
+ char backfile[MAXPATHLEN];
+ const char *str, *prov;
+ unsigned int secsize, version;
+ off_t mediasize;
+ intmax_t val;
+ int error, nargs;
+
+ nargs = gctl_get_int(req, "nargs");
+ if (nargs != 1) {
+ gctl_error(req, "Invalid number of arguments.");
+ return;
+ }
+ prov = gctl_get_ascii(req, "arg0");
+ mediasize = g_get_mediasize(prov);
+ secsize = g_get_sectorsize(prov);
+ if (mediasize == 0 || secsize == 0) {
+ gctl_error(req, "Cannot get informations about %s: %s.", prov,
+ strerror(errno));
+ return;
+ }
+
+ bzero(&md, sizeof(md));
+ strlcpy(md.md_magic, G_ELI_MAGIC, sizeof(md.md_magic));
+ val = gctl_get_intmax(req, "mdversion");
+ if (val == -1) {
+ version = G_ELI_VERSION;
+ } else if (val < 0 || val > G_ELI_VERSION) {
+ gctl_error(req,
+ "Invalid version specified should be between %u and %u.",
+ G_ELI_VERSION_00, G_ELI_VERSION);
+ return;
+ } else {
+ version = val;
+ }
+ md.md_version = version;
+ md.md_flags = 0;
+ if (gctl_get_int(req, "boot"))
+ md.md_flags |= G_ELI_FLAG_BOOT;
+ md.md_ealgo = CRYPTO_ALGORITHM_MIN - 1;
+ str = gctl_get_ascii(req, "aalgo");
+ if (*str != '\0') {
+ if (version < G_ELI_VERSION_01) {
+ gctl_error(req,
+ "Data authentication is supported starting from version %u.",
+ G_ELI_VERSION_01);
+ return;
+ }
+ md.md_aalgo = g_eli_str2aalgo(str);
+ if (md.md_aalgo >= CRYPTO_ALGORITHM_MIN &&
+ md.md_aalgo <= CRYPTO_ALGORITHM_MAX) {
+ md.md_flags |= G_ELI_FLAG_AUTH;
+ } else {
+ /*
+ * For backward compatibility, check if the -a option
+ * was used to provide encryption algorithm.
+ */
+ md.md_ealgo = g_eli_str2ealgo(str);
+ if (md.md_ealgo < CRYPTO_ALGORITHM_MIN ||
+ md.md_ealgo > CRYPTO_ALGORITHM_MAX) {
+ gctl_error(req,
+ "Invalid authentication algorithm.");
+ return;
+ } else {
+ fprintf(stderr, "warning: The -e option, not "
+ "the -a option is now used to specify "
+ "encryption algorithm to use.\n");
+ }
+ }
+ }
+ if (md.md_ealgo < CRYPTO_ALGORITHM_MIN ||
+ md.md_ealgo > CRYPTO_ALGORITHM_MAX) {
+ str = gctl_get_ascii(req, "ealgo");
+ if (*str == '\0') {
+ if (version < G_ELI_VERSION_05)
+ str = "aes-cbc";
+ else
+ str = GELI_ENC_ALGO;
+ }
+ md.md_ealgo = g_eli_str2ealgo(str);
+ if (md.md_ealgo < CRYPTO_ALGORITHM_MIN ||
+ md.md_ealgo > CRYPTO_ALGORITHM_MAX) {
+ gctl_error(req, "Invalid encryption algorithm.");
+ return;
+ }
+ if (md.md_ealgo == CRYPTO_CAMELLIA_CBC &&
+ version < G_ELI_VERSION_04) {
+ gctl_error(req,
+ "Camellia-CBC algorithm is supported starting from version %u.",
+ G_ELI_VERSION_04);
+ return;
+ }
+ if (md.md_ealgo == CRYPTO_AES_XTS &&
+ version < G_ELI_VERSION_05) {
+ gctl_error(req,
+ "AES-XTS algorithm is supported starting from version %u.",
+ G_ELI_VERSION_05);
+ return;
+ }
+ }
+ val = gctl_get_intmax(req, "keylen");
+ md.md_keylen = val;
+ md.md_keylen = g_eli_keylen(md.md_ealgo, md.md_keylen);
+ if (md.md_keylen == 0) {
+ gctl_error(req, "Invalid key length.");
+ return;
+ }
+ md.md_provsize = mediasize;
+
+ val = gctl_get_intmax(req, "iterations");
+ if (val != -1) {
+ int nonewpassphrase;
+
+ /*
+ * Don't allow to set iterations when there will be no
+ * passphrase.
+ */
+ nonewpassphrase = gctl_get_int(req, "nonewpassphrase");
+ if (nonewpassphrase) {
+ gctl_error(req,
+ "Options -i and -P are mutually exclusive.");
+ return;
+ }
+ }
+ md.md_iterations = val;
+
+ val = gctl_get_intmax(req, "sectorsize");
+ if (val == 0)
+ md.md_sectorsize = secsize;
+ else {
+ if (val < 0 || (val % secsize) != 0 || !powerof2(val)) {
+ gctl_error(req, "Invalid sector size.");
+ return;
+ }
+ if (val > sysconf(_SC_PAGE_SIZE)) {
+ fprintf(stderr,
+ "warning: Using sectorsize bigger than the page size!\n");
+ }
+ md.md_sectorsize = val;
+ }
+
+ md.md_keys = 0x01;
+ arc4random_buf(md.md_salt, sizeof(md.md_salt));
+ arc4random_buf(md.md_mkeys, sizeof(md.md_mkeys));
+
+ /* Generate user key. */
+ if (eli_genkey(req, &md, key, true) == NULL) {
+ bzero(key, sizeof(key));
+ bzero(&md, sizeof(md));
+ return;
+ }
+
+ /* Encrypt the first and the only Master Key. */
+ error = g_eli_mkey_encrypt(md.md_ealgo, key, md.md_keylen, md.md_mkeys);
+ bzero(key, sizeof(key));
+ if (error != 0) {
+ bzero(&md, sizeof(md));
+ gctl_error(req, "Cannot encrypt Master Key: %s.",
+ strerror(error));
+ return;
+ }
+
+ eli_metadata_encode(&md, sector);
+ bzero(&md, sizeof(md));
+ error = g_metadata_store(prov, sector, sizeof(sector));
+ bzero(sector, sizeof(sector));
+ if (error != 0) {
+ gctl_error(req, "Cannot store metadata on %s: %s.", prov,
+ strerror(error));
+ return;
+ }
+ if (verbose)
+ printf("Metadata value stored on %s.\n", prov);
+ /* Backup metadata to a file. */
+ str = gctl_get_ascii(req, "backupfile");
+ if (str[0] != '\0') {
+ /* Backupfile given be the user, just copy it. */
+ strlcpy(backfile, str, sizeof(backfile));
+ } else {
+ /* Generate file name automatically. */
+ const char *p = prov;
+ unsigned int i;
+
+ if (strncmp(p, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
+ p += sizeof(_PATH_DEV) - 1;
+ snprintf(backfile, sizeof(backfile), "%s%s.eli",
+ GELI_BACKUP_DIR, p);
+ /* Replace all / with _. */
+ for (i = strlen(GELI_BACKUP_DIR); backfile[i] != '\0'; i++) {
+ if (backfile[i] == '/')
+ backfile[i] = '_';
+ }
+ }
+ if (strcmp(backfile, "none") != 0 &&
+ eli_backup_create(req, prov, backfile) == 0) {
+ printf("\nMetadata backup can be found in %s and\n", backfile);
+ printf("can be restored with the following command:\n");
+ printf("\n\t# geli restore %s %s\n\n", backfile, prov);
+ }
+}
+
+static void
+eli_attach(struct gctl_req *req)
+{
+ struct g_eli_metadata md;
+ unsigned char key[G_ELI_USERKEYLEN];
+ const char *prov;
+ off_t mediasize;
+ int nargs;
+
+ nargs = gctl_get_int(req, "nargs");
+ if (nargs != 1) {
+ gctl_error(req, "Invalid number of arguments.");
+ return;
+ }
+ prov = gctl_get_ascii(req, "arg0");
+
+ if (eli_metadata_read(req, prov, &md) == -1)
+ return;
+
+ mediasize = g_get_mediasize(prov);
+ if (md.md_provsize != (uint64_t)mediasize) {
+ gctl_error(req, "Provider size mismatch.");
+ return;
+ }
+
+ if (eli_genkey(req, &md, key, false) == NULL) {
+ bzero(key, sizeof(key));
+ return;
+ }
+
+ gctl_ro_param(req, "key", sizeof(key), key);
+ if (gctl_issue(req) == NULL) {
+ if (verbose)
+ printf("Attached to %s.\n", prov);
+ }
+ bzero(key, sizeof(key));
+}
+
+static void
+eli_configure_detached(struct gctl_req *req, const char *prov, bool boot)
+{
+ struct g_eli_metadata md;
+
+ if (eli_metadata_read(req, prov, &md) == -1)
+ return;
+
+ if (boot && (md.md_flags & G_ELI_FLAG_BOOT)) {
+ if (verbose)
+ printf("BOOT flag already configured for %s.\n", prov);
+ } else if (!boot && !(md.md_flags & G_ELI_FLAG_BOOT)) {
+ if (verbose)
+ printf("BOOT flag not configured for %s.\n", prov);
+ } else {
+ if (boot)
+ md.md_flags |= G_ELI_FLAG_BOOT;
+ else
+ md.md_flags &= ~G_ELI_FLAG_BOOT;
+ eli_metadata_store(req, prov, &md);
+ }
+ bzero(&md, sizeof(md));
+}
+
+static void
+eli_configure(struct gctl_req *req)
+{
+ const char *prov;
+ bool boot, noboot;
+ int i, nargs;
+
+ nargs = gctl_get_int(req, "nargs");
+ if (nargs == 0) {
+ gctl_error(req, "Too few arguments.");
+ return;
+ }
+
+ boot = gctl_get_int(req, "boot");
+ noboot = gctl_get_int(req, "noboot");
+
+ if (boot && noboot) {
+ gctl_error(req, "Options -b and -B are mutually exclusive.");
+ return;
+ }
+ if (!boot && !noboot) {
+ gctl_error(req, "No option given.");
+ return;
+ }
+
+ /* First attached providers. */
+ gctl_issue(req);
+ /* Now the rest. */
+ for (i = 0; i < nargs; i++) {
+ prov = gctl_get_ascii(req, "arg%d", i);
+ if (!eli_is_attached(prov))
+ eli_configure_detached(req, prov, boot);
+ }
+}
+
+static void
+eli_setkey_attached(struct gctl_req *req, struct g_eli_metadata *md)
+{
+ unsigned char key[G_ELI_USERKEYLEN];
+ intmax_t val, old = 0;
+ int error;
+
+ val = gctl_get_intmax(req, "iterations");
+ /* Check if iterations number should be changed. */
+ if (val != -1)
+ md->md_iterations = val;
+ else
+ old = md->md_iterations;
+
+ /* Generate key for Master Key encryption. */
+ if (eli_genkey(req, md, key, true) == NULL) {
+ bzero(key, sizeof(key));
+ return;
+ }
+ /*
+ * If number of iterations has changed, but wasn't given as a
+ * command-line argument, update the request.
+ */
+ if (val == -1 && md->md_iterations != old) {
+ error = gctl_change_param(req, "iterations", sizeof(intmax_t),
+ &md->md_iterations);
+ assert(error == 0);
+ }
+
+ gctl_ro_param(req, "key", sizeof(key), key);
+ gctl_issue(req);
+ bzero(key, sizeof(key));
+}
+
+static void
+eli_setkey_detached(struct gctl_req *req, const char *prov,
+ struct g_eli_metadata *md)
+{
+ unsigned char key[G_ELI_USERKEYLEN], mkey[G_ELI_DATAIVKEYLEN];
+ unsigned char *mkeydst;
+ unsigned int nkey;
+ intmax_t val;
+ int error;
+
+ if (md->md_keys == 0) {
+ gctl_error(req, "No valid keys on %s.", prov);
+ return;
+ }
+
+ /* Generate key for Master Key decryption. */
+ if (eli_genkey(req, md, key, false) == NULL) {
+ bzero(key, sizeof(key));
+ return;
+ }
+
+ /* Decrypt Master Key. */
+ error = g_eli_mkey_decrypt(md, key, mkey, &nkey);
+ bzero(key, sizeof(key));
+ if (error != 0) {
+ bzero(md, sizeof(*md));
+ if (error == -1)
+ gctl_error(req, "Wrong key for %s.", prov);
+ else /* if (error > 0) */ {
+ gctl_error(req, "Cannot decrypt Master Key: %s.",
+ strerror(error));
+ }
+ return;
+ }
+ if (verbose)
+ printf("Decrypted Master Key %u.\n", nkey);
+
+ val = gctl_get_intmax(req, "keyno");
+ if (val != -1)
+ nkey = val;
+#if 0
+ else
+ ; /* Use the key number which was found during decryption. */
+#endif
+ if (nkey >= G_ELI_MAXMKEYS) {
+ gctl_error(req, "Invalid '%s' argument.", "keyno");
+ return;
+ }
+
+ val = gctl_get_intmax(req, "iterations");
+ /* Check if iterations number should and can be changed. */
+ if (val != -1) {
+ if (bitcount32(md->md_keys) != 1) {
+ gctl_error(req, "To be able to use '-i' option, only "
+ "one key can be defined.");
+ return;
+ }
+ if (md->md_keys != (1 << nkey)) {
+ gctl_error(req, "Only already defined key can be "
+ "changed when '-i' option is used.");
+ return;
+ }
+ md->md_iterations = val;
+ }
+
+ mkeydst = md->md_mkeys + nkey * G_ELI_MKEYLEN;
+ md->md_keys |= (1 << nkey);
+
+ bcopy(mkey, mkeydst, sizeof(mkey));
+ bzero(mkey, sizeof(mkey));
+
+ /* Generate key for Master Key encryption. */
+ if (eli_genkey(req, md, key, true) == NULL) {
+ bzero(key, sizeof(key));
+ bzero(md, sizeof(*md));
+ return;
+ }
+
+ /* Encrypt the Master-Key with the new key. */
+ error = g_eli_mkey_encrypt(md->md_ealgo, key, md->md_keylen, mkeydst);
+ bzero(key, sizeof(key));
+ if (error != 0) {
+ bzero(md, sizeof(*md));
+ gctl_error(req, "Cannot encrypt Master Key: %s.",
+ strerror(error));
+ return;
+ }
+
+ /* Store metadata with fresh key. */
+ eli_metadata_store(req, prov, md);
+ bzero(md, sizeof(*md));
+}
+
+static void
+eli_setkey(struct gctl_req *req)
+{
+ struct g_eli_metadata md;
+ const char *prov;
+ int nargs;
+
+ nargs = gctl_get_int(req, "nargs");
+ if (nargs != 1) {
+ gctl_error(req, "Invalid number of arguments.");
+ return;
+ }
+ prov = gctl_get_ascii(req, "arg0");
+
+ if (eli_metadata_read(req, prov, &md) == -1)
+ return;
+
+ if (eli_is_attached(prov))
+ eli_setkey_attached(req, &md);
+ else
+ eli_setkey_detached(req, prov, &md);
+
+ if (req->error == NULL || req->error[0] == '\0') {
+ printf("Note, that the master key encrypted with old keys "
+ "and/or passphrase may still exists in a metadata backup "
+ "file.\n");
+ }
+}
+
+static void
+eli_delkey_attached(struct gctl_req *req, const char *prov __unused)
+{
+
+ gctl_issue(req);
+}
+
+static void
+eli_delkey_detached(struct gctl_req *req, const char *prov)
+{
+ struct g_eli_metadata md;
+ unsigned char *mkeydst;
+ unsigned int nkey;
+ intmax_t val;
+ bool all, force;
+
+ if (eli_metadata_read(req, prov, &md) == -1)
+ return;
+
+ all = gctl_get_int(req, "all");
+ if (all)
+ arc4random_buf(md.md_mkeys, sizeof(md.md_mkeys));
+ else {
+ force = gctl_get_int(req, "force");
+ val = gctl_get_intmax(req, "keyno");
+ if (val == -1) {
+ gctl_error(req, "Key number has to be specified.");
+ return;
+ }
+ nkey = val;
+ if (nkey >= G_ELI_MAXMKEYS) {
+ gctl_error(req, "Invalid '%s' argument.", "keyno");
+ return;
+ }
+ if (!(md.md_keys & (1 << nkey)) && !force) {
+ gctl_error(req, "Master Key %u is not set.", nkey);
+ return;
+ }
+ md.md_keys &= ~(1 << nkey);
+ if (md.md_keys == 0 && !force) {
+ gctl_error(req, "This is the last Master Key. Use '-f' "
+ "option if you really want to remove it.");
+ return;
+ }
+ mkeydst = md.md_mkeys + nkey * G_ELI_MKEYLEN;
+ arc4random_buf(mkeydst, G_ELI_MKEYLEN);
+ }
+
+ eli_metadata_store(req, prov, &md);
+ bzero(&md, sizeof(md));
+}
+
+static void
+eli_delkey(struct gctl_req *req)
+{
+ const char *prov;
+ int nargs;
+
+ nargs = gctl_get_int(req, "nargs");
+ if (nargs != 1) {
+ gctl_error(req, "Invalid number of arguments.");
+ return;
+ }
+ prov = gctl_get_ascii(req, "arg0");
+
+ if (eli_is_attached(prov))
+ eli_delkey_attached(req, prov);
+ else
+ eli_delkey_detached(req, prov);
+}
+
+static void
+eli_resume(struct gctl_req *req)
+{
+ struct g_eli_metadata md;
+ unsigned char key[G_ELI_USERKEYLEN];
+ const char *prov;
+ off_t mediasize;
+ int nargs;
+
+ nargs = gctl_get_int(req, "nargs");
+ if (nargs != 1) {
+ gctl_error(req, "Invalid number of arguments.");
+ return;
+ }
+ prov = gctl_get_ascii(req, "arg0");
+
+ if (eli_metadata_read(req, prov, &md) == -1)
+ return;
+
+ mediasize = g_get_mediasize(prov);
+ if (md.md_provsize != (uint64_t)mediasize) {
+ gctl_error(req, "Provider size mismatch.");
+ return;
+ }
+
+ if (eli_genkey(req, &md, key, false) == NULL) {
+ bzero(key, sizeof(key));
+ return;
+ }
+
+ gctl_ro_param(req, "key", sizeof(key), key);
+ if (gctl_issue(req) == NULL) {
+ if (verbose)
+ printf("Resumed %s.\n", prov);
+ }
+ bzero(key, sizeof(key));
+}
+
+static int
+eli_trash_metadata(struct gctl_req *req, const char *prov, int fd, off_t offset)
+{
+ unsigned int overwrites;
+ unsigned char *sector;
+ ssize_t size;
+ int error;
+
+ size = sizeof(overwrites);
+ if (sysctlbyname("kern.geom.eli.overwrites", &overwrites, &size,
+ NULL, 0) == -1 || overwrites == 0) {
+ overwrites = G_ELI_OVERWRITES;
+ }
+
+ size = g_sectorsize(fd);
+ if (size <= 0) {
+ gctl_error(req, "Cannot obtain provider sector size %s: %s.",
+ prov, strerror(errno));
+ return (-1);
+ }
+ sector = malloc(size);
+ if (sector == NULL) {
+ gctl_error(req, "Cannot allocate %zd bytes of memory.", size);
+ return (-1);
+ }
+
+ error = 0;
+ do {
+ arc4random_buf(sector, size);
+ if (pwrite(fd, sector, size, offset) != size) {
+ if (error == 0)
+ error = errno;
+ }
+ (void)g_flush(fd);
+ } while (--overwrites > 0);
+ free(sector);
+ if (error != 0) {
+ gctl_error(req, "Cannot trash metadata on provider %s: %s.",
+ prov, strerror(error));
+ return (-1);
+ }
+ return (0);
+}
+
+static void
+eli_kill_detached(struct gctl_req *req, const char *prov)
+{
+ off_t offset;
+ int fd;
+
+ /*
+ * NOTE: Maybe we should verify if this is geli provider first,
+ * but 'kill' command is quite critical so better don't waste
+ * the time.
+ */
+#if 0
+ error = g_metadata_read(prov, (unsigned char *)&md, sizeof(md),
+ G_ELI_MAGIC);
+ if (error != 0) {
+ gctl_error(req, "Cannot read metadata from %s: %s.", prov,
+ strerror(error));
+ return;
+ }
+#endif
+
+ fd = g_open(prov, 1);
+ if (fd == -1) {
+ gctl_error(req, "Cannot open provider %s: %s.", prov,
+ strerror(errno));
+ return;
+ }
+ offset = g_mediasize(fd) - g_sectorsize(fd);
+ if (offset <= 0) {
+ gctl_error(req,
+ "Cannot obtain media size or sector size for provider %s: %s.",
+ prov, strerror(errno));
+ (void)g_close(fd);
+ return;
+ }
+ (void)eli_trash_metadata(req, prov, fd, offset);
+ (void)g_close(fd);
+}
+
+static void
+eli_kill(struct gctl_req *req)
+{
+ const char *prov;
+ int i, nargs, all;
+
+ nargs = gctl_get_int(req, "nargs");
+ all = gctl_get_int(req, "all");
+ if (!all && nargs == 0) {
+ gctl_error(req, "Too few arguments.");
+ return;
+ }
+ /*
+ * How '-a' option combine with a list of providers:
+ * Delete Master Keys from all attached providers:
+ * geli kill -a
+ * Delete Master Keys from all attached providers and from
+ * detached da0 and da1:
+ * geli kill -a da0 da1
+ * Delete Master Keys from (attached or detached) da0 and da1:
+ * geli kill da0 da1
+ */
+
+ /* First detached providers. */
+ for (i = 0; i < nargs; i++) {
+ prov = gctl_get_ascii(req, "arg%d", i);
+ if (!eli_is_attached(prov))
+ eli_kill_detached(req, prov);
+ }
+ /* Now attached providers. */
+ gctl_issue(req);
+}
+
+static int
+eli_backup_create(struct gctl_req *req, const char *prov, const char *file)
+{
+ unsigned char *sector;
+ ssize_t secsize;
+ int error, filefd, ret;
+
+ ret = -1;
+ filefd = -1;
+ sector = NULL;
+ secsize = 0;
+
+ secsize = g_get_sectorsize(prov);
+ if (secsize == 0) {
+ gctl_error(req, "Cannot get informations about %s: %s.", prov,
+ strerror(errno));
+ goto out;
+ }
+ sector = malloc(secsize);
+ if (sector == NULL) {
+ gctl_error(req, "Cannot allocate memory.");
+ goto out;
+ }
+ /* Read metadata from the provider. */
+ error = g_metadata_read(prov, sector, secsize, G_ELI_MAGIC);
+ if (error != 0) {
+ gctl_error(req, "Unable to read metadata from %s: %s.", prov,
+ strerror(error));
+ goto out;
+ }
+
+ filefd = open(file, O_WRONLY | O_TRUNC | O_CREAT, 0600);
+ if (filefd == -1) {
+ gctl_error(req, "Unable to open %s: %s.", file,
+ strerror(errno));
+ goto out;
+ }
+ /* Write metadata to the destination file. */
+ if (write(filefd, sector, secsize) != secsize) {
+ gctl_error(req, "Unable to write to %s: %s.", file,
+ strerror(errno));
+ (void)close(filefd);
+ (void)unlink(file);
+ goto out;
+ }
+ (void)fsync(filefd);
+ (void)close(filefd);
+ /* Success. */
+ ret = 0;
+out:
+ if (sector != NULL) {
+ bzero(sector, secsize);
+ free(sector);
+ }
+ return (ret);
+}
+
+static void
+eli_backup(struct gctl_req *req)
+{
+ const char *file, *prov;
+ int nargs;
+
+ nargs = gctl_get_int(req, "nargs");
+ if (nargs != 2) {
+ gctl_error(req, "Invalid number of arguments.");
+ return;
+ }
+ prov = gctl_get_ascii(req, "arg0");
+ file = gctl_get_ascii(req, "arg1");
+
+ eli_backup_create(req, prov, file);
+}
+
+static void
+eli_restore(struct gctl_req *req)
+{
+ struct g_eli_metadata md;
+ const char *file, *prov;
+ off_t mediasize;
+ int nargs;
+
+ nargs = gctl_get_int(req, "nargs");
+ if (nargs != 2) {
+ gctl_error(req, "Invalid number of arguments.");
+ return;
+ }
+ file = gctl_get_ascii(req, "arg0");
+ prov = gctl_get_ascii(req, "arg1");
+
+ /* Read metadata from the backup file. */
+ if (eli_metadata_read(req, file, &md) == -1)
+ return;
+ /* Obtain provider's mediasize. */
+ mediasize = g_get_mediasize(prov);
+ if (mediasize == 0) {
+ gctl_error(req, "Cannot get informations about %s: %s.", prov,
+ strerror(errno));
+ return;
+ }
+ /* Check if the provider size has changed since we did the backup. */
+ if (md.md_provsize != (uint64_t)mediasize) {
+ if (gctl_get_int(req, "force")) {
+ md.md_provsize = mediasize;
+ } else {
+ gctl_error(req, "Provider size mismatch: "
+ "wrong backup file?");
+ return;
+ }
+ }
+ /* Write metadata to the provider. */
+ (void)eli_metadata_store(req, prov, &md);
+}
+
+static void
+eli_resize(struct gctl_req *req)
+{
+ struct g_eli_metadata md;
+ const char *prov;
+ unsigned char *sector;
+ ssize_t secsize;
+ off_t mediasize, oldsize;
+ int error, nargs, provfd;
+
+ nargs = gctl_get_int(req, "nargs");
+ if (nargs != 1) {
+ gctl_error(req, "Invalid number of arguments.");
+ return;
+ }
+ prov = gctl_get_ascii(req, "arg0");
+
+ provfd = -1;
+ sector = NULL;
+ secsize = 0;
+
+ provfd = g_open(prov, 1);
+ if (provfd == -1) {
+ gctl_error(req, "Cannot open %s: %s.", prov, strerror(errno));
+ goto out;
+ }
+
+ mediasize = g_mediasize(provfd);
+ secsize = g_sectorsize(provfd);
+ if (mediasize == -1 || secsize == -1) {
+ gctl_error(req, "Cannot get information about %s: %s.", prov,
+ strerror(errno));
+ goto out;
+ }
+
+ sector = malloc(secsize);
+ if (sector == NULL) {
+ gctl_error(req, "Cannot allocate memory.");
+ goto out;
+ }
+
+ oldsize = gctl_get_intmax(req, "oldsize");
+ if (oldsize < 0 || oldsize > mediasize) {
+ gctl_error(req, "Invalid oldsize: Out of range.");
+ goto out;
+ }
+ if (oldsize == mediasize) {
+ gctl_error(req, "Size hasn't changed.");
+ goto out;
+ }
+
+ /* Read metadata from the 'oldsize' offset. */
+ if (pread(provfd, sector, secsize, oldsize - secsize) != secsize) {
+ gctl_error(req, "Cannot read old metadata: %s.",
+ strerror(errno));
+ goto out;
+ }
+
+ /* Check if this sector contains geli metadata. */
+ error = eli_metadata_decode(sector, &md);
+ switch (error) {
+ case 0:
+ break;
+ case EOPNOTSUPP:
+ gctl_error(req,
+ "Provider's %s metadata version %u is too new.\n"
+ "geli: The highest supported version is %u.",
+ prov, (unsigned int)md.md_version, G_ELI_VERSION);
+ goto out;
+ case EINVAL:
+ gctl_error(req, "Inconsistent provider's %s metadata.", prov);
+ goto out;
+ default:
+ gctl_error(req,
+ "Unexpected error while decoding provider's %s metadata: %s.",
+ prov, strerror(error));
+ goto out;
+ }
+
+ /*
+ * If the old metadata doesn't have a correct provider size, refuse
+ * to resize.
+ */
+ if (md.md_provsize != (uint64_t)oldsize) {
+ gctl_error(req, "Provider size mismatch at oldsize.");
+ goto out;
+ }
+
+ /*
+ * Update the old metadata with the current provider size and write
+ * it back to the correct place on the provider.
+ */
+ md.md_provsize = mediasize;
+ /* Write metadata to the provider. */
+ (void)eli_metadata_store(req, prov, &md);
+ /* Now trash the old metadata. */
+ (void)eli_trash_metadata(req, prov, provfd, oldsize - secsize);
+out:
+ if (provfd != -1)
+ (void)g_close(provfd);
+ if (sector != NULL) {
+ bzero(sector, secsize);
+ free(sector);
+ }
+}
+
+static void
+eli_version(struct gctl_req *req)
+{
+ struct g_eli_metadata md;
+ const char *name;
+ unsigned int version;
+ int error, i, nargs;
+
+ nargs = gctl_get_int(req, "nargs");
+
+ if (nargs == 0) {
+ unsigned int kernver;
+ ssize_t size;
+
+ size = sizeof(kernver);
+ if (sysctlbyname("kern.geom.eli.version", &kernver, &size,
+ NULL, 0) == -1) {
+ warn("Unable to obtain GELI kernel version");
+ } else {
+ printf("kernel: %u\n", kernver);
+ }
+ printf("userland: %u\n", G_ELI_VERSION);
+ return;
+ }
+
+ for (i = 0; i < nargs; i++) {
+ name = gctl_get_ascii(req, "arg%d", i);
+ error = g_metadata_read(name, (unsigned char *)&md,
+ sizeof(md), G_ELI_MAGIC);
+ if (error != 0) {
+ warn("%s: Unable to read metadata: %s.", name,
+ strerror(error));
+ gctl_error(req, "Not fully done.");
+ continue;
+ }
+ version = le32dec(&md.md_version);
+ printf("%s: %u\n", name, version);
+ }
+}
+
+static void
+eli_clear(struct gctl_req *req)
+{
+ const char *name;
+ int error, i, nargs;
+
+ nargs = gctl_get_int(req, "nargs");
+ if (nargs < 1) {
+ gctl_error(req, "Too few arguments.");
+ return;
+ }
+
+ for (i = 0; i < nargs; i++) {
+ name = gctl_get_ascii(req, "arg%d", i);
+ error = g_metadata_clear(name, G_ELI_MAGIC);
+ if (error != 0) {
+ fprintf(stderr, "Cannot clear metadata on %s: %s.\n",
+ name, strerror(error));
+ gctl_error(req, "Not fully done.");
+ continue;
+ }
+ if (verbose)
+ printf("Metadata cleared on %s.\n", name);
+ }
+}
+
+static void
+eli_dump(struct gctl_req *req)
+{
+ struct g_eli_metadata md;
+ const char *name;
+ int i, nargs;
+
+ nargs = gctl_get_int(req, "nargs");
+ if (nargs < 1) {
+ gctl_error(req, "Too few arguments.");
+ return;
+ }
+
+ for (i = 0; i < nargs; i++) {
+ name = gctl_get_ascii(req, "arg%d", i);
+ if (eli_metadata_read(NULL, name, &md) == -1) {
+ gctl_error(req, "Not fully done.");
+ continue;
+ }
+ printf("Metadata on %s:\n", name);
+ eli_metadata_dump(&md);
+ printf("\n");
+ }
+}
diff --git a/sbin/geom/class/journal/Makefile b/sbin/geom/class/journal/Makefile
new file mode 100644
index 0000000..0e1a38ea
--- /dev/null
+++ b/sbin/geom/class/journal/Makefile
@@ -0,0 +1,12 @@
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../../misc
+
+GEOM_CLASS= journal
+SRCS+= geom_journal_ufs.c
+
+LIBADD= ufs md
+
+CFLAGS+=-I${.CURDIR}/../../../../sys
+
+.include <bsd.lib.mk>
diff --git a/sbin/geom/class/journal/geom_journal.c b/sbin/geom/class/journal/geom_journal.c
new file mode 100644
index 0000000..681ff47
--- /dev/null
+++ b/sbin/geom/class/journal/geom_journal.c
@@ -0,0 +1,348 @@
+/*-
+ * Copyright (c) 2005-2006 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <errno.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <strings.h>
+#include <assert.h>
+#include <libgeom.h>
+#include <geom/journal/g_journal.h>
+#include <core/geom.h>
+#include <misc/subr.h>
+
+#include "geom_journal.h"
+
+
+uint32_t lib_version = G_LIB_VERSION;
+uint32_t version = G_JOURNAL_VERSION;
+
+static void journal_main(struct gctl_req *req, unsigned flags);
+static void journal_clear(struct gctl_req *req);
+static void journal_dump(struct gctl_req *req);
+static void journal_label(struct gctl_req *req);
+
+struct g_command class_commands[] = {
+ { "clear", G_FLAG_VERBOSE, journal_main, G_NULL_OPTS,
+ "[-v] prov ..."
+ },
+ { "dump", 0, journal_main, G_NULL_OPTS,
+ "prov ..."
+ },
+ { "label", G_FLAG_VERBOSE, journal_main,
+ {
+ { 'c', "checksum", NULL, G_TYPE_BOOL },
+ { 'f', "force", NULL, G_TYPE_BOOL },
+ { 'h', "hardcode", NULL, G_TYPE_BOOL },
+ { 's', "jsize", "-1", G_TYPE_NUMBER },
+ G_OPT_SENTINEL
+ },
+ "[-cfhv] [-s jsize] dataprov [jprov]"
+ },
+ { "stop", G_FLAG_VERBOSE, NULL,
+ {
+ { 'f', "force", NULL, G_TYPE_BOOL },
+ G_OPT_SENTINEL
+ },
+ "[-fv] name ..."
+ },
+ { "sync", G_FLAG_VERBOSE, NULL, G_NULL_OPTS,
+ "[-v]"
+ },
+ G_CMD_SENTINEL
+};
+
+static int verbose = 0;
+
+static void
+journal_main(struct gctl_req *req, unsigned flags)
+{
+ const char *name;
+
+ if ((flags & G_FLAG_VERBOSE) != 0)
+ verbose = 1;
+
+ name = gctl_get_ascii(req, "verb");
+ if (name == NULL) {
+ gctl_error(req, "No '%s' argument.", "verb");
+ return;
+ }
+ if (strcmp(name, "label") == 0)
+ journal_label(req);
+ else if (strcmp(name, "clear") == 0)
+ journal_clear(req);
+ else if (strcmp(name, "dump") == 0)
+ journal_dump(req);
+ else
+ gctl_error(req, "Unknown command: %s.", name);
+}
+
+static int
+g_journal_fs_exists(const char *prov)
+{
+
+ if (g_journal_ufs_exists(prov))
+ return (1);
+#if 0
+ if (g_journal_otherfs_exists(prov))
+ return (1);
+#endif
+ return (0);
+}
+
+static int
+g_journal_fs_using_last_sector(const char *prov)
+{
+
+ if (g_journal_ufs_using_last_sector(prov))
+ return (1);
+#if 0
+ if (g_journal_otherfs_using_last_sector(prov))
+ return (1);
+#endif
+ return (0);
+}
+
+static void
+journal_label(struct gctl_req *req)
+{
+ struct g_journal_metadata md;
+ const char *data, *journal, *str;
+ u_char sector[512];
+ intmax_t jsize, msize, ssize;
+ int error, force, i, nargs, checksum, hardcode;
+
+ nargs = gctl_get_int(req, "nargs");
+ str = NULL; /* gcc */
+
+ strlcpy(md.md_magic, G_JOURNAL_MAGIC, sizeof(md.md_magic));
+ md.md_version = G_JOURNAL_VERSION;
+ md.md_id = arc4random();
+ md.md_joffset = 0;
+ md.md_jid = 0;
+ md.md_flags = GJ_FLAG_CLEAN;
+ checksum = gctl_get_int(req, "checksum");
+ if (checksum)
+ md.md_flags |= GJ_FLAG_CHECKSUM;
+ force = gctl_get_int(req, "force");
+ hardcode = gctl_get_int(req, "hardcode");
+
+ if (nargs != 1 && nargs != 2) {
+ gctl_error(req, "Invalid number of arguments.");
+ return;
+ }
+
+ /* Verify the given providers. */
+ for (i = 0; i < nargs; i++) {
+ str = gctl_get_ascii(req, "arg%d", i);
+ if (g_get_mediasize(str) == 0) {
+ gctl_error(req, "Invalid provider %s.", str);
+ return;
+ }
+ }
+
+ data = gctl_get_ascii(req, "arg0");
+ jsize = gctl_get_intmax(req, "jsize");
+ journal = NULL;
+ switch (nargs) {
+ case 1:
+ if (!force && g_journal_fs_exists(data)) {
+ gctl_error(req, "File system exists on %s and this "
+ "operation would destroy it.\nUse -f if you "
+ "really want to do it.", data);
+ return;
+ }
+ journal = data;
+ msize = g_get_mediasize(data);
+ ssize = g_get_sectorsize(data);
+ if (jsize == -1) {
+ /*
+ * No journal size specified. 1GB should be safe
+ * default.
+ */
+ jsize = 1073741824ULL;
+ } else {
+ if (jsize < 104857600) {
+ gctl_error(req, "Journal too small.");
+ return;
+ }
+ if ((jsize % ssize) != 0) {
+ gctl_error(req, "Invalid journal size.");
+ return;
+ }
+ }
+ if (jsize + ssize >= msize) {
+ gctl_error(req, "Provider too small for journalling. "
+ "You can try smaller jsize (default is %jd).",
+ jsize);
+ return;
+ }
+ md.md_jstart = msize - ssize - jsize;
+ md.md_jend = msize - ssize;
+ break;
+ case 2:
+ if (!force && g_journal_fs_using_last_sector(data)) {
+ gctl_error(req, "File system on %s is using the last "
+ "sector and this operation is going to overwrite "
+ "it. Use -f if you really want to do it.", data);
+ return;
+ }
+ journal = gctl_get_ascii(req, "arg1");
+ if (jsize != -1) {
+ gctl_error(req, "jsize argument is valid only for "
+ "all-in-one configuration.");
+ return;
+ }
+ msize = g_get_mediasize(journal);
+ ssize = g_get_sectorsize(journal);
+ md.md_jstart = 0;
+ md.md_jend = msize - ssize;
+ break;
+ }
+
+ if (g_get_sectorsize(data) != g_get_sectorsize(journal)) {
+ gctl_error(req, "Not equal sector sizes.");
+ return;
+ }
+
+ /*
+ * Clear last sector first, to spoil all components if device exists.
+ */
+ for (i = 0; i < nargs; i++) {
+ str = gctl_get_ascii(req, "arg%d", i);
+ error = g_metadata_clear(str, NULL);
+ if (error != 0) {
+ gctl_error(req, "Cannot clear metadata on %s: %s.", str,
+ strerror(error));
+ return;
+ }
+ }
+
+ /*
+ * Ok, store metadata.
+ */
+ for (i = 0; i < nargs; i++) {
+ switch (i) {
+ case 0:
+ str = data;
+ md.md_type = GJ_TYPE_DATA;
+ if (nargs == 1)
+ md.md_type |= GJ_TYPE_JOURNAL;
+ break;
+ case 1:
+ str = journal;
+ md.md_type = GJ_TYPE_JOURNAL;
+ break;
+ }
+ md.md_provsize = g_get_mediasize(str);
+ assert(md.md_provsize != 0);
+ if (!hardcode)
+ bzero(md.md_provider, sizeof(md.md_provider));
+ else {
+ if (strncmp(str, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
+ str += sizeof(_PATH_DEV) - 1;
+ strlcpy(md.md_provider, str, sizeof(md.md_provider));
+ }
+ journal_metadata_encode(&md, sector);
+ error = g_metadata_store(str, sector, sizeof(sector));
+ if (error != 0) {
+ fprintf(stderr, "Cannot store metadata on %s: %s.\n",
+ str, strerror(error));
+ gctl_error(req, "Not fully done.");
+ continue;
+ }
+ if (verbose)
+ printf("Metadata value stored on %s.\n", str);
+ }
+}
+
+static void
+journal_clear(struct gctl_req *req)
+{
+ const char *name;
+ int error, i, nargs;
+
+ nargs = gctl_get_int(req, "nargs");
+ if (nargs < 1) {
+ gctl_error(req, "Too few arguments.");
+ return;
+ }
+
+ for (i = 0; i < nargs; i++) {
+ name = gctl_get_ascii(req, "arg%d", i);
+ error = g_metadata_clear(name, G_JOURNAL_MAGIC);
+ if (error != 0) {
+ fprintf(stderr, "Cannot clear metadata on %s: %s.\n",
+ name, strerror(error));
+ gctl_error(req, "Not fully done.");
+ continue;
+ }
+ if (verbose)
+ printf("Metadata cleared on %s.\n", name);
+ }
+}
+
+static void
+journal_dump(struct gctl_req *req)
+{
+ struct g_journal_metadata md, tmpmd;
+ const char *name;
+ int error, i, nargs;
+
+ nargs = gctl_get_int(req, "nargs");
+ if (nargs < 1) {
+ gctl_error(req, "Too few arguments.");
+ return;
+ }
+
+ for (i = 0; i < nargs; i++) {
+ name = gctl_get_ascii(req, "arg%d", i);
+ error = g_metadata_read(name, (u_char *)&tmpmd, sizeof(tmpmd),
+ G_JOURNAL_MAGIC);
+ if (error != 0) {
+ fprintf(stderr, "Cannot read metadata from %s: %s.\n",
+ name, strerror(error));
+ gctl_error(req, "Not fully done.");
+ continue;
+ }
+ if (journal_metadata_decode((u_char *)&tmpmd, &md) != 0) {
+ fprintf(stderr, "MD5 hash mismatch for %s, skipping.\n",
+ name);
+ gctl_error(req, "Not fully done.");
+ continue;
+ }
+ printf("Metadata on %s:\n", name);
+ journal_metadata_dump(&md);
+ printf("\n");
+ }
+}
diff --git a/sbin/geom/class/journal/geom_journal.h b/sbin/geom/class/journal/geom_journal.h
new file mode 100644
index 0000000..63d35b4
--- /dev/null
+++ b/sbin/geom/class/journal/geom_journal.h
@@ -0,0 +1,33 @@
+/*-
+ * Copyright (c) 2006 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _GEOM_JOURNAL_H_
+#define _GEOM_JOURNAL_H_
+int g_journal_ufs_exists(const char *prov);
+int g_journal_ufs_using_last_sector(const char *prov);
+#endif /* !_GEOM_JOURNAL_H_ */
diff --git a/sbin/geom/class/journal/geom_journal_ufs.c b/sbin/geom/class/journal/geom_journal_ufs.c
new file mode 100644
index 0000000..07d1922
--- /dev/null
+++ b/sbin/geom/class/journal/geom_journal_ufs.c
@@ -0,0 +1,78 @@
+/*-
+ * Copyright (c) 2006 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/disklabel.h>
+#include <sys/mount.h>
+
+#include <ufs/ufs/dinode.h>
+#include <ufs/ffs/fs.h>
+
+#include <libufs.h>
+#include <libgeom.h>
+#include <core/geom.h>
+#include <misc/subr.h>
+
+#include "geom_journal.h"
+
+static struct fs *
+read_superblock(const char *prov)
+{
+ static struct uufsd disk;
+ struct fs *fs;
+
+ if (ufs_disk_fillout(&disk, prov) == -1)
+ return (NULL);
+ fs = &disk.d_fs;
+ ufs_disk_close(&disk);
+ return (fs);
+}
+
+int
+g_journal_ufs_exists(const char *prov)
+{
+
+ return (read_superblock(prov) != NULL);
+}
+
+int
+g_journal_ufs_using_last_sector(const char *prov)
+{
+ struct fs *fs;
+ off_t psize, fssize;
+
+ fs = read_superblock(prov);
+ if (fs == NULL)
+ return (0);
+ /* Provider size in 512 bytes blocks. */
+ psize = g_get_mediasize(prov) / DEV_BSIZE;
+ /* File system size in 512 bytes blocks. */
+ fssize = fsbtodb(fs, fs->fs_size);
+ return (psize <= fssize);
+}
diff --git a/sbin/geom/class/journal/gjournal.8 b/sbin/geom/class/journal/gjournal.8
new file mode 100644
index 0000000..6eb8cde
--- /dev/null
+++ b/sbin/geom/class/journal/gjournal.8
@@ -0,0 +1,346 @@
+.\" Copyright (c) 2006-2009 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd February 17, 2009
+.Dt GJOURNAL 8
+.Os
+.Sh NAME
+.Nm gjournal
+.Nd "control utility for journaled devices"
+.Sh SYNOPSIS
+.Nm
+.Cm label
+.Op Fl cfhv
+.Op Fl s Ar jsize
+.Ar dataprov
+.Op Ar jprov
+.Nm
+.Cm stop
+.Op Fl fv
+.Ar name ...
+.Nm
+.Cm sync
+.Op Fl v
+.Nm
+.Cm clear
+.Op Fl v
+.Ar prov ...
+.Nm
+.Cm dump
+.Ar prov ...
+.Nm
+.Cm list
+.Nm
+.Cm status
+.Nm
+.Cm load
+.Nm
+.Cm unload
+.Sh DESCRIPTION
+The
+.Nm
+utility is used for journal configuration on the given GEOM provider.
+The Journal and data may be stored on the same provider or on two separate
+providers.
+This is block level journaling, not file system level journaling, which means
+everything gets logged, e.g.\& for file systems, it journals both data and
+metadata.
+The
+.Nm
+GEOM class can talk to file systems, which allows the use of
+.Nm
+for file system journaling and to keep file systems in a consistent state.
+At this time, only UFS file system is supported.
+.Pp
+To configure journaling on the UFS file system using
+.Nm ,
+one should first create a
+.Nm
+provider using the
+.Nm
+utility, then run
+.Xr newfs 8
+or
+.Xr tunefs 8
+on it with the
+.Fl J
+flag which instructs UFS to cooperate with the
+.Nm
+provider below.
+There are important differences in how journaled UFS works.
+The most important one is that
+.Xr sync 2
+and
+.Xr fsync 2
+system calls do not work as expected anymore.
+To ensure that data is stored on the data provider, the
+.Nm Cm sync
+command should be used after calling
+.Xr sync 2 .
+For the best performance possible, soft-updates should be disabled when
+.Nm
+is used.
+It is also safe and recommended to use the
+.Cm async
+.Xr mount 8
+option.
+.Pp
+When
+.Nm
+is configured on top of
+.Xr gmirror 8
+or
+.Xr graid3 8
+providers, it also keeps them in a consistent state, thus
+automatic synchronization on power failure or system crash may be disabled
+on those providers.
+.Pp
+The
+.Nm
+utility uses on-disk metadata, stored in the provider's last sector,
+to store all needed information.
+This could be a problem when an existing file system is converted to use
+.Nm .
+.Pp
+The first argument to
+.Nm
+indicates an action to be performed:
+.Bl -tag -width ".Cm status"
+.It Cm label
+Configures
+.Nm
+on the given provider(s).
+If only one provider is given, both data and journal are stored on the same
+provider.
+If two providers are given, the first one will be used as data provider and the
+second will be used as the journal provider.
+.Pp
+Additional options include:
+.Bl -tag -width ".Fl s Ar jsize"
+.It Fl c
+Checksum journal records.
+.It Fl f
+May be used to convert an existing file system to use
+.Nm ,
+but only if the journal will be configured on a separate provider and if the
+last sector in the data provider is not used by the existing file system.
+If
+.Nm
+detects that the last sector is used, it will refuse to overwrite it
+and return an error.
+This behavior may be forced by using the
+.Fl f
+flag, which will force
+.Nm
+to overwrite the last sector.
+.It Fl h
+Hardcode provider names in metadata.
+.It Fl s Ar jsize
+Specifies size of the journal if only one provider is used for both data and
+journal.
+The default is one gigabyte.
+Size should be chosen based on provider's load, and not on its size;
+recommended minimum is twice the size of the physical memory installed.
+It is not recommended to use
+.Nm
+for small file systems (e.g.: only few gigabytes big).
+.El
+.It Cm clear
+Clear metadata on the given providers.
+.It Cm stop
+Stop the given provider.
+.Pp
+Additional options include:
+.Bl -tag -width ".Fl f"
+.It Fl f
+Stop the given provider even if it is opened.
+.El
+.It Cm sync
+Trigger journal switch and enforce sending data to the data provider.
+.It Cm dump
+Dump metadata stored on the given providers.
+.It Cm list
+See
+.Xr geom 8 .
+.It Cm status
+See
+.Xr geom 8 .
+.It Cm load
+See
+.Xr geom 8 .
+.It Cm unload
+See
+.Xr geom 8 .
+.El
+.Pp
+Additional options include:
+.Bl -tag -width ".Fl v"
+.It Fl v
+Be more verbose.
+.El
+.Sh EXIT STATUS
+Exit status is 0 on success, and 1 if the command fails.
+.Sh EXAMPLES
+Create a
+.Nm
+based UFS file system and mount it:
+.Bd -literal -offset indent
+gjournal load
+gjournal label da0
+newfs -J /dev/da0.journal
+mount -o async /dev/da0.journal /mnt
+.Ed
+.Pp
+Configure journaling on an existing file system, but only if
+.Nm
+allows this (i.e., if the last sector is not already used by the file system):
+.Bd -literal -offset indent
+umount /dev/da0s1d
+gjournal label da0s1d da0s1e && \e
+ tunefs -J enable -n disable da0s1d.journal && \e
+ mount -o async /dev/da0s1d.journal /mnt || \e
+ mount /dev/da0s1d /mnt
+.Ed
+.Sh SYSCTLS
+Gjournal adds the sysctl level kern.geom.journal.
+The string and integer information available is detailed below.
+The changeable column shows whether a process with appropriate privilege may
+change the value.
+.Bl -column "accept_immediatelyXXXXXX" integerXXX -offset indent
+.It Sy "sysctl name Type Changeable"
+.It "debug integer yes"
+.It "switch_time integer yes"
+.It "force_switch integer yes"
+.It "parallel_flushes integer yes"
+.It "accept_immediately integer yes"
+.It "parallel_copies integer yes"
+.It "record_entries integer yes"
+.It "optimize integer yes"
+.El
+.Bl -tag -width 6n
+.It Li debug
+Setting a non-zero value enables debugging at various levels.
+Debug level 1 will record actions at a journal level, relating to journal
+switches, metadata updates, etc.
+Debug level 2 will record actions at a higher level, relating to the numbers of
+entries in journals, access requests, etc.
+Debug level 3 will record verbose detail, including insertion of I/Os to the
+journal.
+.It Li switch_time
+The maximum number of seconds a journal is allowed to remain open before
+switching to a new journal.
+.It Li force_switch
+Force a journal switch when the journal uses more than N% of the free journal
+space.
+.It Li parallel_flushes
+The number of flush I/O requests to be sent in parallel when flushing the
+journal to the data provider.
+.It Li accept_immediately
+The maximum number of I/O requests accepted at the same time.
+.It Li parallel_copies
+The number of copy I/O requests to send in parallel.
+.It Li record_entries
+The maximum number of record entries to allow in a single journal.
+.It Li optimize
+Controls whether entries in a journal will be optimized by combining overlapping
+I/Os into a single I/O and reordering the entries in a journal.
+This can be disabled by setting the sysctl to 0.
+.El
+.Ss cache
+The string and integer information available for the cache level
+is detailed below.
+The changeable column shows whether a process with appropriate
+privilege may change the value.
+.Bl -column "alloc_failuresXXXXXX" integerXXX -offset indent
+.It Sy "sysctl name Type Changeable"
+.It "used integer no"
+.It "limit integer yes"
+.It "divisor integer no"
+.It "switch integer yes"
+.It "misses integer yes"
+.It "alloc_failures integer yes"
+.El
+.Bl -tag -width 6n
+.It Li used
+The number of bytes currently allocated to the cache.
+.It Li limit
+The maximum number of bytes to be allocated to the cache.
+.It Li divisor
+Sets the cache size to be used as a proportion of kmem_size.
+A value of 2 (the default) will cause the cache size to be set to 1/2 of the
+kmem_size.
+.It Li switch
+Force a journal switch when this percentage of cache has been used.
+.It Li misses
+The number of cache misses, when data has been read, but was not found in the
+cache.
+.It Li alloc_failures
+The number of times memory failed to be allocated to the cache because the cache
+limit was hit.
+.El
+.Ss stats
+The string and integer information available for the statistics level
+is detailed below.
+The changeable column shows whether a process with appropriate
+privilege may change the value.
+.Bl -column "skipped_bytesXXXXXX" integerXXX -offset indent
+.It Sy "sysctl name Type Changeable"
+.It "skipped_bytes integer yes"
+.It "combined_ios integer yes"
+.It "switches integer yes"
+.It "wait_for_copy integer yes"
+.It "journal_full integer yes"
+.It "low_mem integer yes"
+.El
+.Bl -tag -width 6n
+.It Li skipped_bytes
+The number of bytes skipped.
+.It Li combined_ios
+The number of I/Os which were combined by journal optimization.
+.It Li switches
+The number of journal switches.
+.It Li wait_for_copy
+The number of times the journal switch process had to wait for the previous
+journal copy to complete.
+.It Li journal_full
+The number of times the journal was almost full, forcing a journal switch.
+.It Li low_mem
+The number of times the low_mem hook was called.
+.El
+.Sh SEE ALSO
+.Xr geom 4 ,
+.Xr geom 8 ,
+.Xr mount 8 ,
+.Xr newfs 8 ,
+.Xr tunefs 8 ,
+.Xr umount 8
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Fx 7.0 .
+.Sh AUTHORS
+.An Pawel Jakub Dawidek Aq Mt pjd@FreeBSD.org
diff --git a/sbin/geom/class/label/Makefile b/sbin/geom/class/label/Makefile
new file mode 100644
index 0000000..0b609a1
--- /dev/null
+++ b/sbin/geom/class/label/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../../misc
+
+GEOM_CLASS= label
+
+.include <bsd.lib.mk>
diff --git a/sbin/geom/class/label/geom_label.c b/sbin/geom/class/label/geom_label.c
new file mode 100644
index 0000000..6c880ee
--- /dev/null
+++ b/sbin/geom/class/label/geom_label.c
@@ -0,0 +1,225 @@
+/*-
+ * Copyright (c) 2004-2005 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <assert.h>
+#include <libgeom.h>
+#include <geom/label/g_label.h>
+
+#include "core/geom.h"
+#include "misc/subr.h"
+
+#ifdef STATIC_GEOM_CLASSES
+#define PUBSYM(x) glabel_##x
+#else
+#define PUBSYM(x) x
+#endif
+
+uint32_t PUBSYM(lib_version) = G_LIB_VERSION;
+uint32_t PUBSYM(version) = G_LABEL_VERSION;
+
+static void label_main(struct gctl_req *req, unsigned flags);
+static void label_clear(struct gctl_req *req);
+static void label_dump(struct gctl_req *req);
+static void label_label(struct gctl_req *req);
+
+struct g_command PUBSYM(class_commands)[] = {
+ { "clear", G_FLAG_VERBOSE, label_main, G_NULL_OPTS,
+ "[-v] dev ..."
+ },
+ { "create", G_FLAG_VERBOSE | G_FLAG_LOADKLD, NULL, G_NULL_OPTS,
+ "[-v] name dev"
+ },
+ { "destroy", G_FLAG_VERBOSE, NULL,
+ {
+ { 'f', "force", NULL, G_TYPE_BOOL },
+ G_OPT_SENTINEL
+ },
+ "[-fv] name ..."
+ },
+ { "dump", 0, label_main, G_NULL_OPTS,
+ "dev ..."
+ },
+ { "label", G_FLAG_VERBOSE | G_FLAG_LOADKLD, label_main, G_NULL_OPTS,
+ "[-v] name dev"
+ },
+ { "stop", G_FLAG_VERBOSE, NULL,
+ {
+ { 'f', "force", NULL, G_TYPE_BOOL },
+ G_OPT_SENTINEL
+ },
+ "[-fv] name ..."
+ },
+ G_CMD_SENTINEL
+};
+
+static int verbose = 0;
+
+static void
+label_main(struct gctl_req *req, unsigned flags)
+{
+ const char *name;
+
+ if ((flags & G_FLAG_VERBOSE) != 0)
+ verbose = 1;
+
+ name = gctl_get_ascii(req, "verb");
+ if (name == NULL) {
+ gctl_error(req, "No '%s' argument.", "verb");
+ return;
+ }
+ if (strcmp(name, "label") == 0)
+ label_label(req);
+ else if (strcmp(name, "clear") == 0)
+ label_clear(req);
+ else if (strcmp(name, "dump") == 0)
+ label_dump(req);
+ else
+ gctl_error(req, "Unknown command: %s.", name);
+}
+
+static void
+label_label(struct gctl_req *req)
+{
+ struct g_label_metadata md;
+ const char *name, *label;
+ u_char sector[512];
+ int error, nargs;
+
+ nargs = gctl_get_int(req, "nargs");
+ if (nargs != 2) {
+ gctl_error(req, "Invalid number of arguments.");
+ return;
+ }
+
+ /*
+ * Clear last sector first to spoil all components if device exists.
+ */
+ name = gctl_get_ascii(req, "arg1");
+ error = g_metadata_clear(name, NULL);
+ if (error != 0) {
+ gctl_error(req, "Can't store metadata on %s: %s.", name,
+ strerror(error));
+ return;
+ }
+
+ strlcpy(md.md_magic, G_LABEL_MAGIC, sizeof(md.md_magic));
+ md.md_version = G_LABEL_VERSION;
+ label = gctl_get_ascii(req, "arg0");
+ strlcpy(md.md_label, label, sizeof(md.md_label));
+ md.md_provsize = g_get_mediasize(name);
+ if (md.md_provsize == 0) {
+ gctl_error(req, "Can't get mediasize of %s: %s.", name,
+ strerror(errno));
+ return;
+ }
+
+ /*
+ * Ok, store metadata.
+ */
+ label_metadata_encode(&md, sector);
+ error = g_metadata_store(name, sector, sizeof(sector));
+ if (error != 0) {
+ fprintf(stderr, "Can't store metadata on %s: %s.\n", name,
+ strerror(error));
+ gctl_error(req, "Not done.");
+ }
+ if (verbose)
+ printf("Metadata value stored on %s.\n", name);
+}
+
+static void
+label_clear(struct gctl_req *req)
+{
+ const char *name;
+ int error, i, nargs;
+
+ nargs = gctl_get_int(req, "nargs");
+ if (nargs < 1) {
+ gctl_error(req, "Too few arguments.");
+ return;
+ }
+
+ for (i = 0; i < nargs; i++) {
+ name = gctl_get_ascii(req, "arg%d", i);
+ error = g_metadata_clear(name, G_LABEL_MAGIC);
+ if (error != 0) {
+ fprintf(stderr, "Can't clear metadata on %s: %s.\n",
+ name, strerror(error));
+ gctl_error(req, "Not fully done.");
+ continue;
+ }
+ if (verbose)
+ printf("Metadata cleared on %s.\n", name);
+ }
+}
+
+static void
+label_metadata_dump(const struct g_label_metadata *md)
+{
+
+ printf(" Magic string: %s\n", md->md_magic);
+ printf("Metadata version: %u\n", (u_int)md->md_version);
+ printf(" Label: %s\n", md->md_label);
+}
+
+static void
+label_dump(struct gctl_req *req)
+{
+ struct g_label_metadata md, tmpmd;
+ const char *name;
+ int error, i, nargs;
+
+ nargs = gctl_get_int(req, "nargs");
+ if (nargs < 1) {
+ gctl_error(req, "Too few arguments.");
+ return;
+ }
+
+ for (i = 0; i < nargs; i++) {
+ name = gctl_get_ascii(req, "arg%d", i);
+ error = g_metadata_read(name, (u_char *)&tmpmd, sizeof(tmpmd),
+ G_LABEL_MAGIC);
+ if (error != 0) {
+ fprintf(stderr, "Can't read metadata from %s: %s.\n",
+ name, strerror(error));
+ gctl_error(req, "Not fully done.");
+ continue;
+ }
+ label_metadata_decode((u_char *)&tmpmd, &md);
+ printf("Metadata on %s:\n", name);
+ label_metadata_dump(&md);
+ printf("\n");
+ }
+}
diff --git a/sbin/geom/class/label/glabel.8 b/sbin/geom/class/label/glabel.8
new file mode 100644
index 0000000..a2f665e
--- /dev/null
+++ b/sbin/geom/class/label/glabel.8
@@ -0,0 +1,275 @@
+.\" Copyright (c) 2004-2005 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd April 22, 2013
+.Dt GLABEL 8
+.Os
+.Sh NAME
+.Nm glabel
+.Nd "disk labelization control utility"
+.Sh SYNOPSIS
+.Nm
+.Cm create
+.Op Fl v
+.Ar name
+.Ar dev
+.Nm
+.Cm destroy
+.Op Fl fv
+.Ar name ...
+.Nm
+.Cm label
+.Op Fl v
+.Ar name
+.Ar dev
+.Nm
+.Cm stop
+.Op Fl fv
+.Ar name ...
+.Nm
+.Cm clear
+.Op Fl v
+.Ar dev ...
+.Nm
+.Cm dump
+.Ar dev ...
+.Nm
+.Cm list
+.Nm
+.Cm status
+.Nm
+.Cm load
+.Nm
+.Cm unload
+.Sh DESCRIPTION
+The
+.Nm
+utility is used for GEOM provider labelization.
+A label can be set up on a GEOM provider in two ways:
+.Dq manual
+or
+.Dq automatic .
+When using the
+.Dq manual
+method, no metadata are stored on the devices, so a label has to be configured
+by hand every time it is needed.
+The
+.Dq automatic
+method uses on-disk metadata to store the label and detect it automatically in
+the future.
+.Pp
+This GEOM class also provides volume label detection for file systems.
+Those labels cannot be set with
+.Nm ,
+but must be set with the appropriate file system utility, e.g.\& for UFS
+the file system label is set with
+.Xr tunefs 8 .
+Currently supported file systems are:
+.Pp
+.Bl -bullet -offset indent -compact
+.It
+UFS1 volume names (directory
+.Pa /dev/ufs/ ) .
+.It
+UFS2 volume names (directory
+.Pa /dev/ufs/ ) .
+.It
+UFS1 file system IDs (directory
+.Pa /dev/ufsid/ ) .
+.It
+UFS2 file system IDs (directory
+.Pa /dev/ufsid/ ) .
+.It
+MSDOSFS (FAT12, FAT16, FAT32) (directory
+.Pa /dev/msdosfs/ ) .
+.It
+CD ISO9660 (directory
+.Pa /dev/iso9660/ ) .
+.It
+EXT2FS (directory
+.Pa /dev/ext2fs/ ) .
+.It
+REISERFS (directory
+.Pa /dev/reiserfs/ ) .
+.It
+NTFS (directory
+.Pa /dev/ntfs/ ) .
+.El
+.Pp
+Support for partition metadata is implemented for:
+.Pp
+.Bl -bullet -offset indent -compact
+.It
+GPT labels (directory
+.Pa /dev/gpt/ ) .
+.It
+GPT UUIDs (directory
+.Pa /dev/gptid/ ) .
+.El
+.Pp
+Generic disk ID strings are exported as labels in the format
+.Pa /dev/diskid/GEOM_CLASS-ident
+e.g.
+.Pa /dev/diskid/DISK-6QG3Z026 .
+.Pp
+Generic labels created and managed solely by
+.Xr glabel 8
+are created in the
+.Pa /dev/label/
+directory.
+.Pp
+Note that for all label types, nested GEOM classes will cause additional
+device nodes to be created, with context-specific data appended to their
+names. E.g. for every node like
+.Pa /dev/label/bigdisk
+there will be additional entries for any partitions which the device
+contains, like
+.Pa /dev/label/bigdiskp1
+and
+.Pa /dev/label/bigdiskp1a .
+.Pp
+The first argument to
+.Nm
+indicates an action to be performed:
+.Bl -tag -width ".Cm destroy"
+.It Cm create
+Create temporary label
+.Ar name
+for the given provider.
+This is the
+.Dq manual
+method.
+The kernel module
+.Pa geom_label.ko
+will be loaded if it is not loaded already.
+.It Cm label
+Set up a label
+.Ar name
+for the given provider.
+This is the
+.Dq automatic
+method, where metadata is stored in a provider's last sector.
+The kernel module
+.Pa geom_label.ko
+will be loaded if it is not loaded already.
+.It Cm stop
+Turn off the given label by its
+.Ar name .
+This command does not touch on-disk metadata!
+.It Cm destroy
+Same as
+.Cm stop .
+.It Cm clear
+Clear metadata on the given devices.
+.It Cm dump
+Dump metadata stored on the given devices.
+.It Cm list
+See
+.Xr geom 8 .
+.It Cm status
+See
+.Xr geom 8 .
+.It Cm load
+See
+.Xr geom 8 .
+.It Cm unload
+See
+.Xr geom 8 .
+.El
+.Pp
+Additional options:
+.Bl -tag -width indent
+.It Fl f
+Force the removal of the specified labels.
+.It Fl v
+Be more verbose.
+.El
+.Sh SYSCTL VARIABLES
+The following
+.Xr sysctl 8
+variables can be used to control the behavior of the
+.Nm LABEL
+GEOM class.
+The default value is shown next to each variable.
+.Bl -tag -width indent
+.It Va kern.geom.label.debug : No 0
+Debug level of the
+.Nm LABEL
+GEOM class.
+This can be set to a number between 0 and 2 inclusive.
+If set to 0 minimal debug information is printed, and if set to 2 the
+maximum amount of debug information is printed.
+.El
+.Bl -tag -width indent
+.It Va kern.geom.label.*.enable : No 1
+Most
+.Nm LABEL
+providers implement a
+.Xr sysctl 8
+flag and a tunable variable named in the above format. This flag
+controls if the label provider will be active, tasting devices
+and creating label nodes in the
+.Xr devfs 5
+tree. It is sometimes desirable to disable certain label types if
+they conflict with other classes in complex GEOM topologies.
+.El
+.Sh EXIT STATUS
+Exit status is 0 on success, and 1 if the command fails.
+.Sh EXAMPLES
+The following example shows how to set up a label for disk
+.Dq Li da2 ,
+create a file system on it, and mount it:
+.Bd -literal -offset indent
+glabel label -v usr /dev/da2
+newfs /dev/label/usr
+mount /dev/label/usr /usr
+[...]
+umount /usr
+glabel stop usr
+glabel unload
+.Ed
+.Pp
+The next example shows how to set up a label for a UFS file system:
+.Bd -literal -offset indent
+tunefs -L data /dev/da4s1a
+mount /dev/ufs/data /mnt/data
+.Ed
+.Sh SEE ALSO
+.Xr geom 4 ,
+.Xr loader.conf 5 ,
+.Xr geom 8 ,
+.Xr mount 8 ,
+.Xr newfs 8 ,
+.Xr sysctl 8 ,
+.Xr tunefs 8 ,
+.Xr umount 8
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Fx 5.3 .
+.Sh AUTHORS
+.An Pawel Jakub Dawidek Aq Mt pjd@FreeBSD.org
diff --git a/sbin/geom/class/mirror/Makefile b/sbin/geom/class/mirror/Makefile
new file mode 100644
index 0000000..ce7ee64
--- /dev/null
+++ b/sbin/geom/class/mirror/Makefile
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../../misc
+
+GEOM_CLASS= mirror
+
+LIBADD= md
+
+.include <bsd.lib.mk>
diff --git a/sbin/geom/class/mirror/geom_mirror.c b/sbin/geom/class/mirror/geom_mirror.c
new file mode 100644
index 0000000..30f5f84
--- /dev/null
+++ b/sbin/geom/class/mirror/geom_mirror.c
@@ -0,0 +1,487 @@
+/*-
+ * Copyright (c) 2004-2009 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <err.h>
+#include <errno.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <strings.h>
+#include <assert.h>
+#include <libgeom.h>
+#include <geom/mirror/g_mirror.h>
+#include <core/geom.h>
+#include <misc/subr.h>
+
+uint32_t lib_version = G_LIB_VERSION;
+uint32_t version = G_MIRROR_VERSION;
+
+#define GMIRROR_BALANCE "load"
+#define GMIRROR_SLICE "4096"
+#define GMIRROR_PRIORITY "0"
+
+static void mirror_main(struct gctl_req *req, unsigned flags);
+static void mirror_activate(struct gctl_req *req);
+static void mirror_clear(struct gctl_req *req);
+static void mirror_dump(struct gctl_req *req);
+static void mirror_label(struct gctl_req *req);
+static void mirror_resize(struct gctl_req *req, unsigned flags);
+
+struct g_command class_commands[] = {
+ { "activate", G_FLAG_VERBOSE, mirror_main, G_NULL_OPTS,
+ "[-v] name prov ..."
+ },
+ { "clear", G_FLAG_VERBOSE, mirror_main, G_NULL_OPTS,
+ "[-v] prov ..."
+ },
+ { "configure", G_FLAG_VERBOSE, NULL,
+ {
+ { 'a', "autosync", NULL, G_TYPE_BOOL },
+ { 'b', "balance", "", G_TYPE_STRING },
+ { 'd', "dynamic", NULL, G_TYPE_BOOL },
+ { 'f', "failsync", NULL, G_TYPE_BOOL },
+ { 'F', "nofailsync", NULL, G_TYPE_BOOL },
+ { 'h', "hardcode", NULL, G_TYPE_BOOL },
+ { 'n', "noautosync", NULL, G_TYPE_BOOL },
+ { 'p', "priority", "-1", G_TYPE_NUMBER },
+ { 's', "slice", "-1", G_TYPE_NUMBER },
+ G_OPT_SENTINEL
+ },
+ "[-adfFhnv] [-b balance] [-s slice] name\n"
+ "[-v] -p priority name prov"
+ },
+ { "deactivate", G_FLAG_VERBOSE, NULL, G_NULL_OPTS,
+ "[-v] name prov ..."
+ },
+ { "destroy", G_FLAG_VERBOSE, NULL,
+ {
+ { 'f', "force", NULL, G_TYPE_BOOL },
+ G_OPT_SENTINEL
+ },
+ "[-fv] name ..."
+ },
+ { "dump", 0, mirror_main, G_NULL_OPTS,
+ "prov ..."
+ },
+ { "forget", G_FLAG_VERBOSE, NULL, G_NULL_OPTS,
+ "name ..."
+ },
+ { "label", G_FLAG_VERBOSE, mirror_main,
+ {
+ { 'b', "balance", GMIRROR_BALANCE, G_TYPE_STRING },
+ { 'F', "nofailsync", NULL, G_TYPE_BOOL },
+ { 'h', "hardcode", NULL, G_TYPE_BOOL },
+ { 'n', "noautosync", NULL, G_TYPE_BOOL },
+ { 's', "slice", GMIRROR_SLICE, G_TYPE_NUMBER },
+ G_OPT_SENTINEL
+ },
+ "[-Fhnv] [-b balance] [-s slice] name prov ..."
+ },
+ { "insert", G_FLAG_VERBOSE, NULL,
+ {
+ { 'h', "hardcode", NULL, G_TYPE_BOOL },
+ { 'i', "inactive", NULL, G_TYPE_BOOL },
+ { 'p', "priority", GMIRROR_PRIORITY, G_TYPE_NUMBER },
+ G_OPT_SENTINEL
+ },
+ "[-hiv] [-p priority] name prov ..."
+ },
+ { "rebuild", G_FLAG_VERBOSE, NULL, G_NULL_OPTS,
+ "[-v] name prov ..."
+ },
+ { "remove", G_FLAG_VERBOSE, NULL, G_NULL_OPTS,
+ "[-v] name prov ..."
+ },
+ { "resize", G_FLAG_VERBOSE, mirror_resize,
+ {
+ { 's', "size", "*", G_TYPE_STRING },
+ G_OPT_SENTINEL
+ },
+ "[-s size] [-v] name"
+ },
+ { "stop", G_FLAG_VERBOSE, NULL,
+ {
+ { 'f', "force", NULL, G_TYPE_BOOL },
+ G_OPT_SENTINEL
+ },
+ "[-fv] name ..."
+ },
+ G_CMD_SENTINEL
+};
+
+static int verbose = 0;
+
+static void
+mirror_main(struct gctl_req *req, unsigned flags)
+{
+ const char *name;
+
+ if ((flags & G_FLAG_VERBOSE) != 0)
+ verbose = 1;
+
+ name = gctl_get_ascii(req, "verb");
+ if (name == NULL) {
+ gctl_error(req, "No '%s' argument.", "verb");
+ return;
+ }
+ if (strcmp(name, "label") == 0)
+ mirror_label(req);
+ else if (strcmp(name, "clear") == 0)
+ mirror_clear(req);
+ else if (strcmp(name, "dump") == 0)
+ mirror_dump(req);
+ else if (strcmp(name, "activate") == 0)
+ mirror_activate(req);
+ else
+ gctl_error(req, "Unknown command: %s.", name);
+}
+
+static void
+mirror_label(struct gctl_req *req)
+{
+ struct g_mirror_metadata md;
+ u_char sector[512];
+ const char *str;
+ unsigned sectorsize;
+ off_t mediasize;
+ intmax_t val;
+ int error, i, nargs, bal, hardcode;
+
+ nargs = gctl_get_int(req, "nargs");
+ if (nargs < 2) {
+ gctl_error(req, "Too few arguments.");
+ return;
+ }
+
+ strlcpy(md.md_magic, G_MIRROR_MAGIC, sizeof(md.md_magic));
+ md.md_version = G_MIRROR_VERSION;
+ str = gctl_get_ascii(req, "arg0");
+ strlcpy(md.md_name, str, sizeof(md.md_name));
+ md.md_mid = arc4random();
+ md.md_all = nargs - 1;
+ md.md_mflags = 0;
+ md.md_dflags = 0;
+ md.md_genid = 0;
+ md.md_syncid = 1;
+ md.md_sync_offset = 0;
+ val = gctl_get_intmax(req, "slice");
+ md.md_slice = val;
+ str = gctl_get_ascii(req, "balance");
+ bal = balance_id(str);
+ if (bal == -1) {
+ gctl_error(req, "Invalid balance algorithm.");
+ return;
+ }
+ md.md_balance = bal;
+ if (gctl_get_int(req, "noautosync"))
+ md.md_mflags |= G_MIRROR_DEVICE_FLAG_NOAUTOSYNC;
+ if (gctl_get_int(req, "nofailsync"))
+ md.md_mflags |= G_MIRROR_DEVICE_FLAG_NOFAILSYNC;
+ hardcode = gctl_get_int(req, "hardcode");
+
+ /*
+ * Calculate sectorsize by finding least common multiple from
+ * sectorsizes of every disk and find the smallest mediasize.
+ */
+ mediasize = 0;
+ sectorsize = 0;
+ for (i = 1; i < nargs; i++) {
+ unsigned ssize;
+ off_t msize;
+
+ str = gctl_get_ascii(req, "arg%d", i);
+ msize = g_get_mediasize(str);
+ ssize = g_get_sectorsize(str);
+ if (msize == 0 || ssize == 0) {
+ gctl_error(req, "Can't get informations about %s: %s.",
+ str, strerror(errno));
+ return;
+ }
+ msize -= ssize;
+ if (mediasize == 0 || (mediasize > 0 && msize < mediasize))
+ mediasize = msize;
+ if (sectorsize == 0)
+ sectorsize = ssize;
+ else
+ sectorsize = g_lcm(sectorsize, ssize);
+ }
+ md.md_mediasize = mediasize;
+ md.md_sectorsize = sectorsize;
+ md.md_mediasize -= (md.md_mediasize % md.md_sectorsize);
+
+ /*
+ * Clear last sector first, to spoil all components if device exists.
+ */
+ for (i = 1; i < nargs; i++) {
+ str = gctl_get_ascii(req, "arg%d", i);
+ error = g_metadata_clear(str, NULL);
+ if (error != 0) {
+ gctl_error(req, "Can't store metadata on %s: %s.", str,
+ strerror(error));
+ return;
+ }
+ }
+
+ /*
+ * Ok, store metadata (use disk number as priority).
+ */
+ for (i = 1; i < nargs; i++) {
+ str = gctl_get_ascii(req, "arg%d", i);
+ md.md_did = arc4random();
+ md.md_priority = i - 1;
+ md.md_provsize = g_get_mediasize(str);
+ assert(md.md_provsize != 0);
+ if (!hardcode)
+ bzero(md.md_provider, sizeof(md.md_provider));
+ else {
+ if (strncmp(str, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
+ str += sizeof(_PATH_DEV) - 1;
+ strlcpy(md.md_provider, str, sizeof(md.md_provider));
+ }
+ mirror_metadata_encode(&md, sector);
+ error = g_metadata_store(str, sector, sizeof(sector));
+ if (error != 0) {
+ fprintf(stderr, "Can't store metadata on %s: %s.\n",
+ str, strerror(error));
+ gctl_error(req, "Not fully done.");
+ continue;
+ }
+ if (verbose)
+ printf("Metadata value stored on %s.\n", str);
+ }
+}
+
+static void
+mirror_clear(struct gctl_req *req)
+{
+ const char *name;
+ int error, i, nargs;
+
+ nargs = gctl_get_int(req, "nargs");
+ if (nargs < 1) {
+ gctl_error(req, "Too few arguments.");
+ return;
+ }
+
+ for (i = 0; i < nargs; i++) {
+ name = gctl_get_ascii(req, "arg%d", i);
+ error = g_metadata_clear(name, G_MIRROR_MAGIC);
+ if (error != 0) {
+ fprintf(stderr, "Can't clear metadata on %s: %s.\n",
+ name, strerror(error));
+ gctl_error(req, "Not fully done.");
+ continue;
+ }
+ if (verbose)
+ printf("Metadata cleared on %s.\n", name);
+ }
+}
+
+static void
+mirror_dump(struct gctl_req *req)
+{
+ struct g_mirror_metadata md, tmpmd;
+ const char *name;
+ int error, i, nargs;
+
+ nargs = gctl_get_int(req, "nargs");
+ if (nargs < 1) {
+ gctl_error(req, "Too few arguments.");
+ return;
+ }
+
+ for (i = 0; i < nargs; i++) {
+ name = gctl_get_ascii(req, "arg%d", i);
+ error = g_metadata_read(name, (u_char *)&tmpmd, sizeof(tmpmd),
+ G_MIRROR_MAGIC);
+ if (error != 0) {
+ fprintf(stderr, "Can't read metadata from %s: %s.\n",
+ name, strerror(error));
+ gctl_error(req, "Not fully done.");
+ continue;
+ }
+ if (mirror_metadata_decode((u_char *)&tmpmd, &md) != 0) {
+ fprintf(stderr, "MD5 hash mismatch for %s, skipping.\n",
+ name);
+ gctl_error(req, "Not fully done.");
+ continue;
+ }
+ printf("Metadata on %s:\n", name);
+ mirror_metadata_dump(&md);
+ printf("\n");
+ }
+}
+
+static void
+mirror_activate(struct gctl_req *req)
+{
+ struct g_mirror_metadata md, tmpmd;
+ const char *name, *path;
+ int error, i, nargs;
+
+ nargs = gctl_get_int(req, "nargs");
+ if (nargs < 2) {
+ gctl_error(req, "Too few arguments.");
+ return;
+ }
+ name = gctl_get_ascii(req, "arg0");
+
+ for (i = 1; i < nargs; i++) {
+ path = gctl_get_ascii(req, "arg%d", i);
+ error = g_metadata_read(path, (u_char *)&tmpmd, sizeof(tmpmd),
+ G_MIRROR_MAGIC);
+ if (error != 0) {
+ fprintf(stderr, "Cannot read metadata from %s: %s.\n",
+ path, strerror(error));
+ gctl_error(req, "Not fully done.");
+ continue;
+ }
+ if (mirror_metadata_decode((u_char *)&tmpmd, &md) != 0) {
+ fprintf(stderr,
+ "MD5 hash mismatch for provider %s, skipping.\n",
+ path);
+ gctl_error(req, "Not fully done.");
+ continue;
+ }
+ if (strcmp(md.md_name, name) != 0) {
+ fprintf(stderr,
+ "Provider %s is not the mirror %s component.\n",
+ path, name);
+ gctl_error(req, "Not fully done.");
+ continue;
+ }
+ md.md_dflags &= ~G_MIRROR_DISK_FLAG_INACTIVE;
+ mirror_metadata_encode(&md, (u_char *)&tmpmd);
+ error = g_metadata_store(path, (u_char *)&tmpmd, sizeof(tmpmd));
+ if (error != 0) {
+ fprintf(stderr, "Cannot write metadata from %s: %s.\n",
+ path, strerror(error));
+ gctl_error(req, "Not fully done.");
+ continue;
+ }
+ if (verbose)
+ printf("Provider %s activated.\n", path);
+ }
+}
+
+static struct gclass *
+find_class(struct gmesh *mesh, const char *name)
+{
+ struct gclass *classp;
+
+ LIST_FOREACH(classp, &mesh->lg_class, lg_class) {
+ if (strcmp(classp->lg_name, name) == 0)
+ return (classp);
+ }
+ return (NULL);
+}
+
+static struct ggeom *
+find_geom(struct gclass *classp, const char *name)
+{
+ struct ggeom *gp;
+
+ LIST_FOREACH(gp, &classp->lg_geom, lg_geom) {
+ if (strcmp(gp->lg_name, name) == 0)
+ return (gp);
+ }
+ return (NULL);
+}
+
+static void
+mirror_resize(struct gctl_req *req, unsigned flags __unused)
+{
+ struct gmesh mesh;
+ struct gclass *classp;
+ struct ggeom *gp;
+ struct gprovider *pp;
+ struct gconsumer *cp;
+ off_t size;
+ int error, nargs;
+ const char *name;
+ char ssize[30];
+
+ nargs = gctl_get_int(req, "nargs");
+ if (nargs < 1) {
+ gctl_error(req, "Too few arguments.");
+ return;
+ }
+ error = geom_gettree(&mesh);
+ if (error)
+ errc(EXIT_FAILURE, error, "Cannot get GEOM tree");
+ name = gctl_get_ascii(req, "class");
+ if (name == NULL)
+ abort();
+ classp = find_class(&mesh, name);
+ if (classp == NULL)
+ errx(EXIT_FAILURE, "Class %s not found.", name);
+ name = gctl_get_ascii(req, "arg0");
+ if (name == NULL)
+ abort();
+ gp = find_geom(classp, name);
+ if (gp == NULL)
+ errx(EXIT_FAILURE, "No such geom: %s.", name);
+ pp = LIST_FIRST(&gp->lg_provider);
+ if (pp == NULL)
+ errx(EXIT_FAILURE, "Provider of geom %s not found.", name);
+ size = pp->lg_mediasize;
+ name = gctl_get_ascii(req, "size");
+ if (name == NULL)
+ errx(EXIT_FAILURE, "The size is not specified.");
+ if (*name == '*') {
+#define CSZ(c) ((c)->lg_provider->lg_mediasize - \
+ (c)->lg_provider->lg_sectorsize)
+ /* Find the maximum possible size */
+ LIST_FOREACH(cp, &gp->lg_consumer, lg_consumer) {
+ if (CSZ(cp) > size)
+ size = CSZ(cp);
+ }
+ LIST_FOREACH(cp, &gp->lg_consumer, lg_consumer) {
+ if (CSZ(cp) < size)
+ size = CSZ(cp);
+ }
+#undef CSZ
+ if (size == pp->lg_mediasize)
+ errx(EXIT_FAILURE,
+ "Cannot expand provider %s\n",
+ pp->lg_name);
+ } else {
+ error = g_parse_lba(name, pp->lg_sectorsize, &size);
+ if (error)
+ errc(EXIT_FAILURE, error, "Invalid size param");
+ size *= pp->lg_sectorsize;
+ }
+ snprintf(ssize, sizeof(ssize), "%ju", (uintmax_t)size);
+ gctl_change_param(req, "size", -1, ssize);
+ geom_deletetree(&mesh);
+ gctl_issue(req);
+}
diff --git a/sbin/geom/class/mirror/gmirror.8 b/sbin/geom/class/mirror/gmirror.8
new file mode 100644
index 0000000..fb501ca
--- /dev/null
+++ b/sbin/geom/class/mirror/gmirror.8
@@ -0,0 +1,387 @@
+.\" Copyright (c) 2004-2009 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd December 27, 2013
+.Dt GMIRROR 8
+.Os
+.Sh NAME
+.Nm gmirror
+.Nd "control utility for mirrored devices"
+.Sh SYNOPSIS
+.Nm
+.Cm label
+.Op Fl Fhnv
+.Op Fl b Ar balance
+.Op Fl s Ar slice
+.Ar name
+.Ar prov ...
+.Nm
+.Cm clear
+.Op Fl v
+.Ar prov ...
+.Nm
+.Cm configure
+.Op Fl adfFhnv
+.Op Fl b Ar balance
+.Op Fl s Ar slice
+.Ar name
+.Nm
+.Cm configure
+.Op Fl v
+.Fl p Ar priority
+.Ar name
+.Ar prov
+.Nm
+.Cm rebuild
+.Op Fl v
+.Ar name
+.Ar prov ...
+.Nm
+.Cm resize
+.Op Fl v
+.Op Fl s Ar size
+.Ar name
+.Nm
+.Cm insert
+.Op Fl hiv
+.Op Fl p Ar priority
+.Ar name
+.Ar prov ...
+.Nm
+.Cm remove
+.Op Fl v
+.Ar name
+.Ar prov ...
+.Nm
+.Cm activate
+.Op Fl v
+.Ar name
+.Ar prov ...
+.Nm
+.Cm deactivate
+.Op Fl v
+.Ar name
+.Ar prov ...
+.Nm
+.Cm destroy
+.Op Fl fv
+.Ar name ...
+.Nm
+.Cm forget
+.Op Fl v
+.Ar name ...
+.Nm
+.Cm stop
+.Op Fl fv
+.Ar name ...
+.Nm
+.Cm dump
+.Ar prov ...
+.Nm
+.Cm list
+.Nm
+.Cm status
+.Nm
+.Cm load
+.Nm
+.Cm unload
+.Sh DESCRIPTION
+The
+.Nm
+utility is used for mirror (RAID1) configurations.
+After a mirror's creation, all components are detected and configured
+automatically.
+All operations like failure detection, stale component detection, rebuild
+of stale components, etc.\& are also done automatically.
+The
+.Nm
+utility uses on-disk metadata (stored in the provider's last sector) to store all needed
+information.
+Since the last sector is used for this purpose, it is possible to place a root
+file system on a mirror.
+.Pp
+The first argument to
+.Nm
+indicates an action to be performed:
+.Bl -tag -width ".Cm deactivate"
+.It Cm label
+Create a mirror.
+The order of components is important, because a component's priority is based on its position
+(starting from 0 to 255).
+The component with the biggest priority is used by the
+.Cm prefer
+balance algorithm
+and is also used as a master component when resynchronization is needed,
+e.g.\& after a power failure when the device was open for writing.
+.Pp
+Additional options include:
+.Bl -tag -width ".Fl b Ar balance"
+.It Fl b Ar balance
+Specifies balance algorithm to use, one of:
+.Bl -tag -width ".Cm round-robin"
+.It Cm load
+Read from the component with the lowest load.
+This is the default balance algorithm.
+.It Cm prefer
+Read from the component with the biggest priority.
+.It Cm round-robin
+Use round-robin algorithm when choosing component to read.
+.It Cm split
+Split read requests, which are bigger than or equal to slice size on N pieces,
+where N is the number of active components.
+.El
+.It Fl F
+Do not synchronize after a power failure or system crash.
+Assumes device is in consistent state.
+.It Fl h
+Hardcode providers' names in metadata.
+.It Fl n
+Turn off autosynchronization of stale components.
+.It Fl s Ar slice
+When using the
+.Cm split
+balance algorithm and an I/O READ request is bigger than or equal to this value,
+the I/O request will be split into N pieces, where N is the number of active
+components.
+Defaults to 4096 bytes.
+.El
+.It Cm clear
+Clear metadata on the given providers.
+.It Cm configure
+Configure the given device.
+.Pp
+Additional options include:
+.Bl -tag -width ".Fl p Ar priority"
+.It Fl a
+Turn on autosynchronization of stale components.
+.It Fl b Ar balance
+Specifies balance algorithm to use.
+.It Fl d
+Do not hardcode providers' names in metadata.
+.It Fl f
+Synchronize device after a power failure or system crash.
+.It Fl F
+Do not synchronize after a power failure or system crash.
+Assumes device is in consistent state.
+.It Fl h
+Hardcode providers' names in metadata.
+.It Fl n
+Turn off autosynchronization of stale components.
+.It Fl p Ar priority
+Specifies priority for the given component
+.Ar prov .
+.It Fl s Ar slice
+Specifies slice size for
+.Cm split
+balance algorithm.
+.El
+.It Cm rebuild
+Rebuild the given mirror components forcibly.
+If autosynchronization was not turned off for the given device, this command
+should be unnecessary.
+.It Cm resize
+Change the size of the given mirror.
+.Pp
+Additional options include:
+.Bl -tag -width ".Fl s Ar size"
+.It Fl s Ar size
+New size of the mirror is expressed in logical block numbers.
+This option can be omitted, then it will be automatically calculated to
+maximum available size.
+.El
+.It Cm insert
+Add the given component(s) to the existing mirror.
+.Pp
+Additional options include:
+.Bl -tag -width ".Fl p Ar priority"
+.It Fl h
+Hardcode providers' names in metadata.
+.It Fl i
+Mark component(s) as inactive immediately after insertion.
+.It Fl p Ar priority
+Specifies priority of the given component(s).
+.El
+.It Cm remove
+Remove the given component(s) from the mirror and clear metadata on it.
+.It Cm activate
+Activate the given component(s), which were marked as inactive before.
+.It Cm deactivate
+Mark the given component(s) as inactive, so it will not be automatically
+connected to the mirror.
+.It Cm destroy
+Stop the given mirror and clear metadata on all its components.
+.Pp
+Additional options include:
+.Bl -tag -width ".Fl f"
+.It Fl f
+Stop the given mirror even if it is opened.
+.El
+.It Cm forget
+Forget about components which are not connected.
+This command is useful when a disk has failed and cannot be reconnected, preventing the
+.Cm remove
+command from being used to remove it.
+.It Cm stop
+Stop the given mirror.
+.Pp
+Additional options include:
+.Bl -tag -width ".Fl f"
+.It Fl f
+Stop the given mirror even if it is opened.
+.El
+.It Cm dump
+Dump metadata stored on the given providers.
+.It Cm list
+See
+.Xr geom 8 .
+.It Cm status
+See
+.Xr geom 8 .
+.It Cm load
+See
+.Xr geom 8 .
+.It Cm unload
+See
+.Xr geom 8 .
+.El
+.Pp
+Additional options include:
+.Bl -tag -width ".Fl v"
+.It Fl v
+Be more verbose.
+.El
+.Sh EXIT STATUS
+Exit status is 0 on success, and 1 if the command fails.
+.Sh EXAMPLES
+Use 3 disks to setup a mirror.
+Choose split balance algorithm, split only
+requests which are bigger than or equal to 2kB.
+Create file system,
+mount it, then unmount it and stop device:
+.Bd -literal -offset indent
+gmirror label -v -b split -s 2048 data da0 da1 da2
+newfs /dev/mirror/data
+mount /dev/mirror/data /mnt
+\&...
+umount /mnt
+gmirror stop data
+gmirror unload
+.Ed
+.Pp
+Create a mirror on disk with valid data (note that the last sector of the disk
+will be overwritten).
+Add another disk to this mirror,
+so it will be synchronized with existing disk:
+.Bd -literal -offset indent
+gmirror label -v -b round-robin data da0
+gmirror insert data da1
+.Ed
+.Pp
+Create a mirror, but do not use automatic synchronization feature.
+Add another disk and rebuild it:
+.Bd -literal -offset indent
+gmirror label -v -n -b load data da0 da1
+gmirror insert data da2
+gmirror rebuild data da2
+.Ed
+.Pp
+One disk failed.
+Replace it with a brand new one:
+.Bd -literal -offset indent
+gmirror forget data
+gmirror insert data da1
+.Ed
+.Pp
+Create a mirror, deactivate one component, do the backup and connect it again.
+It will not be resynchronized, if there is no need to do so (there were no writes in
+the meantime):
+.Bd -literal -offset indent
+gmirror label data da0 da1
+gmirror deactivate data da1
+dd if=/dev/da1 of=/backup/data.img bs=1m
+gmirror activate data da1
+.Ed
+.Sh NOTES
+Doing kernel dumps to
+.Nm
+providers is possible, but some conditions have to be met.
+First of all, a kernel dump will go only to one component and
+.Nm
+always chooses the component with the highest priority.
+Reading a dump from the mirror on boot will only work if the
+.Cm prefer
+balance algorithm is used (that way
+.Nm
+will read only from the component with the highest priority).
+If you use a different balance algorithm, you should add:
+.Bd -literal -offset indent
+gmirror configure -b prefer data
+.Ed
+.Pp
+to the
+.Pa /etc/rc.early
+script and:
+.Bd -literal -offset indent
+gmirror configure -b round-robin data
+.Ed
+.Pp
+to the
+.Pa /etc/rc.local
+script.
+The decision which component to choose for dumping is made when
+.Xr dumpon 8
+is called.
+If on the next boot a component with a higher priority will be available,
+the prefer algorithm will choose to read from it and
+.Xr savecore 8
+will find nothing.
+If on the next boot a component with the highest priority will be synchronized,
+the prefer balance algorithm will read from the next one, thus will find nothing
+there.
+.Sh SEE ALSO
+.Xr geom 4 ,
+.Xr dumpon 8 ,
+.Xr geom 8 ,
+.Xr gvinum 8 ,
+.Xr mount 8 ,
+.Xr newfs 8 ,
+.Xr savecore 8 ,
+.Xr umount 8
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Fx 5.3 .
+.Sh AUTHORS
+.An Pawel Jakub Dawidek Aq Mt pjd@FreeBSD.org
+.Sh BUGS
+There should be a way to change a component's priority inside a running mirror.
+.Pp
+There should be a section with an implementation description.
+.Pp
+Documentation for sysctls
+.Va kern.geom.mirror.*
+is missing.
diff --git a/sbin/geom/class/mountver/Makefile b/sbin/geom/class/mountver/Makefile
new file mode 100644
index 0000000..750d1a5
--- /dev/null
+++ b/sbin/geom/class/mountver/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../../misc
+
+GEOM_CLASS= mountver
+
+.include <bsd.lib.mk>
diff --git a/sbin/geom/class/mountver/geom_mountver.c b/sbin/geom/class/mountver/geom_mountver.c
new file mode 100644
index 0000000..a1ed95d
--- /dev/null
+++ b/sbin/geom/class/mountver/geom_mountver.c
@@ -0,0 +1,56 @@
+/*-
+ * Copyright (c) 2010 Edward Tomasz Napierala <trasz@FreeBSD.org>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <stdio.h>
+#include <stdint.h>
+#include <libgeom.h>
+#include <geom/mountver/g_mountver.h>
+
+#include "core/geom.h"
+
+
+uint32_t lib_version = G_LIB_VERSION;
+uint32_t version = G_MOUNTVER_VERSION;
+
+struct g_command class_commands[] = {
+ { "create", G_FLAG_VERBOSE | G_FLAG_LOADKLD, NULL,
+ {
+ G_OPT_SENTINEL
+ },
+ "[-v] dev ..."
+ },
+ { "destroy", G_FLAG_VERBOSE, NULL,
+ {
+ { 'f', "force", NULL, G_TYPE_BOOL },
+ G_OPT_SENTINEL
+ },
+ "[-fv] prov ..."
+ },
+ G_CMD_SENTINEL
+};
diff --git a/sbin/geom/class/mountver/gmountver.8 b/sbin/geom/class/mountver/gmountver.8
new file mode 100644
index 0000000..44736d5
--- /dev/null
+++ b/sbin/geom/class/mountver/gmountver.8
@@ -0,0 +1,130 @@
+.\"-
+.\" Copyright (c) 2010 Edward Tomasz Napierala
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd January 27, 2015
+.Dt GMOUNTVER 8
+.Os
+.Sh NAME
+.Nm gmountver
+.Nd "control utility for disk mount verification GEOM class"
+.Sh SYNOPSIS
+.Nm
+.Cm create
+.Op Fl v
+.Ar dev ...
+.Nm
+.Cm destroy
+.Op Fl fv
+.Ar prov ...
+.Nm
+.Cm list
+.Nm
+.Cm status
+.Op Fl s Ar name
+.Nm
+.Cm load
+.Op Fl v
+.Nm
+.Cm unload
+.Op Fl v
+.Sh DESCRIPTION
+The
+.Nm
+utility is used to control the mount verification GEOM class.
+When configured, it passes all the I/O requests to the underlying provider.
+When the underlying provider disappears - for example because the disk device
+got disconnected - it queues all the I/O requests and waits for the provider
+to reappear.
+When that happens, it attaches to it and sends the queued requests.
+.Pp
+The first argument to
+.Nm
+indicates an action to be performed:
+.Bl -tag -width ".Cm destroy"
+.It Cm create
+Cache the given devices with specified
+.Ar name .
+The kernel module
+.Pa geom_mountver.ko
+will be loaded if it is not loaded already.
+.It Cm destroy
+Destroy
+.Ar name .
+.It Cm list
+See
+.Xr geom 8 .
+.It Cm status
+See
+.Xr geom 8 .
+.It Cm load
+See
+.Xr geom 8 .
+.It Cm unload
+See
+.Xr geom 8 .
+.El
+.Pp
+Additional options:
+.Bl -tag -width indent
+.It Fl f
+Force the removal of the specified mountver device.
+.It Fl v
+Be more verbose.
+.El
+.Sh SYSCTL VARIABLES
+The following
+.Xr sysctl 8
+variables can be used to control the behavior of the
+.Nm MOUNTVER
+GEOM class.
+The default value is shown next to each variable.
+.Bl -tag -width indent
+.It Va kern.geom.mountver.debug : No 0
+Debug level of the
+.Nm MOUNTVER
+GEOM class.
+This can be set to a number between 0 and 3 inclusive.
+If set to 0 minimal debug information is printed, and if set to 3 the
+maximum amount of debug information is printed.
+.It Va kern.geom.mountver.check_ident : No 1
+This can be set to 0 or 1.
+If set to 0,
+.Nm
+will reattach to the device even if the device reports a different disk ID.
+.El
+.Sh EXIT STATUS
+Exit status is 0 on success, and 1 if the command fails.
+.Sh SEE ALSO
+.Xr geom 4 ,
+.Xr geom 8
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Fx 9.0 .
+.Sh AUTHORS
+.An Edward Tomasz Napierala Aq Mt trasz@FreeBSD.org
diff --git a/sbin/geom/class/multipath/Makefile b/sbin/geom/class/multipath/Makefile
new file mode 100644
index 0000000..ff40fe9
--- /dev/null
+++ b/sbin/geom/class/multipath/Makefile
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../../misc
+
+GEOM_CLASS= multipath
+
+CFLAGS+= -I${.CURDIR}/../../../../sys
+
+.include <bsd.lib.mk>
diff --git a/sbin/geom/class/multipath/geom_multipath.c b/sbin/geom/class/multipath/geom_multipath.c
new file mode 100644
index 0000000..cdf35d0
--- /dev/null
+++ b/sbin/geom/class/multipath/geom_multipath.c
@@ -0,0 +1,322 @@
+/*-
+ * Copyright (c) 2006 Mathew Jacob <mjacob@FreeBSD.org>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+#include <sys/param.h>
+#include <errno.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <strings.h>
+#include <assert.h>
+#include <libgeom.h>
+#include <unistd.h>
+#include <uuid.h>
+#include <geom/multipath/g_multipath.h>
+
+#include "core/geom.h"
+#include "misc/subr.h"
+
+uint32_t lib_version = G_LIB_VERSION;
+uint32_t version = G_MULTIPATH_VERSION;
+
+static void mp_main(struct gctl_req *, unsigned int);
+static void mp_label(struct gctl_req *);
+static void mp_clear(struct gctl_req *);
+static void mp_prefer(struct gctl_req *);
+
+struct g_command class_commands[] = {
+ {
+ "create", G_FLAG_VERBOSE | G_FLAG_LOADKLD, NULL,
+ {
+ { 'A', "active_active", NULL, G_TYPE_BOOL },
+ { 'R', "active_read", NULL, G_TYPE_BOOL },
+ G_OPT_SENTINEL
+ },
+ "[-vAR] name prov ..."
+ },
+ {
+ "label", G_FLAG_VERBOSE | G_FLAG_LOADKLD, mp_main,
+ {
+ { 'A', "active_active", NULL, G_TYPE_BOOL },
+ { 'R', "active_read", NULL, G_TYPE_BOOL },
+ G_OPT_SENTINEL
+ },
+ "[-vAR] name prov ..."
+ },
+ { "configure", G_FLAG_VERBOSE, NULL,
+ {
+ { 'A', "active_active", NULL, G_TYPE_BOOL },
+ { 'P', "active_passive", NULL, G_TYPE_BOOL },
+ { 'R', "active_read", NULL, G_TYPE_BOOL },
+ G_OPT_SENTINEL
+ },
+ "[-vAPR] name"
+ },
+ {
+ "add", G_FLAG_VERBOSE, NULL, G_NULL_OPTS,
+ "[-v] name prov"
+ },
+ {
+ "remove", G_FLAG_VERBOSE, NULL, G_NULL_OPTS,
+ "[-v] name prov"
+ },
+ {
+ "prefer", G_FLAG_VERBOSE, mp_main, G_NULL_OPTS,
+ "[-v] prov ..."
+ },
+ {
+ "fail", G_FLAG_VERBOSE, NULL, G_NULL_OPTS,
+ "[-v] name prov"
+ },
+ {
+ "restore", G_FLAG_VERBOSE, NULL, G_NULL_OPTS,
+ "[-v] name prov"
+ },
+ {
+ "rotate", G_FLAG_VERBOSE, NULL, G_NULL_OPTS,
+ "[-v] name"
+ },
+ {
+ "getactive", G_FLAG_VERBOSE, NULL, G_NULL_OPTS,
+ "[-v] name"
+ },
+ {
+ "destroy", G_FLAG_VERBOSE, NULL, G_NULL_OPTS,
+ "[-v] name"
+ },
+ {
+ "stop", G_FLAG_VERBOSE, NULL, G_NULL_OPTS,
+ "[-v] name"
+ },
+ {
+ "clear", G_FLAG_VERBOSE, mp_main, G_NULL_OPTS,
+ "[-v] prov ..."
+ },
+ G_CMD_SENTINEL
+};
+
+static void
+mp_main(struct gctl_req *req, unsigned int flags __unused)
+{
+ const char *name;
+
+ name = gctl_get_ascii(req, "verb");
+ if (name == NULL) {
+ gctl_error(req, "No '%s' argument.", "verb");
+ return;
+ }
+ if (strcmp(name, "label") == 0) {
+ mp_label(req);
+ } else if (strcmp(name, "clear") == 0) {
+ mp_clear(req);
+ } else if (strcmp(name, "prefer") == 0) {
+ mp_prefer(req);
+ } else {
+ gctl_error(req, "Unknown command: %s.", name);
+ }
+}
+
+static void
+mp_label(struct gctl_req *req)
+{
+ struct g_multipath_metadata md;
+ off_t disksize = 0, msize;
+ uint8_t *sector, *rsector;
+ char *ptr;
+ uuid_t uuid;
+ ssize_t secsize = 0, ssize;
+ uint32_t status;
+ const char *name, *name2, *mpname;
+ int error, i, nargs, fd;
+
+ nargs = gctl_get_int(req, "nargs");
+ if (nargs < 2) {
+ gctl_error(req, "wrong number of arguments.");
+ return;
+ }
+
+ /*
+ * First, check each provider to make sure it's the same size.
+ * This also gets us our size and sectorsize for the metadata.
+ */
+ for (i = 1; i < nargs; i++) {
+ name = gctl_get_ascii(req, "arg%d", i);
+ msize = g_get_mediasize(name);
+ ssize = g_get_sectorsize(name);
+ if (msize == 0 || ssize == 0) {
+ gctl_error(req, "cannot get information about %s: %s.",
+ name, strerror(errno));
+ return;
+ }
+ if (i == 1) {
+ secsize = ssize;
+ disksize = msize;
+ } else {
+ if (secsize != ssize) {
+ gctl_error(req, "%s sector size %ju different.",
+ name, (intmax_t)ssize);
+ return;
+ }
+ if (disksize != msize) {
+ gctl_error(req, "%s media size %ju different.",
+ name, (intmax_t)msize);
+ return;
+ }
+ }
+
+ }
+
+ /*
+ * Generate metadata.
+ */
+ strlcpy(md.md_magic, G_MULTIPATH_MAGIC, sizeof(md.md_magic));
+ md.md_version = G_MULTIPATH_VERSION;
+ mpname = gctl_get_ascii(req, "arg0");
+ strlcpy(md.md_name, mpname, sizeof(md.md_name));
+ md.md_size = disksize;
+ md.md_sectorsize = secsize;
+ uuid_create(&uuid, &status);
+ if (status != uuid_s_ok) {
+ gctl_error(req, "cannot create a UUID.");
+ return;
+ }
+ uuid_to_string(&uuid, &ptr, &status);
+ if (status != uuid_s_ok) {
+ gctl_error(req, "cannot stringify a UUID.");
+ return;
+ }
+ strlcpy(md.md_uuid, ptr, sizeof (md.md_uuid));
+ md.md_active_active = gctl_get_int(req, "active_active");
+ if (gctl_get_int(req, "active_read"))
+ md.md_active_active = 2;
+ free(ptr);
+
+ /*
+ * Allocate a sector to write as metadata.
+ */
+ sector = malloc(secsize);
+ if (sector == NULL) {
+ gctl_error(req, "unable to allocate metadata buffer");
+ return;
+ }
+ memset(sector, 0, secsize);
+ rsector = malloc(secsize);
+ if (rsector == NULL) {
+ free(sector);
+ gctl_error(req, "unable to allocate metadata buffer");
+ return;
+ }
+
+ /*
+ * encode the metadata
+ */
+ multipath_metadata_encode(&md, sector);
+
+ /*
+ * Store metadata on the initial provider.
+ */
+ name = gctl_get_ascii(req, "arg1");
+ error = g_metadata_store(name, sector, secsize);
+ if (error != 0) {
+ gctl_error(req, "cannot store metadata on %s: %s.", name, strerror(error));
+ return;
+ }
+
+ /*
+ * Now touch the rest of the providers to hint retaste.
+ */
+ for (i = 2; i < nargs; i++) {
+ name2 = gctl_get_ascii(req, "arg%d", i);
+ fd = g_open(name2, 1);
+ if (fd < 0) {
+ fprintf(stderr, "Unable to open %s: %s.\n",
+ name2, strerror(errno));
+ continue;
+ }
+ if (pread(fd, rsector, secsize, disksize - secsize) !=
+ (ssize_t)secsize) {
+ fprintf(stderr, "Unable to read metadata from %s: %s.\n",
+ name2, strerror(errno));
+ g_close(fd);
+ continue;
+ }
+ g_close(fd);
+ if (memcmp(sector, rsector, secsize)) {
+ fprintf(stderr, "No metadata found on %s."
+ " It is not a path of %s.\n",
+ name2, name);
+ }
+ }
+}
+
+
+static void
+mp_clear(struct gctl_req *req)
+{
+ const char *name;
+ int error, i, nargs;
+
+ nargs = gctl_get_int(req, "nargs");
+ if (nargs < 1) {
+ gctl_error(req, "Too few arguments.");
+ return;
+ }
+
+ for (i = 0; i < nargs; i++) {
+ name = gctl_get_ascii(req, "arg%d", i);
+ error = g_metadata_clear(name, G_MULTIPATH_MAGIC);
+ if (error != 0) {
+ fprintf(stderr, "Can't clear metadata on %s: %s.\n",
+ name, strerror(error));
+ gctl_error(req, "Not fully done.");
+ continue;
+ }
+ }
+}
+
+static void
+mp_prefer(struct gctl_req *req)
+{
+ const char *name, *comp, *errstr;
+ int nargs;
+
+ nargs = gctl_get_int(req, "nargs");
+ if (nargs != 2) {
+ gctl_error(req, "Usage: prefer GEOM PROVIDER");
+ return;
+ }
+ name = gctl_get_ascii(req, "arg0");
+ comp = gctl_get_ascii(req, "arg1");
+ errstr = gctl_issue (req);
+ if (errstr != NULL) {
+ fprintf(stderr, "Can't set %s preferred provider to %s: %s.\n",
+ name, comp, errstr);
+ }
+}
diff --git a/sbin/geom/class/multipath/gmultipath.8 b/sbin/geom/class/multipath/gmultipath.8
new file mode 100644
index 0000000..07ac01e
--- /dev/null
+++ b/sbin/geom/class/multipath/gmultipath.8
@@ -0,0 +1,365 @@
+.\" Copyright (c) 2007 Matthew Jacob
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd April 18, 2012
+.Dt GMULTIPATH 8
+.Os
+.Sh NAME
+.Nm gmultipath
+.Nd "disk multipath control utility"
+.Sh SYNOPSIS
+.Nm
+.Cm create
+.Op Fl ARv
+.Ar name
+.Ar prov ...
+.Nm
+.Cm label
+.Op Fl ARv
+.Ar name
+.Ar prov ...
+.Nm
+.Cm configure
+.Op Fl APRv
+.Ar name
+.Nm
+.Cm add
+.Op Fl v
+.Ar name prov
+.Nm
+.Cm remove
+.Op Fl v
+.Ar name prov
+.Nm
+.Cm fail
+.Op Fl v
+.Ar name prov
+.Nm
+.Cm restore
+.Op Fl v
+.Ar name prov
+.Nm
+.Cm rotate
+.Op Fl v
+.Ar name
+.Nm
+.Cm prefer
+.Op Fl v
+.Ar name
+.Ar prov
+.Nm
+.Cm getactive
+.Op Fl v
+.Ar name
+.Nm
+.Cm destroy
+.Op Fl v
+.Ar name
+.Nm
+.Cm stop
+.Op Fl v
+.Ar name
+.Nm
+.Cm clear
+.Op Fl v
+.Ar prov ...
+.Nm
+.Cm list
+.Nm
+.Cm status
+.Nm
+.Cm load
+.Nm
+.Cm unload
+.Sh DESCRIPTION
+The
+.Nm
+utility is used for device multipath configuration.
+.Pp
+The multipath device can be configured using two different methods:
+.Dq manual
+or
+.Dq automatic .
+When using the
+.Dq manual
+method, no metadata are stored on the devices, so the multipath
+device has to be configured by hand every time it is needed.
+Additional device paths also won't be detected automatically.
+The
+.Dq automatic
+method uses on-disk metadata to detect device and all it's paths.
+Metadata use the last sector of the underlying disk device and
+include device name and UUID.
+The UUID guarantees uniqueness in a shared storage environment
+but is in general too cumbersome to use.
+The name is what is exported via the device interface.
+.Pp
+The first argument to
+.Nm
+indicates an action to be performed:
+.Bl -tag -width ".Cm destroy"
+.It Cm create
+Create multipath device with
+.Dq manual
+method without writing any on-disk metadata.
+It is up to administrator, how to properly identify device paths.
+Kernel will only check that all given providers have same media and
+sector sizes.
+.Pp
+.Fl A
+option enables Active/Active mode,
+.Fl R
+option enables Active/Read mode, otherwise Active/Passive mode is used
+by default.
+.It Cm label
+Create multipath device with
+.Dq automatic
+method.
+Label the first given provider with on-disk metadata using the specified
+.Ar name .
+The rest of given providers will be retasted to detect these metadata.
+It reliably protects against specifying unrelated providers.
+Providers with no matching metadata detected will not be added to the device.
+.Pp
+.Fl A
+option enables Active/Active mode,
+.Fl R
+option enables Active/Read mode, otherwise Active/Passive mode is used
+by default.
+.It Cm configure
+Configure the given multipath device.
+.Pp
+.Fl A
+option enables Active/Active mode,
+.Fl P
+option enables Active/Passive mode,
+.Fl R
+option enables Active/Read mode.
+.It Cm add
+Add the given provider as a path to the given multipath device.
+Should normally be used only for devices created with
+.Dq manual
+method, unless you know what you are doing (you are sure that it is another
+device path, but tasting its metadata in regular
+.Dq automatic
+way is not possible).
+.It Cm remove
+Remove the given provider as a path from the given multipath device.
+If the last path removed, the multipath device will be destroyed.
+.It Cm fail
+Mark specified provider as a path of the specified multipath device as failed.
+If there are other paths present, new requests will be forwarded there.
+.It Cm restore
+Mark specified provider as a path of the specified multipath device as
+operational, allowing it to handle requests.
+.It Cm rotate
+Change the active provider/path to the next available provider in Active/Passive mode.
+.It Cm prefer
+Change the active provider/path to the specified provider in Active/Passive mode.
+.It Cm getactive
+Get the currently active provider(s)/path(s).
+.It Cm destroy
+Destroy the given multipath device clearing metadata.
+.It Cm stop
+Stop the given multipath device without clearing metadata.
+.It Cm clear
+Clear metadata on the given provider.
+.It Cm list
+See
+.Xr geom 8 .
+.It Cm status
+See
+.Xr geom 8 .
+.It Cm load
+See
+.Xr geom 8 .
+.It Cm unload
+See
+.Xr geom 8 .
+.El
+.Sh SYSCTL VARIABLES
+The following
+.Xr sysctl 8
+variable can be used to control the behavior of the
+.Nm MULTIPATH
+GEOM class.
+.Bl -tag -width indent
+.It Va kern.geom.multipath.debug : No 0
+Debug level of the
+.Nm MULTIPATH
+GEOM class.
+This can be set to 0 (default) or 1 to disable or enable various
+forms of chattiness.
+.It Va kern.geom.multipath.exclusive : No 1
+Open underlying providers exclusively, preventing individual paths access.
+.El
+.Sh EXIT STATUS
+Exit status is 0 on success, and 1 if the command fails.
+.Sh MULTIPATH ARCHITECTURE
+This is a multiple path architecture with no device knowledge or
+presumptions other than size matching built in.
+Therefore the user must exercise some care
+in selecting providers that do indeed represent multiple paths to the
+same underlying disk device.
+The reason for this is that there are several
+criteria across multiple underlying transport types that can
+.Ar indicate
+identity, but in all respects such identity can rarely be considered
+.Ar definitive .
+.Pp
+For example, if you use the World Word Port Name of a Fibre Channel
+disk object you might believe that two disks that have the same WWPN
+on different paths (or even disjoint fabrics) might be considered
+the same disk.
+Nearly always this would be a safe assumption, until
+you realize that a WWPN, like an Ethernet MAC address, is a soft
+programmable entity, and that a misconfigured Director Class switch
+could lead you to believe incorrectly that you have found multiple
+paths to the same device.
+This is an extreme and theoretical case, but
+it is possible enough to indicate that the policy for deciding which
+of multiple pathnames refer to the same device should be left to the
+system operator who will use tools and knowledge of their own storage
+subsystem to make the correct configuration selection.
+.Pp
+There are Active/Passive, Active/Read and Active/Active operation modes
+supported.
+In Active/Passive mode only one path has I/O moving on it
+at any point in time.
+This I/O continues until an I/O is returned with
+a generic I/O error or a "Nonexistent Device" error.
+When this occurs, that path is marked FAIL, the next path
+in a list is selected as active and the failed I/O reissued.
+In Active/Active mode all paths not marked FAIL may handle I/O same time.
+Requests are distributed between paths to equalize load.
+For capable devices it allows to utilize bandwidth of all paths.
+In Active/Read mode all paths not marked FAIL may handle reads same time,
+but unlike Active/Active only one path handles write requests at any
+point in time.
+It allows to closer follow original write request order if above layer
+needs it for data consistency (not waiting for requisite write completion
+before sending dependent write).
+.Pp
+When new devices are added to the system the
+.Nm MULTIPATH
+GEOM class is given an opportunity to taste these new devices.
+If a new
+device has a
+.Nm MULTIPATH
+on-disk metadata label, the device is used to either create a new
+.Nm MULTIPATH
+GEOM, or been added the list of paths for an existing
+.Nm MULTIPATH
+GEOM.
+.Pp
+It is this mechanism that works reasonably with
+.Xr isp 4
+and
+.Xr mpt 4
+based Fibre Channel disk devices.
+For these devices, when a device disappears
+(due e.g., to a cable pull or power failure to a switch), the device is
+proactively marked as gone and I/O to it failed.
+This causes the
+.Nm MULTIPATH
+failure event just described.
+.Pp
+When Fibre Channel events inform either
+.Xr isp 4
+or
+.Xr mpt 4
+host bus adapters that new devices may have arrived (e.g., the arrival
+of an RSCN event from the Fabric Domain Controller), they can cause
+a rescan to occur and cause the attachment and configuration of any
+(now) new devices to occur, causing the taste event described above.
+.Pp
+This means that this multipath architecture is not a one-shot path
+failover, but can be considered to be steady state as long as failed
+paths are repaired (automatically or otherwise).
+.Pp
+Automatic rescanning is not a requirement.
+Nor is Fibre Channel.
+The
+same failover mechanisms work equally well for traditional "Parallel"
+SCSI but may require manual intervention with
+.Xr camcontrol 8
+to cause the reattachment of repaired device links.
+.Sh EXAMPLES
+The following example shows how to use
+.Xr camcontrol 8
+to find possible multiple path devices and to create a
+.Nm MULTIPATH
+GEOM class for them.
+.Bd -literal -offset indent
+mysys# camcontrol devlist
+<ECNCTX @WESTVILLE > at scbus0 target 0 lun 0 (da0,pass0)
+<ECNCTX @WESTVILLE > at scbus0 target 0 lun 1 (da1,pass1)
+<ECNCTX @WESTVILLE > at scbus1 target 0 lun 0 (da2,pass2)
+<ECNCTX @WESTVILLE > at scbus1 target 0 lun 1 (da3,pass3)
+mysys# camcontrol inquiry da0 -S
+ECNTX0LUN000000SER10ac0d01
+mysys# camcontrol inquiry da2 -S
+ECNTX0LUN000000SER10ac0d01
+.Ed
+.Pp
+Now that you have used the Serial Number to compare two disk paths
+it is not entirely unreasonable to conclude that these are multiple
+paths to the same device.
+However, only the user who is familiar
+with their storage is qualified to make this judgement.
+.Pp
+You can then use the
+.Nm
+command to label and create a
+.Nm MULTIPATH
+GEOM provider named
+.Ar FRED .
+.Bd -literal -offset indent
+gmultipath label -v FRED /dev/da0 /dev/da2
+disklabel -Brw /dev/multipath/FRED auto
+newfs /dev/multipath/FREDa
+mount /dev/multipath/FREDa /mnt....
+.Ed
+.Pp
+The resultant console output looks something like:
+.Bd -literal -offset indent
+GEOM_MULTIPATH: da0 added to FRED
+GEOM_MULTIPATH: da0 is now active path in FRED
+GEOM_MULTIPATH: da2 added to FRED
+.Ed
+.Sh SEE ALSO
+.Xr geom 4 ,
+.Xr isp 4 ,
+.Xr mpt 4 ,
+.Xr loader.conf 5 ,
+.Xr camcontrol 8 ,
+.Xr geom 8 ,
+.Xr mount 8 ,
+.Xr newfs 8 ,
+.Xr sysctl 8
+.Sh AUTHORS
+.An Matthew Jacob Aq Mt mjacob@FreeBSD.org
+.An Alexander Motin Aq Mt mav@FreeBSD.org
diff --git a/sbin/geom/class/nop/Makefile b/sbin/geom/class/nop/Makefile
new file mode 100644
index 0000000..d9a6ca6
--- /dev/null
+++ b/sbin/geom/class/nop/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../../misc
+
+GEOM_CLASS= nop
+
+.include <bsd.lib.mk>
diff --git a/sbin/geom/class/nop/geom_nop.c b/sbin/geom/class/nop/geom_nop.c
new file mode 100644
index 0000000..25163cc
--- /dev/null
+++ b/sbin/geom/class/nop/geom_nop.c
@@ -0,0 +1,75 @@
+/*-
+ * Copyright (c) 2004-2006 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <stdio.h>
+#include <stdint.h>
+#include <libgeom.h>
+#include <geom/nop/g_nop.h>
+
+#include "core/geom.h"
+
+
+uint32_t lib_version = G_LIB_VERSION;
+uint32_t version = G_NOP_VERSION;
+
+struct g_command class_commands[] = {
+ { "create", G_FLAG_VERBOSE | G_FLAG_LOADKLD, NULL,
+ {
+ { 'e', "error", "-1", G_TYPE_NUMBER },
+ { 'o', "offset", "0", G_TYPE_NUMBER },
+ { 'r', "rfailprob", "-1", G_TYPE_NUMBER },
+ { 's', "size", "0", G_TYPE_NUMBER },
+ { 'S', "secsize", "0", G_TYPE_NUMBER },
+ { 'w', "wfailprob", "-1", G_TYPE_NUMBER },
+ G_OPT_SENTINEL
+ },
+ "[-v] [-e error] [-o offset] [-r rfailprob] [-s size] "
+ "[-S secsize] [-w wfailprob] dev ..."
+ },
+ { "configure", G_FLAG_VERBOSE, NULL,
+ {
+ { 'e', "error", "-1", G_TYPE_NUMBER },
+ { 'r', "rfailprob", "-1", G_TYPE_NUMBER },
+ { 'w', "wfailprob", "-1", G_TYPE_NUMBER },
+ G_OPT_SENTINEL
+ },
+ "[-v] [-e error] [-r rfailprob] [-w wfailprob] prov ..."
+ },
+ { "destroy", G_FLAG_VERBOSE, NULL,
+ {
+ { 'f', "force", NULL, G_TYPE_BOOL },
+ G_OPT_SENTINEL
+ },
+ "[-fv] prov ..."
+ },
+ { "reset", G_FLAG_VERBOSE, NULL, G_NULL_OPTS,
+ "[-v] prov ..."
+ },
+ G_CMD_SENTINEL
+};
diff --git a/sbin/geom/class/nop/gnop.8 b/sbin/geom/class/nop/gnop.8
new file mode 100644
index 0000000..4770090
--- /dev/null
+++ b/sbin/geom/class/nop/gnop.8
@@ -0,0 +1,179 @@
+.\" Copyright (c) 2004-2006 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd April 14, 2013
+.Dt GNOP 8
+.Os
+.Sh NAME
+.Nm gnop
+.Nd "control utility for NOP GEOM class"
+.Sh SYNOPSIS
+.Nm
+.Cm create
+.Op Fl v
+.Op Fl e Ar error
+.Op Fl o Ar offset
+.Op Fl r Ar rfailprob
+.Op Fl s Ar size
+.Op Fl S Ar secsize
+.Op Fl w Ar wfailprob
+.Ar dev ...
+.Nm
+.Cm configure
+.Op Fl v
+.Op Fl e Ar error
+.Op Fl r Ar rfailprob
+.Op Fl w Ar wfailprob
+.Ar prov ...
+.Nm
+.Cm destroy
+.Op Fl fv
+.Ar prov ...
+.Nm
+.Cm reset
+.Op Fl v
+.Ar prov ...
+.Nm
+.Cm list
+.Nm
+.Cm status
+.Nm
+.Cm load
+.Nm
+.Cm unload
+.Sh DESCRIPTION
+The
+.Nm
+utility is used for setting up transparent providers on existing ones.
+Its main purpose is testing other GEOM classes, as it allows forced provider
+removal and I/O error simulation with a given probability.
+It also gathers the following statistics: number of read requests, number of
+write requests, number of bytes read and number of bytes written.
+In addition, it can be used as a good starting point for implementing new GEOM
+classes.
+.Pp
+The first argument to
+.Nm
+indicates an action to be performed:
+.Bl -tag -width ".Cm configure"
+.It Cm create
+Set up a transparent provider on the given devices.
+If the operation succeeds, the new provider should appear with name
+.Pa /dev/ Ns Ao Ar dev Ac Ns Pa .nop .
+The kernel module
+.Pa geom_nop.ko
+will be loaded if it is not loaded already.
+.It Cm configure
+Configure existing transparent provider.
+At the moment it is only used for changing failure probability.
+.It Cm destroy
+Turn off the given transparent providers.
+.It Cm reset
+Reset statistics for the given transparent providers.
+.It Cm list
+See
+.Xr geom 8 .
+.It Cm status
+See
+.Xr geom 8 .
+.It Cm load
+See
+.Xr geom 8 .
+.It Cm unload
+See
+.Xr geom 8 .
+.El
+.Pp
+Additional options:
+.Bl -tag -width ".Fl r Ar rfailprob"
+.It Fl e Ar error
+Specifies the error number to return on failure.
+.It Fl f
+Force the removal of the specified provider.
+.It Fl o Ar offset
+Where to begin on the original provider.
+.It Fl r Ar rfailprob
+Specifies read failure probability in percent.
+.It Fl s Ar size
+Size of the transparent provider.
+.It Fl S Ar secsize
+Sector size of the transparent provider.
+.It Fl w Ar wfailprob
+Specifies write failure probability in percent.
+.It Fl v
+Be more verbose.
+.El
+.Sh SYSCTL VARIABLES
+The following
+.Xr sysctl 8
+variables can be used to control the behavior of the
+.Nm NOP
+GEOM class.
+The default value is shown next to each variable.
+.Bl -tag -width indent
+.It Va kern.geom.nop.debug : No 0
+Debug level of the
+.Nm NOP
+GEOM class.
+This can be set to a number between 0 and 2 inclusive.
+If set to 0, minimal debug information is printed.
+If set to 1, basic debug information is logged along with the I/O requests
+that were returned as errors.
+If set to 2, the maximum amount of debug information is printed including
+all I/O requests.
+.El
+.Sh EXIT STATUS
+Exit status is 0 on success, and 1 if the command fails.
+.Sh EXAMPLES
+The following example shows how to create a transparent provider for disk
+.Pa /dev/da0
+with 50% write failure probability, and how to destroy it.
+.Bd -literal -offset indent
+gnop create -v -w 50 da0
+gnop destroy -v da0.nop
+.Ed
+.Pp
+The traffic statistics for the given transparent providers can be obtained
+with the
+.Cm list
+command.
+The example below shows the number of bytes written with
+.Xr newfs 8 :
+.Bd -literal -offset indent
+gnop create da0
+newfs /dev/da0.nop
+gnop list
+.Ed
+.Sh SEE ALSO
+.Xr geom 4 ,
+.Xr geom 8
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Fx 5.3 .
+.Sh AUTHORS
+.An Pawel Jakub Dawidek Aq Mt pjd@FreeBSD.org
diff --git a/sbin/geom/class/part/Makefile b/sbin/geom/class/part/Makefile
new file mode 100644
index 0000000..4b67de4
--- /dev/null
+++ b/sbin/geom/class/part/Makefile
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../../misc
+
+GEOM_CLASS= part
+
+LIBADD= util
+
+.include <bsd.lib.mk>
diff --git a/sbin/geom/class/part/geom_part.c b/sbin/geom/class/part/geom_part.c
new file mode 100644
index 0000000..133e8f8
--- /dev/null
+++ b/sbin/geom/class/part/geom_part.c
@@ -0,0 +1,1327 @@
+/*-
+ * Copyright (c) 2007, 2008 Marcel Moolenaar
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/stat.h>
+#include <sys/vtoc.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgeom.h>
+#include <libutil.h>
+#include <paths.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <inttypes.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+
+#include "core/geom.h"
+#include "misc/subr.h"
+
+#ifdef STATIC_GEOM_CLASSES
+#define PUBSYM(x) gpart_##x
+#else
+#define PUBSYM(x) x
+#endif
+
+uint32_t PUBSYM(lib_version) = G_LIB_VERSION;
+uint32_t PUBSYM(version) = 0;
+
+static char sstart[32];
+static char ssize[32];
+volatile sig_atomic_t undo_restore;
+
+#define GPART_AUTOFILL "*"
+#define GPART_FLAGS "C"
+
+#define GPART_PARAM_BOOTCODE "bootcode"
+#define GPART_PARAM_INDEX "index"
+#define GPART_PARAM_PARTCODE "partcode"
+
+static struct gclass *find_class(struct gmesh *, const char *);
+static struct ggeom * find_geom(struct gclass *, const char *);
+static const char *find_geomcfg(struct ggeom *, const char *);
+static const char *find_provcfg(struct gprovider *, const char *);
+static struct gprovider *find_provider(struct ggeom *, off_t);
+static const char *fmtsize(int64_t);
+static int gpart_autofill(struct gctl_req *);
+static int gpart_autofill_resize(struct gctl_req *);
+static void gpart_bootcode(struct gctl_req *, unsigned int);
+static void *gpart_bootfile_read(const char *, ssize_t *);
+static void gpart_issue(struct gctl_req *, unsigned int);
+static void gpart_show(struct gctl_req *, unsigned int);
+static void gpart_show_geom(struct ggeom *, const char *, int);
+static int gpart_show_hasopt(struct gctl_req *, const char *, const char *);
+static void gpart_write_partcode(struct ggeom *, int, void *, ssize_t);
+static void gpart_write_partcode_vtoc8(struct ggeom *, int, void *);
+static void gpart_print_error(const char *);
+static void gpart_backup(struct gctl_req *, unsigned int);
+static void gpart_restore(struct gctl_req *, unsigned int);
+
+struct g_command PUBSYM(class_commands)[] = {
+ { "add", 0, gpart_issue, {
+ { 'a', "alignment", GPART_AUTOFILL, G_TYPE_STRING },
+ { 'b', "start", GPART_AUTOFILL, G_TYPE_STRING },
+ { 's', "size", GPART_AUTOFILL, G_TYPE_STRING },
+ { 't', "type", NULL, G_TYPE_STRING },
+ { 'i', GPART_PARAM_INDEX, G_VAL_OPTIONAL, G_TYPE_NUMBER },
+ { 'l', "label", G_VAL_OPTIONAL, G_TYPE_STRING },
+ { 'f', "flags", GPART_FLAGS, G_TYPE_STRING },
+ G_OPT_SENTINEL },
+ "-t type [-a alignment] [-b start] [-s size] [-i index] "
+ "[-l label] [-f flags] geom"
+ },
+ { "backup", 0, gpart_backup, G_NULL_OPTS,
+ "geom"
+ },
+ { "bootcode", 0, gpart_bootcode, {
+ { 'b', GPART_PARAM_BOOTCODE, G_VAL_OPTIONAL, G_TYPE_STRING },
+ { 'p', GPART_PARAM_PARTCODE, G_VAL_OPTIONAL, G_TYPE_STRING },
+ { 'i', GPART_PARAM_INDEX, G_VAL_OPTIONAL, G_TYPE_NUMBER },
+ { 'f', "flags", GPART_FLAGS, G_TYPE_STRING },
+ G_OPT_SENTINEL },
+ "[-b bootcode] [-p partcode -i index] [-f flags] geom"
+ },
+ { "commit", 0, gpart_issue, G_NULL_OPTS,
+ "geom"
+ },
+ { "create", 0, gpart_issue, {
+ { 's', "scheme", NULL, G_TYPE_STRING },
+ { 'n', "entries", G_VAL_OPTIONAL, G_TYPE_NUMBER },
+ { 'f', "flags", GPART_FLAGS, G_TYPE_STRING },
+ G_OPT_SENTINEL },
+ "-s scheme [-n entries] [-f flags] provider"
+ },
+ { "delete", 0, gpart_issue, {
+ { 'i', GPART_PARAM_INDEX, NULL, G_TYPE_NUMBER },
+ { 'f', "flags", GPART_FLAGS, G_TYPE_STRING },
+ G_OPT_SENTINEL },
+ "-i index [-f flags] geom"
+ },
+ { "destroy", 0, gpart_issue, {
+ { 'F', "force", NULL, G_TYPE_BOOL },
+ { 'f', "flags", GPART_FLAGS, G_TYPE_STRING },
+ G_OPT_SENTINEL },
+ "[-F] [-f flags] geom"
+ },
+ { "modify", 0, gpart_issue, {
+ { 'i', GPART_PARAM_INDEX, NULL, G_TYPE_NUMBER },
+ { 'l', "label", G_VAL_OPTIONAL, G_TYPE_STRING },
+ { 't', "type", G_VAL_OPTIONAL, G_TYPE_STRING },
+ { 'f', "flags", GPART_FLAGS, G_TYPE_STRING },
+ G_OPT_SENTINEL },
+ "-i index [-l label] [-t type] [-f flags] geom"
+ },
+ { "set", 0, gpart_issue, {
+ { 'a', "attrib", NULL, G_TYPE_STRING },
+ { 'i', GPART_PARAM_INDEX, G_VAL_OPTIONAL, G_TYPE_NUMBER },
+ { 'f', "flags", GPART_FLAGS, G_TYPE_STRING },
+ G_OPT_SENTINEL },
+ "-a attrib [-i index] [-f flags] geom"
+ },
+ { "show", 0, gpart_show, {
+ { 'l', "show_label", NULL, G_TYPE_BOOL },
+ { 'r', "show_rawtype", NULL, G_TYPE_BOOL },
+ { 'p', "show_providers", NULL, G_TYPE_BOOL },
+ G_OPT_SENTINEL },
+ "[-l | -r] [-p] [geom ...]"
+ },
+ { "undo", 0, gpart_issue, G_NULL_OPTS,
+ "geom"
+ },
+ { "unset", 0, gpart_issue, {
+ { 'a', "attrib", NULL, G_TYPE_STRING },
+ { 'i', GPART_PARAM_INDEX, G_VAL_OPTIONAL, G_TYPE_NUMBER },
+ { 'f', "flags", GPART_FLAGS, G_TYPE_STRING },
+ G_OPT_SENTINEL },
+ "-a attrib [-i index] [-f flags] geom"
+ },
+ { "resize", 0, gpart_issue, {
+ { 'a', "alignment", GPART_AUTOFILL, G_TYPE_STRING },
+ { 's', "size", GPART_AUTOFILL, G_TYPE_STRING },
+ { 'i', GPART_PARAM_INDEX, NULL, G_TYPE_NUMBER },
+ { 'f', "flags", GPART_FLAGS, G_TYPE_STRING },
+ G_OPT_SENTINEL },
+ "-i index [-a alignment] [-s size] [-f flags] geom"
+ },
+ { "restore", 0, gpart_restore, {
+ { 'F', "force", NULL, G_TYPE_BOOL },
+ { 'l', "restore_labels", NULL, G_TYPE_BOOL },
+ { 'f', "flags", GPART_FLAGS, G_TYPE_STRING },
+ G_OPT_SENTINEL },
+ "[-lF] [-f flags] provider [...]"
+ },
+ { "recover", 0, gpart_issue, {
+ { 'f', "flags", GPART_FLAGS, G_TYPE_STRING },
+ G_OPT_SENTINEL },
+ "[-f flags] geom"
+ },
+ G_CMD_SENTINEL
+};
+
+static struct gclass *
+find_class(struct gmesh *mesh, const char *name)
+{
+ struct gclass *classp;
+
+ LIST_FOREACH(classp, &mesh->lg_class, lg_class) {
+ if (strcmp(classp->lg_name, name) == 0)
+ return (classp);
+ }
+ return (NULL);
+}
+
+static struct ggeom *
+find_geom(struct gclass *classp, const char *name)
+{
+ struct ggeom *gp, *wgp;
+
+ if (strncmp(name, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
+ name += sizeof(_PATH_DEV) - 1;
+ wgp = NULL;
+ LIST_FOREACH(gp, &classp->lg_geom, lg_geom) {
+ if (strcmp(gp->lg_name, name) != 0)
+ continue;
+ if (find_geomcfg(gp, "wither") == NULL)
+ return (gp);
+ else
+ wgp = gp;
+ }
+ return (wgp);
+}
+
+static const char *
+find_geomcfg(struct ggeom *gp, const char *cfg)
+{
+ struct gconfig *gc;
+
+ LIST_FOREACH(gc, &gp->lg_config, lg_config) {
+ if (!strcmp(gc->lg_name, cfg))
+ return (gc->lg_val);
+ }
+ return (NULL);
+}
+
+static const char *
+find_provcfg(struct gprovider *pp, const char *cfg)
+{
+ struct gconfig *gc;
+
+ LIST_FOREACH(gc, &pp->lg_config, lg_config) {
+ if (!strcmp(gc->lg_name, cfg))
+ return (gc->lg_val);
+ }
+ return (NULL);
+}
+
+static struct gprovider *
+find_provider(struct ggeom *gp, off_t minsector)
+{
+ struct gprovider *pp, *bestpp;
+ const char *s;
+ off_t sector, bestsector;
+
+ bestpp = NULL;
+ bestsector = 0;
+ LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {
+ s = find_provcfg(pp, "start");
+ sector = (off_t)strtoimax(s, NULL, 0);
+ if (sector < minsector)
+ continue;
+ if (bestpp != NULL && sector >= bestsector)
+ continue;
+
+ bestpp = pp;
+ bestsector = sector;
+ }
+ return (bestpp);
+}
+
+static const char *
+fmtsize(int64_t rawsz)
+{
+ static char buf[5];
+
+ humanize_number(buf, sizeof(buf), rawsz, "", HN_AUTOSCALE,
+ HN_B | HN_NOSPACE | HN_DECIMAL);
+ return (buf);
+}
+
+static const char *
+fmtattrib(struct gprovider *pp)
+{
+ static char buf[128];
+ struct gconfig *gc;
+ u_int idx;
+
+ buf[0] = '\0';
+ idx = 0;
+ LIST_FOREACH(gc, &pp->lg_config, lg_config) {
+ if (strcmp(gc->lg_name, "attrib") != 0)
+ continue;
+ idx += snprintf(buf + idx, sizeof(buf) - idx, "%s%s",
+ (idx == 0) ? " [" : ",", gc->lg_val);
+ }
+ if (idx > 0)
+ snprintf(buf + idx, sizeof(buf) - idx, "] ");
+ return (buf);
+}
+
+#define ALIGNDOWN(d, a) ((d) - (d) % (a))
+#define ALIGNUP(d, a) ((d) % (a) ? (d) - (d) % (a) + (a): (d))
+
+static int
+gpart_autofill_resize(struct gctl_req *req)
+{
+ struct gmesh mesh;
+ struct gclass *cp;
+ struct ggeom *gp;
+ struct gprovider *pp;
+ off_t last, size, start, new_size;
+ off_t lba, new_lba, alignment, offset;
+ const char *s;
+ int error, idx, has_alignment;
+
+ idx = (int)gctl_get_intmax(req, GPART_PARAM_INDEX);
+ if (idx < 1)
+ errx(EXIT_FAILURE, "invalid partition index");
+
+ error = geom_gettree(&mesh);
+ if (error)
+ return (error);
+ s = gctl_get_ascii(req, "class");
+ if (s == NULL)
+ abort();
+ cp = find_class(&mesh, s);
+ if (cp == NULL)
+ errx(EXIT_FAILURE, "Class %s not found.", s);
+ s = gctl_get_ascii(req, "arg0");
+ if (s == NULL)
+ abort();
+ gp = find_geom(cp, s);
+ if (gp == NULL)
+ errx(EXIT_FAILURE, "No such geom: %s.", s);
+ pp = LIST_FIRST(&gp->lg_consumer)->lg_provider;
+ if (pp == NULL)
+ errx(EXIT_FAILURE, "Provider for geom %s not found.", s);
+
+ s = gctl_get_ascii(req, "alignment");
+ has_alignment = (*s == '*') ? 0 : 1;
+ alignment = 1;
+ if (has_alignment) {
+ error = g_parse_lba(s, pp->lg_sectorsize, &alignment);
+ if (error)
+ errc(EXIT_FAILURE, error, "Invalid alignment param");
+ if (alignment == 0)
+ errx(EXIT_FAILURE, "Invalid alignment param");
+ } else {
+ lba = pp->lg_stripesize / pp->lg_sectorsize;
+ if (lba > 0)
+ alignment = lba;
+ }
+ error = gctl_delete_param(req, "alignment");
+ if (error)
+ errc(EXIT_FAILURE, error, "internal error");
+
+ s = gctl_get_ascii(req, "size");
+ if (*s == '*')
+ new_size = 0;
+ else {
+ error = g_parse_lba(s, pp->lg_sectorsize, &new_size);
+ if (error)
+ errc(EXIT_FAILURE, error, "Invalid size param");
+ /* no autofill necessary. */
+ if (has_alignment == 0)
+ goto done;
+ }
+
+ offset = (pp->lg_stripeoffset / pp->lg_sectorsize) % alignment;
+ s = find_geomcfg(gp, "last");
+ if (s == NULL)
+ errx(EXIT_FAILURE, "Final block not found for geom %s",
+ gp->lg_name);
+ last = (off_t)strtoimax(s, NULL, 0);
+ LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {
+ s = find_provcfg(pp, "index");
+ if (s == NULL)
+ continue;
+ if (atoi(s) == idx)
+ break;
+ }
+ if (pp == NULL)
+ errx(EXIT_FAILURE, "invalid partition index");
+
+ s = find_provcfg(pp, "start");
+ start = (off_t)strtoimax(s, NULL, 0);
+ s = find_provcfg(pp, "end");
+ lba = (off_t)strtoimax(s, NULL, 0);
+ size = lba - start + 1;
+
+ pp = find_provider(gp, lba + 1);
+ if (new_size > 0 && (new_size <= size || pp == NULL)) {
+ /* The start offset may be not aligned, so we align the end
+ * offset and then calculate the size.
+ */
+ new_size = ALIGNDOWN(start + offset + new_size,
+ alignment) - start - offset;
+ goto done;
+ }
+ if (pp == NULL) {
+ new_size = ALIGNDOWN(last + offset + 1, alignment) -
+ start - offset;
+ if (new_size < size)
+ return (ENOSPC);
+ } else {
+ s = find_provcfg(pp, "start");
+ new_lba = (off_t)strtoimax(s, NULL, 0);
+ /*
+ * Is there any free space between current and
+ * next providers?
+ */
+ new_lba = ALIGNDOWN(new_lba + offset, alignment) - offset;
+ if (new_lba > lba)
+ new_size = new_lba - start;
+ else {
+ geom_deletetree(&mesh);
+ return (ENOSPC);
+ }
+ }
+done:
+ snprintf(ssize, sizeof(ssize), "%jd", (intmax_t)new_size);
+ gctl_change_param(req, "size", -1, ssize);
+ geom_deletetree(&mesh);
+ return (0);
+}
+
+static int
+gpart_autofill(struct gctl_req *req)
+{
+ struct gmesh mesh;
+ struct gclass *cp;
+ struct ggeom *gp;
+ struct gprovider *pp;
+ off_t first, last, a_first;
+ off_t size, start, a_lba;
+ off_t lba, len, alignment, offset;
+ uintmax_t grade;
+ const char *s;
+ int error, has_size, has_start, has_alignment;
+
+ s = gctl_get_ascii(req, "verb");
+ if (strcmp(s, "resize") == 0)
+ return gpart_autofill_resize(req);
+ if (strcmp(s, "add") != 0)
+ return (0);
+
+ error = geom_gettree(&mesh);
+ if (error)
+ return (error);
+ s = gctl_get_ascii(req, "class");
+ if (s == NULL)
+ abort();
+ cp = find_class(&mesh, s);
+ if (cp == NULL)
+ errx(EXIT_FAILURE, "Class %s not found.", s);
+ s = gctl_get_ascii(req, "arg0");
+ if (s == NULL)
+ abort();
+ gp = find_geom(cp, s);
+ if (gp == NULL) {
+ if (g_device_path(s) == NULL) {
+ errx(EXIT_FAILURE, "No such geom %s.", s);
+ } else {
+ /*
+ * We don't free memory allocated by g_device_path() as
+ * we are about to exit.
+ */
+ errx(EXIT_FAILURE,
+ "No partitioning scheme found on geom %s. Create one first using 'gpart create'.",
+ s);
+ }
+ }
+ pp = LIST_FIRST(&gp->lg_consumer)->lg_provider;
+ if (pp == NULL)
+ errx(EXIT_FAILURE, "Provider for geom %s not found.", s);
+
+ s = gctl_get_ascii(req, "alignment");
+ has_alignment = (*s == '*') ? 0 : 1;
+ alignment = 1;
+ if (has_alignment) {
+ error = g_parse_lba(s, pp->lg_sectorsize, &alignment);
+ if (error)
+ errc(EXIT_FAILURE, error, "Invalid alignment param");
+ if (alignment == 0)
+ errx(EXIT_FAILURE, "Invalid alignment param");
+ }
+ error = gctl_delete_param(req, "alignment");
+ if (error)
+ errc(EXIT_FAILURE, error, "internal error");
+
+ s = gctl_get_ascii(req, "size");
+ has_size = (*s == '*') ? 0 : 1;
+ size = 0;
+ if (has_size) {
+ error = g_parse_lba(s, pp->lg_sectorsize, &size);
+ if (error)
+ errc(EXIT_FAILURE, error, "Invalid size param");
+ }
+
+ s = gctl_get_ascii(req, "start");
+ has_start = (*s == '*') ? 0 : 1;
+ start = 0ULL;
+ if (has_start) {
+ error = g_parse_lba(s, pp->lg_sectorsize, &start);
+ if (error)
+ errc(EXIT_FAILURE, error, "Invalid start param");
+ }
+
+ /* No autofill necessary. */
+ if (has_size && has_start && !has_alignment)
+ goto done;
+
+ len = pp->lg_stripesize / pp->lg_sectorsize;
+ if (len > 0 && !has_alignment)
+ alignment = len;
+
+ /* Adjust parameters to stripeoffset */
+ offset = (pp->lg_stripeoffset / pp->lg_sectorsize) % alignment;
+ start = ALIGNUP(start + offset, alignment);
+ if (size > alignment)
+ size = ALIGNDOWN(size, alignment);
+
+ s = find_geomcfg(gp, "first");
+ if (s == NULL)
+ errx(EXIT_FAILURE, "Starting block not found for geom %s",
+ gp->lg_name);
+ first = (off_t)strtoimax(s, NULL, 0);
+ s = find_geomcfg(gp, "last");
+ if (s == NULL)
+ errx(EXIT_FAILURE, "Final block not found for geom %s",
+ gp->lg_name);
+ last = (off_t)strtoimax(s, NULL, 0);
+ grade = ~0ULL;
+ a_first = ALIGNUP(first + offset, alignment);
+ last = ALIGNDOWN(last + offset, alignment);
+ if (a_first < start)
+ a_first = start;
+ while ((pp = find_provider(gp, first)) != NULL) {
+ s = find_provcfg(pp, "start");
+ lba = (off_t)strtoimax(s, NULL, 0);
+ a_lba = ALIGNDOWN(lba + offset, alignment);
+ if (first < a_lba && a_first < a_lba) {
+ /* Free space [first, lba> */
+ len = a_lba - a_first;
+ if (has_size) {
+ if (len >= size &&
+ (uintmax_t)(len - size) < grade) {
+ start = a_first;
+ grade = len - size;
+ }
+ } else if (has_start) {
+ if (start >= a_first && start < a_lba) {
+ size = a_lba - start;
+ grade = start - a_first;
+ }
+ } else {
+ if (grade == ~0ULL || len > size) {
+ start = a_first;
+ size = len;
+ grade = 0;
+ }
+ }
+ }
+
+ s = find_provcfg(pp, "end");
+ first = (off_t)strtoimax(s, NULL, 0) + 1;
+ if (first + offset > a_first)
+ a_first = ALIGNUP(first + offset, alignment);
+ }
+ if (a_first <= last) {
+ /* Free space [first-last] */
+ len = ALIGNDOWN(last - a_first + 1, alignment);
+ if (has_size) {
+ if (len >= size &&
+ (uintmax_t)(len - size) < grade) {
+ start = a_first;
+ grade = len - size;
+ }
+ } else if (has_start) {
+ if (start >= a_first && start <= last) {
+ size = ALIGNDOWN(last - start + 1, alignment);
+ grade = start - a_first;
+ }
+ } else {
+ if (grade == ~0ULL || len > size) {
+ start = a_first;
+ size = len;
+ grade = 0;
+ }
+ }
+ }
+ if (grade == ~0ULL) {
+ geom_deletetree(&mesh);
+ return (ENOSPC);
+ }
+ start -= offset; /* Return back to real offset */
+done:
+ snprintf(ssize, sizeof(ssize), "%jd", (intmax_t)size);
+ gctl_change_param(req, "size", -1, ssize);
+ snprintf(sstart, sizeof(sstart), "%jd", (intmax_t)start);
+ gctl_change_param(req, "start", -1, sstart);
+ geom_deletetree(&mesh);
+ return (0);
+}
+
+static void
+gpart_show_geom(struct ggeom *gp, const char *element, int show_providers)
+{
+ struct gprovider *pp;
+ const char *s, *scheme;
+ off_t first, last, sector, end;
+ off_t length, secsz;
+ int idx, wblocks, wname, wmax;
+
+ if (find_geomcfg(gp, "wither"))
+ return;
+ scheme = find_geomcfg(gp, "scheme");
+ if (scheme == NULL)
+ errx(EXIT_FAILURE, "Scheme not found for geom %s", gp->lg_name);
+ s = find_geomcfg(gp, "first");
+ if (s == NULL)
+ errx(EXIT_FAILURE, "Starting block not found for geom %s",
+ gp->lg_name);
+ first = (off_t)strtoimax(s, NULL, 0);
+ s = find_geomcfg(gp, "last");
+ if (s == NULL)
+ errx(EXIT_FAILURE, "Final block not found for geom %s",
+ gp->lg_name);
+ last = (off_t)strtoimax(s, NULL, 0);
+ wblocks = strlen(s);
+ s = find_geomcfg(gp, "state");
+ if (s == NULL)
+ errx(EXIT_FAILURE, "State not found for geom %s", gp->lg_name);
+ if (s != NULL && *s != 'C')
+ s = NULL;
+ wmax = strlen(gp->lg_name);
+ if (show_providers) {
+ LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {
+ wname = strlen(pp->lg_name);
+ if (wname > wmax)
+ wmax = wname;
+ }
+ }
+ wname = wmax;
+ pp = LIST_FIRST(&gp->lg_consumer)->lg_provider;
+ secsz = pp->lg_sectorsize;
+ printf("=>%*jd %*jd %*s %s (%s)%s\n",
+ wblocks, (intmax_t)first, wblocks, (intmax_t)(last - first + 1),
+ wname, gp->lg_name,
+ scheme, fmtsize(pp->lg_mediasize),
+ s ? " [CORRUPT]": "");
+
+ while ((pp = find_provider(gp, first)) != NULL) {
+ s = find_provcfg(pp, "start");
+ sector = (off_t)strtoimax(s, NULL, 0);
+
+ s = find_provcfg(pp, "end");
+ end = (off_t)strtoimax(s, NULL, 0);
+ length = end - sector + 1;
+
+ s = find_provcfg(pp, "index");
+ idx = atoi(s);
+ if (first < sector) {
+ printf(" %*jd %*jd %*s - free - (%s)\n",
+ wblocks, (intmax_t)first, wblocks,
+ (intmax_t)(sector - first), wname, "",
+ fmtsize((sector - first) * secsz));
+ }
+ if (show_providers) {
+ printf(" %*jd %*jd %*s %s %s (%s)\n",
+ wblocks, (intmax_t)sector, wblocks,
+ (intmax_t)length, wname, pp->lg_name,
+ find_provcfg(pp, element), fmtattrib(pp),
+ fmtsize(pp->lg_mediasize));
+ } else
+ printf(" %*jd %*jd %*d %s %s (%s)\n",
+ wblocks, (intmax_t)sector, wblocks,
+ (intmax_t)length, wname, idx,
+ find_provcfg(pp, element), fmtattrib(pp),
+ fmtsize(pp->lg_mediasize));
+ first = end + 1;
+ }
+ if (first <= last) {
+ length = last - first + 1;
+ printf(" %*jd %*jd %*s - free - (%s)\n",
+ wblocks, (intmax_t)first, wblocks, (intmax_t)length,
+ wname, "",
+ fmtsize(length * secsz));
+ }
+ printf("\n");
+}
+
+static int
+gpart_show_hasopt(struct gctl_req *req, const char *opt, const char *elt)
+{
+
+ if (!gctl_get_int(req, "%s", opt))
+ return (0);
+
+ if (elt != NULL)
+ errx(EXIT_FAILURE, "-l and -r are mutually exclusive");
+
+ return (1);
+}
+
+static void
+gpart_show(struct gctl_req *req, unsigned int fl __unused)
+{
+ struct gmesh mesh;
+ struct gclass *classp;
+ struct ggeom *gp;
+ const char *element, *name;
+ int error, i, nargs, show_providers;
+
+ element = NULL;
+ if (gpart_show_hasopt(req, "show_label", element))
+ element = "label";
+ if (gpart_show_hasopt(req, "show_rawtype", element))
+ element = "rawtype";
+ if (element == NULL)
+ element = "type";
+
+ name = gctl_get_ascii(req, "class");
+ if (name == NULL)
+ abort();
+ error = geom_gettree(&mesh);
+ if (error != 0)
+ errc(EXIT_FAILURE, error, "Cannot get GEOM tree");
+ classp = find_class(&mesh, name);
+ if (classp == NULL) {
+ geom_deletetree(&mesh);
+ errx(EXIT_FAILURE, "Class %s not found.", name);
+ }
+ show_providers = gctl_get_int(req, "show_providers");
+ nargs = gctl_get_int(req, "nargs");
+ if (nargs > 0) {
+ for (i = 0; i < nargs; i++) {
+ name = gctl_get_ascii(req, "arg%d", i);
+ gp = find_geom(classp, name);
+ if (gp != NULL)
+ gpart_show_geom(gp, element, show_providers);
+ else
+ errx(EXIT_FAILURE, "No such geom: %s.", name);
+ }
+ } else {
+ LIST_FOREACH(gp, &classp->lg_geom, lg_geom) {
+ gpart_show_geom(gp, element, show_providers);
+ }
+ }
+ geom_deletetree(&mesh);
+}
+
+static void
+gpart_backup(struct gctl_req *req, unsigned int fl __unused)
+{
+ struct gmesh mesh;
+ struct gclass *classp;
+ struct gprovider *pp;
+ struct ggeom *gp;
+ const char *s, *scheme;
+ off_t sector, end;
+ off_t length;
+ int error, i, windex, wblocks, wtype;
+
+ if (gctl_get_int(req, "nargs") != 1)
+ errx(EXIT_FAILURE, "Invalid number of arguments.");
+ error = geom_gettree(&mesh);
+ if (error != 0)
+ errc(EXIT_FAILURE, error, "Cannot get GEOM tree");
+ s = gctl_get_ascii(req, "class");
+ if (s == NULL)
+ abort();
+ classp = find_class(&mesh, s);
+ if (classp == NULL) {
+ geom_deletetree(&mesh);
+ errx(EXIT_FAILURE, "Class %s not found.", s);
+ }
+ s = gctl_get_ascii(req, "arg0");
+ if (s == NULL)
+ abort();
+ gp = find_geom(classp, s);
+ if (gp == NULL)
+ errx(EXIT_FAILURE, "No such geom: %s.", s);
+ scheme = find_geomcfg(gp, "scheme");
+ if (scheme == NULL)
+ abort();
+ pp = LIST_FIRST(&gp->lg_consumer)->lg_provider;
+ s = find_geomcfg(gp, "last");
+ if (s == NULL)
+ abort();
+ wblocks = strlen(s);
+ wtype = 0;
+ LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {
+ s = find_provcfg(pp, "type");
+ i = strlen(s);
+ if (i > wtype)
+ wtype = i;
+ }
+ s = find_geomcfg(gp, "entries");
+ if (s == NULL)
+ abort();
+ windex = strlen(s);
+ printf("%s %s\n", scheme, s);
+ LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {
+ s = find_provcfg(pp, "start");
+ sector = (off_t)strtoimax(s, NULL, 0);
+
+ s = find_provcfg(pp, "end");
+ end = (off_t)strtoimax(s, NULL, 0);
+ length = end - sector + 1;
+
+ s = find_provcfg(pp, "label");
+ printf("%-*s %*s %*jd %*jd %s %s\n",
+ windex, find_provcfg(pp, "index"),
+ wtype, find_provcfg(pp, "type"),
+ wblocks, (intmax_t)sector,
+ wblocks, (intmax_t)length,
+ (s != NULL) ? s: "", fmtattrib(pp));
+ }
+ geom_deletetree(&mesh);
+}
+
+static int
+skip_line(const char *p)
+{
+
+ while (*p != '\0') {
+ if (*p == '#')
+ return (1);
+ if (isspace(*p) == 0)
+ return (0);
+ p++;
+ }
+ return (1);
+}
+
+static void
+gpart_sighndl(int sig __unused)
+{
+ undo_restore = 1;
+}
+
+static void
+gpart_restore(struct gctl_req *req, unsigned int fl __unused)
+{
+ struct gmesh mesh;
+ struct gclass *classp;
+ struct gctl_req *r;
+ struct ggeom *gp;
+ struct sigaction si_sa;
+ const char *s, *flags, *errstr, *label;
+ char **ap, *argv[6], line[BUFSIZ], *pline;
+ int error, forced, i, l, nargs, created, rl;
+ intmax_t n;
+
+ nargs = gctl_get_int(req, "nargs");
+ if (nargs < 1)
+ errx(EXIT_FAILURE, "Invalid number of arguments.");
+
+ forced = gctl_get_int(req, "force");
+ flags = gctl_get_ascii(req, "flags");
+ rl = gctl_get_int(req, "restore_labels");
+ s = gctl_get_ascii(req, "class");
+ if (s == NULL)
+ abort();
+ error = geom_gettree(&mesh);
+ if (error != 0)
+ errc(EXIT_FAILURE, error, "Cannot get GEOM tree");
+ classp = find_class(&mesh, s);
+ if (classp == NULL) {
+ geom_deletetree(&mesh);
+ errx(EXIT_FAILURE, "Class %s not found.", s);
+ }
+
+ sigemptyset(&si_sa.sa_mask);
+ si_sa.sa_flags = 0;
+ si_sa.sa_handler = gpart_sighndl;
+ if (sigaction(SIGINT, &si_sa, 0) == -1)
+ err(EXIT_FAILURE, "sigaction SIGINT");
+
+ if (forced) {
+ /* destroy existent partition table before restore */
+ for (i = 0; i < nargs; i++) {
+ s = gctl_get_ascii(req, "arg%d", i);
+ gp = find_geom(classp, s);
+ if (gp != NULL) {
+ r = gctl_get_handle();
+ gctl_ro_param(r, "class", -1,
+ classp->lg_name);
+ gctl_ro_param(r, "verb", -1, "destroy");
+ gctl_ro_param(r, "flags", -1, "restore");
+ gctl_ro_param(r, "force", sizeof(forced),
+ &forced);
+ gctl_ro_param(r, "arg0", -1, s);
+ errstr = gctl_issue(r);
+ if (errstr != NULL && errstr[0] != '\0') {
+ gpart_print_error(errstr);
+ gctl_free(r);
+ goto backout;
+ }
+ gctl_free(r);
+ }
+ }
+ }
+ created = 0;
+ while (undo_restore == 0 &&
+ fgets(line, sizeof(line) - 1, stdin) != NULL) {
+ /* Format of backup entries:
+ * <scheme name> <number of entries>
+ * <index> <type> <start> <size> [label] ['['attrib[,attrib]']']
+ */
+ pline = (char *)line;
+ pline[strlen(line) - 1] = 0;
+ if (skip_line(pline))
+ continue;
+ for (ap = argv;
+ (*ap = strsep(&pline, " \t")) != NULL;)
+ if (**ap != '\0' && ++ap >= &argv[6])
+ break;
+ l = ap - &argv[0];
+ label = pline = NULL;
+ if (l == 1 || l == 2) { /* create table */
+ if (created)
+ errx(EXIT_FAILURE, "Incorrect backup format.");
+ if (l == 2)
+ n = strtoimax(argv[1], NULL, 0);
+ for (i = 0; i < nargs; i++) {
+ s = gctl_get_ascii(req, "arg%d", i);
+ r = gctl_get_handle();
+ gctl_ro_param(r, "class", -1,
+ classp->lg_name);
+ gctl_ro_param(r, "verb", -1, "create");
+ gctl_ro_param(r, "scheme", -1, argv[0]);
+ if (l == 2)
+ gctl_ro_param(r, "entries",
+ sizeof(n), &n);
+ gctl_ro_param(r, "flags", -1, "restore");
+ gctl_ro_param(r, "arg0", -1, s);
+ errstr = gctl_issue(r);
+ if (errstr != NULL && errstr[0] != '\0') {
+ gpart_print_error(errstr);
+ gctl_free(r);
+ goto backout;
+ }
+ gctl_free(r);
+ }
+ created = 1;
+ continue;
+ } else if (l < 4 || created == 0)
+ errx(EXIT_FAILURE, "Incorrect backup format.");
+ else if (l == 5) {
+ if (strchr(argv[4], '[') == NULL)
+ label = argv[4];
+ else
+ pline = argv[4];
+ } else if (l == 6) {
+ label = argv[4];
+ pline = argv[5];
+ }
+ /* Add partitions to each table */
+ for (i = 0; i < nargs; i++) {
+ s = gctl_get_ascii(req, "arg%d", i);
+ r = gctl_get_handle();
+ n = strtoimax(argv[0], NULL, 0);
+ gctl_ro_param(r, "class", -1, classp->lg_name);
+ gctl_ro_param(r, "verb", -1, "add");
+ gctl_ro_param(r, "flags", -1, "restore");
+ gctl_ro_param(r, GPART_PARAM_INDEX, sizeof(n), &n);
+ gctl_ro_param(r, "type", -1, argv[1]);
+ gctl_ro_param(r, "start", -1, argv[2]);
+ gctl_ro_param(r, "size", -1, argv[3]);
+ if (rl != 0 && label != NULL)
+ gctl_ro_param(r, "label", -1, argv[4]);
+ gctl_ro_param(r, "alignment", -1, GPART_AUTOFILL);
+ gctl_ro_param(r, "arg0", -1, s);
+ error = gpart_autofill(r);
+ if (error != 0)
+ errc(EXIT_FAILURE, error, "autofill");
+ errstr = gctl_issue(r);
+ if (errstr != NULL && errstr[0] != '\0') {
+ gpart_print_error(errstr);
+ gctl_free(r);
+ goto backout;
+ }
+ gctl_free(r);
+ }
+ if (pline == NULL || *pline != '[')
+ continue;
+ /* set attributes */
+ pline++;
+ for (ap = argv;
+ (*ap = strsep(&pline, ",]")) != NULL;)
+ if (**ap != '\0' && ++ap >= &argv[6])
+ break;
+ for (i = 0; i < nargs; i++) {
+ l = ap - &argv[0];
+ s = gctl_get_ascii(req, "arg%d", i);
+ while (l > 0) {
+ r = gctl_get_handle();
+ gctl_ro_param(r, "class", -1, classp->lg_name);
+ gctl_ro_param(r, "verb", -1, "set");
+ gctl_ro_param(r, "flags", -1, "restore");
+ gctl_ro_param(r, GPART_PARAM_INDEX,
+ sizeof(n), &n);
+ gctl_ro_param(r, "attrib", -1, argv[--l]);
+ gctl_ro_param(r, "arg0", -1, s);
+ errstr = gctl_issue(r);
+ if (errstr != NULL && errstr[0] != '\0') {
+ gpart_print_error(errstr);
+ gctl_free(r);
+ goto backout;
+ }
+ gctl_free(r);
+ }
+ }
+ }
+ if (undo_restore)
+ goto backout;
+ /* commit changes if needed */
+ if (strchr(flags, 'C') != NULL) {
+ for (i = 0; i < nargs; i++) {
+ s = gctl_get_ascii(req, "arg%d", i);
+ r = gctl_get_handle();
+ gctl_ro_param(r, "class", -1, classp->lg_name);
+ gctl_ro_param(r, "verb", -1, "commit");
+ gctl_ro_param(r, "arg0", -1, s);
+ errstr = gctl_issue(r);
+ if (errstr != NULL && errstr[0] != '\0') {
+ gpart_print_error(errstr);
+ gctl_free(r);
+ goto backout;
+ }
+ gctl_free(r);
+ }
+ }
+ gctl_free(req);
+ geom_deletetree(&mesh);
+ exit(EXIT_SUCCESS);
+
+backout:
+ for (i = 0; i < nargs; i++) {
+ s = gctl_get_ascii(req, "arg%d", i);
+ r = gctl_get_handle();
+ gctl_ro_param(r, "class", -1, classp->lg_name);
+ gctl_ro_param(r, "verb", -1, "undo");
+ gctl_ro_param(r, "arg0", -1, s);
+ gctl_issue(r);
+ gctl_free(r);
+ }
+ gctl_free(req);
+ geom_deletetree(&mesh);
+ exit(EXIT_FAILURE);
+}
+
+static void *
+gpart_bootfile_read(const char *bootfile, ssize_t *size)
+{
+ struct stat sb;
+ void *code;
+ int fd;
+
+ if (stat(bootfile, &sb) == -1)
+ err(EXIT_FAILURE, "%s", bootfile);
+ if (!S_ISREG(sb.st_mode))
+ errx(EXIT_FAILURE, "%s: not a regular file", bootfile);
+ if (sb.st_size == 0)
+ errx(EXIT_FAILURE, "%s: empty file", bootfile);
+ if (*size > 0 && sb.st_size > *size)
+ errx(EXIT_FAILURE, "%s: file too big (%zu limit)", bootfile,
+ *size);
+
+ *size = sb.st_size;
+
+ fd = open(bootfile, O_RDONLY);
+ if (fd == -1)
+ err(EXIT_FAILURE, "%s", bootfile);
+ code = malloc(*size);
+ if (code == NULL)
+ err(EXIT_FAILURE, NULL);
+ if (read(fd, code, *size) != *size)
+ err(EXIT_FAILURE, "%s", bootfile);
+ close(fd);
+
+ return (code);
+}
+
+static void
+gpart_write_partcode(struct ggeom *gp, int idx, void *code, ssize_t size)
+{
+ char dsf[128];
+ struct gprovider *pp;
+ const char *s;
+ char *buf;
+ off_t bsize;
+ int fd;
+
+ LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {
+ s = find_provcfg(pp, "index");
+ if (s == NULL)
+ continue;
+ if (atoi(s) == idx)
+ break;
+ }
+
+ if (pp != NULL) {
+ snprintf(dsf, sizeof(dsf), "/dev/%s", pp->lg_name);
+ fd = open(dsf, O_WRONLY);
+ if (fd == -1)
+ err(EXIT_FAILURE, "%s", dsf);
+ if (lseek(fd, size, SEEK_SET) != size)
+ errx(EXIT_FAILURE, "%s: not enough space", dsf);
+ if (lseek(fd, 0, SEEK_SET) != 0)
+ err(EXIT_FAILURE, "%s", dsf);
+
+ /*
+ * When writing to a disk device, the write must be
+ * sector aligned and not write to any partial sectors,
+ * so round up the buffer size to the next sector and zero it.
+ */
+ bsize = (size + pp->lg_sectorsize - 1) /
+ pp->lg_sectorsize * pp->lg_sectorsize;
+ buf = calloc(1, bsize);
+ if (buf == NULL)
+ err(EXIT_FAILURE, "%s", dsf);
+ bcopy(code, buf, size);
+ if (write(fd, buf, bsize) != bsize)
+ err(EXIT_FAILURE, "%s", dsf);
+ free(buf);
+ close(fd);
+ } else
+ errx(EXIT_FAILURE, "invalid partition index");
+}
+
+static void
+gpart_write_partcode_vtoc8(struct ggeom *gp, int idx, void *code)
+{
+ char dsf[128];
+ struct gprovider *pp;
+ const char *s;
+ int installed, fd;
+
+ installed = 0;
+ LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {
+ s = find_provcfg(pp, "index");
+ if (s == NULL)
+ continue;
+ if (idx != 0 && atoi(s) != idx)
+ continue;
+ snprintf(dsf, sizeof(dsf), "/dev/%s", pp->lg_name);
+ if (pp->lg_sectorsize != sizeof(struct vtoc8))
+ errx(EXIT_FAILURE, "%s: unexpected sector "
+ "size (%d)\n", dsf, pp->lg_sectorsize);
+ fd = open(dsf, O_WRONLY);
+ if (fd == -1)
+ err(EXIT_FAILURE, "%s", dsf);
+ if (lseek(fd, VTOC_BOOTSIZE, SEEK_SET) != VTOC_BOOTSIZE)
+ continue;
+ /*
+ * We ignore the first VTOC_BOOTSIZE bytes of boot code in
+ * order to avoid overwriting the label.
+ */
+ if (lseek(fd, sizeof(struct vtoc8), SEEK_SET) !=
+ sizeof(struct vtoc8))
+ err(EXIT_FAILURE, "%s", dsf);
+ if (write(fd, (caddr_t)code + sizeof(struct vtoc8),
+ VTOC_BOOTSIZE - sizeof(struct vtoc8)) != VTOC_BOOTSIZE -
+ sizeof(struct vtoc8))
+ err(EXIT_FAILURE, "%s", dsf);
+ installed++;
+ close(fd);
+ if (idx != 0 && atoi(s) == idx)
+ break;
+ }
+ if (installed == 0)
+ errx(EXIT_FAILURE, "%s: no partitions", gp->lg_name);
+}
+
+static void
+gpart_bootcode(struct gctl_req *req, unsigned int fl)
+{
+ struct gmesh mesh;
+ struct gclass *classp;
+ struct ggeom *gp;
+ const char *s;
+ void *bootcode, *partcode;
+ size_t bootsize, partsize;
+ int error, idx, vtoc8;
+
+ if (gctl_has_param(req, GPART_PARAM_BOOTCODE)) {
+ s = gctl_get_ascii(req, GPART_PARAM_BOOTCODE);
+ bootsize = 800 * 1024; /* Arbitrary limit. */
+ bootcode = gpart_bootfile_read(s, &bootsize);
+ error = gctl_change_param(req, GPART_PARAM_BOOTCODE, bootsize,
+ bootcode);
+ if (error)
+ errc(EXIT_FAILURE, error, "internal error");
+ } else {
+ bootcode = NULL;
+ bootsize = 0;
+ }
+
+ s = gctl_get_ascii(req, "class");
+ if (s == NULL)
+ abort();
+ error = geom_gettree(&mesh);
+ if (error != 0)
+ errc(EXIT_FAILURE, error, "Cannot get GEOM tree");
+ classp = find_class(&mesh, s);
+ if (classp == NULL) {
+ geom_deletetree(&mesh);
+ errx(EXIT_FAILURE, "Class %s not found.", s);
+ }
+ if (gctl_get_int(req, "nargs") != 1)
+ errx(EXIT_FAILURE, "Invalid number of arguments.");
+ s = gctl_get_ascii(req, "arg0");
+ if (s == NULL)
+ abort();
+ gp = find_geom(classp, s);
+ if (gp == NULL)
+ errx(EXIT_FAILURE, "No such geom: %s.", s);
+ s = find_geomcfg(gp, "scheme");
+ if (s == NULL)
+ errx(EXIT_FAILURE, "Scheme not found for geom %s", gp->lg_name);
+ vtoc8 = 0;
+ if (strcmp(s, "VTOC8") == 0)
+ vtoc8 = 1;
+
+ if (gctl_has_param(req, GPART_PARAM_PARTCODE)) {
+ s = gctl_get_ascii(req, GPART_PARAM_PARTCODE);
+ partsize = vtoc8 != 0 ? VTOC_BOOTSIZE : bootsize * 1024;
+ partcode = gpart_bootfile_read(s, &partsize);
+ error = gctl_delete_param(req, GPART_PARAM_PARTCODE);
+ if (error)
+ errc(EXIT_FAILURE, error, "internal error");
+ } else {
+ partcode = NULL;
+ partsize = 0;
+ }
+
+ if (gctl_has_param(req, GPART_PARAM_INDEX)) {
+ if (partcode == NULL)
+ errx(EXIT_FAILURE, "-i is only valid with -p");
+ idx = (int)gctl_get_intmax(req, GPART_PARAM_INDEX);
+ if (idx < 1)
+ errx(EXIT_FAILURE, "invalid partition index");
+ error = gctl_delete_param(req, GPART_PARAM_INDEX);
+ if (error)
+ errc(EXIT_FAILURE, error, "internal error");
+ } else
+ idx = 0;
+
+ if (partcode != NULL) {
+ if (vtoc8 == 0) {
+ if (idx == 0)
+ errx(EXIT_FAILURE, "missing -i option");
+ gpart_write_partcode(gp, idx, partcode, partsize);
+ } else {
+ if (partsize != VTOC_BOOTSIZE)
+ errx(EXIT_FAILURE, "invalid bootcode");
+ gpart_write_partcode_vtoc8(gp, idx, partcode);
+ }
+ } else
+ if (bootcode == NULL)
+ errx(EXIT_FAILURE, "no -b nor -p");
+
+ if (bootcode != NULL)
+ gpart_issue(req, fl);
+
+ geom_deletetree(&mesh);
+}
+
+static void
+gpart_print_error(const char *errstr)
+{
+ char *errmsg;
+ int error;
+
+ error = strtol(errstr, &errmsg, 0);
+ if (errmsg != errstr) {
+ while (errmsg[0] == ' ')
+ errmsg++;
+ if (errmsg[0] != '\0')
+ warnc(error, "%s", errmsg);
+ else
+ warnc(error, NULL);
+ } else
+ warnx("%s", errmsg);
+}
+
+static void
+gpart_issue(struct gctl_req *req, unsigned int fl __unused)
+{
+ char buf[4096];
+ const char *errstr;
+ int error, status;
+
+ if (gctl_get_int(req, "nargs") != 1)
+ errx(EXIT_FAILURE, "Invalid number of arguments.");
+ (void)gctl_delete_param(req, "nargs");
+
+ /* autofill parameters (if applicable). */
+ error = gpart_autofill(req);
+ if (error) {
+ warnc(error, "autofill");
+ status = EXIT_FAILURE;
+ goto done;
+ }
+
+ bzero(buf, sizeof(buf));
+ gctl_rw_param(req, "output", sizeof(buf), buf);
+ errstr = gctl_issue(req);
+ if (errstr == NULL || errstr[0] == '\0') {
+ if (buf[0] != '\0')
+ printf("%s", buf);
+ status = EXIT_SUCCESS;
+ goto done;
+ }
+
+ gpart_print_error(errstr);
+ status = EXIT_FAILURE;
+
+ done:
+ gctl_free(req);
+ exit(status);
+}
diff --git a/sbin/geom/class/part/gpart.8 b/sbin/geom/class/part/gpart.8
new file mode 100644
index 0000000..520f049
--- /dev/null
+++ b/sbin/geom/class/part/gpart.8
@@ -0,0 +1,1334 @@
+.\" Copyright (c) 2007, 2008 Marcel Moolenaar
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd March 12, 2015
+.Dt GPART 8
+.Os
+.Sh NAME
+.Nm gpart
+.Nd "control utility for the disk partitioning GEOM class"
+.Sh SYNOPSIS
+.\" ==== ADD ====
+.Nm
+.Cm add
+.Fl t Ar type
+.Op Fl a Ar alignment
+.Op Fl b Ar start
+.Op Fl s Ar size
+.Op Fl i Ar index
+.Op Fl l Ar label
+.Op Fl f Ar flags
+.Ar geom
+.\" ==== BACKUP ====
+.Nm
+.Cm backup
+.Ar geom
+.\" ==== BOOTCODE ====
+.Nm
+.Cm bootcode
+.Op Fl b Ar bootcode
+.Op Fl p Ar partcode Fl i Ar index
+.Op Fl f Ar flags
+.Ar geom
+.\" ==== COMMIT ====
+.Nm
+.Cm commit
+.Ar geom
+.\" ==== CREATE ====
+.Nm
+.Cm create
+.Fl s Ar scheme
+.Op Fl n Ar entries
+.Op Fl f Ar flags
+.Ar provider
+.\" ==== DELETE ====
+.Nm
+.Cm delete
+.Fl i Ar index
+.Op Fl f Ar flags
+.Ar geom
+.\" ==== DESTROY ====
+.Nm
+.Cm destroy
+.Op Fl F
+.Op Fl f Ar flags
+.Ar geom
+.\" ==== MODIFY ====
+.Nm
+.Cm modify
+.Fl i Ar index
+.Op Fl l Ar label
+.Op Fl t Ar type
+.Op Fl f Ar flags
+.Ar geom
+.\" ==== RECOVER ====
+.Nm
+.Cm recover
+.Op Fl f Ar flags
+.Ar geom
+.\" ==== RESIZE ====
+.Nm
+.Cm resize
+.Fl i Ar index
+.Op Fl a Ar alignment
+.Op Fl s Ar size
+.Op Fl f Ar flags
+.Ar geom
+.\" ==== RESTORE ====
+.Nm
+.Cm restore
+.Op Fl lF
+.Op Fl f Ar flags
+.Ar provider
+.Op Ar ...
+.\" ==== SET ====
+.Nm
+.Cm set
+.Fl a Ar attrib
+.Fl i Ar index
+.Op Fl f Ar flags
+.Ar geom
+.\" ==== SHOW ====
+.Nm
+.Cm show
+.Op Fl l | r
+.Op Fl p
+.Op Ar geom ...
+.\" ==== UNDO ====
+.Nm
+.Cm undo
+.Ar geom
+.\" ==== UNSET ====
+.Nm
+.Cm unset
+.Fl a Ar attrib
+.Fl i Ar index
+.Op Fl f Ar flags
+.Ar geom
+.\"
+.Nm
+.Cm list
+.Nm
+.Cm status
+.Nm
+.Cm load
+.Nm
+.Cm unload
+.Sh DESCRIPTION
+The
+.Nm
+utility is used to partition GEOM providers, normally disks.
+The first argument is the action to be taken:
+.Bl -tag -width ".Cm bootcode"
+.\" ==== ADD ====
+.It Cm add
+Add a new partition to the partitioning scheme given by
+.Ar geom .
+The partition begins on the logical block address given by the
+.Fl b Ar start
+option.
+Its size is given by the
+.Fl s Ar size
+option.
+SI unit suffixes are allowed.
+One or both
+.Fl b
+and
+.Fl s
+options can be omitted.
+If so they are automatically calculated.
+The type of the partition is given by the
+.Fl t Ar type
+option.
+Partition types are discussed below in the section entitled
+.Sx "PARTITION TYPES" .
+.Pp
+Additional options include:
+.Bl -tag -width 12n
+.It Fl a Ar alignment
+If specified, then
+.Nm
+utility tries to align
+.Ar start
+offset and partition
+.Ar size
+to be multiple of
+.Ar alignment
+value.
+.It Fl i Ar index
+The index in the partition table at which the new partition is to be
+placed.
+The index determines the name of the device special file used
+to represent the partition.
+.It Fl l Ar label
+The label attached to the partition.
+This option is only valid when used on partitioning schemes that support
+partition labels.
+.It Fl f Ar flags
+Additional operational flags.
+See the section entitled
+.Sx "OPERATIONAL FLAGS"
+below for a discussion
+about its use.
+.El
+.\" ==== BACKUP ====
+.It Cm backup
+Dump a partition table to standard output in a special format used by the
+.Cm restore
+action.
+.\" ==== BOOTCODE ====
+.It Cm bootcode
+Embed bootstrap code into the partitioning scheme's metadata on the
+.Ar geom
+(using
+.Fl b Ar bootcode )
+or write bootstrap code into a partition (using
+.Fl p Ar partcode
+and
+.Fl i Ar index ) .
+Not all partitioning schemes have embedded bootstrap code, so the
+.Fl b Ar bootcode
+option is scheme-specific in nature (see the section entitled
+.Sx BOOTSTRAPPING
+below).
+The
+.Fl b Ar bootcode
+option specifies a file that contains the bootstrap code.
+The contents and size of the file are determined by the partitioning
+scheme.
+The
+.Fl p Ar partcode
+option specifies a file that contains the bootstrap code intended to be
+written to a partition.
+The partition is specified by the
+.Fl i Ar index
+option.
+The size of the file must be smaller than the size of the partition.
+.Pp
+Additional options include:
+.Bl -tag -width 10n
+.It Fl f Ar flags
+Additional operational flags.
+See the section entitled
+.Sx "OPERATIONAL FLAGS"
+below for a discussion
+about its use.
+.El
+.\" ==== COMMIT ====
+.It Cm commit
+Commit any pending changes for geom
+.Ar geom .
+All actions are committed by default and will not result in
+pending changes.
+Actions can be modified with the
+.Fl f Ar flags
+option so that they are not committed, but become pending.
+Pending changes are reflected by the geom and the
+.Nm
+utility, but they are not actually written to disk.
+The
+.Cm commit
+action will write all pending changes to disk.
+.\" ==== CREATE ====
+.It Cm create
+Create a new partitioning scheme on a provider given by
+.Ar provider .
+The
+.Fl s Ar scheme
+option determines the scheme to use.
+The kernel must have support for a particular scheme before
+that scheme can be used to partition a disk.
+.Pp
+Additional options include:
+.Bl -tag -width 10n
+.It Fl n Ar entries
+The number of entries in the partition table.
+Every partitioning scheme has a minimum and maximum number of entries.
+This option allows tables to be created with a number of entries
+that is within the limits.
+Some schemes have a maximum equal to the minimum and some schemes have
+a maximum large enough to be considered unlimited.
+By default, partition tables are created with the minimum number of
+entries.
+.It Fl f Ar flags
+Additional operational flags.
+See the section entitled
+.Sx "OPERATIONAL FLAGS"
+below for a discussion
+about its use.
+.El
+.\" ==== DELETE ====
+.It Cm delete
+Delete a partition from geom
+.Ar geom
+and further identified by the
+.Fl i Ar index
+option.
+The partition cannot be actively used by the kernel.
+.Pp
+Additional options include:
+.Bl -tag -width 10n
+.It Fl f Ar flags
+Additional operational flags.
+See the section entitled
+.Sx "OPERATIONAL FLAGS"
+below for a discussion
+about its use.
+.El
+.\" ==== DESTROY ====
+.It Cm destroy
+Destroy the partitioning scheme as implemented by geom
+.Ar geom .
+.Pp
+Additional options include:
+.Bl -tag -width 10n
+.It Fl F
+Forced destroying of the partition table even if it is not empty.
+.It Fl f Ar flags
+Additional operational flags.
+See the section entitled
+.Sx "OPERATIONAL FLAGS"
+below for a discussion
+about its use.
+.El
+.\" ==== MODIFY ====
+.It Cm modify
+Modify a partition from geom
+.Ar geom
+and further identified by the
+.Fl i Ar index
+option.
+Only the type and/or label of the partition can be modified.
+To change the type of a partition, specify the new type with the
+.Fl t Ar type
+option.
+To change the label of a partition, specify the new label with the
+.Fl l Ar label
+option.
+Not all partitioning schemes support labels and it is invalid to
+try to change a partition label in such cases.
+.Pp
+Additional options include:
+.Bl -tag -width 10n
+.It Fl f Ar flags
+Additional operational flags.
+See the section entitled
+.Sx "OPERATIONAL FLAGS"
+below for a discussion
+about its use.
+.El
+.\" ==== RECOVER ====
+.It Cm recover
+Recover a corrupt partition's scheme metadata on the geom
+.Ar geom .
+See the section entitled
+.Sx RECOVERING
+below for the additional information.
+.Pp
+Additional options include:
+.Bl -tag -width 10n
+.It Fl f Ar flags
+Additional operational flags.
+See the section entitled
+.Sx "OPERATIONAL FLAGS"
+below for a discussion
+about its use.
+.El
+.\" ==== RESIZE ====
+.It Cm resize
+Resize a partition from geom
+.Ar geom
+and further identified by the
+.Fl i Ar index
+option.
+New partition size is expressed in logical block
+numbers and can be given by the
+.Fl s Ar size
+option.
+If
+.Fl s
+option is omitted then new size is automatically calculated
+to maximum available from given geom
+.Ar geom .
+.Pp
+Additional options include:
+.Bl -tag -width 12n
+.It Fl a Ar alignment
+If specified, then
+.Nm
+utility tries to align partition
+.Ar size
+to be multiple of
+.Ar alignment
+value.
+.It Fl f Ar flags
+Additional operational flags.
+See the section entitled
+.Sx "OPERATIONAL FLAGS"
+below for a discussion
+about its use.
+.El
+.\" ==== RESTORE ====
+.It Cm restore
+Restore the partition table from a backup previously created by the
+.Cm backup
+action and read from standard input.
+Only the partition table is restored.
+This action does not affect the content of partitions.
+After restoring the partition table and writing bootcode if needed,
+user data must be restored from backup.
+.Pp
+Additional options include:
+.Bl -tag -width 10n
+.It Fl F
+Destroy partition table on the given
+.Ar provider
+before doing restore.
+.It Fl l
+Restore partition labels for partitioning schemes that support them.
+.It Fl f Ar flags
+Additional operational flags.
+See the section entitled
+.Sx "OPERATIONAL FLAGS"
+below for a discussion
+about its use.
+.El
+.\" ==== SET ====
+.It Cm set
+Set the named attribute on the partition entry.
+See the section entitled
+.Sx ATTRIBUTES
+below for a list of available attributes.
+.Pp
+Additional options include:
+.Bl -tag -width 10n
+.It Fl f Ar flags
+Additional operational flags.
+See the section entitled
+.Sx "OPERATIONAL FLAGS"
+below for a discussion
+about its use.
+.El
+.\" ==== SHOW ====
+.It Cm show
+Show current partition information for the specified geoms, or all
+geoms if none are specified.
+The default output includes the logical starting block of each
+partition, the partition size in blocks, the partition index number,
+the partition type, and a human readable partition size.
+Block sizes and locations are based on the device's Sectorsize
+as shown by
+.Cm gpart list .
+Additional options include:
+.Bl -tag -width 10n
+.It Fl l
+For partitioning schemes that support partition labels, print them
+instead of partition type.
+.It Fl p
+Show provider names instead of partition indexes.
+.It Fl r
+Show raw partition type instead of symbolic name.
+.El
+.\" ==== UNDO ====
+.It Cm undo
+Revert any pending changes for geom
+.Ar geom .
+This action is the opposite of the
+.Cm commit
+action and can be used to undo any changes that have not been committed.
+.\" ==== UNSET ====
+.It Cm unset
+Clear the named attribute on the partition entry.
+See the section entitled
+.Sx ATTRIBUTES
+below for a list of available attributes.
+.Pp
+Additional options include:
+.Bl -tag -width 10n
+.It Fl f Ar flags
+Additional operational flags.
+See the section entitled
+.Sx "OPERATIONAL FLAGS"
+below for a discussion
+about its use.
+.El
+.It Cm list
+See
+.Xr geom 8 .
+.It Cm status
+See
+.Xr geom 8 .
+.It Cm load
+See
+.Xr geom 8 .
+.It Cm unload
+See
+.Xr geom 8 .
+.El
+.Sh PARTITIONING SCHEMES
+Several partitioning schemes are supported by the
+.Nm
+utility:
+.Bl -tag -width ".Cm VTOC8"
+.It Cm APM
+Apple Partition Map, used by PowerPC(R) Macintosh(R) computers.
+Requires the
+.Cd GEOM_PART_APM
+kernel option.
+.It Cm BSD
+Traditional BSD disklabel, usually used to subdivide MBR partitions.
+.Po
+This scheme can also be used as the sole partitioning method, without
+an MBR.
+Partition editing tools from other operating systems often do not
+understand the bare disklabel partition layout, so this is sometimes
+called
+.Dq dangerously dedicated .
+.Pc
+Requires the
+.Cm GEOM_PART_BSD
+kernel option.
+.It Cm BSD64
+64-bit implementation of BSD disklabel used in DragonFlyBSD to subdivide MBR
+or GPT partitions.
+Requires the
+.Cm GEOM_PART_BSD64
+kernel option.
+.It Cm LDM
+The Logical Disk Manager is an implementation of volume manager for
+Microsoft Windows NT.
+Requires the
+.Cd GEOM_PART_LDM
+kernel option.
+.It Cm GPT
+GUID Partition Table is used on Intel-based Macintosh computers and
+gradually replacing MBR on most PCs and other systems.
+Requires the
+.Cm GEOM_PART_GPT
+kernel option.
+.It Cm MBR
+Master Boot Record is used on PCs and removable media.
+Requires the
+.Cm GEOM_PART_MBR
+kernel option.
+The
+.Cm GEOM_PART_EBR
+option adds support for the Extended Boot Record (EBR),
+which is used to define a logical partition.
+The
+.Cm GEOM_PART_EBR_COMPAT
+option enables backward compatibility for partition names
+in the EBR scheme.
+It also prevents any type of actions on such partitions.
+.It Cm PC98
+An MBR variant for NEC PC-98 and compatible computers.
+Requires the
+.Cm GEOM_PART_PC98
+kernel option.
+.It Cm VTOC8
+Sun's SMI Volume Table Of Contents, used by
+.Tn SPARC64
+and
+.Tn UltraSPARC
+computers.
+Requires the
+.Cm GEOM_PART_VTOC8
+kernel option.
+.El
+.Sh PARTITION TYPES
+Partition types are identified on disk by particular strings or magic
+values.
+The
+.Nm
+utility uses symbolic names for common partition types so the user
+does not need to know these values or other details of the partitioning
+scheme in question.
+The
+.Nm
+utility also allows the user to specify scheme-specific partition types
+for partition types that do not have symbolic names.
+Symbolic names currently understood and used by
+.Fx
+are:
+.Bl -tag -width ".Cm dragonfly-disklabel64"
+.It Cm apple-boot
+The system partition dedicated to storing boot loaders on some Apple
+systems.
+The scheme-specific types are
+.Qq Li "!171"
+for MBR,
+.Qq Li "!Apple_Bootstrap"
+for APM, and
+.Qq Li "!426f6f74-0000-11aa-aa11-00306543ecac"
+for GPT.
+.It Cm bios-boot
+The system partition dedicated to second stage of the boot loader program.
+Usually it is used by the GRUB 2 loader for GPT partitioning schemes.
+The scheme-specific type is
+.Qq Li "!21686148-6449-6E6F-744E-656564454649" .
+.It Cm efi
+The system partition for computers that use the Extensible Firmware
+Interface (EFI).
+In such cases, the GPT partitioning scheme is used and the
+actual partition type for the system partition can also be specified as
+.Qq Li "!c12a7328-f81f-11d2-ba4b-00a0c93ec93b" .
+.It Cm freebsd
+A
+.Fx
+partition subdivided into filesystems with a
+.Bx
+disklabel.
+This is a legacy partition type and should not be used for the APM
+or GPT schemes.
+The scheme-specific types are
+.Qq Li "!165"
+for MBR,
+.Qq Li "!FreeBSD"
+for APM, and
+.Qq Li "!516e7cb4-6ecf-11d6-8ff8-00022d09712b"
+for GPT.
+.It Cm freebsd-boot
+A
+.Fx
+partition dedicated to bootstrap code.
+The scheme-specific type is
+.Qq Li "!83bd6b9d-7f41-11dc-be0b-001560b84f0f"
+for GPT.
+.It Cm freebsd-swap
+A
+.Fx
+partition dedicated to swap space.
+The scheme-specific types are
+.Qq Li "!FreeBSD-swap"
+for APM,
+.Qq Li "!516e7cb5-6ecf-11d6-8ff8-00022d09712b"
+for GPT, and tag 0x0901 for VTOC8.
+.It Cm freebsd-ufs
+A
+.Fx
+partition that contains a UFS or UFS2 filesystem.
+The scheme-specific types are
+.Qq Li "!FreeBSD-UFS"
+for APM,
+.Qq Li "!516e7cb6-6ecf-11d6-8ff8-00022d09712b"
+for GPT, and tag 0x0902 for VTOC8.
+.It Cm freebsd-vinum
+A
+.Fx
+partition that contains a Vinum volume.
+The scheme-specific types are
+.Qq Li "!FreeBSD-Vinum"
+for APM,
+.Qq Li "!516e7cb8-6ecf-11d6-8ff8-00022d09712b"
+for GPT, and tag 0x0903 for VTOC8.
+.It Cm freebsd-zfs
+A
+.Fx
+partition that contains a ZFS volume.
+The scheme-specific types are
+.Qq Li "!FreeBSD-ZFS"
+for APM,
+.Qq Li "!516e7cba-6ecf-11d6-8ff8-00022d09712b"
+for GPT, and 0x0904 for VTOC8.
+.El
+.Pp
+Another symbolic names that can be used with
+.Cm gpart
+utility are:
+.Bl -tag -width ".Cm dragonfly-disklabel64"
+.It Cm apple-core-storage
+An Apple Mac OS X partition used by logical volume manager known as
+Core Storage.
+The scheme-specific type is
+.Qq Li "!53746f72-6167-11aa-aa11-00306543ecac"
+for GPT.
+.It Cm apple-hfs
+An Apple Mac OS X partition that contains a HFS or HFS+ filesystem.
+The scheme-specific types are
+.Qq Li "!Apple_HFS"
+for APM and
+.Qq Li "!48465300-0000-11aa-aa11-00306543ecac"
+for GPT.
+.It Cm apple-label
+An Apple Mac OS X partition dedicated to partition metadata that descibes
+disk device.
+The scheme-specific type is
+.Qq Li "!4c616265-6c00-11aa-aa11-00306543ecac"
+for GPT.
+.It Cm apple-raid
+An Apple Mac OS X partition used in a software RAID configuration.
+The scheme-specific type is
+.Qq Li "!52414944-0000-11aa-aa11-00306543ecac"
+for GPT.
+.It Cm apple-raid-offline
+An Apple Mac OS X partition used in a software RAID configuration.
+The scheme-specific type is
+.Qq Li "!52414944-5f4f-11aa-aa11-00306543ecac"
+for GPT.
+.It Cm apple-tv-recovery
+An Apple Mac OS X partition used by Apple TV.
+The scheme-specific type is
+.Qq Li "!5265636f-7665-11aa-aa11-00306543ecac"
+for GPT.
+.It Cm apple-ufs
+An Apple Mac OS X partition that contains a UFS filesystem.
+The scheme-specific types are
+.Qq Li "!Apple_UNIX_SVR2"
+for APM and
+.Qq Li "!55465300-0000-11aa-aa11-00306543ecac"
+for GPT.
+.It Cm dragonfly-label32
+A DragonFlyBSD partition subdivided into filesystems with a
+.Bx
+disklabel.
+The scheme-specific type is
+.Qq Li "!9d087404-1ca5-11dc-8817-01301bb8a9f5"
+for GPT.
+.It Cm dragonfly-label64
+A DragonFlyBSD partition subdivided into filesystems with a
+disklabel64.
+The scheme-specific type is
+.Qq Li "!3d48ce54-1d16-11dc-8696-01301bb8a9f5"
+for GPT.
+.It Cm dragonfly-legacy
+A legacy partition type used in DragonFlyBSD.
+The scheme-specific type is
+.Qq Li "!bd215ab2-1d16-11dc-8696-01301bb8a9f5"
+for GPT.
+.It Cm dragonfly-ccd
+A DragonFlyBSD partition used with Concatenated Disk driver.
+The scheme-specific type is
+.Qq Li "!dbd5211b-1ca5-11dc-8817-01301bb8a9f5"
+for GPT.
+.It Cm dragonfly-hammer
+A DragonFlyBSD partition that contains a Hammer filesystem.
+The scheme-specific type is
+.Qq Li "!61dc63ac-6e38-11dc-8513-01301bb8a9f5"
+for GPT.
+.It Cm dragonfly-hammer2
+A DragonFlyBSD partition that contains a Hammer2 filesystem.
+The scheme-specific type is
+.Qq Li "!5cbb9ad1-862d-11dc-a94d-01301bb8a9f5"
+for GPT.
+.It Cm dragonfly-swap
+A DragonFlyBSD partition dedicated to swap space.
+The scheme-specific type is
+.Qq Li "!9d58fdbd-1ca5-11dc-8817-01301bb8a9f5"
+for GPT.
+.It Cm dragonfly-ufs
+A DragonFlyBSD partition that contains an UFS1 filesystem.
+The scheme-specific type is
+.Qq Li "!9d94ce7c-1ca5-11dc-8817-01301bb8a9f5"
+for GPT.
+.It Cm dragonfly-vinum
+A DragonFlyBSD partition used with Logical Volume Manager.
+The scheme-specific type is
+.Qq Li "!9dd4478f-1ca5-11dc-8817-01301bb8a9f5"
+for GPT.
+.It Cm ebr
+A partition subdivided into filesystems with a EBR.
+The scheme-specific type is
+.Qq Li "!5"
+for MBR.
+.It Cm fat16
+A partition that contains a FAT16 filesystem.
+The scheme-specific type is
+.Qq Li "!6"
+for MBR.
+.It Cm fat32
+A partition that contains a FAT32 filesystem.
+The scheme-specific type is
+.Qq Li "!11"
+for MBR.
+.It Cm linux-data
+A Linux partition that contains some filesystem with data.
+The scheme-specific types are
+.Qq Li "!131"
+for MBR and
+.Qq Li "!0fc63daf-8483-4772-8e79-3d69d8477de4"
+for GPT.
+.It Cm linux-lvm
+A Linux partition dedicated to Logical Volume Manager.
+The scheme-specific types are
+.Qq Li "!142"
+for MBR and
+.Qq Li "!e6d6d379-f507-44c2-a23c-238f2a3df928"
+for GPT.
+.It Cm linux-raid
+A Linux partition used in a software RAID configuration.
+The scheme-specific types are
+.Qq Li "!253"
+for MBR and
+.Qq Li "!a19d880f-05fc-4d3b-a006-743f0f84911e"
+for GPT.
+.It Cm linux-swap
+A Linux partition dedicated to swap space.
+The scheme-specific types are
+.Qq Li "!130"
+for MBR and
+.Qq Li "!0657fd6d-a4ab-43c4-84e5-0933c84b4f4f"
+for GPT.
+.It Cm mbr
+A partition that is sub-partitioned by a Master Boot Record (MBR).
+This type is known as
+.Qq Li "!024dee41-33e7-11d3-9d69-0008c781f39f"
+by GPT.
+.It Cm ms-basic-data
+A basic data partition (BDP) for Microsoft operating systems.
+In the GPT this type is the equivalent to partition types
+.Cm fat16 , fat32
+and
+.Cm ntfs
+in MBR.
+The scheme-specific type is
+.Qq Li "!ebd0a0a2-b9e5-4433-87c0-68b6b72699c7"
+for GPT.
+.It Cm ms-ldm-data
+A partition that contains Logical Disk Manager (LDM) volumes.
+The scheme-specific types are
+.Qq Li "!66"
+for MBR,
+.Qq Li "!af9b60a0-1431-4f62-bc68-3311714a69ad"
+for GPT.
+.It Cm ms-ldm-metadata
+A partition that contains Logical Disk Manager (LDM) database.
+The scheme-specific type is
+.Qq Li "!5808c8aa-7e8f-42e0-85d2-e1e90434cfb3"
+for GPT.
+.It Cm netbsd-ccd
+A NetBSD partition used with Concatenated Disk driver.
+The scheme-specific type is
+.Qq Li "!2db519c4-b10f-11dc-b99b-0019d1879648"
+for GPT.
+.It Cm netbsd-cgd
+An encrypted NetBSD partition.
+The scheme-specific type is
+.Qq Li "!2db519ec-b10f-11dc-b99b-0019d1879648"
+for GPT.
+.It Cm netbsd-ffs
+A NetBSD partition that contains an UFS filesystem.
+The scheme-specific type is
+.Qq Li "!49f48d5a-b10e-11dc-b99b-0019d1879648"
+for GPT.
+.It Cm netbsd-lfs
+A NetBSD partition that contains an LFS filesystem.
+The scheme-specific type is
+.Qq Li "!49f48d82-b10e-11dc-b99b-0019d1879648"
+for GPT.
+.It Cm netbsd-raid
+A NetBSD partition used in a software RAID configuration.
+The scheme-specific type is
+.Qq Li "!49f48daa-b10e-11dc-b99b-0019d1879648"
+for GPT.
+.It Cm netbsd-swap
+A NetBSD partition dedicated to swap space.
+The scheme-specific type is
+.Qq Li "!49f48d32-b10e-11dc-b99b-0019d1879648"
+for GPT.
+.It Cm ntfs
+A partition that contains a NTFS or exFAT filesystem.
+The scheme-specific type is
+.Qq Li "!7"
+for MBR.
+.It Cm prep-boot
+The system partition dedicated to storing boot loaders on some PowerPC systems,
+notably those made by IBM.
+The scheme-specific types are
+.Qq Li "!65"
+for MBR and
+.Qq Li "!0x9e1a2d38-c612-4316-aa26-8b49521e5a8b"
+for GPT.
+.It Cm vmware-vmfs
+A partition that contains a VMware File System (VMFS).
+The scheme-specific types are
+.Qq Li "!251"
+for MBR and
+.Qq Li "!aa31e02a-400f-11db-9590-000c2911d1b8"
+for GPT.
+.It Cm vmware-vmkdiag
+A partition that contains a VMware diagostic filesystem.
+The scheme-specific types are
+.Qq Li "!252"
+for MBR and
+.Qq Li "!9d275380-40ad-11db-bf97-000c2911d1b8"
+for GPT.
+.It Cm vmware-reserved
+A VMware reserved partition.
+The scheme-specific type is
+.Qq Li "!9198effc-31c0-11db-8f-78-000c2911d1b8"
+for GPT.
+.It Cm vmware-vsanhdr
+A partition claimed by VMware VSAN.
+The scheme-specific type is
+.Qq Li "!381cfccc-7288-11e0-92ee-000c2911d0b2"
+for GPT.
+.El
+.Sh ATTRIBUTES
+The scheme-specific attributes for EBR:
+.Bl -tag -width ".Cm active"
+.It Cm active
+.El
+.Pp
+The scheme-specific attributes for GPT:
+.Bl -tag -width ".Cm bootfailed"
+.It Cm bootme
+When set, the
+.Nm gptboot
+stage 1 boot loader will try to boot the system from this partition.
+Multiple partitions can be marked with the
+.Cm bootme
+attribute.
+See
+.Xr gptboot 8
+for more details.
+.It Cm bootonce
+Setting this attribute automatically sets the
+.Cm bootme
+attribute.
+When set, the
+.Nm gptboot
+stage 1 boot loader will try to boot the system from this partition only once.
+Multiple partitions can be marked with the
+.Cm bootonce
+and
+.Cm bootme
+attribute pairs.
+See
+.Xr gptboot 8
+for more details.
+.It Cm bootfailed
+This attribute should not be manually managed.
+It is managed by the
+.Nm gptboot
+stage 1 boot loader and the
+.Pa /etc/rc.d/gptboot
+start-up script.
+See
+.Xr gptboot 8
+for more details.
+.El
+.Pp
+The scheme-specific attributes for MBR:
+.Bl -tag -width ".Cm active"
+.It Cm active
+.El
+.Pp
+The scheme-specific attributes for PC98:
+.Bl -tag -width ".Cm bootable"
+.It Cm active
+.It Cm bootable
+.El
+.Sh BOOTSTRAPPING
+.Fx
+supports several partitioning schemes and each scheme uses different
+bootstrap code.
+The bootstrap code is located in a specific disk area for each partitioning
+scheme, and may vary in size for different schemes.
+.Pp
+Bootstrap code can be separated into two types.
+The first type is embedded in the partitioning scheme's metadata, while the
+second type is located on a specific partition.
+Embedding bootstrap code should only be done with the
+.Cm gpart bootcode
+command with the
+.Fl b Ar bootcode
+option.
+The GEOM PART class knows how to safely embed bootstrap code into
+specific partitioning scheme metadata without causing any damage.
+.Pp
+The Master Boot Record (MBR) uses a 512-byte bootstrap code image, embedded
+into the partition table's metadata area.
+There are two variants of this bootstrap code:
+.Pa /boot/mbr
+and
+.Pa /boot/boot0 .
+.Pa /boot/mbr
+searches for a partition with the
+.Cm active
+attribute (see the
+.Sx ATTRIBUTES
+section) in the partition table.
+Then it runs next bootstrap stage.
+The
+.Pa /boot/boot0
+image contains a boot manager with some additional interactive functions
+for multi-booting from a user-selected partition.
+.Pp
+A BSD disklabel is usually created inside an MBR partition (slice)
+with type
+.Cm freebsd
+(see the
+.Sx "PARTITION TYPES"
+section).
+It uses 8 KB size bootstrap code image
+.Pa /boot/boot ,
+embedded into the partition table's metadata area.
+.Pp
+Both types of bootstrap code are used to boot from the GUID Partition Table.
+First, a protective MBR is embedded into the first disk sector from the
+.Pa /boot/pmbr
+image.
+It searches through the GPT for a
+.Cm freebsd-boot
+partition (see the
+.Sx "PARTITION TYPES"
+section) and runs the next bootstrap stage from it.
+The
+.Cm freebsd-boot
+partition should be smaller than 545 KB.
+It can be located either before or after other
+.Fx
+partitions on the disk.
+There are two variants of bootstrap code to write to this partition:
+.Pa /boot/gptboot
+and
+.Pa /boot/gptzfsboot .
+.Pp
+.Pa /boot/gptboot
+is used to boot from UFS partitions.
+.Cm gptboot
+searches through
+.Cm freebsd-ufs
+partitions in the GPT and selects one to boot based on the
+.Cm bootonce
+and
+.Cm bootme
+attributes.
+If neither attribute is found,
+.Pa /boot/gptboot
+boots from the first
+.Cm freebsd-ufs
+partition.
+.Pa /boot/loader
+.Pq the third bootstrap stage
+is loaded from the first partition that matches these conditions.
+See
+.Xr gptboot 8
+for more information.
+.Pp
+.Pa /boot/gptzfsboot
+is used to boot from ZFS.
+It searches through the GPT for
+.Cm freebsd-zfs
+partitions, trying to detect ZFS pools.
+After all pools are detected,
+.Pa /boot/zfsloader
+is started from the first one found.
+.Pp
+The VTOC8 scheme does not support embedding bootstrap code.
+Instead, the 8 KBytes bootstrap code image
+.Pa /boot/boot1
+should be written with the
+.Cm gpart bootcode
+command with the
+.Fl p Ar bootcode
+option to all sufficiently large VTOC8 partitions.
+To do this the
+.Fl i Ar index
+option could be omitted.
+.Pp
+The APM scheme also does not support embedding bootstrap code.
+Instead, the 800 KBytes bootstrap code image
+.Pa /boot/boot1.hfs
+should be written with the
+.Cm gpart bootcode
+command to a partition of type
+.Cm apple-boot ,
+which should also be 800 KB in size.
+.Sh OPERATIONAL FLAGS
+Actions other than the
+.Cm commit
+and
+.Cm undo
+actions take an optional
+.Fl f Ar flags
+option.
+This option is used to specify action-specific operational flags.
+By default, the
+.Nm
+utility defines the
+.Ql C
+flag so that the action is immediately
+committed.
+The user can specify
+.Dq Fl f Cm x
+to have the action result in a pending change that can later, with
+other pending changes, be committed as a single compound change with
+the
+.Cm commit
+action or reverted with the
+.Cm undo
+action.
+.Sh RECOVERING
+The GEOM PART class supports recovering of partition tables only for GPT.
+The GPT primary metadata is stored at the beginning of the device.
+For redundancy, a secondary
+.Pq backup
+copy of the metadata is stored at the end of the device.
+As a result of having two copies, some corruption of metadata is not
+fatal to the working of GPT.
+When the kernel detects corrupt metadata, it marks this table as corrupt
+and reports the problem.
+.Cm destroy
+and
+.Cm recover
+are the only operations allowed on corrupt tables.
+.Pp
+If the first sector of a provider is corrupt, the kernel can not detect GPT
+even if the partition table itself is not corrupt.
+The protective MBR can be rewritten using the
+.Xr dd 1
+command, to restore the ability to detect the GPT.
+The copy of the protective MBR is usually located in the
+.Pa /boot/pmbr
+file.
+.Pp
+If one GPT header appears to be corrupt but the other copy remains intact,
+the kernel will log the following:
+.Bd -literal -offset indent
+GEOM: provider: the primary GPT table is corrupt or invalid.
+GEOM: provider: using the secondary instead -- recovery strongly advised.
+.Ed
+.Pp
+or
+.Bd -literal -offset indent
+GEOM: provider: the secondary GPT table is corrupt or invalid.
+GEOM: provider: using the primary only -- recovery suggested.
+.Ed
+.Pp
+Also
+.Nm
+commands such as
+.Cm show , status
+and
+.Cm list
+will report about corrupt tables.
+.Pp
+If the size of the device has changed (e.g.,\& volume expansion) the
+secondary GPT header will no longer be located in the last sector.
+This is not a metadata corruption, but it is dangerous because any
+corruption of the primary GPT will lead to loss of the partition table.
+This problem is reported by the kernel with the message:
+.Bd -literal -offset indent
+GEOM: provider: the secondary GPT header is not in the last LBA.
+.Ed
+.Pp
+This situation can be recovered with the
+.Cm recover
+command.
+This command reconstructs the corrupt metadata using known valid
+metadata and relocates the secondary GPT to the end of the device.
+.Pp
+.Em NOTE :
+The GEOM PART class can detect the same partition table visible through
+different GEOM providers, and some of them will be marked as corrupt.
+Be careful when choosing a provider for recovery.
+If you choose incorrectly you can destroy the metadata of another GEOM class,
+e.g.,\& GEOM MIRROR or GEOM LABEL.
+.Sh SYSCTL VARIABLES
+The following
+.Xr sysctl 8
+variables can be used to control the behavior of the
+.Nm PART
+GEOM class.
+The default value is shown next to each variable.
+.Bl -tag -width indent
+.It Va kern.geom.part.check_integrity : No 1
+This variable controls the behaviour of metadata integrity checks.
+When integrity checks are enabled, the
+.Nm PART
+GEOM class verifies all generic partition parameters obtained from the
+disk metadata.
+If some inconsistency is detected, the partition table will be
+rejected with a diagnostic message:
+.Sy "GEOM_PART: Integrity check failed (provider, scheme)" .
+.It Va kern.geom.part.ldm.debug : No 0
+Debug level of the Logical Disk Manager (LDM) module.
+This can be set to a number between 0 and 2 inclusive.
+If set to 0 minimal debug information is printed,
+and if set to 2 the maximum amount of debug information is printed.
+.It Va kern.geom.part.ldm.show_mirrors : No 0
+This variable controls how the Logical Disk Manager (LDM) module handles
+mirrored volumes.
+By default mirrored volumes are shown as partitions with type
+.Cm ms-ldm-data
+(see the
+.Sx "PARTITION TYPES"
+section).
+If this variable set to 1 each component of the mirrored volume will be
+present as independent partition.
+.Em NOTE :
+This may break a mirrored volume and lead to data damage.
+.It Va kern.geom.part.mbr.enforce_chs : No 0
+Specify how the Master Boot Record (MBR) module does alignment.
+If this variable is set to a non-zero value, the module will automatically
+recalculate the user-specified offset and size for alignment with the CHS
+geometry.
+Otherwise the values will be left unchanged.
+.El
+.Sh EXIT STATUS
+Exit status is 0 on success, and 1 if the command fails.
+.Sh EXAMPLES
+Create a GPT scheme on
+.Pa ada0 :
+.Bd -literal -offset indent
+/sbin/gpart create -s GPT ada0
+.Ed
+.Pp
+Embed GPT bootstrap code into a protective MBR:
+.Bd -literal -offset indent
+/sbin/gpart bootcode -b /boot/pmbr ada0
+.Ed
+.Pp
+Create a dedicated
+.Cm freebsd-boot
+partition that can boot
+.Fx
+from a
+.Cm freebsd-ufs
+partition, and install bootstrap code into it.
+This partition must be larger than the bootstrap code
+.Po
+usually either
+.Pa /boot/gptboot
+or
+.Pa /boot/gptzfsboot
+.Pc ,
+but smaller than 545 kB since the first-stage loader will load the
+entire partition into memory during boot, regardless of how much data
+it actually contains.
+This example uses 88 blocks (44 kB) so the next partition will be
+aligned on a 64 kB boundary without the need to specify an explicit
+offset or alignment.
+The boot partition itself is aligned on a 4 kB boundary.
+.Bd -literal -offset indent
+/sbin/gpart add -b 40 -s 88 -t freebsd-boot ada0
+/sbin/gpart bootcode -p /boot/gptboot -i 1 ada0
+.Ed
+.Pp
+Create a 512MB-sized
+.Cm freebsd-ufs
+partition to contain a UFS filesystem from which the system can boot.
+.Bd -literal -offset indent
+/sbin/gpart add -s 512M -t freebsd-ufs ada0
+.Ed
+.Pp
+Create an MBR scheme on
+.Pa ada0 ,
+then create a 30GB-sized
+.Fx
+slice, mark it active and
+install the
+.Nm boot0
+boot manager:
+.Bd -literal -offset indent
+/sbin/gpart create -s MBR ada0
+/sbin/gpart add -t freebsd -s 30G ada0
+/sbin/gpart set -a active -i 1 ada0
+/sbin/gpart bootcode -b /boot/boot0 ada0
+.Ed
+.Pp
+Now create a
+.Bx
+scheme
+.Pf ( Bx
+label) with space for up to 20 partitions:
+.Bd -literal -offset indent
+/sbin/gpart create -s BSD -n 20 ada0s1
+.Ed
+.Pp
+Create a 1GB-sized UFS partition and a 4GB-sized swap partition:
+.Bd -literal -offset indent
+/sbin/gpart add -t freebsd-ufs -s 1G ada0s1
+/sbin/gpart add -t freebsd-swap -s 4G ada0s1
+.Ed
+.Pp
+Install bootstrap code for the
+.Bx
+label:
+.Bd -literal -offset indent
+/sbin/gpart bootcode -b /boot/boot ada0s1
+.Ed
+.Pp
+Create a VTOC8 scheme on
+.Pa da0 :
+.Bd -literal -offset indent
+/sbin/gpart create -s VTOC8 da0
+.Ed
+.Pp
+Create a 512MB-sized
+.Cm freebsd-ufs
+partition to contain a UFS filesystem from which the system can boot.
+.Bd -literal -offset indent
+/sbin/gpart add -s 512M -t freebsd-ufs da0
+.Ed
+.Pp
+Create a 15GB-sized
+.Cm freebsd-ufs
+partition to contain a UFS filesystem and aligned on 4KB boundaries:
+.Bd -literal -offset indent
+/sbin/gpart add -s 15G -t freebsd-ufs -a 4k da0
+.Ed
+.Pp
+After creating all required partitions, embed bootstrap code into them:
+.Bd -literal -offset indent
+/sbin/gpart bootcode -p /boot/boot1 da0
+.Ed
+.Pp
+Create a backup of the partition table from
+.Pa da0 :
+.Bd -literal -offset indent
+/sbin/gpart backup da0 > da0.backup
+.Ed
+.Pp
+Restore the partition table from the backup to
+.Pa da0 :
+.Bd -literal -offset indent
+/sbin/gpart restore -l da0 < /mnt/da0.backup
+.Ed
+.Pp
+Clone the partition table from
+.Pa ada0
+to
+.Pa ada1
+and
+.Pa ada2 :
+.Bd -literal -offset indent
+/sbin/gpart backup ada0 | /sbin/gpart restore -F ada1 ada2
+.Ed
+.Sh SEE ALSO
+.Xr dd 1 ,
+.Xr geom 4 ,
+.Xr boot0cfg 8 ,
+.Xr geom 8 ,
+.Xr gptboot 8
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Fx 7.0 .
+.Sh AUTHORS
+.An Marcel Moolenaar Aq Mt marcel@FreeBSD.org
diff --git a/sbin/geom/class/raid/Makefile b/sbin/geom/class/raid/Makefile
new file mode 100644
index 0000000..07d7140
--- /dev/null
+++ b/sbin/geom/class/raid/Makefile
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../../misc
+
+GEOM_CLASS= raid
+
+LIBADD= md
+
+.include <bsd.lib.mk>
diff --git a/sbin/geom/class/raid/geom_raid.c b/sbin/geom/class/raid/geom_raid.c
new file mode 100644
index 0000000..d3383bc
--- /dev/null
+++ b/sbin/geom/class/raid/geom_raid.c
@@ -0,0 +1,92 @@
+/*-
+ * Copyright (c) 2010 Alexander Motin <mav@FreeBSD.org>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <errno.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <strings.h>
+#include <assert.h>
+#include <libgeom.h>
+#include <geom/raid/g_raid.h>
+#include <core/geom.h>
+#include <misc/subr.h>
+
+uint32_t lib_version = G_LIB_VERSION;
+uint32_t version = G_RAID_VERSION;
+
+struct g_command class_commands[] = {
+ { "label", G_FLAG_VERBOSE, NULL,
+ {
+ { 'f', "force", NULL, G_TYPE_BOOL },
+ { 'o', "fmtopt", G_VAL_OPTIONAL, G_TYPE_STRING },
+ { 'S', "size", G_VAL_OPTIONAL, G_TYPE_NUMBER },
+ { 's', "strip", G_VAL_OPTIONAL, G_TYPE_NUMBER },
+ G_OPT_SENTINEL
+ },
+ "[-fv] [-o fmtopt] [-S size] [-s stripsize] format label level prov ..."
+ },
+ { "add", G_FLAG_VERBOSE, NULL,
+ {
+ { 'f', "force", NULL, G_TYPE_BOOL },
+ { 'S', "size", G_VAL_OPTIONAL, G_TYPE_NUMBER },
+ { 's', "strip", G_VAL_OPTIONAL, G_TYPE_NUMBER },
+ G_OPT_SENTINEL
+ },
+ "[-fv] [-S size] [-s stripsize] name label level"
+ },
+ { "delete", G_FLAG_VERBOSE, NULL,
+ {
+ { 'f', "force", NULL, G_TYPE_BOOL },
+ G_OPT_SENTINEL
+ },
+ "[-fv] name [label|num]"
+ },
+ { "insert", G_FLAG_VERBOSE, NULL, G_NULL_OPTS,
+ "[-v] name prov ..."
+ },
+ { "remove", G_FLAG_VERBOSE, NULL, G_NULL_OPTS,
+ "[-v] name prov ..."
+ },
+ { "fail", G_FLAG_VERBOSE, NULL, G_NULL_OPTS,
+ "[-v] name prov ..."
+ },
+ { "stop", G_FLAG_VERBOSE, NULL,
+ {
+ { 'f', "force", NULL, G_TYPE_BOOL },
+ G_OPT_SENTINEL
+ },
+ "[-fv] name"
+ },
+ G_CMD_SENTINEL
+};
+
diff --git a/sbin/geom/class/raid/graid.8 b/sbin/geom/class/raid/graid.8
new file mode 100644
index 0000000..496e44e
--- /dev/null
+++ b/sbin/geom/class/raid/graid.8
@@ -0,0 +1,324 @@
+.\" Copyright (c) 2010 Alexander Motin <mav@FreeBSD.org>
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd April 4, 2013
+.Dt GRAID 8
+.Os
+.Sh NAME
+.Nm graid
+.Nd "control utility for software RAID devices"
+.Sh SYNOPSIS
+.Nm
+.Cm label
+.Op Fl f
+.Op Fl o Ar fmtopt
+.Op Fl S Ar size
+.Op Fl s Ar strip
+.Ar format
+.Ar label
+.Ar level
+.Ar prov ...
+.Nm
+.Cm add
+.Op Fl f
+.Op Fl S Ar size
+.Op Fl s Ar strip
+.Ar name
+.Ar label
+.Ar level
+.Nm
+.Cm delete
+.Op Fl f
+.Ar name
+.Op Ar label | Ar num
+.Nm
+.Cm insert
+.Ar name
+.Ar prov ...
+.Nm
+.Cm remove
+.Ar name
+.Ar prov ...
+.Nm
+.Cm fail
+.Ar name
+.Ar prov ...
+.Nm
+.Cm stop
+.Op Fl fv
+.Ar name ...
+.Nm
+.Cm list
+.Nm
+.Cm status
+.Nm
+.Cm load
+.Nm
+.Cm unload
+.Sh DESCRIPTION
+The
+.Nm
+utility is used to manage software RAID configurations, supported by the
+GEOM RAID class.
+GEOM RAID class uses on-disk metadata to provide access to software-RAID
+volumes defined by different RAID BIOSes.
+Depending on RAID BIOS type and its metadata format, different subsets of
+configurations and features are supported.
+To allow booting from RAID volume, the metadata format should match the
+RAID BIOS type and its capabilities.
+To guarantee that these match, it is recommended to create volumes via the
+RAID BIOS interface, while experienced users are free to do it using this
+utility.
+.Pp
+The first argument to
+.Nm
+indicates an action to be performed:
+.Bl -tag -width ".Cm destroy"
+.It Cm label
+Create an array with single volume.
+The
+.Ar format
+argument specifies the on-disk metadata format to use for this array,
+such as "Intel".
+The
+.Ar label
+argument specifies the label of the created volume.
+The
+.Ar level
+argument specifies the RAID level of the created volume, such as:
+"RAID0", "RAID1", etc.
+The subsequent list enumerates providers to use as array components.
+The special name "NONE" can be used to reserve space for absent disks.
+The order of components can be important, depending on specific RAID level
+and metadata format.
+.Pp
+Additional options include:
+.Bl -tag -width ".Fl s Ar strip"
+.It Fl f
+Enforce specified configuration creation if it is officially unsupported,
+but technically can be created.
+.It Fl o Ar fmtopt
+Specifies metadata format options.
+.It Fl S Ar size
+Use
+.Ar size
+bytes on each component for this volume.
+Should be used if several volumes per array are planned, or if smaller
+components going to be inserted later.
+Defaults to size of the smallest component.
+.It Fl s Ar strip
+Specifies strip size in bytes.
+Defaults to 131072.
+.El
+.It Cm add
+Create another volume on the existing array.
+The
+.Ar name
+argument is the name of the existing array, reported by label command.
+The rest of arguments are the same as for the label command.
+.It Cm delete
+Delete volume(s) from the existing array.
+When the last volume is deleted, the array is also deleted and its metadata
+erased.
+The
+.Ar name
+argument is the name of existing array.
+Optional
+.Ar label
+or
+.Ar num
+arguments allow specifying volume for deletion.
+.Pp
+Additional options include:
+.Bl -tag -width ".Fl f"
+.It Fl f
+Delete volume(s) even if it is still open.
+.El
+.It Cm insert
+Insert specified provider(s) into specified array instead of the first missing
+or failed components.
+If there are no such components, mark disk(s) as spare.
+.It Cm remove
+Remove the specified provider(s) from the specified array and erase metadata.
+If there are spare disks present, the removed disk(s) will be replaced by
+spares.
+.It Cm fail
+Mark the given disks(s) as failed, removing from active use unless absolutely
+necessary due to exhausted redundancy.
+If there are spare disks present - failed disk(s) will be replaced with one
+of them.
+.It Cm stop
+Stop the given array.
+The metadata will not be erased.
+.Pp
+Additional options include:
+.Bl -tag -width ".Fl f"
+.It Fl f
+Stop the given array even if some of its volumes are opened.
+.El
+.It Cm list
+See
+.Xr geom 8 .
+.It Cm status
+See
+.Xr geom 8 .
+.It Cm load
+See
+.Xr geom 8 .
+.It Cm unload
+See
+.Xr geom 8 .
+.El
+.Pp
+Additional options include:
+.Bl -tag -width ".Fl v"
+.It Fl v
+Be more verbose.
+.El
+.Sh SUPPORTED METADATA FORMATS
+The GEOM RAID class follows a modular design, allowing different metadata
+formats to be used.
+Support is currently implemented for the following formats:
+.Bl -tag -width "Intel"
+.It DDF
+The format defined by the SNIA Common RAID Disk Data Format v2.0 specification.
+Used by some Adaptec RAID BIOSes and some hardware RAID controllers.
+Because of high format flexibility different implementations support
+different set of features and have different on-disk metadata layouts.
+To provide compatibility, the GEOM RAID class mimics capabilities
+of the first detected DDF array.
+Respecting that, it may support different number of disks per volume,
+volumes per array, partitions per disk, etc.
+The following configurations are supported: RAID0 (2+ disks), RAID1 (2+ disks),
+RAID1E (3+ disks), RAID3 (3+ disks), RAID4 (3+ disks), RAID5 (3+ disks),
+RAID5E (4+ disks), RAID5EE (4+ disks), RAID5R (3+ disks), RAID6 (4+ disks),
+RAIDMDF (4+ disks), RAID10 (4+ disks), SINGLE (1 disk), CONCAT (2+ disks).
+.Pp
+Format supports two options "BE" and "LE", that mean big-endian byte order
+defined by specification (default) and little-endian used by some Adaptec
+controllers.
+.It Intel
+The format used by Intel RAID BIOS.
+Supports up to two volumes per array.
+Supports configurations: RAID0 (2+ disks), RAID1 (2 disks),
+RAID5 (3+ disks), RAID10 (4 disks).
+Configurations not supported by Intel RAID BIOS, but enforceable on your own
+risk: RAID1 (3+ disks), RAID1E (3+ disks), RAID10 (6+ disks).
+.It JMicron
+The format used by JMicron RAID BIOS.
+Supports one volume per array.
+Supports configurations: RAID0 (2+ disks), RAID1 (2 disks),
+RAID10 (4 disks), CONCAT (2+ disks).
+Configurations not supported by JMicron RAID BIOS, but enforceable on your own
+risk: RAID1 (3+ disks), RAID1E (3+ disks), RAID10 (6+ disks), RAID5 (3+ disks).
+.It NVIDIA
+The format used by NVIDIA MediaShield RAID BIOS.
+Supports one volume per array.
+Supports configurations: RAID0 (2+ disks), RAID1 (2 disks),
+RAID5 (3+ disks), RAID10 (4+ disks), SINGLE (1 disk), CONCAT (2+ disks).
+Configurations not supported by NVIDIA MediaShield RAID BIOS, but enforceable
+on your own risk: RAID1 (3+ disks).
+.It Promise
+The format used by Promise and AMD/ATI RAID BIOSes.
+Supports multiple volumes per array.
+Each disk can be split to be used by up to two arbitrary volumes.
+Supports configurations: RAID0 (2+ disks), RAID1 (2 disks),
+RAID5 (3+ disks), RAID10 (4 disks), SINGLE (1 disk), CONCAT (2+ disks).
+Configurations not supported by RAID BIOSes, but enforceable on your
+own risk: RAID1 (3+ disks), RAID10 (6+ disks).
+.It SiI
+The format used by SiliconImage RAID BIOS.
+Supports one volume per array.
+Supports configurations: RAID0 (2+ disks), RAID1 (2 disks),
+RAID5 (3+ disks), RAID10 (4 disks), SINGLE (1 disk), CONCAT (2+ disks).
+Configurations not supported by SiliconImage RAID BIOS, but enforceable on your
+own risk: RAID1 (3+ disks), RAID10 (6+ disks).
+.El
+.Sh SUPPORTED RAID LEVELS
+The GEOM RAID class follows a modular design, allowing different RAID levels
+to be used.
+Full support for the following RAID levels is currently implemented:
+RAID0, RAID1, RAID1E, RAID10, SINGLE, CONCAT.
+The following RAID levels supported as read-only for volumes in optimal
+state (without using redundancy): RAID4, RAID5, RAID5E, RAID5EE, RAID5R,
+RAID6, RAIDMDF.
+.Sh RAID LEVEL MIGRATION
+The GEOM RAID class has no support for RAID level migration, allowed by some
+metadata formats.
+If you started migration using BIOS or in some other way, make sure to
+complete it there.
+Do not run GEOM RAID class on migrating volumes under pain of possible data
+corruption!
+.Sh 2TiB BARRIERS
+NVIDIA metadata format does not support volumes above 2TiB.
+.Sh SYSCTL VARIABLES
+The following
+.Xr sysctl 8
+variable can be used to control the behavior of the
+.Nm RAID
+GEOM class.
+.Bl -tag -width indent
+.It Va kern.geom.raid.aggressive_spare : No 0
+Use any disks without metadata connected to controllers of the vendor
+matching to volume metadata format as spare.
+Use it with much care to not lose data if connecting unrelated disk!
+.It Va kern.geom.raid.clean_time : No 5
+Mark volume as clean when idle for the specified number of seconds.
+.It Va kern.geom.raid.debug : No 0
+Debug level of the
+.Nm RAID
+GEOM class.
+.It Va kern.geom.raid.enable : No 1
+Enable on-disk metadata taste.
+.It Va kern.geom.raid.idle_threshold : No 1000000
+Time in microseconds to consider a volume idle for rebuild purposes.
+.It Va kern.geom.raid.name_format : No 0
+Providers name format: 0 -- raid/r{num}, 1 -- raid/{label}.
+.It Va kern.geom.raid.read_err_thresh : No 10
+Number of read errors equated to disk failure.
+Write errors are always considered as disk failures.
+.It Va kern.geom.raid.start_timeout : No 30
+Time to wait for missing array components on startup.
+.It Va kern.geom.raid. Ns Ar X Ns Va .enable : No 1
+Enable taste for specific metadata or transformation module.
+.It Va kern.geom.raid.legacy_aliases : No 0
+Enable geom raid emulation of legacy /dev/ar%d devices.
+This should aid the upgrade of systems from legacy to modern releases.
+.El
+.Sh EXIT STATUS
+Exit status is 0 on success, and non-zero if the command fails.
+.Sh SEE ALSO
+.Xr geom 4 ,
+.Xr geom 8 ,
+.Xr gvinum 8
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Fx 9.0 .
+.Sh AUTHORS
+.An Alexander Motin Aq Mt mav@FreeBSD.org
+.An M. Warner Losh Aq Mt imp@FreeBSD.org
diff --git a/sbin/geom/class/raid3/Makefile b/sbin/geom/class/raid3/Makefile
new file mode 100644
index 0000000..74e1245
--- /dev/null
+++ b/sbin/geom/class/raid3/Makefile
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../../misc
+
+GEOM_CLASS= raid3
+
+LIBADD= md
+
+.include <bsd.lib.mk>
diff --git a/sbin/geom/class/raid3/geom_raid3.c b/sbin/geom/class/raid3/geom_raid3.c
new file mode 100644
index 0000000..cd0aa65
--- /dev/null
+++ b/sbin/geom/class/raid3/geom_raid3.c
@@ -0,0 +1,335 @@
+/*-
+ * Copyright (c) 2004-2005 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <errno.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <strings.h>
+#include <assert.h>
+#include <libgeom.h>
+#include <geom/raid3/g_raid3.h>
+#include <core/geom.h>
+#include <misc/subr.h>
+
+
+uint32_t lib_version = G_LIB_VERSION;
+uint32_t version = G_RAID3_VERSION;
+
+static void raid3_main(struct gctl_req *req, unsigned f);
+static void raid3_clear(struct gctl_req *req);
+static void raid3_dump(struct gctl_req *req);
+static void raid3_label(struct gctl_req *req);
+
+struct g_command class_commands[] = {
+ { "clear", G_FLAG_VERBOSE, raid3_main, G_NULL_OPTS,
+ "[-v] prov ..."
+ },
+ { "configure", G_FLAG_VERBOSE, NULL,
+ {
+ { 'a', "autosync", NULL, G_TYPE_BOOL },
+ { 'd', "dynamic", NULL, G_TYPE_BOOL },
+ { 'f', "failsync", NULL, G_TYPE_BOOL },
+ { 'F', "nofailsync", NULL, G_TYPE_BOOL },
+ { 'h', "hardcode", NULL, G_TYPE_BOOL },
+ { 'n', "noautosync", NULL, G_TYPE_BOOL },
+ { 'r', "round_robin", NULL, G_TYPE_BOOL },
+ { 'R', "noround_robin", NULL, G_TYPE_BOOL },
+ { 'w', "verify", NULL, G_TYPE_BOOL },
+ { 'W', "noverify", NULL, G_TYPE_BOOL },
+ G_OPT_SENTINEL
+ },
+ "[-adfFhnrRvwW] name"
+ },
+ { "dump", 0, raid3_main, G_NULL_OPTS,
+ "prov ..."
+ },
+ { "insert", G_FLAG_VERBOSE, NULL,
+ {
+ { 'h', "hardcode", NULL, G_TYPE_BOOL },
+ { 'n', "number", G_VAL_OPTIONAL, G_TYPE_NUMBER },
+ G_OPT_SENTINEL
+ },
+ "[-hv] <-n number> name prov"
+ },
+ { "label", G_FLAG_VERBOSE, raid3_main,
+ {
+ { 'h', "hardcode", NULL, G_TYPE_BOOL },
+ { 'F', "nofailsync", NULL, G_TYPE_BOOL },
+ { 'n', "noautosync", NULL, G_TYPE_BOOL },
+ { 'r', "round_robin", NULL, G_TYPE_BOOL },
+ { 's', "sectorsize", "0", G_TYPE_NUMBER },
+ { 'w', "verify", NULL, G_TYPE_BOOL },
+ G_OPT_SENTINEL
+ },
+ "[-hFnrvw] [-s blocksize] name prov prov prov ..."
+ },
+ { "rebuild", G_FLAG_VERBOSE, NULL, G_NULL_OPTS,
+ "[-v] name prov"
+ },
+ { "remove", G_FLAG_VERBOSE, NULL,
+ {
+ { 'n', "number", NULL, G_TYPE_NUMBER },
+ G_OPT_SENTINEL
+ },
+ "[-v] <-n number> name"
+ },
+ { "stop", G_FLAG_VERBOSE, NULL,
+ {
+ { 'f', "force", NULL, G_TYPE_BOOL },
+ G_OPT_SENTINEL
+ },
+ "[-fv] name ..."
+ },
+ G_CMD_SENTINEL
+};
+
+static int verbose = 0;
+
+static void
+raid3_main(struct gctl_req *req, unsigned flags)
+{
+ const char *name;
+
+ if ((flags & G_FLAG_VERBOSE) != 0)
+ verbose = 1;
+
+ name = gctl_get_ascii(req, "verb");
+ if (name == NULL) {
+ gctl_error(req, "No '%s' argument.", "verb");
+ return;
+ }
+ if (strcmp(name, "label") == 0)
+ raid3_label(req);
+ else if (strcmp(name, "clear") == 0)
+ raid3_clear(req);
+ else if (strcmp(name, "dump") == 0)
+ raid3_dump(req);
+ else
+ gctl_error(req, "Unknown command: %s.", name);
+}
+
+static void
+raid3_label(struct gctl_req *req)
+{
+ struct g_raid3_metadata md;
+ u_char sector[512];
+ const char *str;
+ unsigned sectorsize, ssize;
+ off_t mediasize, msize;
+ int hardcode, round_robin, verify;
+ int error, i, nargs;
+
+ nargs = gctl_get_int(req, "nargs");
+ if (nargs < 4) {
+ gctl_error(req, "Too few arguments.");
+ return;
+ }
+ if (bitcount32(nargs - 2) != 1) {
+ gctl_error(req, "Invalid number of components.");
+ return;
+ }
+
+ strlcpy(md.md_magic, G_RAID3_MAGIC, sizeof(md.md_magic));
+ md.md_version = G_RAID3_VERSION;
+ str = gctl_get_ascii(req, "arg0");
+ strlcpy(md.md_name, str, sizeof(md.md_name));
+ md.md_id = arc4random();
+ md.md_all = nargs - 1;
+ md.md_mflags = 0;
+ md.md_dflags = 0;
+ md.md_genid = 0;
+ md.md_syncid = 1;
+ md.md_sync_offset = 0;
+ if (gctl_get_int(req, "noautosync"))
+ md.md_mflags |= G_RAID3_DEVICE_FLAG_NOAUTOSYNC;
+ if (gctl_get_int(req, "nofailsync"))
+ md.md_mflags |= G_RAID3_DEVICE_FLAG_NOFAILSYNC;
+ round_robin = gctl_get_int(req, "round_robin");
+ if (round_robin)
+ md.md_mflags |= G_RAID3_DEVICE_FLAG_ROUND_ROBIN;
+ verify = gctl_get_int(req, "verify");
+ if (verify)
+ md.md_mflags |= G_RAID3_DEVICE_FLAG_VERIFY;
+ if (round_robin && verify) {
+ gctl_error(req, "Both '%c' and '%c' options given.", 'r', 'w');
+ return;
+ }
+ hardcode = gctl_get_int(req, "hardcode");
+
+ /*
+ * Calculate sectorsize by finding least common multiple from
+ * sectorsizes of every disk and find the smallest mediasize.
+ */
+ mediasize = 0;
+ sectorsize = gctl_get_intmax(req, "sectorsize");
+ for (i = 1; i < nargs; i++) {
+ str = gctl_get_ascii(req, "arg%d", i);
+ msize = g_get_mediasize(str);
+ ssize = g_get_sectorsize(str);
+ if (msize == 0 || ssize == 0) {
+ gctl_error(req, "Can't get informations about %s: %s.",
+ str, strerror(errno));
+ return;
+ }
+ msize -= ssize;
+ if (mediasize == 0 || (mediasize > 0 && msize < mediasize))
+ mediasize = msize;
+ if (sectorsize == 0)
+ sectorsize = ssize;
+ else
+ sectorsize = g_lcm(sectorsize, ssize);
+ }
+ md.md_mediasize = mediasize * (nargs - 2);
+ md.md_sectorsize = sectorsize * (nargs - 2);
+ md.md_mediasize -= (md.md_mediasize % md.md_sectorsize);
+
+ if (md.md_sectorsize > MAXPHYS) {
+ gctl_error(req, "The blocksize is too big.");
+ return;
+ }
+
+ /*
+ * Clear last sector first, to spoil all components if device exists.
+ */
+ for (i = 1; i < nargs; i++) {
+ str = gctl_get_ascii(req, "arg%d", i);
+ error = g_metadata_clear(str, NULL);
+ if (error != 0) {
+ gctl_error(req, "Can't store metadata on %s: %s.", str,
+ strerror(error));
+ return;
+ }
+ }
+
+ /*
+ * Ok, store metadata (use disk number as priority).
+ */
+ for (i = 1; i < nargs; i++) {
+ str = gctl_get_ascii(req, "arg%d", i);
+ msize = g_get_mediasize(str);
+ ssize = g_get_sectorsize(str);
+ if (mediasize < msize - ssize) {
+ fprintf(stderr,
+ "warning: %s: only %jd bytes from %jd bytes used.\n",
+ str, (intmax_t)mediasize, (intmax_t)(msize - ssize));
+ }
+
+ md.md_no = i - 1;
+ md.md_provsize = msize;
+ if (!hardcode)
+ bzero(md.md_provider, sizeof(md.md_provider));
+ else {
+ if (strncmp(str, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
+ str += sizeof(_PATH_DEV) - 1;
+ strlcpy(md.md_provider, str, sizeof(md.md_provider));
+ }
+ if (verify && md.md_no == md.md_all - 1) {
+ /*
+ * In "verify" mode, force synchronization of parity
+ * component on start.
+ */
+ md.md_syncid = 0;
+ }
+ raid3_metadata_encode(&md, sector);
+ error = g_metadata_store(str, sector, sizeof(sector));
+ if (error != 0) {
+ fprintf(stderr, "Can't store metadata on %s: %s.\n",
+ str, strerror(error));
+ gctl_error(req, "Not fully done.");
+ continue;
+ }
+ if (verbose)
+ printf("Metadata value stored on %s.\n", str);
+ }
+}
+
+static void
+raid3_clear(struct gctl_req *req)
+{
+ const char *name;
+ int error, i, nargs;
+
+ nargs = gctl_get_int(req, "nargs");
+ if (nargs < 1) {
+ gctl_error(req, "Too few arguments.");
+ return;
+ }
+
+ for (i = 0; i < nargs; i++) {
+ name = gctl_get_ascii(req, "arg%d", i);
+ error = g_metadata_clear(name, G_RAID3_MAGIC);
+ if (error != 0) {
+ fprintf(stderr, "Can't clear metadata on %s: %s.\n",
+ name, strerror(error));
+ gctl_error(req, "Not fully done.");
+ continue;
+ }
+ if (verbose)
+ printf("Metadata cleared on %s.\n", name);
+ }
+}
+
+static void
+raid3_dump(struct gctl_req *req)
+{
+ struct g_raid3_metadata md, tmpmd;
+ const char *name;
+ int error, i, nargs;
+
+ nargs = gctl_get_int(req, "nargs");
+ if (nargs < 1) {
+ gctl_error(req, "Too few arguments.");
+ return;
+ }
+
+ for (i = 0; i < nargs; i++) {
+ name = gctl_get_ascii(req, "arg%d", i);
+ error = g_metadata_read(name, (u_char *)&tmpmd, sizeof(tmpmd),
+ G_RAID3_MAGIC);
+ if (error != 0) {
+ fprintf(stderr, "Can't read metadata from %s: %s.\n",
+ name, strerror(error));
+ gctl_error(req, "Not fully done.");
+ continue;
+ }
+ if (raid3_metadata_decode((u_char *)&tmpmd, &md) != 0) {
+ fprintf(stderr, "MD5 hash mismatch for %s, skipping.\n",
+ name);
+ gctl_error(req, "Not fully done.");
+ continue;
+ }
+ printf("Metadata on %s:\n", name);
+ raid3_metadata_dump(&md);
+ printf("\n");
+ }
+}
diff --git a/sbin/geom/class/raid3/graid3.8 b/sbin/geom/class/raid3/graid3.8
new file mode 100644
index 0000000..426c94d
--- /dev/null
+++ b/sbin/geom/class/raid3/graid3.8
@@ -0,0 +1,257 @@
+.\" Copyright (c) 2004-2005 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd January 15, 2012
+.Dt GRAID3 8
+.Os
+.Sh NAME
+.Nm graid3
+.Nd "control utility for RAID3 devices"
+.Sh SYNOPSIS
+.Nm
+.Cm label
+.Op Fl Fhnrvw
+.Op Fl s Ar blocksize
+.Ar name
+.Ar prov prov prov ...
+.Nm
+.Cm clear
+.Op Fl v
+.Ar prov ...
+.Nm
+.Cm configure
+.Op Fl adfFhnrRvwW
+.Ar name
+.Nm
+.Cm rebuild
+.Op Fl v
+.Ar name
+.Ar prov
+.Nm
+.Cm insert
+.Op Fl hv
+.Op Fl n Ar number
+.Ar name
+.Ar prov
+.Nm
+.Cm remove
+.Op Fl v
+.Fl n Ar number
+.Ar name
+.Nm
+.Cm stop
+.Op Fl fv
+.Ar name ...
+.Nm
+.Cm list
+.Nm
+.Cm status
+.Nm
+.Cm load
+.Nm
+.Cm unload
+.Sh DESCRIPTION
+The
+.Nm
+utility is used for RAID3 array configuration.
+After a device is created, all components are detected and configured
+automatically.
+All operations such as failure detection, stale component detection, rebuild
+of stale components, etc.\& are also done automatically.
+The
+.Nm
+utility uses on-disk metadata (the provider's last sector) to store all needed
+information.
+.Pp
+The first argument to
+.Nm
+indicates an action to be performed:
+.Bl -tag -width ".Cm configure"
+.It Cm label
+Create a RAID3 device.
+The last given component will contain parity data, whilst the others
+will all contain regular data.
+The number of components must be equal to 3, 5, 9, 17, etc.\& (2^n + 1).
+.Pp
+Additional options include:
+.Bl -tag -width ".Fl h"
+.It Fl F
+Do not synchronize after a power failure or system crash.
+Assumes device is in consistent state.
+.It Fl h
+Hardcode providers' names in metadata.
+.It Fl n
+Turn off autosynchronization of stale components.
+.It Fl r
+Use parity component for reading in round-robin fashion.
+Without this option the parity component is not used at all for reading operations
+when the device is in a complete state.
+With this option specified random I/O read operations are even 40% faster,
+but sequential reads are slower.
+One cannot use this option if the
+.Fl w
+option is also specified.
+.It Fl s
+Manually specify array block size. Block size will be set equal to least
+common multiple of all component's sector sizes and specified value.
+Note that array sector size calculated as multiple of block size and number
+of regular data components. Big values may decrease performance and compatibility,
+as all I/O requests have to be multiple of sector size.
+.It Fl w
+Use verify reading feature.
+When reading from a device in a complete state, also read data from the parity component
+and verify the data by comparing XORed regular data with parity data.
+If verification fails, an
+.Er EIO
+error is returned and the value of the
+.Va kern.geom.raid3.stat.parity_mismatch
+sysctl is increased.
+One cannot use this option if the
+.Fl r
+option is also specified.
+.El
+.It Cm clear
+Clear metadata on the given providers.
+.It Cm configure
+Configure the given device.
+.Pp
+Additional options include:
+.Bl -tag -width ".Fl a"
+.It Fl a
+Turn on autosynchronization of stale components.
+.It Fl d
+Do not hardcode providers' names in metadata.
+.It Fl f
+Synchronize device after a power failure or system crash.
+.It Fl F
+Do not synchronize after a power failure or system crash.
+Assumes device is in consistent state.
+.It Fl h
+Hardcode providers' names in metadata.
+.It Fl n
+Turn off autosynchronization of stale components.
+.It Fl r
+Turn on round-robin reading.
+.It Fl R
+Turn off round-robin reading.
+.It Fl w
+Turn on verify reading.
+.It Fl W
+Turn off verify reading.
+.El
+.It Cm rebuild
+Rebuild the given component forcibly.
+If autosynchronization was not turned off for the given device, this command
+should be unnecessary.
+.It Cm insert
+Add the given component to the existing array, if one of the components was
+removed previously with the
+.Cm remove
+command or if one component is missing and will not be connected again.
+If no number is given, new component will be added instead of first missed
+component.
+.Pp
+Additional options include:
+.Bl -tag -width ".Fl h"
+.It Fl h
+Hardcode providers' names in metadata.
+.El
+.It Cm remove
+Remove the given component from the given array and clear metadata on it.
+.It Cm stop
+Stop the given arrays.
+.Pp
+Additional options include:
+.Bl -tag -width ".Fl f"
+.It Fl f
+Stop the given array even if it is opened.
+.El
+.It Cm list
+See
+.Xr geom 8 .
+.It Cm status
+See
+.Xr geom 8 .
+.It Cm load
+See
+.Xr geom 8 .
+.It Cm unload
+See
+.Xr geom 8 .
+.El
+.Pp
+Additional options include:
+.Bl -tag -width ".Fl v"
+.It Fl v
+Be more verbose.
+.El
+.Sh EXIT STATUS
+Exit status is 0 on success, and 1 if the command fails.
+.Sh EXAMPLES
+Use 3 disks to setup a RAID3 array (with the round-robin reading feature).
+Create a file system, mount it, then unmount it and stop device:
+.Bd -literal -offset indent
+graid3 label -v -r data da0 da1 da2
+newfs /dev/raid3/data
+mount /dev/raid3/data /mnt
+\&...
+umount /mnt
+graid3 stop data
+graid3 unload
+.Ed
+.Pp
+Create a RAID3 array, but do not use the automatic synchronization feature.
+Rebuild parity component:
+.Bd -literal -offset indent
+graid3 label -n data da0 da1 da2
+graid3 rebuild data da2
+.Ed
+.Pp
+Replace one data disk with a brand new one:
+.Bd -literal -offset indent
+graid3 remove -n 0 data
+graid3 insert -n 0 data da5
+.Ed
+.Sh SEE ALSO
+.Xr geom 4 ,
+.Xr geom 8 ,
+.Xr gvinum 8 ,
+.Xr mount 8 ,
+.Xr newfs 8 ,
+.Xr umount 8
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Fx 5.3 .
+.Sh AUTHORS
+.An Pawel Jakub Dawidek Aq Mt pjd@FreeBSD.org
+.Sh BUGS
+There should be a section with an implementation description.
+.Pp
+Documentation for sysctls
+.Va kern.geom.raid3.*
+is missing.
diff --git a/sbin/geom/class/sched/Makefile b/sbin/geom/class/sched/Makefile
new file mode 100644
index 0000000..6f54d3f
--- /dev/null
+++ b/sbin/geom/class/sched/Makefile
@@ -0,0 +1,8 @@
+# GEOM_LIBRARY_PATH
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../../misc
+
+GEOM_CLASS= sched
+
+.include <bsd.lib.mk>
diff --git a/sbin/geom/class/sched/geom_sched.c b/sbin/geom/class/sched/geom_sched.c
new file mode 100644
index 0000000..727913c
--- /dev/null
+++ b/sbin/geom/class/sched/geom_sched.c
@@ -0,0 +1,126 @@
+/*-
+ * Copyright (c) 2009 Fabio Checconi
+ * Copyright (c) 2010 Luigi Rizzo, Universita` di Pisa
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ */
+
+/*
+ * $Id$
+ * $FreeBSD$
+ *
+ * This file implements the userspace library used by the 'geom'
+ * command to load and manipulate disk schedulers.
+ */
+
+#include <sys/cdefs.h>
+#include <sys/param.h>
+#include <sys/linker.h>
+#include <sys/module.h>
+
+#include <stdio.h>
+#include <stdint.h>
+#include <libgeom.h>
+
+#include "core/geom.h"
+#include "misc/subr.h"
+
+#define G_SCHED_VERSION 0
+
+uint32_t lib_version = G_LIB_VERSION;
+uint32_t version = G_SCHED_VERSION;
+
+/*
+ * storage for parameters used by this geom class.
+ * Right now only the scheduler name is used.
+ */
+#define GSCHED_ALGO "rr" /* default scheduler */
+
+/*
+ * Adapt to differences in geom library.
+ * in V1 struct g_command misses gc_argname, eld, and G_BOOL is undefined
+ */
+#if G_LIB_VERSION <= 1
+#define G_TYPE_BOOL G_TYPE_NUMBER
+#endif
+#if G_LIB_VERSION >= 3 && G_LIB_VERSION <= 4
+#define G_ARGNAME NULL,
+#else
+#define G_ARGNAME
+#endif
+
+static void
+gcmd_createinsert(struct gctl_req *req, unsigned flags __unused)
+{
+ const char *reqalgo;
+ char name[64];
+
+ if (gctl_has_param(req, "algo"))
+ reqalgo = gctl_get_ascii(req, "algo");
+ else
+ reqalgo = GSCHED_ALGO;
+
+ snprintf(name, sizeof(name), "gsched_%s", reqalgo);
+ /*
+ * Do not complain about errors here, gctl_issue()
+ * will fail anyway.
+ */
+ if (modfind(name) < 0)
+ kldload(name);
+ gctl_issue(req);
+}
+
+struct g_command class_commands[] = {
+ { "create", G_FLAG_VERBOSE | G_FLAG_LOADKLD, gcmd_createinsert,
+ {
+ { 'a', "algo", GSCHED_ALGO, G_TYPE_STRING },
+ G_OPT_SENTINEL
+ },
+ G_ARGNAME "[-v] [-a algorithm_name] dev ..."
+ },
+ { "insert", G_FLAG_VERBOSE | G_FLAG_LOADKLD, gcmd_createinsert,
+ {
+ { 'a', "algo", GSCHED_ALGO, G_TYPE_STRING },
+ G_OPT_SENTINEL
+ },
+ G_ARGNAME "[-v] [-a algorithm_name] dev ..."
+ },
+ { "configure", G_FLAG_VERBOSE, NULL,
+ {
+ { 'a', "algo", GSCHED_ALGO, G_TYPE_STRING },
+ G_OPT_SENTINEL
+ },
+ G_ARGNAME "[-v] [-a algorithm_name] prov ..."
+ },
+ { "destroy", G_FLAG_VERBOSE, NULL,
+ {
+ { 'f', "force", NULL, G_TYPE_BOOL },
+ G_OPT_SENTINEL
+ },
+ G_ARGNAME "[-fv] prov ..."
+ },
+ { "reset", G_FLAG_VERBOSE, NULL, G_NULL_OPTS,
+ G_ARGNAME "[-v] prov ..."
+ },
+ G_CMD_SENTINEL
+};
diff --git a/sbin/geom/class/sched/gsched.8 b/sbin/geom/class/sched/gsched.8
new file mode 100644
index 0000000..facb5c1
--- /dev/null
+++ b/sbin/geom/class/sched/gsched.8
@@ -0,0 +1,162 @@
+.\" Copyright (c) 2009-2010 Fabio Checconi
+.\" Copyright (c) 2009-2010 Luigi Rizzo, Universita` di Pisa
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd July 26, 2012
+.Dt GSCHED 8
+.Os
+.Sh NAME
+.Nm gsched
+.Nd "control utility for disk scheduler GEOM class"
+.Sh SYNOPSIS
+.Nm
+.Cm create
+.Op Fl v
+.Op Fl a Ar algorithm
+.Ar provider ...
+.Nm
+.Cm insert
+.Op Fl v
+.Op Fl a Ar algorithm
+.Ar provider ...
+.Nm
+.Cm configure
+.Op Fl v
+.Op Fl a Ar algorithm
+.Ar node ...
+.Nm
+.Cm destroy
+.Op Fl fv
+.Ar node ...
+.Nm
+.Cm reset
+.Op Fl v
+.Ar node ...
+.Nm
+.Cm { list | status | load | unload }
+.Sh DESCRIPTION
+The
+.Nm
+utility (also callable as
+.Nm geom sched ... )
+changes the scheduling policy of the requests going to a provider.
+.Pp
+The first argument to
+.Nm
+indicates an action to be performed:
+.Bl -tag -width ".Cm configure"
+.It Cm create
+Create a new provider and geom node using the specified scheduling algorithm.
+.Ar algorithm
+is the name of the scheduling algorithm used for the provider.
+Available algorithms include:
+.Ar rr ,
+which implements anticipatory scheduling with round robin service
+among clients;
+.Ar as ,
+which implements a simple form of anticipatory scheduling with
+no per-client queue.
+.Pp
+If the operation succeeds, the new provider should appear with name
+.Pa /dev/ Ns Ao Ar dev Ac Ns Pa .sched. .
+The kernel module
+.Pa geom_sched.ko
+will be loaded if it is not loaded already.
+.It Cm insert
+Operates as "create", but the insertion is "transparent",
+i.e. the existing provider is rerouted to the newly created geom,
+which in turn forwards requests to the existing geom.
+This operation allows one to start/stop a scheduling service
+on an already existing provider.
+.Pp
+A subsequent "destroy" will remove the newly created geom and
+hook the provider back to the original geom.
+.It Cm configure
+Configure existing scheduling provider. It supports the same options
+as the
+.Nm create
+command.
+.It Cm destroy
+Destroy the geom specified in the parameter.
+.It Cm reset
+Do nothing.
+.It Cm list | status | load | unload
+See
+.Xr geom 8 .
+.El
+.Pp
+Additional options:
+.Bl -tag -width ".Fl f"
+.It Fl f
+Force the removal of the specified provider.
+.It Fl v
+Be more verbose.
+.El
+.Sh SYSCTL VARIABLES
+The following
+.Xr sysctl 8
+variables can be used to control the behavior of the
+.Nm SCHED
+GEOM class.
+The default value is shown next to each variable.
+.Bl -tag -width indent
+.It Va kern.geom.sched.debug : No 0
+Debug level of the
+.Nm SCHED
+GEOM class.
+This can be set to a number between 0 and 2 inclusive.
+If set to 0 minimal debug information is printed, and if set to 2 the
+maximum amount of debug information is printed.
+.El
+.Sh EXIT STATUS
+Exit status is 0 on success, and 1 if the command fails.
+.Sh EXAMPLES
+The following example shows how to create a scheduling provider for disk
+.Pa /dev/ada0 ,
+and how to destroy it.
+.Bd -literal -offset indent
+# Load the geom_sched module:
+kldload geom_sched
+# Load some scheduler classes used by geom_sched:
+kldload gsched_rr
+# Configure device ada0 to use scheduler "rr":
+geom sched insert -a rr ada0
+# Now provider ada0 uses the "rr" algorithm;
+# the new geom is ada0.sched.
+# Remove the scheduler on the device:
+geom sched destroy -v ada0.sched.
+.Ed
+.Sh SEE ALSO
+.Xr geom 4 ,
+.Xr geom 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 8.1 .
+.Sh AUTHORS
+.An Fabio Checconi Aq Mt fabio@FreeBSD.org
+.An Luigi Rizzo Aq Mt luigi@FreeBSD.org
diff --git a/sbin/geom/class/shsec/Makefile b/sbin/geom/class/shsec/Makefile
new file mode 100644
index 0000000..6b5c835
--- /dev/null
+++ b/sbin/geom/class/shsec/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../../misc
+
+GEOM_CLASS= shsec
+
+.include <bsd.lib.mk>
diff --git a/sbin/geom/class/shsec/geom_shsec.c b/sbin/geom/class/shsec/geom_shsec.c
new file mode 100644
index 0000000..a5d1b66
--- /dev/null
+++ b/sbin/geom/class/shsec/geom_shsec.c
@@ -0,0 +1,259 @@
+/*-
+ * Copyright (c) 2004-2005 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <errno.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <strings.h>
+#include <assert.h>
+#include <libgeom.h>
+#include <geom/shsec/g_shsec.h>
+
+#include "core/geom.h"
+#include "misc/subr.h"
+
+
+uint32_t lib_version = G_LIB_VERSION;
+uint32_t version = G_SHSEC_VERSION;
+
+static void shsec_main(struct gctl_req *req, unsigned flags);
+static void shsec_clear(struct gctl_req *req);
+static void shsec_dump(struct gctl_req *req);
+static void shsec_label(struct gctl_req *req);
+
+struct g_command class_commands[] = {
+ { "clear", G_FLAG_VERBOSE, shsec_main, G_NULL_OPTS,
+ "[-v] prov ..."
+ },
+ { "dump", 0, shsec_main, G_NULL_OPTS,
+ "prov ..."
+ },
+ { "label", G_FLAG_VERBOSE | G_FLAG_LOADKLD, shsec_main,
+ {
+ { 'h', "hardcode", NULL, G_TYPE_BOOL },
+ G_OPT_SENTINEL
+ },
+ "[-hv] name prov prov ..."
+ },
+ { "stop", G_FLAG_VERBOSE, NULL,
+ {
+ { 'f', "force", NULL, G_TYPE_BOOL },
+ G_OPT_SENTINEL
+ },
+ "[-fv] name ..."
+ },
+ G_CMD_SENTINEL
+};
+
+static int verbose = 0;
+
+static void
+shsec_main(struct gctl_req *req, unsigned flags)
+{
+ const char *name;
+
+ if ((flags & G_FLAG_VERBOSE) != 0)
+ verbose = 1;
+
+ name = gctl_get_ascii(req, "verb");
+ if (name == NULL) {
+ gctl_error(req, "No '%s' argument.", "verb");
+ return;
+ }
+ if (strcmp(name, "label") == 0)
+ shsec_label(req);
+ else if (strcmp(name, "clear") == 0)
+ shsec_clear(req);
+ else if (strcmp(name, "dump") == 0)
+ shsec_dump(req);
+ else
+ gctl_error(req, "Unknown command: %s.", name);
+}
+
+static void
+shsec_label(struct gctl_req *req)
+{
+ struct g_shsec_metadata md;
+ off_t compsize, msize;
+ u_char sector[512];
+ unsigned ssize, secsize;
+ const char *name;
+ int error, i, nargs, hardcode;
+
+ nargs = gctl_get_int(req, "nargs");
+ if (nargs <= 2) {
+ gctl_error(req, "Too few arguments.");
+ return;
+ }
+ hardcode = gctl_get_int(req, "hardcode");
+
+ /*
+ * Clear last sector first to spoil all components if device exists.
+ */
+ compsize = 0;
+ secsize = 0;
+ for (i = 1; i < nargs; i++) {
+ name = gctl_get_ascii(req, "arg%d", i);
+ msize = g_get_mediasize(name);
+ ssize = g_get_sectorsize(name);
+ if (msize == 0 || ssize == 0) {
+ gctl_error(req, "Can't get informations about %s: %s.",
+ name, strerror(errno));
+ return;
+ }
+ msize -= ssize;
+ if (compsize == 0 || (compsize > 0 && msize < compsize))
+ compsize = msize;
+ if (secsize == 0)
+ secsize = ssize;
+ else
+ secsize = g_lcm(secsize, ssize);
+
+ error = g_metadata_clear(name, NULL);
+ if (error != 0) {
+ gctl_error(req, "Can't store metadata on %s: %s.", name,
+ strerror(error));
+ return;
+ }
+ }
+
+ strlcpy(md.md_magic, G_SHSEC_MAGIC, sizeof(md.md_magic));
+ md.md_version = G_SHSEC_VERSION;
+ name = gctl_get_ascii(req, "arg0");
+ strlcpy(md.md_name, name, sizeof(md.md_name));
+ md.md_id = arc4random();
+ md.md_all = nargs - 1;
+
+ /*
+ * Ok, store metadata.
+ */
+ for (i = 1; i < nargs; i++) {
+ name = gctl_get_ascii(req, "arg%d", i);
+ msize = g_get_mediasize(name);
+ ssize = g_get_sectorsize(name);
+ if (compsize < msize - ssize) {
+ fprintf(stderr,
+ "warning: %s: only %jd bytes from %jd bytes used.\n",
+ name, (intmax_t)compsize, (intmax_t)(msize - ssize));
+ }
+
+ md.md_no = i - 1;
+ md.md_provsize = msize;
+ if (!hardcode)
+ bzero(md.md_provider, sizeof(md.md_provider));
+ else {
+ if (strncmp(name, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
+ name += sizeof(_PATH_DEV) - 1;
+ strlcpy(md.md_provider, name, sizeof(md.md_provider));
+ }
+ shsec_metadata_encode(&md, sector);
+ error = g_metadata_store(name, sector, sizeof(sector));
+ if (error != 0) {
+ fprintf(stderr, "Can't store metadata on %s: %s.\n",
+ name, strerror(error));
+ gctl_error(req, "Not fully done.");
+ continue;
+ }
+ if (verbose)
+ printf("Metadata value stored on %s.\n", name);
+ }
+}
+
+static void
+shsec_clear(struct gctl_req *req)
+{
+ const char *name;
+ int error, i, nargs;
+
+ nargs = gctl_get_int(req, "nargs");
+ if (nargs < 1) {
+ gctl_error(req, "Too few arguments.");
+ return;
+ }
+
+ for (i = 0; i < nargs; i++) {
+ name = gctl_get_ascii(req, "arg%d", i);
+ error = g_metadata_clear(name, G_SHSEC_MAGIC);
+ if (error != 0) {
+ fprintf(stderr, "Can't clear metadata on %s: %s.\n",
+ name, strerror(error));
+ gctl_error(req, "Not fully done.");
+ continue;
+ }
+ if (verbose)
+ printf("Metadata cleared on %s.\n", name);
+ }
+}
+
+static void
+shsec_metadata_dump(const struct g_shsec_metadata *md)
+{
+
+ printf(" Magic string: %s\n", md->md_magic);
+ printf(" Metadata version: %u\n", (u_int)md->md_version);
+ printf(" Device name: %s\n", md->md_name);
+ printf(" Device ID: %u\n", (u_int)md->md_id);
+ printf(" Disk number: %u\n", (u_int)md->md_no);
+ printf("Total number of disks: %u\n", (u_int)md->md_all);
+ printf(" Hardcoded provider: %s\n", md->md_provider);
+}
+
+static void
+shsec_dump(struct gctl_req *req)
+{
+ struct g_shsec_metadata md, tmpmd;
+ const char *name;
+ int error, i, nargs;
+
+ nargs = gctl_get_int(req, "nargs");
+ if (nargs < 1) {
+ gctl_error(req, "Too few arguments.");
+ return;
+ }
+
+ for (i = 0; i < nargs; i++) {
+ name = gctl_get_ascii(req, "arg%d", i);
+ error = g_metadata_read(name, (u_char *)&tmpmd, sizeof(tmpmd),
+ G_SHSEC_MAGIC);
+ if (error != 0) {
+ fprintf(stderr, "Can't read metadata from %s: %s.\n",
+ name, strerror(error));
+ gctl_error(req, "Not fully done.");
+ continue;
+ }
+ shsec_metadata_decode((u_char *)&tmpmd, &md);
+ printf("Metadata on %s:\n", name);
+ shsec_metadata_dump(&md);
+ printf("\n");
+ }
+}
diff --git a/sbin/geom/class/shsec/gshsec.8 b/sbin/geom/class/shsec/gshsec.8
new file mode 100644
index 0000000..dcfd2b3
--- /dev/null
+++ b/sbin/geom/class/shsec/gshsec.8
@@ -0,0 +1,130 @@
+.\" Copyright (c) 2005 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd October 1, 2013
+.Dt GSHSEC 8
+.Os
+.Sh NAME
+.Nm gshsec
+.Nd "control utility for shared secret devices"
+.Sh SYNOPSIS
+.Nm
+.Cm label
+.Op Fl hv
+.Ar name
+.Ar prov prov ...
+.Nm
+.Cm stop
+.Op Fl fv
+.Ar name ...
+.Nm
+.Cm clear
+.Op Fl v
+.Ar prov ...
+.Nm
+.Cm dump
+.Ar prov ...
+.Nm
+.Cm list
+.Nm
+.Cm status
+.Nm
+.Cm load
+.Nm
+.Cm unload
+.Sh DESCRIPTION
+The
+.Nm
+utility is used for setting up a device which contains a shared secret.
+The secret is shared between the given providers.
+To collect the secret, all providers are needed.
+If one of the components is missing, there is no way to get any useful data from
+the rest of them.
+The first argument to
+.Nm
+indicates an action to be performed:
+.Bl -tag -width ".Cm destroy"
+.It Cm label
+Set up a shared secret device from the given components with the specified
+.Ar name .
+Metadata are stored in the last sector of every component.
+.It Cm stop
+Turn off an existing shared secret device by its
+.Ar name .
+This command does not touch on-disk metadata!
+.It Cm clear
+Clear metadata on the given providers.
+.It Cm dump
+Dump metadata stored on the given providers.
+.It Cm list
+See
+.Xr geom 8 .
+.It Cm status
+See
+.Xr geom 8 .
+.It Cm load
+See
+.Xr geom 8 .
+.It Cm unload
+See
+.Xr geom 8 .
+.El
+.Pp
+Additional options:
+.Bl -tag -width ".Fl f"
+.It Fl f
+Force the removal of the specified shared secret device.
+.It Fl h
+Hardcode providers' names in metadata.
+.It Fl v
+Be more verbose.
+.El
+.Sh EXIT STATUS
+Exit status is 0 on success, and 1 if the command fails.
+.Sh EXAMPLES
+The following example shows how to create a shared secret device.
+The secret will be split between a slice on a local disk and a USB Pen drive.
+.Bd -literal -offset indent
+gshsec label -v secret /dev/ada0s1 /dev/da0
+newfs /dev/shsec/secret
+.Ed
+.Pp
+From now on, when the USB Pen drive is inserted, it will be automatically
+detected and connected, making the secret available via the
+.Pa /dev/shsec/secret
+device.
+.Sh SEE ALSO
+.Xr geom 4 ,
+.Xr gbde 8 ,
+.Xr geom 8 ,
+.Xr newfs 8
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Fx 5.4 .
+.Sh AUTHORS
+.An Pawel Jakub Dawidek Aq Mt pjd@FreeBSD.org
diff --git a/sbin/geom/class/stripe/Makefile b/sbin/geom/class/stripe/Makefile
new file mode 100644
index 0000000..8ee5f6e
--- /dev/null
+++ b/sbin/geom/class/stripe/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../../misc
+
+GEOM_CLASS= stripe
+
+.include <bsd.lib.mk>
diff --git a/sbin/geom/class/stripe/geom_stripe.c b/sbin/geom/class/stripe/geom_stripe.c
new file mode 100644
index 0000000..1fb16e9
--- /dev/null
+++ b/sbin/geom/class/stripe/geom_stripe.c
@@ -0,0 +1,285 @@
+/*-
+ * Copyright (c) 2004-2005 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <errno.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <strings.h>
+#include <assert.h>
+#include <libgeom.h>
+#include <geom/stripe/g_stripe.h>
+
+#include "core/geom.h"
+#include "misc/subr.h"
+
+
+uint32_t lib_version = G_LIB_VERSION;
+uint32_t version = G_STRIPE_VERSION;
+
+#define GSTRIPE_STRIPESIZE "65536"
+
+static void stripe_main(struct gctl_req *req, unsigned flags);
+static void stripe_clear(struct gctl_req *req);
+static void stripe_dump(struct gctl_req *req);
+static void stripe_label(struct gctl_req *req);
+
+struct g_command class_commands[] = {
+ { "clear", G_FLAG_VERBOSE, stripe_main, G_NULL_OPTS,
+ "[-v] prov ..."
+ },
+ { "create", G_FLAG_VERBOSE | G_FLAG_LOADKLD, NULL,
+ {
+ { 's', "stripesize", GSTRIPE_STRIPESIZE, G_TYPE_NUMBER },
+ G_OPT_SENTINEL
+ },
+ "[-v] [-s stripesize] name prov prov ..."
+ },
+ { "destroy", G_FLAG_VERBOSE, NULL,
+ {
+ { 'f', "force", NULL, G_TYPE_BOOL },
+ G_OPT_SENTINEL
+ },
+ "[-fv] name ..."
+ },
+ { "dump", 0, stripe_main, G_NULL_OPTS,
+ "prov ..."
+ },
+ { "label", G_FLAG_VERBOSE | G_FLAG_LOADKLD, stripe_main,
+ {
+ { 'h', "hardcode", NULL, G_TYPE_BOOL },
+ { 's', "stripesize", GSTRIPE_STRIPESIZE, G_TYPE_NUMBER },
+ G_OPT_SENTINEL
+ },
+ "[-hv] [-s stripesize] name prov prov ..."
+ },
+ { "stop", G_FLAG_VERBOSE, NULL,
+ {
+ { 'f', "force", NULL, G_TYPE_BOOL },
+ G_OPT_SENTINEL
+ },
+ "[-fv] name ..."
+ },
+ G_CMD_SENTINEL
+};
+
+static int verbose = 0;
+
+static void
+stripe_main(struct gctl_req *req, unsigned flags)
+{
+ const char *name;
+
+ if ((flags & G_FLAG_VERBOSE) != 0)
+ verbose = 1;
+
+ name = gctl_get_ascii(req, "verb");
+ if (name == NULL) {
+ gctl_error(req, "No '%s' argument.", "verb");
+ return;
+ }
+ if (strcmp(name, "label") == 0)
+ stripe_label(req);
+ else if (strcmp(name, "clear") == 0)
+ stripe_clear(req);
+ else if (strcmp(name, "dump") == 0)
+ stripe_dump(req);
+ else
+ gctl_error(req, "Unknown command: %s.", name);
+}
+
+static void
+stripe_label(struct gctl_req *req)
+{
+ struct g_stripe_metadata md;
+ intmax_t stripesize;
+ off_t compsize, msize;
+ u_char sector[512];
+ unsigned ssize, secsize;
+ const char *name;
+ int error, i, nargs, hardcode;
+
+ nargs = gctl_get_int(req, "nargs");
+ if (nargs < 3) {
+ gctl_error(req, "Too few arguments.");
+ return;
+ }
+ hardcode = gctl_get_int(req, "hardcode");
+
+ /*
+ * Clear last sector first to spoil all components if device exists.
+ */
+ compsize = 0;
+ secsize = 0;
+ for (i = 1; i < nargs; i++) {
+ name = gctl_get_ascii(req, "arg%d", i);
+ msize = g_get_mediasize(name);
+ ssize = g_get_sectorsize(name);
+ if (msize == 0 || ssize == 0) {
+ gctl_error(req, "Can't get informations about %s: %s.",
+ name, strerror(errno));
+ return;
+ }
+ msize -= ssize;
+ if (compsize == 0 || (compsize > 0 && msize < compsize))
+ compsize = msize;
+ if (secsize == 0)
+ secsize = ssize;
+ else
+ secsize = g_lcm(secsize, ssize);
+
+ error = g_metadata_clear(name, NULL);
+ if (error != 0) {
+ gctl_error(req, "Can't store metadata on %s: %s.", name,
+ strerror(error));
+ return;
+ }
+ }
+
+ strlcpy(md.md_magic, G_STRIPE_MAGIC, sizeof(md.md_magic));
+ md.md_version = G_STRIPE_VERSION;
+ name = gctl_get_ascii(req, "arg0");
+ strlcpy(md.md_name, name, sizeof(md.md_name));
+ md.md_id = arc4random();
+ md.md_all = nargs - 1;
+ stripesize = gctl_get_intmax(req, "stripesize");
+ if ((stripesize % secsize) != 0) {
+ gctl_error(req, "Stripesize should be multiple of %u.",
+ secsize);
+ return;
+ }
+ md.md_stripesize = stripesize;
+
+ /*
+ * Ok, store metadata.
+ */
+ for (i = 1; i < nargs; i++) {
+ name = gctl_get_ascii(req, "arg%d", i);
+ msize = g_get_mediasize(name);
+ ssize = g_get_sectorsize(name);
+ if (compsize < msize - ssize) {
+ fprintf(stderr,
+ "warning: %s: only %jd bytes from %jd bytes used.\n",
+ name, (intmax_t)compsize, (intmax_t)(msize - ssize));
+ }
+
+ md.md_no = i - 1;
+ md.md_provsize = msize;
+ if (!hardcode)
+ bzero(md.md_provider, sizeof(md.md_provider));
+ else {
+ if (strncmp(name, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
+ name += sizeof(_PATH_DEV) - 1;
+ strlcpy(md.md_provider, name, sizeof(md.md_provider));
+ }
+ stripe_metadata_encode(&md, sector);
+ error = g_metadata_store(name, sector, sizeof(sector));
+ if (error != 0) {
+ fprintf(stderr, "Can't store metadata on %s: %s.\n",
+ name, strerror(error));
+ gctl_error(req, "Not fully done.");
+ continue;
+ }
+ if (verbose)
+ printf("Metadata value stored on %s.\n", name);
+ }
+}
+
+static void
+stripe_clear(struct gctl_req *req)
+{
+ const char *name;
+ int error, i, nargs;
+
+ nargs = gctl_get_int(req, "nargs");
+ if (nargs < 1) {
+ gctl_error(req, "Too few arguments.");
+ return;
+ }
+
+ for (i = 0; i < nargs; i++) {
+ name = gctl_get_ascii(req, "arg%d", i);
+ error = g_metadata_clear(name, G_STRIPE_MAGIC);
+ if (error != 0) {
+ fprintf(stderr, "Can't clear metadata on %s: %s.\n",
+ name, strerror(error));
+ gctl_error(req, "Not fully done.");
+ continue;
+ }
+ if (verbose)
+ printf("Metadata cleared on %s.\n", name);
+ }
+}
+
+static void
+stripe_metadata_dump(const struct g_stripe_metadata *md)
+{
+
+ printf(" Magic string: %s\n", md->md_magic);
+ printf(" Metadata version: %u\n", (u_int)md->md_version);
+ printf(" Device name: %s\n", md->md_name);
+ printf(" Device ID: %u\n", (u_int)md->md_id);
+ printf(" Disk number: %u\n", (u_int)md->md_no);
+ printf("Total number of disks: %u\n", (u_int)md->md_all);
+ printf(" Stripe size: %u\n", (u_int)md->md_stripesize);
+ printf(" Hardcoded provider: %s\n", md->md_provider);
+}
+
+static void
+stripe_dump(struct gctl_req *req)
+{
+ struct g_stripe_metadata md, tmpmd;
+ const char *name;
+ int error, i, nargs;
+
+ nargs = gctl_get_int(req, "nargs");
+ if (nargs < 1) {
+ gctl_error(req, "Too few arguments.");
+ return;
+ }
+
+ for (i = 0; i < nargs; i++) {
+ name = gctl_get_ascii(req, "arg%d", i);
+ error = g_metadata_read(name, (u_char *)&tmpmd, sizeof(tmpmd),
+ G_STRIPE_MAGIC);
+ if (error != 0) {
+ fprintf(stderr, "Can't read metadata from %s: %s.\n",
+ name, strerror(error));
+ gctl_error(req, "Not fully done.");
+ continue;
+ }
+ stripe_metadata_decode((u_char *)&tmpmd, &md);
+ printf("Metadata on %s:\n", name);
+ stripe_metadata_dump(&md);
+ printf("\n");
+ }
+}
diff --git a/sbin/geom/class/stripe/gstripe.8 b/sbin/geom/class/stripe/gstripe.8
new file mode 100644
index 0000000..f1f34fe
--- /dev/null
+++ b/sbin/geom/class/stripe/gstripe.8
@@ -0,0 +1,243 @@
+.\" Copyright (c) 2004-2005 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd May 21, 2004
+.Dt GSTRIPE 8
+.Os
+.Sh NAME
+.Nm gstripe
+.Nd "control utility for striped devices"
+.Sh SYNOPSIS
+.Nm
+.Cm create
+.Op Fl v
+.Op Fl s Ar stripesize
+.Ar name
+.Ar prov prov ...
+.Nm
+.Cm destroy
+.Op Fl fv
+.Ar name ...
+.Nm
+.Cm label
+.Op Fl hv
+.Op Fl s Ar stripesize
+.Ar name
+.Ar prov prov ...
+.Nm
+.Cm stop
+.Op Fl fv
+.Ar name ...
+.Nm
+.Cm clear
+.Op Fl v
+.Ar prov ...
+.Nm
+.Cm dump
+.Ar prov ...
+.Nm
+.Cm list
+.Nm
+.Cm status
+.Nm
+.Cm load
+.Nm
+.Cm unload
+.Sh DESCRIPTION
+The
+.Nm
+utility is used for setting up a stripe on two or more disks.
+The striped device can be configured using two different methods:
+.Dq manual
+or
+.Dq automatic .
+When using the
+.Dq manual
+method, no metadata are stored on the devices, so the striped
+device has to be configured by hand every time it is needed.
+The
+.Dq automatic
+method uses on-disk metadata to detect devices.
+Once devices are labeled, they will be automatically detected and
+configured.
+.Pp
+The first argument to
+.Nm
+indicates an action to be performed:
+.Bl -tag -width ".Cm destroy"
+.It Cm create
+Set up a striped device from the given devices with specified
+.Ar name .
+This is the
+.Dq manual
+method and the stripe will not exist after a reboot (see
+.Sx DESCRIPTION
+above).
+The kernel module
+.Pa geom_stripe.ko
+will be loaded if it is not loaded already.
+.It Cm label
+Set up a striped device from the given devices with the specified
+.Ar name .
+This is the
+.Dq automatic
+method, where metadata are stored in every device's last sector.
+The kernel module
+.Pa geom_stripe.ko
+will be loaded if it is not loaded already.
+.It Cm stop
+Turn off an existing striped device by its
+.Ar name .
+This command does not touch on-disk metadata!
+.It Cm destroy
+Same as
+.Cm stop .
+.It Cm clear
+Clear metadata on the given devices.
+.It Cm dump
+Dump metadata stored on the given devices.
+.It Cm list
+See
+.Xr geom 8 .
+.It Cm status
+See
+.Xr geom 8 .
+.It Cm load
+See
+.Xr geom 8 .
+.It Cm unload
+See
+.Xr geom 8 .
+.El
+.Pp
+Additional options:
+.Bl -tag -width ".Fl s Ar stripesize"
+.It Fl f
+Force the removal of the specified striped device.
+.It Fl h
+Hardcode providers' names in metadata.
+.It Fl s Ar stripesize
+Specifies size of stripe block in bytes.
+The
+.Ar stripesize
+must be a multiple of the largest sector size of all the providers.
+.It Fl v
+Be more verbose.
+.El
+.Sh SYSCTL VARIABLES
+The following
+.Xr sysctl 8
+variables can be used to control the behavior of the
+.Nm STRIPE
+GEOM class.
+The default value is shown next to each variable.
+.Bl -tag -width indent
+.It Va kern.geom.stripe.debug : No 0
+Debug level of the
+.Nm STRIPE
+GEOM class.
+This can be set to a number between 0 and 3 inclusive.
+If set to 0 minimal debug information is printed, and if set to 3 the
+maximum amount of debug information is printed.
+.It Va kern.geom.stripe.fast : No 0
+If set to a non-zero value enable
+.Dq "fast mode"
+instead of the normal
+.Dq "economic mode" .
+Compared to
+.Dq "economic mode" ,
+.Dq "fast mode"
+uses more memory, but it is much faster for smaller stripe sizes.
+If enough memory cannot be allocated,
+.Nm STRIPE
+will fall back to
+.Dq "economic mode" .
+.It Va kern.geom.stripe.maxmem : No 13107200
+Maximum amount of memory that can be consumed by
+.Dq "fast mode"
+(in bytes).
+This
+.Xr sysctl 8
+variable is read-only and can only be set as a tunable in
+.Xr loader.conf 5 .
+.It Va kern.geom.stripe.fast_failed
+A count of how many times
+.Dq "fast mode"
+has failed due to an insufficient amount of memory.
+If this value is large, you should consider increasing the
+.Va kern.geom.stripe.maxmem
+value.
+.El
+.Sh EXIT STATUS
+Exit status is 0 on success, and 1 if the command fails.
+.Sh EXAMPLES
+The following example shows how to set up a striped device from four disks with a
+128KB stripe size for automatic configuration,
+create a file system on it,
+and mount it:
+.Bd -literal -offset indent
+gstripe label -v -s 131072 data /dev/da0 /dev/da1 /dev/da2 /dev/da3
+newfs /dev/stripe/data
+mount /dev/stripe/data /mnt
+[...]
+umount /mnt
+gstripe stop data
+gstripe unload
+.Ed
+.Sh COMPATIBILITY
+The
+.Nm
+interleave is in number of bytes,
+unlike
+.Xr ccdconfig 8
+which use the number of sectors.
+A
+.Xr ccdconfig 8
+.Ar ileave
+of
+.Ql 128
+is 64 KB (128 512B sectors).
+The same stripe interleave would be specified as
+.Ql 65536
+for
+.Nm .
+.Sh SEE ALSO
+.Xr geom 4 ,
+.Xr loader.conf 5 ,
+.Xr ccdconfig 8 ,
+.Xr geom 8 ,
+.Xr gvinum 8 ,
+.Xr mount 8 ,
+.Xr newfs 8 ,
+.Xr sysctl 8 ,
+.Xr umount 8
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Fx 5.3 .
+.Sh AUTHORS
+.An Pawel Jakub Dawidek Aq Mt pjd@FreeBSD.org
diff --git a/sbin/geom/class/virstor/Makefile b/sbin/geom/class/virstor/Makefile
new file mode 100644
index 0000000..0924f0d
--- /dev/null
+++ b/sbin/geom/class/virstor/Makefile
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../../misc ${.CURDIR}/../../../../sys/geom/virstor
+
+GEOM_CLASS= virstor
+
+SRCS+= binstream.c
+SRCS+= g_virstor_md.c
+
+.include <bsd.lib.mk>
diff --git a/sbin/geom/class/virstor/geom_virstor.c b/sbin/geom/class/virstor/geom_virstor.c
new file mode 100644
index 0000000..15a5676
--- /dev/null
+++ b/sbin/geom/class/virstor/geom_virstor.c
@@ -0,0 +1,583 @@
+/*-
+ * Copyright (c) 2005 Ivan Voras <ivoras@freebsd.org>
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <errno.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <strings.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <libgeom.h>
+#include <err.h>
+#include <assert.h>
+
+#include <core/geom.h>
+#include <misc/subr.h>
+
+#include <geom/virstor/g_virstor_md.h>
+#include <geom/virstor/g_virstor.h>
+
+uint32_t lib_version = G_LIB_VERSION;
+uint32_t version = G_VIRSTOR_VERSION;
+
+#define GVIRSTOR_CHUNK_SIZE "4M"
+#define GVIRSTOR_VIR_SIZE "2T"
+
+#if G_LIB_VERSION == 1
+/* Support RELENG_6 */
+#define G_TYPE_BOOL G_TYPE_NONE
+#endif
+
+/*
+ * virstor_main gets called by the geom(8) utility
+ */
+static void virstor_main(struct gctl_req *req, unsigned flags);
+
+struct g_command class_commands[] = {
+ { "clear", G_FLAG_VERBOSE, virstor_main, G_NULL_OPTS,
+ "[-v] prov ..."
+ },
+ { "dump", 0, virstor_main, G_NULL_OPTS,
+ "prov ..."
+ },
+ { "label", G_FLAG_VERBOSE | G_FLAG_LOADKLD, virstor_main,
+ {
+ { 'h', "hardcode", NULL, G_TYPE_BOOL},
+ { 'm', "chunk_size", GVIRSTOR_CHUNK_SIZE, G_TYPE_NUMBER},
+ { 's', "vir_size", GVIRSTOR_VIR_SIZE, G_TYPE_NUMBER},
+ G_OPT_SENTINEL
+ },
+ "[-h] [-v] [-m chunk_size] [-s vir_size] name provider0 [provider1 ...]"
+ },
+ { "destroy", G_FLAG_VERBOSE, NULL,
+ {
+ { 'f', "force", NULL, G_TYPE_BOOL},
+ G_OPT_SENTINEL
+ },
+ "[-fv] name ..."
+ },
+ { "stop", G_FLAG_VERBOSE, NULL,
+ {
+ { 'f', "force", NULL, G_TYPE_BOOL},
+ G_OPT_SENTINEL
+ },
+ "[-fv] name ... (alias for \"destroy\")"
+ },
+ { "add", G_FLAG_VERBOSE, NULL,
+ {
+ { 'h', "hardcode", NULL, G_TYPE_BOOL},
+ G_OPT_SENTINEL
+ },
+ "[-vh] name prov [prov ...]"
+ },
+ { "remove", G_FLAG_VERBOSE, NULL, G_NULL_OPTS,
+ "[-v] name ..."
+ },
+ G_CMD_SENTINEL
+};
+
+static int verbose = 0;
+
+/* Helper functions' declarations */
+static void virstor_clear(struct gctl_req *req);
+static void virstor_dump(struct gctl_req *req);
+static void virstor_label(struct gctl_req *req);
+
+/* Dispatcher function (no real work done here, only verbose flag recorder) */
+static void
+virstor_main(struct gctl_req *req, unsigned flags)
+{
+ const char *name;
+
+ if ((flags & G_FLAG_VERBOSE) != 0)
+ verbose = 1;
+
+ name = gctl_get_ascii(req, "verb");
+ if (name == NULL) {
+ gctl_error(req, "No '%s' argument.", "verb");
+ return;
+ }
+ if (strcmp(name, "label") == 0)
+ virstor_label(req);
+ else if (strcmp(name, "clear") == 0)
+ virstor_clear(req);
+ else if (strcmp(name, "dump") == 0)
+ virstor_dump(req);
+ else
+ gctl_error(req, "%s: Unknown command: %s.", __func__, name);
+
+ /* No CTASSERT in userland
+ CTASSERT(VIRSTOR_MAP_BLOCK_ENTRIES*VIRSTOR_MAP_ENTRY_SIZE == MAXPHYS);
+ */
+}
+
+static void
+pathgen(const char *name, char *path, size_t size)
+{
+
+ if (strncmp(name, _PATH_DEV, sizeof(_PATH_DEV) - 1) != 0)
+ snprintf(path, size, "%s%s", _PATH_DEV, name);
+ else
+ strlcpy(path, name, size);
+}
+
+static int
+my_g_metadata_store(const char *name, u_char *md, size_t size)
+{
+ char path[MAXPATHLEN];
+ unsigned sectorsize;
+ off_t mediasize;
+ u_char *sector;
+ int error, fd;
+
+ pathgen(name, path, sizeof(path));
+ sector = NULL;
+ error = 0;
+
+ fd = open(path, O_RDWR);
+ if (fd == -1)
+ return (errno);
+ mediasize = g_get_mediasize(name);
+ if (mediasize == 0) {
+ error = errno;
+ goto out;
+ }
+ sectorsize = g_get_sectorsize(name);
+ if (sectorsize == 0) {
+ error = errno;
+ goto out;
+ }
+ assert(sectorsize >= size);
+ sector = malloc(sectorsize);
+ if (sector == NULL) {
+ error = ENOMEM;
+ goto out;
+ }
+ bcopy(md, sector, size);
+ if (pwrite(fd, sector, sectorsize, mediasize - sectorsize) !=
+ (ssize_t)sectorsize) {
+ error = errno;
+ goto out;
+ }
+out:
+ if (sector != NULL)
+ free(sector);
+ close(fd);
+ return (error);
+}
+
+/*
+ * Labels a new geom Meaning: parses and checks the parameters, calculates &
+ * writes metadata to the relevant providers so when the next round of
+ * "tasting" comes (which will be just after the provider(s) are closed) geom
+ * can be instantiated with the tasted metadata.
+ */
+static void
+virstor_label(struct gctl_req *req)
+{
+ struct g_virstor_metadata md;
+ off_t msize;
+ unsigned char *sect;
+ unsigned int i;
+ size_t ssize, secsize;
+ const char *name;
+ char param[32];
+ int hardcode, nargs, error;
+ struct virstor_map_entry *map;
+ size_t total_chunks; /* We'll run out of memory if
+ this needs to be bigger. */
+ unsigned int map_chunks; /* Chunks needed by the map (map size). */
+ size_t map_size; /* In bytes. */
+ ssize_t written;
+ int fd;
+
+ nargs = gctl_get_int(req, "nargs");
+ if (nargs < 2) {
+ gctl_error(req, "Too few arguments (%d): expecting: name "
+ "provider0 [provider1 ...]", nargs);
+ return;
+ }
+
+ hardcode = gctl_get_int(req, "hardcode");
+
+ /*
+ * Initialize constant parts of metadata: magic signature, version,
+ * name.
+ */
+ bzero(&md, sizeof(md));
+ strlcpy(md.md_magic, G_VIRSTOR_MAGIC, sizeof(md.md_magic));
+ md.md_version = G_VIRSTOR_VERSION;
+ name = gctl_get_ascii(req, "arg0");
+ if (name == NULL) {
+ gctl_error(req, "No 'arg%u' argument.", 0);
+ return;
+ }
+ strlcpy(md.md_name, name, sizeof(md.md_name));
+
+ md.md_virsize = (off_t)gctl_get_intmax(req, "vir_size");
+ md.md_chunk_size = gctl_get_intmax(req, "chunk_size");
+ md.md_count = nargs - 1;
+
+ if (md.md_virsize == 0 || md.md_chunk_size == 0) {
+ gctl_error(req, "Virtual size and chunk size must be non-zero");
+ return;
+ }
+
+ if (md.md_chunk_size % MAXPHYS != 0) {
+ /* XXX: This is not strictly needed, but it's convenient to
+ * impose some limitations on it, so why not MAXPHYS. */
+ size_t new_size = (md.md_chunk_size / MAXPHYS) * MAXPHYS;
+ if (new_size < md.md_chunk_size)
+ new_size += MAXPHYS;
+ fprintf(stderr, "Resizing chunk size to be a multiple of "
+ "MAXPHYS (%d kB).\n", MAXPHYS / 1024);
+ fprintf(stderr, "New chunk size: %zu kB\n", new_size / 1024);
+ md.md_chunk_size = new_size;
+ }
+
+ if (md.md_virsize % md.md_chunk_size != 0) {
+ off_t chunk_count = md.md_virsize / md.md_chunk_size;
+ md.md_virsize = chunk_count * md.md_chunk_size;
+ fprintf(stderr, "Resizing virtual size to be a multiple of "
+ "chunk size.\n");
+ fprintf(stderr, "New virtual size: %zu MB\n",
+ (size_t)(md.md_virsize/(1024 * 1024)));
+ }
+
+ msize = secsize = 0;
+ for (i = 1; i < (unsigned)nargs; i++) {
+ snprintf(param, sizeof(param), "arg%u", i);
+ name = gctl_get_ascii(req, "%s", param);
+ ssize = g_get_sectorsize(name);
+ if (ssize == 0)
+ fprintf(stderr, "%s for %s\n", strerror(errno), name);
+ msize += g_get_mediasize(name);
+ if (secsize == 0)
+ secsize = ssize;
+ else if (secsize != ssize) {
+ gctl_error(req, "Devices need to have same sector size "
+ "(%u on %s needs to be %u).",
+ (u_int)ssize, name, (u_int)secsize);
+ return;
+ }
+ }
+
+ if (secsize == 0) {
+ gctl_error(req, "Device not specified");
+ return;
+ }
+
+ if (md.md_chunk_size % secsize != 0) {
+ fprintf(stderr, "Error: chunk size is not a multiple of sector "
+ "size.");
+ gctl_error(req, "Chunk size (in bytes) must be multiple of %u.",
+ (unsigned int)secsize);
+ return;
+ }
+
+ total_chunks = md.md_virsize / md.md_chunk_size;
+ map_size = total_chunks * sizeof(*map);
+ assert(md.md_virsize % md.md_chunk_size == 0);
+
+ ssize = map_size % secsize;
+ if (ssize != 0) {
+ size_t add_chunks = (secsize - ssize) / sizeof(*map);
+ total_chunks += add_chunks;
+ md.md_virsize = (off_t)total_chunks * (off_t)md.md_chunk_size;
+ map_size = total_chunks * sizeof(*map);
+ fprintf(stderr, "Resizing virtual size to fit virstor "
+ "structures.\n");
+ fprintf(stderr, "New virtual size: %ju MB (%zu new chunks)\n",
+ (uintmax_t)(md.md_virsize / (1024 * 1024)), add_chunks);
+ }
+
+ if (verbose)
+ printf("Total virtual chunks: %zu (%zu MB each), %ju MB total "
+ "virtual size.\n",
+ total_chunks, (size_t)(md.md_chunk_size / (1024 * 1024)),
+ md.md_virsize/(1024 * 1024));
+
+ if ((off_t)md.md_virsize < msize)
+ fprintf(stderr, "WARNING: Virtual storage size < Physical "
+ "available storage (%ju < %ju)\n", md.md_virsize, msize);
+
+ /* Clear last sector first to spoil all components if device exists. */
+ if (verbose)
+ printf("Clearing metadata on");
+
+ for (i = 1; i < (unsigned)nargs; i++) {
+ snprintf(param, sizeof(param), "arg%u", i);
+ name = gctl_get_ascii(req, "%s", param);
+
+ if (verbose)
+ printf(" %s", name);
+
+ msize = g_get_mediasize(name);
+ ssize = g_get_sectorsize(name);
+ if (msize == 0 || ssize == 0) {
+ gctl_error(req, "Can't retrieve information about "
+ "%s: %s.", name, strerror(errno));
+ return;
+ }
+ if (msize < (off_t) MAX(md.md_chunk_size*4, map_size))
+ gctl_error(req, "Device %s is too small", name);
+ error = g_metadata_clear(name, NULL);
+ if (error != 0) {
+ gctl_error(req, "Can't clear metadata on %s: %s.", name,
+ strerror(error));
+ return;
+ }
+ }
+
+
+ /* Write allocation table to the first provider - this needs to be done
+ * before metadata is written because when kernel tastes it it's too
+ * late */
+ name = gctl_get_ascii(req, "arg1"); /* device with metadata */
+ if (verbose)
+ printf(".\nWriting allocation table to %s...", name);
+
+ /* How many chunks does the map occupy? */
+ map_chunks = map_size/md.md_chunk_size;
+ if (map_size % md.md_chunk_size != 0)
+ map_chunks++;
+ if (verbose) {
+ printf(" (%zu MB, %d chunks) ", map_size/(1024*1024), map_chunks);
+ fflush(stdout);
+ }
+
+ if (strncmp(name, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
+ fd = open(name, O_RDWR);
+ else {
+ sprintf(param, "%s%s", _PATH_DEV, name);
+ fd = open(param, O_RDWR);
+ }
+ if (fd < 0)
+ gctl_error(req, "Cannot open provider %s to write map", name);
+
+ /* Do it with calloc because there might be a need to set up chunk flags
+ * in the future */
+ map = calloc(total_chunks, sizeof(*map));
+ if (map == NULL) {
+ gctl_error(req,
+ "Out of memory (need %zu bytes for allocation map)",
+ map_size);
+ }
+
+ written = pwrite(fd, map, map_size, 0);
+ free(map);
+ if ((size_t)written != map_size) {
+ if (verbose) {
+ fprintf(stderr, "\nTried to write %zu, written %zd (%s)\n",
+ map_size, written, strerror(errno));
+ }
+ gctl_error(req, "Error writing out allocation map!");
+ return;
+ }
+ close (fd);
+
+ if (verbose)
+ printf("\nStoring metadata on ");
+
+ /*
+ * ID is randomly generated, unique for a geom. This is used to
+ * recognize all providers belonging to one geom.
+ */
+ md.md_id = arc4random();
+
+ /* Ok, store metadata. */
+ for (i = 1; i < (unsigned)nargs; i++) {
+ snprintf(param, sizeof(param), "arg%u", i);
+ name = gctl_get_ascii(req, "%s", param);
+
+ msize = g_get_mediasize(name);
+ ssize = g_get_sectorsize(name);
+
+ if (verbose)
+ printf("%s ", name);
+
+ /* this provider's position/type in geom */
+ md.no = i - 1;
+ /* this provider's size */
+ md.provsize = msize;
+ /* chunk allocation info */
+ md.chunk_count = md.provsize / md.md_chunk_size;
+ if (verbose)
+ printf("(%u chunks) ", md.chunk_count);
+ /* Check to make sure last sector is unused */
+ if ((off_t)(md.chunk_count * md.md_chunk_size) > (off_t)(msize-ssize))
+ md.chunk_count--;
+ md.chunk_next = 0;
+ if (i != 1) {
+ md.chunk_reserved = 0;
+ md.flags = 0;
+ } else {
+ md.chunk_reserved = map_chunks * 2;
+ md.flags = VIRSTOR_PROVIDER_ALLOCATED |
+ VIRSTOR_PROVIDER_CURRENT;
+ md.chunk_next = md.chunk_reserved;
+ if (verbose)
+ printf("(%u reserved) ", md.chunk_reserved);
+ }
+
+ if (!hardcode)
+ bzero(md.provider, sizeof(md.provider));
+ else {
+ /* convert "/dev/something" to "something" */
+ if (strncmp(name, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0) {
+ strlcpy(md.provider, name + sizeof(_PATH_DEV) - 1,
+ sizeof(md.provider));
+ } else
+ strlcpy(md.provider, name, sizeof(md.provider));
+ }
+ sect = malloc(ssize);
+ if (sect == NULL)
+ err(1, "Cannot allocate sector of %zu bytes", ssize);
+ bzero(sect, ssize);
+ virstor_metadata_encode(&md, sect);
+ error = my_g_metadata_store(name, sect, ssize);
+ free(sect);
+ if (error != 0) {
+ if (verbose)
+ printf("\n");
+ fprintf(stderr, "Can't store metadata on %s: %s.\n",
+ name, strerror(error));
+ gctl_error(req,
+ "Not fully done (error storing metadata).");
+ return;
+ }
+ }
+#if 0
+ if (verbose)
+ printf("\n");
+#endif
+}
+
+/* Clears metadata on given provider(s) IF it's owned by us */
+static void
+virstor_clear(struct gctl_req *req)
+{
+ const char *name;
+ char param[32];
+ unsigned i;
+ int nargs, error;
+ int fd;
+
+ nargs = gctl_get_int(req, "nargs");
+ if (nargs < 1) {
+ gctl_error(req, "Too few arguments.");
+ return;
+ }
+ for (i = 0; i < (unsigned)nargs; i++) {
+ snprintf(param, sizeof(param), "arg%u", i);
+ name = gctl_get_ascii(req, "%s", param);
+
+ error = g_metadata_clear(name, G_VIRSTOR_MAGIC);
+ if (error != 0) {
+ fprintf(stderr, "Can't clear metadata on %s: %s "
+ "(do I own it?)\n", name, strerror(error));
+ gctl_error(req,
+ "Not fully done (can't clear metadata).");
+ continue;
+ }
+ if (strncmp(name, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
+ fd = open(name, O_RDWR);
+ else {
+ sprintf(param, "%s%s", _PATH_DEV, name);
+ fd = open(param, O_RDWR);
+ }
+ if (fd < 0) {
+ gctl_error(req, "Cannot clear header sector for %s",
+ name);
+ continue;
+ }
+ if (verbose)
+ printf("Metadata cleared on %s.\n", name);
+ }
+}
+
+/* Print some metadata information */
+static void
+virstor_metadata_dump(const struct g_virstor_metadata *md)
+{
+ printf(" Magic string: %s\n", md->md_magic);
+ printf(" Metadata version: %u\n", (u_int) md->md_version);
+ printf(" Device name: %s\n", md->md_name);
+ printf(" Device ID: %u\n", (u_int) md->md_id);
+ printf(" Provider index: %u\n", (u_int) md->no);
+ printf(" Active providers: %u\n", (u_int) md->md_count);
+ printf(" Hardcoded provider: %s\n",
+ md->provider[0] != '\0' ? md->provider : "(not hardcoded)");
+ printf(" Virtual size: %u MB\n",
+ (unsigned int)(md->md_virsize/(1024 * 1024)));
+ printf(" Chunk size: %u kB\n", md->md_chunk_size / 1024);
+ printf(" Chunks on provider: %u\n", md->chunk_count);
+ printf(" Chunks free: %u\n", md->chunk_count - md->chunk_next);
+ printf(" Reserved chunks: %u\n", md->chunk_reserved);
+}
+
+/* Called by geom(8) via gvirstor_main() to dump metadata information */
+static void
+virstor_dump(struct gctl_req *req)
+{
+ struct g_virstor_metadata md;
+ u_char tmpmd[512]; /* temporary buffer */
+ const char *name;
+ char param[16];
+ int nargs, error, i;
+
+ assert(sizeof(tmpmd) >= sizeof(md));
+
+ nargs = gctl_get_int(req, "nargs");
+ if (nargs < 1) {
+ gctl_error(req, "Too few arguments.");
+ return;
+ }
+ for (i = 0; i < nargs; i++) {
+ snprintf(param, sizeof(param), "arg%u", i);
+ name = gctl_get_ascii(req, "%s", param);
+
+ error = g_metadata_read(name, (u_char *) & tmpmd, sizeof(tmpmd),
+ G_VIRSTOR_MAGIC);
+ if (error != 0) {
+ fprintf(stderr, "Can't read metadata from %s: %s.\n",
+ name, strerror(error));
+ gctl_error(req,
+ "Not fully done (error reading metadata).");
+ continue;
+ }
+ virstor_metadata_decode((u_char *) & tmpmd, &md);
+ printf("Metadata on %s:\n", name);
+ virstor_metadata_dump(&md);
+ printf("\n");
+ }
+}
diff --git a/sbin/geom/class/virstor/gvirstor.8 b/sbin/geom/class/virstor/gvirstor.8
new file mode 100644
index 0000000..3d93e5b
--- /dev/null
+++ b/sbin/geom/class/virstor/gvirstor.8
@@ -0,0 +1,299 @@
+.\" Copyright (c) 2006-2011 Ivan Voras <ivoras@FreeBSD.org>
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd October 1, 2013
+.Dt GVIRSTOR 8
+.Os
+.Sh NAME
+.Nm gvirstor
+.Nd "control utility for virtual data storage devices"
+.Sh SYNOPSIS
+.Nm
+.Cm label
+.Op Fl hv
+.Op Fl s Ar virsize
+.Op Fl m Ar chunksize
+.Ar name
+.Ar prov ...
+.Nm
+.Cm stop
+.Op Fl fv
+.Ar name ...
+.Nm
+.Cm destroy
+.Op Fl fv
+.Ar name ...
+.Nm
+.Cm add
+.Op Fl vh
+.Ar name prov ...
+.Nm
+.Cm remove
+.Op Fl v
+.Ar name prov ...
+.Nm
+.Cm clear
+.Op Fl v
+.Ar prov ...
+.Nm
+.Cm dump
+.Ar prov ...
+.Nm
+.Cm list
+.Nm
+.Cm status
+.Nm
+.Cm load
+.Nm
+.Cm unload
+.Sh DESCRIPTION
+The
+.Nm
+utility is used for setting up a virtual storage device of arbitrary
+large size
+.Pq for example, several TB ,
+consisting of an arbitrary number of physical storage devices with the
+total size which is equal to or smaller than the virtual size.
+Data for the virtual devices will be allocated from physical devices on
+demand.
+The idea behind
+.Nm
+is similar to the concept of Virtual Memory in operating systems,
+effectively allowing users to overcommit on storage
+.Pq free file system space .
+The concept is also known as "thin provisioning" in virtualization
+environments, only here it is implemented on the level of physical storage
+devices.
+.Pp
+The first argument to
+.Nm
+indicates an action to be performed:
+.Bl -tag -width ".Cm remove"
+.It Cm label
+Set up a virtual device from the given components with the specified
+.Ar name .
+Metadata is stored in the last sector of every component.
+Argument
+.Fl s Ar virsize
+is the size of new virtual device, with default being set to 2 TiB
+.Pq 2097152 MiB .
+Argument
+.Fl m Ar chunksize
+is the chunk size, with default being set to 4 MiB
+.Pq 4096 KiB .
+The default arguments are thus
+.Qq Fl s Ar 2097152 Fl m Ar 4096 .
+.It Cm stop
+Turn off an existing virtual device with the given
+.Ar name .
+This command does not touch on-disk metadata.
+As with other GEOM classes, stopped geoms cannot be started manually.
+.It Cm destroy
+Same as
+.Cm stop.
+.It Cm add
+Adds new components to existing virtual device with the given
+.Ar name .
+The specified virstor device must exist and be active
+.Pq i.e. module loaded, device present in Pa /dev .
+This action can be safely performed while the virstor device is in use
+.Pq Qo hot Qc operation .
+.It Cm remove
+Removes components from existing virtual device with the given
+.Ar name .
+Only unallocated providers can be removed.
+.It Cm clear
+Clear metadata on the given providers.
+.It Cm dump
+Dump metadata stored on the given providers.
+.It Cm list
+See
+.Xr geom 8 .
+.It Cm status
+See
+.Xr geom 8 .
+.It Cm load
+See
+.Xr geom 8 .
+.It Cm unload
+See
+.Xr geom 8 .
+.El
+.Pp
+Additional options:
+.Bl -tag -width ".Fl f"
+.It Fl f
+Force the removal of the specified virtual device.
+.It Fl h
+Hardcode providers' names in metadata.
+.It Fl v
+Be more verbose.
+.El
+.Sh EXAMPLES
+The following example shows how to create a virtual device of default size
+.Pq 2 TiB ,
+of default chunk
+.Pq extent
+size
+.Pq 4 MiB ,
+with two physical devices for backing storage.
+.Bd -literal -offset indent
+.No gvirstor label -v Ar mydata Ar /dev/ada4 Ar /dev/ada6
+.No newfs Ar /dev/virstor/mydata
+.Ed
+.Pp
+From now on, the virtual device will be available via the
+.Pa /dev/virstor/mydata
+device entry.
+To add a new physical device / component to an active virstor device:
+.Bd -literal -offset indent
+.No gvirstor add Ar mydata Ar ada8
+.Ed
+.Pp
+This will add physical storage of
+.Ar ada8
+to
+.Pa /dev/virstor/mydata
+device.
+.Pp
+To see the device status information
+.Pq including how much physical storage is still available for the virtual device ,
+use:
+.Bd -literal -offset indent
+gvirstor list
+.Ed
+.Pp
+All standard
+.Xr geom 8
+subcommands
+.Pq e.g. Cm status , Cm help
+are also supported.
+.Sh SYSCTL VARIABLES
+.Nm
+has several
+.Xr sysctl 8
+tunable variables.
+.Bd -literal -offset indent
+.Va int kern.geom.virstor.debug
+.Ed
+.Pp
+This sysctl controls verbosity of the kernel module, in the range
+1 to 15.
+Messages that are marked with higher verbosity levels than this are
+suppressed.
+Default value is 5 and it is not recommended to set this tunable to less
+than 2, because level 1 messages are error events, and level 2 messages
+are system warnings.
+.Bd -literal -offset indent
+.Va int kern.geom.virstor.chunk_watermark
+.Ed
+.Pp
+Value in this sysctl sets warning watermark level for physical chunk
+usage on a single component.
+The warning is issued when a virstor component has less than this many
+free chunks
+.Pq default 100 .
+.Bd -literal -offset indent
+.Va int kern.geom.virstor.component_watermark
+.Ed
+.Pp
+Value in this sysctl sets warning watermark level for component usage.
+The warning is issued when there are less than this many unallocated
+components
+.Pq default is 1 .
+.Pp
+All these sysctls are also available as
+.Xr loader 8
+tunables.
+.Sh DIAGNOSTICS
+.Ex -std
+.Pp
+.Nm
+kernel module issues log messages with prefixes in standardized format,
+which is useful for log message filtering and dispatching.
+Each message line begins with
+.Bd -literal -offset indent
+.Li GEOM_VIRSTOR[%d]:
+.Ed
+.Pp
+The number
+.Pq %d
+is message verbosity / importance level, in the range 1 to 15.
+If a message filtering, dispatching or operator alert system is used, it
+is recommended that messages with levels 1 and 2 be taken seriously
+.Pq for example, to catch out-of-space conditions as set by watermark
+sysctls.
+.Sh SEE ALSO
+.Xr geom 4 ,
+.Xr fstab 5 ,
+.Xr geom 8 ,
+.Xr glabel 8 ,
+.Xr newfs 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 7.0 .
+.Sh AUTHORS
+.An Ivan Voras Aq Mt ivoras@FreeBSD.org
+.Pp
+Sponsored by Google Summer of Code 2006.
+.Sh BUGS
+Commands
+.Cm add
+and
+.Cm remove
+contain unavoidable critical sections which may make the virstor
+device unusable if a power failure
+.Pq or other disruptive event
+happens during their execution.
+It is recommended to run them when the system is quiescent.
+.Sh ASSUMPTIONS AND INTERACTION WITH FILE SYSTEMS
+There are several assumptions that
+.Nm
+has in its operation: that the size of the virtual storage device will not
+change once it is set, and that the sizes of individual physical storage
+components will always remain constant during their existence.
+For alternative ways to implement virtual or resizable file systems see
+.Xr zfs 1M ,
+.Xr gconcat 8
+and
+.Xr growfs 8 .
+.Pp
+Note that
+.Nm
+has nontrivial interaction with file systems which initialize a large
+number of on-disk structures during newfs.
+If such file systems attempt to spread their structures across the drive
+media
+.Pq like UFS/UFS2 does ,
+their efforts will be effectively foiled by sequential allocation of
+chunks in
+.Nm
+and all their structures will be physically allocated at the start
+of the first virstor component.
+This could have a significant impact on file system performance
+.Pq which can in some rare cases be even positive .
diff --git a/sbin/geom/core/Makefile b/sbin/geom/core/Makefile
new file mode 100644
index 0000000..0636d03
--- /dev/null
+++ b/sbin/geom/core/Makefile
@@ -0,0 +1,16 @@
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../misc
+
+PROG= geom
+MAN= geom.8
+SRCS= geom.c subr.c
+
+NO_SHARED=NO
+
+CFLAGS+= -DGEOM_CLASS_DIR=\"${GEOM_CLASS_DIR}\"
+CFLAGS+= -I${.CURDIR}/../../../sys -I${.CURDIR} -I${.CURDIR}/..
+
+LIBADD= geom util
+
+.include <bsd.prog.mk>
diff --git a/sbin/geom/core/geom.8 b/sbin/geom/core/geom.8
new file mode 100644
index 0000000..ab960ff
--- /dev/null
+++ b/sbin/geom/core/geom.8
@@ -0,0 +1,206 @@
+.\" Copyright (c) 2004-2005 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd January 5, 2011
+.Dt GEOM 8
+.Os
+.Sh NAME
+.Nm geom
+.Nd "universal control utility for GEOM classes"
+.Sh SYNOPSIS
+.Nm
+.Ar class
+.Cm help
+.Nm
+.Ar class
+.Cm list
+.Op Fl a
+.Op Ar name ...
+.Nm
+.Ar class
+.Cm status
+.Op Fl ags
+.Op Ar name ...
+.Nm
+.Ar class
+.Cm load
+.Op Fl v
+.Nm
+.Ar class
+.Cm unload
+.Op Fl v
+.Sh DESCRIPTION
+The
+.Nm
+utility is used to control various GEOM classes.
+A class has to be aware of
+.Xr geom 8
+communication methods, but there are also some standard commands
+which can be used for existing
+.Xr geom 8
+unaware classes.
+Here is the list of standard commands:
+.Bl -tag -width ".Cm status"
+.It Cm help
+List all available commands for the given class.
+.It Cm list
+Print detailed information (within the given class) about all geoms
+(if no additional arguments were specified) or the given geoms.
+This command is only available if the given class exists in the kernel.
+Additional options include:
+.Bl -tag -width ".Fl a"
+.It Fl a
+Print information for geoms without providers.
+.El
+.It Cm status
+Print general information (within the given class) about all geoms
+(if no additional arguments were specified) or the given geoms.
+This command is only available if the given class exists in the kernel.
+.Pp
+Additional options include:
+.Bl -tag -width ".Fl s"
+.It Fl a
+When used with -g, print status for geoms without providers.
+.It Fl g
+Report statuses for geoms instead of providers.
+.It Fl s
+Produce script-friendly output.
+.El
+.It Cm load
+Load the kernel module that implements the given class.
+This command is only available if the class does not yet exist in the kernel and
+the file
+.Pa geom_ Ns Ao Ar class Ac Ns Pa .ko
+can be found in one of the directories specified in
+.Va kern.module_path
+sysctl.
+.It Cm unload
+Unload the kernel module which implements the given class.
+This command is only available if the given class is loaded as a
+kernel module.
+.El
+.Pp
+Class-specific commands are implemented as shared libraries which
+are stored in
+.Pa /lib/geom/
+directory and are loaded via
+.Xr dlopen 3
+function when the class name is known.
+When a class-specific shared library exists, a direct utility should also be
+available under the name of
+.Nm g Ns Ar class .
+.Pp
+Currently available classes which are aware of
+.Xr geom 8 :
+.Pp
+.Bl -bullet -offset indent -compact
+.It
+CACHE
+.It
+CONCAT
+.It
+ELI
+.It
+JOURNAL
+.It
+LABEL
+.It
+MIRROR
+.It
+MOUNTVER
+.It
+MULTIPATH
+.It
+NOP
+.It
+PART
+.It
+RAID
+.It
+RAID3
+.It
+SCHED
+.It
+SHSEC
+.It
+STRIPE
+.It
+VIRSTOR
+.El
+.Sh ENVIRONMENT
+The following environment variables affect the execution of
+.Nm :
+.Bl -tag -width ".Ev GEOM_LIBRARY_PATH"
+.It Ev GEOM_LIBRARY_PATH
+Specifies the path where shared libraries are stored instead of
+.Pa /lib/geom/ .
+Multiple paths can be specified with a colon-separated list of paths.
+.El
+.Sh EXIT STATUS
+Exit status is 0 on success, and 1 if the command fails.
+.Sh EXAMPLES
+The following example shows how to set up a stripe on three disks for automatic
+configuration:
+.Bd -literal -offset indent
+geom stripe label -v -s 65536 data /dev/da0 /dev/da1 /dev/da2
+or:
+gstripe label -v -s 65536 data /dev/da0 /dev/da1 /dev/da2
+.Ed
+.Pp
+Print the list of all providers from the DISK class:
+.Bd -literal -offset indent
+geom disk list
+.Ed
+.Pp
+Unload a kernel module which implements the MD class:
+.Bd -literal -offset indent
+geom md unload
+.Ed
+.Sh SEE ALSO
+.Xr libgeom 3 ,
+.Xr geom 4 ,
+.Xr gcache 8 ,
+.Xr gconcat 8 ,
+.Xr geli 8 ,
+.Xr gjournal 8 ,
+.Xr glabel 8 ,
+.Xr gmirror 8 ,
+.Xr gmountver 8 ,
+.Xr gmultipath 8 ,
+.Xr gnop 8 ,
+.Xr gpart 8 ,
+.Xr graid3 8 ,
+.Xr gsched 8 ,
+.Xr gshsec 8 ,
+.Xr gstripe 8 ,
+.Xr gvirstor 8
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Fx 5.3 .
+.Sh AUTHORS
+.An Pawel Jakub Dawidek Aq Mt pjd@FreeBSD.org
diff --git a/sbin/geom/core/geom.c b/sbin/geom/core/geom.c
new file mode 100644
index 0000000..5d23d93
--- /dev/null
+++ b/sbin/geom/core/geom.c
@@ -0,0 +1,1185 @@
+/*-
+ * Copyright (c) 2004-2009 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/linker.h>
+#include <sys/module.h>
+#include <sys/stat.h>
+#include <sys/sysctl.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <libgen.h>
+#include <libutil.h>
+#include <inttypes.h>
+#include <dlfcn.h>
+#include <assert.h>
+#include <libgeom.h>
+#include <geom.h>
+
+#include "misc/subr.h"
+
+#ifdef STATIC_GEOM_CLASSES
+extern uint32_t gpart_version;
+extern struct g_command gpart_class_commands[];
+extern uint32_t glabel_version;
+extern struct g_command glabel_class_commands[];
+#endif
+
+static char comm[MAXPATHLEN], *class_name = NULL, *gclass_name = NULL;
+static uint32_t *version = NULL;
+static int verbose = 0;
+static struct g_command *class_commands = NULL;
+
+#define GEOM_CLASS_CMDS 0x01
+#define GEOM_STD_CMDS 0x02
+static struct g_command *find_command(const char *cmdstr, int flags);
+static int std_available(const char *name);
+
+static void std_help(struct gctl_req *req, unsigned flags);
+static void std_list(struct gctl_req *req, unsigned flags);
+static void std_status(struct gctl_req *req, unsigned flags);
+static void std_load(struct gctl_req *req, unsigned flags);
+static void std_unload(struct gctl_req *req, unsigned flags);
+
+static struct g_command std_commands[] = {
+ { "help", 0, std_help, G_NULL_OPTS, NULL },
+ { "list", 0, std_list,
+ {
+ { 'a', "all", NULL, G_TYPE_BOOL },
+ G_OPT_SENTINEL
+ },
+ "[-a] [name ...]"
+ },
+ { "status", 0, std_status,
+ {
+ { 'a', "all", NULL, G_TYPE_BOOL },
+ { 'g', "geoms", NULL, G_TYPE_BOOL },
+ { 's', "script", NULL, G_TYPE_BOOL },
+ G_OPT_SENTINEL
+ },
+ "[-ags] [name ...]"
+ },
+ { "load", G_FLAG_VERBOSE | G_FLAG_LOADKLD, std_load, G_NULL_OPTS,
+ NULL },
+ { "unload", G_FLAG_VERBOSE, std_unload, G_NULL_OPTS, NULL },
+ G_CMD_SENTINEL
+};
+
+static void
+usage_command(struct g_command *cmd, const char *prefix)
+{
+ struct g_option *opt;
+ unsigned i;
+
+ if (cmd->gc_usage != NULL) {
+ char *pos, *ptr, *sptr;
+
+ sptr = ptr = strdup(cmd->gc_usage);
+ while ((pos = strsep(&ptr, "\n")) != NULL) {
+ if (*pos == '\0')
+ continue;
+ fprintf(stderr, "%s %s %s %s\n", prefix, comm,
+ cmd->gc_name, pos);
+ }
+ free(sptr);
+ return;
+ }
+
+ fprintf(stderr, "%s %s %s", prefix, comm, cmd->gc_name);
+ if ((cmd->gc_flags & G_FLAG_VERBOSE) != 0)
+ fprintf(stderr, " [-v]");
+ for (i = 0; ; i++) {
+ opt = &cmd->gc_options[i];
+ if (opt->go_name == NULL)
+ break;
+ if (opt->go_val != NULL || G_OPT_TYPE(opt) == G_TYPE_BOOL)
+ fprintf(stderr, " [");
+ else
+ fprintf(stderr, " ");
+ fprintf(stderr, "-%c", opt->go_char);
+ if (G_OPT_TYPE(opt) != G_TYPE_BOOL)
+ fprintf(stderr, " %s", opt->go_name);
+ if (opt->go_val != NULL || G_OPT_TYPE(opt) == G_TYPE_BOOL)
+ fprintf(stderr, "]");
+ }
+ fprintf(stderr, "\n");
+}
+
+static void
+usage(void)
+{
+
+ if (class_name == NULL) {
+ errx(EXIT_FAILURE, "usage: %s <class> <command> [options]",
+ "geom");
+ } else {
+ struct g_command *cmd;
+ const char *prefix;
+ unsigned i;
+
+ prefix = "usage:";
+ if (class_commands != NULL) {
+ for (i = 0; ; i++) {
+ cmd = &class_commands[i];
+ if (cmd->gc_name == NULL)
+ break;
+ usage_command(cmd, prefix);
+ prefix = " ";
+ }
+ }
+ for (i = 0; ; i++) {
+ cmd = &std_commands[i];
+ if (cmd->gc_name == NULL)
+ break;
+ /*
+ * If class defines command, which has the same name as
+ * standard command, skip it, because it was already
+ * shown on usage().
+ */
+ if (find_command(cmd->gc_name, GEOM_CLASS_CMDS) != NULL)
+ continue;
+ usage_command(cmd, prefix);
+ prefix = " ";
+ }
+ exit(EXIT_FAILURE);
+ }
+}
+
+static void
+load_module(void)
+{
+ char name1[64], name2[64];
+
+ snprintf(name1, sizeof(name1), "g_%s", class_name);
+ snprintf(name2, sizeof(name2), "geom_%s", class_name);
+ if (modfind(name1) < 0) {
+ /* Not present in kernel, try loading it. */
+ if (kldload(name2) < 0 || modfind(name1) < 0) {
+ if (errno != EEXIST) {
+ errx(EXIT_FAILURE,
+ "%s module not available!", name2);
+ }
+ }
+ }
+}
+
+static int
+strlcatf(char *str, size_t size, const char *format, ...)
+{
+ size_t len;
+ va_list ap;
+ int ret;
+
+ len = strlen(str);
+ str += len;
+ size -= len;
+
+ va_start(ap, format);
+ ret = vsnprintf(str, size, format, ap);
+ va_end(ap);
+
+ return (ret);
+}
+
+/*
+ * Find given option in options available for given command.
+ */
+static struct g_option *
+find_option(struct g_command *cmd, char ch)
+{
+ struct g_option *opt;
+ unsigned i;
+
+ for (i = 0; ; i++) {
+ opt = &cmd->gc_options[i];
+ if (opt->go_name == NULL)
+ return (NULL);
+ if (opt->go_char == ch)
+ return (opt);
+ }
+ /* NOTREACHED */
+ return (NULL);
+}
+
+/*
+ * Add given option to gctl_req.
+ */
+static void
+set_option(struct gctl_req *req, struct g_option *opt, const char *val)
+{
+ const char *optname;
+ uint64_t number;
+ void *ptr;
+
+ if (G_OPT_ISMULTI(opt)) {
+ size_t optnamesize;
+
+ if (G_OPT_NUM(opt) == UCHAR_MAX)
+ errx(EXIT_FAILURE, "Too many -%c options.", opt->go_char);
+
+ /*
+ * Base option name length plus 3 bytes for option number
+ * (max. 255 options) plus 1 byte for terminating '\0'.
+ */
+ optnamesize = strlen(opt->go_name) + 3 + 1;
+ ptr = malloc(optnamesize);
+ if (ptr == NULL)
+ errx(EXIT_FAILURE, "No memory.");
+ snprintf(ptr, optnamesize, "%s%u", opt->go_name, G_OPT_NUM(opt));
+ G_OPT_NUMINC(opt);
+ optname = ptr;
+ } else {
+ optname = opt->go_name;
+ }
+
+ if (G_OPT_TYPE(opt) == G_TYPE_NUMBER) {
+ if (expand_number(val, &number) == -1) {
+ err(EXIT_FAILURE, "Invalid value for '%c' argument",
+ opt->go_char);
+ }
+ ptr = malloc(sizeof(intmax_t));
+ if (ptr == NULL)
+ errx(EXIT_FAILURE, "No memory.");
+ *(intmax_t *)ptr = number;
+ opt->go_val = ptr;
+ gctl_ro_param(req, optname, sizeof(intmax_t), opt->go_val);
+ } else if (G_OPT_TYPE(opt) == G_TYPE_STRING) {
+ gctl_ro_param(req, optname, -1, val);
+ } else if (G_OPT_TYPE(opt) == G_TYPE_BOOL) {
+ ptr = malloc(sizeof(int));
+ if (ptr == NULL)
+ errx(EXIT_FAILURE, "No memory.");
+ *(int *)ptr = *val - '0';
+ opt->go_val = ptr;
+ gctl_ro_param(req, optname, sizeof(int), opt->go_val);
+ } else {
+ assert(!"Invalid type");
+ }
+
+ if (G_OPT_ISMULTI(opt))
+ free(__DECONST(char *, optname));
+}
+
+/*
+ * 1. Add given argument by caller.
+ * 2. Add default values of not given arguments.
+ * 3. Add the rest of arguments.
+ */
+static void
+parse_arguments(struct g_command *cmd, struct gctl_req *req, int *argc,
+ char ***argv)
+{
+ struct g_option *opt;
+ char opts[64];
+ unsigned i;
+ int ch;
+
+ *opts = '\0';
+ if ((cmd->gc_flags & G_FLAG_VERBOSE) != 0)
+ strlcat(opts, "v", sizeof(opts));
+ for (i = 0; ; i++) {
+ opt = &cmd->gc_options[i];
+ if (opt->go_name == NULL)
+ break;
+ assert(G_OPT_TYPE(opt) != 0);
+ assert((opt->go_type & ~(G_TYPE_MASK | G_TYPE_MULTI)) == 0);
+ /* Multiple bool arguments makes no sense. */
+ assert(G_OPT_TYPE(opt) != G_TYPE_BOOL ||
+ (opt->go_type & G_TYPE_MULTI) == 0);
+ strlcatf(opts, sizeof(opts), "%c", opt->go_char);
+ if (G_OPT_TYPE(opt) != G_TYPE_BOOL)
+ strlcat(opts, ":", sizeof(opts));
+ }
+
+ /*
+ * Add specified arguments.
+ */
+ while ((ch = getopt(*argc, *argv, opts)) != -1) {
+ /* Standard (not passed to kernel) options. */
+ switch (ch) {
+ case 'v':
+ verbose = 1;
+ continue;
+ }
+ /* Options passed to kernel. */
+ opt = find_option(cmd, ch);
+ if (opt == NULL)
+ usage();
+ if (!G_OPT_ISMULTI(opt) && G_OPT_ISDONE(opt)) {
+ warnx("Option '%c' specified twice.", opt->go_char);
+ usage();
+ }
+ G_OPT_DONE(opt);
+
+ if (G_OPT_TYPE(opt) == G_TYPE_BOOL)
+ set_option(req, opt, "1");
+ else
+ set_option(req, opt, optarg);
+ }
+ *argc -= optind;
+ *argv += optind;
+
+ /*
+ * Add not specified arguments, but with default values.
+ */
+ for (i = 0; ; i++) {
+ opt = &cmd->gc_options[i];
+ if (opt->go_name == NULL)
+ break;
+ if (G_OPT_ISDONE(opt))
+ continue;
+
+ if (G_OPT_TYPE(opt) == G_TYPE_BOOL) {
+ assert(opt->go_val == NULL);
+ set_option(req, opt, "0");
+ } else {
+ if (opt->go_val == NULL) {
+ warnx("Option '%c' not specified.",
+ opt->go_char);
+ usage();
+ } else if (opt->go_val == G_VAL_OPTIONAL) {
+ /* add nothing. */
+ } else {
+ set_option(req, opt, opt->go_val);
+ }
+ }
+ }
+
+ /*
+ * Add rest of given arguments.
+ */
+ gctl_ro_param(req, "nargs", sizeof(int), argc);
+ for (i = 0; i < (unsigned)*argc; i++) {
+ char argname[16];
+
+ snprintf(argname, sizeof(argname), "arg%u", i);
+ gctl_ro_param(req, argname, -1, (*argv)[i]);
+ }
+}
+
+/*
+ * Find given command in commands available for given class.
+ */
+static struct g_command *
+find_command(const char *cmdstr, int flags)
+{
+ struct g_command *cmd;
+ unsigned i;
+
+ /*
+ * First try to find command defined by loaded library.
+ */
+ if ((flags & GEOM_CLASS_CMDS) != 0 && class_commands != NULL) {
+ for (i = 0; ; i++) {
+ cmd = &class_commands[i];
+ if (cmd->gc_name == NULL)
+ break;
+ if (strcmp(cmd->gc_name, cmdstr) == 0)
+ return (cmd);
+ }
+ }
+ /*
+ * Now try to find in standard commands.
+ */
+ if ((flags & GEOM_STD_CMDS) != 0) {
+ for (i = 0; ; i++) {
+ cmd = &std_commands[i];
+ if (cmd->gc_name == NULL)
+ break;
+ if (strcmp(cmd->gc_name, cmdstr) == 0)
+ return (cmd);
+ }
+ }
+ return (NULL);
+}
+
+static unsigned
+set_flags(struct g_command *cmd)
+{
+ unsigned flags = 0;
+
+ if ((cmd->gc_flags & G_FLAG_VERBOSE) != 0 && verbose)
+ flags |= G_FLAG_VERBOSE;
+
+ return (flags);
+}
+
+/*
+ * Run command.
+ */
+static void
+run_command(int argc, char *argv[])
+{
+ struct g_command *cmd;
+ struct gctl_req *req;
+ const char *errstr;
+ char buf[4096];
+
+ /* First try to find a command defined by a class. */
+ cmd = find_command(argv[0], GEOM_CLASS_CMDS);
+ if (cmd == NULL) {
+ /* Now, try to find a standard command. */
+ cmd = find_command(argv[0], GEOM_STD_CMDS);
+ if (cmd == NULL) {
+ warnx("Unknown command: %s.", argv[0]);
+ usage();
+ }
+ if (!std_available(cmd->gc_name)) {
+ warnx("Command '%s' not available.", argv[0]);
+ exit(EXIT_FAILURE);
+ }
+ }
+ if ((cmd->gc_flags & G_FLAG_LOADKLD) != 0)
+ load_module();
+
+ req = gctl_get_handle();
+ gctl_ro_param(req, "class", -1, gclass_name);
+ gctl_ro_param(req, "verb", -1, argv[0]);
+ if (version != NULL)
+ gctl_ro_param(req, "version", sizeof(*version), version);
+ parse_arguments(cmd, req, &argc, &argv);
+
+ bzero(buf, sizeof(buf));
+ if (cmd->gc_func != NULL) {
+ unsigned flags;
+
+ flags = set_flags(cmd);
+ cmd->gc_func(req, flags);
+ errstr = req->error;
+ } else {
+ gctl_rw_param(req, "output", sizeof(buf), buf);
+ errstr = gctl_issue(req);
+ }
+ if (errstr != NULL && errstr[0] != '\0') {
+ warnx("%s", errstr);
+ if (strncmp(errstr, "warning: ", strlen("warning: ")) != 0) {
+ gctl_free(req);
+ exit(EXIT_FAILURE);
+ }
+ }
+ if (buf[0] != '\0')
+ printf("%s", buf);
+ gctl_free(req);
+ if (verbose)
+ printf("Done.\n");
+ exit(EXIT_SUCCESS);
+}
+
+#ifndef STATIC_GEOM_CLASSES
+static const char *
+library_path(void)
+{
+ const char *path;
+
+ path = getenv("GEOM_LIBRARY_PATH");
+ if (path == NULL)
+ path = GEOM_CLASS_DIR;
+ return (path);
+}
+
+static void
+load_library(void)
+{
+ char *curpath, path[MAXPATHLEN], *tofree, *totalpath;
+ uint32_t *lib_version;
+ void *dlh;
+ int ret;
+
+ ret = 0;
+ tofree = totalpath = strdup(library_path());
+ if (totalpath == NULL)
+ err(EXIT_FAILURE, "Not enough memory for library path");
+
+ if (strchr(totalpath, ':') != NULL)
+ curpath = strsep(&totalpath, ":");
+ else
+ curpath = totalpath;
+ /* Traverse the paths to find one that contains the library we want. */
+ while (curpath != NULL) {
+ snprintf(path, sizeof(path), "%s/geom_%s.so", curpath,
+ class_name);
+ ret = access(path, F_OK);
+ if (ret == -1) {
+ if (errno == ENOENT) {
+ /*
+ * If we cannot find library, try the next
+ * path.
+ */
+ curpath = strsep(&totalpath, ":");
+ continue;
+ }
+ err(EXIT_FAILURE, "Cannot access library");
+ }
+ break;
+ }
+ free(tofree);
+ /* No library was found, but standard commands can still be used */
+ if (ret == -1)
+ return;
+ dlh = dlopen(path, RTLD_NOW);
+ if (dlh == NULL)
+ errx(EXIT_FAILURE, "Cannot open library: %s.", dlerror());
+ lib_version = dlsym(dlh, "lib_version");
+ if (lib_version == NULL) {
+ warnx("Cannot find symbol %s: %s.", "lib_version", dlerror());
+ dlclose(dlh);
+ exit(EXIT_FAILURE);
+ }
+ if (*lib_version != G_LIB_VERSION) {
+ dlclose(dlh);
+ errx(EXIT_FAILURE, "%s and %s are not synchronized.",
+ getprogname(), path);
+ }
+ version = dlsym(dlh, "version");
+ if (version == NULL) {
+ warnx("Cannot find symbol %s: %s.", "version", dlerror());
+ dlclose(dlh);
+ exit(EXIT_FAILURE);
+ }
+ class_commands = dlsym(dlh, "class_commands");
+ if (class_commands == NULL) {
+ warnx("Cannot find symbol %s: %s.", "class_commands",
+ dlerror());
+ dlclose(dlh);
+ exit(EXIT_FAILURE);
+ }
+}
+#endif /* !STATIC_GEOM_CLASSES */
+
+/*
+ * Class name should be all capital letters.
+ */
+static void
+set_class_name(void)
+{
+ char *s1, *s2;
+
+ s1 = class_name;
+ for (; *s1 != '\0'; s1++)
+ *s1 = tolower(*s1);
+ gclass_name = malloc(strlen(class_name) + 1);
+ if (gclass_name == NULL)
+ errx(EXIT_FAILURE, "No memory");
+ s1 = gclass_name;
+ s2 = class_name;
+ for (; *s2 != '\0'; s2++)
+ *s1++ = toupper(*s2);
+ *s1 = '\0';
+}
+
+static void
+get_class(int *argc, char ***argv)
+{
+
+ snprintf(comm, sizeof(comm), "%s", basename((*argv)[0]));
+ if (strcmp(comm, "geom") == 0) {
+ if (*argc < 2)
+ usage();
+ else if (*argc == 2) {
+ if (strcmp((*argv)[1], "-h") == 0 ||
+ strcmp((*argv)[1], "help") == 0) {
+ usage();
+ }
+ }
+ strlcatf(comm, sizeof(comm), " %s", (*argv)[1]);
+ class_name = (*argv)[1];
+ *argc -= 2;
+ *argv += 2;
+ } else if (*comm == 'g') {
+ class_name = comm + 1;
+ *argc -= 1;
+ *argv += 1;
+ } else {
+ errx(EXIT_FAILURE, "Invalid utility name.");
+ }
+
+#ifndef STATIC_GEOM_CLASSES
+ load_library();
+#else
+ if (!strcasecmp(class_name, "part")) {
+ version = &gpart_version;
+ class_commands = gpart_class_commands;
+ } else if (!strcasecmp(class_name, "label")) {
+ version = &glabel_version;
+ class_commands = glabel_class_commands;
+ } else
+ errx(EXIT_FAILURE, "Invalid class name.");
+#endif /* !STATIC_GEOM_CLASSES */
+
+ set_class_name();
+
+ /* If we can't load or list, it's not a class. */
+ if (!std_available("load") && !std_available("list"))
+ errx(EXIT_FAILURE, "Invalid class name.");
+
+ if (*argc < 1)
+ usage();
+}
+
+int
+main(int argc, char *argv[])
+{
+
+ get_class(&argc, &argv);
+ run_command(argc, argv);
+ /* NOTREACHED */
+
+ exit(EXIT_FAILURE);
+}
+
+static struct gclass *
+find_class(struct gmesh *mesh, const char *name)
+{
+ struct gclass *classp;
+
+ LIST_FOREACH(classp, &mesh->lg_class, lg_class) {
+ if (strcmp(classp->lg_name, name) == 0)
+ return (classp);
+ }
+ return (NULL);
+}
+
+static struct ggeom *
+find_geom(struct gclass *classp, const char *name)
+{
+ struct ggeom *gp;
+
+ LIST_FOREACH(gp, &classp->lg_geom, lg_geom) {
+ if (strcmp(gp->lg_name, name) == 0)
+ return (gp);
+ }
+ return (NULL);
+}
+
+static void
+list_one_provider(struct gprovider *pp, const char *prefix)
+{
+ struct gconfig *conf;
+ char buf[5];
+
+ printf("Name: %s\n", pp->lg_name);
+ humanize_number(buf, sizeof(buf), (int64_t)pp->lg_mediasize, "",
+ HN_AUTOSCALE, HN_B | HN_NOSPACE | HN_DECIMAL);
+ printf("%sMediasize: %jd (%s)\n", prefix, (intmax_t)pp->lg_mediasize,
+ buf);
+ printf("%sSectorsize: %u\n", prefix, pp->lg_sectorsize);
+ if (pp->lg_stripesize > 0 || pp->lg_stripeoffset > 0) {
+ printf("%sStripesize: %ju\n", prefix, pp->lg_stripesize);
+ printf("%sStripeoffset: %ju\n", prefix, pp->lg_stripeoffset);
+ }
+ printf("%sMode: %s\n", prefix, pp->lg_mode);
+ LIST_FOREACH(conf, &pp->lg_config, lg_config) {
+ printf("%s%s: %s\n", prefix, conf->lg_name, conf->lg_val);
+ }
+}
+
+static void
+list_one_consumer(struct gconsumer *cp, const char *prefix)
+{
+ struct gprovider *pp;
+ struct gconfig *conf;
+
+ pp = cp->lg_provider;
+ if (pp == NULL)
+ printf("[no provider]\n");
+ else {
+ char buf[5];
+
+ printf("Name: %s\n", pp->lg_name);
+ humanize_number(buf, sizeof(buf), (int64_t)pp->lg_mediasize, "",
+ HN_AUTOSCALE, HN_B | HN_NOSPACE | HN_DECIMAL);
+ printf("%sMediasize: %jd (%s)\n", prefix,
+ (intmax_t)pp->lg_mediasize, buf);
+ printf("%sSectorsize: %u\n", prefix, pp->lg_sectorsize);
+ if (pp->lg_stripesize > 0 || pp->lg_stripeoffset > 0) {
+ printf("%sStripesize: %ju\n", prefix, pp->lg_stripesize);
+ printf("%sStripeoffset: %ju\n", prefix, pp->lg_stripeoffset);
+ }
+ printf("%sMode: %s\n", prefix, cp->lg_mode);
+ }
+ LIST_FOREACH(conf, &cp->lg_config, lg_config) {
+ printf("%s%s: %s\n", prefix, conf->lg_name, conf->lg_val);
+ }
+}
+
+static void
+list_one_geom(struct ggeom *gp)
+{
+ struct gprovider *pp;
+ struct gconsumer *cp;
+ struct gconfig *conf;
+ unsigned n;
+
+ printf("Geom name: %s\n", gp->lg_name);
+ LIST_FOREACH(conf, &gp->lg_config, lg_config) {
+ printf("%s: %s\n", conf->lg_name, conf->lg_val);
+ }
+ if (!LIST_EMPTY(&gp->lg_provider)) {
+ printf("Providers:\n");
+ n = 1;
+ LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {
+ printf("%u. ", n++);
+ list_one_provider(pp, " ");
+ }
+ }
+ if (!LIST_EMPTY(&gp->lg_consumer)) {
+ printf("Consumers:\n");
+ n = 1;
+ LIST_FOREACH(cp, &gp->lg_consumer, lg_consumer) {
+ printf("%u. ", n++);
+ list_one_consumer(cp, " ");
+ }
+ }
+ printf("\n");
+}
+
+static void
+std_help(struct gctl_req *req __unused, unsigned flags __unused)
+{
+
+ usage();
+}
+
+static int
+std_list_available(void)
+{
+ struct gmesh mesh;
+ struct gclass *classp;
+ int error;
+
+ error = geom_gettree(&mesh);
+ if (error != 0)
+ errc(EXIT_FAILURE, error, "Cannot get GEOM tree");
+ classp = find_class(&mesh, gclass_name);
+ geom_deletetree(&mesh);
+ if (classp != NULL)
+ return (1);
+ return (0);
+}
+
+static void
+std_list(struct gctl_req *req, unsigned flags __unused)
+{
+ struct gmesh mesh;
+ struct gclass *classp;
+ struct ggeom *gp;
+ const char *name;
+ int all, error, i, nargs;
+
+ error = geom_gettree(&mesh);
+ if (error != 0)
+ errc(EXIT_FAILURE, error, "Cannot get GEOM tree");
+ classp = find_class(&mesh, gclass_name);
+ if (classp == NULL) {
+ geom_deletetree(&mesh);
+ errx(EXIT_FAILURE, "Class %s not found.", gclass_name);
+ }
+ nargs = gctl_get_int(req, "nargs");
+ all = gctl_get_int(req, "all");
+ if (nargs > 0) {
+ for (i = 0; i < nargs; i++) {
+ name = gctl_get_ascii(req, "arg%d", i);
+ gp = find_geom(classp, name);
+ if (gp == NULL)
+ errx(EXIT_FAILURE, "No such geom: %s.", name);
+ list_one_geom(gp);
+ }
+ } else {
+ LIST_FOREACH(gp, &classp->lg_geom, lg_geom) {
+ if (LIST_EMPTY(&gp->lg_provider) && !all)
+ continue;
+ list_one_geom(gp);
+ }
+ }
+ geom_deletetree(&mesh);
+}
+
+static int
+std_status_available(void)
+{
+
+ /* 'status' command is available when 'list' command is. */
+ return (std_list_available());
+}
+
+static void
+status_update_len(struct ggeom *gp, int *name_len, int *status_len)
+{
+ struct gconfig *conf;
+ int len;
+
+ assert(gp != NULL);
+ assert(name_len != NULL);
+ assert(status_len != NULL);
+
+ len = strlen(gp->lg_name);
+ if (*name_len < len)
+ *name_len = len;
+ LIST_FOREACH(conf, &gp->lg_config, lg_config) {
+ if (strcasecmp(conf->lg_name, "state") == 0) {
+ len = strlen(conf->lg_val);
+ if (*status_len < len)
+ *status_len = len;
+ }
+ }
+}
+
+static void
+status_update_len_prs(struct ggeom *gp, int *name_len, int *status_len)
+{
+ struct gprovider *pp;
+ struct gconfig *conf;
+ int len, glen;
+
+ assert(gp != NULL);
+ assert(name_len != NULL);
+ assert(status_len != NULL);
+
+ glen = 0;
+ LIST_FOREACH(conf, &gp->lg_config, lg_config) {
+ if (strcasecmp(conf->lg_name, "state") == 0) {
+ glen = strlen(conf->lg_val);
+ break;
+ }
+ }
+ LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {
+ len = strlen(pp->lg_name);
+ if (*name_len < len)
+ *name_len = len;
+ len = glen;
+ LIST_FOREACH(conf, &pp->lg_config, lg_config) {
+ if (strcasecmp(conf->lg_name, "state") == 0) {
+ len = strlen(conf->lg_val);
+ break;
+ }
+ }
+ if (*status_len < len)
+ *status_len = len;
+ }
+}
+
+static char *
+status_one_consumer(struct gconsumer *cp)
+{
+ static char buf[256];
+ struct gprovider *pp;
+ struct gconfig *conf;
+ const char *state, *syncr;
+
+ pp = cp->lg_provider;
+ if (pp == NULL)
+ return (NULL);
+ state = NULL;
+ syncr = NULL;
+ LIST_FOREACH(conf, &cp->lg_config, lg_config) {
+ if (strcasecmp(conf->lg_name, "state") == 0)
+ state = conf->lg_val;
+ if (strcasecmp(conf->lg_name, "synchronized") == 0)
+ syncr = conf->lg_val;
+ }
+ if (state == NULL && syncr == NULL)
+ snprintf(buf, sizeof(buf), "%s", pp->lg_name);
+ else if (state != NULL && syncr != NULL) {
+ snprintf(buf, sizeof(buf), "%s (%s, %s)", pp->lg_name,
+ state, syncr);
+ } else {
+ snprintf(buf, sizeof(buf), "%s (%s)", pp->lg_name,
+ state ? state : syncr);
+ }
+ return (buf);
+}
+
+static void
+status_one_geom(struct ggeom *gp, int script, int name_len, int status_len)
+{
+ struct gconsumer *cp;
+ struct gconfig *conf;
+ const char *name, *status, *component;
+ int gotone;
+
+ name = gp->lg_name;
+ status = "N/A";
+ LIST_FOREACH(conf, &gp->lg_config, lg_config) {
+ if (strcasecmp(conf->lg_name, "state") == 0) {
+ status = conf->lg_val;
+ break;
+ }
+ }
+ gotone = 0;
+ LIST_FOREACH(cp, &gp->lg_consumer, lg_consumer) {
+ component = status_one_consumer(cp);
+ if (component == NULL)
+ continue;
+ gotone = 1;
+ printf("%*s %*s %s\n", name_len, name, status_len, status,
+ component);
+ if (!script)
+ name = status = "";
+ }
+ if (!gotone) {
+ printf("%*s %*s %s\n", name_len, name, status_len, status,
+ "N/A");
+ }
+}
+
+static void
+status_one_geom_prs(struct ggeom *gp, int script, int name_len, int status_len)
+{
+ struct gprovider *pp;
+ struct gconsumer *cp;
+ struct gconfig *conf;
+ const char *name, *status, *component;
+ int gotone;
+
+ LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {
+ name = pp->lg_name;
+ status = "N/A";
+ LIST_FOREACH(conf, &gp->lg_config, lg_config) {
+ if (strcasecmp(conf->lg_name, "state") == 0) {
+ status = conf->lg_val;
+ break;
+ }
+ }
+ LIST_FOREACH(conf, &pp->lg_config, lg_config) {
+ if (strcasecmp(conf->lg_name, "state") == 0) {
+ status = conf->lg_val;
+ break;
+ }
+ }
+ gotone = 0;
+ LIST_FOREACH(cp, &gp->lg_consumer, lg_consumer) {
+ component = status_one_consumer(cp);
+ if (component == NULL)
+ continue;
+ gotone = 1;
+ printf("%*s %*s %s\n", name_len, name,
+ status_len, status, component);
+ if (!script)
+ name = status = "";
+ }
+ if (!gotone) {
+ printf("%*s %*s %s\n", name_len, name,
+ status_len, status, "N/A");
+ }
+ }
+}
+
+static void
+std_status(struct gctl_req *req, unsigned flags __unused)
+{
+ struct gmesh mesh;
+ struct gclass *classp;
+ struct ggeom *gp;
+ const char *name;
+ int name_len, status_len;
+ int all, error, geoms, i, n, nargs, script;
+
+ error = geom_gettree(&mesh);
+ if (error != 0)
+ errc(EXIT_FAILURE, error, "Cannot get GEOM tree");
+ classp = find_class(&mesh, gclass_name);
+ if (classp == NULL)
+ errx(EXIT_FAILURE, "Class %s not found.", gclass_name);
+ nargs = gctl_get_int(req, "nargs");
+ all = gctl_get_int(req, "all");
+ geoms = gctl_get_int(req, "geoms");
+ script = gctl_get_int(req, "script");
+ if (script) {
+ name_len = 0;
+ status_len = 0;
+ } else {
+ name_len = strlen("Name");
+ status_len = strlen("Status");
+ }
+ if (nargs > 0) {
+ for (i = 0, n = 0; i < nargs; i++) {
+ name = gctl_get_ascii(req, "arg%d", i);
+ gp = find_geom(classp, name);
+ if (gp == NULL)
+ errx(EXIT_FAILURE, "No such geom: %s.", name);
+ if (geoms) {
+ status_update_len(gp,
+ &name_len, &status_len);
+ } else {
+ status_update_len_prs(gp,
+ &name_len, &status_len);
+ }
+ n++;
+ }
+ if (n == 0)
+ goto end;
+ } else {
+ n = 0;
+ LIST_FOREACH(gp, &classp->lg_geom, lg_geom) {
+ if (LIST_EMPTY(&gp->lg_provider) && !all)
+ continue;
+ if (geoms) {
+ status_update_len(gp,
+ &name_len, &status_len);
+ } else {
+ status_update_len_prs(gp,
+ &name_len, &status_len);
+ }
+ n++;
+ }
+ if (n == 0)
+ goto end;
+ }
+ if (!script) {
+ printf("%*s %*s %s\n", name_len, "Name", status_len, "Status",
+ "Components");
+ }
+ if (nargs > 0) {
+ for (i = 0; i < nargs; i++) {
+ name = gctl_get_ascii(req, "arg%d", i);
+ gp = find_geom(classp, name);
+ if (gp == NULL)
+ continue;
+ if (geoms) {
+ status_one_geom(gp, script, name_len,
+ status_len);
+ } else {
+ status_one_geom_prs(gp, script, name_len,
+ status_len);
+ }
+ }
+ } else {
+ LIST_FOREACH(gp, &classp->lg_geom, lg_geom) {
+ if (LIST_EMPTY(&gp->lg_provider) && !all)
+ continue;
+ if (geoms) {
+ status_one_geom(gp, script, name_len,
+ status_len);
+ } else {
+ status_one_geom_prs(gp, script, name_len,
+ status_len);
+ }
+ }
+ }
+end:
+ geom_deletetree(&mesh);
+}
+
+static int
+std_load_available(void)
+{
+ char name[MAXPATHLEN], paths[MAXPATHLEN * 8], *p;
+ struct stat sb;
+ size_t len;
+
+ snprintf(name, sizeof(name), "g_%s", class_name);
+ /*
+ * If already in kernel, "load" command is not available.
+ */
+ if (modfind(name) >= 0)
+ return (0);
+ bzero(paths, sizeof(paths));
+ len = sizeof(paths);
+ if (sysctlbyname("kern.module_path", paths, &len, NULL, 0) < 0)
+ err(EXIT_FAILURE, "sysctl(kern.module_path)");
+ for (p = strtok(paths, ";"); p != NULL; p = strtok(NULL, ";")) {
+ snprintf(name, sizeof(name), "%s/geom_%s.ko", p, class_name);
+ /*
+ * If geom_<name>.ko file exists, "load" command is available.
+ */
+ if (stat(name, &sb) == 0)
+ return (1);
+ }
+ return (0);
+}
+
+static void
+std_load(struct gctl_req *req __unused, unsigned flags)
+{
+
+ /*
+ * Do nothing special here, because of G_FLAG_LOADKLD flag,
+ * module is already loaded.
+ */
+ if ((flags & G_FLAG_VERBOSE) != 0)
+ printf("Module available.\n");
+}
+
+static int
+std_unload_available(void)
+{
+ char name[64];
+ int id;
+
+ snprintf(name, sizeof(name), "geom_%s", class_name);
+ id = kldfind(name);
+ if (id >= 0)
+ return (1);
+ return (0);
+}
+
+static void
+std_unload(struct gctl_req *req, unsigned flags __unused)
+{
+ char name[64];
+ int id;
+
+ snprintf(name, sizeof(name), "geom_%s", class_name);
+ id = kldfind(name);
+ if (id < 0) {
+ gctl_error(req, "Could not find module: %s.", strerror(errno));
+ return;
+ }
+ if (kldunload(id) < 0) {
+ gctl_error(req, "Could not unload module: %s.",
+ strerror(errno));
+ return;
+ }
+}
+
+static int
+std_available(const char *name)
+{
+
+ if (strcmp(name, "help") == 0)
+ return (1);
+ else if (strcmp(name, "list") == 0)
+ return (std_list_available());
+ else if (strcmp(name, "status") == 0)
+ return (std_status_available());
+ else if (strcmp(name, "load") == 0)
+ return (std_load_available());
+ else if (strcmp(name, "unload") == 0)
+ return (std_unload_available());
+ else
+ assert(!"Unknown standard command.");
+ return (0);
+}
diff --git a/sbin/geom/core/geom.h b/sbin/geom/core/geom.h
new file mode 100644
index 0000000..3b0a8eb
--- /dev/null
+++ b/sbin/geom/core/geom.h
@@ -0,0 +1,75 @@
+/*-
+ * Copyright (c) 2004 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _GEOM_H_
+#define _GEOM_H_
+#define G_LIB_VERSION 5
+
+#define G_FLAG_NONE 0x0000
+#define G_FLAG_VERBOSE 0x0001
+#define G_FLAG_LOADKLD 0x0002
+
+#define G_TYPE_NONE 0x00
+#define G_TYPE_BOOL 0x01
+#define G_TYPE_STRING 0x02
+#define G_TYPE_NUMBER 0x03
+#define G_TYPE_MASK 0x0f
+#define G_TYPE_DONE 0x10
+#define G_TYPE_MULTI 0x20
+#define G_TYPE_NUMMASK 0xff00
+#define G_TYPE_NUMSHIFT 8
+
+#define G_OPT_MAX 16
+#define G_OPT_DONE(opt) do { (opt)->go_type |= G_TYPE_DONE; } while (0)
+#define G_OPT_ISDONE(opt) ((opt)->go_type & G_TYPE_DONE)
+#define G_OPT_ISMULTI(opt) ((opt)->go_type & G_TYPE_MULTI)
+#define G_OPT_TYPE(opt) ((opt)->go_type & G_TYPE_MASK)
+#define G_OPT_NUM(opt) (((opt)->go_type & G_TYPE_NUMMASK) >> G_TYPE_NUMSHIFT)
+#define G_OPT_NUMINC(opt) ((opt)->go_type += (1 << G_TYPE_NUMSHIFT))
+
+#define G_VAL_OPTIONAL ((void *)-1)
+
+#define G_OPT_SENTINEL { '\0', NULL, NULL, G_TYPE_NONE }
+#define G_NULL_OPTS { G_OPT_SENTINEL }
+#define G_CMD_SENTINEL { NULL, 0, NULL, G_NULL_OPTS, NULL }
+
+struct g_option {
+ char go_char;
+ const char *go_name;
+ const void *go_val;
+ unsigned go_type;
+};
+
+struct g_command {
+ const char *gc_name;
+ unsigned gc_flags;
+ void (*gc_func)(struct gctl_req *, unsigned);
+ struct g_option gc_options[G_OPT_MAX];
+ const char *gc_usage;
+};
+#endif /* !_GEOM_H_ */
diff --git a/sbin/geom/misc/subr.c b/sbin/geom/misc/subr.c
new file mode 100644
index 0000000..f7b2764
--- /dev/null
+++ b/sbin/geom/misc/subr.c
@@ -0,0 +1,537 @@
+/*-
+ * Copyright (c) 2004-2010 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/disk.h>
+#include <sys/endian.h>
+#include <sys/uio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <inttypes.h>
+#include <stdarg.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+#include <assert.h>
+#include <libgeom.h>
+
+#include "misc/subr.h"
+
+
+struct std_metadata {
+ char md_magic[16];
+ uint32_t md_version;
+};
+
+static void
+std_metadata_decode(const unsigned char *data, struct std_metadata *md)
+{
+
+ bcopy(data, md->md_magic, sizeof(md->md_magic));
+ md->md_version = le32dec(data + 16);
+}
+
+/*
+ * Greatest Common Divisor.
+ */
+static unsigned int
+gcd(unsigned int a, unsigned int b)
+{
+ unsigned int c;
+
+ while (b != 0) {
+ c = a;
+ a = b;
+ b = (c % b);
+ }
+ return (a);
+}
+
+/*
+ * Least Common Multiple.
+ */
+unsigned int
+g_lcm(unsigned int a, unsigned int b)
+{
+
+ return ((a * b) / gcd(a, b));
+}
+
+uint32_t
+bitcount32(uint32_t x)
+{
+
+ x = (x & 0x55555555) + ((x & 0xaaaaaaaa) >> 1);
+ x = (x & 0x33333333) + ((x & 0xcccccccc) >> 2);
+ x = (x & 0x0f0f0f0f) + ((x & 0xf0f0f0f0) >> 4);
+ x = (x & 0x00ff00ff) + ((x & 0xff00ff00) >> 8);
+ x = (x & 0x0000ffff) + ((x & 0xffff0000) >> 16);
+ return (x);
+}
+
+/*
+ * The size of a sector is context specific (i.e. determined by the
+ * media). But when users enter a value with a SI unit, they really
+ * mean the byte-size or byte-offset and not the size or offset in
+ * sectors. We should map the byte-oriented value into a sector-oriented
+ * value when we already know the sector size in bytes. At this time
+ * we can use g_parse_lba() function. It converts user specified
+ * value into sectors with following conditions:
+ * o Sectors size taken as argument from caller.
+ * o When no SI unit is specified the value is in sectors.
+ * o With an SI unit the value is in bytes.
+ * o The 'b' suffix forces byte interpretation and the 's'
+ * suffix forces sector interpretation.
+ *
+ * Thus:
+ * o 2 and 2s mean 2 sectors, and 2b means 2 bytes.
+ * o 4k and 4kb mean 4096 bytes, and 4ks means 4096 sectors.
+ *
+ */
+int
+g_parse_lba(const char *lbastr, unsigned int sectorsize, off_t *sectors)
+{
+ off_t number, mult, unit;
+ char *s;
+
+ assert(lbastr != NULL);
+ assert(sectorsize > 0);
+ assert(sectors != NULL);
+
+ number = (off_t)strtoimax(lbastr, &s, 0);
+ if (s == lbastr || number < 0)
+ return (EINVAL);
+
+ mult = 1;
+ unit = sectorsize;
+ if (*s == '\0')
+ goto done;
+ switch (*s) {
+ case 'e': case 'E':
+ mult *= 1024;
+ /* FALLTHROUGH */
+ case 'p': case 'P':
+ mult *= 1024;
+ /* FALLTHROUGH */
+ case 't': case 'T':
+ mult *= 1024;
+ /* FALLTHROUGH */
+ case 'g': case 'G':
+ mult *= 1024;
+ /* FALLTHROUGH */
+ case 'm': case 'M':
+ mult *= 1024;
+ /* FALLTHROUGH */
+ case 'k': case 'K':
+ mult *= 1024;
+ break;
+ default:
+ goto sfx;
+ }
+ unit = 1; /* bytes */
+ s++;
+ if (*s == '\0')
+ goto done;
+sfx:
+ switch (*s) {
+ case 's': case 'S':
+ unit = sectorsize; /* sector */
+ break;
+ case 'b': case 'B':
+ unit = 1; /* bytes */
+ break;
+ default:
+ return (EINVAL);
+ }
+ s++;
+ if (*s != '\0')
+ return (EINVAL);
+done:
+ if ((OFF_MAX / unit) < mult || (OFF_MAX / mult / unit) < number)
+ return (ERANGE);
+ number *= mult * unit;
+ if (number % sectorsize)
+ return (EINVAL);
+ number /= sectorsize;
+ *sectors = number;
+ return (0);
+}
+
+off_t
+g_get_mediasize(const char *name)
+{
+ off_t mediasize;
+ int fd;
+
+ fd = g_open(name, 0);
+ if (fd == -1)
+ return (0);
+ mediasize = g_mediasize(fd);
+ if (mediasize == -1)
+ mediasize = 0;
+ (void)g_close(fd);
+ return (mediasize);
+}
+
+unsigned int
+g_get_sectorsize(const char *name)
+{
+ ssize_t sectorsize;
+ int fd;
+
+ fd = g_open(name, 0);
+ if (fd == -1)
+ return (0);
+ sectorsize = g_sectorsize(fd);
+ if (sectorsize == -1)
+ sectorsize = 0;
+ (void)g_close(fd);
+ return ((unsigned int)sectorsize);
+}
+
+int
+g_metadata_read(const char *name, unsigned char *md, size_t size,
+ const char *magic)
+{
+ struct std_metadata stdmd;
+ unsigned char *sector;
+ ssize_t sectorsize;
+ off_t mediasize;
+ int error, fd;
+
+ sector = NULL;
+ error = 0;
+
+ fd = g_open(name, 0);
+ if (fd == -1)
+ return (errno);
+ mediasize = g_mediasize(fd);
+ if (mediasize == -1) {
+ error = errno;
+ goto out;
+ }
+ sectorsize = g_sectorsize(fd);
+ if (sectorsize == -1) {
+ error = errno;
+ goto out;
+ }
+ assert(sectorsize >= (ssize_t)size);
+ sector = malloc(sectorsize);
+ if (sector == NULL) {
+ error = ENOMEM;
+ goto out;
+ }
+ if (pread(fd, sector, sectorsize, mediasize - sectorsize) !=
+ sectorsize) {
+ error = errno;
+ goto out;
+ }
+ if (magic != NULL) {
+ std_metadata_decode(sector, &stdmd);
+ if (strcmp(stdmd.md_magic, magic) != 0) {
+ error = EINVAL;
+ goto out;
+ }
+ }
+ bcopy(sector, md, size);
+out:
+ if (sector != NULL)
+ free(sector);
+ g_close(fd);
+ return (error);
+}
+
+int
+g_metadata_store(const char *name, const unsigned char *md, size_t size)
+{
+ unsigned char *sector;
+ ssize_t sectorsize;
+ off_t mediasize;
+ int error, fd;
+
+ sector = NULL;
+ error = 0;
+
+ fd = g_open(name, 1);
+ if (fd == -1)
+ return (errno);
+ mediasize = g_mediasize(fd);
+ if (mediasize == -1) {
+ error = errno;
+ goto out;
+ }
+ sectorsize = g_sectorsize(fd);
+ if (sectorsize == -1) {
+ error = errno;
+ goto out;
+ }
+ assert(sectorsize >= (ssize_t)size);
+ sector = malloc(sectorsize);
+ if (sector == NULL) {
+ error = ENOMEM;
+ goto out;
+ }
+ bcopy(md, sector, size);
+ if (pwrite(fd, sector, sectorsize, mediasize - sectorsize) !=
+ sectorsize) {
+ error = errno;
+ goto out;
+ }
+ (void)g_flush(fd);
+out:
+ if (sector != NULL)
+ free(sector);
+ (void)g_close(fd);
+ return (error);
+}
+
+int
+g_metadata_clear(const char *name, const char *magic)
+{
+ struct std_metadata md;
+ unsigned char *sector;
+ ssize_t sectorsize;
+ off_t mediasize;
+ int error, fd;
+
+ sector = NULL;
+ error = 0;
+
+ fd = g_open(name, 1);
+ if (fd == -1)
+ return (errno);
+ mediasize = g_mediasize(fd);
+ if (mediasize == 0) {
+ error = errno;
+ goto out;
+ }
+ sectorsize = g_sectorsize(fd);
+ if (sectorsize == 0) {
+ error = errno;
+ goto out;
+ }
+ sector = malloc(sectorsize);
+ if (sector == NULL) {
+ error = ENOMEM;
+ goto out;
+ }
+ if (magic != NULL) {
+ if (pread(fd, sector, sectorsize, mediasize - sectorsize) !=
+ sectorsize) {
+ error = errno;
+ goto out;
+ }
+ std_metadata_decode(sector, &md);
+ if (strcmp(md.md_magic, magic) != 0) {
+ error = EINVAL;
+ goto out;
+ }
+ }
+ bzero(sector, sectorsize);
+ if (pwrite(fd, sector, sectorsize, mediasize - sectorsize) !=
+ sectorsize) {
+ error = errno;
+ goto out;
+ }
+ (void)g_flush(fd);
+out:
+ if (sector != NULL)
+ free(sector);
+ g_close(fd);
+ return (error);
+}
+
+/*
+ * Set an error message, if one does not already exist.
+ */
+void
+gctl_error(struct gctl_req *req, const char *error, ...)
+{
+ va_list ap;
+
+ if (req != NULL && req->error != NULL)
+ return;
+ va_start(ap, error);
+ if (req != NULL) {
+ vasprintf(&req->error, error, ap);
+ } else {
+ vfprintf(stderr, error, ap);
+ fprintf(stderr, "\n");
+ }
+ va_end(ap);
+}
+
+static void *
+gctl_get_param(struct gctl_req *req, size_t len, const char *pfmt, va_list ap)
+{
+ struct gctl_req_arg *argp;
+ char param[256];
+ unsigned int i;
+ void *p;
+
+ vsnprintf(param, sizeof(param), pfmt, ap);
+ for (i = 0; i < req->narg; i++) {
+ argp = &req->arg[i];
+ if (strcmp(param, argp->name))
+ continue;
+ if (!(argp->flag & GCTL_PARAM_RD))
+ continue;
+ p = argp->value;
+ if (len == 0) {
+ /* We are looking for a string. */
+ if (argp->len < 1) {
+ fprintf(stderr, "No length argument (%s).\n",
+ param);
+ abort();
+ }
+ if (((char *)p)[argp->len - 1] != '\0') {
+ fprintf(stderr, "Unterminated argument (%s).\n",
+ param);
+ abort();
+ }
+ } else if ((int)len != argp->len) {
+ fprintf(stderr, "Wrong length %s argument.\n", param);
+ abort();
+ }
+ return (p);
+ }
+ fprintf(stderr, "No such argument (%s).\n", param);
+ abort();
+}
+
+int
+gctl_get_int(struct gctl_req *req, const char *pfmt, ...)
+{
+ int *p;
+ va_list ap;
+
+ va_start(ap, pfmt);
+ p = gctl_get_param(req, sizeof(int), pfmt, ap);
+ va_end(ap);
+ return (*p);
+}
+
+intmax_t
+gctl_get_intmax(struct gctl_req *req, const char *pfmt, ...)
+{
+ intmax_t *p;
+ va_list ap;
+
+ va_start(ap, pfmt);
+ p = gctl_get_param(req, sizeof(intmax_t), pfmt, ap);
+ va_end(ap);
+ return (*p);
+}
+
+const char *
+gctl_get_ascii(struct gctl_req *req, const char *pfmt, ...)
+{
+ const char *p;
+ va_list ap;
+
+ va_start(ap, pfmt);
+ p = gctl_get_param(req, 0, pfmt, ap);
+ va_end(ap);
+ return (p);
+}
+
+int
+gctl_change_param(struct gctl_req *req, const char *name, int len,
+ const void *value)
+{
+ struct gctl_req_arg *ap;
+ unsigned int i;
+
+ if (req == NULL || req->error != NULL)
+ return (EDOOFUS);
+ for (i = 0; i < req->narg; i++) {
+ ap = &req->arg[i];
+ if (strcmp(ap->name, name) != 0)
+ continue;
+ ap->value = __DECONST(void *, value);
+ if (len >= 0) {
+ ap->flag &= ~GCTL_PARAM_ASCII;
+ ap->len = len;
+ } else if (len < 0) {
+ ap->flag |= GCTL_PARAM_ASCII;
+ ap->len = strlen(value) + 1;
+ }
+ return (0);
+ }
+ return (ENOENT);
+}
+
+int
+gctl_delete_param(struct gctl_req *req, const char *name)
+{
+ struct gctl_req_arg *ap;
+ unsigned int i;
+
+ if (req == NULL || req->error != NULL)
+ return (EDOOFUS);
+
+ i = 0;
+ while (i < req->narg) {
+ ap = &req->arg[i];
+ if (strcmp(ap->name, name) == 0)
+ break;
+ i++;
+ }
+ if (i == req->narg)
+ return (ENOENT);
+
+ free(ap->name);
+ req->narg--;
+ while (i < req->narg) {
+ req->arg[i] = req->arg[i + 1];
+ i++;
+ }
+ return (0);
+}
+
+int
+gctl_has_param(struct gctl_req *req, const char *name)
+{
+ struct gctl_req_arg *ap;
+ unsigned int i;
+
+ if (req == NULL || req->error != NULL)
+ return (0);
+
+ for (i = 0; i < req->narg; i++) {
+ ap = &req->arg[i];
+ if (strcmp(ap->name, name) == 0)
+ return (1);
+ }
+ return (0);
+}
diff --git a/sbin/geom/misc/subr.h b/sbin/geom/misc/subr.h
new file mode 100644
index 0000000..5ad1269
--- /dev/null
+++ b/sbin/geom/misc/subr.h
@@ -0,0 +1,54 @@
+/*-
+ * Copyright (c) 2004-2010 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _SUBR_H_
+#define _SUBR_H_
+#include <stdint.h>
+
+unsigned int g_lcm(unsigned int a, unsigned int b);
+uint32_t bitcount32(uint32_t x);
+int g_parse_lba(const char *lbastr, unsigned int sectorsize, off_t *sectors);
+
+off_t g_get_mediasize(const char *name);
+unsigned int g_get_sectorsize(const char *name);
+
+int g_metadata_read(const char *name, unsigned char *md, size_t size,
+ const char *magic);
+int g_metadata_store(const char *name, const unsigned char *md, size_t size);
+int g_metadata_clear(const char *name, const char *magic);
+
+void gctl_error(struct gctl_req *req, const char *error, ...) __printflike(2, 3);
+int gctl_get_int(struct gctl_req *req, const char *pfmt, ...) __printflike(2, 3);
+intmax_t gctl_get_intmax(struct gctl_req *req, const char *pfmt, ...) __printflike(2, 3);
+const char *gctl_get_ascii(struct gctl_req *req, const char *pfmt, ...) __printflike(2, 3);
+int gctl_change_param(struct gctl_req *req, const char *name, int len,
+ const void *value);
+int gctl_delete_param(struct gctl_req *req, const char *name);
+int gctl_has_param(struct gctl_req *req, const char *name);
+
+#endif /* !_SUBR_H_ */
diff --git a/sbin/ggate/Makefile b/sbin/ggate/Makefile
new file mode 100644
index 0000000..b463359
--- /dev/null
+++ b/sbin/ggate/Makefile
@@ -0,0 +1,14 @@
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+SUBDIR= ${_ggatec} \
+ ${_ggated} \
+ ggatel
+
+.if ${MK_LIBTHR} != "no"
+_ggatec= ggatec
+_ggated= ggated
+.endif
+
+.include <bsd.subdir.mk>
diff --git a/sbin/ggate/Makefile.inc b/sbin/ggate/Makefile.inc
new file mode 100644
index 0000000..265f86d
--- /dev/null
+++ b/sbin/ggate/Makefile.inc
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+.include "../Makefile.inc"
diff --git a/sbin/ggate/ggatec/Makefile b/sbin/ggate/ggatec/Makefile
new file mode 100644
index 0000000..e228fea
--- /dev/null
+++ b/sbin/ggate/ggatec/Makefile
@@ -0,0 +1,15 @@
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../shared
+
+PROG= ggatec
+MAN= ggatec.8
+SRCS= ggatec.c ggate.c
+
+CFLAGS+= -DMAX_SEND_SIZE=32768
+CFLAGS+= -DLIBGEOM
+CFLAGS+= -I${.CURDIR}/../shared
+
+LIBADD= geom util pthread
+
+.include <bsd.prog.mk>
diff --git a/sbin/ggate/ggatec/ggatec.8 b/sbin/ggate/ggatec/ggatec.8
new file mode 100644
index 0000000..8545baf
--- /dev/null
+++ b/sbin/ggate/ggatec/ggatec.8
@@ -0,0 +1,180 @@
+.\" Copyright (c) 2004 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd April 2, 2015
+.Dt GGATEC 8
+.Os
+.Sh NAME
+.Nm ggatec
+.Nd "GEOM Gate network client and control utility"
+.Sh SYNOPSIS
+.Nm
+.Cm create
+.Op Fl n
+.Op Fl v
+.Op Fl o Cm ro | wo | rw
+.Op Fl p Ar port
+.Op Fl q Ar queue_size
+.Op Fl R Ar rcvbuf
+.Op Fl S Ar sndbuf
+.Op Fl s Ar sectorsize
+.Op Fl t Ar timeout
+.Op Fl u Ar unit
+.Ar host
+.Ar path
+.Nm
+.Cm rescue
+.Op Fl n
+.Op Fl v
+.Op Fl o Cm ro | wo | rw
+.Op Fl p Ar port
+.Op Fl R Ar rcvbuf
+.Op Fl S Ar sndbuf
+.Fl u Ar unit
+.Ar host
+.Ar path
+.Nm
+.Cm destroy
+.Op Fl f
+.Fl u Ar unit
+.Nm
+.Cm list
+.Op Fl v
+.Op Fl u Ar unit
+.Sh DESCRIPTION
+The
+.Nm
+utility is a network client for the GEOM Gate class.
+It is responsible for the creation of
+.Nm ggate
+devices and forwarding I/O requests between the
+.Nm geom_gate.ko
+kernel module and the
+.Xr ggated 8
+network daemon.
+Available commands:
+.Bl -tag -width ".Cm destroy"
+.It Cm create
+Connect to a
+.Xr ggated 8
+daemon on the specified host and create a
+.Nm ggate
+provider for the specified remote file or device.
+.It Cm rescue
+Create a new connection after the
+.Nm
+process has died or been killed.
+The new connection to the
+.Xr ggated 8
+daemon handles pending and future requests.
+.It Cm destroy
+Destroy the given
+.Nm ggate
+provider.
+.It Cm list
+List
+.Nm ggate
+providers.
+.El
+.Pp
+Available options:
+.Bl -tag -width ".Fl s Cm ro | wo | rw"
+.It Fl f
+Forcibly destroy
+.Nm ggate
+provider (cancels all pending requests).
+.It Fl n
+Do not use
+.Dv TCP_NODELAY
+option on TCP sockets.
+.It Fl o Cm ro | wo | rw
+Specify permission to use when opening the file or device: read-only
+.Pq Cm ro ,
+write-only
+.Pq Cm wo ,
+or read-write
+.Pq Cm rw .
+Default is
+.Cm rw .
+.It Fl p Ar port
+Port to connect to on the remote host.
+Default is 3080.
+.It Fl q Ar queue_size
+Number of pending I/O requests that can be queued before they will
+start to be canceled.
+Default is 1024.
+.It Fl R Ar rcvbuf
+Size of receive buffer to use.
+Default is 131072 (128kB).
+.It Fl S Ar sndbuf
+Size of send buffer to use.
+Default is 131072 (128kB).
+.It Fl s Ar sectorsize
+Sector size for
+.Nm ggate
+provider.
+If not specified, it is taken from device, or set to 512 bytes for files.
+.It Fl t Ar timeout
+Number of seconds to wait before an I/O request will be canceled.
+Default is 0, which means no timeout.
+.It Fl u Ar unit
+Unit number to use.
+.It Fl v
+Do not fork, run in foreground and print debug information on standard
+output.
+.It Ar host
+Remote host to connect to.
+.It Ar path
+Path to a regular file or device.
+.El
+.Sh EXIT STATUS
+Exit status is 0 on success, or 1 if the command fails.
+To get details about the failure,
+.Nm
+should be called with the
+.Fl v
+option.
+.Sh EXAMPLES
+Use a CD-ROM device on a remote host.
+.Bd -literal -offset indent
+server# cat /etc/gg.exports
+client RO /dev/acd0
+server# ggated
+
+client# ggatec create -o ro server /dev/acd0
+ggate0
+client# mount_cd9660 /dev/ggate0 /cdrom
+.Ed
+.Sh SEE ALSO
+.Xr geom 4 ,
+.Xr ggated 8 ,
+.Xr ggatel 8 ,
+.Xr mount_cd9660 8
+.Sh AUTHORS
+The
+.Nm
+utility as well as this manual page was written by
+.An Pawel Jakub Dawidek Aq Mt pjd@FreeBSD.org .
diff --git a/sbin/ggate/ggatec/ggatec.c b/sbin/ggate/ggatec/ggatec.c
new file mode 100644
index 0000000..6f9263c
--- /dev/null
+++ b/sbin/ggate/ggatec/ggatec.c
@@ -0,0 +1,642 @@
+/*-
+ * Copyright (c) 2004 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ *
+ * $FreeBSD$
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <libgen.h>
+#include <pthread.h>
+#include <signal.h>
+#include <err.h>
+#include <errno.h>
+#include <assert.h>
+
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+#include <sys/syslog.h>
+#include <sys/time.h>
+#include <sys/bio.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+
+#include <geom/gate/g_gate.h>
+#include "ggate.h"
+
+
+static enum { UNSET, CREATE, DESTROY, LIST, RESCUE } action = UNSET;
+
+static const char *path = NULL;
+static const char *host = NULL;
+static int unit = G_GATE_UNIT_AUTO;
+static unsigned flags = 0;
+static int force = 0;
+static unsigned queue_size = G_GATE_QUEUE_SIZE;
+static unsigned port = G_GATE_PORT;
+static off_t mediasize;
+static unsigned sectorsize = 0;
+static unsigned timeout = G_GATE_TIMEOUT;
+static int sendfd, recvfd;
+static uint32_t token;
+static pthread_t sendtd, recvtd;
+static int reconnect;
+
+static void
+usage(void)
+{
+
+ fprintf(stderr, "usage: %s create [-nv] [-o <ro|wo|rw>] [-p port] "
+ "[-q queue_size] [-R rcvbuf] [-S sndbuf] [-s sectorsize] "
+ "[-t timeout] [-u unit] <host> <path>\n", getprogname());
+ fprintf(stderr, " %s rescue [-nv] [-o <ro|wo|rw>] [-p port] "
+ "[-R rcvbuf] [-S sndbuf] <-u unit> <host> <path>\n", getprogname());
+ fprintf(stderr, " %s destroy [-f] <-u unit>\n", getprogname());
+ fprintf(stderr, " %s list [-v] [-u unit]\n", getprogname());
+ exit(EXIT_FAILURE);
+}
+
+static void *
+send_thread(void *arg __unused)
+{
+ struct g_gate_ctl_io ggio;
+ struct g_gate_hdr hdr;
+ char buf[MAXPHYS];
+ ssize_t data;
+ int error;
+
+ g_gate_log(LOG_NOTICE, "%s: started!", __func__);
+
+ ggio.gctl_version = G_GATE_VERSION;
+ ggio.gctl_unit = unit;
+ ggio.gctl_data = buf;
+
+ for (;;) {
+ ggio.gctl_length = sizeof(buf);
+ ggio.gctl_error = 0;
+ g_gate_ioctl(G_GATE_CMD_START, &ggio);
+ error = ggio.gctl_error;
+ switch (error) {
+ case 0:
+ break;
+ case ECANCELED:
+ if (reconnect)
+ break;
+ /* Exit gracefully. */
+ g_gate_close_device();
+ exit(EXIT_SUCCESS);
+#if 0
+ case ENOMEM:
+ /* Buffer too small. */
+ ggio.gctl_data = realloc(ggio.gctl_data,
+ ggio.gctl_length);
+ if (ggio.gctl_data != NULL) {
+ bsize = ggio.gctl_length;
+ goto once_again;
+ }
+ /* FALLTHROUGH */
+#endif
+ case ENXIO:
+ default:
+ g_gate_xlog("ioctl(/dev/%s): %s.", G_GATE_CTL_NAME,
+ strerror(error));
+ }
+
+ if (reconnect)
+ break;
+
+ switch (ggio.gctl_cmd) {
+ case BIO_READ:
+ hdr.gh_cmd = GGATE_CMD_READ;
+ break;
+ case BIO_WRITE:
+ hdr.gh_cmd = GGATE_CMD_WRITE;
+ break;
+ }
+ hdr.gh_seq = ggio.gctl_seq;
+ hdr.gh_offset = ggio.gctl_offset;
+ hdr.gh_length = ggio.gctl_length;
+ hdr.gh_error = 0;
+ g_gate_swap2n_hdr(&hdr);
+
+ data = g_gate_send(sendfd, &hdr, sizeof(hdr), MSG_NOSIGNAL);
+ g_gate_log(LOG_DEBUG, "Sent hdr packet.");
+ g_gate_swap2h_hdr(&hdr);
+ if (reconnect)
+ break;
+ if (data != sizeof(hdr)) {
+ g_gate_log(LOG_ERR, "Lost connection 1.");
+ reconnect = 1;
+ pthread_kill(recvtd, SIGUSR1);
+ break;
+ }
+
+ if (hdr.gh_cmd == GGATE_CMD_WRITE) {
+ data = g_gate_send(sendfd, ggio.gctl_data,
+ ggio.gctl_length, MSG_NOSIGNAL);
+ if (reconnect)
+ break;
+ if (data != ggio.gctl_length) {
+ g_gate_log(LOG_ERR, "Lost connection 2 (%zd != %zd).", data, (ssize_t)ggio.gctl_length);
+ reconnect = 1;
+ pthread_kill(recvtd, SIGUSR1);
+ break;
+ }
+ g_gate_log(LOG_DEBUG, "Sent %zd bytes (offset=%llu, "
+ "size=%u).", data, hdr.gh_offset, hdr.gh_length);
+ }
+ }
+ g_gate_log(LOG_DEBUG, "%s: Died.", __func__);
+ return (NULL);
+}
+
+static void *
+recv_thread(void *arg __unused)
+{
+ struct g_gate_ctl_io ggio;
+ struct g_gate_hdr hdr;
+ char buf[MAXPHYS];
+ ssize_t data;
+
+ g_gate_log(LOG_NOTICE, "%s: started!", __func__);
+
+ ggio.gctl_version = G_GATE_VERSION;
+ ggio.gctl_unit = unit;
+ ggio.gctl_data = buf;
+
+ for (;;) {
+ data = g_gate_recv(recvfd, &hdr, sizeof(hdr), MSG_WAITALL);
+ if (reconnect)
+ break;
+ g_gate_swap2h_hdr(&hdr);
+ if (data != sizeof(hdr)) {
+ if (data == -1 && errno == EAGAIN)
+ continue;
+ g_gate_log(LOG_ERR, "Lost connection 3.");
+ reconnect = 1;
+ pthread_kill(sendtd, SIGUSR1);
+ break;
+ }
+ g_gate_log(LOG_DEBUG, "Received hdr packet.");
+
+ ggio.gctl_seq = hdr.gh_seq;
+ ggio.gctl_cmd = hdr.gh_cmd;
+ ggio.gctl_offset = hdr.gh_offset;
+ ggio.gctl_length = hdr.gh_length;
+ ggio.gctl_error = hdr.gh_error;
+
+ if (ggio.gctl_error == 0 && ggio.gctl_cmd == GGATE_CMD_READ) {
+ data = g_gate_recv(recvfd, ggio.gctl_data,
+ ggio.gctl_length, MSG_WAITALL);
+ if (reconnect)
+ break;
+ g_gate_log(LOG_DEBUG, "Received data packet.");
+ if (data != ggio.gctl_length) {
+ g_gate_log(LOG_ERR, "Lost connection 4.");
+ reconnect = 1;
+ pthread_kill(sendtd, SIGUSR1);
+ break;
+ }
+ g_gate_log(LOG_DEBUG, "Received %d bytes (offset=%ju, "
+ "size=%zu).", data, (uintmax_t)hdr.gh_offset,
+ (size_t)hdr.gh_length);
+ }
+
+ g_gate_ioctl(G_GATE_CMD_DONE, &ggio);
+ }
+ g_gate_log(LOG_DEBUG, "%s: Died.", __func__);
+ pthread_exit(NULL);
+}
+
+static int
+handshake(int dir)
+{
+ struct g_gate_version ver;
+ struct g_gate_cinit cinit;
+ struct g_gate_sinit sinit;
+ struct sockaddr_in serv;
+ int sfd;
+
+ /*
+ * Do the network stuff.
+ */
+ bzero(&serv, sizeof(serv));
+ serv.sin_family = AF_INET;
+ serv.sin_addr.s_addr = g_gate_str2ip(host);
+ if (serv.sin_addr.s_addr == INADDR_NONE) {
+ g_gate_log(LOG_DEBUG, "Invalid IP/host name: %s.", host);
+ return (-1);
+ }
+ serv.sin_port = htons(port);
+ sfd = socket(AF_INET, SOCK_STREAM, 0);
+ if (sfd == -1) {
+ g_gate_log(LOG_DEBUG, "Cannot open socket: %s.",
+ strerror(errno));
+ return (-1);
+ }
+
+ g_gate_socket_settings(sfd);
+
+ if (connect(sfd, (struct sockaddr *)&serv, sizeof(serv)) == -1) {
+ g_gate_log(LOG_DEBUG, "Cannot connect to server: %s.",
+ strerror(errno));
+ close(sfd);
+ return (-1);
+ }
+
+ g_gate_log(LOG_INFO, "Connected to the server: %s:%d.", host, port);
+
+ /*
+ * Create and send version packet.
+ */
+ g_gate_log(LOG_DEBUG, "Sending version packet.");
+ assert(strlen(GGATE_MAGIC) == sizeof(ver.gv_magic));
+ bcopy(GGATE_MAGIC, ver.gv_magic, sizeof(ver.gv_magic));
+ ver.gv_version = GGATE_VERSION;
+ ver.gv_error = 0;
+ g_gate_swap2n_version(&ver);
+ if (g_gate_send(sfd, &ver, sizeof(ver), MSG_NOSIGNAL) == -1) {
+ g_gate_log(LOG_DEBUG, "Error while sending version packet: %s.",
+ strerror(errno));
+ close(sfd);
+ return (-1);
+ }
+ bzero(&ver, sizeof(ver));
+ if (g_gate_recv(sfd, &ver, sizeof(ver), MSG_WAITALL) == -1) {
+ g_gate_log(LOG_DEBUG, "Error while receiving data: %s.",
+ strerror(errno));
+ close(sfd);
+ return (-1);
+ }
+ if (ver.gv_error != 0) {
+ g_gate_log(LOG_DEBUG, "Version verification problem: %s.",
+ strerror(errno));
+ close(sfd);
+ return (-1);
+ }
+
+ /*
+ * Create and send initial packet.
+ */
+ g_gate_log(LOG_DEBUG, "Sending initial packet.");
+ if (strlcpy(cinit.gc_path, path, sizeof(cinit.gc_path)) >=
+ sizeof(cinit.gc_path)) {
+ g_gate_log(LOG_DEBUG, "Path name too long.");
+ close(sfd);
+ return (-1);
+ }
+ cinit.gc_flags = flags | dir;
+ cinit.gc_token = token;
+ cinit.gc_nconn = 2;
+ g_gate_swap2n_cinit(&cinit);
+ if (g_gate_send(sfd, &cinit, sizeof(cinit), MSG_NOSIGNAL) == -1) {
+ g_gate_log(LOG_DEBUG, "Error while sending initial packet: %s.",
+ strerror(errno));
+ close(sfd);
+ return (-1);
+ }
+ g_gate_swap2h_cinit(&cinit);
+
+ /*
+ * Receiving initial packet from server.
+ */
+ g_gate_log(LOG_DEBUG, "Receiving initial packet.");
+ if (g_gate_recv(sfd, &sinit, sizeof(sinit), MSG_WAITALL) == -1) {
+ g_gate_log(LOG_DEBUG, "Error while receiving data: %s.",
+ strerror(errno));
+ close(sfd);
+ return (-1);
+ }
+ g_gate_swap2h_sinit(&sinit);
+ if (sinit.gs_error != 0) {
+ g_gate_log(LOG_DEBUG, "Error from server: %s.",
+ strerror(sinit.gs_error));
+ close(sfd);
+ return (-1);
+ }
+ g_gate_log(LOG_DEBUG, "Received initial packet.");
+
+ mediasize = sinit.gs_mediasize;
+ if (sectorsize == 0)
+ sectorsize = sinit.gs_sectorsize;
+
+ return (sfd);
+}
+
+static void
+mydaemon(void)
+{
+
+ if (g_gate_verbose > 0)
+ return;
+ if (daemon(0, 0) == 0)
+ return;
+ if (action == CREATE)
+ g_gate_destroy(unit, 1);
+ err(EXIT_FAILURE, "Cannot daemonize");
+}
+
+static int
+g_gatec_connect(void)
+{
+
+ token = arc4random();
+ /*
+ * Our receive descriptor is connected to the send descriptor on the
+ * server side.
+ */
+ recvfd = handshake(GGATE_FLAG_SEND);
+ if (recvfd == -1)
+ return (0);
+ /*
+ * Our send descriptor is connected to the receive descriptor on the
+ * server side.
+ */
+ sendfd = handshake(GGATE_FLAG_RECV);
+ if (sendfd == -1)
+ return (0);
+ return (1);
+}
+
+static void
+g_gatec_start(void)
+{
+ int error;
+
+ reconnect = 0;
+ error = pthread_create(&recvtd, NULL, recv_thread, NULL);
+ if (error != 0) {
+ g_gate_destroy(unit, 1);
+ g_gate_xlog("pthread_create(recv_thread): %s.",
+ strerror(error));
+ }
+ sendtd = pthread_self();
+ send_thread(NULL);
+ /* Disconnected. */
+ close(sendfd);
+ close(recvfd);
+}
+
+static void
+signop(int sig __unused)
+{
+
+ /* Do nothing. */
+}
+
+static void
+g_gatec_loop(void)
+{
+ struct g_gate_ctl_cancel ggioc;
+
+ signal(SIGUSR1, signop);
+ for (;;) {
+ g_gatec_start();
+ g_gate_log(LOG_NOTICE, "Disconnected [%s %s]. Connecting...",
+ host, path);
+ while (!g_gatec_connect()) {
+ sleep(2);
+ g_gate_log(LOG_NOTICE, "Connecting [%s %s]...", host,
+ path);
+ }
+ ggioc.gctl_version = G_GATE_VERSION;
+ ggioc.gctl_unit = unit;
+ ggioc.gctl_seq = 0;
+ g_gate_ioctl(G_GATE_CMD_CANCEL, &ggioc);
+ }
+}
+
+static void
+g_gatec_create(void)
+{
+ struct g_gate_ctl_create ggioc;
+
+ if (!g_gatec_connect())
+ g_gate_xlog("Cannot connect: %s.", strerror(errno));
+
+ /*
+ * Ok, got both sockets, time to create provider.
+ */
+ ggioc.gctl_version = G_GATE_VERSION;
+ ggioc.gctl_mediasize = mediasize;
+ ggioc.gctl_sectorsize = sectorsize;
+ ggioc.gctl_flags = flags;
+ ggioc.gctl_maxcount = queue_size;
+ ggioc.gctl_timeout = timeout;
+ ggioc.gctl_unit = unit;
+ snprintf(ggioc.gctl_info, sizeof(ggioc.gctl_info), "%s:%u %s", host,
+ port, path);
+ g_gate_ioctl(G_GATE_CMD_CREATE, &ggioc);
+ if (unit == -1) {
+ printf("%s%u\n", G_GATE_PROVIDER_NAME, ggioc.gctl_unit);
+ fflush(stdout);
+ }
+ unit = ggioc.gctl_unit;
+
+ mydaemon();
+ g_gatec_loop();
+}
+
+static void
+g_gatec_rescue(void)
+{
+ struct g_gate_ctl_cancel ggioc;
+
+ if (!g_gatec_connect())
+ g_gate_xlog("Cannot connect: %s.", strerror(errno));
+
+ ggioc.gctl_version = G_GATE_VERSION;
+ ggioc.gctl_unit = unit;
+ ggioc.gctl_seq = 0;
+ g_gate_ioctl(G_GATE_CMD_CANCEL, &ggioc);
+
+ mydaemon();
+ g_gatec_loop();
+}
+
+int
+main(int argc, char *argv[])
+{
+
+ if (argc < 2)
+ usage();
+ if (strcasecmp(argv[1], "create") == 0)
+ action = CREATE;
+ else if (strcasecmp(argv[1], "destroy") == 0)
+ action = DESTROY;
+ else if (strcasecmp(argv[1], "list") == 0)
+ action = LIST;
+ else if (strcasecmp(argv[1], "rescue") == 0)
+ action = RESCUE;
+ else
+ usage();
+ argc -= 1;
+ argv += 1;
+ for (;;) {
+ int ch;
+
+ ch = getopt(argc, argv, "fno:p:q:R:S:s:t:u:v");
+ if (ch == -1)
+ break;
+ switch (ch) {
+ case 'f':
+ if (action != DESTROY)
+ usage();
+ force = 1;
+ break;
+ case 'n':
+ if (action != CREATE && action != RESCUE)
+ usage();
+ nagle = 0;
+ break;
+ case 'o':
+ if (action != CREATE && action != RESCUE)
+ usage();
+ if (strcasecmp("ro", optarg) == 0)
+ flags = G_GATE_FLAG_READONLY;
+ else if (strcasecmp("wo", optarg) == 0)
+ flags = G_GATE_FLAG_WRITEONLY;
+ else if (strcasecmp("rw", optarg) == 0)
+ flags = 0;
+ else {
+ errx(EXIT_FAILURE,
+ "Invalid argument for '-o' option.");
+ }
+ break;
+ case 'p':
+ if (action != CREATE && action != RESCUE)
+ usage();
+ errno = 0;
+ port = strtoul(optarg, NULL, 10);
+ if (port == 0 && errno != 0)
+ errx(EXIT_FAILURE, "Invalid port.");
+ break;
+ case 'q':
+ if (action != CREATE)
+ usage();
+ errno = 0;
+ queue_size = strtoul(optarg, NULL, 10);
+ if (queue_size == 0 && errno != 0)
+ errx(EXIT_FAILURE, "Invalid queue_size.");
+ break;
+ case 'R':
+ if (action != CREATE && action != RESCUE)
+ usage();
+ errno = 0;
+ rcvbuf = strtoul(optarg, NULL, 10);
+ if (rcvbuf == 0 && errno != 0)
+ errx(EXIT_FAILURE, "Invalid rcvbuf.");
+ break;
+ case 'S':
+ if (action != CREATE && action != RESCUE)
+ usage();
+ errno = 0;
+ sndbuf = strtoul(optarg, NULL, 10);
+ if (sndbuf == 0 && errno != 0)
+ errx(EXIT_FAILURE, "Invalid sndbuf.");
+ break;
+ case 's':
+ if (action != CREATE)
+ usage();
+ errno = 0;
+ sectorsize = strtoul(optarg, NULL, 10);
+ if (sectorsize == 0 && errno != 0)
+ errx(EXIT_FAILURE, "Invalid sectorsize.");
+ break;
+ case 't':
+ if (action != CREATE)
+ usage();
+ errno = 0;
+ timeout = strtoul(optarg, NULL, 10);
+ if (timeout == 0 && errno != 0)
+ errx(EXIT_FAILURE, "Invalid timeout.");
+ break;
+ case 'u':
+ errno = 0;
+ unit = strtol(optarg, NULL, 10);
+ if (unit == 0 && errno != 0)
+ errx(EXIT_FAILURE, "Invalid unit number.");
+ break;
+ case 'v':
+ if (action == DESTROY)
+ usage();
+ g_gate_verbose++;
+ break;
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ switch (action) {
+ case CREATE:
+ if (argc != 2)
+ usage();
+ g_gate_load_module();
+ g_gate_open_device();
+ host = argv[0];
+ path = argv[1];
+ g_gatec_create();
+ break;
+ case DESTROY:
+ if (unit == -1) {
+ fprintf(stderr, "Required unit number.\n");
+ usage();
+ }
+ g_gate_verbose = 1;
+ g_gate_open_device();
+ g_gate_destroy(unit, force);
+ break;
+ case LIST:
+ g_gate_list(unit, g_gate_verbose);
+ break;
+ case RESCUE:
+ if (argc != 2)
+ usage();
+ if (unit == -1) {
+ fprintf(stderr, "Required unit number.\n");
+ usage();
+ }
+ g_gate_open_device();
+ host = argv[0];
+ path = argv[1];
+ g_gatec_rescue();
+ break;
+ case UNSET:
+ default:
+ usage();
+ }
+ g_gate_close_device();
+ exit(EXIT_SUCCESS);
+}
diff --git a/sbin/ggate/ggated/Makefile b/sbin/ggate/ggated/Makefile
new file mode 100644
index 0000000..af5c9bd
--- /dev/null
+++ b/sbin/ggate/ggated/Makefile
@@ -0,0 +1,13 @@
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../shared
+
+PROG= ggated
+MAN= ggated.8
+SRCS= ggated.c ggate.c
+
+LIBADD= pthread
+
+CFLAGS+= -I${.CURDIR}/../shared
+
+.include <bsd.prog.mk>
diff --git a/sbin/ggate/ggated/ggated.8 b/sbin/ggate/ggated/ggated.8
new file mode 100644
index 0000000..3560fe0
--- /dev/null
+++ b/sbin/ggate/ggated/ggated.8
@@ -0,0 +1,111 @@
+.\" Copyright (c) 2004 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd April 29, 2004
+.Dt GGATED 8
+.Os
+.Sh NAME
+.Nm ggated
+.Nd "GEOM Gate network daemon"
+.Sh SYNOPSIS
+.Nm
+.Op Fl h
+.Op Fl n
+.Op Fl v
+.Op Fl a Ar address
+.Op Fl p Ar port
+.Op Fl R Ar rcvbuf
+.Op Fl S Ar sndbuf
+.Op Ar "exports file"
+.Sh DESCRIPTION
+The
+.Nm
+utility is a network server for GEOM Gate class.
+It runs on a server machine to service GEOM Gate requests from workers
+placed on a client machine.
+Keep in mind, that connection between
+.Xr ggatec 8
+and
+.Nm
+is not encrypted.
+.Pp
+Available options:
+.Bl -tag -width ".Ar exports\ file"
+.It Fl a Ar address
+Specifies an IP address to bind to.
+.It Fl h
+Print available options.
+.It Fl n
+Do not use
+.Dv TCP_NODELAY
+option on TCP sockets.
+.It Fl p Ar port
+Port on which
+.Nm
+listens for connection.
+Default is 3080.
+.It Fl R Ar rcvbuf
+Size of receive buffer to use.
+Default is 131072 (128kB).
+.It Fl S Ar sndbuf
+Size of send buffer to use.
+Default is 131072 (128kB).
+.It Fl v
+Do not fork, run in foreground and print debug informations on standard
+output.
+.It Ar "exports file"
+An alternate location for the exports file.
+.El
+.Pp
+The format of an exports file is as follows:
+.Bd -literal -offset indent
+1.2.3.4 RO /dev/acd0
+1.2.3.0/24 RW /tmp/test.img
+hostname WO /tmp/image
+.Ed
+.Sh EXIT STATUS
+Exit status is 0 on success, or 1 if the command fails.
+To get details about the failure,
+.Nm
+should be called with the
+.Fl v
+option.
+.Sh EXAMPLES
+Export CD-ROM device and a file:
+.Bd -literal -offset indent
+# echo "1.2.3.0/24 RO /dev/acd0" > /etc/gg.exports
+# echo "client RW /image" >> /etc/gg.exports
+# ggated
+.Ed
+.Sh SEE ALSO
+.Xr geom 4 ,
+.Xr ggatec 8 ,
+.Xr ggatel 8
+.Sh AUTHORS
+The
+.Nm
+utility as well as this manual page was written by
+.An Pawel Jakub Dawidek Aq Mt pjd@FreeBSD.org .
diff --git a/sbin/ggate/ggated/ggated.c b/sbin/ggate/ggated/ggated.c
new file mode 100644
index 0000000..01aa00a
--- /dev/null
+++ b/sbin/ggate/ggated/ggated.c
@@ -0,0 +1,1050 @@
+/*-
+ * Copyright (c) 2004 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ *
+ * $FreeBSD$
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/endian.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/disk.h>
+#include <sys/bio.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <signal.h>
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <string.h>
+#include <libgen.h>
+#include <syslog.h>
+#include <stdarg.h>
+
+#include "ggate.h"
+
+
+#define GGATED_EXPORT_FILE "/etc/gg.exports"
+
+struct ggd_connection {
+ off_t c_mediasize;
+ unsigned c_sectorsize;
+ unsigned c_flags; /* flags (RO/RW) */
+ int c_diskfd;
+ int c_sendfd;
+ int c_recvfd;
+ time_t c_birthtime;
+ char *c_path;
+ uint64_t c_token;
+ in_addr_t c_srcip;
+ LIST_ENTRY(ggd_connection) c_next;
+};
+
+struct ggd_request {
+ struct g_gate_hdr r_hdr;
+ char *r_data;
+ TAILQ_ENTRY(ggd_request) r_next;
+};
+#define r_cmd r_hdr.gh_cmd
+#define r_offset r_hdr.gh_offset
+#define r_length r_hdr.gh_length
+#define r_error r_hdr.gh_error
+
+struct ggd_export {
+ char *e_path; /* path to device/file */
+ in_addr_t e_ip; /* remote IP address */
+ in_addr_t e_mask; /* IP mask */
+ unsigned e_flags; /* flags (RO/RW) */
+ SLIST_ENTRY(ggd_export) e_next;
+};
+
+static const char *exports_file = GGATED_EXPORT_FILE;
+static int got_sighup = 0;
+static in_addr_t bindaddr;
+
+static TAILQ_HEAD(, ggd_request) inqueue = TAILQ_HEAD_INITIALIZER(inqueue);
+static TAILQ_HEAD(, ggd_request) outqueue = TAILQ_HEAD_INITIALIZER(outqueue);
+static pthread_mutex_t inqueue_mtx, outqueue_mtx;
+static pthread_cond_t inqueue_cond, outqueue_cond;
+
+static SLIST_HEAD(, ggd_export) exports = SLIST_HEAD_INITIALIZER(exports);
+static LIST_HEAD(, ggd_connection) connections = LIST_HEAD_INITIALIZER(connections);
+
+static void *recv_thread(void *arg);
+static void *disk_thread(void *arg);
+static void *send_thread(void *arg);
+
+static void
+usage(void)
+{
+
+ fprintf(stderr, "usage: %s [-nv] [-a address] [-p port] [-R rcvbuf] "
+ "[-S sndbuf] [exports file]\n", getprogname());
+ exit(EXIT_FAILURE);
+}
+
+static char *
+ip2str(in_addr_t ip)
+{
+ static char sip[16];
+
+ snprintf(sip, sizeof(sip), "%u.%u.%u.%u",
+ ((ip >> 24) & 0xff),
+ ((ip >> 16) & 0xff),
+ ((ip >> 8) & 0xff),
+ (ip & 0xff));
+ return (sip);
+}
+
+static in_addr_t
+countmask(unsigned m)
+{
+ in_addr_t mask;
+
+ if (m == 0) {
+ mask = 0x0;
+ } else {
+ mask = 1 << (32 - m);
+ mask--;
+ mask = ~mask;
+ }
+ return (mask);
+}
+
+static void
+line_parse(char *line, unsigned lineno)
+{
+ struct ggd_export *ex;
+ char *word, *path, *sflags;
+ unsigned flags, i, vmask;
+ in_addr_t ip, mask;
+
+ ip = mask = flags = vmask = 0;
+ path = NULL;
+ sflags = NULL;
+
+ for (i = 0, word = strtok(line, " \t"); word != NULL;
+ i++, word = strtok(NULL, " \t")) {
+ switch (i) {
+ case 0: /* IP address or host name */
+ ip = g_gate_str2ip(strsep(&word, "/"));
+ if (ip == INADDR_NONE) {
+ g_gate_xlog("Invalid IP/host name at line %u.",
+ lineno);
+ }
+ ip = ntohl(ip);
+ if (word == NULL)
+ vmask = 32;
+ else {
+ errno = 0;
+ vmask = strtoul(word, NULL, 10);
+ if (vmask == 0 && errno != 0) {
+ g_gate_xlog("Invalid IP mask value at "
+ "line %u.", lineno);
+ }
+ if ((unsigned)vmask > 32) {
+ g_gate_xlog("Invalid IP mask value at line %u.",
+ lineno);
+ }
+ }
+ mask = countmask(vmask);
+ break;
+ case 1: /* flags */
+ if (strcasecmp("rd", word) == 0 ||
+ strcasecmp("ro", word) == 0) {
+ flags = O_RDONLY;
+ } else if (strcasecmp("wo", word) == 0) {
+ flags = O_WRONLY;
+ } else if (strcasecmp("rw", word) == 0) {
+ flags = O_RDWR;
+ } else {
+ g_gate_xlog("Invalid value in flags field at "
+ "line %u.", lineno);
+ }
+ sflags = word;
+ break;
+ case 2: /* path */
+ if (strlen(word) >= MAXPATHLEN) {
+ g_gate_xlog("Path too long at line %u. ",
+ lineno);
+ }
+ path = word;
+ break;
+ default:
+ g_gate_xlog("Too many arguments at line %u. ", lineno);
+ }
+ }
+ if (i != 3)
+ g_gate_xlog("Too few arguments at line %u.", lineno);
+
+ ex = malloc(sizeof(*ex));
+ if (ex == NULL)
+ g_gate_xlog("Not enough memory.");
+ ex->e_path = strdup(path);
+ if (ex->e_path == NULL)
+ g_gate_xlog("Not enough memory.");
+
+ /* Made 'and' here. */
+ ex->e_ip = (ip & mask);
+ ex->e_mask = mask;
+ ex->e_flags = flags;
+
+ SLIST_INSERT_HEAD(&exports, ex, e_next);
+
+ g_gate_log(LOG_DEBUG, "Added %s/%u %s %s to exports list.",
+ ip2str(ex->e_ip), vmask, path, sflags);
+}
+
+static void
+exports_clear(void)
+{
+ struct ggd_export *ex;
+
+ while (!SLIST_EMPTY(&exports)) {
+ ex = SLIST_FIRST(&exports);
+ SLIST_REMOVE_HEAD(&exports, e_next);
+ free(ex);
+ }
+}
+
+#define EXPORTS_LINE_SIZE 2048
+static void
+exports_get(void)
+{
+ char buf[EXPORTS_LINE_SIZE], *line;
+ unsigned lineno = 0, objs = 0, len;
+ FILE *fd;
+
+ exports_clear();
+
+ fd = fopen(exports_file, "r");
+ if (fd == NULL) {
+ g_gate_xlog("Cannot open exports file (%s): %s.", exports_file,
+ strerror(errno));
+ }
+
+ g_gate_log(LOG_INFO, "Reading exports file (%s).", exports_file);
+
+ for (;;) {
+ if (fgets(buf, sizeof(buf), fd) == NULL) {
+ if (feof(fd))
+ break;
+
+ g_gate_xlog("Error while reading exports file: %s.",
+ strerror(errno));
+ }
+
+ /* Increase line count. */
+ lineno++;
+
+ /* Skip spaces and tabs. */
+ for (line = buf; *line == ' ' || *line == '\t'; ++line)
+ ;
+
+ /* Empty line, comment or empty line at the end of file. */
+ if (*line == '\n' || *line == '#' || *line == '\0')
+ continue;
+
+ len = strlen(line);
+ if (line[len - 1] == '\n') {
+ /* Remove new line char. */
+ line[len - 1] = '\0';
+ } else {
+ if (!feof(fd))
+ g_gate_xlog("Line %u too long.", lineno);
+ }
+
+ line_parse(line, lineno);
+ objs++;
+ }
+
+ fclose(fd);
+
+ if (objs == 0)
+ g_gate_xlog("There are no objects to export.");
+
+ g_gate_log(LOG_INFO, "Exporting %u object(s).", objs);
+}
+
+static int
+exports_check(struct ggd_export *ex, struct g_gate_cinit *cinit,
+ struct ggd_connection *conn)
+{
+ char ipmask[32]; /* 32 == strlen("xxx.xxx.xxx.xxx/xxx.xxx.xxx.xxx")+1 */
+ int error = 0, flags;
+
+ strlcpy(ipmask, ip2str(ex->e_ip), sizeof(ipmask));
+ strlcat(ipmask, "/", sizeof(ipmask));
+ strlcat(ipmask, ip2str(ex->e_mask), sizeof(ipmask));
+ if ((cinit->gc_flags & GGATE_FLAG_RDONLY) != 0) {
+ if (ex->e_flags == O_WRONLY) {
+ g_gate_log(LOG_WARNING, "Read-only access requested, "
+ "but %s (%s) is exported write-only.", ex->e_path,
+ ipmask);
+ return (EPERM);
+ } else {
+ conn->c_flags |= GGATE_FLAG_RDONLY;
+ }
+ } else if ((cinit->gc_flags & GGATE_FLAG_WRONLY) != 0) {
+ if (ex->e_flags == O_RDONLY) {
+ g_gate_log(LOG_WARNING, "Write-only access requested, "
+ "but %s (%s) is exported read-only.", ex->e_path,
+ ipmask);
+ return (EPERM);
+ } else {
+ conn->c_flags |= GGATE_FLAG_WRONLY;
+ }
+ } else {
+ if (ex->e_flags == O_RDONLY) {
+ g_gate_log(LOG_WARNING, "Read-write access requested, "
+ "but %s (%s) is exported read-only.", ex->e_path,
+ ipmask);
+ return (EPERM);
+ } else if (ex->e_flags == O_WRONLY) {
+ g_gate_log(LOG_WARNING, "Read-write access requested, "
+ "but %s (%s) is exported write-only.", ex->e_path,
+ ipmask);
+ return (EPERM);
+ }
+ }
+ if ((conn->c_flags & GGATE_FLAG_RDONLY) != 0)
+ flags = O_RDONLY;
+ else if ((conn->c_flags & GGATE_FLAG_WRONLY) != 0)
+ flags = O_WRONLY;
+ else
+ flags = O_RDWR;
+ conn->c_diskfd = open(ex->e_path, flags);
+ if (conn->c_diskfd == -1) {
+ error = errno;
+ g_gate_log(LOG_ERR, "Cannot open %s: %s.", ex->e_path,
+ strerror(error));
+ return (error);
+ }
+ return (0);
+}
+
+static struct ggd_export *
+exports_find(struct sockaddr *s, struct g_gate_cinit *cinit,
+ struct ggd_connection *conn)
+{
+ struct ggd_export *ex;
+ in_addr_t ip;
+ int error;
+
+ ip = htonl(((struct sockaddr_in *)(void *)s)->sin_addr.s_addr);
+ SLIST_FOREACH(ex, &exports, e_next) {
+ if ((ip & ex->e_mask) != ex->e_ip) {
+ g_gate_log(LOG_DEBUG, "exports[%s]: IP mismatch.",
+ ex->e_path);
+ continue;
+ }
+ if (strcmp(cinit->gc_path, ex->e_path) != 0) {
+ g_gate_log(LOG_DEBUG, "exports[%s]: Path mismatch.",
+ ex->e_path);
+ continue;
+ }
+ error = exports_check(ex, cinit, conn);
+ if (error == 0)
+ return (ex);
+ else {
+ errno = error;
+ return (NULL);
+ }
+ }
+ g_gate_log(LOG_WARNING, "Unauthorized connection from: %s.",
+ ip2str(ip));
+ errno = EPERM;
+ return (NULL);
+}
+
+/*
+ * Remove timed out connections.
+ */
+static void
+connection_cleanups(void)
+{
+ struct ggd_connection *conn, *tconn;
+ time_t now;
+
+ time(&now);
+ LIST_FOREACH_SAFE(conn, &connections, c_next, tconn) {
+ if (now - conn->c_birthtime > 10) {
+ LIST_REMOVE(conn, c_next);
+ g_gate_log(LOG_NOTICE,
+ "Connection from %s [%s] removed.",
+ ip2str(conn->c_srcip), conn->c_path);
+ close(conn->c_diskfd);
+ close(conn->c_sendfd);
+ close(conn->c_recvfd);
+ free(conn->c_path);
+ free(conn);
+ }
+ }
+}
+
+static struct ggd_connection *
+connection_find(struct g_gate_cinit *cinit)
+{
+ struct ggd_connection *conn;
+
+ LIST_FOREACH(conn, &connections, c_next) {
+ if (conn->c_token == cinit->gc_token)
+ break;
+ }
+ return (conn);
+}
+
+static struct ggd_connection *
+connection_new(struct g_gate_cinit *cinit, struct sockaddr *s, int sfd)
+{
+ struct ggd_connection *conn;
+ in_addr_t ip;
+
+ /*
+ * First, look for old connections.
+ * We probably should do it every X seconds, but what for?
+ * It is only dangerous if an attacker wants to overload connections
+ * queue, so here is a good place to do the cleanups.
+ */
+ connection_cleanups();
+
+ conn = malloc(sizeof(*conn));
+ if (conn == NULL)
+ return (NULL);
+ conn->c_path = strdup(cinit->gc_path);
+ if (conn->c_path == NULL) {
+ free(conn);
+ return (NULL);
+ }
+ conn->c_token = cinit->gc_token;
+ ip = htonl(((struct sockaddr_in *)(void *)s)->sin_addr.s_addr);
+ conn->c_srcip = ip;
+ conn->c_sendfd = conn->c_recvfd = -1;
+ if ((cinit->gc_flags & GGATE_FLAG_SEND) != 0)
+ conn->c_sendfd = sfd;
+ else
+ conn->c_recvfd = sfd;
+ conn->c_mediasize = 0;
+ conn->c_sectorsize = 0;
+ time(&conn->c_birthtime);
+ conn->c_flags = cinit->gc_flags;
+ LIST_INSERT_HEAD(&connections, conn, c_next);
+ g_gate_log(LOG_DEBUG, "Connection created [%s, %s].", ip2str(ip),
+ conn->c_path);
+ return (conn);
+}
+
+static int
+connection_add(struct ggd_connection *conn, struct g_gate_cinit *cinit,
+ struct sockaddr *s, int sfd)
+{
+ in_addr_t ip;
+
+ ip = htonl(((struct sockaddr_in *)(void *)s)->sin_addr.s_addr);
+ if ((cinit->gc_flags & GGATE_FLAG_SEND) != 0) {
+ if (conn->c_sendfd != -1) {
+ g_gate_log(LOG_WARNING,
+ "Send socket already exists [%s, %s].", ip2str(ip),
+ conn->c_path);
+ return (EEXIST);
+ }
+ conn->c_sendfd = sfd;
+ } else {
+ if (conn->c_recvfd != -1) {
+ g_gate_log(LOG_WARNING,
+ "Receive socket already exists [%s, %s].",
+ ip2str(ip), conn->c_path);
+ return (EEXIST);
+ }
+ conn->c_recvfd = sfd;
+ }
+ g_gate_log(LOG_DEBUG, "Connection added [%s, %s].", ip2str(ip),
+ conn->c_path);
+ return (0);
+}
+
+/*
+ * Remove one socket from the given connection or the whole
+ * connection if sfd == -1.
+ */
+static void
+connection_remove(struct ggd_connection *conn)
+{
+
+ LIST_REMOVE(conn, c_next);
+ g_gate_log(LOG_DEBUG, "Connection removed [%s %s].",
+ ip2str(conn->c_srcip), conn->c_path);
+ if (conn->c_sendfd != -1)
+ close(conn->c_sendfd);
+ if (conn->c_recvfd != -1)
+ close(conn->c_recvfd);
+ free(conn->c_path);
+ free(conn);
+}
+
+static int
+connection_ready(struct ggd_connection *conn)
+{
+
+ return (conn->c_sendfd != -1 && conn->c_recvfd != -1);
+}
+
+static void
+connection_launch(struct ggd_connection *conn)
+{
+ pthread_t td;
+ int error, pid;
+
+ pid = fork();
+ if (pid > 0)
+ return;
+ else if (pid == -1) {
+ g_gate_log(LOG_ERR, "Cannot fork: %s.", strerror(errno));
+ return;
+ }
+ g_gate_log(LOG_DEBUG, "Process created [%s].", conn->c_path);
+
+ /*
+ * Create condition variables and mutexes for in-queue and out-queue
+ * synchronization.
+ */
+ error = pthread_mutex_init(&inqueue_mtx, NULL);
+ if (error != 0) {
+ g_gate_xlog("pthread_mutex_init(inqueue_mtx): %s.",
+ strerror(error));
+ }
+ error = pthread_cond_init(&inqueue_cond, NULL);
+ if (error != 0) {
+ g_gate_xlog("pthread_cond_init(inqueue_cond): %s.",
+ strerror(error));
+ }
+ error = pthread_mutex_init(&outqueue_mtx, NULL);
+ if (error != 0) {
+ g_gate_xlog("pthread_mutex_init(outqueue_mtx): %s.",
+ strerror(error));
+ }
+ error = pthread_cond_init(&outqueue_cond, NULL);
+ if (error != 0) {
+ g_gate_xlog("pthread_cond_init(outqueue_cond): %s.",
+ strerror(error));
+ }
+
+ /*
+ * Create threads:
+ * recvtd - thread for receiving I/O request
+ * diskio - thread for doing I/O request
+ * sendtd - thread for sending I/O requests back
+ */
+ error = pthread_create(&td, NULL, send_thread, conn);
+ if (error != 0) {
+ g_gate_xlog("pthread_create(send_thread): %s.",
+ strerror(error));
+ }
+ error = pthread_create(&td, NULL, recv_thread, conn);
+ if (error != 0) {
+ g_gate_xlog("pthread_create(recv_thread): %s.",
+ strerror(error));
+ }
+ disk_thread(conn);
+}
+
+static void
+sendfail(int sfd, int error, const char *fmt, ...)
+{
+ struct g_gate_sinit sinit;
+ va_list ap;
+ ssize_t data;
+
+ sinit.gs_error = error;
+ g_gate_swap2n_sinit(&sinit);
+ data = g_gate_send(sfd, &sinit, sizeof(sinit), 0);
+ g_gate_swap2h_sinit(&sinit);
+ if (data != sizeof(sinit)) {
+ g_gate_log(LOG_WARNING, "Cannot send initial packet: %s.",
+ strerror(errno));
+ return;
+ }
+ if (fmt != NULL) {
+ va_start(ap, fmt);
+ g_gate_vlog(LOG_WARNING, fmt, ap);
+ va_end(ap);
+ }
+}
+
+static void *
+malloc_waitok(size_t size)
+{
+ void *p;
+
+ while ((p = malloc(size)) == NULL) {
+ g_gate_log(LOG_DEBUG, "Cannot allocate %zu bytes.", size);
+ sleep(1);
+ }
+ return (p);
+}
+
+static void *
+recv_thread(void *arg)
+{
+ struct ggd_connection *conn;
+ struct ggd_request *req;
+ ssize_t data;
+ int error, fd;
+
+ conn = arg;
+ g_gate_log(LOG_NOTICE, "%s: started [%s]!", __func__, conn->c_path);
+ fd = conn->c_recvfd;
+ for (;;) {
+ /*
+ * Get header packet.
+ */
+ req = malloc_waitok(sizeof(*req));
+ data = g_gate_recv(fd, &req->r_hdr, sizeof(req->r_hdr),
+ MSG_WAITALL);
+ if (data == 0) {
+ g_gate_log(LOG_DEBUG, "Process %u exiting.", getpid());
+ exit(EXIT_SUCCESS);
+ } else if (data == -1) {
+ g_gate_xlog("Error while receiving hdr packet: %s.",
+ strerror(errno));
+ } else if (data != sizeof(req->r_hdr)) {
+ g_gate_xlog("Malformed hdr packet received.");
+ }
+ g_gate_log(LOG_DEBUG, "Received hdr packet.");
+ g_gate_swap2h_hdr(&req->r_hdr);
+
+ g_gate_log(LOG_DEBUG, "%s: offset=%jd length=%u", __func__,
+ (intmax_t)req->r_offset, (unsigned)req->r_length);
+
+ /*
+ * Allocate memory for data.
+ */
+ req->r_data = malloc_waitok(req->r_length);
+
+ /*
+ * Receive data to write for WRITE request.
+ */
+ if (req->r_cmd == GGATE_CMD_WRITE) {
+ g_gate_log(LOG_DEBUG, "Waiting for %u bytes of data...",
+ req->r_length);
+ data = g_gate_recv(fd, req->r_data, req->r_length,
+ MSG_WAITALL);
+ if (data == -1) {
+ g_gate_xlog("Error while receiving data: %s.",
+ strerror(errno));
+ }
+ }
+
+ /*
+ * Put the request onto the incoming queue.
+ */
+ error = pthread_mutex_lock(&inqueue_mtx);
+ assert(error == 0);
+ TAILQ_INSERT_TAIL(&inqueue, req, r_next);
+ error = pthread_cond_signal(&inqueue_cond);
+ assert(error == 0);
+ error = pthread_mutex_unlock(&inqueue_mtx);
+ assert(error == 0);
+ }
+}
+
+static void *
+disk_thread(void *arg)
+{
+ struct ggd_connection *conn;
+ struct ggd_request *req;
+ ssize_t data;
+ int error, fd;
+
+ conn = arg;
+ g_gate_log(LOG_NOTICE, "%s: started [%s]!", __func__, conn->c_path);
+ fd = conn->c_diskfd;
+ for (;;) {
+ /*
+ * Get a request from the incoming queue.
+ */
+ error = pthread_mutex_lock(&inqueue_mtx);
+ assert(error == 0);
+ while ((req = TAILQ_FIRST(&inqueue)) == NULL) {
+ error = pthread_cond_wait(&inqueue_cond, &inqueue_mtx);
+ assert(error == 0);
+ }
+ TAILQ_REMOVE(&inqueue, req, r_next);
+ error = pthread_mutex_unlock(&inqueue_mtx);
+ assert(error == 0);
+
+ /*
+ * Check the request.
+ */
+ assert(req->r_cmd == GGATE_CMD_READ || req->r_cmd == GGATE_CMD_WRITE);
+ assert(req->r_offset + req->r_length <= (uintmax_t)conn->c_mediasize);
+ assert((req->r_offset % conn->c_sectorsize) == 0);
+ assert((req->r_length % conn->c_sectorsize) == 0);
+
+ g_gate_log(LOG_DEBUG, "%s: offset=%jd length=%u", __func__,
+ (intmax_t)req->r_offset, (unsigned)req->r_length);
+
+ /*
+ * Do the request.
+ */
+ data = 0;
+ switch (req->r_cmd) {
+ case GGATE_CMD_READ:
+ data = pread(fd, req->r_data, req->r_length,
+ req->r_offset);
+ break;
+ case GGATE_CMD_WRITE:
+ data = pwrite(fd, req->r_data, req->r_length,
+ req->r_offset);
+ /* Free data memory here - better sooner. */
+ free(req->r_data);
+ req->r_data = NULL;
+ break;
+ }
+ if (data != (ssize_t)req->r_length) {
+ /* Report short reads/writes as I/O errors. */
+ if (errno == 0)
+ errno = EIO;
+ g_gate_log(LOG_ERR, "Disk error: %s", strerror(errno));
+ req->r_error = errno;
+ if (req->r_data != NULL) {
+ free(req->r_data);
+ req->r_data = NULL;
+ }
+ }
+
+ /*
+ * Put the request onto the outgoing queue.
+ */
+ error = pthread_mutex_lock(&outqueue_mtx);
+ assert(error == 0);
+ TAILQ_INSERT_TAIL(&outqueue, req, r_next);
+ error = pthread_cond_signal(&outqueue_cond);
+ assert(error == 0);
+ error = pthread_mutex_unlock(&outqueue_mtx);
+ assert(error == 0);
+ }
+
+ /* NOTREACHED */
+ return (NULL);
+}
+
+static void *
+send_thread(void *arg)
+{
+ struct ggd_connection *conn;
+ struct ggd_request *req;
+ ssize_t data;
+ int error, fd;
+
+ conn = arg;
+ g_gate_log(LOG_NOTICE, "%s: started [%s]!", __func__, conn->c_path);
+ fd = conn->c_sendfd;
+ for (;;) {
+ /*
+ * Get a request from the outgoing queue.
+ */
+ error = pthread_mutex_lock(&outqueue_mtx);
+ assert(error == 0);
+ while ((req = TAILQ_FIRST(&outqueue)) == NULL) {
+ error = pthread_cond_wait(&outqueue_cond,
+ &outqueue_mtx);
+ assert(error == 0);
+ }
+ TAILQ_REMOVE(&outqueue, req, r_next);
+ error = pthread_mutex_unlock(&outqueue_mtx);
+ assert(error == 0);
+
+ g_gate_log(LOG_DEBUG, "%s: offset=%jd length=%u", __func__,
+ (intmax_t)req->r_offset, (unsigned)req->r_length);
+
+ /*
+ * Send the request.
+ */
+ g_gate_swap2n_hdr(&req->r_hdr);
+ if (g_gate_send(fd, &req->r_hdr, sizeof(req->r_hdr), 0) == -1) {
+ g_gate_xlog("Error while sending hdr packet: %s.",
+ strerror(errno));
+ }
+ g_gate_log(LOG_DEBUG, "Sent hdr packet.");
+ g_gate_swap2h_hdr(&req->r_hdr);
+ if (req->r_data != NULL) {
+ data = g_gate_send(fd, req->r_data, req->r_length, 0);
+ if (data != (ssize_t)req->r_length) {
+ g_gate_xlog("Error while sending data: %s.",
+ strerror(errno));
+ }
+ g_gate_log(LOG_DEBUG,
+ "Sent %zd bytes (offset=%ju, size=%zu).", data,
+ (uintmax_t)req->r_offset, (size_t)req->r_length);
+ free(req->r_data);
+ }
+ free(req);
+ }
+
+ /* NOTREACHED */
+ return (NULL);
+}
+
+static void
+log_connection(struct sockaddr *from)
+{
+ in_addr_t ip;
+
+ ip = htonl(((struct sockaddr_in *)(void *)from)->sin_addr.s_addr);
+ g_gate_log(LOG_INFO, "Connection from: %s.", ip2str(ip));
+}
+
+static int
+handshake(struct sockaddr *from, int sfd)
+{
+ struct g_gate_version ver;
+ struct g_gate_cinit cinit;
+ struct g_gate_sinit sinit;
+ struct ggd_connection *conn;
+ struct ggd_export *ex;
+ ssize_t data;
+
+ log_connection(from);
+ /*
+ * Phase 1: Version verification.
+ */
+ g_gate_log(LOG_DEBUG, "Receiving version packet.");
+ data = g_gate_recv(sfd, &ver, sizeof(ver), MSG_WAITALL);
+ g_gate_swap2h_version(&ver);
+ if (data != sizeof(ver)) {
+ g_gate_log(LOG_WARNING, "Malformed version packet.");
+ return (0);
+ }
+ g_gate_log(LOG_DEBUG, "Version packet received.");
+ if (memcmp(ver.gv_magic, GGATE_MAGIC, strlen(GGATE_MAGIC)) != 0) {
+ g_gate_log(LOG_WARNING, "Invalid magic field.");
+ return (0);
+ }
+ if (ver.gv_version != GGATE_VERSION) {
+ g_gate_log(LOG_WARNING, "Version %u is not supported.",
+ ver.gv_version);
+ return (0);
+ }
+ ver.gv_error = 0;
+ g_gate_swap2n_version(&ver);
+ data = g_gate_send(sfd, &ver, sizeof(ver), 0);
+ g_gate_swap2h_version(&ver);
+ if (data == -1) {
+ sendfail(sfd, errno, "Error while sending version packet: %s.",
+ strerror(errno));
+ return (0);
+ }
+
+ /*
+ * Phase 2: Request verification.
+ */
+ g_gate_log(LOG_DEBUG, "Receiving initial packet.");
+ data = g_gate_recv(sfd, &cinit, sizeof(cinit), MSG_WAITALL);
+ g_gate_swap2h_cinit(&cinit);
+ if (data != sizeof(cinit)) {
+ g_gate_log(LOG_WARNING, "Malformed initial packet.");
+ return (0);
+ }
+ g_gate_log(LOG_DEBUG, "Initial packet received.");
+ conn = connection_find(&cinit);
+ if (conn != NULL) {
+ /*
+ * Connection should already exists.
+ */
+ g_gate_log(LOG_DEBUG, "Found existing connection (token=%lu).",
+ (unsigned long)conn->c_token);
+ if (connection_add(conn, &cinit, from, sfd) == -1) {
+ connection_remove(conn);
+ return (0);
+ }
+ } else {
+ /*
+ * New connection, allocate space.
+ */
+ conn = connection_new(&cinit, from, sfd);
+ if (conn == NULL) {
+ sendfail(sfd, ENOMEM,
+ "Cannot allocate new connection.");
+ return (0);
+ }
+ g_gate_log(LOG_DEBUG, "New connection created (token=%lu).",
+ (unsigned long)conn->c_token);
+ }
+
+ ex = exports_find(from, &cinit, conn);
+ if (ex == NULL) {
+ connection_remove(conn);
+ sendfail(sfd, errno, NULL);
+ return (0);
+ }
+ if (conn->c_mediasize == 0) {
+ conn->c_mediasize = g_gate_mediasize(conn->c_diskfd);
+ conn->c_sectorsize = g_gate_sectorsize(conn->c_diskfd);
+ }
+ sinit.gs_mediasize = conn->c_mediasize;
+ sinit.gs_sectorsize = conn->c_sectorsize;
+ sinit.gs_error = 0;
+
+ g_gate_log(LOG_DEBUG, "Sending initial packet.");
+
+ g_gate_swap2n_sinit(&sinit);
+ data = g_gate_send(sfd, &sinit, sizeof(sinit), 0);
+ g_gate_swap2h_sinit(&sinit);
+ if (data == -1) {
+ sendfail(sfd, errno, "Error while sending initial packet: %s.",
+ strerror(errno));
+ return (0);
+ }
+
+ if (connection_ready(conn)) {
+ connection_launch(conn);
+ connection_remove(conn);
+ }
+ return (1);
+}
+
+static void
+huphandler(int sig __unused)
+{
+
+ got_sighup = 1;
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct sockaddr_in serv;
+ struct sockaddr from;
+ socklen_t fromlen;
+ int sfd, tmpsfd;
+ unsigned port;
+
+ bindaddr = htonl(INADDR_ANY);
+ port = G_GATE_PORT;
+ for (;;) {
+ int ch;
+
+ ch = getopt(argc, argv, "a:hnp:R:S:v");
+ if (ch == -1)
+ break;
+ switch (ch) {
+ case 'a':
+ bindaddr = g_gate_str2ip(optarg);
+ if (bindaddr == INADDR_NONE) {
+ errx(EXIT_FAILURE,
+ "Invalid IP/host name to bind to.");
+ }
+ break;
+ case 'n':
+ nagle = 0;
+ break;
+ case 'p':
+ errno = 0;
+ port = strtoul(optarg, NULL, 10);
+ if (port == 0 && errno != 0)
+ errx(EXIT_FAILURE, "Invalid port.");
+ break;
+ case 'R':
+ errno = 0;
+ rcvbuf = strtoul(optarg, NULL, 10);
+ if (rcvbuf == 0 && errno != 0)
+ errx(EXIT_FAILURE, "Invalid rcvbuf.");
+ break;
+ case 'S':
+ errno = 0;
+ sndbuf = strtoul(optarg, NULL, 10);
+ if (sndbuf == 0 && errno != 0)
+ errx(EXIT_FAILURE, "Invalid sndbuf.");
+ break;
+ case 'v':
+ g_gate_verbose++;
+ break;
+ case 'h':
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argv[0] != NULL)
+ exports_file = argv[0];
+ exports_get();
+
+ if (!g_gate_verbose) {
+ /* Run in daemon mode. */
+ if (daemon(0, 0) == -1)
+ g_gate_xlog("Cannot daemonize: %s", strerror(errno));
+ }
+
+ signal(SIGCHLD, SIG_IGN);
+
+ sfd = socket(AF_INET, SOCK_STREAM, 0);
+ if (sfd == -1)
+ g_gate_xlog("Cannot open stream socket: %s.", strerror(errno));
+ bzero(&serv, sizeof(serv));
+ serv.sin_family = AF_INET;
+ serv.sin_addr.s_addr = bindaddr;
+ serv.sin_port = htons(port);
+
+ g_gate_socket_settings(sfd);
+
+ if (bind(sfd, (struct sockaddr *)&serv, sizeof(serv)) == -1)
+ g_gate_xlog("bind(): %s.", strerror(errno));
+ if (listen(sfd, 5) == -1)
+ g_gate_xlog("listen(): %s.", strerror(errno));
+
+ g_gate_log(LOG_INFO, "Listen on port: %d.", port);
+
+ signal(SIGHUP, huphandler);
+
+ for (;;) {
+ fromlen = sizeof(from);
+ tmpsfd = accept(sfd, &from, &fromlen);
+ if (tmpsfd == -1)
+ g_gate_xlog("accept(): %s.", strerror(errno));
+
+ if (got_sighup) {
+ got_sighup = 0;
+ exports_get();
+ }
+
+ if (!handshake(&from, tmpsfd))
+ close(tmpsfd);
+ }
+ close(sfd);
+ exit(EXIT_SUCCESS);
+}
diff --git a/sbin/ggate/ggatel/Makefile b/sbin/ggate/ggatel/Makefile
new file mode 100644
index 0000000..be88bd4
--- /dev/null
+++ b/sbin/ggate/ggatel/Makefile
@@ -0,0 +1,14 @@
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../shared
+
+PROG= ggatel
+MAN= ggatel.8
+SRCS= ggatel.c ggate.c
+
+CFLAGS+= -DLIBGEOM
+CFLAGS+= -I${.CURDIR}/../shared
+
+LIBADD= geom util
+
+.include <bsd.prog.mk>
diff --git a/sbin/ggate/ggatel/ggatel.8 b/sbin/ggate/ggatel/ggatel.8
new file mode 100644
index 0000000..37e11cd
--- /dev/null
+++ b/sbin/ggate/ggatel/ggatel.8
@@ -0,0 +1,157 @@
+.\" Copyright (c) 2004 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd April 9, 2015
+.Dt GGATEL 8
+.Os
+.Sh NAME
+.Nm ggatel
+.Nd "GEOM Gate local control utility"
+.Sh SYNOPSIS
+.Nm
+.Cm create
+.Op Fl v
+.Op Fl o Cm ro | wo | rw
+.Op Fl s Ar sectorsize
+.Op Fl t Ar timeout
+.Op Fl u Ar unit
+.Ar path
+.Nm
+.Cm destroy
+.Op Fl f
+.Fl u Ar unit
+.Nm
+.Cm list
+.Op Fl v
+.Op Fl u Ar unit
+.Nm
+.Cm rescue
+.Op Fl v
+.Op Fl o Cm ro | wo | rw
+.Fl u Ar unit
+.Ar path
+.Sh DESCRIPTION
+The
+.Nm
+utility is a local GEOM Gate class consumer.
+It can be used as a replacement for
+.Xr md 4
+devices or as a
+.Dq GEOMificator
+for non GEOM-aware devices, but it was mainly created as an example
+on how to use and how to communicate with the GEOM Gate kernel module.
+.Pp
+Available commands:
+.Bl -tag -width ".Cm destroy"
+.It Cm create
+Create a
+.Nm ggate
+provider related to the given regular file or device.
+.It Cm destroy
+Destroy the given
+.Nm ggate
+provider.
+.It Cm list
+List
+.Nm ggate
+providers.
+.It Cm rescue
+Take over a previously created provider and handle pending and future
+requests. This is useful if the initial
+.Nm
+process died. To prevent data loss, the given path must lead to the
+regular file or device that was used to create the provider.
+.El
+.Pp
+Available options:
+.Bl -tag -width ".Fl s Cm ro | wo | rw"
+.It Fl f
+Forcibly destroy
+.Nm ggate
+provider (cancels all pending requests).
+.It Fl o Cm ro | wo | rw
+Specify permission to use when opening the file or device: read-only
+.Pq Cm ro ,
+write-only
+.Pq Cm wo ,
+or read-write
+.Pq Cm rw .
+Default is
+.Cm rw .
+.It Fl s Ar sectorsize
+Sector size for
+.Nm ggate
+provider.
+If not specified, it is taken from device, or set to 512 bytes for files.
+.It Fl t Ar timeout
+Number of seconds to wait before an I/O request will be canceled.
+0 means no timeout.
+Default is 30.
+.It Fl u Ar unit
+Unit number to use.
+.It Fl v
+Do not fork, run in foreground and print debug information on standard
+output.
+.It Ar path
+Path to a regular file or device.
+.El
+.Sh EXIT STATUS
+Exit status is 0 on success, or 1 if the command fails.
+To get details about the failure,
+.Nm
+should be called with the
+.Fl v
+option.
+.Sh EXAMPLES
+.Dq GEOMify
+the
+.Dq Li fd0
+device and use
+.Xr gbde 8
+to encrypt data on a floppy.
+.Bd -literal -offset indent
+ggatel create -u 5 /dev/fd0
+gbde init /dev/ggate5
+gbde attach ggate5
+newfs /dev/ggate5.bde
+mount /dev/ggate5.bde /secret
+cp /private/foo /secret/
+umount /secret
+gbde detach ggate5
+ggatel destroy -u 5
+.Ed
+.Sh SEE ALSO
+.Xr geom 4 ,
+.Xr gbde 8 ,
+.Xr ggatec 8 ,
+.Xr ggated 8 ,
+.Xr mount 8 ,
+.Xr newfs 8
+.Sh AUTHORS
+The
+.Nm
+utility as well as this manual page was written by
+.An Pawel Jakub Dawidek Aq Mt pjd@FreeBSD.org .
diff --git a/sbin/ggate/ggatel/ggatel.c b/sbin/ggate/ggatel/ggatel.c
new file mode 100644
index 0000000..abfe7c1
--- /dev/null
+++ b/sbin/ggate/ggatel/ggatel.c
@@ -0,0 +1,327 @@
+/*-
+ * Copyright (c) 2004 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ *
+ * $FreeBSD$
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <err.h>
+#include <errno.h>
+#include <assert.h>
+#include <sys/param.h>
+#include <sys/time.h>
+#include <sys/bio.h>
+#include <sys/disk.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/syslog.h>
+
+#include <geom/gate/g_gate.h>
+#include "ggate.h"
+
+
+static enum { UNSET, CREATE, DESTROY, LIST, RESCUE } action = UNSET;
+
+static const char *path = NULL;
+static int unit = G_GATE_UNIT_AUTO;
+static unsigned flags = 0;
+static int force = 0;
+static unsigned sectorsize = 0;
+static unsigned timeout = G_GATE_TIMEOUT;
+
+static void
+usage(void)
+{
+
+ fprintf(stderr, "usage: %s create [-v] [-o <ro|wo|rw>] "
+ "[-s sectorsize] [-t timeout] [-u unit] <path>\n", getprogname());
+ fprintf(stderr, " %s rescue [-v] [-o <ro|wo|rw>] <-u unit> "
+ "<path>\n", getprogname());
+ fprintf(stderr, " %s destroy [-f] <-u unit>\n", getprogname());
+ fprintf(stderr, " %s list [-v] [-u unit]\n", getprogname());
+ exit(EXIT_FAILURE);
+}
+
+static int
+g_gate_openflags(unsigned ggflags)
+{
+
+ if ((ggflags & G_GATE_FLAG_READONLY) != 0)
+ return (O_RDONLY);
+ else if ((ggflags & G_GATE_FLAG_WRITEONLY) != 0)
+ return (O_WRONLY);
+ return (O_RDWR);
+}
+
+static void
+g_gatel_serve(int fd)
+{
+ struct g_gate_ctl_io ggio;
+ size_t bsize;
+
+ if (g_gate_verbose == 0) {
+ if (daemon(0, 0) == -1) {
+ g_gate_destroy(unit, 1);
+ err(EXIT_FAILURE, "Cannot daemonize");
+ }
+ }
+ g_gate_log(LOG_DEBUG, "Worker created: %u.", getpid());
+ ggio.gctl_version = G_GATE_VERSION;
+ ggio.gctl_unit = unit;
+ bsize = sectorsize;
+ ggio.gctl_data = malloc(bsize);
+ for (;;) {
+ int error;
+once_again:
+ ggio.gctl_length = bsize;
+ ggio.gctl_error = 0;
+ g_gate_ioctl(G_GATE_CMD_START, &ggio);
+ error = ggio.gctl_error;
+ switch (error) {
+ case 0:
+ break;
+ case ECANCELED:
+ /* Exit gracefully. */
+ free(ggio.gctl_data);
+ g_gate_close_device();
+ close(fd);
+ exit(EXIT_SUCCESS);
+ case ENOMEM:
+ /* Buffer too small. */
+ assert(ggio.gctl_cmd == BIO_DELETE ||
+ ggio.gctl_cmd == BIO_WRITE);
+ ggio.gctl_data = realloc(ggio.gctl_data,
+ ggio.gctl_length);
+ if (ggio.gctl_data != NULL) {
+ bsize = ggio.gctl_length;
+ goto once_again;
+ }
+ /* FALLTHROUGH */
+ case ENXIO:
+ default:
+ g_gate_xlog("ioctl(/dev/%s): %s.", G_GATE_CTL_NAME,
+ strerror(error));
+ }
+
+ error = 0;
+ switch (ggio.gctl_cmd) {
+ case BIO_READ:
+ if ((size_t)ggio.gctl_length > bsize) {
+ ggio.gctl_data = realloc(ggio.gctl_data,
+ ggio.gctl_length);
+ if (ggio.gctl_data != NULL)
+ bsize = ggio.gctl_length;
+ else
+ error = ENOMEM;
+ }
+ if (error == 0) {
+ if (pread(fd, ggio.gctl_data, ggio.gctl_length,
+ ggio.gctl_offset) == -1) {
+ error = errno;
+ }
+ }
+ break;
+ case BIO_DELETE:
+ case BIO_WRITE:
+ if (pwrite(fd, ggio.gctl_data, ggio.gctl_length,
+ ggio.gctl_offset) == -1) {
+ error = errno;
+ }
+ break;
+ default:
+ error = EOPNOTSUPP;
+ }
+
+ ggio.gctl_error = error;
+ g_gate_ioctl(G_GATE_CMD_DONE, &ggio);
+ }
+}
+
+static void
+g_gatel_create(void)
+{
+ struct g_gate_ctl_create ggioc;
+ int fd;
+
+ fd = open(path, g_gate_openflags(flags) | O_DIRECT | O_FSYNC);
+ if (fd == -1)
+ err(EXIT_FAILURE, "Cannot open %s", path);
+ ggioc.gctl_version = G_GATE_VERSION;
+ ggioc.gctl_unit = unit;
+ ggioc.gctl_mediasize = g_gate_mediasize(fd);
+ if (sectorsize == 0)
+ sectorsize = g_gate_sectorsize(fd);
+ ggioc.gctl_sectorsize = sectorsize;
+ ggioc.gctl_timeout = timeout;
+ ggioc.gctl_flags = flags;
+ ggioc.gctl_maxcount = 0;
+ strlcpy(ggioc.gctl_info, path, sizeof(ggioc.gctl_info));
+ g_gate_ioctl(G_GATE_CMD_CREATE, &ggioc);
+ if (unit == -1)
+ printf("%s%u\n", G_GATE_PROVIDER_NAME, ggioc.gctl_unit);
+ unit = ggioc.gctl_unit;
+ g_gatel_serve(fd);
+}
+
+static void
+g_gatel_rescue(void)
+{
+ struct g_gate_ctl_cancel ggioc;
+ int fd;
+
+ fd = open(path, g_gate_openflags(flags));
+ if (fd == -1)
+ err(EXIT_FAILURE, "Cannot open %s", path);
+
+ ggioc.gctl_version = G_GATE_VERSION;
+ ggioc.gctl_unit = unit;
+ ggioc.gctl_seq = 0;
+ g_gate_ioctl(G_GATE_CMD_CANCEL, &ggioc);
+
+ g_gatel_serve(fd);
+}
+
+int
+main(int argc, char *argv[])
+{
+
+ if (argc < 2)
+ usage();
+ if (strcasecmp(argv[1], "create") == 0)
+ action = CREATE;
+ else if (strcasecmp(argv[1], "rescue") == 0)
+ action = RESCUE;
+ else if (strcasecmp(argv[1], "destroy") == 0)
+ action = DESTROY;
+ else if (strcasecmp(argv[1], "list") == 0)
+ action = LIST;
+ else
+ usage();
+ argc -= 1;
+ argv += 1;
+ for (;;) {
+ int ch;
+
+ ch = getopt(argc, argv, "fo:s:t:u:v");
+ if (ch == -1)
+ break;
+ switch (ch) {
+ case 'f':
+ if (action != DESTROY)
+ usage();
+ force = 1;
+ break;
+ case 'o':
+ if (action != CREATE && action != RESCUE)
+ usage();
+ if (strcasecmp("ro", optarg) == 0)
+ flags = G_GATE_FLAG_READONLY;
+ else if (strcasecmp("wo", optarg) == 0)
+ flags = G_GATE_FLAG_WRITEONLY;
+ else if (strcasecmp("rw", optarg) == 0)
+ flags = 0;
+ else {
+ errx(EXIT_FAILURE,
+ "Invalid argument for '-o' option.");
+ }
+ break;
+ case 's':
+ if (action != CREATE)
+ usage();
+ errno = 0;
+ sectorsize = strtoul(optarg, NULL, 10);
+ if (sectorsize == 0 && errno != 0)
+ errx(EXIT_FAILURE, "Invalid sectorsize.");
+ break;
+ case 't':
+ if (action != CREATE)
+ usage();
+ errno = 0;
+ timeout = strtoul(optarg, NULL, 10);
+ if (timeout == 0 && errno != 0)
+ errx(EXIT_FAILURE, "Invalid timeout.");
+ break;
+ case 'u':
+ errno = 0;
+ unit = strtol(optarg, NULL, 10);
+ if (unit == 0 && errno != 0)
+ errx(EXIT_FAILURE, "Invalid unit number.");
+ break;
+ case 'v':
+ if (action == DESTROY)
+ usage();
+ g_gate_verbose++;
+ break;
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ switch (action) {
+ case CREATE:
+ if (argc != 1)
+ usage();
+ g_gate_load_module();
+ g_gate_open_device();
+ path = argv[0];
+ g_gatel_create();
+ break;
+ case RESCUE:
+ if (argc != 1)
+ usage();
+ if (unit == -1) {
+ fprintf(stderr, "Required unit number.\n");
+ usage();
+ }
+ g_gate_open_device();
+ path = argv[0];
+ g_gatel_rescue();
+ break;
+ case DESTROY:
+ if (unit == -1) {
+ fprintf(stderr, "Required unit number.\n");
+ usage();
+ }
+ g_gate_verbose = 1;
+ g_gate_open_device();
+ g_gate_destroy(unit, force);
+ break;
+ case LIST:
+ g_gate_list(unit, g_gate_verbose);
+ break;
+ case UNSET:
+ default:
+ usage();
+ }
+ g_gate_close_device();
+ exit(EXIT_SUCCESS);
+}
diff --git a/sbin/ggate/shared/ggate.c b/sbin/ggate/shared/ggate.c
new file mode 100644
index 0000000..cf9b9ca
--- /dev/null
+++ b/sbin/ggate/shared/ggate.c
@@ -0,0 +1,409 @@
+/*-
+ * Copyright (c) 2004 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ *
+ * $FreeBSD$
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/param.h>
+#include <sys/disk.h>
+#include <sys/stat.h>
+#include <sys/endian.h>
+#include <sys/socket.h>
+#include <sys/linker.h>
+#include <sys/module.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <signal.h>
+#include <err.h>
+#include <errno.h>
+#include <string.h>
+#include <strings.h>
+#include <libgen.h>
+#include <libutil.h>
+#include <netdb.h>
+#include <syslog.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <libgeom.h>
+
+#include <geom/gate/g_gate.h>
+#include "ggate.h"
+
+
+int g_gate_devfd = -1;
+int g_gate_verbose = 0;
+
+
+void
+g_gate_vlog(int priority, const char *message, va_list ap)
+{
+
+ if (g_gate_verbose) {
+ const char *prefix;
+
+ switch (priority) {
+ case LOG_ERR:
+ prefix = "error";
+ break;
+ case LOG_WARNING:
+ prefix = "warning";
+ break;
+ case LOG_NOTICE:
+ prefix = "notice";
+ break;
+ case LOG_INFO:
+ prefix = "info";
+ break;
+ case LOG_DEBUG:
+ prefix = "debug";
+ break;
+ default:
+ prefix = "unknown";
+ }
+
+ printf("%s: ", prefix);
+ vprintf(message, ap);
+ printf("\n");
+ } else {
+ if (priority != LOG_DEBUG)
+ vsyslog(priority, message, ap);
+ }
+}
+
+void
+g_gate_log(int priority, const char *message, ...)
+{
+ va_list ap;
+
+ va_start(ap, message);
+ g_gate_vlog(priority, message, ap);
+ va_end(ap);
+}
+
+void
+g_gate_xvlog(const char *message, va_list ap)
+{
+
+ g_gate_vlog(LOG_ERR, message, ap);
+ g_gate_vlog(LOG_ERR, "Exiting.", ap);
+ exit(EXIT_FAILURE);
+}
+
+void
+g_gate_xlog(const char *message, ...)
+{
+ va_list ap;
+
+ va_start(ap, message);
+ g_gate_xvlog(message, ap);
+ /* NOTREACHED */
+ va_end(ap);
+ exit(EXIT_FAILURE);
+}
+
+off_t
+g_gate_mediasize(int fd)
+{
+ off_t mediasize;
+ struct stat sb;
+
+ if (fstat(fd, &sb) == -1)
+ g_gate_xlog("fstat(): %s.", strerror(errno));
+ if (S_ISCHR(sb.st_mode)) {
+ if (ioctl(fd, DIOCGMEDIASIZE, &mediasize) == -1) {
+ g_gate_xlog("Can't get media size: %s.",
+ strerror(errno));
+ }
+ } else if (S_ISREG(sb.st_mode)) {
+ mediasize = sb.st_size;
+ } else {
+ g_gate_xlog("Unsupported file system object.");
+ }
+ return (mediasize);
+}
+
+unsigned
+g_gate_sectorsize(int fd)
+{
+ unsigned secsize;
+ struct stat sb;
+
+ if (fstat(fd, &sb) == -1)
+ g_gate_xlog("fstat(): %s.", strerror(errno));
+ if (S_ISCHR(sb.st_mode)) {
+ if (ioctl(fd, DIOCGSECTORSIZE, &secsize) == -1) {
+ g_gate_xlog("Can't get sector size: %s.",
+ strerror(errno));
+ }
+ } else if (S_ISREG(sb.st_mode)) {
+ secsize = 512;
+ } else {
+ g_gate_xlog("Unsupported file system object.");
+ }
+ return (secsize);
+}
+
+void
+g_gate_open_device(void)
+{
+
+ g_gate_devfd = open("/dev/" G_GATE_CTL_NAME, O_RDWR);
+ if (g_gate_devfd == -1)
+ err(EXIT_FAILURE, "open(/dev/%s)", G_GATE_CTL_NAME);
+}
+
+void
+g_gate_close_device(void)
+{
+
+ close(g_gate_devfd);
+}
+
+void
+g_gate_ioctl(unsigned long req, void *data)
+{
+
+ if (ioctl(g_gate_devfd, req, data) == -1) {
+ g_gate_xlog("%s: ioctl(/dev/%s): %s.", getprogname(),
+ G_GATE_CTL_NAME, strerror(errno));
+ }
+}
+
+void
+g_gate_destroy(int unit, int force)
+{
+ struct g_gate_ctl_destroy ggio;
+
+ ggio.gctl_version = G_GATE_VERSION;
+ ggio.gctl_unit = unit;
+ ggio.gctl_force = force;
+ g_gate_ioctl(G_GATE_CMD_DESTROY, &ggio);
+}
+
+void
+g_gate_load_module(void)
+{
+
+ if (modfind("g_gate") == -1) {
+ /* Not present in kernel, try loading it. */
+ if (kldload("geom_gate") == -1 || modfind("g_gate") == -1) {
+ if (errno != EEXIST) {
+ errx(EXIT_FAILURE,
+ "geom_gate module not available!");
+ }
+ }
+ }
+}
+
+/*
+ * When we send from ggatec packets larger than 32kB, performance drops
+ * significantly (eg. to 256kB/s over 1Gbit/s link). This is not a problem
+ * when data is send from ggated. I don't know why, so for now I limit
+ * size of packets send from ggatec to 32kB by defining MAX_SEND_SIZE
+ * in ggatec Makefile.
+ */
+#ifndef MAX_SEND_SIZE
+#define MAX_SEND_SIZE MAXPHYS
+#endif
+ssize_t
+g_gate_send(int s, const void *buf, size_t len, int flags)
+{
+ ssize_t done = 0, done2;
+ const unsigned char *p = buf;
+
+ while (len > 0) {
+ done2 = send(s, p, MIN(len, MAX_SEND_SIZE), flags);
+ if (done2 == 0)
+ break;
+ else if (done2 == -1) {
+ if (errno == EAGAIN) {
+ printf("%s: EAGAIN\n", __func__);
+ continue;
+ }
+ done = -1;
+ break;
+ }
+ done += done2;
+ p += done2;
+ len -= done2;
+ }
+ return (done);
+}
+
+ssize_t
+g_gate_recv(int s, void *buf, size_t len, int flags)
+{
+ ssize_t done;
+
+ do {
+ done = recv(s, buf, len, flags);
+ } while (done == -1 && errno == EAGAIN);
+ return (done);
+}
+
+int nagle = 1;
+unsigned rcvbuf = G_GATE_RCVBUF;
+unsigned sndbuf = G_GATE_SNDBUF;
+
+void
+g_gate_socket_settings(int sfd)
+{
+ struct timeval tv;
+ int bsize, on;
+
+ /* Socket settings. */
+ on = 1;
+ if (nagle) {
+ if (setsockopt(sfd, IPPROTO_TCP, TCP_NODELAY, &on,
+ sizeof(on)) == -1) {
+ g_gate_xlog("setsockopt() error: %s.", strerror(errno));
+ }
+ }
+ if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1)
+ g_gate_xlog("setsockopt(SO_REUSEADDR): %s.", strerror(errno));
+ bsize = rcvbuf;
+ if (setsockopt(sfd, SOL_SOCKET, SO_RCVBUF, &bsize, sizeof(bsize)) == -1)
+ g_gate_xlog("setsockopt(SO_RCVBUF): %s.", strerror(errno));
+ bsize = sndbuf;
+ if (setsockopt(sfd, SOL_SOCKET, SO_SNDBUF, &bsize, sizeof(bsize)) == -1)
+ g_gate_xlog("setsockopt(SO_SNDBUF): %s.", strerror(errno));
+ tv.tv_sec = 8;
+ tv.tv_usec = 0;
+ if (setsockopt(sfd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) == -1) {
+ g_gate_log(LOG_ERR, "setsockopt(SO_SNDTIMEO) error: %s.",
+ strerror(errno));
+ }
+ if (setsockopt(sfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) == -1) {
+ g_gate_log(LOG_ERR, "setsockopt(SO_RCVTIMEO) error: %s.",
+ strerror(errno));
+ }
+}
+
+#ifdef LIBGEOM
+static struct gclass *
+find_class(struct gmesh *mesh, const char *name)
+{
+ struct gclass *class;
+
+ LIST_FOREACH(class, &mesh->lg_class, lg_class) {
+ if (strcmp(class->lg_name, name) == 0)
+ return (class);
+ }
+ return (NULL);
+}
+
+static const char *
+get_conf(struct ggeom *gp, const char *name)
+{
+ struct gconfig *conf;
+
+ LIST_FOREACH(conf, &gp->lg_config, lg_config) {
+ if (strcmp(conf->lg_name, name) == 0)
+ return (conf->lg_val);
+ }
+ return (NULL);
+}
+
+static void
+show_config(struct ggeom *gp, int verbose)
+{
+ struct gprovider *pp;
+ char buf[5];
+
+ pp = LIST_FIRST(&gp->lg_provider);
+ if (pp == NULL)
+ return;
+ if (!verbose) {
+ printf("%s\n", pp->lg_name);
+ return;
+ }
+ printf(" NAME: %s\n", pp->lg_name);
+ printf(" info: %s\n", get_conf(gp, "info"));
+ printf(" access: %s\n", get_conf(gp, "access"));
+ printf(" timeout: %s\n", get_conf(gp, "timeout"));
+ printf("queue_count: %s\n", get_conf(gp, "queue_count"));
+ printf(" queue_size: %s\n", get_conf(gp, "queue_size"));
+ printf(" references: %s\n", get_conf(gp, "ref"));
+ humanize_number(buf, sizeof(buf), (int64_t)pp->lg_mediasize, "",
+ HN_AUTOSCALE, HN_B | HN_NOSPACE | HN_DECIMAL);
+ printf(" mediasize: %jd (%s)\n", (intmax_t)pp->lg_mediasize, buf);
+ printf(" sectorsize: %u\n", pp->lg_sectorsize);
+ printf(" mode: %s\n", pp->lg_mode);
+ printf("\n");
+}
+
+void
+g_gate_list(int unit, int verbose)
+{
+ struct gmesh mesh;
+ struct gclass *class;
+ struct ggeom *gp;
+ char name[64];
+ int error;
+
+ error = geom_gettree(&mesh);
+ if (error != 0)
+ exit(EXIT_FAILURE);
+ class = find_class(&mesh, G_GATE_CLASS_NAME);
+ if (class == NULL) {
+ geom_deletetree(&mesh);
+ exit(EXIT_SUCCESS);
+ }
+ if (unit >= 0) {
+ snprintf(name, sizeof(name), "%s%d", G_GATE_PROVIDER_NAME,
+ unit);
+ }
+ LIST_FOREACH(gp, &class->lg_geom, lg_geom) {
+ if (unit != -1 && strcmp(gp->lg_name, name) != 0)
+ continue;
+ show_config(gp, verbose);
+ }
+ geom_deletetree(&mesh);
+ exit(EXIT_SUCCESS);
+}
+#endif /* LIBGEOM */
+
+in_addr_t
+g_gate_str2ip(const char *str)
+{
+ struct hostent *hp;
+ in_addr_t ip;
+
+ ip = inet_addr(str);
+ if (ip != INADDR_NONE) {
+ /* It is a valid IP address. */
+ return (ip);
+ }
+ /* Check if it is a valid host name. */
+ hp = gethostbyname(str);
+ if (hp == NULL)
+ return (INADDR_NONE);
+ return (((struct in_addr *)(void *)hp->h_addr)->s_addr);
+}
diff --git a/sbin/ggate/shared/ggate.h b/sbin/ggate/shared/ggate.h
new file mode 100644
index 0000000..898efea
--- /dev/null
+++ b/sbin/ggate/shared/ggate.h
@@ -0,0 +1,196 @@
+/*-
+ * Copyright (c) 2004 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _GGATE_H_
+#define _GGATE_H_
+
+#include <sys/endian.h>
+#include <stdarg.h>
+
+#define G_GATE_PORT 3080
+
+#define G_GATE_RCVBUF 131072
+#define G_GATE_SNDBUF 131072
+#define G_GATE_QUEUE_SIZE 1024
+#define G_GATE_TIMEOUT 0
+
+#define GGATE_MAGIC "GEOM_GATE "
+#define GGATE_VERSION 0
+
+#define GGATE_FLAG_RDONLY 0x0001
+#define GGATE_FLAG_WRONLY 0x0002
+/*
+ * If GGATE_FLAG_SEND not GGATE_FLAG_RECV flag is set, this is initial
+ * connection.
+ * If GGATE_FLAG_SEND flag is set - this is socket to send data.
+ * If GGATE_FLAG_RECV flag is set - this is socket to receive data.
+ */
+#define GGATE_FLAG_SEND 0x0004
+#define GGATE_FLAG_RECV 0x0008
+
+#define GGATE_CMD_READ 0
+#define GGATE_CMD_WRITE 1
+
+extern int g_gate_devfd;
+extern int g_gate_verbose;
+
+extern int nagle;
+extern unsigned rcvbuf, sndbuf;
+
+struct g_gate_version {
+ char gv_magic[16];
+ uint16_t gv_version;
+ uint16_t gv_error;
+} __packed;
+
+/* Client's initial packet. */
+struct g_gate_cinit {
+ char gc_path[PATH_MAX + 1];
+ uint64_t gc_flags;
+ uint16_t gc_nconn;
+ uint32_t gc_token;
+} __packed;
+
+/* Server's initial packet. */
+struct g_gate_sinit {
+ uint8_t gs_flags;
+ uint64_t gs_mediasize;
+ uint32_t gs_sectorsize;
+ uint16_t gs_error;
+} __packed;
+
+/* Control struct. */
+struct g_gate_hdr {
+ uint8_t gh_cmd; /* command */
+ uint64_t gh_offset; /* device offset */
+ uint32_t gh_length; /* size of block */
+ uint64_t gh_seq; /* request number */
+ uint16_t gh_error; /* error value (0 if ok) */
+} __packed;
+
+void g_gate_vlog(int priority, const char *message, va_list ap);
+void g_gate_log(int priority, const char *message, ...);
+void g_gate_xvlog(const char *message, va_list ap) __dead2;
+void g_gate_xlog(const char *message, ...) __dead2;
+off_t g_gate_mediasize(int fd);
+unsigned g_gate_sectorsize(int fd);
+void g_gate_open_device(void);
+void g_gate_close_device(void);
+void g_gate_ioctl(unsigned long req, void *data);
+void g_gate_destroy(int unit, int force);
+void g_gate_load_module(void);
+ssize_t g_gate_recv(int s, void *buf, size_t len, int flags);
+ssize_t g_gate_send(int s, const void *buf, size_t len, int flags);
+void g_gate_socket_settings(int sfd);
+#ifdef LIBGEOM
+void g_gate_list(int unit, int verbose);
+#endif
+in_addr_t g_gate_str2ip(const char *str);
+
+/*
+ * g_gate_swap2h_* - functions swap bytes to host byte order (from big endian).
+ * g_gate_swap2n_* - functions swap bytes to network byte order (actually
+ * to big endian byte order).
+ */
+
+static __inline void
+g_gate_swap2h_version(struct g_gate_version *ver)
+{
+
+ ver->gv_version = be16toh(ver->gv_version);
+ ver->gv_error = be16toh(ver->gv_error);
+}
+
+static __inline void
+g_gate_swap2n_version(struct g_gate_version *ver)
+{
+
+ ver->gv_version = htobe16(ver->gv_version);
+ ver->gv_error = htobe16(ver->gv_error);
+}
+
+static __inline void
+g_gate_swap2h_cinit(struct g_gate_cinit *cinit)
+{
+
+ cinit->gc_flags = be64toh(cinit->gc_flags);
+ cinit->gc_nconn = be16toh(cinit->gc_nconn);
+ cinit->gc_token = be32toh(cinit->gc_token);
+}
+
+static __inline void
+g_gate_swap2n_cinit(struct g_gate_cinit *cinit)
+{
+
+ cinit->gc_flags = htobe64(cinit->gc_flags);
+ cinit->gc_nconn = htobe16(cinit->gc_nconn);
+ cinit->gc_token = htobe32(cinit->gc_token);
+}
+
+static __inline void
+g_gate_swap2h_sinit(struct g_gate_sinit *sinit)
+{
+
+ /* Swap only used fields. */
+ sinit->gs_mediasize = be64toh(sinit->gs_mediasize);
+ sinit->gs_sectorsize = be32toh(sinit->gs_sectorsize);
+ sinit->gs_error = be16toh(sinit->gs_error);
+}
+
+static __inline void
+g_gate_swap2n_sinit(struct g_gate_sinit *sinit)
+{
+
+ /* Swap only used fields. */
+ sinit->gs_mediasize = htobe64(sinit->gs_mediasize);
+ sinit->gs_sectorsize = htobe32(sinit->gs_sectorsize);
+ sinit->gs_error = htobe16(sinit->gs_error);
+}
+
+static __inline void
+g_gate_swap2h_hdr(struct g_gate_hdr *hdr)
+{
+
+ /* Swap only used fields. */
+ hdr->gh_offset = be64toh(hdr->gh_offset);
+ hdr->gh_length = be32toh(hdr->gh_length);
+ hdr->gh_seq = be64toh(hdr->gh_seq);
+ hdr->gh_error = be16toh(hdr->gh_error);
+}
+
+static __inline void
+g_gate_swap2n_hdr(struct g_gate_hdr *hdr)
+{
+
+ /* Swap only used fields. */
+ hdr->gh_offset = htobe64(hdr->gh_offset);
+ hdr->gh_length = htobe32(hdr->gh_length);
+ hdr->gh_seq = htobe64(hdr->gh_seq);
+ hdr->gh_error = htobe16(hdr->gh_error);
+}
+#endif /* _GGATE_H_ */
diff --git a/sbin/growfs/Makefile b/sbin/growfs/Makefile
new file mode 100644
index 0000000..e7017a7
--- /dev/null
+++ b/sbin/growfs/Makefile
@@ -0,0 +1,28 @@
+# @(#)Makefile 8.8 (Berkeley) 6/21/2000
+#
+# $TSHeader: src/sbin/growfs/Makefile,v 1.4 2000/12/05 19:45:24 tomsoft Exp $
+# $FreeBSD$
+#
+
+.include <src.opts.mk>
+
+.PATH: ${.CURDIR}/../mount
+
+PROG= growfs
+SRCS= growfs.c getmntopts.c
+MAN= growfs.8
+CFLAGS+=-I${.CURDIR}/../mount
+
+.if defined(GFSDBG)
+SRCS+= debug.c
+CFLAGS+= -DFS_DEBUG
+NO_WCAST_ALIGN= yes
+.endif
+
+LIBADD= util
+
+.if ${MK_TESTS} != "no"
+SUBDIR+= tests
+.endif
+
+.include <bsd.prog.mk>
diff --git a/sbin/growfs/debug.c b/sbin/growfs/debug.c
new file mode 100644
index 0000000..55e2b13
--- /dev/null
+++ b/sbin/growfs/debug.c
@@ -0,0 +1,841 @@
+/*
+ * Copyright (c) 2000 Christoph Herrmann, Thomas-Henning von Kamptz
+ * Copyright (c) 1980, 1989, 1993 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Christoph Herrmann and Thomas-Henning von Kamptz, Munich and Frankfurt.
+ *
+ * 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, as well as Christoph
+ * Herrmann and Thomas-Henning von Kamptz.
+ * 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.
+ *
+ * $TSHeader: src/sbin/growfs/debug.c,v 1.3 2000/12/12 19:31:00 tomsoft Exp $
+ *
+ */
+
+#ifndef lint
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <sys/param.h>
+
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+#include <ufs/ufs/dinode.h>
+#include <ufs/ffs/fs.h>
+
+#include "debug.h"
+
+#ifdef FS_DEBUG
+
+static FILE *dbg_log = NULL;
+static unsigned int indent = 0;
+
+/*
+ * prototypes not done here, as they come with debug.h
+ */
+
+/*
+ * Open the filehandle where all debug output has to go.
+ */
+void
+dbg_open(const char *fn)
+{
+
+ if (strcmp(fn, "-") == 0)
+ dbg_log = fopen("/dev/stdout", "a");
+ else
+ dbg_log = fopen(fn, "a");
+
+ return;
+}
+
+/*
+ * Close the filehandle where all debug output went to.
+ */
+void
+dbg_close(void)
+{
+
+ if (dbg_log) {
+ fclose(dbg_log);
+ dbg_log = NULL;
+ }
+
+ return;
+}
+
+/*
+ * Dump out a full file system block in hex.
+ */
+void
+dbg_dump_hex(struct fs *sb, const char *comment, unsigned char *mem)
+{
+ int i, j, k;
+
+ if (!dbg_log)
+ return;
+
+ fprintf(dbg_log, "===== START HEXDUMP =====\n");
+ fprintf(dbg_log, "# %d@%lx: %s\n", indent, (unsigned long)mem, comment);
+ indent++;
+ for (i = 0; i < sb->fs_bsize; i += 24) {
+ for (j = 0; j < 3; j++) {
+ for (k = 0; k < 8; k++)
+ fprintf(dbg_log, "%02x ", *mem++);
+ fprintf(dbg_log, " ");
+ }
+ fprintf(dbg_log, "\n");
+ }
+ indent--;
+ fprintf(dbg_log, "===== END HEXDUMP =====\n");
+
+ return;
+}
+
+/*
+ * Dump the superblock.
+ */
+void
+dbg_dump_fs(struct fs *sb, const char *comment)
+{
+ int j;
+
+ if (!dbg_log)
+ return;
+
+ fprintf(dbg_log, "===== START SUPERBLOCK =====\n");
+ fprintf(dbg_log, "# %d@%lx: %s\n", indent, (unsigned long)sb, comment);
+ indent++;
+
+ fprintf(dbg_log, "sblkno int32_t 0x%08x\n",
+ sb->fs_sblkno);
+ fprintf(dbg_log, "cblkno int32_t 0x%08x\n",
+ sb->fs_cblkno);
+ fprintf(dbg_log, "iblkno int32_t 0x%08x\n",
+ sb->fs_iblkno);
+ fprintf(dbg_log, "dblkno int32_t 0x%08x\n",
+ sb->fs_dblkno);
+
+ fprintf(dbg_log, "old_cgoffset int32_t 0x%08x\n",
+ sb->fs_old_cgoffset);
+ fprintf(dbg_log, "old_cgmask int32_t 0x%08x\n",
+ sb->fs_old_cgmask);
+ fprintf(dbg_log, "old_time int32_t %10u\n",
+ (unsigned int)sb->fs_old_time);
+ fprintf(dbg_log, "old_size int32_t 0x%08x\n",
+ sb->fs_old_size);
+ fprintf(dbg_log, "old_dsize int32_t 0x%08x\n",
+ sb->fs_old_dsize);
+ fprintf(dbg_log, "ncg int32_t 0x%08x\n",
+ sb->fs_ncg);
+ fprintf(dbg_log, "bsize int32_t 0x%08x\n",
+ sb->fs_bsize);
+ fprintf(dbg_log, "fsize int32_t 0x%08x\n",
+ sb->fs_fsize);
+ fprintf(dbg_log, "frag int32_t 0x%08x\n",
+ sb->fs_frag);
+
+ fprintf(dbg_log, "minfree int32_t 0x%08x\n",
+ sb->fs_minfree);
+ fprintf(dbg_log, "old_rotdelay int32_t 0x%08x\n",
+ sb->fs_old_rotdelay);
+ fprintf(dbg_log, "old_rps int32_t 0x%08x\n",
+ sb->fs_old_rps);
+
+ fprintf(dbg_log, "bmask int32_t 0x%08x\n",
+ sb->fs_bmask);
+ fprintf(dbg_log, "fmask int32_t 0x%08x\n",
+ sb->fs_fmask);
+ fprintf(dbg_log, "bshift int32_t 0x%08x\n",
+ sb->fs_bshift);
+ fprintf(dbg_log, "fshift int32_t 0x%08x\n",
+ sb->fs_fshift);
+
+ fprintf(dbg_log, "maxcontig int32_t 0x%08x\n",
+ sb->fs_maxcontig);
+ fprintf(dbg_log, "maxbpg int32_t 0x%08x\n",
+ sb->fs_maxbpg);
+
+ fprintf(dbg_log, "fragshift int32_t 0x%08x\n",
+ sb->fs_fragshift);
+ fprintf(dbg_log, "fsbtodb int32_t 0x%08x\n",
+ sb->fs_fsbtodb);
+ fprintf(dbg_log, "sbsize int32_t 0x%08x\n",
+ sb->fs_sbsize);
+ fprintf(dbg_log, "spare1 int32_t[2] 0x%08x 0x%08x\n",
+ sb->fs_spare1[0], sb->fs_spare1[1]);
+ fprintf(dbg_log, "nindir int32_t 0x%08x\n",
+ sb->fs_nindir);
+ fprintf(dbg_log, "inopb int32_t 0x%08x\n",
+ sb->fs_inopb);
+ fprintf(dbg_log, "old_nspf int32_t 0x%08x\n",
+ sb->fs_old_nspf);
+
+ fprintf(dbg_log, "optim int32_t 0x%08x\n",
+ sb->fs_optim);
+
+ fprintf(dbg_log, "old_npsect int32_t 0x%08x\n",
+ sb->fs_old_npsect);
+ fprintf(dbg_log, "old_interleave int32_t 0x%08x\n",
+ sb->fs_old_interleave);
+ fprintf(dbg_log, "old_trackskew int32_t 0x%08x\n",
+ sb->fs_old_trackskew);
+
+ fprintf(dbg_log, "id int32_t[2] 0x%08x 0x%08x\n",
+ sb->fs_id[0], sb->fs_id[1]);
+
+ fprintf(dbg_log, "old_csaddr int32_t 0x%08x\n",
+ sb->fs_old_csaddr);
+ fprintf(dbg_log, "cssize int32_t 0x%08x\n",
+ sb->fs_cssize);
+ fprintf(dbg_log, "cgsize int32_t 0x%08x\n",
+ sb->fs_cgsize);
+
+ fprintf(dbg_log, "spare2 int32_t 0x%08x\n",
+ sb->fs_spare2);
+ fprintf(dbg_log, "old_nsect int32_t 0x%08x\n",
+ sb->fs_old_nsect);
+ fprintf(dbg_log, "old_spc int32_t 0x%08x\n",
+ sb->fs_old_spc);
+
+ fprintf(dbg_log, "old_ncyl int32_t 0x%08x\n",
+ sb->fs_old_ncyl);
+
+ fprintf(dbg_log, "old_cpg int32_t 0x%08x\n",
+ sb->fs_old_cpg);
+ fprintf(dbg_log, "ipg int32_t 0x%08x\n",
+ sb->fs_ipg);
+ fprintf(dbg_log, "fpg int32_t 0x%08x\n",
+ sb->fs_fpg);
+
+ dbg_dump_csum("internal old_cstotal", &sb->fs_old_cstotal);
+
+ fprintf(dbg_log, "fmod int8_t 0x%02x\n",
+ sb->fs_fmod);
+ fprintf(dbg_log, "clean int8_t 0x%02x\n",
+ sb->fs_clean);
+ fprintf(dbg_log, "ronly int8_t 0x%02x\n",
+ sb->fs_ronly);
+ fprintf(dbg_log, "old_flags int8_t 0x%02x\n",
+ sb->fs_old_flags);
+ fprintf(dbg_log, "fsmnt u_char[MAXMNTLEN] \"%s\"\n",
+ sb->fs_fsmnt);
+ fprintf(dbg_log, "volname u_char[MAXVOLLEN] \"%s\"\n",
+ sb->fs_volname);
+ fprintf(dbg_log, "swuid u_int64_t 0x%08x%08x\n",
+ ((unsigned int *)&(sb->fs_swuid))[1],
+ ((unsigned int *)&(sb->fs_swuid))[0]);
+
+ fprintf(dbg_log, "pad int32_t 0x%08x\n",
+ sb->fs_pad);
+
+ fprintf(dbg_log, "cgrotor int32_t 0x%08x\n",
+ sb->fs_cgrotor);
+/*
+ * struct csum[MAXCSBUFS] - is only maintained in memory
+ */
+/* fprintf(dbg_log, " int32_t\n", sb->*fs_maxcluster);*/
+ fprintf(dbg_log, "old_cpc int32_t 0x%08x\n",
+ sb->fs_old_cpc);
+/*
+ * int16_t fs_opostbl[16][8] - is dumped when used in dbg_dump_sptbl
+ */
+ fprintf(dbg_log, "maxbsize int32_t 0x%08x\n",
+ sb->fs_maxbsize);
+ fprintf(dbg_log, "unrefs int64_t 0x%08jx\n",
+ sb->fs_unrefs);
+ fprintf(dbg_log, "sblockloc int64_t 0x%08x%08x\n",
+ ((unsigned int *)&(sb->fs_sblockloc))[1],
+ ((unsigned int *)&(sb->fs_sblockloc))[0]);
+
+ dbg_dump_csum_total("internal cstotal", &sb->fs_cstotal);
+
+ fprintf(dbg_log, "time ufs_time_t %10u\n",
+ (unsigned int)sb->fs_time);
+
+ fprintf(dbg_log, "size int64_t 0x%08x%08x\n",
+ ((unsigned int *)&(sb->fs_size))[1],
+ ((unsigned int *)&(sb->fs_size))[0]);
+ fprintf(dbg_log, "dsize int64_t 0x%08x%08x\n",
+ ((unsigned int *)&(sb->fs_dsize))[1],
+ ((unsigned int *)&(sb->fs_dsize))[0]);
+ fprintf(dbg_log, "csaddr ufs2_daddr_t 0x%08x%08x\n",
+ ((unsigned int *)&(sb->fs_csaddr))[1],
+ ((unsigned int *)&(sb->fs_csaddr))[0]);
+ fprintf(dbg_log, "pendingblocks int64_t 0x%08x%08x\n",
+ ((unsigned int *)&(sb->fs_pendingblocks))[1],
+ ((unsigned int *)&(sb->fs_pendingblocks))[0]);
+ fprintf(dbg_log, "pendinginodes int32_t 0x%08x\n",
+ sb->fs_pendinginodes);
+
+ for (j = 0; j < FSMAXSNAP; j++) {
+ fprintf(dbg_log, "snapinum int32_t[%2d] 0x%08x\n",
+ j, sb->fs_snapinum[j]);
+ if (!sb->fs_snapinum[j]) { /* list is dense */
+ break;
+ }
+ }
+ fprintf(dbg_log, "avgfilesize int32_t 0x%08x\n",
+ sb->fs_avgfilesize);
+ fprintf(dbg_log, "avgfpdir int32_t 0x%08x\n",
+ sb->fs_avgfpdir);
+ fprintf(dbg_log, "save_cgsize int32_t 0x%08x\n",
+ sb->fs_save_cgsize);
+ fprintf(dbg_log, "flags int32_t 0x%08x\n",
+ sb->fs_flags);
+ fprintf(dbg_log, "contigsumsize int32_t 0x%08x\n",
+ sb->fs_contigsumsize);
+ fprintf(dbg_log, "maxsymlinklen int32_t 0x%08x\n",
+ sb->fs_maxsymlinklen);
+ fprintf(dbg_log, "old_inodefmt int32_t 0x%08x\n",
+ sb->fs_old_inodefmt);
+ fprintf(dbg_log, "maxfilesize u_int64_t 0x%08x%08x\n",
+ ((unsigned int *)&(sb->fs_maxfilesize))[1],
+ ((unsigned int *)&(sb->fs_maxfilesize))[0]);
+ fprintf(dbg_log, "qbmask int64_t 0x%08x%08x\n",
+ ((unsigned int *)&(sb->fs_qbmask))[1],
+ ((unsigned int *)&(sb->fs_qbmask))[0]);
+ fprintf(dbg_log, "qfmask int64_t 0x%08x%08x\n",
+ ((unsigned int *)&(sb->fs_qfmask))[1],
+ ((unsigned int *)&(sb->fs_qfmask))[0]);
+ fprintf(dbg_log, "state int32_t 0x%08x\n",
+ sb->fs_state);
+ fprintf(dbg_log, "old_postblformat int32_t 0x%08x\n",
+ sb->fs_old_postblformat);
+ fprintf(dbg_log, "old_nrpos int32_t 0x%08x\n",
+ sb->fs_old_nrpos);
+ fprintf(dbg_log, "spare5 int32_t[2] 0x%08x 0x%08x\n",
+ sb->fs_spare5[0], sb->fs_spare5[1]);
+ fprintf(dbg_log, "magic int32_t 0x%08x\n",
+ sb->fs_magic);
+
+ indent--;
+ fprintf(dbg_log, "===== END SUPERBLOCK =====\n");
+
+ return;
+}
+
+/*
+ * Dump a cylinder group.
+ */
+void
+dbg_dump_cg(const char *comment, struct cg *cgr)
+{
+ int j;
+
+ if (!dbg_log)
+ return;
+
+ fprintf(dbg_log, "===== START CYLINDER GROUP =====\n");
+ fprintf(dbg_log, "# %d@%lx: %s\n", indent, (unsigned long)cgr, comment);
+ indent++;
+
+ fprintf(dbg_log, "magic int32_t 0x%08x\n", cgr->cg_magic);
+ fprintf(dbg_log, "old_time int32_t 0x%08x\n", cgr->cg_old_time);
+ fprintf(dbg_log, "cgx int32_t 0x%08x\n", cgr->cg_cgx);
+ fprintf(dbg_log, "old_ncyl int16_t 0x%04x\n", cgr->cg_old_ncyl);
+ fprintf(dbg_log, "old_niblk int16_t 0x%04x\n", cgr->cg_old_niblk);
+ fprintf(dbg_log, "ndblk int32_t 0x%08x\n", cgr->cg_ndblk);
+ dbg_dump_csum("internal cs", &cgr->cg_cs);
+ fprintf(dbg_log, "rotor int32_t 0x%08x\n", cgr->cg_rotor);
+ fprintf(dbg_log, "frotor int32_t 0x%08x\n", cgr->cg_frotor);
+ fprintf(dbg_log, "irotor int32_t 0x%08x\n", cgr->cg_irotor);
+ for (j = 0; j < MAXFRAG; j++) {
+ fprintf(dbg_log, "frsum int32_t[%d] 0x%08x\n", j,
+ cgr->cg_frsum[j]);
+ }
+ fprintf(dbg_log, "old_btotoff int32_t 0x%08x\n", cgr->cg_old_btotoff);
+ fprintf(dbg_log, "old_boff int32_t 0x%08x\n", cgr->cg_old_boff);
+ fprintf(dbg_log, "iusedoff int32_t 0x%08x\n", cgr->cg_iusedoff);
+ fprintf(dbg_log, "freeoff int32_t 0x%08x\n", cgr->cg_freeoff);
+ fprintf(dbg_log, "nextfreeoff int32_t 0x%08x\n",
+ cgr->cg_nextfreeoff);
+ fprintf(dbg_log, "clustersumoff int32_t 0x%08x\n",
+ cgr->cg_clustersumoff);
+ fprintf(dbg_log, "clusteroff int32_t 0x%08x\n",
+ cgr->cg_clusteroff);
+ fprintf(dbg_log, "nclusterblks int32_t 0x%08x\n",
+ cgr->cg_nclusterblks);
+ fprintf(dbg_log, "niblk int32_t 0x%08x\n", cgr->cg_niblk);
+ fprintf(dbg_log, "initediblk int32_t 0x%08x\n", cgr->cg_initediblk);
+ fprintf(dbg_log, "unrefs int32_t 0x%08x\n", cgr->cg_unrefs);
+ fprintf(dbg_log, "time ufs_time_t %10u\n",
+ (unsigned int)cgr->cg_initediblk);
+
+ indent--;
+ fprintf(dbg_log, "===== END CYLINDER GROUP =====\n");
+
+ return;
+}
+
+/*
+ * Dump a cylinder summary.
+ */
+void
+dbg_dump_csum(const char *comment, struct csum *cs)
+{
+
+ if (!dbg_log)
+ return;
+
+ fprintf(dbg_log, "===== START CYLINDER SUMMARY =====\n");
+ fprintf(dbg_log, "# %d@%lx: %s\n", indent, (unsigned long)cs, comment);
+ indent++;
+
+ fprintf(dbg_log, "ndir int32_t 0x%08x\n", cs->cs_ndir);
+ fprintf(dbg_log, "nbfree int32_t 0x%08x\n", cs->cs_nbfree);
+ fprintf(dbg_log, "nifree int32_t 0x%08x\n", cs->cs_nifree);
+ fprintf(dbg_log, "nffree int32_t 0x%08x\n", cs->cs_nffree);
+
+ indent--;
+ fprintf(dbg_log, "===== END CYLINDER SUMMARY =====\n");
+
+ return;
+}
+
+/*
+ * Dump a cylinder summary.
+ */
+void
+dbg_dump_csum_total(const char *comment, struct csum_total *cs)
+{
+
+ if (!dbg_log)
+ return;
+
+ fprintf(dbg_log, "===== START CYLINDER SUMMARY TOTAL =====\n");
+ fprintf(dbg_log, "# %d@%lx: %s\n", indent, (unsigned long)cs, comment);
+ indent++;
+
+ fprintf(dbg_log, "ndir int64_t 0x%08x%08x\n",
+ ((unsigned int *)&(cs->cs_ndir))[1],
+ ((unsigned int *)&(cs->cs_ndir))[0]);
+ fprintf(dbg_log, "nbfree int64_t 0x%08x%08x\n",
+ ((unsigned int *)&(cs->cs_nbfree))[1],
+ ((unsigned int *)&(cs->cs_nbfree))[0]);
+ fprintf(dbg_log, "nifree int64_t 0x%08x%08x\n",
+ ((unsigned int *)&(cs->cs_nifree))[1],
+ ((unsigned int *)&(cs->cs_nifree))[0]);
+ fprintf(dbg_log, "nffree int64_t 0x%08x%08x\n",
+ ((unsigned int *)&(cs->cs_nffree))[1],
+ ((unsigned int *)&(cs->cs_nffree))[0]);
+ fprintf(dbg_log, "numclusters int64_t 0x%08x%08x\n",
+ ((unsigned int *)&(cs->cs_numclusters))[1],
+ ((unsigned int *)&(cs->cs_numclusters))[0]);
+
+ indent--;
+ fprintf(dbg_log, "===== END CYLINDER SUMMARY TOTAL =====\n");
+
+ return;
+}
+/*
+ * Dump the inode allocation map in one cylinder group.
+ */
+void
+dbg_dump_inmap(struct fs *sb, const char *comment, struct cg *cgr)
+{
+ int j,k,l,e;
+ unsigned char *cp;
+
+ if (!dbg_log)
+ return;
+
+ fprintf(dbg_log, "===== START INODE ALLOCATION MAP =====\n");
+ fprintf(dbg_log, "# %d@%lx: %s\n", indent, (unsigned long)cgr, comment);
+ indent++;
+
+ cp = (unsigned char *)cg_inosused(cgr);
+ e = sb->fs_ipg / 8;
+ for (j = 0; j < e; j += 32) {
+ fprintf(dbg_log, "%08x: ", j);
+ for (k = 0; k < 32; k += 8) {
+ if (j + k + 8 < e) {
+ fprintf(dbg_log,
+ "%02x%02x%02x%02x%02x%02x%02x%02x ",
+ cp[0], cp[1], cp[2], cp[3],
+ cp[4], cp[5], cp[6], cp[7]);
+ } else {
+ for (l = 0; (l < 8) && (j + k + l < e); l++) {
+ fprintf(dbg_log, "%02x", cp[l]);
+ }
+ }
+ cp += 8;
+ }
+ fprintf(dbg_log, "\n");
+ }
+
+ indent--;
+ fprintf(dbg_log, "===== END INODE ALLOCATION MAP =====\n");
+
+ return;
+}
+
+
+/*
+ * Dump the fragment allocation map in one cylinder group.
+ */
+void
+dbg_dump_frmap(struct fs *sb, const char *comment, struct cg *cgr)
+{
+ int j,k,l,e;
+ unsigned char *cp;
+
+ if (!dbg_log)
+ return;
+
+ fprintf(dbg_log, "===== START FRAGMENT ALLOCATION MAP =====\n");
+ fprintf(dbg_log, "# %d@%lx: %s\n", indent, (unsigned long)cgr, comment);
+ indent++;
+
+ cp = (unsigned char *)cg_blksfree(cgr);
+ if (sb->fs_old_nspf)
+ e = howmany((sb->fs_old_cpg * sb->fs_old_spc / sb->fs_old_nspf), CHAR_BIT);
+ else
+ e = 0;
+ for (j = 0; j < e; j += 32) {
+ fprintf(dbg_log, "%08x: ", j);
+ for (k = 0; k < 32; k += 8) {
+ if (j + k + 8 <e) {
+ fprintf(dbg_log,
+ "%02x%02x%02x%02x%02x%02x%02x%02x ",
+ cp[0], cp[1], cp[2], cp[3],
+ cp[4], cp[5], cp[6], cp[7]);
+ } else {
+ for (l = 0; (l < 8) && (j + k + l < e); l++) {
+ fprintf(dbg_log, "%02x", cp[l]);
+ }
+ }
+ cp += 8;
+ }
+ fprintf(dbg_log, "\n");
+ }
+
+ indent--;
+ fprintf(dbg_log, "===== END FRAGMENT ALLOCATION MAP =====\n");
+
+ return;
+}
+
+/*
+ * Dump the cluster allocation map in one cylinder group.
+ */
+void
+dbg_dump_clmap(struct fs *sb, const char *comment, struct cg *cgr)
+{
+ int j,k,l,e;
+ unsigned char *cp;
+
+ if (!dbg_log)
+ return;
+
+ fprintf(dbg_log, "===== START CLUSTER ALLOCATION MAP =====\n");
+ fprintf(dbg_log, "# %d@%lx: %s\n", indent, (unsigned long)cgr, comment);
+ indent++;
+
+ cp = (unsigned char *)cg_clustersfree(cgr);
+ if (sb->fs_old_nspf)
+ e = howmany(sb->fs_old_cpg * sb->fs_old_spc / (sb->fs_old_nspf << sb->fs_fragshift), CHAR_BIT);
+ else
+ e = 0;
+ for (j = 0; j < e; j += 32) {
+ fprintf(dbg_log, "%08x: ", j);
+ for (k = 0; k < 32; k += 8) {
+ if (j + k + 8 < e) {
+ fprintf(dbg_log,
+ "%02x%02x%02x%02x%02x%02x%02x%02x ",
+ cp[0], cp[1], cp[2], cp[3],
+ cp[4], cp[5], cp[6], cp[7]);
+ } else {
+ for (l = 0; (l < 8) && (j + k + l <e); l++) {
+ fprintf(dbg_log, "%02x", cp[l]);
+ }
+ }
+ cp += 8;
+ }
+ fprintf(dbg_log, "\n");
+ }
+
+ indent--;
+ fprintf(dbg_log, "===== END CLUSTER ALLOCATION MAP =====\n");
+
+ return;
+}
+
+/*
+ * Dump the cluster availability summary of one cylinder group.
+ */
+void
+dbg_dump_clsum(struct fs *sb, const char *comment, struct cg *cgr)
+{
+ int j;
+ int *ip;
+
+ if (!dbg_log)
+ return;
+
+ fprintf(dbg_log, "===== START CLUSTER SUMMARY =====\n");
+ fprintf(dbg_log, "# %d@%lx: %s\n", indent, (unsigned long)cgr, comment);
+ indent++;
+
+ ip = (int *)cg_clustersum(cgr);
+ for (j = 0; j <= sb->fs_contigsumsize; j++) {
+ fprintf(dbg_log, "%02d: %8d\n", j, *ip++);
+ }
+
+ indent--;
+ fprintf(dbg_log, "===== END CLUSTER SUMMARY =====\n");
+
+ return;
+}
+
+#ifdef NOT_CURRENTLY
+/*
+ * This code dates from before the UFS2 integration, and doesn't compile
+ * post-UFS2 due to the use of cg_blks(). I'm not sure how best to update
+ * this for UFS2, where the rotational bits of UFS no longer apply, so
+ * will leave it disabled for now; it should probably be re-enabled
+ * specifically for UFS1.
+ */
+/*
+ * Dump the block summary, and the rotational layout table.
+ */
+void
+dbg_dump_sptbl(struct fs *sb, const char *comment, struct cg *cgr)
+{
+ int j,k;
+ int *ip;
+
+ if (!dbg_log)
+ return;
+
+ fprintf(dbg_log,
+ "===== START BLOCK SUMMARY AND POSITION TABLE =====\n");
+ fprintf(dbg_log, "# %d@%lx: %s\n", indent, (unsigned long)cgr, comment);
+ indent++;
+
+ ip = (int *)cg_blktot(cgr);
+ for (j = 0; j < sb->fs_old_cpg; j++) {
+ fprintf(dbg_log, "%2d: %5d = ", j, *ip++);
+ for (k = 0; k < sb->fs_old_nrpos; k++) {
+ fprintf(dbg_log, "%4d", cg_blks(sb, cgr, j)[k]);
+ if (k < sb->fs_old_nrpos - 1)
+ fprintf(dbg_log, " + ");
+ }
+ fprintf(dbg_log, "\n");
+ }
+
+ indent--;
+ fprintf(dbg_log, "===== END BLOCK SUMMARY AND POSITION TABLE =====\n");
+
+ return;
+}
+#endif
+
+/*
+ * Dump a UFS1 inode structure.
+ */
+void
+dbg_dump_ufs1_ino(struct fs *sb, const char *comment, struct ufs1_dinode *ino)
+{
+ int ictr;
+ int remaining_blocks;
+
+ if (!dbg_log)
+ return;
+
+ fprintf(dbg_log, "===== START UFS1 INODE DUMP =====\n");
+ fprintf(dbg_log, "# %d@%lx: %s\n", indent, (unsigned long)ino, comment);
+ indent++;
+
+ fprintf(dbg_log, "mode u_int16_t 0%o\n", ino->di_mode);
+ fprintf(dbg_log, "nlink int16_t 0x%04x\n", ino->di_nlink);
+ fprintf(dbg_log, "size u_int64_t 0x%08x%08x\n",
+ ((unsigned int *)&(ino->di_size))[1],
+ ((unsigned int *)&(ino->di_size))[0]);
+ fprintf(dbg_log, "atime int32_t 0x%08x\n", ino->di_atime);
+ fprintf(dbg_log, "atimensec int32_t 0x%08x\n",
+ ino->di_atimensec);
+ fprintf(dbg_log, "mtime int32_t 0x%08x\n",
+ ino->di_mtime);
+ fprintf(dbg_log, "mtimensec int32_t 0x%08x\n",
+ ino->di_mtimensec);
+ fprintf(dbg_log, "ctime int32_t 0x%08x\n", ino->di_ctime);
+ fprintf(dbg_log, "ctimensec int32_t 0x%08x\n",
+ ino->di_ctimensec);
+
+ remaining_blocks = howmany(ino->di_size, sb->fs_bsize); /* XXX ts - +1? */
+ for (ictr = 0; ictr < MIN(NDADDR, remaining_blocks); ictr++) {
+ fprintf(dbg_log, "db ufs_daddr_t[%x] 0x%08x\n", ictr,
+ ino->di_db[ictr]);
+ }
+ remaining_blocks -= NDADDR;
+ if (remaining_blocks > 0) {
+ fprintf(dbg_log, "ib ufs_daddr_t[0] 0x%08x\n",
+ ino->di_ib[0]);
+ }
+ remaining_blocks -= howmany(sb->fs_bsize, sizeof(ufs1_daddr_t));
+ if (remaining_blocks > 0) {
+ fprintf(dbg_log, "ib ufs_daddr_t[1] 0x%08x\n",
+ ino->di_ib[1]);
+ }
+#define SQUARE(a) ((a) * (a))
+ remaining_blocks -= SQUARE(howmany(sb->fs_bsize, sizeof(ufs1_daddr_t)));
+#undef SQUARE
+ if (remaining_blocks > 0) {
+ fprintf(dbg_log, "ib ufs_daddr_t[2] 0x%08x\n",
+ ino->di_ib[2]);
+ }
+
+ fprintf(dbg_log, "flags u_int32_t 0x%08x\n", ino->di_flags);
+ fprintf(dbg_log, "blocks int32_t 0x%08x\n", ino->di_blocks);
+ fprintf(dbg_log, "gen int32_t 0x%08x\n", ino->di_gen);
+ fprintf(dbg_log, "uid u_int32_t 0x%08x\n", ino->di_uid);
+ fprintf(dbg_log, "gid u_int32_t 0x%08x\n", ino->di_gid);
+
+ indent--;
+ fprintf(dbg_log, "===== END UFS1 INODE DUMP =====\n");
+
+ return;
+}
+
+/*
+ * Dump a UFS2 inode structure.
+ */
+void
+dbg_dump_ufs2_ino(struct fs *sb, const char *comment, struct ufs2_dinode *ino)
+{
+ int ictr;
+ int remaining_blocks;
+
+ if (!dbg_log)
+ return;
+
+ fprintf(dbg_log, "===== START UFS2 INODE DUMP =====\n");
+ fprintf(dbg_log, "# %d@%lx: %s\n", indent, (unsigned long)ino, comment);
+ indent++;
+
+ fprintf(dbg_log, "mode u_int16_t 0%o\n", ino->di_mode);
+ fprintf(dbg_log, "nlink int16_t 0x%04x\n", ino->di_nlink);
+ fprintf(dbg_log, "uid u_int32_t 0x%08x\n", ino->di_uid);
+ fprintf(dbg_log, "gid u_int32_t 0x%08x\n", ino->di_gid);
+ fprintf(dbg_log, "blksize u_int32_t 0x%08x\n", ino->di_blksize);
+ fprintf(dbg_log, "size u_int64_t 0x%08x%08x\n",
+ ((unsigned int *)&(ino->di_size))[1],
+ ((unsigned int *)&(ino->di_size))[0]);
+ fprintf(dbg_log, "blocks u_int64_t 0x%08x%08x\n",
+ ((unsigned int *)&(ino->di_blocks))[1],
+ ((unsigned int *)&(ino->di_blocks))[0]);
+ fprintf(dbg_log, "atime ufs_time_t %10jd\n", ino->di_atime);
+ fprintf(dbg_log, "mtime ufs_time_t %10jd\n", ino->di_mtime);
+ fprintf(dbg_log, "ctime ufs_time_t %10jd\n", ino->di_ctime);
+ fprintf(dbg_log, "birthtime ufs_time_t %10jd\n", ino->di_birthtime);
+ fprintf(dbg_log, "mtimensec int32_t 0x%08x\n", ino->di_mtimensec);
+ fprintf(dbg_log, "atimensec int32_t 0x%08x\n", ino->di_atimensec);
+ fprintf(dbg_log, "ctimensec int32_t 0x%08x\n", ino->di_ctimensec);
+ fprintf(dbg_log, "birthnsec int32_t 0x%08x\n", ino->di_birthnsec);
+ fprintf(dbg_log, "gen int32_t 0x%08x\n", ino->di_gen);
+ fprintf(dbg_log, "kernflags u_int32_t 0x%08x\n", ino->di_kernflags);
+ fprintf(dbg_log, "flags u_int32_t 0x%08x\n", ino->di_flags);
+ fprintf(dbg_log, "extsize u_int32_t 0x%08x\n", ino->di_extsize);
+
+ /* XXX: What do we do with di_extb[NXADDR]? */
+
+ remaining_blocks = howmany(ino->di_size, sb->fs_bsize); /* XXX ts - +1? */
+ for (ictr = 0; ictr < MIN(NDADDR, remaining_blocks); ictr++) {
+ fprintf(dbg_log, "db ufs2_daddr_t[%x] 0x%16jx\n", ictr,
+ ino->di_db[ictr]);
+ }
+ remaining_blocks -= NDADDR;
+ if (remaining_blocks > 0) {
+ fprintf(dbg_log, "ib ufs2_daddr_t[0] 0x%16jx\n",
+ ino->di_ib[0]);
+ }
+ remaining_blocks -= howmany(sb->fs_bsize, sizeof(ufs2_daddr_t));
+ if (remaining_blocks > 0) {
+ fprintf(dbg_log, "ib ufs2_daddr_t[1] 0x%16jx\n",
+ ino->di_ib[1]);
+ }
+#define SQUARE(a) ((a) * (a))
+ remaining_blocks -= SQUARE(howmany(sb->fs_bsize, sizeof(ufs2_daddr_t)));
+#undef SQUARE
+ if (remaining_blocks > 0) {
+ fprintf(dbg_log, "ib ufs2_daddr_t[2] 0x%16jx\n",
+ ino->di_ib[2]);
+ }
+
+ indent--;
+ fprintf(dbg_log, "===== END UFS2 INODE DUMP =====\n");
+
+ return;
+}
+
+/*
+ * Dump an indirect block. The iteration to dump a full file has to be
+ * written around.
+ */
+void
+dbg_dump_iblk(struct fs *sb, const char *comment, char *block, size_t length)
+{
+ unsigned int *mem, i, j, size;
+
+ if (!dbg_log)
+ return;
+
+ fprintf(dbg_log, "===== START INDIRECT BLOCK DUMP =====\n");
+ fprintf(dbg_log, "# %d@%lx: %s\n", indent, (unsigned long)block,
+ comment);
+ indent++;
+
+ if (sb->fs_magic == FS_UFS1_MAGIC)
+ size = sizeof(ufs1_daddr_t);
+ else
+ size = sizeof(ufs2_daddr_t);
+
+ mem = (unsigned int *)block;
+ for (i = 0; (size_t)i < MIN(howmany(sb->fs_bsize, size), length);
+ i += 8) {
+ fprintf(dbg_log, "%04x: ", i);
+ for (j = 0; j < 8; j++) {
+ if ((size_t)(i + j) < length)
+ fprintf(dbg_log, "%08X ", *mem++);
+ }
+ fprintf(dbg_log, "\n");
+ }
+
+ indent--;
+ fprintf(dbg_log, "===== END INDIRECT BLOCK DUMP =====\n");
+
+ return;
+}
+
+#endif /* FS_DEBUG */
+
diff --git a/sbin/growfs/debug.h b/sbin/growfs/debug.h
new file mode 100644
index 0000000..3cad9d9
--- /dev/null
+++ b/sbin/growfs/debug.h
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2000 Christoph Herrmann, Thomas-Henning von Kamptz
+ * Copyright (c) 1980, 1989, 1993 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Christoph Herrmann and Thomas-Henning von Kamptz, Munich and Frankfurt.
+ *
+ * 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, as well as Christoph
+ * Herrmann and Thomas-Henning von Kamptz.
+ * 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.
+ *
+ * $TSHeader: src/sbin/growfs/debug.h,v 1.2 2000/11/16 18:43:50 tom Exp $
+ * $FreeBSD$
+ *
+ */
+
+#ifdef FS_DEBUG
+
+/* ********************************************************** INCLUDES ***** */
+#include <sys/param.h>
+
+#include <ufs/ufs/dinode.h>
+#include <ufs/ffs/fs.h>
+
+void dbg_open(const char *);
+void dbg_close(void);
+void dbg_dump_hex(struct fs *, const char *, unsigned char *);
+void dbg_dump_fs(struct fs *, const char *);
+void dbg_dump_cg(const char *, struct cg *);
+void dbg_dump_csum(const char *, struct csum *);
+void dbg_dump_csum_total(const char *, struct csum_total *);
+void dbg_dump_ufs1_ino(struct fs *, const char *, struct ufs1_dinode *);
+void dbg_dump_ufs2_ino(struct fs *, const char *, struct ufs2_dinode *);
+void dbg_dump_iblk(struct fs *, const char *, char *, size_t);
+void dbg_dump_inmap(struct fs *, const char *, struct cg *);
+void dbg_dump_frmap(struct fs *, const char *, struct cg *);
+void dbg_dump_clmap(struct fs *, const char *, struct cg *);
+void dbg_dump_clsum(struct fs *, const char *, struct cg *);
+void dbg_dump_sptbl(struct fs *, const char *, struct cg *);
+
+#define DBG_OPEN(P) dbg_open((P))
+#define DBG_CLOSE dbg_close()
+#define DBG_DUMP_HEX(F,C,M) dbg_dump_hex((F),(C),(M))
+#define DBG_DUMP_FS(F,C) dbg_dump_fs((F),(C))
+#define DBG_DUMP_CG(F,C,M) dbg_dump_cg((C),(M))
+#define DBG_DUMP_CSUM(F,C,M) dbg_dump_csum((C),(M))
+#define DBG_DUMP_INO(F,C,M) (F)->fs_magic == FS_UFS1_MAGIC \
+ ? dbg_dump_ufs1_ino((F),(C),(struct ufs1_dinode *)(M)) \
+ : dbg_dump_ufs2_ino((F),(C),(struct ufs2_dinode *)(M))
+#define DBG_DUMP_IBLK(F,C,M,L) dbg_dump_iblk((F),(C),(M),(L))
+#define DBG_DUMP_INMAP(F,C,M) dbg_dump_inmap((F),(C),(M))
+#define DBG_DUMP_FRMAP(F,C,M) dbg_dump_frmap((F),(C),(M))
+#define DBG_DUMP_CLMAP(F,C,M) dbg_dump_clmap((F),(C),(M))
+#define DBG_DUMP_CLSUM(F,C,M) dbg_dump_clsum((F),(C),(M))
+#ifdef NOT_CURRENTLY
+#define DBG_DUMP_SPTBL(F,C,M) dbg_dump_sptbl((F),(C),(M))
+#endif
+
+#define DL_TRC 0x01
+#define DL_INFO 0x02
+extern int _dbg_lvl_;
+
+#define DBG_FUNC(N) char __FKT__[] = {N};
+#define DBG_ENTER if(_dbg_lvl_ & DL_TRC) { \
+ fprintf(stderr, "~>%s: %s\n", __FILE__, __FKT__ ); \
+ }
+#define DBG_LEAVE if(_dbg_lvl_ & DL_TRC) { \
+ fprintf(stderr, "~<%s[%d]: %s\n", __FILE__, __LINE__, __FKT__ ); \
+ }
+#define DBG_TRC if(_dbg_lvl_ & DL_TRC) { \
+ fprintf(stderr, "~=%s[%d]: %s\n", __FILE__, __LINE__, __FKT__ ); \
+ }
+#define DBG_PRINT0(A) if(_dbg_lvl_ & DL_INFO) { \
+ fprintf(stderr, "~ %s", (A)); \
+ }
+#define DBG_PRINT1(A,B) if(_dbg_lvl_ & DL_INFO) { \
+ fprintf(stderr, "~ "); \
+ fprintf(stderr, (A), (B)); \
+ }
+#define DBG_PRINT2(A,B,C) if(_dbg_lvl_ & DL_INFO) { \
+ fprintf(stderr, "~ "); \
+ fprintf(stderr, (A), (B), (C)); \
+ }
+#define DBG_PRINT3(A,B,C,D) if(_dbg_lvl_ & DL_INFO) { \
+ fprintf(stderr, "~ "); \
+ fprintf(stderr, (A), (B), (C), (D)); \
+ }
+#define DBG_PRINT4(A,B,C,D,E) if(_dbg_lvl_ & DL_INFO) { \
+ fprintf(stderr, "~ "); \
+ fprintf(stderr, (A), (B), (C), (D), (E)); \
+ }
+#else /* not FS_DEBUG */
+
+#define DBG_OPEN(P)
+#define DBG_CLOSE
+#define DBG_DUMP_HEX(F,C,M)
+#define DBG_DUMP_FS(F,C)
+#define DBG_DUMP_CG(F,C,M)
+#define DBG_DUMP_CSUM(F,C,M)
+#define DBG_DUMP_INO(F,C,M)
+#define DBG_DUMP_IBLK(F,C,M,L)
+#define DBG_DUMP_INMAP(F,C,M)
+#define DBG_DUMP_FRMAP(F,C,M)
+#define DBG_DUMP_CLMAP(F,C,M)
+#define DBG_DUMP_CLSUM(F,C,M)
+#define DBG_DUMP_SPTBL(F,C,M)
+#define DBG_FUNC(N)
+#define DBG_ENTER
+#define DBG_TRC
+#define DBG_LEAVE
+#define DBG_PRINT0(A)
+#define DBG_PRINT1(A,B)
+#define DBG_PRINT2(A,B,C)
+#define DBG_PRINT3(A,B,C,D)
+#define DBG_PRINT4(A,B,C,D,E)
+
+#endif /* FS_DEBUG */
diff --git a/sbin/growfs/growfs.8 b/sbin/growfs/growfs.8
new file mode 100644
index 0000000..42dc530
--- /dev/null
+++ b/sbin/growfs/growfs.8
@@ -0,0 +1,138 @@
+.\" Copyright (c) 2000 Christoph Herrmann, Thomas-Henning von Kamptz
+.\" Copyright (c) 1980, 1989, 1993 The Regents of the University of California.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" Christoph Herrmann and Thomas-Henning von Kamptz, Munich and Frankfurt.
+.\"
+.\" 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, as well as Christoph
+.\" Herrmann and Thomas-Henning von Kamptz.
+.\" 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.
+.\"
+.\" $TSHeader: src/sbin/growfs/growfs.8,v 1.3 2000/12/12 19:31:00 tomsoft Exp $
+.\" $FreeBSD$
+.\"
+.Dd November 20, 2014
+.Dt GROWFS 8
+.Os
+.Sh NAME
+.Nm growfs
+.Nd expand an existing UFS file system
+.Sh SYNOPSIS
+.Nm
+.Op Fl Ny
+.Op Fl s Ar size
+.Ar special | filesystem
+.Sh DESCRIPTION
+The
+.Nm
+utility makes it possible to expand an UFS file system.
+Before running
+.Nm
+the partition or slice containing the file system must be extended using
+.Xr gpart 8 .
+If you are using volumes you must enlarge them by using
+.Xr gvinum 8 .
+The
+.Nm
+utility extends the size of the file system on the specified special file.
+The following options are available:
+.Bl -tag -width indent
+.It Fl N
+.Dq Test mode .
+Causes the new file system parameters to be printed out without actually
+enlarging the file system.
+.It Fl y
+.Dq Expert mode .
+Usually
+.Nm
+will ask you if you took a backup of your data before and will do some tests
+whether
+.Ar special
+is currently mounted or whether there are any active snapshots on the file
+system specified.
+This will be suppressed.
+So use this option with great care!
+.It Fl s Ar size
+Determines the
+.Ar size
+of the file system after enlarging in sectors.
+.Ar Size
+is the number of 512 byte sectors unless suffixed with a
+.Cm b , k , m , g ,
+or
+.Cm t
+which
+denotes byte, kilobyte, megabyte, gigabyte and terabyte respectively.
+This value defaults to the size of the raw partition specified in
+.Ar special
+(in other words,
+.Nm
+will enlarge the file system to the size of the entire partition).
+.El
+.Sh EXAMPLES
+Expand root file system to fill up available space:
+.Dl growfs /
+.Pp
+Resize
+.Pa /dev/ada0p1
+partition to 2GB and expand the file system:
+.Dl gpart resize -i 1 -s 2G ada0
+.Dl growfs -s 2G /dev/ada0p1
+.Sh SEE ALSO
+.Xr dumpfs 8 ,
+.Xr ffsinfo 8 ,
+.Xr fsck 8 ,
+.Xr fsdb 8 ,
+.Xr gpart 8 ,
+.Xr newfs 8 ,
+.Xr tunefs 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 4.4 .
+The ability to resize mounted file systems was added in
+.Fx 10.0 .
+.Sh AUTHORS
+.An Christoph Herrmann Aq Mt chm@FreeBSD.org
+.An Thomas-Henning von Kamptz Aq Mt tomsoft@FreeBSD.org
+.An The GROWFS team Aq Mt growfs@Tomsoft.COM
+.An Edward Tomasz Napierala Aq Mt trasz@FreeBSD.org
+.Sh CAVEATS
+When expanding a file system mounted read-write, any writes to that file system
+will be temporarily suspended until the expansion is finished.
+.Sh BUGS
+Normally
+.Nm
+writes cylinder group summary to disk and reads it again later for doing more
+updates.
+This read operation will provide unexpected data when using
+.Fl N .
+Therefore, this part cannot really be simulated and will be skipped in test
+mode.
diff --git a/sbin/growfs/growfs.c b/sbin/growfs/growfs.c
new file mode 100644
index 0000000..7b85b25
--- /dev/null
+++ b/sbin/growfs/growfs.c
@@ -0,0 +1,1742 @@
+/*
+ * Copyright (c) 1980, 1989, 1993 The Regents of the University of California.
+ * Copyright (c) 2000 Christoph Herrmann, Thomas-Henning von Kamptz
+ * Copyright (c) 2012 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Christoph Herrmann and Thomas-Henning von Kamptz, Munich and Frankfurt.
+ *
+ * Portions of this software were developed by Edward Tomasz Napierala
+ * under sponsorship from the FreeBSD Foundation.
+ *
+ * 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, as well as Christoph
+ * Herrmann and Thomas-Henning von Kamptz.
+ * 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.
+ *
+ * $TSHeader: src/sbin/growfs/growfs.c,v 1.5 2000/12/12 19:31:00 tomsoft Exp $
+ *
+ */
+
+#ifndef lint
+static const char copyright[] =
+"@(#) Copyright (c) 2000 Christoph Herrmann, Thomas-Henning von Kamptz\n\
+Copyright (c) 1980, 1989, 1993 The Regents of the University of California.\n\
+All rights reserved.\n";
+#endif /* not lint */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/disk.h>
+#include <sys/ucred.h>
+#include <sys/mount.h>
+
+#include <stdio.h>
+#include <paths.h>
+#include <ctype.h>
+#include <err.h>
+#include <fcntl.h>
+#include <fstab.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <mntopts.h>
+#include <paths.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <ufs/ufs/dinode.h>
+#include <ufs/ffs/fs.h>
+#include <libutil.h>
+
+#include "debug.h"
+
+#ifdef FS_DEBUG
+int _dbg_lvl_ = (DL_INFO); /* DL_TRC */
+#endif /* FS_DEBUG */
+
+static union {
+ struct fs fs;
+ char pad[SBLOCKSIZE];
+} fsun1, fsun2;
+#define sblock fsun1.fs /* the new superblock */
+#define osblock fsun2.fs /* the old superblock */
+
+/*
+ * Possible superblock locations ordered from most to least likely.
+ */
+static int sblock_try[] = SBLOCKSEARCH;
+static ufs2_daddr_t sblockloc;
+
+static union {
+ struct cg cg;
+ char pad[MAXBSIZE];
+} cgun1, cgun2;
+#define acg cgun1.cg /* a cylinder cgroup (new) */
+#define aocg cgun2.cg /* an old cylinder group */
+
+static struct csum *fscs; /* cylinder summary */
+
+static void growfs(int, int, unsigned int);
+static void rdfs(ufs2_daddr_t, size_t, void *, int);
+static void wtfs(ufs2_daddr_t, size_t, void *, int, unsigned int);
+static int charsperline(void);
+static void usage(void);
+static int isblock(struct fs *, unsigned char *, int);
+static void clrblock(struct fs *, unsigned char *, int);
+static void setblock(struct fs *, unsigned char *, int);
+static void initcg(int, time_t, int, unsigned int);
+static void updjcg(int, time_t, int, int, unsigned int);
+static void updcsloc(time_t, int, int, unsigned int);
+static void frag_adjust(ufs2_daddr_t, int);
+static void updclst(int);
+static void mount_reload(const struct statfs *stfs);
+
+/*
+ * Here we actually start growing the file system. We basically read the
+ * cylinder summary from the first cylinder group as we want to update
+ * this on the fly during our various operations. First we handle the
+ * changes in the former last cylinder group. Afterwards we create all new
+ * cylinder groups. Now we handle the cylinder group containing the
+ * cylinder summary which might result in a relocation of the whole
+ * structure. In the end we write back the updated cylinder summary, the
+ * new superblock, and slightly patched versions of the super block
+ * copies.
+ */
+static void
+growfs(int fsi, int fso, unsigned int Nflag)
+{
+ DBG_FUNC("growfs")
+ time_t modtime;
+ uint cylno;
+ int i, j, width;
+ char tmpbuf[100];
+
+ DBG_ENTER;
+
+ time(&modtime);
+
+ /*
+ * Get the cylinder summary into the memory.
+ */
+ fscs = (struct csum *)calloc((size_t)1, (size_t)sblock.fs_cssize);
+ if (fscs == NULL)
+ errx(1, "calloc failed");
+ for (i = 0; i < osblock.fs_cssize; i += osblock.fs_bsize) {
+ rdfs(fsbtodb(&osblock, osblock.fs_csaddr +
+ numfrags(&osblock, i)), (size_t)MIN(osblock.fs_cssize - i,
+ osblock.fs_bsize), (void *)(((char *)fscs) + i), fsi);
+ }
+
+#ifdef FS_DEBUG
+ {
+ struct csum *dbg_csp;
+ u_int32_t dbg_csc;
+ char dbg_line[80];
+
+ dbg_csp = fscs;
+
+ for (dbg_csc = 0; dbg_csc < osblock.fs_ncg; dbg_csc++) {
+ snprintf(dbg_line, sizeof(dbg_line),
+ "%d. old csum in old location", dbg_csc);
+ DBG_DUMP_CSUM(&osblock, dbg_line, dbg_csp++);
+ }
+ }
+#endif /* FS_DEBUG */
+ DBG_PRINT0("fscs read\n");
+
+ /*
+ * Do all needed changes in the former last cylinder group.
+ */
+ updjcg(osblock.fs_ncg - 1, modtime, fsi, fso, Nflag);
+
+ /*
+ * Dump out summary information about file system.
+ */
+#ifdef FS_DEBUG
+#define B2MBFACTOR (1 / (1024.0 * 1024.0))
+ printf("growfs: %.1fMB (%jd sectors) block size %d, fragment size %d\n",
+ (float)sblock.fs_size * sblock.fs_fsize * B2MBFACTOR,
+ (intmax_t)fsbtodb(&sblock, sblock.fs_size), sblock.fs_bsize,
+ sblock.fs_fsize);
+ printf("\tusing %d cylinder groups of %.2fMB, %d blks, %d inodes.\n",
+ sblock.fs_ncg, (float)sblock.fs_fpg * sblock.fs_fsize * B2MBFACTOR,
+ sblock.fs_fpg / sblock.fs_frag, sblock.fs_ipg);
+ if (sblock.fs_flags & FS_DOSOFTDEP)
+ printf("\twith soft updates\n");
+#undef B2MBFACTOR
+#endif /* FS_DEBUG */
+
+ /*
+ * Now build the cylinders group blocks and
+ * then print out indices of cylinder groups.
+ */
+ printf("super-block backups (for fsck_ffs -b #) at:\n");
+ i = 0;
+ width = charsperline();
+
+ /*
+ * Iterate for only the new cylinder groups.
+ */
+ for (cylno = osblock.fs_ncg; cylno < sblock.fs_ncg; cylno++) {
+ initcg(cylno, modtime, fso, Nflag);
+ j = sprintf(tmpbuf, " %jd%s",
+ (intmax_t)fsbtodb(&sblock, cgsblock(&sblock, cylno)),
+ cylno < (sblock.fs_ncg - 1) ? "," : "" );
+ if (i + j >= width) {
+ printf("\n");
+ i = 0;
+ }
+ i += j;
+ printf("%s", tmpbuf);
+ fflush(stdout);
+ }
+ printf("\n");
+
+ /*
+ * Do all needed changes in the first cylinder group.
+ * allocate blocks in new location
+ */
+ updcsloc(modtime, fsi, fso, Nflag);
+
+ /*
+ * Now write the cylinder summary back to disk.
+ */
+ for (i = 0; i < sblock.fs_cssize; i += sblock.fs_bsize) {
+ wtfs(fsbtodb(&sblock, sblock.fs_csaddr + numfrags(&sblock, i)),
+ (size_t)MIN(sblock.fs_cssize - i, sblock.fs_bsize),
+ (void *)(((char *)fscs) + i), fso, Nflag);
+ }
+ DBG_PRINT0("fscs written\n");
+
+#ifdef FS_DEBUG
+ {
+ struct csum *dbg_csp;
+ u_int32_t dbg_csc;
+ char dbg_line[80];
+
+ dbg_csp = fscs;
+ for (dbg_csc = 0; dbg_csc < sblock.fs_ncg; dbg_csc++) {
+ snprintf(dbg_line, sizeof(dbg_line),
+ "%d. new csum in new location", dbg_csc);
+ DBG_DUMP_CSUM(&sblock, dbg_line, dbg_csp++);
+ }
+ }
+#endif /* FS_DEBUG */
+
+ /*
+ * Now write the new superblock back to disk.
+ */
+ sblock.fs_time = modtime;
+ wtfs(sblockloc, (size_t)SBLOCKSIZE, (void *)&sblock, fso, Nflag);
+ DBG_PRINT0("sblock written\n");
+ DBG_DUMP_FS(&sblock, "new initial sblock");
+
+ /*
+ * Clean up the dynamic fields in our superblock copies.
+ */
+ sblock.fs_fmod = 0;
+ sblock.fs_clean = 1;
+ sblock.fs_ronly = 0;
+ sblock.fs_cgrotor = 0;
+ sblock.fs_state = 0;
+ memset((void *)&sblock.fs_fsmnt, 0, sizeof(sblock.fs_fsmnt));
+ sblock.fs_flags &= FS_DOSOFTDEP;
+
+ /*
+ * XXX
+ * The following fields are currently distributed from the superblock
+ * to the copies:
+ * fs_minfree
+ * fs_rotdelay
+ * fs_maxcontig
+ * fs_maxbpg
+ * fs_minfree,
+ * fs_optim
+ * fs_flags regarding SOFTPDATES
+ *
+ * We probably should rather change the summary for the cylinder group
+ * statistics here to the value of what would be in there, if the file
+ * system were created initially with the new size. Therefor we still
+ * need to find an easy way of calculating that.
+ * Possibly we can try to read the first superblock copy and apply the
+ * "diffed" stats between the old and new superblock by still copying
+ * certain parameters onto that.
+ */
+
+ /*
+ * Write out the duplicate super blocks.
+ */
+ for (cylno = 0; cylno < sblock.fs_ncg; cylno++) {
+ wtfs(fsbtodb(&sblock, cgsblock(&sblock, cylno)),
+ (size_t)SBLOCKSIZE, (void *)&sblock, fso, Nflag);
+ }
+ DBG_PRINT0("sblock copies written\n");
+ DBG_DUMP_FS(&sblock, "new other sblocks");
+
+ DBG_LEAVE;
+ return;
+}
+
+/*
+ * This creates a new cylinder group structure, for more details please see
+ * the source of newfs(8), as this function is taken over almost unchanged.
+ * As this is never called for the first cylinder group, the special
+ * provisions for that case are removed here.
+ */
+static void
+initcg(int cylno, time_t modtime, int fso, unsigned int Nflag)
+{
+ DBG_FUNC("initcg")
+ static caddr_t iobuf;
+ long blkno, start;
+ ino_t ino;
+ ufs2_daddr_t i, cbase, dmax;
+ struct ufs1_dinode *dp1;
+ struct csum *cs;
+ uint j, d, dupper, dlower;
+
+ if (iobuf == NULL && (iobuf = malloc(sblock.fs_bsize * 3)) == NULL)
+ errx(37, "panic: cannot allocate I/O buffer");
+
+ /*
+ * Determine block bounds for cylinder group.
+ * Allow space for super block summary information in first
+ * cylinder group.
+ */
+ cbase = cgbase(&sblock, cylno);
+ dmax = cbase + sblock.fs_fpg;
+ if (dmax > sblock.fs_size)
+ dmax = sblock.fs_size;
+ dlower = cgsblock(&sblock, cylno) - cbase;
+ dupper = cgdmin(&sblock, cylno) - cbase;
+ if (cylno == 0) /* XXX fscs may be relocated */
+ dupper += howmany(sblock.fs_cssize, sblock.fs_fsize);
+ cs = &fscs[cylno];
+ memset(&acg, 0, sblock.fs_cgsize);
+ acg.cg_time = modtime;
+ acg.cg_magic = CG_MAGIC;
+ acg.cg_cgx = cylno;
+ acg.cg_niblk = sblock.fs_ipg;
+ acg.cg_initediblk = sblock.fs_ipg < 2 * INOPB(&sblock) ?
+ sblock.fs_ipg : 2 * INOPB(&sblock);
+ acg.cg_ndblk = dmax - cbase;
+ if (sblock.fs_contigsumsize > 0)
+ acg.cg_nclusterblks = acg.cg_ndblk / sblock.fs_frag;
+ start = &acg.cg_space[0] - (u_char *)(&acg.cg_firstfield);
+ if (sblock.fs_magic == FS_UFS2_MAGIC) {
+ acg.cg_iusedoff = start;
+ } else {
+ acg.cg_old_ncyl = sblock.fs_old_cpg;
+ acg.cg_old_time = acg.cg_time;
+ acg.cg_time = 0;
+ acg.cg_old_niblk = acg.cg_niblk;
+ acg.cg_niblk = 0;
+ acg.cg_initediblk = 0;
+ acg.cg_old_btotoff = start;
+ acg.cg_old_boff = acg.cg_old_btotoff +
+ sblock.fs_old_cpg * sizeof(int32_t);
+ acg.cg_iusedoff = acg.cg_old_boff +
+ sblock.fs_old_cpg * sizeof(u_int16_t);
+ }
+ acg.cg_freeoff = acg.cg_iusedoff + howmany(sblock.fs_ipg, CHAR_BIT);
+ acg.cg_nextfreeoff = acg.cg_freeoff + howmany(sblock.fs_fpg, CHAR_BIT);
+ if (sblock.fs_contigsumsize > 0) {
+ acg.cg_clustersumoff =
+ roundup(acg.cg_nextfreeoff, sizeof(u_int32_t));
+ acg.cg_clustersumoff -= sizeof(u_int32_t);
+ acg.cg_clusteroff = acg.cg_clustersumoff +
+ (sblock.fs_contigsumsize + 1) * sizeof(u_int32_t);
+ acg.cg_nextfreeoff = acg.cg_clusteroff +
+ howmany(fragstoblks(&sblock, sblock.fs_fpg), CHAR_BIT);
+ }
+ if (acg.cg_nextfreeoff > (unsigned)sblock.fs_cgsize) {
+ /*
+ * This should never happen as we would have had that panic
+ * already on file system creation
+ */
+ errx(37, "panic: cylinder group too big");
+ }
+ acg.cg_cs.cs_nifree += sblock.fs_ipg;
+ if (cylno == 0)
+ for (ino = 0; ino < ROOTINO; ino++) {
+ setbit(cg_inosused(&acg), ino);
+ acg.cg_cs.cs_nifree--;
+ }
+ /*
+ * For the old file system, we have to initialize all the inodes.
+ */
+ if (sblock.fs_magic == FS_UFS1_MAGIC) {
+ bzero(iobuf, sblock.fs_bsize);
+ for (i = 0; i < sblock.fs_ipg / INOPF(&sblock);
+ i += sblock.fs_frag) {
+ dp1 = (struct ufs1_dinode *)(void *)iobuf;
+ for (j = 0; j < INOPB(&sblock); j++) {
+ dp1->di_gen = arc4random();
+ dp1++;
+ }
+ wtfs(fsbtodb(&sblock, cgimin(&sblock, cylno) + i),
+ sblock.fs_bsize, iobuf, fso, Nflag);
+ }
+ }
+ if (cylno > 0) {
+ /*
+ * In cylno 0, beginning space is reserved
+ * for boot and super blocks.
+ */
+ for (d = 0; d < dlower; d += sblock.fs_frag) {
+ blkno = d / sblock.fs_frag;
+ setblock(&sblock, cg_blksfree(&acg), blkno);
+ if (sblock.fs_contigsumsize > 0)
+ setbit(cg_clustersfree(&acg), blkno);
+ acg.cg_cs.cs_nbfree++;
+ }
+ sblock.fs_dsize += dlower;
+ }
+ sblock.fs_dsize += acg.cg_ndblk - dupper;
+ if ((i = dupper % sblock.fs_frag)) {
+ acg.cg_frsum[sblock.fs_frag - i]++;
+ for (d = dupper + sblock.fs_frag - i; dupper < d; dupper++) {
+ setbit(cg_blksfree(&acg), dupper);
+ acg.cg_cs.cs_nffree++;
+ }
+ }
+ for (d = dupper; d + sblock.fs_frag <= acg.cg_ndblk;
+ d += sblock.fs_frag) {
+ blkno = d / sblock.fs_frag;
+ setblock(&sblock, cg_blksfree(&acg), blkno);
+ if (sblock.fs_contigsumsize > 0)
+ setbit(cg_clustersfree(&acg), blkno);
+ acg.cg_cs.cs_nbfree++;
+ }
+ if (d < acg.cg_ndblk) {
+ acg.cg_frsum[acg.cg_ndblk - d]++;
+ for (; d < acg.cg_ndblk; d++) {
+ setbit(cg_blksfree(&acg), d);
+ acg.cg_cs.cs_nffree++;
+ }
+ }
+ if (sblock.fs_contigsumsize > 0) {
+ int32_t *sump = cg_clustersum(&acg);
+ u_char *mapp = cg_clustersfree(&acg);
+ int map = *mapp++;
+ int bit = 1;
+ int run = 0;
+
+ for (i = 0; i < acg.cg_nclusterblks; i++) {
+ if ((map & bit) != 0)
+ run++;
+ else if (run != 0) {
+ if (run > sblock.fs_contigsumsize)
+ run = sblock.fs_contigsumsize;
+ sump[run]++;
+ run = 0;
+ }
+ if ((i & (CHAR_BIT - 1)) != CHAR_BIT - 1)
+ bit <<= 1;
+ else {
+ map = *mapp++;
+ bit = 1;
+ }
+ }
+ if (run != 0) {
+ if (run > sblock.fs_contigsumsize)
+ run = sblock.fs_contigsumsize;
+ sump[run]++;
+ }
+ }
+ sblock.fs_cstotal.cs_ndir += acg.cg_cs.cs_ndir;
+ sblock.fs_cstotal.cs_nffree += acg.cg_cs.cs_nffree;
+ sblock.fs_cstotal.cs_nbfree += acg.cg_cs.cs_nbfree;
+ sblock.fs_cstotal.cs_nifree += acg.cg_cs.cs_nifree;
+ *cs = acg.cg_cs;
+
+ memcpy(iobuf, &acg, sblock.fs_cgsize);
+ memset(iobuf + sblock.fs_cgsize, '\0',
+ sblock.fs_bsize * 3 - sblock.fs_cgsize);
+
+ wtfs(fsbtodb(&sblock, cgtod(&sblock, cylno)),
+ sblock.fs_bsize * 3, iobuf, fso, Nflag);
+ DBG_DUMP_CG(&sblock, "new cg", &acg);
+
+ DBG_LEAVE;
+ return;
+}
+
+/*
+ * Here we add or subtract (sign +1/-1) the available fragments in a given
+ * block to or from the fragment statistics. By subtracting before and adding
+ * after an operation on the free frag map we can easy update the fragment
+ * statistic, which seems to be otherwise a rather complex operation.
+ */
+static void
+frag_adjust(ufs2_daddr_t frag, int sign)
+{
+ DBG_FUNC("frag_adjust")
+ int fragsize;
+ int f;
+
+ DBG_ENTER;
+
+ fragsize = 0;
+ /*
+ * Here frag only needs to point to any fragment in the block we want
+ * to examine.
+ */
+ for (f = rounddown(frag, sblock.fs_frag);
+ f < roundup(frag + 1, sblock.fs_frag); f++) {
+ /*
+ * Count contiguous free fragments.
+ */
+ if (isset(cg_blksfree(&acg), f)) {
+ fragsize++;
+ } else {
+ if (fragsize && fragsize < sblock.fs_frag) {
+ /*
+ * We found something in between.
+ */
+ acg.cg_frsum[fragsize] += sign;
+ DBG_PRINT2("frag_adjust [%d]+=%d\n",
+ fragsize, sign);
+ }
+ fragsize = 0;
+ }
+ }
+ if (fragsize && fragsize < sblock.fs_frag) {
+ /*
+ * We found something.
+ */
+ acg.cg_frsum[fragsize] += sign;
+ DBG_PRINT2("frag_adjust [%d]+=%d\n", fragsize, sign);
+ }
+ DBG_PRINT2("frag_adjust [[%d]]+=%d\n", fragsize, sign);
+
+ DBG_LEAVE;
+ return;
+}
+
+/*
+ * Here we do all needed work for the former last cylinder group. It has to be
+ * changed in any case, even if the file system ended exactly on the end of
+ * this group, as there is some slightly inconsistent handling of the number
+ * of cylinders in the cylinder group. We start again by reading the cylinder
+ * group from disk. If the last block was not fully available, we first handle
+ * the missing fragments, then we handle all new full blocks in that file
+ * system and finally we handle the new last fragmented block in the file
+ * system. We again have to handle the fragment statistics rotational layout
+ * tables and cluster summary during all those operations.
+ */
+static void
+updjcg(int cylno, time_t modtime, int fsi, int fso, unsigned int Nflag)
+{
+ DBG_FUNC("updjcg")
+ ufs2_daddr_t cbase, dmax, dupper;
+ struct csum *cs;
+ int i, k;
+ int j = 0;
+
+ DBG_ENTER;
+
+ /*
+ * Read the former last (joining) cylinder group from disk, and make
+ * a copy.
+ */
+ rdfs(fsbtodb(&osblock, cgtod(&osblock, cylno)),
+ (size_t)osblock.fs_cgsize, (void *)&aocg, fsi);
+ DBG_PRINT0("jcg read\n");
+ DBG_DUMP_CG(&sblock, "old joining cg", &aocg);
+
+ memcpy((void *)&cgun1, (void *)&cgun2, sizeof(cgun2));
+
+ /*
+ * If the cylinder group had already its new final size almost
+ * nothing is to be done ... except:
+ * For some reason the value of cg_ncyl in the last cylinder group has
+ * to be zero instead of fs_cpg. As this is now no longer the last
+ * cylinder group we have to change that value now to fs_cpg.
+ */
+
+ if (cgbase(&osblock, cylno + 1) == osblock.fs_size) {
+ if (sblock.fs_magic == FS_UFS1_MAGIC)
+ acg.cg_old_ncyl = sblock.fs_old_cpg;
+
+ wtfs(fsbtodb(&sblock, cgtod(&sblock, cylno)),
+ (size_t)sblock.fs_cgsize, (void *)&acg, fso, Nflag);
+ DBG_PRINT0("jcg written\n");
+ DBG_DUMP_CG(&sblock, "new joining cg", &acg);
+
+ DBG_LEAVE;
+ return;
+ }
+
+ /*
+ * Set up some variables needed later.
+ */
+ cbase = cgbase(&sblock, cylno);
+ dmax = cbase + sblock.fs_fpg;
+ if (dmax > sblock.fs_size)
+ dmax = sblock.fs_size;
+ dupper = cgdmin(&sblock, cylno) - cbase;
+ if (cylno == 0) /* XXX fscs may be relocated */
+ dupper += howmany(sblock.fs_cssize, sblock.fs_fsize);
+
+ /*
+ * Set pointer to the cylinder summary for our cylinder group.
+ */
+ cs = fscs + cylno;
+
+ /*
+ * Touch the cylinder group, update all fields in the cylinder group as
+ * needed, update the free space in the superblock.
+ */
+ acg.cg_time = modtime;
+ if ((unsigned)cylno == sblock.fs_ncg - 1) {
+ /*
+ * This is still the last cylinder group.
+ */
+ if (sblock.fs_magic == FS_UFS1_MAGIC)
+ acg.cg_old_ncyl =
+ sblock.fs_old_ncyl % sblock.fs_old_cpg;
+ } else {
+ acg.cg_old_ncyl = sblock.fs_old_cpg;
+ }
+ DBG_PRINT2("jcg dbg: %d %u", cylno, sblock.fs_ncg);
+#ifdef FS_DEBUG
+ if (sblock.fs_magic == FS_UFS1_MAGIC)
+ DBG_PRINT2("%d %u", acg.cg_old_ncyl, sblock.fs_old_cpg);
+#endif
+ DBG_PRINT0("\n");
+ acg.cg_ndblk = dmax - cbase;
+ sblock.fs_dsize += acg.cg_ndblk - aocg.cg_ndblk;
+ if (sblock.fs_contigsumsize > 0)
+ acg.cg_nclusterblks = acg.cg_ndblk / sblock.fs_frag;
+
+ /*
+ * Now we have to update the free fragment bitmap for our new free
+ * space. There again we have to handle the fragmentation and also
+ * the rotational layout tables and the cluster summary. This is
+ * also done per fragment for the first new block if the old file
+ * system end was not on a block boundary, per fragment for the new
+ * last block if the new file system end is not on a block boundary,
+ * and per block for all space in between.
+ *
+ * Handle the first new block here if it was partially available
+ * before.
+ */
+ if (osblock.fs_size % sblock.fs_frag) {
+ if (roundup(osblock.fs_size, sblock.fs_frag) <=
+ sblock.fs_size) {
+ /*
+ * The new space is enough to fill at least this
+ * block
+ */
+ j = 0;
+ for (i = roundup(osblock.fs_size - cbase,
+ sblock.fs_frag) - 1; i >= osblock.fs_size - cbase;
+ i--) {
+ setbit(cg_blksfree(&acg), i);
+ acg.cg_cs.cs_nffree++;
+ j++;
+ }
+
+ /*
+ * Check if the fragment just created could join an
+ * already existing fragment at the former end of the
+ * file system.
+ */
+ if (isblock(&sblock, cg_blksfree(&acg),
+ ((osblock.fs_size - cgbase(&sblock, cylno)) /
+ sblock.fs_frag))) {
+ /*
+ * The block is now completely available.
+ */
+ DBG_PRINT0("block was\n");
+ acg.cg_frsum[osblock.fs_size % sblock.fs_frag]--;
+ acg.cg_cs.cs_nbfree++;
+ acg.cg_cs.cs_nffree -= sblock.fs_frag;
+ k = rounddown(osblock.fs_size - cbase,
+ sblock.fs_frag);
+ updclst((osblock.fs_size - cbase) /
+ sblock.fs_frag);
+ } else {
+ /*
+ * Lets rejoin a possible partially growed
+ * fragment.
+ */
+ k = 0;
+ while (isset(cg_blksfree(&acg), i) &&
+ (i >= rounddown(osblock.fs_size - cbase,
+ sblock.fs_frag))) {
+ i--;
+ k++;
+ }
+ if (k)
+ acg.cg_frsum[k]--;
+ acg.cg_frsum[k + j]++;
+ }
+ } else {
+ /*
+ * We only grow by some fragments within this last
+ * block.
+ */
+ for (i = sblock.fs_size - cbase - 1;
+ i >= osblock.fs_size - cbase; i--) {
+ setbit(cg_blksfree(&acg), i);
+ acg.cg_cs.cs_nffree++;
+ j++;
+ }
+ /*
+ * Lets rejoin a possible partially growed fragment.
+ */
+ k = 0;
+ while (isset(cg_blksfree(&acg), i) &&
+ (i >= rounddown(osblock.fs_size - cbase,
+ sblock.fs_frag))) {
+ i--;
+ k++;
+ }
+ if (k)
+ acg.cg_frsum[k]--;
+ acg.cg_frsum[k + j]++;
+ }
+ }
+
+ /*
+ * Handle all new complete blocks here.
+ */
+ for (i = roundup(osblock.fs_size - cbase, sblock.fs_frag);
+ i + sblock.fs_frag <= dmax - cbase; /* XXX <= or only < ? */
+ i += sblock.fs_frag) {
+ j = i / sblock.fs_frag;
+ setblock(&sblock, cg_blksfree(&acg), j);
+ updclst(j);
+ acg.cg_cs.cs_nbfree++;
+ }
+
+ /*
+ * Handle the last new block if there are stll some new fragments left.
+ * Here we don't have to bother about the cluster summary or the even
+ * the rotational layout table.
+ */
+ if (i < (dmax - cbase)) {
+ acg.cg_frsum[dmax - cbase - i]++;
+ for (; i < dmax - cbase; i++) {
+ setbit(cg_blksfree(&acg), i);
+ acg.cg_cs.cs_nffree++;
+ }
+ }
+
+ sblock.fs_cstotal.cs_nffree +=
+ (acg.cg_cs.cs_nffree - aocg.cg_cs.cs_nffree);
+ sblock.fs_cstotal.cs_nbfree +=
+ (acg.cg_cs.cs_nbfree - aocg.cg_cs.cs_nbfree);
+ /*
+ * The following statistics are not changed here:
+ * sblock.fs_cstotal.cs_ndir
+ * sblock.fs_cstotal.cs_nifree
+ * As the statistics for this cylinder group are ready, copy it to
+ * the summary information array.
+ */
+ *cs = acg.cg_cs;
+
+ /*
+ * Write the updated "joining" cylinder group back to disk.
+ */
+ wtfs(fsbtodb(&sblock, cgtod(&sblock, cylno)), (size_t)sblock.fs_cgsize,
+ (void *)&acg, fso, Nflag);
+ DBG_PRINT0("jcg written\n");
+ DBG_DUMP_CG(&sblock, "new joining cg", &acg);
+
+ DBG_LEAVE;
+ return;
+}
+
+/*
+ * Here we update the location of the cylinder summary. We have two possible
+ * ways of growing the cylinder summary:
+ * (1) We can try to grow the summary in the current location, and relocate
+ * possibly used blocks within the current cylinder group.
+ * (2) Alternatively we can relocate the whole cylinder summary to the first
+ * new completely empty cylinder group. Once the cylinder summary is no
+ * longer in the beginning of the first cylinder group you should never
+ * use a version of fsck which is not aware of the possibility to have
+ * this structure in a non standard place.
+ * Option (2) is considered to be less intrusive to the structure of the file-
+ * system, so that's the one being used.
+ */
+static void
+updcsloc(time_t modtime, int fsi, int fso, unsigned int Nflag)
+{
+ DBG_FUNC("updcsloc")
+ struct csum *cs;
+ int ocscg, ncscg;
+ ufs2_daddr_t d;
+ int lcs = 0;
+ int block;
+
+ DBG_ENTER;
+
+ if (howmany(sblock.fs_cssize, sblock.fs_fsize) ==
+ howmany(osblock.fs_cssize, osblock.fs_fsize)) {
+ /*
+ * No new fragment needed.
+ */
+ DBG_LEAVE;
+ return;
+ }
+ ocscg = dtog(&osblock, osblock.fs_csaddr);
+ cs = fscs + ocscg;
+
+ /*
+ * Read original cylinder group from disk, and make a copy.
+ * XXX If Nflag is set in some very rare cases we now miss
+ * some changes done in updjcg by reading the unmodified
+ * block from disk.
+ */
+ rdfs(fsbtodb(&osblock, cgtod(&osblock, ocscg)),
+ (size_t)osblock.fs_cgsize, (void *)&aocg, fsi);
+ DBG_PRINT0("oscg read\n");
+ DBG_DUMP_CG(&sblock, "old summary cg", &aocg);
+
+ memcpy((void *)&cgun1, (void *)&cgun2, sizeof(cgun2));
+
+ /*
+ * Touch the cylinder group, set up local variables needed later
+ * and update the superblock.
+ */
+ acg.cg_time = modtime;
+
+ /*
+ * XXX In the case of having active snapshots we may need much more
+ * blocks for the copy on write. We need each block twice, and
+ * also up to 8*3 blocks for indirect blocks for all possible
+ * references.
+ */
+ /*
+ * There is not enough space in the old cylinder group to
+ * relocate all blocks as needed, so we relocate the whole
+ * cylinder group summary to a new group. We try to use the
+ * first complete new cylinder group just created. Within the
+ * cylinder group we align the area immediately after the
+ * cylinder group information location in order to be as
+ * close as possible to the original implementation of ffs.
+ *
+ * First we have to make sure we'll find enough space in the
+ * new cylinder group. If not, then we currently give up.
+ * We start with freeing everything which was used by the
+ * fragments of the old cylinder summary in the current group.
+ * Now we write back the group meta data, read in the needed
+ * meta data from the new cylinder group, and start allocating
+ * within that group. Here we can assume, the group to be
+ * completely empty. Which makes the handling of fragments and
+ * clusters a lot easier.
+ */
+ DBG_TRC;
+ if (sblock.fs_ncg - osblock.fs_ncg < 2)
+ errx(2, "panic: not enough space");
+
+ /*
+ * Point "d" to the first fragment not used by the cylinder
+ * summary.
+ */
+ d = osblock.fs_csaddr + (osblock.fs_cssize / osblock.fs_fsize);
+
+ /*
+ * Set up last cluster size ("lcs") already here. Calculate
+ * the size for the trailing cluster just behind where "d"
+ * points to.
+ */
+ if (sblock.fs_contigsumsize > 0) {
+ for (block = howmany(d % sblock.fs_fpg, sblock.fs_frag),
+ lcs = 0; lcs < sblock.fs_contigsumsize; block++, lcs++) {
+ if (isclr(cg_clustersfree(&acg), block))
+ break;
+ }
+ }
+
+ /*
+ * Point "d" to the last frag used by the cylinder summary.
+ */
+ d--;
+
+ DBG_PRINT1("d=%jd\n", (intmax_t)d);
+ if ((d + 1) % sblock.fs_frag) {
+ /*
+ * The end of the cylinder summary is not a complete
+ * block.
+ */
+ DBG_TRC;
+ frag_adjust(d % sblock.fs_fpg, -1);
+ for (; (d + 1) % sblock.fs_frag; d--) {
+ DBG_PRINT1("d=%jd\n", (intmax_t)d);
+ setbit(cg_blksfree(&acg), d % sblock.fs_fpg);
+ acg.cg_cs.cs_nffree++;
+ sblock.fs_cstotal.cs_nffree++;
+ }
+ /*
+ * Point "d" to the last fragment of the last
+ * (incomplete) block of the cylinder summary.
+ */
+ d++;
+ frag_adjust(d % sblock.fs_fpg, 1);
+
+ if (isblock(&sblock, cg_blksfree(&acg),
+ (d % sblock.fs_fpg) / sblock.fs_frag)) {
+ DBG_PRINT1("d=%jd\n", (intmax_t)d);
+ acg.cg_cs.cs_nffree -= sblock.fs_frag;
+ acg.cg_cs.cs_nbfree++;
+ sblock.fs_cstotal.cs_nffree -= sblock.fs_frag;
+ sblock.fs_cstotal.cs_nbfree++;
+ if (sblock.fs_contigsumsize > 0) {
+ setbit(cg_clustersfree(&acg),
+ (d % sblock.fs_fpg) / sblock.fs_frag);
+ if (lcs < sblock.fs_contigsumsize) {
+ if (lcs)
+ cg_clustersum(&acg)[lcs]--;
+ lcs++;
+ cg_clustersum(&acg)[lcs]++;
+ }
+ }
+ }
+ /*
+ * Point "d" to the first fragment of the block before
+ * the last incomplete block.
+ */
+ d--;
+ }
+
+ DBG_PRINT1("d=%jd\n", (intmax_t)d);
+ for (d = rounddown(d, sblock.fs_frag); d >= osblock.fs_csaddr;
+ d -= sblock.fs_frag) {
+ DBG_TRC;
+ DBG_PRINT1("d=%jd\n", (intmax_t)d);
+ setblock(&sblock, cg_blksfree(&acg),
+ (d % sblock.fs_fpg) / sblock.fs_frag);
+ acg.cg_cs.cs_nbfree++;
+ sblock.fs_cstotal.cs_nbfree++;
+ if (sblock.fs_contigsumsize > 0) {
+ setbit(cg_clustersfree(&acg),
+ (d % sblock.fs_fpg) / sblock.fs_frag);
+ /*
+ * The last cluster size is already set up.
+ */
+ if (lcs < sblock.fs_contigsumsize) {
+ if (lcs)
+ cg_clustersum(&acg)[lcs]--;
+ lcs++;
+ cg_clustersum(&acg)[lcs]++;
+ }
+ }
+ }
+ *cs = acg.cg_cs;
+
+ /*
+ * Now write the former cylinder group containing the cylinder
+ * summary back to disk.
+ */
+ wtfs(fsbtodb(&sblock, cgtod(&sblock, ocscg)),
+ (size_t)sblock.fs_cgsize, (void *)&acg, fso, Nflag);
+ DBG_PRINT0("oscg written\n");
+ DBG_DUMP_CG(&sblock, "old summary cg", &acg);
+
+ /*
+ * Find the beginning of the new cylinder group containing the
+ * cylinder summary.
+ */
+ sblock.fs_csaddr = cgdmin(&sblock, osblock.fs_ncg);
+ ncscg = dtog(&sblock, sblock.fs_csaddr);
+ cs = fscs + ncscg;
+
+ /*
+ * If Nflag is specified, we would now read random data instead
+ * of an empty cg structure from disk. So we can't simulate that
+ * part for now.
+ */
+ if (Nflag) {
+ DBG_PRINT0("nscg update skipped\n");
+ DBG_LEAVE;
+ return;
+ }
+
+ /*
+ * Read the future cylinder group containing the cylinder
+ * summary from disk, and make a copy.
+ */
+ rdfs(fsbtodb(&sblock, cgtod(&sblock, ncscg)),
+ (size_t)sblock.fs_cgsize, (void *)&aocg, fsi);
+ DBG_PRINT0("nscg read\n");
+ DBG_DUMP_CG(&sblock, "new summary cg", &aocg);
+
+ memcpy((void *)&cgun1, (void *)&cgun2, sizeof(cgun2));
+
+ /*
+ * Allocate all complete blocks used by the new cylinder
+ * summary.
+ */
+ for (d = sblock.fs_csaddr; d + sblock.fs_frag <=
+ sblock.fs_csaddr + (sblock.fs_cssize / sblock.fs_fsize);
+ d += sblock.fs_frag) {
+ clrblock(&sblock, cg_blksfree(&acg),
+ (d % sblock.fs_fpg) / sblock.fs_frag);
+ acg.cg_cs.cs_nbfree--;
+ sblock.fs_cstotal.cs_nbfree--;
+ if (sblock.fs_contigsumsize > 0) {
+ clrbit(cg_clustersfree(&acg),
+ (d % sblock.fs_fpg) / sblock.fs_frag);
+ }
+ }
+
+ /*
+ * Allocate all fragments used by the cylinder summary in the
+ * last block.
+ */
+ if (d < sblock.fs_csaddr + (sblock.fs_cssize / sblock.fs_fsize)) {
+ for (; d - sblock.fs_csaddr <
+ sblock.fs_cssize/sblock.fs_fsize; d++) {
+ clrbit(cg_blksfree(&acg), d % sblock.fs_fpg);
+ acg.cg_cs.cs_nffree--;
+ sblock.fs_cstotal.cs_nffree--;
+ }
+ acg.cg_cs.cs_nbfree--;
+ acg.cg_cs.cs_nffree += sblock.fs_frag;
+ sblock.fs_cstotal.cs_nbfree--;
+ sblock.fs_cstotal.cs_nffree += sblock.fs_frag;
+ if (sblock.fs_contigsumsize > 0)
+ clrbit(cg_clustersfree(&acg),
+ (d % sblock.fs_fpg) / sblock.fs_frag);
+
+ frag_adjust(d % sblock.fs_fpg, 1);
+ }
+ /*
+ * XXX Handle the cluster statistics here in the case this
+ * cylinder group is now almost full, and the remaining
+ * space is less then the maximum cluster size. This is
+ * probably not needed, as you would hardly find a file
+ * system which has only MAXCSBUFS+FS_MAXCONTIG of free
+ * space right behind the cylinder group information in
+ * any new cylinder group.
+ */
+
+ /*
+ * Update our statistics in the cylinder summary.
+ */
+ *cs = acg.cg_cs;
+
+ /*
+ * Write the new cylinder group containing the cylinder summary
+ * back to disk.
+ */
+ wtfs(fsbtodb(&sblock, cgtod(&sblock, ncscg)),
+ (size_t)sblock.fs_cgsize, (void *)&acg, fso, Nflag);
+ DBG_PRINT0("nscg written\n");
+ DBG_DUMP_CG(&sblock, "new summary cg", &acg);
+
+ DBG_LEAVE;
+ return;
+}
+
+/*
+ * Here we read some block(s) from disk.
+ */
+static void
+rdfs(ufs2_daddr_t bno, size_t size, void *bf, int fsi)
+{
+ DBG_FUNC("rdfs")
+ ssize_t n;
+
+ DBG_ENTER;
+
+ if (bno < 0)
+ err(32, "rdfs: attempting to read negative block number");
+ if (lseek(fsi, (off_t)bno * DEV_BSIZE, 0) < 0)
+ err(33, "rdfs: seek error: %jd", (intmax_t)bno);
+ n = read(fsi, bf, size);
+ if (n != (ssize_t)size)
+ err(34, "rdfs: read error: %jd", (intmax_t)bno);
+
+ DBG_LEAVE;
+ return;
+}
+
+/*
+ * Here we write some block(s) to disk.
+ */
+static void
+wtfs(ufs2_daddr_t bno, size_t size, void *bf, int fso, unsigned int Nflag)
+{
+ DBG_FUNC("wtfs")
+ ssize_t n;
+
+ DBG_ENTER;
+
+ if (Nflag) {
+ DBG_LEAVE;
+ return;
+ }
+ if (lseek(fso, (off_t)bno * DEV_BSIZE, SEEK_SET) < 0)
+ err(35, "wtfs: seek error: %ld", (long)bno);
+ n = write(fso, bf, size);
+ if (n != (ssize_t)size)
+ err(36, "wtfs: write error: %ld", (long)bno);
+
+ DBG_LEAVE;
+ return;
+}
+
+/*
+ * Here we check if all frags of a block are free. For more details again
+ * please see the source of newfs(8), as this function is taken over almost
+ * unchanged.
+ */
+static int
+isblock(struct fs *fs, unsigned char *cp, int h)
+{
+ DBG_FUNC("isblock")
+ unsigned char mask;
+
+ DBG_ENTER;
+
+ switch (fs->fs_frag) {
+ case 8:
+ DBG_LEAVE;
+ return (cp[h] == 0xff);
+ case 4:
+ mask = 0x0f << ((h & 0x1) << 2);
+ DBG_LEAVE;
+ return ((cp[h >> 1] & mask) == mask);
+ case 2:
+ mask = 0x03 << ((h & 0x3) << 1);
+ DBG_LEAVE;
+ return ((cp[h >> 2] & mask) == mask);
+ case 1:
+ mask = 0x01 << (h & 0x7);
+ DBG_LEAVE;
+ return ((cp[h >> 3] & mask) == mask);
+ default:
+ fprintf(stderr, "isblock bad fs_frag %d\n", fs->fs_frag);
+ DBG_LEAVE;
+ return (0);
+ }
+}
+
+/*
+ * Here we allocate a complete block in the block map. For more details again
+ * please see the source of newfs(8), as this function is taken over almost
+ * unchanged.
+ */
+static void
+clrblock(struct fs *fs, unsigned char *cp, int h)
+{
+ DBG_FUNC("clrblock")
+
+ DBG_ENTER;
+
+ switch ((fs)->fs_frag) {
+ case 8:
+ cp[h] = 0;
+ break;
+ case 4:
+ cp[h >> 1] &= ~(0x0f << ((h & 0x1) << 2));
+ break;
+ case 2:
+ cp[h >> 2] &= ~(0x03 << ((h & 0x3) << 1));
+ break;
+ case 1:
+ cp[h >> 3] &= ~(0x01 << (h & 0x7));
+ break;
+ default:
+ warnx("clrblock bad fs_frag %d", fs->fs_frag);
+ break;
+ }
+
+ DBG_LEAVE;
+ return;
+}
+
+/*
+ * Here we free a complete block in the free block map. For more details again
+ * please see the source of newfs(8), as this function is taken over almost
+ * unchanged.
+ */
+static void
+setblock(struct fs *fs, unsigned char *cp, int h)
+{
+ DBG_FUNC("setblock")
+
+ DBG_ENTER;
+
+ switch (fs->fs_frag) {
+ case 8:
+ cp[h] = 0xff;
+ break;
+ case 4:
+ cp[h >> 1] |= (0x0f << ((h & 0x1) << 2));
+ break;
+ case 2:
+ cp[h >> 2] |= (0x03 << ((h & 0x3) << 1));
+ break;
+ case 1:
+ cp[h >> 3] |= (0x01 << (h & 0x7));
+ break;
+ default:
+ warnx("setblock bad fs_frag %d", fs->fs_frag);
+ break;
+ }
+
+ DBG_LEAVE;
+ return;
+}
+
+/*
+ * Figure out how many lines our current terminal has. For more details again
+ * please see the source of newfs(8), as this function is taken over almost
+ * unchanged.
+ */
+static int
+charsperline(void)
+{
+ DBG_FUNC("charsperline")
+ int columns;
+ char *cp;
+ struct winsize ws;
+
+ DBG_ENTER;
+
+ columns = 0;
+ if (ioctl(0, TIOCGWINSZ, &ws) != -1)
+ columns = ws.ws_col;
+ if (columns == 0 && (cp = getenv("COLUMNS")))
+ columns = atoi(cp);
+ if (columns == 0)
+ columns = 80; /* last resort */
+
+ DBG_LEAVE;
+ return (columns);
+}
+
+static int
+is_dev(const char *name)
+{
+ struct stat devstat;
+
+ if (stat(name, &devstat) != 0)
+ return (0);
+ if (!S_ISCHR(devstat.st_mode))
+ return (0);
+ return (1);
+}
+
+/*
+ * Return mountpoint on which the device is currently mounted.
+ */
+static const struct statfs *
+dev_to_statfs(const char *dev)
+{
+ struct stat devstat, mntdevstat;
+ struct statfs *mntbuf, *statfsp;
+ char device[MAXPATHLEN];
+ char *mntdevname;
+ int i, mntsize;
+
+ /*
+ * First check the mounted filesystems.
+ */
+ if (stat(dev, &devstat) != 0)
+ return (NULL);
+ if (!S_ISCHR(devstat.st_mode) && !S_ISBLK(devstat.st_mode))
+ return (NULL);
+
+ mntsize = getmntinfo(&mntbuf, MNT_NOWAIT);
+ for (i = 0; i < mntsize; i++) {
+ statfsp = &mntbuf[i];
+ mntdevname = statfsp->f_mntfromname;
+ if (*mntdevname != '/') {
+ strcpy(device, _PATH_DEV);
+ strcat(device, mntdevname);
+ mntdevname = device;
+ }
+ if (stat(mntdevname, &mntdevstat) == 0 &&
+ mntdevstat.st_rdev == devstat.st_rdev)
+ return (statfsp);
+ }
+
+ return (NULL);
+}
+
+static const char *
+mountpoint_to_dev(const char *mountpoint)
+{
+ struct statfs *mntbuf, *statfsp;
+ struct fstab *fs;
+ int i, mntsize;
+
+ /*
+ * First check the mounted filesystems.
+ */
+ mntsize = getmntinfo(&mntbuf, MNT_NOWAIT);
+ for (i = 0; i < mntsize; i++) {
+ statfsp = &mntbuf[i];
+
+ if (strcmp(statfsp->f_mntonname, mountpoint) == 0)
+ return (statfsp->f_mntfromname);
+ }
+
+ /*
+ * Check the fstab.
+ */
+ fs = getfsfile(mountpoint);
+ if (fs != NULL)
+ return (fs->fs_spec);
+
+ return (NULL);
+}
+
+static const char *
+getdev(const char *name)
+{
+ static char device[MAXPATHLEN];
+ const char *cp, *dev;
+
+ if (is_dev(name))
+ return (name);
+
+ cp = strrchr(name, '/');
+ if (cp == 0) {
+ snprintf(device, sizeof(device), "%s%s", _PATH_DEV, name);
+ if (is_dev(device))
+ return (device);
+ }
+
+ dev = mountpoint_to_dev(name);
+ if (dev != NULL && is_dev(dev))
+ return (dev);
+
+ return (NULL);
+}
+
+/*
+ * growfs(8) is a utility which allows to increase the size of an existing
+ * ufs file system. Currently this can only be done on unmounted file system.
+ * It recognizes some command line options to specify the new desired size,
+ * and it does some basic checkings. The old file system size is determined
+ * and after some more checks like we can really access the new last block
+ * on the disk etc. we calculate the new parameters for the superblock. After
+ * having done this we just call growfs() which will do the work.
+ * We still have to provide support for snapshots. Therefore we first have to
+ * understand what data structures are always replicated in the snapshot on
+ * creation, for all other blocks we touch during our procedure, we have to
+ * keep the old blocks unchanged somewhere available for the snapshots. If we
+ * are lucky, then we only have to handle our blocks to be relocated in that
+ * way.
+ * Also we have to consider in what order we actually update the critical
+ * data structures of the file system to make sure, that in case of a disaster
+ * fsck(8) is still able to restore any lost data.
+ * The foreseen last step then will be to provide for growing even mounted
+ * file systems. There we have to extend the mount() system call to provide
+ * userland access to the file system locking facility.
+ */
+int
+main(int argc, char **argv)
+{
+ DBG_FUNC("main")
+ const char *device;
+ const struct statfs *statfsp;
+ uint64_t size = 0;
+ off_t mediasize;
+ int error, i, j, fsi, fso, ch, Nflag = 0, yflag = 0;
+ char *p, reply[5], oldsizebuf[6], newsizebuf[6];
+ void *testbuf;
+
+ DBG_ENTER;
+
+ while ((ch = getopt(argc, argv, "Ns:vy")) != -1) {
+ switch(ch) {
+ case 'N':
+ Nflag = 1;
+ break;
+ case 's':
+ size = (off_t)strtoumax(optarg, &p, 0);
+ if (p == NULL || *p == '\0')
+ size *= DEV_BSIZE;
+ else if (*p == 'b' || *p == 'B')
+ ; /* do nothing */
+ else if (*p == 'k' || *p == 'K')
+ size <<= 10;
+ else if (*p == 'm' || *p == 'M')
+ size <<= 20;
+ else if (*p == 'g' || *p == 'G')
+ size <<= 30;
+ else if (*p == 't' || *p == 'T') {
+ size <<= 30;
+ size <<= 10;
+ } else
+ errx(1, "unknown suffix on -s argument");
+ break;
+ case 'v': /* for compatibility to newfs */
+ break;
+ case 'y':
+ yflag = 1;
+ break;
+ case '?':
+ /* FALLTHROUGH */
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 1)
+ usage();
+
+ /*
+ * Now try to guess the device name.
+ */
+ device = getdev(*argv);
+ if (device == NULL)
+ errx(1, "cannot find special device for %s", *argv);
+
+ statfsp = dev_to_statfs(device);
+
+ fsi = open(device, O_RDONLY);
+ if (fsi < 0)
+ err(1, "%s", device);
+
+ /*
+ * Try to guess the slice size if not specified.
+ */
+ if (ioctl(fsi, DIOCGMEDIASIZE, &mediasize) == -1)
+ err(1,"DIOCGMEDIASIZE");
+
+ /*
+ * Check if that partition is suitable for growing a file system.
+ */
+ if (mediasize < 1)
+ errx(1, "partition is unavailable");
+
+ /*
+ * Read the current superblock, and take a backup.
+ */
+ for (i = 0; sblock_try[i] != -1; i++) {
+ sblockloc = sblock_try[i] / DEV_BSIZE;
+ rdfs(sblockloc, (size_t)SBLOCKSIZE, (void *)&(osblock), fsi);
+ if ((osblock.fs_magic == FS_UFS1_MAGIC ||
+ (osblock.fs_magic == FS_UFS2_MAGIC &&
+ osblock.fs_sblockloc == sblock_try[i])) &&
+ osblock.fs_bsize <= MAXBSIZE &&
+ osblock.fs_bsize >= (int32_t) sizeof(struct fs))
+ break;
+ }
+ if (sblock_try[i] == -1)
+ errx(1, "superblock not recognized");
+ memcpy((void *)&fsun1, (void *)&fsun2, sizeof(fsun2));
+
+ DBG_OPEN("/tmp/growfs.debug"); /* already here we need a superblock */
+ DBG_DUMP_FS(&sblock, "old sblock");
+
+ /*
+ * Determine size to grow to. Default to the device size.
+ */
+ if (size == 0)
+ size = mediasize;
+ else {
+ if (size > (uint64_t)mediasize) {
+ humanize_number(oldsizebuf, sizeof(oldsizebuf), size,
+ "B", HN_AUTOSCALE, HN_B | HN_NOSPACE | HN_DECIMAL);
+ humanize_number(newsizebuf, sizeof(newsizebuf),
+ mediasize,
+ "B", HN_AUTOSCALE, HN_B | HN_NOSPACE | HN_DECIMAL);
+
+ errx(1, "requested size %s is larger "
+ "than the available %s", oldsizebuf, newsizebuf);
+ }
+ }
+
+ /*
+ * Make sure the new size is a multiple of fs_fsize; /dev/ufssuspend
+ * only supports fragment-aligned IO requests.
+ */
+ size -= size % osblock.fs_fsize;
+
+ if (size <= (uint64_t)(osblock.fs_size * osblock.fs_fsize)) {
+ humanize_number(oldsizebuf, sizeof(oldsizebuf),
+ osblock.fs_size * osblock.fs_fsize,
+ "B", HN_AUTOSCALE, HN_B | HN_NOSPACE | HN_DECIMAL);
+ humanize_number(newsizebuf, sizeof(newsizebuf), size,
+ "B", HN_AUTOSCALE, HN_B | HN_NOSPACE | HN_DECIMAL);
+
+ errx(1, "requested size %s is not larger than the current "
+ "filesystem size %s", newsizebuf, oldsizebuf);
+ }
+
+ sblock.fs_size = dbtofsb(&osblock, size / DEV_BSIZE);
+ sblock.fs_providersize = dbtofsb(&osblock, mediasize / DEV_BSIZE);
+
+ /*
+ * Are we really growing?
+ */
+ if (osblock.fs_size >= sblock.fs_size) {
+ errx(1, "we are not growing (%jd->%jd)",
+ (intmax_t)osblock.fs_size, (intmax_t)sblock.fs_size);
+ }
+
+ /*
+ * Check if we find an active snapshot.
+ */
+ if (yflag == 0) {
+ for (j = 0; j < FSMAXSNAP; j++) {
+ if (sblock.fs_snapinum[j]) {
+ errx(1, "active snapshot found in file system; "
+ "please remove all snapshots before "
+ "using growfs");
+ }
+ if (!sblock.fs_snapinum[j]) /* list is dense */
+ break;
+ }
+ }
+
+ if (yflag == 0 && Nflag == 0) {
+ if (statfsp != NULL && (statfsp->f_flags & MNT_RDONLY) == 0)
+ printf("Device is mounted read-write; resizing will "
+ "result in temporary write suspension for %s.\n",
+ statfsp->f_mntonname);
+ printf("It's strongly recommended to make a backup "
+ "before growing the file system.\n"
+ "OK to grow filesystem on %s", device);
+ if (statfsp != NULL)
+ printf(", mounted on %s,", statfsp->f_mntonname);
+ humanize_number(oldsizebuf, sizeof(oldsizebuf),
+ osblock.fs_size * osblock.fs_fsize,
+ "B", HN_AUTOSCALE, HN_B | HN_NOSPACE | HN_DECIMAL);
+ humanize_number(newsizebuf, sizeof(newsizebuf),
+ sblock.fs_size * sblock.fs_fsize,
+ "B", HN_AUTOSCALE, HN_B | HN_NOSPACE | HN_DECIMAL);
+ printf(" from %s to %s? [Yes/No] ", oldsizebuf, newsizebuf);
+ fflush(stdout);
+ fgets(reply, (int)sizeof(reply), stdin);
+ if (strcasecmp(reply, "Yes\n")){
+ printf("\nNothing done\n");
+ exit (0);
+ }
+ }
+
+ /*
+ * Try to access our device for writing. If it's not mounted,
+ * or mounted read-only, simply open it; otherwise, use UFS
+ * suspension mechanism.
+ */
+ if (Nflag) {
+ fso = -1;
+ } else {
+ if (statfsp != NULL && (statfsp->f_flags & MNT_RDONLY) == 0) {
+ fso = open(_PATH_UFSSUSPEND, O_RDWR);
+ if (fso == -1)
+ err(1, "unable to open %s", _PATH_UFSSUSPEND);
+ error = ioctl(fso, UFSSUSPEND, &statfsp->f_fsid);
+ if (error != 0)
+ err(1, "UFSSUSPEND");
+ } else {
+ fso = open(device, O_WRONLY);
+ if (fso < 0)
+ err(1, "%s", device);
+ }
+ }
+
+ /*
+ * Try to access our new last block in the file system.
+ */
+ testbuf = malloc(sblock.fs_fsize);
+ if (testbuf == NULL)
+ err(1, "malloc");
+ rdfs((ufs2_daddr_t)((size - sblock.fs_fsize) / DEV_BSIZE),
+ sblock.fs_fsize, testbuf, fsi);
+ wtfs((ufs2_daddr_t)((size - sblock.fs_fsize) / DEV_BSIZE),
+ sblock.fs_fsize, testbuf, fso, Nflag);
+ free(testbuf);
+
+ /*
+ * Now calculate new superblock values and check for reasonable
+ * bound for new file system size:
+ * fs_size: is derived from user input
+ * fs_dsize: should get updated in the routines creating or
+ * updating the cylinder groups on the fly
+ * fs_cstotal: should get updated in the routines creating or
+ * updating the cylinder groups
+ */
+
+ /*
+ * Update the number of cylinders and cylinder groups in the file system.
+ */
+ if (sblock.fs_magic == FS_UFS1_MAGIC) {
+ sblock.fs_old_ncyl =
+ sblock.fs_size * sblock.fs_old_nspf / sblock.fs_old_spc;
+ if (sblock.fs_size * sblock.fs_old_nspf >
+ sblock.fs_old_ncyl * sblock.fs_old_spc)
+ sblock.fs_old_ncyl++;
+ }
+ sblock.fs_ncg = howmany(sblock.fs_size, sblock.fs_fpg);
+
+ /*
+ * Allocate last cylinder group only if there is enough room
+ * for at least one data block.
+ */
+ if (sblock.fs_size % sblock.fs_fpg != 0 &&
+ sblock.fs_size <= cgdmin(&sblock, sblock.fs_ncg - 1)) {
+ humanize_number(oldsizebuf, sizeof(oldsizebuf),
+ (sblock.fs_size % sblock.fs_fpg) * sblock.fs_fsize,
+ "B", HN_AUTOSCALE, HN_B | HN_NOSPACE | HN_DECIMAL);
+ warnx("no room to allocate last cylinder group; "
+ "leaving %s unused", oldsizebuf);
+ sblock.fs_ncg--;
+ if (sblock.fs_magic == FS_UFS1_MAGIC)
+ sblock.fs_old_ncyl = sblock.fs_ncg * sblock.fs_old_cpg;
+ sblock.fs_size = sblock.fs_ncg * sblock.fs_fpg;
+ }
+
+ /*
+ * Update the space for the cylinder group summary information in the
+ * respective cylinder group data area.
+ */
+ sblock.fs_cssize =
+ fragroundup(&sblock, sblock.fs_ncg * sizeof(struct csum));
+
+ if (osblock.fs_size >= sblock.fs_size)
+ errx(1, "not enough new space");
+
+ DBG_PRINT0("sblock calculated\n");
+
+ /*
+ * Ok, everything prepared, so now let's do the tricks.
+ */
+ growfs(fsi, fso, Nflag);
+
+ close(fsi);
+ if (fso > -1) {
+ if (statfsp != NULL && (statfsp->f_flags & MNT_RDONLY) == 0) {
+ error = ioctl(fso, UFSRESUME);
+ if (error != 0)
+ err(1, "UFSRESUME");
+ }
+ error = close(fso);
+ if (error != 0)
+ err(1, "close");
+ if (statfsp != NULL && (statfsp->f_flags & MNT_RDONLY) != 0)
+ mount_reload(statfsp);
+ }
+
+ DBG_CLOSE;
+
+ DBG_LEAVE;
+ return (0);
+}
+
+/*
+ * Dump a line of usage.
+ */
+static void
+usage(void)
+{
+ DBG_FUNC("usage")
+
+ DBG_ENTER;
+
+ fprintf(stderr, "usage: growfs [-Ny] [-s size] special | filesystem\n");
+
+ DBG_LEAVE;
+ exit(1);
+}
+
+/*
+ * This updates most parameters and the bitmap related to cluster. We have to
+ * assume that sblock, osblock, acg are set up.
+ */
+static void
+updclst(int block)
+{
+ DBG_FUNC("updclst")
+ static int lcs = 0;
+
+ DBG_ENTER;
+
+ if (sblock.fs_contigsumsize < 1) /* no clustering */
+ return;
+ /*
+ * update cluster allocation map
+ */
+ setbit(cg_clustersfree(&acg), block);
+
+ /*
+ * update cluster summary table
+ */
+ if (!lcs) {
+ /*
+ * calculate size for the trailing cluster
+ */
+ for (block--; lcs < sblock.fs_contigsumsize; block--, lcs++ ) {
+ if (isclr(cg_clustersfree(&acg), block))
+ break;
+ }
+ }
+ if (lcs < sblock.fs_contigsumsize) {
+ if (lcs)
+ cg_clustersum(&acg)[lcs]--;
+ lcs++;
+ cg_clustersum(&acg)[lcs]++;
+ }
+
+ DBG_LEAVE;
+ return;
+}
+
+static void
+mount_reload(const struct statfs *stfs)
+{
+ char errmsg[255];
+ struct iovec *iov;
+ int iovlen;
+
+ iov = NULL;
+ iovlen = 0;
+ *errmsg = '\0';
+ build_iovec(&iov, &iovlen, "fstype", __DECONST(char *, "ffs"), 4);
+ build_iovec(&iov, &iovlen, "fspath", __DECONST(char *, stfs->f_mntonname), (size_t)-1);
+ build_iovec(&iov, &iovlen, "errmsg", errmsg, sizeof(errmsg));
+ build_iovec(&iov, &iovlen, "update", NULL, 0);
+ build_iovec(&iov, &iovlen, "reload", NULL, 0);
+
+ if (nmount(iov, iovlen, stfs->f_flags) < 0) {
+ errmsg[sizeof(errmsg) - 1] = '\0';
+ err(9, "%s: cannot reload filesystem%s%s", stfs->f_mntonname,
+ *errmsg != '\0' ? ": " : "", errmsg);
+ }
+}
diff --git a/sbin/growfs/tests/Makefile b/sbin/growfs/tests/Makefile
new file mode 100644
index 0000000..7a6a831
--- /dev/null
+++ b/sbin/growfs/tests/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+TESTSDIR= ${TESTSBASE}/sbin/growfs
+
+TAP_TESTS_PERL= legacy_test
+
+.include <bsd.test.mk>
diff --git a/sbin/growfs/tests/legacy_test.pl b/sbin/growfs/tests/legacy_test.pl
new file mode 100755
index 0000000..7316951
--- /dev/null
+++ b/sbin/growfs/tests/legacy_test.pl
@@ -0,0 +1,89 @@
+# $FreeBSD$
+
+use strict;
+use warnings;
+use Test::More tests => 19;
+use Fcntl qw(:DEFAULT :seek);
+
+use constant BLK => 512;
+use constant BLKS_PER_MB => 2048;
+
+my $unit;
+END { system "mdconfig -du$unit" if defined $unit };
+
+sub setsize {
+ my ($partszMB, $unitszMB) = @_;
+
+ open my $fd, "|-", "disklabel -R md$unit /dev/stdin" or die;
+ print $fd "a: ", ($partszMB * BLKS_PER_MB), " 0 4.2BSD 1024 8192\n";
+ print $fd "c: ", ($unitszMB * BLKS_PER_MB), " 0 unused 0 0\n";
+ close $fd;
+}
+
+sub fill {
+ my ($start, $size, $content) = @_;
+
+ my $content512 = $content x (int(512 / length $content) + 1);
+ substr($content512, 512) = "";
+ sysopen my $fd, "/dev/md$unit", O_RDWR or die "/dev/md$unit: $!";
+ seek($fd, $start * BLK, SEEK_SET);
+ while ($size) {
+ syswrite($fd, $content512) == 512 or die "write: $!";
+ $size--;
+ }
+}
+
+SKIP: {
+ skip "Cannot test without UID 0", 19 if $<;
+
+ chomp(my $md = `mdconfig -s40m`);
+ like($md, qr/^md\d+$/, "Created $md with size 40m") or die;
+ $unit = substr $md, 2;
+
+ for my $type (1..2) {
+
+ initialise: {
+ ok(setsize(10, 40), "Sized ${md}a to 10m");
+ system "newfs -O $type -U ${md}a >/dev/null";
+ is($?, 0, "Initialised the filesystem on ${md}a as UFS$type");
+ chomp(my @out = `fsck -tufs -y ${md}a`);
+ ok(!grep(/MODIFIED/, @out), "fsck says ${md}a is clean, " .
+ scalar(@out) . " lines of output");
+ }
+
+ extend20_zeroed: {
+ ok(setsize(20, 40), "Sized ${md}a to 20m");
+ diag "Filling the extent with zeros";
+ fill(10 * BLKS_PER_MB, 10 * BLKS_PER_MB, chr(0));
+ my $out = `growfs -y ${md}a`;
+ is($?, 0, "Extended the filesystem on ${md}a") or print $out;
+
+ my ($unallocated) = $out =~ m{\d+ sectors cannot be allocated};
+ fill(30 * BLKS_PER_MB - $unallocated, $unallocated, chr(0))
+ if $unallocated;
+
+ chomp(my @out = `fsck -tufs -y ${md}a`);
+ ok(!grep(/MODIFIED/, @out), "fsck says ${md}a is clean, " .
+ scalar(@out) . " lines of output");
+ }
+
+ extend30_garbaged: {
+ ok(setsize(30, 40), "Sized ${md}a to 30m");
+ diag "Filling the extent with garbage";
+ fill(20 * BLKS_PER_MB, 10 * BLKS_PER_MB, chr(0xaa) . chr(0x55));
+ my $out = `growfs -y ${md}a`;
+ is($?, 0, "Extended the filesystem on ${md}a") or print $out;
+
+ my ($unallocated) = $out =~ m{\d+ sectors cannot be allocated};
+ fill(30 * BLKS_PER_MB - $unallocated, $unallocated, chr(0))
+ if $unallocated;
+
+ chomp(my @out = `fsck -tufs -y ${md}a`);
+ ok(!grep(/MODIFIED/, @out), "fsck says ${md}a is clean, " .
+ scalar(@out) . " lines of output");
+ }
+ }
+
+ system "mdconfig -du$unit";
+ undef $unit;
+}
diff --git a/sbin/gvinum/Makefile b/sbin/gvinum/Makefile
new file mode 100644
index 0000000..0e56920
--- /dev/null
+++ b/sbin/gvinum/Makefile
@@ -0,0 +1,14 @@
+# $FreeBSD$
+
+PROG= gvinum
+SRCS= gvinum.c gvinum.h geom_vinum_share.c
+MAN= gvinum.8
+
+WARNS?= 2
+CFLAGS+= -I${.CURDIR}/../../sys -I${DESTDIR}/${INCLUDEDIR}/edit
+
+LIBADD= edit geom
+
+.PATH: ${.CURDIR}/../../sys/geom/vinum
+
+.include <bsd.prog.mk>
diff --git a/sbin/gvinum/gvinum.8 b/sbin/gvinum/gvinum.8
new file mode 100644
index 0000000..a8a448c
--- /dev/null
+++ b/sbin/gvinum/gvinum.8
@@ -0,0 +1,446 @@
+.\" Copyright (c) 2005 Chris Jones
+.\" All rights reserved.
+.\"
+.\" This software was developed for the FreeBSD Project by Chris Jones
+.\" thanks to the support of Google's Summer of Code program and
+.\" mentoring by Lukas Ertl.
+.\"
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd May 6, 2014
+.Dt GVINUM 8
+.Os
+.Sh NAME
+.Nm gvinum
+.Nd Logical Volume Manager control program
+.Sh SYNOPSIS
+.Nm
+.Op Ar command
+.Op Fl options
+.Sh COMMANDS
+.Bl -tag -width indent
+.It Ic attach Ar plex volume Op Cm rename
+.It Ic attach Ar subdisk plex Oo Ar offset Oc Op Cm rename
+Attach a plex to a volume, or a subdisk to a plex.
+If offset is specified, the subdisk will be attached to the given offset within
+the plex.
+If rename is specified, the subdisk or plex will change name according to the
+object it attaches to.
+.It Ic checkparity Oo Fl f Oc Ar plex
+Check the parity blocks of a RAID-5 plex.
+The parity check will start at the
+beginning of the plex if the
+.Fl f
+flag is specified, or otherwise at the location of the parity check pointer,
+the first location at which plex's parity is incorrect.
+All subdisks in the
+plex must be up for a parity check.
+.It Ic concat Oo Fl fv Oc Oo Fl n Ar name Oc Ar drives
+Create a concatenated volume from the specified drives.
+If no name is specified, a unique name will be set by
+.Ic gvinum .
+.It Ic create Oo Fl f Oc Op Ar description-file
+Create a volume as described in
+.Ar description-file .
+If no
+.Ar description-file
+provided, opens an editor and provides the current
+.Nm
+configuration for editing.
+The
+.Fl f
+flag will make gvinum ignore any errors regarding creating objects that already
+exists.
+However, in contrast to vinum, objects that are not properly named in the
+.Ar description-file
+will not be created when the
+.Fl f
+flag is given.
+.It Ic detach Oo Fl f Oc Op Ar plex | subdisk
+Detach a plex or subdisk from the volume or plex to which it is
+attached.
+.It Ic grow Ar plex device
+Grow a plex by creating a gvinum drive and subdisk on device and attach it to
+the plex.
+If required by the plex organization, it will be put into the growable state.
+.It Ic help
+Provides a synopsis of
+.Nm
+commands and arguments.
+.It Ic l | list Oo Fl rvV Oc Op Ar volume | plex | subdisk
+.It Ic ld Oo Fl rvV Oc Op Ar drive ...
+.It Ic ls Oo Fl rvV Oc Op Ar subdisk ...
+.It Ic lp Oo Fl rvV Oc Op Ar plex ...
+.It Ic lv Oo Fl rvV Oc Op Ar volume ...
+List information about the relevant object(s).
+The
+.Fl r
+flag provides recursive display, showing each object's subordinate objects in
+proper relation.
+The
+.Fl v
+and
+.Fl V
+flags provide progressively more detailed output.
+.It Ic mirror Oo Fl fsv Oc Oo Fl n Ar name Oc Ar drives
+Create a mirrored volume from the specified drives.
+It requires at least a multiple of 2 drives.
+If no name is specified, a unique name will be set by gvinum.
+If the
+.Fl s
+flag is specified, a striped mirror will be created, and thus requires a
+multiple of 4 drives.
+.It Ic move | mv Fl f Ar drive subdisk Op Ar ...
+Move the subdisk(s) to the specified drive.
+The
+.Fl f
+flag is required, as all data on the indicated subdisk(s) will be destroyed as
+part of the move.
+This can currently only be done when the subdisk is
+not being accessed.
+.Pp
+If a single subdisk is moved, and it forms a part of a RAID-5 plex, the moved
+subdisks will need to be set to the
+.Dq stale
+state, and the plex will require a
+.Ic start
+command.
+If multiple subdisk(s) is moved, and form part of a RAID-5 plex, the
+moved disk(s) will need to be set to the
+.Dq up
+state and the plex will require a
+.Ic rebuildparity
+command.
+If the subdisk(s) form part of a plex that is mirrored with other
+plexes, the plex will require restarting and will sync once restarted.
+Moving
+more than one subdisk in a RAID-5 plex or subdisks from both sides of a
+mirrored plex volume will destroy data.
+Note that parity rebuilds and syncing
+must be started manually after a move.
+.It Ic printconfig
+Write a copy of the current configuration to standard output.
+.It Ic quit
+Exit
+.Nm
+when running in interactive mode.
+Normally this would be done by entering the
+EOF character.
+.It Ic raid5 Oo Fl fv Oc Oo Fl s Ar stripesize Oc Oo Fl n Ar name Oc Ar drives
+Create a RAID-5 volume from the specified drives.
+If no name is specified, a unique name will be set by
+.Ic gvinum .
+This organization requires at least three drives.
+.It Ic rename Oo Fl r Oc Ar drive | subdisk | plex | volume newname
+Change the name of the specified object.
+The
+.Fl r
+flag will recursively rename subordinate objects.
+.Pp
+Note that device nodes will not be renamed until
+.Nm
+is restarted.
+.It Ic rebuildparity Oo Fl f Oc Ar plex
+Rebuild the parity blocks of a RAID-5 plex.
+The parity rebuild will start at
+the beginning of the plex if the
+.Fl f
+flag is specified, or otherwise at the location of the parity check pointer.
+All subdisks in the plex must be up for a parity check.
+.It Ic resetconfig Oo Fl f Oc
+Reset the complete
+.Nm
+configuration.
+.It Ic rm Oo Fl r Oc Ar volume | plex | subdisk
+Remove an object and, if
+.Fl r
+is specified, its subordinate objects.
+.It Ic saveconfig
+Save
+.Nm
+configuration to disk after configuration failures.
+.It Ic setstate Oo Fl f Oc Ar state volume | plex | subdisk | drive
+Set state without influencing other objects, for diagnostic purposes
+only.
+The
+.Fl f
+flag forces state changes regardless of whether they are legal.
+.It Ic start
+Read configuration from all vinum drives.
+.It Ic start Oo Fl S Ar size Oc Ar volume | plex | subdisk
+Allow the system to access the objects.
+If necessary, plexes will be synced and rebuilt.
+If a subdisk was added to a running RAID-5 or striped plex, gvinum will
+expand into this subdisk and grow the whole RAID-5 array.
+This can be done without unmounting your filesystem.
+The
+.Fl S
+flag is currently ignored.
+.It Ic stop Oo Fl f Oc Op Ar volume | plex | subdisk
+Terminate access to the objects, or stop
+.Nm
+if no parameters are specified.
+.It Ic stripe Oo Fl fv Oc Oo Fl n Ar name Oc Ar drives
+Create a striped volume from the specified drives. If no name is specified,
+a unique name will be set by
+.Ic gvinum .
+This organization requires at least two drives.
+.El
+.Sh DESCRIPTION
+The
+.Nm
+utility communicates with the kernel component of the GVinum logical volume
+manager.
+It is designed either for interactive use, when started without
+command line arguments, or to execute a single command if the command is
+supplied on the command line.
+In interactive mode,
+.Nm
+maintains a command line history.
+.Sh OPTIONS
+The
+.Nm
+commands may be followed by an option.
+.Bl -tag -width indent
+.It Fl f
+The
+.Fl f
+.Pq Dq force
+option overrides safety checks.
+It should be used with extreme caution.
+This
+option is required in order to use the
+.Ic move
+command.
+.It Fl r
+The
+.Fl r
+.Pq Dq recursive
+option applies the command recursively to subordinate objects.
+For example, in
+conjunction with the
+.Ic lv
+command, the
+.Fl r
+option will also show information about the plexes and subdisks belonging to
+the volume.
+It is also used by the
+.Ic rename
+command to indicate that subordinate objects such as subdisks should be renamed
+to match the object(s) specified and by the
+.Ic rm
+command to delete plexes belonging to a volume and so on.
+.It Fl v
+The
+.Fl v
+.Pq Dq verbose
+option provides more detailed output.
+.It Fl V
+The
+.Fl V
+.Pq Dq "very verbose"
+option provides even more detailed output than
+.Fl v .
+.El
+.Sh ENVIRONMENT
+.Bl -tag -width ".Ev EDITOR"
+.It Ev EDITOR
+The name of the editor to use for editing configuration files, by
+default
+.Xr vi 1
+is invoked.
+.El
+.Sh FILES
+.Bl -tag -width ".Pa /dev/gvinum/plex"
+.It Pa /dev/gvinum
+directory with device nodes for
+.Nm
+objects
+.El
+.Sh EXAMPLES
+To create a mirror on disks /dev/ada1 and /dev/ada2, create a filesystem,
+mount, unmount and then stop
+.Ic gvinum :
+.Pp
+.Dl "gvinum mirror /dev/ada1 /dev/ada2"
+.Dl "newfs /dev/gvinum/gvinumvolume0"
+.Dl "mount /dev/gvinum/gvinumvolume0 /mnt"
+.Dl "..."
+.Dl "unmount /mnt"
+.Dl "gvinum stop"
+.Pp
+To create a striped mirror on disks /dev/ada1 /dev/ada2 /dev/ada3 and
+/dev/ada4 named "data" and create a filesystem:
+.Pp
+.Dl "gvinum mirror -s -n data /dev/ada1 /dev/ada2 /dev/ada3 /dev/ada4"
+.Dl "newfs /dev/gvinum/data"
+.Pp
+To create a raid5 array on disks /dev/ada1 /dev/ada2 and /dev/ada3,
+with stripesize 493k you can use the raid5 command:
+.Pp
+.Dl "gvinum raid5 -s 493k /dev/ada1 /dev/ada2 /dev/ada3"
+.Pp
+Then the volume will be created automatically.
+Afterwards, you have to initialize the volume:
+.Pp
+.Dl "gvinum start myraid5vol"
+.Pp
+The initialization will start, and the states will be updated when it's
+finished.
+The list command will give you information about its progress.
+.Pp
+Imagine that one of the drives fails, and the output of 'printconfig' looks
+something like this:
+.Pp
+.Dl "drive gvinumdrive1 device /dev/ada2"
+.Dl "drive gvinumdrive2 device /dev/???"
+.Dl "drive gvinumdrive0 device /dev/ada1"
+.Dl "volume myraid5vol"
+.Dl "plex name myraid5vol.p0 org raid5 986s vol myraid5vol"
+.Dl "sd name myraid5vol.p0.s2 drive gvinumdrive2 len 32538s driveoffset 265s"
+.Dl "plex myraid5vol.p0 plexoffset 1972s"
+.Dl "sd name myraid5vol.p0.s1 drive gvinumdrive1 len 32538s driveoffset 265s"
+.Dl "plex myraid5vol.p0 plexoffset 986s"
+.Dl "sd name myraid5vol.p0.s0 drive gvinumdrive0 len 32538s driveoffset 265s"
+.Dl "plex myraid5vol.p0 plexoffset 0s"
+.Pp
+Create a new drive with this configuration:
+.Pp
+.Dl "drive gdrive4 device /dev/ada4"
+.Pp
+Then move the stale subdisk to the new drive:
+.Pp
+.Dl "gvinum move gdrive4 myraid5vol.p0.s2"
+.Pp
+Then, initiate the rebuild:
+.Pp
+.Dl "gvinum start myraid5vol.p0"
+.Pp
+The plex will go up form degraded mode after the rebuild is finished.
+The plex can still be used while the rebuild is in progress, although requests
+might be delayed.
+.Pp
+Given the configuration as in the previous example, growing a RAID-5 or STRIPED
+array is accomplished by using the grow command:
+.Pp
+.Dl "gvinum grow myraid5vol.p0 /dev/ada4"
+.Pp
+If everything went ok, the plex state should now be set to growable.
+You can then start the growing with the
+.Ic start
+command:
+.Pp
+.Dl "gvinum start myraid5vol.p0"
+.Pp
+As with rebuilding, you can watch the progress using the
+.Ic list
+command.
+.Pp
+For a more advanced usage and detailed explanation of gvinum, the
+handbook is recommended.
+.Sh SEE ALSO
+.Xr geom 4 ,
+.Xr geom 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 5.3 .
+The
+.Nm vinum
+utility, on which
+.Nm
+is based, was written by
+.An "Greg Lehey" .
+.Pp
+The
+.Nm
+utility
+was written by
+.An "Lukas Ertl" .
+The
+.Ic move
+and
+.Ic rename
+commands and
+documentation were added by
+.An "Chris Jones"
+through the 2005 Google Summer
+of Code program.
+A partial rewrite of gvinum was done by
+.An "Lukas Ertl"
+and
+.An "Ulf Lilleengen"
+through the 2007 Google Summer of Code program.
+The documentation have been updated to reflect the new functionality.
+.Sh AUTHORS
+.An Lukas Ertl Aq Mt le@FreeBSD.org
+.An Chris Jones Aq Mt soc-cjones@FreeBSD.org
+.An Ulf Lilleengen Aq Mt lulf@FreeBSD.org
+.Sh BUGS
+Currently,
+.Nm
+does not rename devices in
+.Pa /dev/gvinum
+until reloaded.
+.Pp
+The
+.Fl S
+initsize flag to
+.Ic start
+is ignored.
+.Pp
+Moving subdisks that are not part of a mirrored or RAID-5 volume will
+destroy data.
+It is perhaps a bug to permit this.
+.Pp
+Plexes in which subdisks have been moved do not automatically sync or
+rebuild parity.
+This may leave data unprotected and is perhaps unwise.
+.Pp
+Currently,
+.Nm
+does not yet fully implement all of the functions found in
+.Nm vinum .
+Specifically, the following commands from
+.Nm vinum
+are not supported:
+.Bl -tag -width indent
+.It Ic debug
+Cause the volume manager to enter the kernel debugger.
+.It Ic debug Ar flags
+Set debugging flags.
+.It Ic dumpconfig Op Ar drive ...
+List the configuration information stored on the specified drives, or all
+drives in the system if no drive names are specified.
+.It Ic info Op Fl vV
+List information about volume manager state.
+.It Ic label Ar volume
+Create a volume label.
+.It Ic resetstats Oo Fl r Oc Op Ar volume | plex | subdisk
+Reset statistics counters for the specified objects, or for all objects if none
+are specified.
+.It Ic setdaemon Op Ar value
+Set daemon configuration.
+.El
diff --git a/sbin/gvinum/gvinum.c b/sbin/gvinum/gvinum.c
new file mode 100644
index 0000000..4feaa81
--- /dev/null
+++ b/sbin/gvinum/gvinum.c
@@ -0,0 +1,1442 @@
+/*
+ * Copyright (c) 2004 Lukas Ertl
+ * Copyright (c) 2005 Chris Jones
+ * Copyright (c) 2007 Ulf Lilleengen
+ * All rights reserved.
+ *
+ * Portions of this software were developed for the FreeBSD Project
+ * by Chris Jones thanks to the support of Google's Summer of Code
+ * program and mentoring by Lukas Ertl.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/param.h>
+#include <sys/linker.h>
+#include <sys/lock.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+#include <sys/queue.h>
+#include <sys/utsname.h>
+
+#include <geom/vinum/geom_vinum_var.h>
+#include <geom/vinum/geom_vinum_share.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <libgeom.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <paths.h>
+#include <readline/readline.h>
+#include <readline/history.h>
+#include <unistd.h>
+
+#include "gvinum.h"
+
+static void gvinum_attach(int, char * const *);
+static void gvinum_concat(int, char * const *);
+static void gvinum_create(int, char * const *);
+static void gvinum_detach(int, char * const *);
+static void gvinum_grow(int, char * const *);
+static void gvinum_help(void);
+static void gvinum_list(int, char * const *);
+static void gvinum_move(int, char * const *);
+static void gvinum_mirror(int, char * const *);
+static void gvinum_parityop(int, char * const * , int);
+static void gvinum_printconfig(int, char * const *);
+static void gvinum_raid5(int, char * const *);
+static void gvinum_rename(int, char * const *);
+static void gvinum_resetconfig(int, char * const *);
+static void gvinum_rm(int, char * const *);
+static void gvinum_saveconfig(void);
+static void gvinum_setstate(int, char * const *);
+static void gvinum_start(int, char * const *);
+static void gvinum_stop(int, char * const *);
+static void gvinum_stripe(int, char * const *);
+static void parseline(int, char * const *);
+static void printconfig(FILE *, const char *);
+
+static char *create_drive(const char *);
+static void create_volume(int, char * const * , const char *);
+static char *find_name(const char *, int, int);
+static const char *find_pattern(char *, const char *);
+static void copy_device(struct gv_drive *, const char *);
+#define find_drive() \
+ find_name("gvinumdrive", GV_TYPE_DRIVE, GV_MAXDRIVENAME)
+
+int
+main(int argc, char **argv)
+{
+ int line, tokens;
+ char buffer[BUFSIZ], *inputline, *token[GV_MAXARGS];
+
+ /* Load the module if necessary. */
+ if (modfind(GVINUMMOD) < 0) {
+ if (kldload(GVINUMKLD) < 0 && modfind(GVINUMMOD) < 0)
+ err(1, GVINUMKLD ": Kernel module not available");
+ }
+
+ /* Arguments given on the command line. */
+ if (argc > 1) {
+ argc--;
+ argv++;
+ parseline(argc, argv);
+
+ /* Interactive mode. */
+ } else {
+ for (;;) {
+ inputline = readline("gvinum -> ");
+ if (inputline == NULL) {
+ if (ferror(stdin)) {
+ err(1, "can't read input");
+ } else {
+ printf("\n");
+ exit(0);
+ }
+ } else if (*inputline) {
+ add_history(inputline);
+ strcpy(buffer, inputline);
+ free(inputline);
+ line++; /* count the lines */
+ tokens = gv_tokenize(buffer, token, GV_MAXARGS);
+ if (tokens)
+ parseline(tokens, token);
+ }
+ }
+ }
+ exit(0);
+}
+
+/* Attach a plex to a volume or a subdisk to a plex. */
+static void
+gvinum_attach(int argc, char * const *argv)
+{
+ struct gctl_req *req;
+ const char *errstr;
+ int rename;
+ off_t offset;
+
+ rename = 0;
+ offset = -1;
+ if (argc < 3) {
+ warnx("usage:\tattach <subdisk> <plex> [rename] "
+ "[<plexoffset>]\n"
+ "\tattach <plex> <volume> [rename]");
+ return;
+ }
+ if (argc > 3) {
+ if (!strcmp(argv[3], "rename")) {
+ rename = 1;
+ if (argc == 5)
+ offset = strtol(argv[4], NULL, 0);
+ } else
+ offset = strtol(argv[3], NULL, 0);
+ }
+ req = gctl_get_handle();
+ gctl_ro_param(req, "class", -1, "VINUM");
+ gctl_ro_param(req, "verb", -1, "attach");
+ gctl_ro_param(req, "child", -1, argv[1]);
+ gctl_ro_param(req, "parent", -1, argv[2]);
+ gctl_ro_param(req, "offset", sizeof(off_t), &offset);
+ gctl_ro_param(req, "rename", sizeof(int), &rename);
+ errstr = gctl_issue(req);
+ if (errstr != NULL)
+ warnx("attach failed: %s", errstr);
+ gctl_free(req);
+}
+
+static void
+gvinum_create(int argc, char * const *argv)
+{
+ struct gctl_req *req;
+ struct gv_drive *d;
+ struct gv_plex *p;
+ struct gv_sd *s;
+ struct gv_volume *v;
+ FILE *tmp;
+ int drives, errors, fd, flags, i, line, plexes, plex_in_volume;
+ int sd_in_plex, status, subdisks, tokens, undeffd, volumes;
+ const char *errstr;
+ char buf[BUFSIZ], buf1[BUFSIZ], commandline[BUFSIZ], *ed, *sdname;
+ char original[BUFSIZ], tmpfile[20], *token[GV_MAXARGS];
+ char plex[GV_MAXPLEXNAME], volume[GV_MAXVOLNAME];
+
+ tmp = NULL;
+ flags = 0;
+ for (i = 1; i < argc; i++) {
+ /* Force flag used to ignore already created drives. */
+ if (!strcmp(argv[i], "-f")) {
+ flags |= GV_FLAG_F;
+ /* Else it must be a file. */
+ } else {
+ if ((tmp = fopen(argv[i], "r")) == NULL) {
+ warn("can't open '%s' for reading", argv[i]);
+ return;
+ }
+ }
+ }
+
+ /* We didn't get a file. */
+ if (tmp == NULL) {
+ snprintf(tmpfile, sizeof(tmpfile), "/tmp/gvinum.XXXXXX");
+
+ if ((fd = mkstemp(tmpfile)) == -1) {
+ warn("temporary file not accessible");
+ return;
+ }
+ if ((tmp = fdopen(fd, "w")) == NULL) {
+ warn("can't open '%s' for writing", tmpfile);
+ return;
+ }
+ printconfig(tmp, "# ");
+ fclose(tmp);
+
+ ed = getenv("EDITOR");
+ if (ed == NULL)
+ ed = _PATH_VI;
+
+ snprintf(commandline, sizeof(commandline), "%s %s", ed,
+ tmpfile);
+ status = system(commandline);
+ if (status != 0) {
+ warn("couldn't exec %s; status: %d", ed, status);
+ return;
+ }
+
+ if ((tmp = fopen(tmpfile, "r")) == NULL) {
+ warn("can't open '%s' for reading", tmpfile);
+ return;
+ }
+ }
+
+ req = gctl_get_handle();
+ gctl_ro_param(req, "class", -1, "VINUM");
+ gctl_ro_param(req, "verb", -1, "create");
+ gctl_ro_param(req, "flags", sizeof(int), &flags);
+
+ drives = volumes = plexes = subdisks = 0;
+ plex_in_volume = sd_in_plex = undeffd = 0;
+ plex[0] = '\0';
+ errors = 0;
+ line = 1;
+ while ((fgets(buf, BUFSIZ, tmp)) != NULL) {
+
+ /* Skip empty lines and comments. */
+ if (*buf == '\0' || *buf == '#') {
+ line++;
+ continue;
+ }
+
+ /* Kill off the newline. */
+ buf[strlen(buf) - 1] = '\0';
+
+ /*
+ * Copy the original input line in case we need it for error
+ * output.
+ */
+ strlcpy(original, buf, sizeof(original));
+
+ tokens = gv_tokenize(buf, token, GV_MAXARGS);
+ if (tokens <= 0) {
+ line++;
+ continue;
+ }
+
+ /* Volume definition. */
+ if (!strcmp(token[0], "volume")) {
+ v = gv_new_volume(tokens, token);
+ if (v == NULL) {
+ warnx("line %d: invalid volume definition",
+ line);
+ warnx("line %d: '%s'", line, original);
+ errors++;
+ line++;
+ continue;
+ }
+
+ /* Reset plex count for this volume. */
+ plex_in_volume = 0;
+
+ /*
+ * Set default volume name for following plex
+ * definitions.
+ */
+ strlcpy(volume, v->name, sizeof(volume));
+
+ snprintf(buf1, sizeof(buf1), "volume%d", volumes);
+ gctl_ro_param(req, buf1, sizeof(*v), v);
+ volumes++;
+
+ /* Plex definition. */
+ } else if (!strcmp(token[0], "plex")) {
+ p = gv_new_plex(tokens, token);
+ if (p == NULL) {
+ warnx("line %d: invalid plex definition", line);
+ warnx("line %d: '%s'", line, original);
+ errors++;
+ line++;
+ continue;
+ }
+
+ /* Reset subdisk count for this plex. */
+ sd_in_plex = 0;
+
+ /* Default name. */
+ if (strlen(p->name) == 0) {
+ snprintf(p->name, sizeof(p->name), "%s.p%d",
+ volume, plex_in_volume++);
+ }
+
+ /* Default volume. */
+ if (strlen(p->volume) == 0) {
+ snprintf(p->volume, sizeof(p->volume), "%s",
+ volume);
+ }
+
+ /*
+ * Set default plex name for following subdisk
+ * definitions.
+ */
+ strlcpy(plex, p->name, sizeof(plex));
+
+ snprintf(buf1, sizeof(buf1), "plex%d", plexes);
+ gctl_ro_param(req, buf1, sizeof(*p), p);
+ plexes++;
+
+ /* Subdisk definition. */
+ } else if (!strcmp(token[0], "sd")) {
+ s = gv_new_sd(tokens, token);
+ if (s == NULL) {
+ warnx("line %d: invalid subdisk "
+ "definition:", line);
+ warnx("line %d: '%s'", line, original);
+ errors++;
+ line++;
+ continue;
+ }
+
+ /* Default name. */
+ if (strlen(s->name) == 0) {
+ if (strlen(plex) == 0) {
+ sdname = find_name("gvinumsubdisk.p",
+ GV_TYPE_SD, GV_MAXSDNAME);
+ snprintf(s->name, sizeof(s->name),
+ "%s.s%d", sdname, undeffd++);
+ free(sdname);
+ } else {
+ snprintf(s->name, sizeof(s->name),
+ "%s.s%d",plex, sd_in_plex++);
+ }
+ }
+
+ /* Default plex. */
+ if (strlen(s->plex) == 0)
+ snprintf(s->plex, sizeof(s->plex), "%s", plex);
+
+ snprintf(buf1, sizeof(buf1), "sd%d", subdisks);
+ gctl_ro_param(req, buf1, sizeof(*s), s);
+ subdisks++;
+
+ /* Subdisk definition. */
+ } else if (!strcmp(token[0], "drive")) {
+ d = gv_new_drive(tokens, token);
+ if (d == NULL) {
+ warnx("line %d: invalid drive definition:",
+ line);
+ warnx("line %d: '%s'", line, original);
+ errors++;
+ line++;
+ continue;
+ }
+
+ snprintf(buf1, sizeof(buf1), "drive%d", drives);
+ gctl_ro_param(req, buf1, sizeof(*d), d);
+ drives++;
+
+ /* Everything else is bogus. */
+ } else {
+ warnx("line %d: invalid definition:", line);
+ warnx("line %d: '%s'", line, original);
+ errors++;
+ }
+ line++;
+ }
+
+ fclose(tmp);
+ unlink(tmpfile);
+
+ if (!errors && (volumes || plexes || subdisks || drives)) {
+ gctl_ro_param(req, "volumes", sizeof(int), &volumes);
+ gctl_ro_param(req, "plexes", sizeof(int), &plexes);
+ gctl_ro_param(req, "subdisks", sizeof(int), &subdisks);
+ gctl_ro_param(req, "drives", sizeof(int), &drives);
+ errstr = gctl_issue(req);
+ if (errstr != NULL)
+ warnx("create failed: %s", errstr);
+ }
+ gctl_free(req);
+}
+
+/* Create a concatenated volume. */
+static void
+gvinum_concat(int argc, char * const *argv)
+{
+
+ if (argc < 2) {
+ warnx("usage:\tconcat [-fv] [-n name] drives\n");
+ return;
+ }
+ create_volume(argc, argv, "concat");
+}
+
+/* Create a drive quick and dirty. */
+static char *
+create_drive(const char *device)
+{
+ struct gv_drive *d;
+ struct gctl_req *req;
+ const char *errstr;
+ char *drivename, *dname;
+ int drives, i, flags, volumes, subdisks, plexes;
+ int found = 0;
+
+ flags = plexes = subdisks = volumes = 0;
+ drives = 1;
+ dname = NULL;
+
+ drivename = find_drive();
+ if (drivename == NULL)
+ return (NULL);
+
+ req = gctl_get_handle();
+ gctl_ro_param(req, "class", -1, "VINUM");
+ gctl_ro_param(req, "verb", -1, "create");
+ d = gv_alloc_drive();
+ if (d == NULL)
+ err(1, "unable to allocate for gv_drive object");
+
+ strlcpy(d->name, drivename, sizeof(d->name));
+ copy_device(d, device);
+ gctl_ro_param(req, "drive0", sizeof(*d), d);
+ gctl_ro_param(req, "flags", sizeof(int), &flags);
+ gctl_ro_param(req, "drives", sizeof(int), &drives);
+ gctl_ro_param(req, "volumes", sizeof(int), &volumes);
+ gctl_ro_param(req, "plexes", sizeof(int), &plexes);
+ gctl_ro_param(req, "subdisks", sizeof(int), &subdisks);
+ errstr = gctl_issue(req);
+ if (errstr != NULL) {
+ warnx("error creating drive: %s", errstr);
+ drivename = NULL;
+ } else {
+ /* XXX: This is needed because we have to make sure the drives
+ * are created before we return. */
+ /* Loop until it's in the config. */
+ for (i = 0; i < 100000; i++) {
+ dname = find_name("gvinumdrive", GV_TYPE_DRIVE,
+ GV_MAXDRIVENAME);
+ /* If we got a different name, quit. */
+ if (dname == NULL)
+ continue;
+ if (strcmp(dname, drivename))
+ found = 1;
+ free(dname);
+ dname = NULL;
+ if (found)
+ break;
+ usleep(100000); /* Sleep for 0.1s */
+ }
+ if (found == 0) {
+ warnx("error creating drive");
+ drivename = NULL;
+ }
+ }
+ gctl_free(req);
+ return (drivename);
+}
+
+/*
+ * General routine for creating a volume. Mainly for use by concat, mirror,
+ * raid5 and stripe commands.
+ */
+static void
+create_volume(int argc, char * const *argv, const char *verb)
+{
+ struct gctl_req *req;
+ const char *errstr;
+ char buf[BUFSIZ], *drivename, *volname;
+ int drives, flags, i;
+ off_t stripesize;
+
+ flags = 0;
+ drives = 0;
+ volname = NULL;
+ stripesize = 262144;
+
+ /* XXX: Should we check for argument length? */
+
+ req = gctl_get_handle();
+ gctl_ro_param(req, "class", -1, "VINUM");
+
+ for (i = 1; i < argc; i++) {
+ if (!strcmp(argv[i], "-f")) {
+ flags |= GV_FLAG_F;
+ } else if (!strcmp(argv[i], "-n")) {
+ volname = argv[++i];
+ } else if (!strcmp(argv[i], "-v")) {
+ flags |= GV_FLAG_V;
+ } else if (!strcmp(argv[i], "-s")) {
+ flags |= GV_FLAG_S;
+ if (!strcmp(verb, "raid5"))
+ stripesize = gv_sizespec(argv[++i]);
+ } else {
+ /* Assume it's a drive. */
+ snprintf(buf, sizeof(buf), "drive%d", drives++);
+
+ /* First we create the drive. */
+ drivename = create_drive(argv[i]);
+ if (drivename == NULL)
+ goto bad;
+ /* Then we add it to the request. */
+ gctl_ro_param(req, buf, -1, drivename);
+ }
+ }
+
+ gctl_ro_param(req, "stripesize", sizeof(off_t), &stripesize);
+
+ /* Find a free volume name. */
+ if (volname == NULL)
+ volname = find_name("gvinumvolume", GV_TYPE_VOL, GV_MAXVOLNAME);
+
+ /* Then we send a request to actually create the volumes. */
+ gctl_ro_param(req, "verb", -1, verb);
+ gctl_ro_param(req, "flags", sizeof(int), &flags);
+ gctl_ro_param(req, "drives", sizeof(int), &drives);
+ gctl_ro_param(req, "name", -1, volname);
+ errstr = gctl_issue(req);
+ if (errstr != NULL)
+ warnx("creating %s volume failed: %s", verb, errstr);
+bad:
+ gctl_free(req);
+}
+
+/* Parse a line of the config, return the word after <pattern>. */
+static const char *
+find_pattern(char *line, const char *pattern)
+{
+ char *ptr;
+
+ ptr = strsep(&line, " ");
+ while (ptr != NULL) {
+ if (!strcmp(ptr, pattern)) {
+ /* Return the next. */
+ ptr = strsep(&line, " ");
+ return (ptr);
+ }
+ ptr = strsep(&line, " ");
+ }
+ return (NULL);
+}
+
+/* Find a free name for an object given a prefix. */
+static char *
+find_name(const char *prefix, int type, int namelen)
+{
+ struct gctl_req *req;
+ char comment[1], buf[GV_CFG_LEN - 1], *sname, *ptr;
+ const char *errstr, *name;
+ int i, n, begin, len, conflict;
+ char line[1024];
+
+ comment[0] = '\0';
+
+ /* Find a name. Fetch out configuration first. */
+ req = gctl_get_handle();
+ gctl_ro_param(req, "class", -1, "VINUM");
+ gctl_ro_param(req, "verb", -1, "getconfig");
+ gctl_ro_param(req, "comment", -1, comment);
+ gctl_rw_param(req, "config", sizeof(buf), buf);
+ errstr = gctl_issue(req);
+ if (errstr != NULL) {
+ warnx("can't get configuration: %s", errstr);
+ return (NULL);
+ }
+ gctl_free(req);
+
+ begin = 0;
+ len = strlen(buf);
+ i = 0;
+ sname = malloc(namelen + 1);
+
+ /* XXX: Max object setting? */
+ for (n = 0; n < 10000; n++) {
+ snprintf(sname, namelen, "%s%d", prefix, n);
+ conflict = 0;
+ begin = 0;
+ /* Loop through the configuration line by line. */
+ for (i = 0; i < len; i++) {
+ if (buf[i] == '\n' || buf[i] == '\0') {
+ ptr = buf + begin;
+ strlcpy(line, ptr, (i - begin) + 1);
+ begin = i + 1;
+ switch (type) {
+ case GV_TYPE_DRIVE:
+ name = find_pattern(line, "drive");
+ break;
+ case GV_TYPE_VOL:
+ name = find_pattern(line, "volume");
+ break;
+ case GV_TYPE_PLEX:
+ case GV_TYPE_SD:
+ name = find_pattern(line, "name");
+ break;
+ default:
+ printf("Invalid type given\n");
+ continue;
+ }
+ if (name == NULL)
+ continue;
+ if (!strcmp(sname, name)) {
+ conflict = 1;
+ /* XXX: Could quit the loop earlier. */
+ }
+ }
+ }
+ if (!conflict)
+ return (sname);
+ }
+ free(sname);
+ return (NULL);
+}
+
+static void
+copy_device(struct gv_drive *d, const char *device)
+{
+
+ if (strncmp(device, "/dev/", 5) == 0)
+ strlcpy(d->device, (device + 5), sizeof(d->device));
+ else
+ strlcpy(d->device, device, sizeof(d->device));
+}
+
+/* Detach a plex or subdisk from its parent. */
+static void
+gvinum_detach(int argc, char * const *argv)
+{
+ const char *errstr;
+ struct gctl_req *req;
+ int flags, i;
+
+ flags = 0;
+ optreset = 1;
+ optind = 1;
+ while ((i = getopt(argc, argv, "f")) != -1) {
+ switch (i) {
+ case 'f':
+ flags |= GV_FLAG_F;
+ break;
+ default:
+ warn("invalid flag: %c", i);
+ return;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc != 1) {
+ warnx("usage: detach [-f] <subdisk> | <plex>");
+ return;
+ }
+
+ req = gctl_get_handle();
+ gctl_ro_param(req, "class", -1, "VINUM");
+ gctl_ro_param(req, "verb", -1, "detach");
+ gctl_ro_param(req, "object", -1, argv[0]);
+ gctl_ro_param(req, "flags", sizeof(int), &flags);
+
+ errstr = gctl_issue(req);
+ if (errstr != NULL)
+ warnx("detach failed: %s", errstr);
+ gctl_free(req);
+}
+
+static void
+gvinum_help(void)
+{
+
+ printf("COMMANDS\n"
+ "checkparity [-f] plex\n"
+ " Check the parity blocks of a RAID-5 plex.\n"
+ "create [-f] description-file\n"
+ " Create as per description-file or open editor.\n"
+ "attach plex volume [rename]\n"
+ "attach subdisk plex [offset] [rename]\n"
+ " Attach a plex to a volume, or a subdisk to a plex\n"
+ "concat [-fv] [-n name] drives\n"
+ " Create a concatenated volume from the specified drives.\n"
+ "detach [-f] [plex | subdisk]\n"
+ " Detach a plex or a subdisk from the volume or plex to\n"
+ " which it is attached.\n"
+ "grow plex drive\n"
+ " Grow plex by creating a properly sized subdisk on drive\n"
+ "l | list [-r] [-v] [-V] [volume | plex | subdisk]\n"
+ " List information about specified objects.\n"
+ "ld [-r] [-v] [-V] [volume]\n"
+ " List information about drives.\n"
+ "ls [-r] [-v] [-V] [subdisk]\n"
+ " List information about subdisks.\n"
+ "lp [-r] [-v] [-V] [plex]\n"
+ " List information about plexes.\n"
+ "lv [-r] [-v] [-V] [volume]\n"
+ " List information about volumes.\n"
+ "mirror [-fsv] [-n name] drives\n"
+ " Create a mirrored volume from the specified drives.\n"
+ "move | mv -f drive object ...\n"
+ " Move the object(s) to the specified drive.\n"
+ "quit Exit the vinum program when running in interactive mode."
+ " Nor-\n"
+ " mally this would be done by entering the EOF character.\n"
+ "raid5 [-fv] [-s stripesize] [-n name] drives\n"
+ " Create a RAID-5 volume from the specified drives.\n"
+ "rename [-r] [drive | subdisk | plex | volume] newname\n"
+ " Change the name of the specified object.\n"
+ "rebuildparity plex [-f]\n"
+ " Rebuild the parity blocks of a RAID-5 plex.\n"
+ "resetconfig [-f]\n"
+ " Reset the complete gvinum configuration\n"
+ "rm [-r] [-f] volume | plex | subdisk | drive\n"
+ " Remove an object.\n"
+ "saveconfig\n"
+ " Save vinum configuration to disk after configuration"
+ " failures.\n"
+ "setstate [-f] state [volume | plex | subdisk | drive]\n"
+ " Set state without influencing other objects, for"
+ " diagnostic pur-\n"
+ " poses only.\n"
+ "start [-S size] volume | plex | subdisk\n"
+ " Allow the system to access the objects.\n"
+ "stripe [-fv] [-n name] drives\n"
+ " Create a striped volume from the specified drives.\n"
+ );
+}
+
+static void
+gvinum_setstate(int argc, char * const *argv)
+{
+ struct gctl_req *req;
+ int flags, i;
+ const char *errstr;
+
+ flags = 0;
+
+ optreset = 1;
+ optind = 1;
+
+ while ((i = getopt(argc, argv, "f")) != -1) {
+ switch (i) {
+ case 'f':
+ flags |= GV_FLAG_F;
+ break;
+ case '?':
+ default:
+ warn("invalid flag: %c", i);
+ return;
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 2) {
+ warnx("usage: setstate [-f] <state> <obj>");
+ return;
+ }
+
+ /*
+ * XXX: This hack is needed to avoid tripping over (now) invalid
+ * 'classic' vinum states and will go away later.
+ */
+ if (strcmp(argv[0], "up") && strcmp(argv[0], "down") &&
+ strcmp(argv[0], "stale")) {
+ warnx("invalid state '%s'", argv[0]);
+ return;
+ }
+
+ req = gctl_get_handle();
+ gctl_ro_param(req, "class", -1, "VINUM");
+ gctl_ro_param(req, "verb", -1, "setstate");
+ gctl_ro_param(req, "state", -1, argv[0]);
+ gctl_ro_param(req, "object", -1, argv[1]);
+ gctl_ro_param(req, "flags", sizeof(int), &flags);
+
+ errstr = gctl_issue(req);
+ if (errstr != NULL)
+ warnx("%s", errstr);
+ gctl_free(req);
+}
+
+static void
+gvinum_list(int argc, char * const *argv)
+{
+ struct gctl_req *req;
+ int flags, i, j;
+ const char *errstr;
+ char buf[20], *cmd, config[GV_CFG_LEN + 1];
+
+ flags = 0;
+ cmd = "list";
+
+ if (argc) {
+ optreset = 1;
+ optind = 1;
+ cmd = argv[0];
+ while ((j = getopt(argc, argv, "rsvV")) != -1) {
+ switch (j) {
+ case 'r':
+ flags |= GV_FLAG_R;
+ break;
+ case 's':
+ flags |= GV_FLAG_S;
+ break;
+ case 'v':
+ flags |= GV_FLAG_V;
+ break;
+ case 'V':
+ flags |= GV_FLAG_V;
+ flags |= GV_FLAG_VV;
+ break;
+ case '?':
+ default:
+ return;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ }
+
+ req = gctl_get_handle();
+ gctl_ro_param(req, "class", -1, "VINUM");
+ gctl_ro_param(req, "verb", -1, "list");
+ gctl_ro_param(req, "cmd", -1, cmd);
+ gctl_ro_param(req, "argc", sizeof(int), &argc);
+ gctl_ro_param(req, "flags", sizeof(int), &flags);
+ gctl_rw_param(req, "config", sizeof(config), config);
+ if (argc) {
+ for (i = 0; i < argc; i++) {
+ snprintf(buf, sizeof(buf), "argv%d", i);
+ gctl_ro_param(req, buf, -1, argv[i]);
+ }
+ }
+ errstr = gctl_issue(req);
+ if (errstr != NULL) {
+ warnx("can't get configuration: %s", errstr);
+ gctl_free(req);
+ return;
+ }
+
+ printf("%s", config);
+ gctl_free(req);
+}
+
+/* Create a mirrored volume. */
+static void
+gvinum_mirror(int argc, char * const *argv)
+{
+
+ if (argc < 2) {
+ warnx("usage\tmirror [-fsv] [-n name] drives\n");
+ return;
+ }
+ create_volume(argc, argv, "mirror");
+}
+
+/* Note that move is currently of form '[-r] target object [...]' */
+static void
+gvinum_move(int argc, char * const *argv)
+{
+ struct gctl_req *req;
+ const char *errstr;
+ char buf[20];
+ int flags, i, j;
+
+ flags = 0;
+ if (argc) {
+ optreset = 1;
+ optind = 1;
+ while ((j = getopt(argc, argv, "f")) != -1) {
+ switch (j) {
+ case 'f':
+ flags |= GV_FLAG_F;
+ break;
+ case '?':
+ default:
+ return;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ }
+
+ switch (argc) {
+ case 0:
+ warnx("no destination or object(s) to move specified");
+ return;
+ case 1:
+ warnx("no object(s) to move specified");
+ return;
+ default:
+ break;
+ }
+
+ req = gctl_get_handle();
+ gctl_ro_param(req, "class", -1, "VINUM");
+ gctl_ro_param(req, "verb", -1, "move");
+ gctl_ro_param(req, "argc", sizeof(int), &argc);
+ gctl_ro_param(req, "flags", sizeof(int), &flags);
+ gctl_ro_param(req, "destination", -1, argv[0]);
+ for (i = 1; i < argc; i++) {
+ snprintf(buf, sizeof(buf), "argv%d", i);
+ gctl_ro_param(req, buf, -1, argv[i]);
+ }
+ errstr = gctl_issue(req);
+ if (errstr != NULL)
+ warnx("can't move object(s): %s", errstr);
+ gctl_free(req);
+}
+
+static void
+gvinum_printconfig(int argc, char * const *argv)
+{
+
+ printconfig(stdout, "");
+}
+
+static void
+gvinum_parityop(int argc, char * const *argv, int rebuild)
+{
+ struct gctl_req *req;
+ int flags, i;
+ const char *errstr;
+ char *op;
+
+ if (rebuild) {
+ op = "rebuildparity";
+ } else {
+ op = "checkparity";
+ }
+
+ optreset = 1;
+ optind = 1;
+ flags = 0;
+ while ((i = getopt(argc, argv, "fv")) != -1) {
+ switch (i) {
+ case 'f':
+ flags |= GV_FLAG_F;
+ break;
+ case 'v':
+ flags |= GV_FLAG_V;
+ break;
+ default:
+ warnx("invalid flag '%c'", i);
+ return;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 1) {
+ warn("usage: %s [-f] [-v] <plex>", op);
+ return;
+ }
+
+ req = gctl_get_handle();
+ gctl_ro_param(req, "class", -1, "VINUM");
+ gctl_ro_param(req, "verb", -1, op);
+ gctl_ro_param(req, "rebuild", sizeof(int), &rebuild);
+ gctl_ro_param(req, "flags", sizeof(int), &flags);
+ gctl_ro_param(req, "plex", -1, argv[0]);
+
+ errstr = gctl_issue(req);
+ if (errstr)
+ warnx("%s\n", errstr);
+ gctl_free(req);
+}
+
+/* Create a RAID-5 volume. */
+static void
+gvinum_raid5(int argc, char * const *argv)
+{
+
+ if (argc < 2) {
+ warnx("usage:\traid5 [-fv] [-s stripesize] [-n name] drives\n");
+ return;
+ }
+ create_volume(argc, argv, "raid5");
+}
+
+static void
+gvinum_rename(int argc, char * const *argv)
+{
+ struct gctl_req *req;
+ const char *errstr;
+ int flags, j;
+
+ flags = 0;
+
+ if (argc) {
+ optreset = 1;
+ optind = 1;
+ while ((j = getopt(argc, argv, "r")) != -1) {
+ switch (j) {
+ case 'r':
+ flags |= GV_FLAG_R;
+ break;
+ default:
+ return;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ }
+
+ switch (argc) {
+ case 0:
+ warnx("no object to rename specified");
+ return;
+ case 1:
+ warnx("no new name specified");
+ return;
+ case 2:
+ break;
+ default:
+ warnx("more than one new name specified");
+ return;
+ }
+
+ req = gctl_get_handle();
+ gctl_ro_param(req, "class", -1, "VINUM");
+ gctl_ro_param(req, "verb", -1, "rename");
+ gctl_ro_param(req, "flags", sizeof(int), &flags);
+ gctl_ro_param(req, "object", -1, argv[0]);
+ gctl_ro_param(req, "newname", -1, argv[1]);
+ errstr = gctl_issue(req);
+ if (errstr != NULL)
+ warnx("can't rename object: %s", errstr);
+ gctl_free(req);
+}
+
+static void
+gvinum_rm(int argc, char * const *argv)
+{
+ struct gctl_req *req;
+ int flags, i, j;
+ const char *errstr;
+ char buf[20];
+
+ flags = 0;
+ optreset = 1;
+ optind = 1;
+ while ((j = getopt(argc, argv, "rf")) != -1) {
+ switch (j) {
+ case 'f':
+ flags |= GV_FLAG_F;
+ break;
+ case 'r':
+ flags |= GV_FLAG_R;
+ break;
+ default:
+ return;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ req = gctl_get_handle();
+ gctl_ro_param(req, "class", -1, "VINUM");
+ gctl_ro_param(req, "verb", -1, "remove");
+ gctl_ro_param(req, "argc", sizeof(int), &argc);
+ gctl_ro_param(req, "flags", sizeof(int), &flags);
+ if (argc) {
+ for (i = 0; i < argc; i++) {
+ snprintf(buf, sizeof(buf), "argv%d", i);
+ gctl_ro_param(req, buf, -1, argv[i]);
+ }
+ }
+ errstr = gctl_issue(req);
+ if (errstr != NULL) {
+ warnx("can't remove: %s", errstr);
+ gctl_free(req);
+ return;
+ }
+ gctl_free(req);
+}
+
+static void
+gvinum_resetconfig(int argc, char * const *argv)
+{
+ struct gctl_req *req;
+ const char *errstr;
+ char reply[32];
+ int flags, i;
+
+ flags = 0;
+ while ((i = getopt(argc, argv, "f")) != -1) {
+ switch (i) {
+ case 'f':
+ flags |= GV_FLAG_F;
+ break;
+ default:
+ warn("invalid flag: %c", i);
+ return;
+ }
+ }
+ if ((flags & GV_FLAG_F) == 0) {
+ if (!isatty(STDIN_FILENO)) {
+ warn("Please enter this command from a tty device\n");
+ return;
+ }
+ printf(" WARNING! This command will completely wipe out"
+ " your gvinum configuration.\n"
+ " All data will be lost. If you really want to do this,"
+ " enter the text\n\n"
+ " NO FUTURE\n"
+ " Enter text -> ");
+ fgets(reply, sizeof(reply), stdin);
+ if (strcmp(reply, "NO FUTURE\n")) {
+ printf("\n No change\n");
+ return;
+ }
+ }
+ req = gctl_get_handle();
+ gctl_ro_param(req, "class", -1, "VINUM");
+ gctl_ro_param(req, "verb", -1, "resetconfig");
+ errstr = gctl_issue(req);
+ if (errstr != NULL) {
+ warnx("can't reset config: %s", errstr);
+ gctl_free(req);
+ return;
+ }
+ gctl_free(req);
+ printf("gvinum configuration obliterated\n");
+}
+
+static void
+gvinum_saveconfig(void)
+{
+ struct gctl_req *req;
+ const char *errstr;
+
+ req = gctl_get_handle();
+ gctl_ro_param(req, "class", -1, "VINUM");
+ gctl_ro_param(req, "verb", -1, "saveconfig");
+ errstr = gctl_issue(req);
+ if (errstr != NULL)
+ warnx("can't save configuration: %s", errstr);
+ gctl_free(req);
+}
+
+static void
+gvinum_start(int argc, char * const *argv)
+{
+ struct gctl_req *req;
+ int i, initsize, j;
+ const char *errstr;
+ char buf[20];
+
+ /* 'start' with no arguments is a no-op. */
+ if (argc == 1)
+ return;
+
+ initsize = 0;
+
+ optreset = 1;
+ optind = 1;
+ while ((j = getopt(argc, argv, "S")) != -1) {
+ switch (j) {
+ case 'S':
+ initsize = atoi(optarg);
+ break;
+ default:
+ return;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (!initsize)
+ initsize = 512;
+
+ req = gctl_get_handle();
+ gctl_ro_param(req, "class", -1, "VINUM");
+ gctl_ro_param(req, "verb", -1, "start");
+ gctl_ro_param(req, "argc", sizeof(int), &argc);
+ gctl_ro_param(req, "initsize", sizeof(int), &initsize);
+ if (argc) {
+ for (i = 0; i < argc; i++) {
+ snprintf(buf, sizeof(buf), "argv%d", i);
+ gctl_ro_param(req, buf, -1, argv[i]);
+ }
+ }
+ errstr = gctl_issue(req);
+ if (errstr != NULL) {
+ warnx("can't start: %s", errstr);
+ gctl_free(req);
+ return;
+ }
+
+ gctl_free(req);
+}
+
+static void
+gvinum_stop(int argc, char * const *argv)
+{
+ int err, fileid;
+
+ fileid = kldfind(GVINUMKLD);
+ if (fileid == -1) {
+ if (modfind(GVINUMMOD) < 0)
+ warn("cannot find " GVINUMKLD);
+ return;
+ }
+
+ /*
+ * This little hack prevents that we end up in an infinite loop in
+ * g_unload_class(). gv_unload() will return EAGAIN so that the GEOM
+ * event thread will be free for the g_wither_geom() call from
+ * gv_unload(). It's silly, but it works.
+ */
+ printf("unloading " GVINUMKLD " kernel module... ");
+ fflush(stdout);
+ if ((err = kldunload(fileid)) != 0 && (errno == EAGAIN)) {
+ sleep(1);
+ err = kldunload(fileid);
+ }
+ if (err != 0) {
+ printf(" failed!\n");
+ warn("cannot unload " GVINUMKLD);
+ return;
+ }
+
+ printf("done\n");
+ exit(0);
+}
+
+/* Create a striped volume. */
+static void
+gvinum_stripe(int argc, char * const *argv)
+{
+
+ if (argc < 2) {
+ warnx("usage:\tstripe [-fv] [-n name] drives\n");
+ return;
+ }
+ create_volume(argc, argv, "stripe");
+}
+
+/* Grow a subdisk by adding disk backed by provider. */
+static void
+gvinum_grow(int argc, char * const *argv)
+{
+ struct gctl_req *req;
+ char *drive, *sdname;
+ char sdprefix[GV_MAXSDNAME];
+ struct gv_drive *d;
+ struct gv_sd *s;
+ const char *errstr;
+ int drives, volumes, plexes, subdisks, flags;
+
+ flags = 0;
+ drives = volumes = plexes = subdisks = 0;
+ if (argc < 3) {
+ warnx("usage:\tgrow plex drive\n");
+ return;
+ }
+
+ s = gv_alloc_sd();
+ if (s == NULL) {
+ warn("unable to create subdisk");
+ return;
+ }
+ d = gv_alloc_drive();
+ if (d == NULL) {
+ warn("unable to create drive");
+ free(s);
+ return;
+ }
+ /* Lookup device and set an appropriate drive name. */
+ drive = find_drive();
+ if (drive == NULL) {
+ warn("unable to find an appropriate drive name");
+ free(s);
+ free(d);
+ return;
+ }
+ strlcpy(d->name, drive, sizeof(d->name));
+ copy_device(d, argv[2]);
+
+ drives = 1;
+
+ /* We try to use the plex name as basis for the subdisk name. */
+ snprintf(sdprefix, sizeof(sdprefix), "%s.s", argv[1]);
+ sdname = find_name(sdprefix, GV_TYPE_SD, GV_MAXSDNAME);
+ if (sdname == NULL) {
+ warn("unable to find an appropriate subdisk name");
+ free(s);
+ free(d);
+ free(drive);
+ return;
+ }
+ strlcpy(s->name, sdname, sizeof(s->name));
+ free(sdname);
+ strlcpy(s->plex, argv[1], sizeof(s->plex));
+ strlcpy(s->drive, d->name, sizeof(s->drive));
+ subdisks = 1;
+
+ req = gctl_get_handle();
+ gctl_ro_param(req, "class", -1, "VINUM");
+ gctl_ro_param(req, "verb", -1, "create");
+ gctl_ro_param(req, "flags", sizeof(int), &flags);
+ gctl_ro_param(req, "volumes", sizeof(int), &volumes);
+ gctl_ro_param(req, "plexes", sizeof(int), &plexes);
+ gctl_ro_param(req, "subdisks", sizeof(int), &subdisks);
+ gctl_ro_param(req, "drives", sizeof(int), &drives);
+ gctl_ro_param(req, "drive0", sizeof(*d), d);
+ gctl_ro_param(req, "sd0", sizeof(*s), s);
+ errstr = gctl_issue(req);
+ free(drive);
+ if (errstr != NULL) {
+ warnx("unable to grow plex: %s", errstr);
+ free(s);
+ free(d);
+ return;
+ }
+ gctl_free(req);
+}
+
+static void
+parseline(int argc, char * const *argv)
+{
+
+ if (argc <= 0)
+ return;
+
+ if (!strcmp(argv[0], "create"))
+ gvinum_create(argc, argv);
+ else if (!strcmp(argv[0], "exit") || !strcmp(argv[0], "quit"))
+ exit(0);
+ else if (!strcmp(argv[0], "attach"))
+ gvinum_attach(argc, argv);
+ else if (!strcmp(argv[0], "detach"))
+ gvinum_detach(argc, argv);
+ else if (!strcmp(argv[0], "concat"))
+ gvinum_concat(argc, argv);
+ else if (!strcmp(argv[0], "grow"))
+ gvinum_grow(argc, argv);
+ else if (!strcmp(argv[0], "help"))
+ gvinum_help();
+ else if (!strcmp(argv[0], "list") || !strcmp(argv[0], "l"))
+ gvinum_list(argc, argv);
+ else if (!strcmp(argv[0], "ld"))
+ gvinum_list(argc, argv);
+ else if (!strcmp(argv[0], "lp"))
+ gvinum_list(argc, argv);
+ else if (!strcmp(argv[0], "ls"))
+ gvinum_list(argc, argv);
+ else if (!strcmp(argv[0], "lv"))
+ gvinum_list(argc, argv);
+ else if (!strcmp(argv[0], "mirror"))
+ gvinum_mirror(argc, argv);
+ else if (!strcmp(argv[0], "move"))
+ gvinum_move(argc, argv);
+ else if (!strcmp(argv[0], "mv"))
+ gvinum_move(argc, argv);
+ else if (!strcmp(argv[0], "printconfig"))
+ gvinum_printconfig(argc, argv);
+ else if (!strcmp(argv[0], "raid5"))
+ gvinum_raid5(argc, argv);
+ else if (!strcmp(argv[0], "rename"))
+ gvinum_rename(argc, argv);
+ else if (!strcmp(argv[0], "resetconfig"))
+ gvinum_resetconfig(argc, argv);
+ else if (!strcmp(argv[0], "rm"))
+ gvinum_rm(argc, argv);
+ else if (!strcmp(argv[0], "saveconfig"))
+ gvinum_saveconfig();
+ else if (!strcmp(argv[0], "setstate"))
+ gvinum_setstate(argc, argv);
+ else if (!strcmp(argv[0], "start"))
+ gvinum_start(argc, argv);
+ else if (!strcmp(argv[0], "stop"))
+ gvinum_stop(argc, argv);
+ else if (!strcmp(argv[0], "stripe"))
+ gvinum_stripe(argc, argv);
+ else if (!strcmp(argv[0], "checkparity"))
+ gvinum_parityop(argc, argv, 0);
+ else if (!strcmp(argv[0], "rebuildparity"))
+ gvinum_parityop(argc, argv, 1);
+ else
+ printf("unknown command '%s'\n", argv[0]);
+}
+
+/*
+ * The guts of printconfig. This is called from gvinum_printconfig and from
+ * gvinum_create when called without an argument, in order to give the user
+ * something to edit.
+ */
+static void
+printconfig(FILE *of, const char *comment)
+{
+ struct gctl_req *req;
+ struct utsname uname_s;
+ const char *errstr;
+ time_t now;
+ char buf[GV_CFG_LEN + 1];
+
+ uname(&uname_s);
+ time(&now);
+
+ req = gctl_get_handle();
+ gctl_ro_param(req, "class", -1, "VINUM");
+ gctl_ro_param(req, "verb", -1, "getconfig");
+ gctl_ro_param(req, "comment", -1, comment);
+ gctl_rw_param(req, "config", sizeof(buf), buf);
+ errstr = gctl_issue(req);
+ if (errstr != NULL) {
+ warnx("can't get configuration: %s", errstr);
+ return;
+ }
+ gctl_free(req);
+
+ fprintf(of, "# Vinum configuration of %s, saved at %s",
+ uname_s.nodename,
+ ctime(&now));
+
+ if (*comment != '\0')
+ fprintf(of, "# Current configuration:\n");
+
+ fprintf(of, "%s", buf);
+}
diff --git a/sbin/gvinum/gvinum.h b/sbin/gvinum/gvinum.h
new file mode 100644
index 0000000..8b72eea
--- /dev/null
+++ b/sbin/gvinum/gvinum.h
@@ -0,0 +1,40 @@
+/*-
+ * Copyright (c) 1997, 1998
+ * Nan Yang Computer Services Limited. All rights reserved.
+ *
+ * This software is distributed under the so-called ``Berkeley
+ * License'':
+ *
+ * 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 Nan Yang Computer
+ * Services Limited.
+ * 4. Neither the name of the Company 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 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 company 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.
+ */
+
+/* $FreeBSD$ */
+
+#define GVINUMMOD "g_vinum"
+#define GVINUMKLD "geom_vinum"
diff --git a/sbin/hastctl/Makefile b/sbin/hastctl/Makefile
new file mode 100644
index 0000000..41d443d
--- /dev/null
+++ b/sbin/hastctl/Makefile
@@ -0,0 +1,45 @@
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+.PATH: ${.CURDIR}/../hastd
+
+PROG= hastctl
+SRCS= activemap.c
+SRCS+= crc32.c
+SRCS+= ebuf.c
+SRCS+= hast_checksum.c hast_compression.c hast_proto.c hastctl.c
+SRCS+= lzf.c
+SRCS+= metadata.c
+SRCS+= nv.c
+SRCS+= parse.y pjdlog.c
+SRCS+= proto.c proto_common.c proto_uds.c
+SRCS+= token.l
+SRCS+= subr.c
+SRCS+= y.tab.h
+MAN= hastctl.8
+
+NO_WFORMAT=
+NO_WCAST_ALIGN=
+NO_WMISSING_VARIABLE_DECLARATIONS=
+CFLAGS+=-I${.CURDIR}/../hastd
+CFLAGS+=-DHAVE_CAPSICUM
+CFLAGS+=-DINET
+.if ${MK_INET6_SUPPORT} != "no"
+CFLAGS+=-DINET6
+.endif
+# This is needed to have WARNS > 1.
+CFLAGS+=-DYY_NO_UNPUT
+CFLAGS+=-DYY_NO_INPUT
+
+LIBADD= util
+.if ${MK_OPENSSL} != "no"
+LIBADD+= crypto
+CFLAGS+=-DHAVE_CRYPTO
+.endif
+
+YFLAGS+=-v
+
+CLEANFILES=y.tab.c y.tab.h y.output
+
+.include <bsd.prog.mk>
diff --git a/sbin/hastctl/hastctl.8 b/sbin/hastctl/hastctl.8
new file mode 100644
index 0000000..397d4cf
--- /dev/null
+++ b/sbin/hastctl/hastctl.8
@@ -0,0 +1,228 @@
+.\" Copyright (c) 2010 The FreeBSD Foundation
+.\" All rights reserved.
+.\"
+.\" This software was developed by Pawel Jakub Dawidek under sponsorship from
+.\" the FreeBSD Foundation.
+.\"
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd March 14, 2013
+.Dt HASTCTL 8
+.Os
+.Sh NAME
+.Nm hastctl
+.Nd "Highly Available Storage control utility"
+.Sh SYNOPSIS
+.Nm
+.Cm create
+.Op Fl d
+.Op Fl c Ar config
+.Op Fl e Ar extentsize
+.Op Fl k Ar keepdirty
+.Op Fl m Ar mediasize
+.Ar name ...
+.Nm
+.Cm role
+.Op Fl d
+.Op Fl c Ar config
+.Aq init | primary | secondary
+.Ar all | name ...
+.Nm
+.Cm list
+.Op Fl d
+.Op Fl c Ar config
+.Op Ar all | name ...
+.Nm
+.Cm status
+.Op Fl d
+.Op Fl c Ar config
+.Op Ar all | name ...
+.Nm
+.Cm dump
+.Op Fl d
+.Op Fl c Ar config
+.Op Ar all | name ...
+.Sh DESCRIPTION
+The
+.Nm
+utility is used to control the behaviour of the
+.Xr hastd 8
+daemon.
+.Pp
+This utility should be used by HA software like
+.Nm heartbeat
+or
+.Nm ucarp
+to setup HAST resources role when changing from primary mode to
+secondary or vice versa.
+Be aware that if a file system like UFS exists on HAST provider and
+primary node dies, file system has to be checked for inconsistencies
+with the
+.Xr fsck 8
+utility after switching secondary node to primary role.
+.Pp
+The first argument to
+.Nm
+indicates an action to be performed:
+.Bl -tag -width ".Cm create"
+.It Cm create
+Initialize local provider configured for the given resource.
+Additional options include:
+.Bl -tag -width ".Fl e Ar extentsize"
+.It Fl e Ar extentsize
+Size of an extent.
+Extent is a block which is used for synchronization.
+.Xr hastd 8
+maintains a map of dirty extents and extent is the smallest region that
+can be marked as dirty.
+If any part of an extent is modified, entire extent will be synchronized
+when nodes connect.
+If extent size is too small, there will be too much disk activity
+related to dirty map updates, which will degrade performance of the
+given resource.
+If extent size is too large, synchronization, even in case of short
+outage, can take a long time increasing the risk of losing up-to-date
+node before synchronization process is completed.
+The default extent size is
+.Va 2MB .
+.It Fl k Ar keepdirty
+Maximum number of dirty extents to keep dirty all the time.
+Most recently used extents are kept dirty to reduce number of metadata
+updates.
+The default number of most recently used extents which will be kept
+dirty is
+.Va 64 .
+.It Fl m Ar mediasize
+Size of the smaller provider used as backend storage on both nodes.
+This option can be omitted if node providers have the same size on both
+sides.
+.El
+.Pp
+If size is suffixed with a k, M, G or T, it is taken as a kilobyte,
+megabyte, gigabyte or terabyte measurement respectively.
+.It Cm role
+Change role of the given resource.
+The role can be one of:
+.Bl -tag -width ".Cm secondary"
+.It Cm init
+Resource is turned off.
+.It Cm primary
+Local
+.Xr hastd 8
+daemon will act as primary node for the given resource.
+System on which resource role is set to primary can use
+.Pa /dev/hast/<name>
+GEOM provider.
+.It Cm secondary
+Local
+.Xr hastd 8
+daemon will act as secondary node for the given resource - it will wait
+for connection from the primary node and will handle I/O requests
+received from it.
+GEOM provider
+.Pa /dev/hast/<name>
+will not be created on secondary node.
+.El
+.It Cm list
+Present verbose status of the configured resources.
+.It Cm status
+Present terse (and more easy machine-parseable) status of the configured
+resources.
+.It Cm dump
+Dump metadata stored on local component for the configured resources.
+.El
+.Pp
+In addition, every subcommand can be followed by the following options:
+.Bl -tag -width ".Fl c Ar config"
+.It Fl c Ar config
+Specify alternative location of the configuration file.
+The default location is
+.Pa /etc/hast.conf .
+.It Fl d
+Print debugging information.
+This option can be specified multiple times to raise the verbosity
+level.
+.El
+.Sh FILES
+.Bl -tag -width ".Pa /var/run/hastctl" -compact
+.It Pa /etc/hast.conf
+Configuration file for
+.Nm
+and
+.Xr hastd 8 .
+.It Pa /var/run/hastctl
+Control socket used by
+.Nm
+to communicate with the
+.Xr hastd 8
+daemon.
+.El
+.Sh EXIT STATUS
+Exit status is 0 on success, or one of the values described in
+.Xr sysexits 3
+on failure.
+.Sh EXAMPLES
+Initialize HAST provider, create file system on it and mount it.
+.Bd -literal -offset indent
+nodeB# hastctl create shared
+nodeB# hastd
+nodeB# hastctl role secondary shared
+
+nodeA# hastctl create shared
+nodeA# hastd
+nodeA# hastctl role primary shared
+nodeA# newfs -U /dev/hast/shared
+nodeA# mount -o noatime /dev/hast/shared /shared
+nodeA# application_start
+.Ed
+.Pp
+Switch roles for the
+.Nm shared
+HAST resource.
+.Bd -literal -offset indent
+nodeA# application_stop
+nodeA# umount -f /shared
+nodeA# hastctl role secondary shared
+
+nodeB# hastctl role primary shared
+nodeB# fsck -t ufs /dev/hast/shared
+nodeB# mount -o noatime /dev/hast/shared /shared
+nodeB# application_start
+.Ed
+.Sh SEE ALSO
+.Xr sysexits 3 ,
+.Xr geom 4 ,
+.Xr hast.conf 5 ,
+.Xr fsck 8 ,
+.Xr ggatec 8 ,
+.Xr ggatel 8 ,
+.Xr hastd 8 ,
+.Xr mount 8 ,
+.Xr newfs 8
+.Sh AUTHORS
+The
+.Nm
+was developed by
+.An Pawel Jakub Dawidek Aq Mt pjd@FreeBSD.org
+under sponsorship of the FreeBSD Foundation.
diff --git a/sbin/hastctl/hastctl.c b/sbin/hastctl/hastctl.c
new file mode 100644
index 0000000..474f1b2
--- /dev/null
+++ b/sbin/hastctl/hastctl.c
@@ -0,0 +1,584 @@
+/*-
+ * Copyright (c) 2009-2010 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+
+#include <err.h>
+#include <libutil.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <activemap.h>
+
+#include "hast.h"
+#include "hast_proto.h"
+#include "metadata.h"
+#include "nv.h"
+#include "pjdlog.h"
+#include "proto.h"
+#include "subr.h"
+
+/* Path to configuration file. */
+static const char *cfgpath = HAST_CONFIG;
+/* Hastd configuration. */
+static struct hastd_config *cfg;
+/* Control connection. */
+static struct proto_conn *controlconn;
+
+enum {
+ CMD_INVALID,
+ CMD_CREATE,
+ CMD_ROLE,
+ CMD_STATUS,
+ CMD_DUMP,
+ CMD_LIST
+};
+
+static __dead2 void
+usage(void)
+{
+
+ fprintf(stderr,
+ "usage: %s create [-d] [-c config] [-e extentsize] [-k keepdirty]\n"
+ "\t\t[-m mediasize] name ...\n",
+ getprogname());
+ fprintf(stderr,
+ " %s role [-d] [-c config] <init | primary | secondary> all | name ...\n",
+ getprogname());
+ fprintf(stderr,
+ " %s list [-d] [-c config] [all | name ...]\n",
+ getprogname());
+ fprintf(stderr,
+ " %s status [-d] [-c config] [all | name ...]\n",
+ getprogname());
+ fprintf(stderr,
+ " %s dump [-d] [-c config] [all | name ...]\n",
+ getprogname());
+ exit(EX_USAGE);
+}
+
+static int
+create_one(struct hast_resource *res, intmax_t mediasize, intmax_t extentsize,
+ intmax_t keepdirty)
+{
+ unsigned char *buf;
+ size_t mapsize;
+ int ec;
+
+ ec = 0;
+ pjdlog_prefix_set("[%s] ", res->hr_name);
+
+ if (provinfo(res, true) == -1) {
+ ec = EX_NOINPUT;
+ goto end;
+ }
+ if (mediasize == 0)
+ mediasize = res->hr_local_mediasize;
+ else if (mediasize > res->hr_local_mediasize) {
+ pjdlog_error("Provided mediasize is larger than provider %s size.",
+ res->hr_localpath);
+ ec = EX_DATAERR;
+ goto end;
+ }
+ if (!powerof2(res->hr_local_sectorsize)) {
+ pjdlog_error("Sector size of provider %s is not power of 2 (%u).",
+ res->hr_localpath, res->hr_local_sectorsize);
+ ec = EX_DATAERR;
+ goto end;
+ }
+ if (extentsize == 0)
+ extentsize = HAST_EXTENTSIZE;
+ if (extentsize < res->hr_local_sectorsize) {
+ pjdlog_error("Extent size (%jd) is less than sector size (%u).",
+ (intmax_t)extentsize, res->hr_local_sectorsize);
+ ec = EX_DATAERR;
+ goto end;
+ }
+ if ((extentsize % res->hr_local_sectorsize) != 0) {
+ pjdlog_error("Extent size (%jd) is not multiple of sector size (%u).",
+ (intmax_t)extentsize, res->hr_local_sectorsize);
+ ec = EX_DATAERR;
+ goto end;
+ }
+ mapsize = activemap_calc_ondisk_size(mediasize - METADATA_SIZE,
+ extentsize, res->hr_local_sectorsize);
+ if (keepdirty == 0)
+ keepdirty = HAST_KEEPDIRTY;
+ res->hr_datasize = mediasize - METADATA_SIZE - mapsize;
+ res->hr_extentsize = extentsize;
+ res->hr_keepdirty = keepdirty;
+
+ res->hr_localoff = METADATA_SIZE + mapsize;
+
+ if (metadata_write(res) == -1) {
+ ec = EX_IOERR;
+ goto end;
+ }
+ buf = calloc(1, mapsize);
+ if (buf == NULL) {
+ pjdlog_error("Unable to allocate %zu bytes of memory for initial bitmap.",
+ mapsize);
+ ec = EX_TEMPFAIL;
+ goto end;
+ }
+ if (pwrite(res->hr_localfd, buf, mapsize, METADATA_SIZE) !=
+ (ssize_t)mapsize) {
+ pjdlog_errno(LOG_ERR, "Unable to store initial bitmap on %s",
+ res->hr_localpath);
+ free(buf);
+ ec = EX_IOERR;
+ goto end;
+ }
+ free(buf);
+end:
+ if (res->hr_localfd >= 0)
+ close(res->hr_localfd);
+ pjdlog_prefix_set("%s", "");
+ return (ec);
+}
+
+static void
+control_create(int argc, char *argv[], intmax_t mediasize, intmax_t extentsize,
+ intmax_t keepdirty)
+{
+ struct hast_resource *res;
+ int ec, ii, ret;
+
+ /* Initialize the given resources. */
+ if (argc < 1)
+ usage();
+ ec = 0;
+ for (ii = 0; ii < argc; ii++) {
+ TAILQ_FOREACH(res, &cfg->hc_resources, hr_next) {
+ if (strcmp(argv[ii], res->hr_name) == 0)
+ break;
+ }
+ if (res == NULL) {
+ pjdlog_error("Unknown resource %s.", argv[ii]);
+ if (ec == 0)
+ ec = EX_DATAERR;
+ continue;
+ }
+ ret = create_one(res, mediasize, extentsize, keepdirty);
+ if (ret != 0 && ec == 0)
+ ec = ret;
+ }
+ exit(ec);
+}
+
+static int
+dump_one(struct hast_resource *res)
+{
+ int ret;
+
+ ret = metadata_read(res, false);
+ if (ret != 0)
+ return (ret);
+
+ printf("resource: %s\n", res->hr_name);
+ printf(" datasize: %ju (%NB)\n", (uintmax_t)res->hr_datasize,
+ (intmax_t)res->hr_datasize);
+ printf(" extentsize: %d (%NB)\n", res->hr_extentsize,
+ (intmax_t)res->hr_extentsize);
+ printf(" keepdirty: %d\n", res->hr_keepdirty);
+ printf(" localoff: %ju\n", (uintmax_t)res->hr_localoff);
+ printf(" resuid: %ju\n", (uintmax_t)res->hr_resuid);
+ printf(" localcnt: %ju\n", (uintmax_t)res->hr_primary_localcnt);
+ printf(" remotecnt: %ju\n", (uintmax_t)res->hr_primary_remotecnt);
+ printf(" prevrole: %s\n", role2str(res->hr_previous_role));
+
+ return (0);
+}
+
+static void
+control_dump(int argc, char *argv[])
+{
+ struct hast_resource *res;
+ int ec, ret;
+
+ /* Dump metadata of the given resource(s). */
+
+ ec = 0;
+ if (argc == 0 || (argc == 1 && strcmp(argv[0], "all") == 0)) {
+ TAILQ_FOREACH(res, &cfg->hc_resources, hr_next) {
+ ret = dump_one(res);
+ if (ret != 0 && ec == 0)
+ ec = ret;
+ }
+ } else {
+ int ii;
+
+ for (ii = 0; ii < argc; ii++) {
+ TAILQ_FOREACH(res, &cfg->hc_resources, hr_next) {
+ if (strcmp(argv[ii], res->hr_name) == 0)
+ break;
+ }
+ if (res == NULL) {
+ pjdlog_error("Unknown resource %s.", argv[ii]);
+ if (ec == 0)
+ ec = EX_DATAERR;
+ continue;
+ }
+ ret = dump_one(res);
+ if (ret != 0 && ec == 0)
+ ec = ret;
+ }
+ }
+ exit(ec);
+}
+
+static int
+control_set_role(struct nv *nv, const char *newrole)
+{
+ const char *res, *oldrole;
+ unsigned int ii;
+ int error, ret;
+
+ ret = 0;
+
+ for (ii = 0; ; ii++) {
+ res = nv_get_string(nv, "resource%u", ii);
+ if (res == NULL)
+ break;
+ pjdlog_prefix_set("[%s] ", res);
+ error = nv_get_int16(nv, "error%u", ii);
+ if (error != 0) {
+ if (ret == 0)
+ ret = error;
+ pjdlog_warning("Received error %d from hastd.", error);
+ continue;
+ }
+ oldrole = nv_get_string(nv, "role%u", ii);
+ if (strcmp(oldrole, newrole) == 0)
+ pjdlog_debug(2, "Role unchanged (%s).", oldrole);
+ else {
+ pjdlog_debug(1, "Role changed from %s to %s.", oldrole,
+ newrole);
+ }
+ }
+ pjdlog_prefix_set("%s", "");
+ return (ret);
+}
+
+static int
+control_list(struct nv *nv)
+{
+ pid_t pid;
+ unsigned int ii;
+ const char *str;
+ int error, ret;
+
+ ret = 0;
+
+ for (ii = 0; ; ii++) {
+ str = nv_get_string(nv, "resource%u", ii);
+ if (str == NULL)
+ break;
+ printf("%s:\n", str);
+ error = nv_get_int16(nv, "error%u", ii);
+ if (error != 0) {
+ if (ret == 0)
+ ret = error;
+ printf(" error: %d\n", error);
+ continue;
+ }
+ printf(" role: %s\n", nv_get_string(nv, "role%u", ii));
+ printf(" provname: %s\n",
+ nv_get_string(nv, "provname%u", ii));
+ printf(" localpath: %s\n",
+ nv_get_string(nv, "localpath%u", ii));
+ printf(" extentsize: %u (%NB)\n",
+ (unsigned int)nv_get_uint32(nv, "extentsize%u", ii),
+ (intmax_t)nv_get_uint32(nv, "extentsize%u", ii));
+ printf(" keepdirty: %u\n",
+ (unsigned int)nv_get_uint32(nv, "keepdirty%u", ii));
+ printf(" remoteaddr: %s\n",
+ nv_get_string(nv, "remoteaddr%u", ii));
+ str = nv_get_string(nv, "sourceaddr%u", ii);
+ if (str != NULL)
+ printf(" sourceaddr: %s\n", str);
+ printf(" replication: %s\n",
+ nv_get_string(nv, "replication%u", ii));
+ str = nv_get_string(nv, "status%u", ii);
+ if (str != NULL)
+ printf(" status: %s\n", str);
+ pid = nv_get_int32(nv, "workerpid%u", ii);
+ if (pid != 0)
+ printf(" workerpid: %d\n", pid);
+ printf(" dirty: %ju (%NB)\n",
+ (uintmax_t)nv_get_uint64(nv, "dirty%u", ii),
+ (intmax_t)nv_get_uint64(nv, "dirty%u", ii));
+ printf(" statistics:\n");
+ printf(" reads: %ju\n",
+ (uintmax_t)nv_get_uint64(nv, "stat_read%u", ii));
+ printf(" writes: %ju\n",
+ (uintmax_t)nv_get_uint64(nv, "stat_write%u", ii));
+ printf(" deletes: %ju\n",
+ (uintmax_t)nv_get_uint64(nv, "stat_delete%u", ii));
+ printf(" flushes: %ju\n",
+ (uintmax_t)nv_get_uint64(nv, "stat_flush%u", ii));
+ printf(" activemap updates: %ju\n",
+ (uintmax_t)nv_get_uint64(nv, "stat_activemap_update%u", ii));
+ printf(" local errors: "
+ "read: %ju, write: %ju, delete: %ju, flush: %ju\n",
+ (uintmax_t)nv_get_uint64(nv, "stat_read_error%u", ii),
+ (uintmax_t)nv_get_uint64(nv, "stat_write_error%u", ii),
+ (uintmax_t)nv_get_uint64(nv, "stat_delete_error%u", ii),
+ (uintmax_t)nv_get_uint64(nv, "stat_flush_error%u", ii));
+ printf(" queues: "
+ "local: %ju, send: %ju, recv: %ju, done: %ju, idle: %ju\n",
+ (uintmax_t)nv_get_uint64(nv, "local_queue_size%u", ii),
+ (uintmax_t)nv_get_uint64(nv, "send_queue_size%u", ii),
+ (uintmax_t)nv_get_uint64(nv, "recv_queue_size%u", ii),
+ (uintmax_t)nv_get_uint64(nv, "done_queue_size%u", ii),
+ (uintmax_t)nv_get_uint64(nv, "idle_queue_size%u", ii));
+ }
+ return (ret);
+}
+
+static int
+control_status(struct nv *nv)
+{
+ unsigned int ii;
+ const char *str;
+ int error, hprinted, ret;
+
+ hprinted = 0;
+ ret = 0;
+
+ for (ii = 0; ; ii++) {
+ str = nv_get_string(nv, "resource%u", ii);
+ if (str == NULL)
+ break;
+ if (!hprinted) {
+ printf("Name\tStatus\t Role\t\tComponents\n");
+ hprinted = 1;
+ }
+ printf("%s\t", str);
+ error = nv_get_int16(nv, "error%u", ii);
+ if (error != 0) {
+ if (ret == 0)
+ ret = error;
+ printf("ERR%d\n", error);
+ continue;
+ }
+ str = nv_get_string(nv, "status%u", ii);
+ printf("%-9s", (str != NULL) ? str : "-");
+ printf("%-15s", nv_get_string(nv, "role%u", ii));
+ printf("%s\t",
+ nv_get_string(nv, "localpath%u", ii));
+ printf("%s\n",
+ nv_get_string(nv, "remoteaddr%u", ii));
+ }
+ return (ret);
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct nv *nv;
+ int64_t mediasize, extentsize, keepdirty;
+ int cmd, debug, error, ii;
+ const char *optstr;
+
+ debug = 0;
+ mediasize = extentsize = keepdirty = 0;
+
+ if (argc == 1)
+ usage();
+
+ if (strcmp(argv[1], "create") == 0) {
+ cmd = CMD_CREATE;
+ optstr = "c:de:k:m:h";
+ } else if (strcmp(argv[1], "role") == 0) {
+ cmd = CMD_ROLE;
+ optstr = "c:dh";
+ } else if (strcmp(argv[1], "list") == 0) {
+ cmd = CMD_LIST;
+ optstr = "c:dh";
+ } else if (strcmp(argv[1], "status") == 0) {
+ cmd = CMD_STATUS;
+ optstr = "c:dh";
+ } else if (strcmp(argv[1], "dump") == 0) {
+ cmd = CMD_DUMP;
+ optstr = "c:dh";
+ } else
+ usage();
+
+ argc--;
+ argv++;
+
+ for (;;) {
+ int ch;
+
+ ch = getopt(argc, argv, optstr);
+ if (ch == -1)
+ break;
+ switch (ch) {
+ case 'c':
+ cfgpath = optarg;
+ break;
+ case 'd':
+ debug++;
+ break;
+ case 'e':
+ if (expand_number(optarg, &extentsize) == -1)
+ errx(EX_USAGE, "Invalid extentsize");
+ break;
+ case 'k':
+ if (expand_number(optarg, &keepdirty) == -1)
+ errx(EX_USAGE, "Invalid keepdirty");
+ break;
+ case 'm':
+ if (expand_number(optarg, &mediasize) == -1)
+ errx(EX_USAGE, "Invalid mediasize");
+ break;
+ case 'h':
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ switch (cmd) {
+ case CMD_CREATE:
+ case CMD_ROLE:
+ if (argc == 0)
+ usage();
+ break;
+ }
+
+ pjdlog_init(PJDLOG_MODE_STD);
+ pjdlog_debug_set(debug);
+
+ cfg = yy_config_parse(cfgpath, true);
+ PJDLOG_ASSERT(cfg != NULL);
+
+ switch (cmd) {
+ case CMD_CREATE:
+ control_create(argc, argv, mediasize, extentsize, keepdirty);
+ /* NOTREACHED */
+ PJDLOG_ABORT("What are we doing here?!");
+ break;
+ case CMD_DUMP:
+ /* Dump metadata from local component of the given resource. */
+ control_dump(argc, argv);
+ /* NOTREACHED */
+ PJDLOG_ABORT("What are we doing here?!");
+ break;
+ case CMD_ROLE:
+ /* Change role for the given resources. */
+ if (argc < 2)
+ usage();
+ nv = nv_alloc();
+ nv_add_uint8(nv, HASTCTL_CMD_SETROLE, "cmd");
+ if (strcmp(argv[0], "init") == 0)
+ nv_add_uint8(nv, HAST_ROLE_INIT, "role");
+ else if (strcmp(argv[0], "primary") == 0)
+ nv_add_uint8(nv, HAST_ROLE_PRIMARY, "role");
+ else if (strcmp(argv[0], "secondary") == 0)
+ nv_add_uint8(nv, HAST_ROLE_SECONDARY, "role");
+ else
+ usage();
+ for (ii = 0; ii < argc - 1; ii++)
+ nv_add_string(nv, argv[ii + 1], "resource%d", ii);
+ break;
+ case CMD_LIST:
+ case CMD_STATUS:
+ /* Obtain status of the given resources. */
+ nv = nv_alloc();
+ nv_add_uint8(nv, HASTCTL_CMD_STATUS, "cmd");
+ if (argc == 0)
+ nv_add_string(nv, "all", "resource%d", 0);
+ else {
+ for (ii = 0; ii < argc; ii++)
+ nv_add_string(nv, argv[ii], "resource%d", ii);
+ }
+ break;
+ default:
+ PJDLOG_ABORT("Impossible command!");
+ }
+
+ /* Setup control connection... */
+ if (proto_client(NULL, cfg->hc_controladdr, &controlconn) == -1) {
+ pjdlog_exit(EX_OSERR,
+ "Unable to setup control connection to %s",
+ cfg->hc_controladdr);
+ }
+ /* ...and connect to hastd. */
+ if (proto_connect(controlconn, HAST_TIMEOUT) == -1) {
+ pjdlog_exit(EX_OSERR, "Unable to connect to hastd via %s",
+ cfg->hc_controladdr);
+ }
+
+ if (drop_privs(NULL) != 0)
+ exit(EX_CONFIG);
+
+ /* Send the command to the server... */
+ if (hast_proto_send(NULL, controlconn, nv, NULL, 0) == -1) {
+ pjdlog_exit(EX_UNAVAILABLE,
+ "Unable to send command to hastd via %s",
+ cfg->hc_controladdr);
+ }
+ nv_free(nv);
+ /* ...and receive reply. */
+ if (hast_proto_recv_hdr(controlconn, &nv) == -1) {
+ pjdlog_exit(EX_UNAVAILABLE,
+ "cannot receive reply from hastd via %s",
+ cfg->hc_controladdr);
+ }
+
+ error = nv_get_int16(nv, "error");
+ if (error != 0) {
+ pjdlog_exitx(EX_SOFTWARE, "Error %d received from hastd.",
+ error);
+ }
+ nv_set_error(nv, 0);
+
+ switch (cmd) {
+ case CMD_ROLE:
+ error = control_set_role(nv, argv[0]);
+ break;
+ case CMD_LIST:
+ error = control_list(nv);
+ break;
+ case CMD_STATUS:
+ error = control_status(nv);
+ break;
+ default:
+ PJDLOG_ABORT("Impossible command!");
+ }
+
+ exit(error);
+}
diff --git a/sbin/hastd/Makefile b/sbin/hastd/Makefile
new file mode 100644
index 0000000..e3fed8d
--- /dev/null
+++ b/sbin/hastd/Makefile
@@ -0,0 +1,43 @@
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+PROG= hastd
+SRCS= activemap.c
+SRCS+= control.c crc32.c
+SRCS+= ebuf.c event.c
+SRCS+= hast_checksum.c hast_compression.c hast_proto.c hastd.c hooks.c
+SRCS+= lzf.c
+SRCS+= metadata.c
+SRCS+= nv.c
+SRCS+= secondary.c
+SRCS+= parse.y pjdlog.c primary.c
+SRCS+= proto.c proto_common.c proto_socketpair.c proto_tcp.c proto_uds.c
+SRCS+= rangelock.c
+SRCS+= subr.c
+SRCS+= token.l
+SRCS+= y.tab.h
+MAN= hastd.8 hast.conf.5
+
+NO_WFORMAT=
+NO_WCAST_ALIGN=
+NO_WMISSING_VARIABLE_DECLARATIONS=
+CFLAGS+=-I${.CURDIR}
+CFLAGS+=-DHAVE_CAPSICUM
+CFLAGS+=-DPROTO_TCP_DEFAULT_PORT=8457
+CFLAGS+=-DINET
+.if ${MK_INET6_SUPPORT} != "no"
+CFLAGS+=-DINET6
+.endif
+
+LIBADD= geom pthread util
+.if ${MK_OPENSSL} != "no"
+LIBADD+= crypto
+CFLAGS+=-DHAVE_CRYPTO
+.endif
+
+YFLAGS+=-v
+
+CLEANFILES=y.tab.c y.tab.h y.output
+
+.include <bsd.prog.mk>
diff --git a/sbin/hastd/activemap.c b/sbin/hastd/activemap.c
new file mode 100644
index 0000000..64b95e3
--- /dev/null
+++ b/sbin/hastd/activemap.c
@@ -0,0 +1,701 @@
+/*-
+ * Copyright (c) 2009-2010 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h> /* powerof2() */
+#include <sys/queue.h>
+
+#include <bitstring.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <pjdlog.h>
+
+#include "activemap.h"
+
+#ifndef PJDLOG_ASSERT
+#include <assert.h>
+#define PJDLOG_ASSERT(...) assert(__VA_ARGS__)
+#endif
+
+#define ACTIVEMAP_MAGIC 0xac71e4
+struct activemap {
+ int am_magic; /* Magic value. */
+ off_t am_mediasize; /* Media size in bytes. */
+ uint32_t am_extentsize; /* Extent size in bytes,
+ must be power of 2. */
+ uint8_t am_extentshift;/* 2 ^ extentbits == extentsize */
+ int am_nextents; /* Number of extents. */
+ size_t am_mapsize; /* Bitmap size in bytes. */
+ uint16_t *am_memtab; /* An array that holds number of pending
+ writes per extent. */
+ bitstr_t *am_diskmap; /* On-disk bitmap of dirty extents. */
+ bitstr_t *am_memmap; /* In-memory bitmap of dirty extents. */
+ size_t am_diskmapsize; /* Map size rounded up to sector size. */
+ uint64_t am_ndirty; /* Number of dirty regions. */
+ bitstr_t *am_syncmap; /* Bitmap of extents to sync. */
+ off_t am_syncoff; /* Next synchronization offset. */
+ TAILQ_HEAD(skeepdirty, keepdirty) am_keepdirty; /* List of extents that
+ we keep dirty to reduce bitmap
+ updates. */
+ int am_nkeepdirty; /* Number of am_keepdirty elements. */
+ int am_nkeepdirty_limit; /* Maximum number of am_keepdirty
+ elements. */
+};
+
+struct keepdirty {
+ int kd_extent;
+ TAILQ_ENTRY(keepdirty) kd_next;
+};
+
+/*
+ * Helper function taken from sys/systm.h to calculate extentshift.
+ */
+static uint32_t
+bitcount32(uint32_t x)
+{
+
+ x = (x & 0x55555555) + ((x & 0xaaaaaaaa) >> 1);
+ x = (x & 0x33333333) + ((x & 0xcccccccc) >> 2);
+ x = (x + (x >> 4)) & 0x0f0f0f0f;
+ x = (x + (x >> 8));
+ x = (x + (x >> 16)) & 0x000000ff;
+ return (x);
+}
+
+static __inline int
+off2ext(const struct activemap *amp, off_t offset)
+{
+ int extent;
+
+ PJDLOG_ASSERT(offset >= 0 && offset < amp->am_mediasize);
+ extent = (offset >> amp->am_extentshift);
+ PJDLOG_ASSERT(extent >= 0 && extent < amp->am_nextents);
+ return (extent);
+}
+
+static __inline off_t
+ext2off(const struct activemap *amp, int extent)
+{
+ off_t offset;
+
+ PJDLOG_ASSERT(extent >= 0 && extent < amp->am_nextents);
+ offset = ((off_t)extent << amp->am_extentshift);
+ PJDLOG_ASSERT(offset >= 0 && offset < amp->am_mediasize);
+ return (offset);
+}
+
+/*
+ * Function calculates number of requests needed to synchronize the given
+ * extent.
+ */
+static __inline int
+ext2reqs(const struct activemap *amp, int ext)
+{
+ off_t left;
+
+ if (ext < amp->am_nextents - 1)
+ return (((amp->am_extentsize - 1) / MAXPHYS) + 1);
+
+ PJDLOG_ASSERT(ext == amp->am_nextents - 1);
+ left = amp->am_mediasize % amp->am_extentsize;
+ if (left == 0)
+ left = amp->am_extentsize;
+ return (((left - 1) / MAXPHYS) + 1);
+}
+
+/*
+ * Initialize activemap structure and allocate memory for internal needs.
+ * Function returns 0 on success and -1 if any of the allocations failed.
+ */
+int
+activemap_init(struct activemap **ampp, uint64_t mediasize, uint32_t extentsize,
+ uint32_t sectorsize, uint32_t keepdirty)
+{
+ struct activemap *amp;
+
+ PJDLOG_ASSERT(ampp != NULL);
+ PJDLOG_ASSERT(mediasize > 0);
+ PJDLOG_ASSERT(extentsize > 0);
+ PJDLOG_ASSERT(powerof2(extentsize));
+ PJDLOG_ASSERT(sectorsize > 0);
+ PJDLOG_ASSERT(powerof2(sectorsize));
+ PJDLOG_ASSERT(keepdirty > 0);
+
+ amp = malloc(sizeof(*amp));
+ if (amp == NULL)
+ return (-1);
+
+ amp->am_mediasize = mediasize;
+ amp->am_nkeepdirty_limit = keepdirty;
+ amp->am_extentsize = extentsize;
+ amp->am_extentshift = bitcount32(extentsize - 1);
+ amp->am_nextents = ((mediasize - 1) / extentsize) + 1;
+ amp->am_mapsize = sizeof(bitstr_t) * bitstr_size(amp->am_nextents);
+ amp->am_diskmapsize = roundup2(amp->am_mapsize, sectorsize);
+ amp->am_ndirty = 0;
+ amp->am_syncoff = -2;
+ TAILQ_INIT(&amp->am_keepdirty);
+ amp->am_nkeepdirty = 0;
+
+ amp->am_memtab = calloc(amp->am_nextents, sizeof(amp->am_memtab[0]));
+ amp->am_diskmap = calloc(1, amp->am_diskmapsize);
+ amp->am_memmap = bit_alloc(amp->am_nextents);
+ amp->am_syncmap = bit_alloc(amp->am_nextents);
+
+ /*
+ * Check to see if any of the allocations above failed.
+ */
+ if (amp->am_memtab == NULL || amp->am_diskmap == NULL ||
+ amp->am_memmap == NULL || amp->am_syncmap == NULL) {
+ if (amp->am_memtab != NULL)
+ free(amp->am_memtab);
+ if (amp->am_diskmap != NULL)
+ free(amp->am_diskmap);
+ if (amp->am_memmap != NULL)
+ free(amp->am_memmap);
+ if (amp->am_syncmap != NULL)
+ free(amp->am_syncmap);
+ amp->am_magic = 0;
+ free(amp);
+ errno = ENOMEM;
+ return (-1);
+ }
+
+ amp->am_magic = ACTIVEMAP_MAGIC;
+ *ampp = amp;
+
+ return (0);
+}
+
+static struct keepdirty *
+keepdirty_find(struct activemap *amp, int extent)
+{
+ struct keepdirty *kd;
+
+ TAILQ_FOREACH(kd, &amp->am_keepdirty, kd_next) {
+ if (kd->kd_extent == extent)
+ break;
+ }
+ return (kd);
+}
+
+static bool
+keepdirty_add(struct activemap *amp, int extent)
+{
+ struct keepdirty *kd;
+
+ kd = keepdirty_find(amp, extent);
+ if (kd != NULL) {
+ /*
+ * Only move element at the beginning.
+ */
+ TAILQ_REMOVE(&amp->am_keepdirty, kd, kd_next);
+ TAILQ_INSERT_HEAD(&amp->am_keepdirty, kd, kd_next);
+ return (false);
+ }
+ /*
+ * Add new element, but first remove the most unused one if
+ * we have too many.
+ */
+ if (amp->am_nkeepdirty >= amp->am_nkeepdirty_limit) {
+ kd = TAILQ_LAST(&amp->am_keepdirty, skeepdirty);
+ PJDLOG_ASSERT(kd != NULL);
+ TAILQ_REMOVE(&amp->am_keepdirty, kd, kd_next);
+ amp->am_nkeepdirty--;
+ PJDLOG_ASSERT(amp->am_nkeepdirty > 0);
+ }
+ if (kd == NULL)
+ kd = malloc(sizeof(*kd));
+ /* We can ignore allocation failure. */
+ if (kd != NULL) {
+ kd->kd_extent = extent;
+ amp->am_nkeepdirty++;
+ TAILQ_INSERT_HEAD(&amp->am_keepdirty, kd, kd_next);
+ }
+
+ return (true);
+}
+
+static void
+keepdirty_fill(struct activemap *amp)
+{
+ struct keepdirty *kd;
+
+ TAILQ_FOREACH(kd, &amp->am_keepdirty, kd_next)
+ bit_set(amp->am_diskmap, kd->kd_extent);
+}
+
+static void
+keepdirty_free(struct activemap *amp)
+{
+ struct keepdirty *kd;
+
+ while ((kd = TAILQ_FIRST(&amp->am_keepdirty)) != NULL) {
+ TAILQ_REMOVE(&amp->am_keepdirty, kd, kd_next);
+ amp->am_nkeepdirty--;
+ free(kd);
+ }
+ PJDLOG_ASSERT(amp->am_nkeepdirty == 0);
+}
+
+/*
+ * Function frees resources allocated by activemap_init() function.
+ */
+void
+activemap_free(struct activemap *amp)
+{
+
+ PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC);
+
+ amp->am_magic = 0;
+
+ keepdirty_free(amp);
+ free(amp->am_memtab);
+ free(amp->am_diskmap);
+ free(amp->am_memmap);
+ free(amp->am_syncmap);
+}
+
+/*
+ * Function should be called before we handle write requests. It updates
+ * internal structures and returns true if on-disk metadata should be updated.
+ */
+bool
+activemap_write_start(struct activemap *amp, off_t offset, off_t length)
+{
+ bool modified;
+ off_t end;
+ int ext;
+
+ PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC);
+ PJDLOG_ASSERT(length > 0);
+
+ modified = false;
+ end = offset + length - 1;
+
+ for (ext = off2ext(amp, offset); ext <= off2ext(amp, end); ext++) {
+ /*
+ * If the number of pending writes is increased from 0,
+ * we have to mark the extent as dirty also in on-disk bitmap.
+ * By returning true we inform the caller that on-disk bitmap
+ * was modified and has to be flushed to disk.
+ */
+ if (amp->am_memtab[ext]++ == 0) {
+ PJDLOG_ASSERT(!bit_test(amp->am_memmap, ext));
+ bit_set(amp->am_memmap, ext);
+ amp->am_ndirty++;
+ }
+ if (keepdirty_add(amp, ext))
+ modified = true;
+ }
+
+ return (modified);
+}
+
+/*
+ * Function should be called after receiving write confirmation. It updates
+ * internal structures and returns true if on-disk metadata should be updated.
+ */
+bool
+activemap_write_complete(struct activemap *amp, off_t offset, off_t length)
+{
+ bool modified;
+ off_t end;
+ int ext;
+
+ PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC);
+ PJDLOG_ASSERT(length > 0);
+
+ modified = false;
+ end = offset + length - 1;
+
+ for (ext = off2ext(amp, offset); ext <= off2ext(amp, end); ext++) {
+ /*
+ * If the number of pending writes goes down to 0, we have to
+ * mark the extent as clean also in on-disk bitmap.
+ * By returning true we inform the caller that on-disk bitmap
+ * was modified and has to be flushed to disk.
+ */
+ PJDLOG_ASSERT(amp->am_memtab[ext] > 0);
+ PJDLOG_ASSERT(bit_test(amp->am_memmap, ext));
+ if (--amp->am_memtab[ext] == 0) {
+ bit_clear(amp->am_memmap, ext);
+ amp->am_ndirty--;
+ if (keepdirty_find(amp, ext) == NULL)
+ modified = true;
+ }
+ }
+
+ return (modified);
+}
+
+/*
+ * Function should be called after finishing synchronization of one extent.
+ * It returns true if on-disk metadata should be updated.
+ */
+bool
+activemap_extent_complete(struct activemap *amp, int extent)
+{
+ bool modified;
+ int reqs;
+
+ PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC);
+ PJDLOG_ASSERT(extent >= 0 && extent < amp->am_nextents);
+
+ modified = false;
+
+ reqs = ext2reqs(amp, extent);
+ PJDLOG_ASSERT(amp->am_memtab[extent] >= reqs);
+ amp->am_memtab[extent] -= reqs;
+ PJDLOG_ASSERT(bit_test(amp->am_memmap, extent));
+ if (amp->am_memtab[extent] == 0) {
+ bit_clear(amp->am_memmap, extent);
+ amp->am_ndirty--;
+ modified = true;
+ }
+
+ return (modified);
+}
+
+/*
+ * Function returns number of dirty regions.
+ */
+uint64_t
+activemap_ndirty(const struct activemap *amp)
+{
+
+ PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC);
+
+ return (amp->am_ndirty);
+}
+
+/*
+ * Function compare on-disk bitmap and in-memory bitmap and returns true if
+ * they differ and should be flushed to the disk.
+ */
+bool
+activemap_differ(const struct activemap *amp)
+{
+
+ PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC);
+
+ return (memcmp(amp->am_diskmap, amp->am_memmap,
+ amp->am_mapsize) != 0);
+}
+
+/*
+ * Function returns number of bytes used by bitmap.
+ */
+size_t
+activemap_size(const struct activemap *amp)
+{
+
+ PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC);
+
+ return (amp->am_mapsize);
+}
+
+/*
+ * Function returns number of bytes needed for storing on-disk bitmap.
+ * This is the same as activemap_size(), but rounded up to sector size.
+ */
+size_t
+activemap_ondisk_size(const struct activemap *amp)
+{
+
+ PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC);
+
+ return (amp->am_diskmapsize);
+}
+
+/*
+ * Function copies the given buffer read from disk to the internal bitmap.
+ */
+void
+activemap_copyin(struct activemap *amp, const unsigned char *buf, size_t size)
+{
+ int ext;
+
+ PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC);
+ PJDLOG_ASSERT(size >= amp->am_mapsize);
+
+ memcpy(amp->am_diskmap, buf, amp->am_mapsize);
+ memcpy(amp->am_memmap, buf, amp->am_mapsize);
+ memcpy(amp->am_syncmap, buf, amp->am_mapsize);
+
+ bit_ffs(amp->am_memmap, amp->am_nextents, &ext);
+ if (ext == -1) {
+ /* There are no dirty extents, so we can leave now. */
+ return;
+ }
+ /*
+ * Set synchronization offset to the first dirty extent.
+ */
+ activemap_sync_rewind(amp);
+ /*
+ * We have dirty extents and we want them to stay that way until
+ * we synchronize, so we set number of pending writes to number
+ * of requests needed to synchronize one extent.
+ */
+ amp->am_ndirty = 0;
+ for (; ext < amp->am_nextents; ext++) {
+ if (bit_test(amp->am_memmap, ext)) {
+ amp->am_memtab[ext] = ext2reqs(amp, ext);
+ amp->am_ndirty++;
+ }
+ }
+}
+
+/*
+ * Function merges the given bitmap with existing one.
+ */
+void
+activemap_merge(struct activemap *amp, const unsigned char *buf, size_t size)
+{
+ bitstr_t *remmap = __DECONST(bitstr_t *, buf);
+ int ext;
+
+ PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC);
+ PJDLOG_ASSERT(size >= amp->am_mapsize);
+
+ bit_ffs(remmap, amp->am_nextents, &ext);
+ if (ext == -1) {
+ /* There are no dirty extents, so we can leave now. */
+ return;
+ }
+ /*
+ * We have dirty extents and we want them to stay that way until
+ * we synchronize, so we set number of pending writes to number
+ * of requests needed to synchronize one extent.
+ */
+ for (; ext < amp->am_nextents; ext++) {
+ /* Local extent already dirty. */
+ if (bit_test(amp->am_syncmap, ext))
+ continue;
+ /* Remote extent isn't dirty. */
+ if (!bit_test(remmap, ext))
+ continue;
+ bit_set(amp->am_syncmap, ext);
+ bit_set(amp->am_memmap, ext);
+ bit_set(amp->am_diskmap, ext);
+ if (amp->am_memtab[ext] == 0)
+ amp->am_ndirty++;
+ amp->am_memtab[ext] = ext2reqs(amp, ext);
+ }
+ /*
+ * Set synchronization offset to the first dirty extent.
+ */
+ activemap_sync_rewind(amp);
+}
+
+/*
+ * Function returns pointer to internal bitmap that should be written to disk.
+ */
+const unsigned char *
+activemap_bitmap(struct activemap *amp, size_t *sizep)
+{
+
+ PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC);
+
+ if (sizep != NULL)
+ *sizep = amp->am_diskmapsize;
+ memcpy(amp->am_diskmap, amp->am_memmap, amp->am_mapsize);
+ keepdirty_fill(amp);
+ return ((const unsigned char *)amp->am_diskmap);
+}
+
+/*
+ * Function calculates size needed to store bitmap on disk.
+ */
+size_t
+activemap_calc_ondisk_size(uint64_t mediasize, uint32_t extentsize,
+ uint32_t sectorsize)
+{
+ uint64_t nextents, mapsize;
+
+ PJDLOG_ASSERT(mediasize > 0);
+ PJDLOG_ASSERT(extentsize > 0);
+ PJDLOG_ASSERT(powerof2(extentsize));
+ PJDLOG_ASSERT(sectorsize > 0);
+ PJDLOG_ASSERT(powerof2(sectorsize));
+
+ nextents = ((mediasize - 1) / extentsize) + 1;
+ mapsize = sizeof(bitstr_t) * bitstr_size(nextents);
+ return (roundup2(mapsize, sectorsize));
+}
+
+/*
+ * Set synchronization offset to the first dirty extent.
+ */
+void
+activemap_sync_rewind(struct activemap *amp)
+{
+ int ext;
+
+ PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC);
+
+ bit_ffs(amp->am_syncmap, amp->am_nextents, &ext);
+ if (ext == -1) {
+ /* There are no extents to synchronize. */
+ amp->am_syncoff = -2;
+ return;
+ }
+ /*
+ * Mark that we want to start synchronization from the beginning.
+ */
+ amp->am_syncoff = -1;
+}
+
+/*
+ * Return next offset of where we should synchronize.
+ */
+off_t
+activemap_sync_offset(struct activemap *amp, off_t *lengthp, int *syncextp)
+{
+ off_t syncoff, left;
+ int ext;
+
+ PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC);
+ PJDLOG_ASSERT(lengthp != NULL);
+ PJDLOG_ASSERT(syncextp != NULL);
+
+ *syncextp = -1;
+
+ if (amp->am_syncoff == -2)
+ return (-1);
+
+ if (amp->am_syncoff >= 0 &&
+ (amp->am_syncoff + MAXPHYS >= amp->am_mediasize ||
+ off2ext(amp, amp->am_syncoff) !=
+ off2ext(amp, amp->am_syncoff + MAXPHYS))) {
+ /*
+ * We are about to change extent, so mark previous one as clean.
+ */
+ ext = off2ext(amp, amp->am_syncoff);
+ bit_clear(amp->am_syncmap, ext);
+ *syncextp = ext;
+ amp->am_syncoff = -1;
+ }
+
+ if (amp->am_syncoff == -1) {
+ /*
+ * Let's find first extent to synchronize.
+ */
+ bit_ffs(amp->am_syncmap, amp->am_nextents, &ext);
+ if (ext == -1) {
+ amp->am_syncoff = -2;
+ return (-1);
+ }
+ amp->am_syncoff = ext2off(amp, ext);
+ } else {
+ /*
+ * We don't change extent, so just increase offset.
+ */
+ amp->am_syncoff += MAXPHYS;
+ if (amp->am_syncoff >= amp->am_mediasize) {
+ amp->am_syncoff = -2;
+ return (-1);
+ }
+ }
+
+ syncoff = amp->am_syncoff;
+ left = ext2off(amp, off2ext(amp, syncoff)) +
+ amp->am_extentsize - syncoff;
+ if (syncoff + left > amp->am_mediasize)
+ left = amp->am_mediasize - syncoff;
+ if (left > MAXPHYS)
+ left = MAXPHYS;
+
+ PJDLOG_ASSERT(left >= 0 && left <= MAXPHYS);
+ PJDLOG_ASSERT(syncoff >= 0 && syncoff < amp->am_mediasize);
+ PJDLOG_ASSERT(syncoff + left >= 0 &&
+ syncoff + left <= amp->am_mediasize);
+
+ *lengthp = left;
+ return (syncoff);
+}
+
+/*
+ * Mark extent(s) containing the given region for synchronization.
+ * Most likely one of the components is unavailable.
+ */
+bool
+activemap_need_sync(struct activemap *amp, off_t offset, off_t length)
+{
+ bool modified;
+ off_t end;
+ int ext;
+
+ PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC);
+
+ modified = false;
+ end = offset + length - 1;
+
+ for (ext = off2ext(amp, offset); ext <= off2ext(amp, end); ext++) {
+ if (bit_test(amp->am_syncmap, ext)) {
+ /* Already marked for synchronization. */
+ PJDLOG_ASSERT(bit_test(amp->am_memmap, ext));
+ continue;
+ }
+ bit_set(amp->am_syncmap, ext);
+ if (!bit_test(amp->am_memmap, ext)) {
+ bit_set(amp->am_memmap, ext);
+ amp->am_ndirty++;
+ }
+ amp->am_memtab[ext] += ext2reqs(amp, ext);
+ modified = true;
+ }
+
+ return (modified);
+}
+
+void
+activemap_dump(const struct activemap *amp)
+{
+ int bit;
+
+ printf("M: ");
+ for (bit = 0; bit < amp->am_nextents; bit++)
+ printf("%d", bit_test(amp->am_memmap, bit) ? 1 : 0);
+ printf("\n");
+ printf("D: ");
+ for (bit = 0; bit < amp->am_nextents; bit++)
+ printf("%d", bit_test(amp->am_diskmap, bit) ? 1 : 0);
+ printf("\n");
+ printf("S: ");
+ for (bit = 0; bit < amp->am_nextents; bit++)
+ printf("%d", bit_test(amp->am_syncmap, bit) ? 1 : 0);
+ printf("\n");
+}
diff --git a/sbin/hastd/activemap.h b/sbin/hastd/activemap.h
new file mode 100644
index 0000000..42f0221
--- /dev/null
+++ b/sbin/hastd/activemap.h
@@ -0,0 +1,69 @@
+/*-
+ * Copyright (c) 2009-2010 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _ACTIVEMAP_H_
+#define _ACTIVEMAP_H_
+
+#include <stdbool.h>
+#include <stdint.h>
+
+struct activemap;
+
+int activemap_init(struct activemap **ampp, uint64_t mediasize,
+ uint32_t extentsize, uint32_t sectorsize, uint32_t keepdirty);
+void activemap_free(struct activemap *amp);
+
+bool activemap_write_start(struct activemap *amp, off_t offset, off_t length);
+bool activemap_write_complete(struct activemap *amp, off_t offset,
+ off_t length);
+bool activemap_extent_complete(struct activemap *amp, int extent);
+uint64_t activemap_ndirty(const struct activemap *amp);
+
+bool activemap_differ(const struct activemap *amp);
+size_t activemap_size(const struct activemap *amp);
+size_t activemap_ondisk_size(const struct activemap *amp);
+void activemap_copyin(struct activemap *amp, const unsigned char *buf,
+ size_t size);
+void activemap_merge(struct activemap *amp, const unsigned char *buf,
+ size_t size);
+const unsigned char *activemap_bitmap(struct activemap *amp, size_t *sizep);
+
+size_t activemap_calc_ondisk_size(uint64_t mediasize, uint32_t extentsize,
+ uint32_t sectorsize);
+
+void activemap_sync_rewind(struct activemap *amp);
+off_t activemap_sync_offset(struct activemap *amp, off_t *lengthp,
+ int *syncextp);
+bool activemap_need_sync(struct activemap *amp, off_t offset, off_t length);
+
+void activemap_dump(const struct activemap *amp);
+
+#endif /* !_ACTIVEMAP_H_ */
diff --git a/sbin/hastd/control.c b/sbin/hastd/control.c
new file mode 100644
index 0000000..364225b
--- /dev/null
+++ b/sbin/hastd/control.c
@@ -0,0 +1,522 @@
+/*-
+ * Copyright (c) 2009-2010 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <errno.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "hast.h"
+#include "hastd.h"
+#include "hast_checksum.h"
+#include "hast_compression.h"
+#include "hast_proto.h"
+#include "hooks.h"
+#include "nv.h"
+#include "pjdlog.h"
+#include "proto.h"
+#include "subr.h"
+
+#include "control.h"
+
+void
+child_cleanup(struct hast_resource *res)
+{
+
+ proto_close(res->hr_ctrl);
+ res->hr_ctrl = NULL;
+ if (res->hr_event != NULL) {
+ proto_close(res->hr_event);
+ res->hr_event = NULL;
+ }
+ if (res->hr_conn != NULL) {
+ proto_close(res->hr_conn);
+ res->hr_conn = NULL;
+ }
+ res->hr_workerpid = 0;
+}
+
+static void
+control_set_role_common(struct hastd_config *cfg, struct nv *nvout,
+ uint8_t role, struct hast_resource *res, const char *name, unsigned int no)
+{
+ int oldrole;
+
+ /* Name is always needed. */
+ if (name != NULL)
+ nv_add_string(nvout, name, "resource%u", no);
+
+ if (res == NULL) {
+ PJDLOG_ASSERT(cfg != NULL);
+ PJDLOG_ASSERT(name != NULL);
+
+ TAILQ_FOREACH(res, &cfg->hc_resources, hr_next) {
+ if (strcmp(res->hr_name, name) == 0)
+ break;
+ }
+ if (res == NULL) {
+ nv_add_int16(nvout, EHAST_NOENTRY, "error%u", no);
+ return;
+ }
+ }
+ PJDLOG_ASSERT(res != NULL);
+
+ /* Send previous role back. */
+ nv_add_string(nvout, role2str(res->hr_role), "role%u", no);
+
+ /* Nothing changed, return here. */
+ if (role == res->hr_role)
+ return;
+
+ pjdlog_prefix_set("[%s] (%s) ", res->hr_name, role2str(res->hr_role));
+ pjdlog_info("Role changed to %s.", role2str(role));
+
+ /* Change role to the new one. */
+ oldrole = res->hr_role;
+ res->hr_role = role;
+ pjdlog_prefix_set("[%s] (%s) ", res->hr_name, role2str(res->hr_role));
+
+ /*
+ * If previous role was primary or secondary we have to kill process
+ * doing that work.
+ */
+ if (res->hr_workerpid != 0) {
+ if (kill(res->hr_workerpid, SIGTERM) == -1) {
+ pjdlog_errno(LOG_WARNING,
+ "Unable to kill worker process %u",
+ (unsigned int)res->hr_workerpid);
+ } else if (waitpid(res->hr_workerpid, NULL, 0) !=
+ res->hr_workerpid) {
+ pjdlog_errno(LOG_WARNING,
+ "Error while waiting for worker process %u",
+ (unsigned int)res->hr_workerpid);
+ } else {
+ pjdlog_debug(1, "Worker process %u stopped.",
+ (unsigned int)res->hr_workerpid);
+ }
+ child_cleanup(res);
+ }
+
+ /* Start worker process if we are changing to primary. */
+ if (role == HAST_ROLE_PRIMARY)
+ hastd_primary(res);
+ pjdlog_prefix_set("%s", "");
+ hook_exec(res->hr_exec, "role", res->hr_name, role2str(oldrole),
+ role2str(res->hr_role), NULL);
+}
+
+void
+control_set_role(struct hast_resource *res, uint8_t role)
+{
+
+ control_set_role_common(NULL, NULL, role, res, NULL, 0);
+}
+
+static void
+control_status_worker(struct hast_resource *res, struct nv *nvout,
+ unsigned int no)
+{
+ struct nv *cnvin, *cnvout;
+ const char *str;
+ int error;
+
+ cnvin = NULL;
+
+ /*
+ * Prepare and send command to worker process.
+ */
+ cnvout = nv_alloc();
+ nv_add_uint8(cnvout, CONTROL_STATUS, "cmd");
+ error = nv_error(cnvout);
+ if (error != 0) {
+ pjdlog_common(LOG_ERR, 0, error,
+ "Unable to prepare control header");
+ goto end;
+ }
+ if (hast_proto_send(res, res->hr_ctrl, cnvout, NULL, 0) == -1) {
+ error = errno;
+ pjdlog_errno(LOG_ERR, "Unable to send control header");
+ goto end;
+ }
+
+ /*
+ * Receive response.
+ */
+ if (hast_proto_recv_hdr(res->hr_ctrl, &cnvin) == -1) {
+ error = errno;
+ pjdlog_errno(LOG_ERR, "Unable to receive control header");
+ goto end;
+ }
+
+ error = nv_get_int16(cnvin, "error");
+ if (error != 0)
+ goto end;
+
+ if ((str = nv_get_string(cnvin, "status")) == NULL) {
+ error = ENOENT;
+ pjdlog_errno(LOG_ERR, "Field 'status' is missing.");
+ goto end;
+ }
+ nv_add_string(nvout, str, "status%u", no);
+ nv_add_uint64(nvout, nv_get_uint64(cnvin, "dirty"), "dirty%u", no);
+ nv_add_uint32(nvout, nv_get_uint32(cnvin, "extentsize"),
+ "extentsize%u", no);
+ nv_add_uint32(nvout, nv_get_uint32(cnvin, "keepdirty"),
+ "keepdirty%u", no);
+ nv_add_uint64(nvout, nv_get_uint64(cnvin, "stat_read"),
+ "stat_read%u", no);
+ nv_add_uint64(nvout, nv_get_uint64(cnvin, "stat_write"),
+ "stat_write%u", no);
+ nv_add_uint64(nvout, nv_get_uint64(cnvin, "stat_delete"),
+ "stat_delete%u", no);
+ nv_add_uint64(nvout, nv_get_uint64(cnvin, "stat_flush"),
+ "stat_flush%u", no);
+ nv_add_uint64(nvout, nv_get_uint64(cnvin, "stat_activemap_update"),
+ "stat_activemap_update%u", no);
+ nv_add_uint64(nvout, nv_get_uint64(cnvin, "stat_read_error"),
+ "stat_read_error%u", no);
+ nv_add_uint64(nvout, nv_get_uint64(cnvin, "stat_write_error"),
+ "stat_write_error%u", no);
+ nv_add_uint64(nvout, nv_get_uint64(cnvin, "stat_delete_error"),
+ "stat_delete_error%u", no);
+ nv_add_uint64(nvout, nv_get_uint64(cnvin, "stat_flush_error"),
+ "stat_flush_error%u", no);
+ nv_add_uint64(nvout, nv_get_uint64(cnvin, "idle_queue_size"),
+ "idle_queue_size%u", no);
+ nv_add_uint64(nvout, nv_get_uint64(cnvin, "local_queue_size"),
+ "local_queue_size%u", no);
+ nv_add_uint64(nvout, nv_get_uint64(cnvin, "send_queue_size"),
+ "send_queue_size%u", no);
+ nv_add_uint64(nvout, nv_get_uint64(cnvin, "recv_queue_size"),
+ "recv_queue_size%u", no);
+ nv_add_uint64(nvout, nv_get_uint64(cnvin, "done_queue_size"),
+ "done_queue_size%u", no);
+end:
+ if (cnvin != NULL)
+ nv_free(cnvin);
+ if (cnvout != NULL)
+ nv_free(cnvout);
+ if (error != 0)
+ nv_add_int16(nvout, error, "error");
+}
+
+static void
+control_status(struct hastd_config *cfg, struct nv *nvout,
+ struct hast_resource *res, const char *name, unsigned int no)
+{
+
+ PJDLOG_ASSERT(cfg != NULL);
+ PJDLOG_ASSERT(nvout != NULL);
+ PJDLOG_ASSERT(name != NULL);
+
+ /* Name is always needed. */
+ nv_add_string(nvout, name, "resource%u", no);
+
+ if (res == NULL) {
+ TAILQ_FOREACH(res, &cfg->hc_resources, hr_next) {
+ if (strcmp(res->hr_name, name) == 0)
+ break;
+ }
+ if (res == NULL) {
+ nv_add_int16(nvout, EHAST_NOENTRY, "error%u", no);
+ return;
+ }
+ }
+ PJDLOG_ASSERT(res != NULL);
+ nv_add_string(nvout, res->hr_provname, "provname%u", no);
+ nv_add_string(nvout, res->hr_localpath, "localpath%u", no);
+ nv_add_string(nvout, res->hr_remoteaddr, "remoteaddr%u", no);
+ if (res->hr_sourceaddr[0] != '\0')
+ nv_add_string(nvout, res->hr_sourceaddr, "sourceaddr%u", no);
+ switch (res->hr_replication) {
+ case HAST_REPLICATION_FULLSYNC:
+ nv_add_string(nvout, "fullsync", "replication%u", no);
+ break;
+ case HAST_REPLICATION_MEMSYNC:
+ nv_add_string(nvout, "memsync", "replication%u", no);
+ break;
+ case HAST_REPLICATION_ASYNC:
+ nv_add_string(nvout, "async", "replication%u", no);
+ break;
+ default:
+ nv_add_string(nvout, "unknown", "replication%u", no);
+ break;
+ }
+ nv_add_string(nvout, checksum_name(res->hr_checksum),
+ "checksum%u", no);
+ nv_add_string(nvout, compression_name(res->hr_compression),
+ "compression%u", no);
+ nv_add_string(nvout, role2str(res->hr_role), "role%u", no);
+ nv_add_int32(nvout, res->hr_workerpid, "workerpid%u", no);
+
+ switch (res->hr_role) {
+ case HAST_ROLE_PRIMARY:
+ PJDLOG_ASSERT(res->hr_workerpid != 0);
+ /* FALLTHROUGH */
+ case HAST_ROLE_SECONDARY:
+ if (res->hr_workerpid != 0)
+ break;
+ /* FALLTHROUGH */
+ default:
+ return;
+ }
+
+ /*
+ * If we are here, it means that we have a worker process, which we
+ * want to ask some questions.
+ */
+ control_status_worker(res, nvout, no);
+}
+
+void
+control_handle(struct hastd_config *cfg)
+{
+ struct proto_conn *conn;
+ struct nv *nvin, *nvout;
+ unsigned int ii;
+ const char *str;
+ uint8_t cmd, role;
+ int error;
+
+ if (proto_accept(cfg->hc_controlconn, &conn) == -1) {
+ pjdlog_errno(LOG_ERR, "Unable to accept control connection");
+ return;
+ }
+
+ cfg->hc_controlin = conn;
+ nvin = nvout = NULL;
+ role = HAST_ROLE_UNDEF;
+
+ if (hast_proto_recv_hdr(conn, &nvin) == -1) {
+ pjdlog_errno(LOG_ERR, "Unable to receive control header");
+ nvin = NULL;
+ goto close;
+ }
+
+ /* Obtain command code. 0 means that nv_get_uint8() failed. */
+ cmd = nv_get_uint8(nvin, "cmd");
+ if (cmd == 0) {
+ pjdlog_error("Control header is missing 'cmd' field.");
+ goto close;
+ }
+
+ /* Allocate outgoing nv structure. */
+ nvout = nv_alloc();
+ if (nvout == NULL) {
+ pjdlog_error("Unable to allocate header for control response.");
+ goto close;
+ }
+
+ error = 0;
+
+ str = nv_get_string(nvin, "resource0");
+ if (str == NULL) {
+ pjdlog_error("Control header is missing 'resource0' field.");
+ error = EHAST_INVALID;
+ goto fail;
+ }
+ if (cmd == HASTCTL_CMD_SETROLE) {
+ role = nv_get_uint8(nvin, "role");
+ switch (role) {
+ case HAST_ROLE_INIT:
+ case HAST_ROLE_PRIMARY:
+ case HAST_ROLE_SECONDARY:
+ break;
+ default:
+ pjdlog_error("Invalid role received (%hhu).", role);
+ error = EHAST_INVALID;
+ goto fail;
+ }
+ }
+ if (strcmp(str, "all") == 0) {
+ struct hast_resource *res;
+
+ /* All configured resources. */
+
+ ii = 0;
+ TAILQ_FOREACH(res, &cfg->hc_resources, hr_next) {
+ switch (cmd) {
+ case HASTCTL_CMD_SETROLE:
+ control_set_role_common(cfg, nvout, role, res,
+ res->hr_name, ii++);
+ break;
+ case HASTCTL_CMD_STATUS:
+ control_status(cfg, nvout, res, res->hr_name,
+ ii++);
+ break;
+ default:
+ pjdlog_error("Invalid command received (%hhu).",
+ cmd);
+ error = EHAST_UNIMPLEMENTED;
+ goto fail;
+ }
+ }
+ } else {
+ /* Only selected resources. */
+
+ for (ii = 0; ; ii++) {
+ str = nv_get_string(nvin, "resource%u", ii);
+ if (str == NULL)
+ break;
+ switch (cmd) {
+ case HASTCTL_CMD_SETROLE:
+ control_set_role_common(cfg, nvout, role, NULL,
+ str, ii);
+ break;
+ case HASTCTL_CMD_STATUS:
+ control_status(cfg, nvout, NULL, str, ii);
+ break;
+ default:
+ pjdlog_error("Invalid command received (%hhu).",
+ cmd);
+ error = EHAST_UNIMPLEMENTED;
+ goto fail;
+ }
+ }
+ }
+ if (nv_error(nvout) != 0)
+ goto close;
+fail:
+ if (error != 0)
+ nv_add_int16(nvout, error, "error");
+
+ if (hast_proto_send(NULL, conn, nvout, NULL, 0) == -1)
+ pjdlog_errno(LOG_ERR, "Unable to send control response");
+close:
+ if (nvin != NULL)
+ nv_free(nvin);
+ if (nvout != NULL)
+ nv_free(nvout);
+ proto_close(conn);
+ cfg->hc_controlin = NULL;
+}
+
+/*
+ * Thread handles control requests from the parent.
+ */
+void *
+ctrl_thread(void *arg)
+{
+ struct hast_resource *res = arg;
+ struct nv *nvin, *nvout;
+ uint8_t cmd;
+
+ for (;;) {
+ if (hast_proto_recv_hdr(res->hr_ctrl, &nvin) == -1) {
+ if (sigexit_received)
+ pthread_exit(NULL);
+ pjdlog_errno(LOG_ERR,
+ "Unable to receive control message");
+ kill(getpid(), SIGTERM);
+ pthread_exit(NULL);
+ }
+ cmd = nv_get_uint8(nvin, "cmd");
+ if (cmd == 0) {
+ pjdlog_error("Control message is missing 'cmd' field.");
+ nv_free(nvin);
+ continue;
+ }
+ nvout = nv_alloc();
+ switch (cmd) {
+ case CONTROL_STATUS:
+ if (res->hr_remotein != NULL &&
+ res->hr_remoteout != NULL) {
+ nv_add_string(nvout, "complete", "status");
+ } else {
+ nv_add_string(nvout, "degraded", "status");
+ }
+ nv_add_uint32(nvout, (uint32_t)res->hr_extentsize,
+ "extentsize");
+ if (res->hr_role == HAST_ROLE_PRIMARY) {
+ nv_add_uint32(nvout,
+ (uint32_t)res->hr_keepdirty, "keepdirty");
+ nv_add_uint64(nvout,
+ (uint64_t)(activemap_ndirty(res->hr_amp) *
+ res->hr_extentsize), "dirty");
+ } else {
+ nv_add_uint32(nvout, (uint32_t)0, "keepdirty");
+ nv_add_uint64(nvout, (uint64_t)0, "dirty");
+ }
+ nv_add_uint64(nvout, res->hr_stat_read, "stat_read");
+ nv_add_uint64(nvout, res->hr_stat_write, "stat_write");
+ nv_add_uint64(nvout, res->hr_stat_delete,
+ "stat_delete");
+ nv_add_uint64(nvout, res->hr_stat_flush, "stat_flush");
+ nv_add_uint64(nvout, res->hr_stat_activemap_update,
+ "stat_activemap_update");
+ nv_add_uint64(nvout, res->hr_stat_read_error,
+ "stat_read_error");
+ nv_add_uint64(nvout, res->hr_stat_write_error +
+ res->hr_stat_activemap_write_error,
+ "stat_write_error");
+ nv_add_uint64(nvout, res->hr_stat_delete_error,
+ "stat_delete_error");
+ nv_add_uint64(nvout, res->hr_stat_flush_error +
+ res->hr_stat_activemap_flush_error,
+ "stat_flush_error");
+ res->output_status_aux(nvout);
+ nv_add_int16(nvout, 0, "error");
+ break;
+ case CONTROL_RELOAD:
+ /*
+ * When parent receives SIGHUP and discovers that
+ * something related to us has changes, it sends reload
+ * message to us.
+ */
+ PJDLOG_ASSERT(res->hr_role == HAST_ROLE_PRIMARY);
+ primary_config_reload(res, nvin);
+ nv_add_int16(nvout, 0, "error");
+ break;
+ default:
+ nv_add_int16(nvout, EINVAL, "error");
+ break;
+ }
+ nv_free(nvin);
+ if (nv_error(nvout) != 0) {
+ pjdlog_error("Unable to create answer on control message.");
+ nv_free(nvout);
+ continue;
+ }
+ if (hast_proto_send(NULL, res->hr_ctrl, nvout, NULL, 0) == -1) {
+ pjdlog_errno(LOG_ERR,
+ "Unable to send reply to control message");
+ }
+ nv_free(nvout);
+ }
+ /* NOTREACHED */
+ return (NULL);
+}
diff --git a/sbin/hastd/control.h b/sbin/hastd/control.h
new file mode 100644
index 0000000..0795c70
--- /dev/null
+++ b/sbin/hastd/control.h
@@ -0,0 +1,49 @@
+/*-
+ * Copyright (c) 2009-2010 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _CONTROL_H_
+#define _CONTROL_H_
+
+#define CONTROL_STATUS 10
+#define CONTROL_RELOAD 11
+
+struct hastd_config;
+struct hast_resource;
+
+void child_cleanup(struct hast_resource *res);
+
+void control_set_role(struct hast_resource *res, uint8_t role);
+
+void control_handle(struct hastd_config *cfg);
+
+void *ctrl_thread(void *arg);
+
+#endif /* !_CONTROL_H_ */
diff --git a/sbin/hastd/crc32.c b/sbin/hastd/crc32.c
new file mode 100644
index 0000000..e8bc74a
--- /dev/null
+++ b/sbin/hastd/crc32.c
@@ -0,0 +1,115 @@
+/*-
+ * COPYRIGHT (C) 1986 Gary S. Brown. You may use this program, or
+ * code or tables extracted from it, as desired without restriction.
+ */
+
+/*
+ * First, the polynomial itself and its table of feedback terms. The
+ * polynomial is
+ * X^32+X^26+X^23+X^22+X^16+X^12+X^11+X^10+X^8+X^7+X^5+X^4+X^2+X^1+X^0
+ *
+ * Note that we take it "backwards" and put the highest-order term in
+ * the lowest-order bit. The X^32 term is "implied"; the LSB is the
+ * X^31 term, etc. The X^0 term (usually shown as "+1") results in
+ * the MSB being 1
+ *
+ * Note that the usual hardware shift register implementation, which
+ * is what we're using (we're merely optimizing it by doing eight-bit
+ * chunks at a time) shifts bits into the lowest-order term. In our
+ * implementation, that means shifting towards the right. Why do we
+ * do it this way? Because the calculated CRC must be transmitted in
+ * order from highest-order term to lowest-order term. UARTs transmit
+ * characters in order from LSB to MSB. By storing the CRC this way
+ * we hand it to the UART in the order low-byte to high-byte; the UART
+ * sends each low-bit to hight-bit; and the result is transmission bit
+ * by bit from highest- to lowest-order term without requiring any bit
+ * shuffling on our part. Reception works similarly
+ *
+ * The feedback terms table consists of 256, 32-bit entries. Notes
+ *
+ * The table can be generated at runtime if desired; code to do so
+ * is shown later. It might not be obvious, but the feedback
+ * terms simply represent the results of eight shift/xor opera
+ * tions for all combinations of data and CRC register values
+ *
+ * The values must be right-shifted by eight bits by the "updcrc
+ * logic; the shift must be unsigned (bring in zeroes). On some
+ * hardware you could probably optimize the shift in assembler by
+ * using byte-swap instructions
+ * polynomial $edb88320
+ *
+ *
+ * CRC32 code derived from work by Gary S. Brown.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <stdint.h>
+
+#include <crc32.h>
+
+uint32_t crc32_tab[] = {
+ 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
+ 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
+ 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
+ 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
+ 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
+ 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
+ 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
+ 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
+ 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
+ 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
+ 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
+ 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
+ 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
+ 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
+ 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
+ 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
+ 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
+ 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
+ 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
+ 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
+ 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
+ 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
+ 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
+ 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
+ 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
+ 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
+ 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
+ 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
+ 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
+ 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
+ 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
+ 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
+ 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
+ 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
+ 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
+ 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
+ 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
+ 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
+ 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
+ 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
+ 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
+ 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
+ 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
+};
+
+/*
+ * A function that calculates the CRC-32 based on the table above is
+ * given below for documentation purposes. An equivalent implementation
+ * of this function that's actually used in the kernel can be found
+ * in sys/libkern.h, where it can be inlined.
+ *
+ * uint32_t
+ * crc32(const void *buf, size_t size)
+ * {
+ * const uint8_t *p = buf;
+ * uint32_t crc;
+ *
+ * crc = ~0U;
+ * while (size--)
+ * crc = crc32_tab[(crc ^ *p++) & 0xFF] ^ (crc >> 8);
+ * return crc ^ ~0U;
+ * }
+ */
diff --git a/sbin/hastd/crc32.h b/sbin/hastd/crc32.h
new file mode 100644
index 0000000..3812a83
--- /dev/null
+++ b/sbin/hastd/crc32.h
@@ -0,0 +1,28 @@
+/*-
+ * COPYRIGHT (C) 1986 Gary S. Brown. You may use this program, or
+ * code or tables extracted from it, as desired without restriction.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _CRC32_H_
+#define _CRC32_H_
+
+#include <stdint.h> /* uint32_t */
+#include <stdlib.h> /* size_t */
+
+extern uint32_t crc32_tab[];
+
+static __inline uint32_t
+crc32(const void *buf, size_t size)
+{
+ const uint8_t *p = buf;
+ uint32_t crc;
+
+ crc = ~0U;
+ while (size--)
+ crc = crc32_tab[(crc ^ *p++) & 0xFF] ^ (crc >> 8);
+ return (crc ^ ~0U);
+}
+
+#endif /* !_CRC32_H_ */
diff --git a/sbin/hastd/ebuf.c b/sbin/hastd/ebuf.c
new file mode 100644
index 0000000..1ae2a26
--- /dev/null
+++ b/sbin/hastd/ebuf.c
@@ -0,0 +1,259 @@
+/*-
+ * Copyright (c) 2009-2010 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <strings.h>
+#include <unistd.h>
+
+#include <pjdlog.h>
+
+#include "ebuf.h"
+
+#ifndef PJDLOG_ASSERT
+#include <assert.h>
+#define PJDLOG_ASSERT(...) assert(__VA_ARGS__)
+#endif
+
+#define EBUF_MAGIC 0xeb0f41c
+struct ebuf {
+ /* Magic to assert the caller uses valid structure. */
+ int eb_magic;
+ /* Address where we did the allocation. */
+ unsigned char *eb_start;
+ /* Allocation end address. */
+ unsigned char *eb_end;
+ /* Start of real data. */
+ unsigned char *eb_used;
+ /* Size of real data. */
+ size_t eb_size;
+};
+
+static int ebuf_head_extend(struct ebuf *eb, size_t size);
+static int ebuf_tail_extend(struct ebuf *eb, size_t size);
+
+struct ebuf *
+ebuf_alloc(size_t size)
+{
+ struct ebuf *eb;
+ int rerrno;
+
+ eb = malloc(sizeof(*eb));
+ if (eb == NULL)
+ return (NULL);
+ size += PAGE_SIZE;
+ eb->eb_start = malloc(size);
+ if (eb->eb_start == NULL) {
+ rerrno = errno;
+ free(eb);
+ errno = rerrno;
+ return (NULL);
+ }
+ eb->eb_end = eb->eb_start + size;
+ /*
+ * We set start address for real data not at the first entry, because
+ * we want to be able to add data at the front.
+ */
+ eb->eb_used = eb->eb_start + PAGE_SIZE / 4;
+ eb->eb_size = 0;
+ eb->eb_magic = EBUF_MAGIC;
+
+ return (eb);
+}
+
+void
+ebuf_free(struct ebuf *eb)
+{
+
+ PJDLOG_ASSERT(eb != NULL && eb->eb_magic == EBUF_MAGIC);
+
+ eb->eb_magic = 0;
+
+ free(eb->eb_start);
+ free(eb);
+}
+
+int
+ebuf_add_head(struct ebuf *eb, const void *data, size_t size)
+{
+
+ PJDLOG_ASSERT(eb != NULL && eb->eb_magic == EBUF_MAGIC);
+
+ if (size > (size_t)(eb->eb_used - eb->eb_start)) {
+ /*
+ * We can't add more entries at the front, so we have to extend
+ * our buffer.
+ */
+ if (ebuf_head_extend(eb, size) == -1)
+ return (-1);
+ }
+ PJDLOG_ASSERT(size <= (size_t)(eb->eb_used - eb->eb_start));
+
+ eb->eb_size += size;
+ eb->eb_used -= size;
+ /*
+ * If data is NULL the caller just wants to reserve place.
+ */
+ if (data != NULL)
+ bcopy(data, eb->eb_used, size);
+
+ return (0);
+}
+
+int
+ebuf_add_tail(struct ebuf *eb, const void *data, size_t size)
+{
+
+ PJDLOG_ASSERT(eb != NULL && eb->eb_magic == EBUF_MAGIC);
+
+ if (size > (size_t)(eb->eb_end - (eb->eb_used + eb->eb_size))) {
+ /*
+ * We can't add more entries at the back, so we have to extend
+ * our buffer.
+ */
+ if (ebuf_tail_extend(eb, size) == -1)
+ return (-1);
+ }
+ PJDLOG_ASSERT(size <=
+ (size_t)(eb->eb_end - (eb->eb_used + eb->eb_size)));
+
+ /*
+ * If data is NULL the caller just wants to reserve space.
+ */
+ if (data != NULL)
+ bcopy(data, eb->eb_used + eb->eb_size, size);
+ eb->eb_size += size;
+
+ return (0);
+}
+
+void
+ebuf_del_head(struct ebuf *eb, size_t size)
+{
+
+ PJDLOG_ASSERT(eb != NULL && eb->eb_magic == EBUF_MAGIC);
+ PJDLOG_ASSERT(size <= eb->eb_size);
+
+ eb->eb_used += size;
+ eb->eb_size -= size;
+}
+
+void
+ebuf_del_tail(struct ebuf *eb, size_t size)
+{
+
+ PJDLOG_ASSERT(eb != NULL && eb->eb_magic == EBUF_MAGIC);
+ PJDLOG_ASSERT(size <= eb->eb_size);
+
+ eb->eb_size -= size;
+}
+
+/*
+ * Return pointer to the data and data size.
+ */
+void *
+ebuf_data(struct ebuf *eb, size_t *sizep)
+{
+
+ PJDLOG_ASSERT(eb != NULL && eb->eb_magic == EBUF_MAGIC);
+
+ if (sizep != NULL)
+ *sizep = eb->eb_size;
+ return (eb->eb_size > 0 ? eb->eb_used : NULL);
+}
+
+/*
+ * Return data size.
+ */
+size_t
+ebuf_size(struct ebuf *eb)
+{
+
+ PJDLOG_ASSERT(eb != NULL && eb->eb_magic == EBUF_MAGIC);
+
+ return (eb->eb_size);
+}
+
+/*
+ * Function adds size + (PAGE_SIZE / 4) bytes at the front of the buffer..
+ */
+static int
+ebuf_head_extend(struct ebuf *eb, size_t size)
+{
+ unsigned char *newstart, *newused;
+ size_t newsize;
+
+ PJDLOG_ASSERT(eb != NULL && eb->eb_magic == EBUF_MAGIC);
+
+ newsize = eb->eb_end - eb->eb_start + (PAGE_SIZE / 4) + size;
+
+ newstart = malloc(newsize);
+ if (newstart == NULL)
+ return (-1);
+ newused =
+ newstart + (PAGE_SIZE / 4) + size + (eb->eb_used - eb->eb_start);
+
+ bcopy(eb->eb_used, newused, eb->eb_size);
+
+ eb->eb_start = newstart;
+ eb->eb_used = newused;
+ eb->eb_end = newstart + newsize;
+
+ return (0);
+}
+
+/*
+ * Function adds size + ((3 * PAGE_SIZE) / 4) bytes at the back.
+ */
+static int
+ebuf_tail_extend(struct ebuf *eb, size_t size)
+{
+ unsigned char *newstart;
+ size_t newsize;
+
+ PJDLOG_ASSERT(eb != NULL && eb->eb_magic == EBUF_MAGIC);
+
+ newsize = eb->eb_end - eb->eb_start + size + ((3 * PAGE_SIZE) / 4);
+
+ newstart = realloc(eb->eb_start, newsize);
+ if (newstart == NULL)
+ return (-1);
+
+ eb->eb_used = newstart + (eb->eb_used - eb->eb_start);
+ eb->eb_start = newstart;
+ eb->eb_end = newstart + newsize;
+
+ return (0);
+}
diff --git a/sbin/hastd/ebuf.h b/sbin/hastd/ebuf.h
new file mode 100644
index 0000000..06275e7
--- /dev/null
+++ b/sbin/hastd/ebuf.h
@@ -0,0 +1,51 @@
+/*-
+ * Copyright (c) 2009-2010 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _EBUF_H_
+#define _EBUF_H_
+
+#include <stdlib.h> /* size_t */
+
+struct ebuf;
+
+struct ebuf *ebuf_alloc(size_t size);
+void ebuf_free(struct ebuf *eb);
+
+int ebuf_add_head(struct ebuf *eb, const void *data, size_t size);
+int ebuf_add_tail(struct ebuf *eb, const void *data, size_t size);
+
+void ebuf_del_head(struct ebuf *eb, size_t size);
+void ebuf_del_tail(struct ebuf *eb, size_t size);
+
+void *ebuf_data(struct ebuf *eb, size_t *sizep);
+size_t ebuf_size(struct ebuf *eb);
+
+#endif /* !_EBUF_H_ */
diff --git a/sbin/hastd/event.c b/sbin/hastd/event.c
new file mode 100644
index 0000000..ef65df1
--- /dev/null
+++ b/sbin/hastd/event.c
@@ -0,0 +1,161 @@
+/*-
+ * Copyright (c) 2010 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <errno.h>
+
+#include "hast.h"
+#include "hast_proto.h"
+#include "hooks.h"
+#include "nv.h"
+#include "pjdlog.h"
+#include "proto.h"
+#include "subr.h"
+
+#include "event.h"
+
+void
+event_send(const struct hast_resource *res, int event)
+{
+ struct nv *nvin, *nvout;
+ int error;
+
+ PJDLOG_ASSERT(res != NULL);
+ PJDLOG_ASSERT(event >= EVENT_MIN && event <= EVENT_MAX);
+
+ nvin = nvout = NULL;
+
+ /*
+ * Prepare and send event to parent process.
+ */
+ nvout = nv_alloc();
+ nv_add_uint8(nvout, (uint8_t)event, "event");
+ error = nv_error(nvout);
+ if (error != 0) {
+ pjdlog_common(LOG_ERR, 0, error,
+ "Unable to prepare event header");
+ goto done;
+ }
+ if (hast_proto_send(res, res->hr_event, nvout, NULL, 0) == -1) {
+ pjdlog_errno(LOG_ERR, "Unable to send event header");
+ goto done;
+ }
+ if (hast_proto_recv_hdr(res->hr_event, &nvin) == -1) {
+ pjdlog_errno(LOG_ERR, "Unable to receive event header");
+ goto done;
+ }
+ /*
+ * Do nothing with the answer. We only wait for it to be sure not
+ * to exit too quickly after sending an event and exiting immediately.
+ */
+done:
+ if (nvin != NULL)
+ nv_free(nvin);
+ if (nvout != NULL)
+ nv_free(nvout);
+}
+
+int
+event_recv(const struct hast_resource *res)
+{
+ struct nv *nvin, *nvout;
+ const char *evstr;
+ uint8_t event;
+ int error;
+
+ PJDLOG_ASSERT(res != NULL);
+
+ nvin = nvout = NULL;
+
+ if (hast_proto_recv_hdr(res->hr_event, &nvin) == -1) {
+ /*
+ * First error log as debug. This is because worker process
+ * most likely exited.
+ */
+ pjdlog_common(LOG_DEBUG, 1, errno,
+ "Unable to receive event header");
+ goto fail;
+ }
+
+ event = nv_get_uint8(nvin, "event");
+ if (event == EVENT_NONE) {
+ pjdlog_error("Event header is missing 'event' field.");
+ goto fail;
+ }
+
+ switch (event) {
+ case EVENT_CONNECT:
+ evstr = "connect";
+ break;
+ case EVENT_DISCONNECT:
+ evstr = "disconnect";
+ break;
+ case EVENT_SYNCSTART:
+ evstr = "syncstart";
+ break;
+ case EVENT_SYNCDONE:
+ evstr = "syncdone";
+ break;
+ case EVENT_SYNCINTR:
+ evstr = "syncintr";
+ break;
+ case EVENT_SPLITBRAIN:
+ evstr = "split-brain";
+ break;
+ default:
+ pjdlog_error("Event header contain invalid event number (%hhu).",
+ event);
+ goto fail;
+ }
+
+ pjdlog_prefix_set("[%s] (%s) ", res->hr_name, role2str(res->hr_role));
+ hook_exec(res->hr_exec, evstr, res->hr_name, NULL);
+ pjdlog_prefix_set("%s", "");
+
+ nvout = nv_alloc();
+ nv_add_int16(nvout, 0, "error");
+ error = nv_error(nvout);
+ if (error != 0) {
+ pjdlog_common(LOG_ERR, 0, error,
+ "Unable to prepare event header");
+ goto fail;
+ }
+ if (hast_proto_send(res, res->hr_event, nvout, NULL, 0) == -1) {
+ pjdlog_errno(LOG_ERR, "Unable to send event header");
+ goto fail;
+ }
+ nv_free(nvin);
+ nv_free(nvout);
+ return (0);
+fail:
+ if (nvin != NULL)
+ nv_free(nvin);
+ if (nvout != NULL)
+ nv_free(nvout);
+ return (-1);
+}
diff --git a/sbin/hastd/event.h b/sbin/hastd/event.h
new file mode 100644
index 0000000..1614bf1
--- /dev/null
+++ b/sbin/hastd/event.h
@@ -0,0 +1,46 @@
+/*-
+ * Copyright (c) 2010 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _EVENT_H_
+#define _EVENT_H_
+
+#define EVENT_NONE 0
+#define EVENT_CONNECT 1
+#define EVENT_DISCONNECT 2
+#define EVENT_SYNCSTART 3
+#define EVENT_SYNCDONE 4
+#define EVENT_SYNCINTR 5
+#define EVENT_SPLITBRAIN 6
+
+#define EVENT_MIN EVENT_CONNECT
+#define EVENT_MAX EVENT_SPLITBRAIN
+
+void event_send(const struct hast_resource *res, int event);
+int event_recv(const struct hast_resource *res);
+
+#endif /* !_EVENT_H_ */
diff --git a/sbin/hastd/hast.conf.5 b/sbin/hastd/hast.conf.5
new file mode 100644
index 0000000..9267e36
--- /dev/null
+++ b/sbin/hastd/hast.conf.5
@@ -0,0 +1,441 @@
+.\" Copyright (c) 2010 The FreeBSD Foundation
+.\" Copyright (c) 2010-2012 Pawel Jakub Dawidek <pawel@dawidek.net>
+.\" All rights reserved.
+.\"
+.\" This documentation was written by Pawel Jakub Dawidek under sponsorship from
+.\" the FreeBSD Foundation.
+.\"
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd January 25, 2012
+.Dt HAST.CONF 5
+.Os
+.Sh NAME
+.Nm hast.conf
+.Nd configuration file for the
+.Xr hastd 8
+daemon and the
+.Xr hastctl 8
+utility
+.Sh DESCRIPTION
+The
+.Nm
+file is used by both
+.Xr hastd 8
+daemon
+and
+.Xr hastctl 8
+control utility.
+Configuration file is designed in a way that exactly the same file can be
+(and should be) used on both HAST nodes.
+Every line starting with # is treated as comment and ignored.
+.Sh CONFIGURATION FILE SYNTAX
+General syntax of the
+.Nm
+file is following:
+.Bd -literal -offset indent
+# Global section
+control <addr>
+listen <addr>
+replication <mode>
+checksum <algorithm>
+compression <algorithm>
+timeout <seconds>
+exec <path>
+metaflush on | off
+pidfile <path>
+
+on <node> {
+ # Node section
+ control <addr>
+ listen <addr>
+ pidfile <path>
+}
+
+on <node> {
+ # Node section
+ control <addr>
+ listen <addr>
+ pidfile <path>
+}
+
+resource <name> {
+ # Resource section
+ replication <mode>
+ checksum <algorithm>
+ compression <algorithm>
+ name <name>
+ local <path>
+ timeout <seconds>
+ exec <path>
+ metaflush on | off
+
+ on <node> {
+ # Resource-node section
+ name <name>
+ # Required
+ local <path>
+ metaflush on | off
+ # Required
+ remote <addr>
+ source <addr>
+ }
+ on <node> {
+ # Resource-node section
+ name <name>
+ # Required
+ local <path>
+ metaflush on | off
+ # Required
+ remote <addr>
+ source <addr>
+ }
+}
+.Ed
+.Pp
+Most of the various available configuration parameters are optional.
+If parameter is not defined in the particular section, it will be
+inherited from the parent section.
+For example, if the
+.Ic listen
+parameter is not defined in the node section, it will be inherited from
+the global section.
+In case the global section does not define the
+.Ic listen
+parameter at all, the default value will be used.
+.Sh CONFIGURATION FILE DESCRIPTION
+The
+.Aq node
+argument can be replaced either by a full hostname as obtained by
+.Xr gethostname 3 ,
+only first part of the hostname, by node's UUID as found in the
+.Va kern.hostuuid
+.Xr sysctl 8
+variable
+or by node's hostid as found in the
+.Va kern.hostid
+.Xr sysctl 8
+variable.
+.Pp
+The following statements are available:
+.Bl -tag -width ".Ic xxxx"
+.It Ic control Aq addr
+.Pp
+Address for communication with
+.Xr hastctl 8 .
+Each of the following examples defines the same control address:
+.Bd -literal -offset indent
+uds:///var/run/hastctl
+unix:///var/run/hastctl
+/var/run/hastctl
+.Ed
+.Pp
+The default value is
+.Pa uds:///var/run/hastctl .
+.It Ic pidfile Aq path
+.Pp
+File in which to store the process ID of the main
+.Xr hastd 8
+process.
+.Pp
+The default value is
+.Pa /var/run/hastd.pid .
+.It Ic listen Aq addr
+.Pp
+Address to listen on in form of:
+.Bd -literal -offset indent
+protocol://protocol-specific-address
+.Ed
+.Pp
+Each of the following examples defines the same listen address:
+.Bd -literal -offset indent
+0.0.0.0
+0.0.0.0:8457
+tcp://0.0.0.0
+tcp://0.0.0.0:8457
+tcp4://0.0.0.0
+tcp4://0.0.0.0:8457
+.Ed
+.Pp
+Multiple listen addresses can be specified.
+By default
+.Nm hastd
+listens on
+.Pa tcp4://0.0.0.0:8457
+and
+.Pa tcp6://[::]:8457
+if kernel supports IPv4 and IPv6 respectively.
+.It Ic replication Aq mode
+.Pp
+Replication mode should be one of the following:
+.Bl -tag -width ".Ic xxxx"
+.It Ic memsync
+.Pp
+Report the write operation as completed when local write completes and
+when the remote node acknowledges the data receipt, but before it
+actually stores the data.
+The data on remote node will be stored directly after sending
+acknowledgement.
+This mode is intended to reduce latency, but still provides a very good
+reliability.
+The only situation where some small amount of data could be lost is when
+the data is stored on primary node and sent to the secondary.
+Secondary node then acknowledges data receipt and primary reports
+success to an application.
+However, it may happen that the secondary goes down before the received
+data is really stored locally.
+Before secondary node returns, primary node dies entirely.
+When the secondary node comes back to life it becomes the new primary.
+Unfortunately some small amount of data which was confirmed to be stored
+to the application was lost.
+The risk of such a situation is very small.
+The
+.Ic memsync
+replication mode is the default.
+.It Ic fullsync
+.Pp
+Mark the write operation as completed when local as well as remote
+write completes.
+This is the safest and the slowest replication mode.
+.It Ic async
+.Pp
+The write operation is reported as complete right after the local write
+completes.
+This is the fastest and the most dangerous replication mode.
+This mode should be used when replicating to a distant node where
+latency is too high for other modes.
+.El
+.It Ic checksum Aq algorithm
+.Pp
+Checksum algorithm should be one of the following:
+.Bl -tag -width ".Ic sha256"
+.It Ic none
+No checksum will be calculated for the data being send over the network.
+This is the default setting.
+.It Ic crc32
+CRC32 checksum will be calculated.
+.It Ic sha256
+SHA256 checksum will be calculated.
+.El
+.It Ic compression Aq algorithm
+.Pp
+Compression algorithm should be one of the following:
+.Bl -tag -width ".Ic none"
+.It Ic none
+Data send over the network will not be compressed.
+.It Ic hole
+Only blocks that contain all zeros will be compressed.
+This is very useful for initial synchronization where potentially many blocks
+are still all zeros.
+There should be no measurable performance overhead when this algorithm is being
+used.
+This is the default setting.
+.It Ic lzf
+The LZF algorithm by Marc Alexander Lehmann will be used to compress the data
+send over the network.
+LZF is very fast, general purpose compression algorithm.
+.El
+.It Ic timeout Aq seconds
+.Pp
+Connection timeout in seconds.
+The default value is
+.Va 20 .
+.It Ic exec Aq path
+.Pp
+Execute the given program on various HAST events.
+Below is the list of currently implemented events and arguments the given
+program is executed with:
+.Bl -tag -width ".Ic xxxx"
+.It Ic "<path> role <resource> <oldrole> <newrole>"
+.Pp
+Executed on both primary and secondary nodes when resource role is changed.
+.It Ic "<path> connect <resource>"
+.Pp
+Executed on both primary and secondary nodes when connection for the given
+resource between the nodes is established.
+.It Ic "<path> disconnect <resource>"
+.Pp
+Executed on both primary and secondary nodes when connection for the given
+resource between the nodes is lost.
+.It Ic "<path> syncstart <resource>"
+.Pp
+Executed on primary node when synchronization process of secondary node is
+started.
+.It Ic "<path> syncdone <resource>"
+.Pp
+Executed on primary node when synchronization process of secondary node is
+completed successfully.
+.It Ic "<path> syncintr <resource>"
+.Pp
+Executed on primary node when synchronization process of secondary node is
+interrupted, most likely due to secondary node outage or connection failure
+between the nodes.
+.It Ic "<path> split-brain <resource>"
+.Pp
+Executed on both primary and secondary nodes when split-brain condition is
+detected.
+.El
+.Pp
+The
+.Aq path
+argument should contain full path to executable program.
+If the given program exits with code different than
+.Va 0 ,
+.Nm hastd
+will log it as an error.
+.Pp
+The
+.Aq resource
+argument is resource name from the configuration file.
+.Pp
+The
+.Aq oldrole
+argument is previous resource role (before the change).
+It can be one of:
+.Ar init ,
+.Ar secondary ,
+.Ar primary .
+.Pp
+The
+.Aq newrole
+argument is current resource role (after the change).
+It can be one of:
+.Ar init ,
+.Ar secondary ,
+.Ar primary .
+.It Ic metaflush on | off
+.Pp
+When set to
+.Va on ,
+flush write cache of the local provider after every metadata (activemap) update.
+Flushing write cache ensures that provider will not reorder writes and that
+metadata will be properly updated before real data is stored.
+If the local provider does not support flushing write cache (it returns
+.Er EOPNOTSUPP
+on the
+.Cm BIO_FLUSH
+request),
+.Nm hastd
+will disable
+.Ic metaflush
+automatically.
+The default value is
+.Va on .
+.It Ic name Aq name
+.Pp
+GEOM provider name that will appear as
+.Pa /dev/hast/<name> .
+If name is not defined, resource name will be used as provider name.
+.It Ic local Aq path
+.Pp
+Path to the local component which will be used as backend provider for
+the resource.
+This can be either GEOM provider or regular file.
+.It Ic remote Aq addr
+.Pp
+Address of the remote
+.Nm hastd
+daemon.
+Format is the same as for the
+.Ic listen
+statement.
+When operating as a primary node this address will be used to connect to
+the secondary node.
+When operating as a secondary node only connections from this address
+will be accepted.
+.Pp
+A special value of
+.Va none
+can be used when the remote address is not yet known (eg. the other node is not
+set up yet).
+.It Ic source Aq addr
+.Pp
+Local address to bind to before connecting to the remote
+.Nm hastd
+daemon.
+Format is the same as for the
+.Ic listen
+statement.
+.El
+.Sh FILES
+.Bl -tag -width ".Pa /var/run/hastctl" -compact
+.It Pa /etc/hast.conf
+The default
+.Xr hastctl 8
+and
+.Xr hastd 8
+configuration file.
+.It Pa /var/run/hastctl
+Control socket used by the
+.Xr hastctl 8
+control utility to communicate with the
+.Xr hastd 8
+daemon.
+.El
+.Sh EXAMPLES
+The example configuration file can look as follows:
+.Bd -literal -offset indent
+listen tcp://0.0.0.0
+
+on hasta {
+ listen tcp://2001:db8::1/64
+}
+on hastb {
+ listen tcp://2001:db8::2/64
+}
+
+resource shared {
+ local /dev/da0
+
+ on hasta {
+ remote tcp://10.0.0.2
+ }
+ on hastb {
+ remote tcp://10.0.0.1
+ }
+}
+resource tank {
+ on hasta {
+ local /dev/mirror/tanka
+ source tcp://10.0.0.1
+ remote tcp://10.0.0.2
+ }
+ on hastb {
+ local /dev/mirror/tankb
+ source tcp://10.0.0.2
+ remote tcp://10.0.0.1
+ }
+}
+.Ed
+.Sh SEE ALSO
+.Xr gethostname 3 ,
+.Xr geom 4 ,
+.Xr hastctl 8 ,
+.Xr hastd 8
+.Sh AUTHORS
+The
+.Nm
+was written by
+.An Pawel Jakub Dawidek Aq Mt pjd@FreeBSD.org
+under sponsorship of the FreeBSD Foundation.
diff --git a/sbin/hastd/hast.h b/sbin/hastd/hast.h
new file mode 100644
index 0000000..c529de5
--- /dev/null
+++ b/sbin/hastd/hast.h
@@ -0,0 +1,269 @@
+/*-
+ * Copyright (c) 2009-2010 The FreeBSD Foundation
+ * Copyright (c) 2011 Pawel Jakub Dawidek <pawel@dawidek.net>
+ * All rights reserved.
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _HAST_H_
+#define _HAST_H_
+
+#include <sys/queue.h>
+#include <sys/socket.h>
+
+#include <arpa/inet.h>
+
+#include <netinet/in.h>
+
+#include <limits.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#include <activemap.h>
+
+#include "proto.h"
+
+/*
+ * Version history:
+ * 0 - initial version
+ * 1 - HIO_KEEPALIVE added
+ * 2 - "memsync" and "received" attributes added for memsync mode
+ */
+#define HAST_PROTO_VERSION 2
+
+#define EHAST_OK 0
+#define EHAST_NOENTRY 1
+#define EHAST_INVALID 2
+#define EHAST_NOMEMORY 3
+#define EHAST_UNIMPLEMENTED 4
+
+#define HASTCTL_CMD_UNKNOWN 0
+#define HASTCTL_CMD_SETROLE 1
+#define HASTCTL_CMD_STATUS 2
+
+#define HAST_ROLE_UNDEF 0
+#define HAST_ROLE_INIT 1
+#define HAST_ROLE_PRIMARY 2
+#define HAST_ROLE_SECONDARY 3
+
+#define HAST_SYNCSRC_UNDEF 0
+#define HAST_SYNCSRC_PRIMARY 1
+#define HAST_SYNCSRC_SECONDARY 2
+
+#define HIO_UNDEF 0
+#define HIO_READ 1
+#define HIO_WRITE 2
+#define HIO_DELETE 3
+#define HIO_FLUSH 4
+#define HIO_KEEPALIVE 5
+
+#define HAST_USER "hast"
+#define HAST_TIMEOUT 20
+#define HAST_CONFIG "/etc/hast.conf"
+#define HAST_CONTROL "/var/run/hastctl"
+#define HASTD_LISTEN_TCP4 "tcp4://0.0.0.0:8457"
+#define HASTD_LISTEN_TCP6 "tcp6://[::]:8457"
+#define HASTD_PIDFILE "/var/run/hastd.pid"
+
+/* Default extent size. */
+#define HAST_EXTENTSIZE 2097152
+/* Default maximum number of extents that are kept dirty. */
+#define HAST_KEEPDIRTY 64
+
+#define HAST_ADDRSIZE 1024
+#define HAST_TOKEN_SIZE 16
+
+/* Number of seconds to sleep between reconnect retries or keepalive packets. */
+#define HAST_KEEPALIVE 10
+
+struct hastd_listen {
+ /* Address to listen on. */
+ char hl_addr[HAST_ADDRSIZE];
+ /* Protocol-specific data. */
+ struct proto_conn *hl_conn;
+ TAILQ_ENTRY(hastd_listen) hl_next;
+};
+
+struct hastd_config {
+ /* Address to communicate with hastctl(8). */
+ char hc_controladdr[HAST_ADDRSIZE];
+ /* Protocol-specific data. */
+ struct proto_conn *hc_controlconn;
+ /* Incoming control connection. */
+ struct proto_conn *hc_controlin;
+ /* PID file path. */
+ char hc_pidfile[PATH_MAX];
+ /* List of addresses to listen on. */
+ TAILQ_HEAD(, hastd_listen) hc_listen;
+ /* List of resources. */
+ TAILQ_HEAD(, hast_resource) hc_resources;
+};
+
+#define HAST_REPLICATION_FULLSYNC 0
+#define HAST_REPLICATION_MEMSYNC 1
+#define HAST_REPLICATION_ASYNC 2
+
+#define HAST_COMPRESSION_NONE 0
+#define HAST_COMPRESSION_HOLE 1
+#define HAST_COMPRESSION_LZF 2
+
+#define HAST_CHECKSUM_NONE 0
+#define HAST_CHECKSUM_CRC32 1
+#define HAST_CHECKSUM_SHA256 2
+
+struct nv;
+
+/*
+ * Structure that describes single resource.
+ */
+struct hast_resource {
+ /* Resource name. */
+ char hr_name[NAME_MAX];
+ /* Negotiated replication mode (HAST_REPLICATION_*). */
+ int hr_replication;
+ /* Configured replication mode (HAST_REPLICATION_*). */
+ int hr_original_replication;
+ /* Provider name that will appear in /dev/hast/. */
+ char hr_provname[NAME_MAX];
+ /* Synchronization extent size. */
+ int hr_extentsize;
+ /* Maximum number of extents that are kept dirty. */
+ int hr_keepdirty;
+ /* Path to a program to execute on various events. */
+ char hr_exec[PATH_MAX];
+ /* Compression algorithm. */
+ int hr_compression;
+ /* Checksum algorithm. */
+ int hr_checksum;
+ /* Protocol version. */
+ int hr_version;
+
+ /* Path to local component. */
+ char hr_localpath[PATH_MAX];
+ /* Descriptor to access local component. */
+ int hr_localfd;
+ /* Offset into local component. */
+ off_t hr_localoff;
+ /* Size of usable space. */
+ off_t hr_datasize;
+ /* Size of entire local provider. */
+ off_t hr_local_mediasize;
+ /* Sector size of local provider. */
+ unsigned int hr_local_sectorsize;
+ /* Is flushing write cache supported by the local provider? */
+ bool hr_localflush;
+ /* Flush write cache on metadata updates? */
+ int hr_metaflush;
+
+ /* Descriptor for /dev/ggctl communication. */
+ int hr_ggatefd;
+ /* Unit number for ggate communication. */
+ int hr_ggateunit;
+
+ /* Address of the remote component. */
+ char hr_remoteaddr[HAST_ADDRSIZE];
+ /* Local address to bind to for outgoing connections. */
+ char hr_sourceaddr[HAST_ADDRSIZE];
+ /* Connection for incoming data. */
+ struct proto_conn *hr_remotein;
+ /* Connection for outgoing data. */
+ struct proto_conn *hr_remoteout;
+ /* Token to verify both in and out connection are coming from
+ the same node (not necessarily from the same address). */
+ unsigned char hr_token[HAST_TOKEN_SIZE];
+ /* Connection timeout. */
+ int hr_timeout;
+
+ /* Resource unique identifier. */
+ uint64_t hr_resuid;
+ /* Primary's local modification count. */
+ uint64_t hr_primary_localcnt;
+ /* Primary's remote modification count. */
+ uint64_t hr_primary_remotecnt;
+ /* Secondary's local modification count. */
+ uint64_t hr_secondary_localcnt;
+ /* Secondary's remote modification count. */
+ uint64_t hr_secondary_remotecnt;
+ /* Synchronization source. */
+ uint8_t hr_syncsrc;
+
+ /* Resource role: HAST_ROLE_{INIT,PRIMARY,SECONDARY}. */
+ int hr_role;
+ /* Previous resource role: HAST_ROLE_{INIT,PRIMARY,SECONDARY}. */
+ int hr_previous_role;
+ /* PID of child worker process. 0 - no child. */
+ pid_t hr_workerpid;
+ /* Control commands from parent to child. */
+ struct proto_conn *hr_ctrl;
+ /* Events from child to parent. */
+ struct proto_conn *hr_event;
+ /* Connection requests from child to parent. */
+ struct proto_conn *hr_conn;
+
+ /* Activemap structure. */
+ struct activemap *hr_amp;
+ /* Lock used to synchronize access to hr_amp. */
+ pthread_mutex_t hr_amp_lock;
+ /* Lock used to synchronize access to hr_amp diskmap. */
+ pthread_mutex_t hr_amp_diskmap_lock;
+
+ /* Number of BIO_READ requests. */
+ uint64_t hr_stat_read;
+ /* Number of BIO_WRITE requests. */
+ uint64_t hr_stat_write;
+ /* Number of BIO_DELETE requests. */
+ uint64_t hr_stat_delete;
+ /* Number of BIO_FLUSH requests. */
+ uint64_t hr_stat_flush;
+ /* Number of activemap updates. */
+ uint64_t hr_stat_activemap_update;
+ /* Number of local read errors. */
+ uint64_t hr_stat_read_error;
+ /* Number of local write errors. */
+ uint64_t hr_stat_write_error;
+ /* Number of local delete errors. */
+ uint64_t hr_stat_delete_error;
+ /* Number of flush errors. */
+ uint64_t hr_stat_flush_error;
+ /* Number of activemap write errors. */
+ uint64_t hr_stat_activemap_write_error;
+ /* Number of activemap flush errors. */
+ uint64_t hr_stat_activemap_flush_error;
+
+ /* Function to output worker specific info on control status request. */
+ void (*output_status_aux)(struct nv *);
+
+ /* Next resource. */
+ TAILQ_ENTRY(hast_resource) hr_next;
+};
+
+struct hastd_config *yy_config_parse(const char *config, bool exitonerror);
+void yy_config_free(struct hastd_config *config);
+
+#endif /* !_HAST_H_ */
diff --git a/sbin/hastd/hast_checksum.c b/sbin/hastd/hast_checksum.c
new file mode 100644
index 0000000..795744e
--- /dev/null
+++ b/sbin/hastd/hast_checksum.c
@@ -0,0 +1,160 @@
+/*-
+ * Copyright (c) 2011 Pawel Jakub Dawidek <pawel@dawidek.net>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <errno.h>
+#include <string.h>
+#include <strings.h>
+
+#ifdef HAVE_CRYPTO
+#include <openssl/sha.h>
+#endif
+
+#include <crc32.h>
+#include <hast.h>
+#include <nv.h>
+#include <pjdlog.h>
+
+#include "hast_checksum.h"
+
+#ifdef HAVE_CRYPTO
+#define MAX_HASH_SIZE SHA256_DIGEST_LENGTH
+#else
+#define MAX_HASH_SIZE 4
+#endif
+
+static void
+hast_crc32_checksum(const unsigned char *data, size_t size,
+ unsigned char *hash, size_t *hsizep)
+{
+ uint32_t crc;
+
+ crc = crc32(data, size);
+ /* XXXPJD: Do we have to use htole32() on crc first? */
+ bcopy(&crc, hash, sizeof(crc));
+ *hsizep = sizeof(crc);
+}
+
+#ifdef HAVE_CRYPTO
+static void
+hast_sha256_checksum(const unsigned char *data, size_t size,
+ unsigned char *hash, size_t *hsizep)
+{
+ SHA256_CTX ctx;
+
+ SHA256_Init(&ctx);
+ SHA256_Update(&ctx, data, size);
+ SHA256_Final(hash, &ctx);
+ *hsizep = SHA256_DIGEST_LENGTH;
+}
+#endif /* HAVE_CRYPTO */
+
+const char *
+checksum_name(int num)
+{
+
+ switch (num) {
+ case HAST_CHECKSUM_NONE:
+ return ("none");
+ case HAST_CHECKSUM_CRC32:
+ return ("crc32");
+ case HAST_CHECKSUM_SHA256:
+ return ("sha256");
+ }
+ return ("unknown");
+}
+
+int
+checksum_send(const struct hast_resource *res, struct nv *nv, void **datap,
+ size_t *sizep, bool *freedatap __unused)
+{
+ unsigned char hash[MAX_HASH_SIZE];
+ size_t hsize;
+
+ switch (res->hr_checksum) {
+ case HAST_CHECKSUM_NONE:
+ return (0);
+ case HAST_CHECKSUM_CRC32:
+ hast_crc32_checksum(*datap, *sizep, hash, &hsize);
+ break;
+#ifdef HAVE_CRYPTO
+ case HAST_CHECKSUM_SHA256:
+ hast_sha256_checksum(*datap, *sizep, hash, &hsize);
+ break;
+#endif
+ default:
+ PJDLOG_ABORT("Invalid checksum: %d.", res->hr_checksum);
+ }
+ nv_add_string(nv, checksum_name(res->hr_checksum), "checksum");
+ nv_add_uint8_array(nv, hash, hsize, "hash");
+ if (nv_error(nv) != 0) {
+ errno = nv_error(nv);
+ return (-1);
+ }
+ return (0);
+}
+
+int
+checksum_recv(const struct hast_resource *res __unused, struct nv *nv,
+ void **datap, size_t *sizep, bool *freedatap __unused)
+{
+ unsigned char chash[MAX_HASH_SIZE];
+ const unsigned char *rhash;
+ size_t chsize, rhsize;
+ const char *algo;
+
+ algo = nv_get_string(nv, "checksum");
+ if (algo == NULL)
+ return (0); /* No checksum. */
+ rhash = nv_get_uint8_array(nv, &rhsize, "hash");
+ if (rhash == NULL) {
+ pjdlog_error("Hash is missing.");
+ return (-1); /* Hash not found. */
+ }
+ if (strcmp(algo, "crc32") == 0)
+ hast_crc32_checksum(*datap, *sizep, chash, &chsize);
+#ifdef HAVE_CRYPTO
+ else if (strcmp(algo, "sha256") == 0)
+ hast_sha256_checksum(*datap, *sizep, chash, &chsize);
+#endif
+ else {
+ pjdlog_error("Unknown checksum algorithm '%s'.", algo);
+ return (-1); /* Unknown checksum algorithm. */
+ }
+ if (rhsize != chsize) {
+ pjdlog_error("Invalid hash size (%zu) for %s, should be %zu.",
+ rhsize, algo, chsize);
+ return (-1); /* Different hash size. */
+ }
+ if (bcmp(rhash, chash, chsize) != 0) {
+ pjdlog_error("Hash mismatch.");
+ return (-1); /* Hash mismatch. */
+ }
+
+ return (0);
+}
diff --git a/sbin/hastd/hast_checksum.h b/sbin/hastd/hast_checksum.h
new file mode 100644
index 0000000..9799828
--- /dev/null
+++ b/sbin/hastd/hast_checksum.h
@@ -0,0 +1,44 @@
+/*-
+ * Copyright (c) 2011 Pawel Jakub Dawidek <pawel@dawidek.net>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _HAST_CHECKSUM_H_
+#define _HAST_CHECKSUM_H_
+
+#include <stdlib.h> /* size_t */
+
+#include <hast.h>
+#include <nv.h>
+
+const char *checksum_name(int num);
+
+int checksum_send(const struct hast_resource *res, struct nv *nv,
+ void **datap, size_t *sizep, bool *freedatap);
+int checksum_recv(const struct hast_resource *res, struct nv *nv,
+ void **datap, size_t *sizep, bool *freedatap);
+
+#endif /* !_HAST_CHECKSUM_H_ */
diff --git a/sbin/hastd/hast_compression.c b/sbin/hastd/hast_compression.c
new file mode 100644
index 0000000..f524eb1
--- /dev/null
+++ b/sbin/hastd/hast_compression.c
@@ -0,0 +1,283 @@
+/*-
+ * Copyright (c) 2011 Pawel Jakub Dawidek <pawel@dawidek.net>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/endian.h>
+
+#include <errno.h>
+#include <string.h>
+#include <strings.h>
+
+#include <hast.h>
+#include <lzf.h>
+#include <nv.h>
+#include <pjdlog.h>
+
+#include "hast_compression.h"
+
+static bool
+allzeros(const void *data, size_t size)
+{
+ const uint64_t *p = data;
+ unsigned int i;
+ uint64_t v;
+
+ PJDLOG_ASSERT((size % sizeof(*p)) == 0);
+
+ /*
+ * This is the fastest method I found for checking if the given
+ * buffer contain all zeros.
+ * Because inside the loop we don't check at every step, we would
+ * get an answer only after walking through entire buffer.
+ * To return early if the buffer doesn't contain all zeros, we probe
+ * 8 bytes at the beginning, in the middle and at the end of the buffer
+ * first.
+ */
+
+ size >>= 3; /* divide by 8 */
+ if ((p[0] | p[size >> 1] | p[size - 1]) != 0)
+ return (false);
+ v = 0;
+ for (i = 0; i < size; i++)
+ v |= *p++;
+ return (v == 0);
+}
+
+static void *
+hast_hole_compress(const unsigned char *data, size_t *sizep)
+{
+ uint32_t size;
+ void *newbuf;
+
+ if (!allzeros(data, *sizep))
+ return (NULL);
+
+ newbuf = malloc(sizeof(size));
+ if (newbuf == NULL) {
+ pjdlog_warning("Unable to compress (no memory: %zu).",
+ (size_t)*sizep);
+ return (NULL);
+ }
+ size = htole32((uint32_t)*sizep);
+ bcopy(&size, newbuf, sizeof(size));
+ *sizep = sizeof(size);
+
+ return (newbuf);
+}
+
+static void *
+hast_hole_decompress(const unsigned char *data, size_t *sizep)
+{
+ uint32_t size;
+ void *newbuf;
+
+ if (*sizep != sizeof(size)) {
+ pjdlog_error("Unable to decompress (invalid size: %zu).",
+ *sizep);
+ return (NULL);
+ }
+
+ bcopy(data, &size, sizeof(size));
+ size = le32toh(size);
+
+ newbuf = malloc(size);
+ if (newbuf == NULL) {
+ pjdlog_error("Unable to decompress (no memory: %zu).",
+ (size_t)size);
+ return (NULL);
+ }
+ bzero(newbuf, size);
+ *sizep = size;
+
+ return (newbuf);
+}
+
+/* Minimum block size to try to compress. */
+#define HAST_LZF_COMPRESS_MIN 1024
+
+static void *
+hast_lzf_compress(const unsigned char *data, size_t *sizep)
+{
+ unsigned char *newbuf;
+ uint32_t origsize;
+ size_t newsize;
+
+ origsize = *sizep;
+
+ if (origsize <= HAST_LZF_COMPRESS_MIN)
+ return (NULL);
+
+ newsize = sizeof(origsize) + origsize - HAST_LZF_COMPRESS_MIN;
+ newbuf = malloc(newsize);
+ if (newbuf == NULL) {
+ pjdlog_warning("Unable to compress (no memory: %zu).",
+ newsize);
+ return (NULL);
+ }
+ newsize = lzf_compress(data, *sizep, newbuf + sizeof(origsize),
+ newsize - sizeof(origsize));
+ if (newsize == 0) {
+ free(newbuf);
+ return (NULL);
+ }
+ origsize = htole32(origsize);
+ bcopy(&origsize, newbuf, sizeof(origsize));
+
+ *sizep = sizeof(origsize) + newsize;
+ return (newbuf);
+}
+
+static void *
+hast_lzf_decompress(const unsigned char *data, size_t *sizep)
+{
+ unsigned char *newbuf;
+ uint32_t origsize;
+ size_t newsize;
+
+ PJDLOG_ASSERT(*sizep > sizeof(origsize));
+
+ bcopy(data, &origsize, sizeof(origsize));
+ origsize = le32toh(origsize);
+ PJDLOG_ASSERT(origsize > HAST_LZF_COMPRESS_MIN);
+
+ newbuf = malloc(origsize);
+ if (newbuf == NULL) {
+ pjdlog_error("Unable to decompress (no memory: %zu).",
+ (size_t)origsize);
+ return (NULL);
+ }
+ newsize = lzf_decompress(data + sizeof(origsize),
+ *sizep - sizeof(origsize), newbuf, origsize);
+ if (newsize == 0) {
+ free(newbuf);
+ pjdlog_error("Unable to decompress.");
+ return (NULL);
+ }
+ PJDLOG_ASSERT(newsize == origsize);
+
+ *sizep = newsize;
+ return (newbuf);
+}
+
+const char *
+compression_name(int num)
+{
+
+ switch (num) {
+ case HAST_COMPRESSION_NONE:
+ return ("none");
+ case HAST_COMPRESSION_HOLE:
+ return ("hole");
+ case HAST_COMPRESSION_LZF:
+ return ("lzf");
+ }
+ return ("unknown");
+}
+
+int
+compression_send(const struct hast_resource *res, struct nv *nv, void **datap,
+ size_t *sizep, bool *freedatap)
+{
+ unsigned char *newbuf;
+ int compression;
+ size_t size;
+
+ size = *sizep;
+ compression = res->hr_compression;
+
+ switch (compression) {
+ case HAST_COMPRESSION_NONE:
+ return (0);
+ case HAST_COMPRESSION_HOLE:
+ newbuf = hast_hole_compress(*datap, &size);
+ break;
+ case HAST_COMPRESSION_LZF:
+ /* Try 'hole' compression first. */
+ newbuf = hast_hole_compress(*datap, &size);
+ if (newbuf != NULL)
+ compression = HAST_COMPRESSION_HOLE;
+ else
+ newbuf = hast_lzf_compress(*datap, &size);
+ break;
+ default:
+ PJDLOG_ABORT("Invalid compression: %d.", res->hr_compression);
+ }
+
+ if (newbuf == NULL) {
+ /* Unable to compress the data. */
+ return (0);
+ }
+ nv_add_string(nv, compression_name(compression), "compression");
+ if (nv_error(nv) != 0) {
+ free(newbuf);
+ errno = nv_error(nv);
+ return (-1);
+ }
+ if (*freedatap)
+ free(*datap);
+ *freedatap = true;
+ *datap = newbuf;
+ *sizep = size;
+
+ return (0);
+}
+
+int
+compression_recv(const struct hast_resource *res __unused, struct nv *nv,
+ void **datap, size_t *sizep, bool *freedatap)
+{
+ unsigned char *newbuf;
+ const char *algo;
+ size_t size;
+
+ algo = nv_get_string(nv, "compression");
+ if (algo == NULL)
+ return (0); /* No compression. */
+
+ newbuf = NULL;
+ size = *sizep;
+
+ if (strcmp(algo, "hole") == 0)
+ newbuf = hast_hole_decompress(*datap, &size);
+ else if (strcmp(algo, "lzf") == 0)
+ newbuf = hast_lzf_decompress(*datap, &size);
+ else {
+ pjdlog_error("Unknown compression algorithm '%s'.", algo);
+ return (-1); /* Unknown compression algorithm. */
+ }
+
+ if (newbuf == NULL)
+ return (-1);
+ if (*freedatap)
+ free(*datap);
+ *freedatap = true;
+ *datap = newbuf;
+ *sizep = size;
+
+ return (0);
+}
diff --git a/sbin/hastd/hast_compression.h b/sbin/hastd/hast_compression.h
new file mode 100644
index 0000000..eabdfb2
--- /dev/null
+++ b/sbin/hastd/hast_compression.h
@@ -0,0 +1,44 @@
+/*-
+ * Copyright (c) 2011 Pawel Jakub Dawidek <pawel@dawidek.net>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _HAST_COMPRESSION_H_
+#define _HAST_COMPRESSION_H_
+
+#include <stdlib.h> /* size_t */
+
+#include <hast.h>
+#include <nv.h>
+
+const char *compression_name(int num);
+
+int compression_send(const struct hast_resource *res, struct nv *nv,
+ void **datap, size_t *sizep, bool *freedatap);
+int compression_recv(const struct hast_resource *res, struct nv *nv,
+ void **datap, size_t *sizep, bool *freedatap);
+
+#endif /* !_HAST_COMPRESSION_H_ */
diff --git a/sbin/hastd/hast_proto.c b/sbin/hastd/hast_proto.c
new file mode 100644
index 0000000..dd41fb1
--- /dev/null
+++ b/sbin/hastd/hast_proto.c
@@ -0,0 +1,222 @@
+/*-
+ * Copyright (c) 2009-2010 The FreeBSD Foundation
+ * Copyright (c) 2011 Pawel Jakub Dawidek <pawel@dawidek.net>
+ * All rights reserved.
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/endian.h>
+
+#include <errno.h>
+#include <strings.h>
+
+#include <hast.h>
+#include <ebuf.h>
+#include <nv.h>
+#include <pjdlog.h>
+#include <proto.h>
+
+#ifdef HAVE_CRYPTO
+#include "hast_checksum.h"
+#endif
+#include "hast_compression.h"
+#include "hast_proto.h"
+
+struct hast_main_header {
+ /* Protocol version. */
+ uint8_t version;
+ /* Size of nv headers. */
+ uint32_t size;
+} __packed;
+
+typedef int hps_send_t(const struct hast_resource *, struct nv *nv, void **,
+ size_t *, bool *);
+typedef int hps_recv_t(const struct hast_resource *, struct nv *nv, void **,
+ size_t *, bool *);
+
+struct hast_pipe_stage {
+ const char *hps_name;
+ hps_send_t *hps_send;
+ hps_recv_t *hps_recv;
+};
+
+static struct hast_pipe_stage pipeline[] = {
+ { "compression", compression_send, compression_recv },
+#ifdef HAVE_CRYPTO
+ { "checksum", checksum_send, checksum_recv }
+#endif
+};
+
+/*
+ * Send the given nv structure via conn.
+ * We keep headers in nv structure and pass data in separate argument.
+ * There can be no data at all (data is NULL then).
+ */
+int
+hast_proto_send(const struct hast_resource *res, struct proto_conn *conn,
+ struct nv *nv, const void *data, size_t size)
+{
+ struct hast_main_header hdr;
+ struct ebuf *eb;
+ bool freedata;
+ void *dptr, *hptr;
+ size_t hsize;
+ int ret;
+
+ dptr = (void *)(uintptr_t)data;
+ freedata = false;
+ ret = -1;
+
+ if (data != NULL) {
+ unsigned int ii;
+
+ for (ii = 0; ii < sizeof(pipeline) / sizeof(pipeline[0]);
+ ii++) {
+ (void)pipeline[ii].hps_send(res, nv, &dptr, &size,
+ &freedata);
+ }
+ nv_add_uint32(nv, size, "size");
+ if (nv_error(nv) != 0) {
+ errno = nv_error(nv);
+ goto end;
+ }
+ }
+
+ eb = nv_hton(nv);
+ if (eb == NULL)
+ goto end;
+
+ hdr.version = res != NULL ? res->hr_version : HAST_PROTO_VERSION;
+ hdr.size = htole32((uint32_t)ebuf_size(eb));
+ if (ebuf_add_head(eb, &hdr, sizeof(hdr)) == -1)
+ goto end;
+
+ hptr = ebuf_data(eb, &hsize);
+ if (proto_send(conn, hptr, hsize) == -1)
+ goto end;
+ if (data != NULL && proto_send(conn, dptr, size) == -1)
+ goto end;
+
+ ret = 0;
+end:
+ if (freedata)
+ free(dptr);
+ return (ret);
+}
+
+int
+hast_proto_recv_hdr(const struct proto_conn *conn, struct nv **nvp)
+{
+ struct hast_main_header hdr;
+ struct nv *nv;
+ struct ebuf *eb;
+ void *hptr;
+
+ eb = NULL;
+ nv = NULL;
+
+ if (proto_recv(conn, &hdr, sizeof(hdr)) == -1)
+ goto fail;
+
+ if (hdr.version > HAST_PROTO_VERSION) {
+ errno = ERPCMISMATCH;
+ goto fail;
+ }
+
+ hdr.size = le32toh(hdr.size);
+
+ eb = ebuf_alloc(hdr.size);
+ if (eb == NULL)
+ goto fail;
+ if (ebuf_add_tail(eb, NULL, hdr.size) == -1)
+ goto fail;
+ hptr = ebuf_data(eb, NULL);
+ PJDLOG_ASSERT(hptr != NULL);
+ if (proto_recv(conn, hptr, hdr.size) == -1)
+ goto fail;
+ nv = nv_ntoh(eb);
+ if (nv == NULL)
+ goto fail;
+
+ *nvp = nv;
+ return (0);
+fail:
+ if (eb != NULL)
+ ebuf_free(eb);
+ return (-1);
+}
+
+int
+hast_proto_recv_data(const struct hast_resource *res, struct proto_conn *conn,
+ struct nv *nv, void *data, size_t size)
+{
+ unsigned int ii;
+ bool freedata;
+ size_t dsize;
+ void *dptr;
+ int ret;
+
+ PJDLOG_ASSERT(data != NULL);
+ PJDLOG_ASSERT(size > 0);
+
+ ret = -1;
+ freedata = false;
+ dptr = data;
+
+ dsize = nv_get_uint32(nv, "size");
+ if (dsize > size) {
+ errno = EINVAL;
+ goto end;
+ } else if (dsize == 0) {
+ (void)nv_set_error(nv, 0);
+ } else {
+ if (proto_recv(conn, data, dsize) == -1)
+ goto end;
+ for (ii = sizeof(pipeline) / sizeof(pipeline[0]); ii > 0;
+ ii--) {
+ ret = pipeline[ii - 1].hps_recv(res, nv, &dptr,
+ &dsize, &freedata);
+ if (ret == -1)
+ goto end;
+ }
+ ret = -1;
+ if (dsize > size) {
+ errno = EINVAL;
+ goto end;
+ }
+ if (dptr != data)
+ bcopy(dptr, data, dsize);
+ }
+
+ ret = 0;
+end:
+ if (freedata)
+ free(dptr);
+ return (ret);
+}
diff --git a/sbin/hastd/hast_proto.h b/sbin/hastd/hast_proto.h
new file mode 100644
index 0000000..49f3b56
--- /dev/null
+++ b/sbin/hastd/hast_proto.h
@@ -0,0 +1,46 @@
+/*-
+ * Copyright (c) 2009-2010 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _HAST_PROTO_H_
+#define _HAST_PROTO_H_
+
+#include <stdlib.h> /* size_t */
+
+#include <nv.h>
+#include <proto.h>
+
+int hast_proto_send(const struct hast_resource *res, struct proto_conn *conn,
+ struct nv *nv, const void *data, size_t size);
+int hast_proto_recv_hdr(const struct proto_conn *conn, struct nv **nvp);
+int hast_proto_recv_data(const struct hast_resource *res,
+ struct proto_conn *conn, struct nv *nv, void *data, size_t size);
+
+#endif /* !_HAST_PROTO_H_ */
diff --git a/sbin/hastd/hastd.8 b/sbin/hastd/hastd.8
new file mode 100644
index 0000000..e30a11a
--- /dev/null
+++ b/sbin/hastd/hastd.8
@@ -0,0 +1,232 @@
+.\" Copyright (c) 2010 The FreeBSD Foundation
+.\" All rights reserved.
+.\"
+.\" This software was developed by Pawel Jakub Dawidek under sponsorship from
+.\" the FreeBSD Foundation.
+.\"
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd February 1, 2010
+.Dt HASTD 8
+.Os
+.Sh NAME
+.Nm hastd
+.Nd "Highly Available Storage daemon"
+.Sh SYNOPSIS
+.Nm
+.Op Fl dFh
+.Op Fl c Ar config
+.Op Fl P Ar pidfile
+.Sh DESCRIPTION
+The
+.Nm
+daemon is responsible for managing highly available GEOM providers.
+.Pp
+.Nm
+allows to transparently store data on two physically separated machines
+connected over the TCP/IP network.
+Only one machine (cluster node) can actively use storage provided by
+.Nm .
+This machine is called primary.
+The
+.Nm
+daemon operates on block level, which makes it transparent to file
+systems and applications.
+.Pp
+There is one main
+.Nm
+daemon which starts new worker process as soon as a role for the given
+resource is changed to primary or as soon as a role for the given
+resource is changed to secondary and remote (primary) node will
+successfully connect to it.
+Every worker process gets a new process title (see
+.Xr setproctitle 3 ) ,
+which describes its role and resource it controls.
+The exact format is:
+.Bd -literal -offset indent
+hastd: <resource name> (<role>)
+.Ed
+.Pp
+If (and only if)
+.Nm
+operates in primary role for the given resource, a corresponding
+.Pa /dev/hast/<name>
+disk-like device (GEOM provider) is created.
+File systems and applications can use this provider to send I/O
+requests to.
+Every write, delete and flush operation
+.Dv ( BIO_WRITE , BIO_DELETE , BIO_FLUSH )
+is sent to the local component and replicated on the remote (secondary) node
+if it is available.
+Read operations
+.Dv ( BIO_READ )
+are handled locally unless an I/O error occurs or the local version of the data
+is not up-to-date yet (synchronization is in progress).
+.Pp
+The
+.Nm
+daemon uses the GEOM Gate class to receive I/O requests from the
+in-kernel GEOM infrastructure.
+The
+.Nm geom_gate.ko
+module is loaded automatically if the kernel was not compiled with the
+following option:
+.Bd -ragged -offset indent
+.Cd "options GEOM_GATE"
+.Ed
+.Pp
+The connection between two
+.Nm
+daemons is always initiated from the one running as primary to the one
+running as secondary.
+When the primary
+.Nm
+is unable to connect or the connection fails, it will try to re-establish
+the connection every few seconds.
+Once the connection is established, the primary
+.Nm
+will synchronize every extent that was modified during connection outage
+to the secondary
+.Nm .
+.Pp
+It is possible that in the case of a connection outage between the nodes the
+.Nm
+primary role for the given resource will be configured on both nodes.
+This in turn leads to incompatible data modifications.
+Such a condition is called a split-brain and cannot be automatically
+resolved by the
+.Nm
+daemon as this will lead most likely to data corruption or loss of
+important changes.
+Even though it cannot be fixed by
+.Nm
+itself, it will be detected and a further connection between independently
+modified nodes will not be possible.
+Once this situation is manually resolved by an administrator, the resource
+on one of the nodes can be initialized (erasing local data), which makes
+a connection to the remote node possible again.
+Connection of the freshly initialized component will trigger full resource
+synchronization.
+.Pp
+A
+.Nm
+daemon never picks its role automatically.
+The role has to be configured with the
+.Xr hastctl 8
+control utility by additional software like
+.Nm ucarp
+or
+.Nm heartbeat
+that can reliably manage role separation and switch secondary node to
+primary role in case of the primary's failure.
+.Pp
+The
+.Nm
+daemon can be started with the following command line arguments:
+.Bl -tag -width ".Fl P Ar pidfile"
+.It Fl c Ar config
+Specify alternative location of the configuration file.
+The default location is
+.Pa /etc/hast.conf .
+.It Fl d
+Print or log debugging information.
+This option can be specified multiple times to raise the verbosity
+level.
+.It Fl F
+Start the
+.Nm
+daemon in the foreground.
+By default
+.Nm
+starts in the background.
+.It Fl h
+Print the
+.Nm
+usage message.
+.It Fl P Ar pidfile
+Specify alternative location of a file where main process PID will be
+stored.
+The default location is
+.Pa /var/run/hastd.pid .
+.El
+.Sh FILES
+.Bl -tag -width ".Pa /var/run/hastd.pid" -compact
+.It Pa /etc/hast.conf
+The configuration file for
+.Nm
+and
+.Xr hastctl 8 .
+.It Pa /var/run/hastctl
+Control socket used by the
+.Xr hastctl 8
+control utility to communicate with
+.Nm .
+.It Pa /var/run/hastd.pid
+The default location of the
+.Nm
+PID file.
+.El
+.Sh EXIT STATUS
+Exit status is 0 on success, or one of the values described in
+.Xr sysexits 3
+on failure.
+.Sh EXAMPLES
+Launch
+.Nm
+on both nodes.
+Set role for resource
+.Nm shared
+to primary on
+.Nm nodeA
+and to secondary on
+.Nm nodeB .
+Create file system on
+.Pa /dev/hast/shared
+provider and mount it.
+.Bd -literal -offset indent
+nodeB# hastd
+nodeB# hastctl role secondary shared
+
+nodeA# hastd
+nodeA# hastctl role primary shared
+nodeA# newfs -U /dev/hast/shared
+nodeA# mount -o noatime /dev/hast/shared /shared
+.Ed
+.Sh SEE ALSO
+.Xr sysexits 3 ,
+.Xr geom 4 ,
+.Xr hast.conf 5 ,
+.Xr ggatec 8 ,
+.Xr ggated 8 ,
+.Xr ggatel 8 ,
+.Xr hastctl 8 ,
+.Xr mount 8 ,
+.Xr newfs 8 ,
+.Xr g_bio 9
+.Sh AUTHORS
+The
+.Nm
+was developed by
+.An Pawel Jakub Dawidek Aq Mt pjd@FreeBSD.org
+under sponsorship of the FreeBSD Foundation.
diff --git a/sbin/hastd/hastd.c b/sbin/hastd/hastd.c
new file mode 100644
index 0000000..bac390a
--- /dev/null
+++ b/sbin/hastd/hastd.c
@@ -0,0 +1,1337 @@
+/*-
+ * Copyright (c) 2009-2010 The FreeBSD Foundation
+ * Copyright (c) 2010-2011 Pawel Jakub Dawidek <pawel@dawidek.net>
+ * All rights reserved.
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/linker.h>
+#include <sys/module.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <err.h>
+#include <errno.h>
+#include <libutil.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <activemap.h>
+#include <pjdlog.h>
+
+#include "control.h"
+#include "event.h"
+#include "hast.h"
+#include "hast_proto.h"
+#include "hastd.h"
+#include "hooks.h"
+#include "subr.h"
+
+/* Path to configuration file. */
+const char *cfgpath = HAST_CONFIG;
+/* Hastd configuration. */
+static struct hastd_config *cfg;
+/* Was SIGINT or SIGTERM signal received? */
+bool sigexit_received = false;
+/* Path to pidfile. */
+static const char *pidfile;
+/* Pidfile handle. */
+struct pidfh *pfh;
+/* Do we run in foreground? */
+static bool foreground;
+
+/* How often check for hooks running for too long. */
+#define REPORT_INTERVAL 5
+
+static void
+usage(void)
+{
+
+ errx(EX_USAGE, "[-dFh] [-c config] [-P pidfile]");
+}
+
+static void
+g_gate_load(void)
+{
+
+ if (modfind("g_gate") == -1) {
+ /* Not present in kernel, try loading it. */
+ if (kldload("geom_gate") == -1 || modfind("g_gate") == -1) {
+ if (errno != EEXIST) {
+ pjdlog_exit(EX_OSERR,
+ "Unable to load geom_gate module");
+ }
+ }
+ }
+}
+
+void
+descriptors_cleanup(struct hast_resource *res)
+{
+ struct hast_resource *tres, *tmres;
+ struct hastd_listen *lst;
+
+ TAILQ_FOREACH_SAFE(tres, &cfg->hc_resources, hr_next, tmres) {
+ if (tres == res) {
+ PJDLOG_VERIFY(res->hr_role == HAST_ROLE_SECONDARY ||
+ (res->hr_remotein == NULL &&
+ res->hr_remoteout == NULL));
+ continue;
+ }
+ if (tres->hr_remotein != NULL)
+ proto_close(tres->hr_remotein);
+ if (tres->hr_remoteout != NULL)
+ proto_close(tres->hr_remoteout);
+ if (tres->hr_ctrl != NULL)
+ proto_close(tres->hr_ctrl);
+ if (tres->hr_event != NULL)
+ proto_close(tres->hr_event);
+ if (tres->hr_conn != NULL)
+ proto_close(tres->hr_conn);
+ TAILQ_REMOVE(&cfg->hc_resources, tres, hr_next);
+ free(tres);
+ }
+ if (cfg->hc_controlin != NULL)
+ proto_close(cfg->hc_controlin);
+ proto_close(cfg->hc_controlconn);
+ while ((lst = TAILQ_FIRST(&cfg->hc_listen)) != NULL) {
+ TAILQ_REMOVE(&cfg->hc_listen, lst, hl_next);
+ if (lst->hl_conn != NULL)
+ proto_close(lst->hl_conn);
+ free(lst);
+ }
+ (void)pidfile_close(pfh);
+ hook_fini();
+ pjdlog_fini();
+}
+
+static const char *
+dtype2str(mode_t mode)
+{
+
+ if (S_ISBLK(mode))
+ return ("block device");
+ else if (S_ISCHR(mode))
+ return ("character device");
+ else if (S_ISDIR(mode))
+ return ("directory");
+ else if (S_ISFIFO(mode))
+ return ("pipe or FIFO");
+ else if (S_ISLNK(mode))
+ return ("symbolic link");
+ else if (S_ISREG(mode))
+ return ("regular file");
+ else if (S_ISSOCK(mode))
+ return ("socket");
+ else if (S_ISWHT(mode))
+ return ("whiteout");
+ else
+ return ("unknown");
+}
+
+void
+descriptors_assert(const struct hast_resource *res, int pjdlogmode)
+{
+ char msg[256];
+ struct stat sb;
+ long maxfd;
+ bool isopen;
+ mode_t mode;
+ int fd;
+
+ /*
+ * At this point descriptor to syslog socket is closed, so if we want
+ * to log assertion message, we have to first store it in 'msg' local
+ * buffer and then open syslog socket and log it.
+ */
+ msg[0] = '\0';
+
+ maxfd = sysconf(_SC_OPEN_MAX);
+ if (maxfd == -1) {
+ pjdlog_init(pjdlogmode);
+ pjdlog_prefix_set("[%s] (%s) ", res->hr_name,
+ role2str(res->hr_role));
+ pjdlog_errno(LOG_WARNING, "sysconf(_SC_OPEN_MAX) failed");
+ pjdlog_fini();
+ maxfd = 16384;
+ }
+ for (fd = 0; fd <= maxfd; fd++) {
+ if (fstat(fd, &sb) == 0) {
+ isopen = true;
+ mode = sb.st_mode;
+ } else if (errno == EBADF) {
+ isopen = false;
+ mode = 0;
+ } else {
+ (void)snprintf(msg, sizeof(msg),
+ "Unable to fstat descriptor %d: %s", fd,
+ strerror(errno));
+ break;
+ }
+ if (fd == STDIN_FILENO || fd == STDOUT_FILENO ||
+ fd == STDERR_FILENO) {
+ if (!isopen) {
+ (void)snprintf(msg, sizeof(msg),
+ "Descriptor %d (%s) is closed, but should be open.",
+ fd, (fd == STDIN_FILENO ? "stdin" :
+ (fd == STDOUT_FILENO ? "stdout" : "stderr")));
+ break;
+ }
+ } else if (fd == proto_descriptor(res->hr_event)) {
+ if (!isopen) {
+ (void)snprintf(msg, sizeof(msg),
+ "Descriptor %d (event) is closed, but should be open.",
+ fd);
+ break;
+ }
+ if (!S_ISSOCK(mode)) {
+ (void)snprintf(msg, sizeof(msg),
+ "Descriptor %d (event) is %s, but should be %s.",
+ fd, dtype2str(mode), dtype2str(S_IFSOCK));
+ break;
+ }
+ } else if (fd == proto_descriptor(res->hr_ctrl)) {
+ if (!isopen) {
+ (void)snprintf(msg, sizeof(msg),
+ "Descriptor %d (ctrl) is closed, but should be open.",
+ fd);
+ break;
+ }
+ if (!S_ISSOCK(mode)) {
+ (void)snprintf(msg, sizeof(msg),
+ "Descriptor %d (ctrl) is %s, but should be %s.",
+ fd, dtype2str(mode), dtype2str(S_IFSOCK));
+ break;
+ }
+ } else if (res->hr_role == HAST_ROLE_PRIMARY &&
+ fd == proto_descriptor(res->hr_conn)) {
+ if (!isopen) {
+ (void)snprintf(msg, sizeof(msg),
+ "Descriptor %d (conn) is closed, but should be open.",
+ fd);
+ break;
+ }
+ if (!S_ISSOCK(mode)) {
+ (void)snprintf(msg, sizeof(msg),
+ "Descriptor %d (conn) is %s, but should be %s.",
+ fd, dtype2str(mode), dtype2str(S_IFSOCK));
+ break;
+ }
+ } else if (res->hr_role == HAST_ROLE_SECONDARY &&
+ res->hr_conn != NULL &&
+ fd == proto_descriptor(res->hr_conn)) {
+ if (isopen) {
+ (void)snprintf(msg, sizeof(msg),
+ "Descriptor %d (conn) is open, but should be closed.",
+ fd);
+ break;
+ }
+ } else if (res->hr_role == HAST_ROLE_SECONDARY &&
+ fd == proto_descriptor(res->hr_remotein)) {
+ if (!isopen) {
+ (void)snprintf(msg, sizeof(msg),
+ "Descriptor %d (remote in) is closed, but should be open.",
+ fd);
+ break;
+ }
+ if (!S_ISSOCK(mode)) {
+ (void)snprintf(msg, sizeof(msg),
+ "Descriptor %d (remote in) is %s, but should be %s.",
+ fd, dtype2str(mode), dtype2str(S_IFSOCK));
+ break;
+ }
+ } else if (res->hr_role == HAST_ROLE_SECONDARY &&
+ fd == proto_descriptor(res->hr_remoteout)) {
+ if (!isopen) {
+ (void)snprintf(msg, sizeof(msg),
+ "Descriptor %d (remote out) is closed, but should be open.",
+ fd);
+ break;
+ }
+ if (!S_ISSOCK(mode)) {
+ (void)snprintf(msg, sizeof(msg),
+ "Descriptor %d (remote out) is %s, but should be %s.",
+ fd, dtype2str(mode), dtype2str(S_IFSOCK));
+ break;
+ }
+ } else {
+ if (isopen) {
+ (void)snprintf(msg, sizeof(msg),
+ "Descriptor %d is open (%s), but should be closed.",
+ fd, dtype2str(mode));
+ break;
+ }
+ }
+ }
+ if (msg[0] != '\0') {
+ pjdlog_init(pjdlogmode);
+ pjdlog_prefix_set("[%s] (%s) ", res->hr_name,
+ role2str(res->hr_role));
+ PJDLOG_ABORT("%s", msg);
+ }
+}
+
+static void
+child_exit_log(unsigned int pid, int status)
+{
+
+ if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
+ pjdlog_debug(1, "Worker process exited gracefully (pid=%u).",
+ pid);
+ } else if (WIFSIGNALED(status)) {
+ pjdlog_error("Worker process killed (pid=%u, signal=%d).",
+ pid, WTERMSIG(status));
+ } else {
+ pjdlog_error("Worker process exited ungracefully (pid=%u, exitcode=%d).",
+ pid, WIFEXITED(status) ? WEXITSTATUS(status) : -1);
+ }
+}
+
+static void
+child_exit(void)
+{
+ struct hast_resource *res;
+ int status;
+ pid_t pid;
+
+ while ((pid = wait3(&status, WNOHANG, NULL)) > 0) {
+ /* Find resource related to the process that just exited. */
+ TAILQ_FOREACH(res, &cfg->hc_resources, hr_next) {
+ if (pid == res->hr_workerpid)
+ break;
+ }
+ if (res == NULL) {
+ /*
+ * This can happen when new connection arrives and we
+ * cancel child responsible for the old one or if this
+ * was hook which we executed.
+ */
+ hook_check_one(pid, status);
+ continue;
+ }
+ pjdlog_prefix_set("[%s] (%s) ", res->hr_name,
+ role2str(res->hr_role));
+ child_exit_log(pid, status);
+ child_cleanup(res);
+ if (res->hr_role == HAST_ROLE_PRIMARY) {
+ /*
+ * Restart child process if it was killed by signal
+ * or exited because of temporary problem.
+ */
+ if (WIFSIGNALED(status) ||
+ (WIFEXITED(status) &&
+ WEXITSTATUS(status) == EX_TEMPFAIL)) {
+ sleep(1);
+ pjdlog_info("Restarting worker process.");
+ hastd_primary(res);
+ } else {
+ res->hr_role = HAST_ROLE_INIT;
+ pjdlog_info("Changing resource role back to %s.",
+ role2str(res->hr_role));
+ }
+ }
+ pjdlog_prefix_set("%s", "");
+ }
+}
+
+static bool
+resource_needs_restart(const struct hast_resource *res0,
+ const struct hast_resource *res1)
+{
+
+ PJDLOG_ASSERT(strcmp(res0->hr_name, res1->hr_name) == 0);
+
+ if (strcmp(res0->hr_provname, res1->hr_provname) != 0)
+ return (true);
+ if (strcmp(res0->hr_localpath, res1->hr_localpath) != 0)
+ return (true);
+ if (res0->hr_role == HAST_ROLE_INIT ||
+ res0->hr_role == HAST_ROLE_SECONDARY) {
+ if (strcmp(res0->hr_remoteaddr, res1->hr_remoteaddr) != 0)
+ return (true);
+ if (strcmp(res0->hr_sourceaddr, res1->hr_sourceaddr) != 0)
+ return (true);
+ if (res0->hr_replication != res1->hr_replication)
+ return (true);
+ if (res0->hr_checksum != res1->hr_checksum)
+ return (true);
+ if (res0->hr_compression != res1->hr_compression)
+ return (true);
+ if (res0->hr_timeout != res1->hr_timeout)
+ return (true);
+ if (strcmp(res0->hr_exec, res1->hr_exec) != 0)
+ return (true);
+ /*
+ * When metaflush has changed we don't really need restart,
+ * but it is just easier this way.
+ */
+ if (res0->hr_metaflush != res1->hr_metaflush)
+ return (true);
+ }
+ return (false);
+}
+
+static bool
+resource_needs_reload(const struct hast_resource *res0,
+ const struct hast_resource *res1)
+{
+
+ PJDLOG_ASSERT(strcmp(res0->hr_name, res1->hr_name) == 0);
+ PJDLOG_ASSERT(strcmp(res0->hr_provname, res1->hr_provname) == 0);
+ PJDLOG_ASSERT(strcmp(res0->hr_localpath, res1->hr_localpath) == 0);
+
+ if (res0->hr_role != HAST_ROLE_PRIMARY)
+ return (false);
+
+ if (strcmp(res0->hr_remoteaddr, res1->hr_remoteaddr) != 0)
+ return (true);
+ if (strcmp(res0->hr_sourceaddr, res1->hr_sourceaddr) != 0)
+ return (true);
+ if (res0->hr_replication != res1->hr_replication)
+ return (true);
+ if (res0->hr_checksum != res1->hr_checksum)
+ return (true);
+ if (res0->hr_compression != res1->hr_compression)
+ return (true);
+ if (res0->hr_timeout != res1->hr_timeout)
+ return (true);
+ if (strcmp(res0->hr_exec, res1->hr_exec) != 0)
+ return (true);
+ if (res0->hr_metaflush != res1->hr_metaflush)
+ return (true);
+ return (false);
+}
+
+static void
+resource_reload(const struct hast_resource *res)
+{
+ struct nv *nvin, *nvout;
+ int error;
+
+ PJDLOG_ASSERT(res->hr_role == HAST_ROLE_PRIMARY);
+
+ nvout = nv_alloc();
+ nv_add_uint8(nvout, CONTROL_RELOAD, "cmd");
+ nv_add_string(nvout, res->hr_remoteaddr, "remoteaddr");
+ nv_add_string(nvout, res->hr_sourceaddr, "sourceaddr");
+ nv_add_int32(nvout, (int32_t)res->hr_replication, "replication");
+ nv_add_int32(nvout, (int32_t)res->hr_checksum, "checksum");
+ nv_add_int32(nvout, (int32_t)res->hr_compression, "compression");
+ nv_add_int32(nvout, (int32_t)res->hr_timeout, "timeout");
+ nv_add_string(nvout, res->hr_exec, "exec");
+ nv_add_int32(nvout, (int32_t)res->hr_metaflush, "metaflush");
+ if (nv_error(nvout) != 0) {
+ nv_free(nvout);
+ pjdlog_error("Unable to allocate header for reload message.");
+ return;
+ }
+ if (hast_proto_send(res, res->hr_ctrl, nvout, NULL, 0) == -1) {
+ pjdlog_errno(LOG_ERR, "Unable to send reload message");
+ nv_free(nvout);
+ return;
+ }
+ nv_free(nvout);
+
+ /* Receive response. */
+ if (hast_proto_recv_hdr(res->hr_ctrl, &nvin) == -1) {
+ pjdlog_errno(LOG_ERR, "Unable to receive reload reply");
+ return;
+ }
+ error = nv_get_int16(nvin, "error");
+ nv_free(nvin);
+ if (error != 0) {
+ pjdlog_common(LOG_ERR, 0, error, "Reload failed");
+ return;
+ }
+}
+
+static void
+hastd_reload(void)
+{
+ struct hastd_config *newcfg;
+ struct hast_resource *nres, *cres, *tres;
+ struct hastd_listen *nlst, *clst;
+ struct pidfh *newpfh;
+ unsigned int nlisten;
+ uint8_t role;
+ pid_t otherpid;
+
+ pjdlog_info("Reloading configuration...");
+
+ newpfh = NULL;
+
+ newcfg = yy_config_parse(cfgpath, false);
+ if (newcfg == NULL)
+ goto failed;
+
+ /*
+ * Check if control address has changed.
+ */
+ if (strcmp(cfg->hc_controladdr, newcfg->hc_controladdr) != 0) {
+ if (proto_server(newcfg->hc_controladdr,
+ &newcfg->hc_controlconn) == -1) {
+ pjdlog_errno(LOG_ERR,
+ "Unable to listen on control address %s",
+ newcfg->hc_controladdr);
+ goto failed;
+ }
+ }
+ /*
+ * Check if any listen address has changed.
+ */
+ nlisten = 0;
+ TAILQ_FOREACH(nlst, &newcfg->hc_listen, hl_next) {
+ TAILQ_FOREACH(clst, &cfg->hc_listen, hl_next) {
+ if (strcmp(nlst->hl_addr, clst->hl_addr) == 0)
+ break;
+ }
+ if (clst != NULL && clst->hl_conn != NULL) {
+ pjdlog_info("Keep listening on address %s.",
+ nlst->hl_addr);
+ nlst->hl_conn = clst->hl_conn;
+ nlisten++;
+ } else if (proto_server(nlst->hl_addr, &nlst->hl_conn) == 0) {
+ pjdlog_info("Listening on new address %s.",
+ nlst->hl_addr);
+ nlisten++;
+ } else {
+ pjdlog_errno(LOG_WARNING,
+ "Unable to listen on address %s", nlst->hl_addr);
+ }
+ }
+ if (nlisten == 0) {
+ pjdlog_error("No addresses to listen on.");
+ goto failed;
+ }
+ /*
+ * Check if pidfile's path has changed.
+ */
+ if (!foreground && pidfile == NULL &&
+ strcmp(cfg->hc_pidfile, newcfg->hc_pidfile) != 0) {
+ newpfh = pidfile_open(newcfg->hc_pidfile, 0600, &otherpid);
+ if (newpfh == NULL) {
+ if (errno == EEXIST) {
+ pjdlog_errno(LOG_WARNING,
+ "Another hastd is already running, pidfile: %s, pid: %jd.",
+ newcfg->hc_pidfile, (intmax_t)otherpid);
+ } else {
+ pjdlog_errno(LOG_WARNING,
+ "Unable to open or create pidfile %s",
+ newcfg->hc_pidfile);
+ }
+ } else if (pidfile_write(newpfh) == -1) {
+ /* Write PID to a file. */
+ pjdlog_errno(LOG_WARNING,
+ "Unable to write PID to file %s",
+ newcfg->hc_pidfile);
+ } else {
+ pjdlog_debug(1, "PID stored in %s.",
+ newcfg->hc_pidfile);
+ }
+ }
+
+ /* No failures from now on. */
+
+ /*
+ * Switch to new control socket.
+ */
+ if (newcfg->hc_controlconn != NULL) {
+ pjdlog_info("Control socket changed from %s to %s.",
+ cfg->hc_controladdr, newcfg->hc_controladdr);
+ proto_close(cfg->hc_controlconn);
+ cfg->hc_controlconn = newcfg->hc_controlconn;
+ newcfg->hc_controlconn = NULL;
+ strlcpy(cfg->hc_controladdr, newcfg->hc_controladdr,
+ sizeof(cfg->hc_controladdr));
+ }
+ /*
+ * Switch to new pidfile.
+ */
+ if (newpfh != NULL) {
+ pjdlog_info("Pidfile changed from %s to %s.", cfg->hc_pidfile,
+ newcfg->hc_pidfile);
+ (void)pidfile_remove(pfh);
+ pfh = newpfh;
+ (void)strlcpy(cfg->hc_pidfile, newcfg->hc_pidfile,
+ sizeof(cfg->hc_pidfile));
+ }
+ /*
+ * Switch to new listen addresses. Close all that were removed.
+ */
+ while ((clst = TAILQ_FIRST(&cfg->hc_listen)) != NULL) {
+ TAILQ_FOREACH(nlst, &newcfg->hc_listen, hl_next) {
+ if (strcmp(nlst->hl_addr, clst->hl_addr) == 0)
+ break;
+ }
+ if (nlst == NULL && clst->hl_conn != NULL) {
+ proto_close(clst->hl_conn);
+ pjdlog_info("No longer listening on address %s.",
+ clst->hl_addr);
+ }
+ TAILQ_REMOVE(&cfg->hc_listen, clst, hl_next);
+ free(clst);
+ }
+ TAILQ_CONCAT(&cfg->hc_listen, &newcfg->hc_listen, hl_next);
+
+ /*
+ * Stop and remove resources that were removed from the configuration.
+ */
+ TAILQ_FOREACH_SAFE(cres, &cfg->hc_resources, hr_next, tres) {
+ TAILQ_FOREACH(nres, &newcfg->hc_resources, hr_next) {
+ if (strcmp(cres->hr_name, nres->hr_name) == 0)
+ break;
+ }
+ if (nres == NULL) {
+ control_set_role(cres, HAST_ROLE_INIT);
+ TAILQ_REMOVE(&cfg->hc_resources, cres, hr_next);
+ pjdlog_info("Resource %s removed.", cres->hr_name);
+ free(cres);
+ }
+ }
+ /*
+ * Move new resources to the current configuration.
+ */
+ TAILQ_FOREACH_SAFE(nres, &newcfg->hc_resources, hr_next, tres) {
+ TAILQ_FOREACH(cres, &cfg->hc_resources, hr_next) {
+ if (strcmp(cres->hr_name, nres->hr_name) == 0)
+ break;
+ }
+ if (cres == NULL) {
+ TAILQ_REMOVE(&newcfg->hc_resources, nres, hr_next);
+ TAILQ_INSERT_TAIL(&cfg->hc_resources, nres, hr_next);
+ pjdlog_info("Resource %s added.", nres->hr_name);
+ }
+ }
+ /*
+ * Deal with modified resources.
+ * Depending on what has changed exactly we might want to perform
+ * different actions.
+ *
+ * We do full resource restart in the following situations:
+ * Resource role is INIT or SECONDARY.
+ * Resource role is PRIMARY and path to local component or provider
+ * name has changed.
+ * In case of PRIMARY, the worker process will be killed and restarted,
+ * which also means removing /dev/hast/<name> provider and
+ * recreating it.
+ *
+ * We do just reload (send SIGHUP to worker process) if we act as
+ * PRIMARY, but only if remote address, source address, replication
+ * mode, timeout, execution path or metaflush has changed.
+ * For those, there is no need to restart worker process.
+ * If PRIMARY receives SIGHUP, it will reconnect if remote address or
+ * source address has changed or it will set new timeout if only timeout
+ * has changed or it will update metaflush if only metaflush has
+ * changed.
+ */
+ TAILQ_FOREACH_SAFE(nres, &newcfg->hc_resources, hr_next, tres) {
+ TAILQ_FOREACH(cres, &cfg->hc_resources, hr_next) {
+ if (strcmp(cres->hr_name, nres->hr_name) == 0)
+ break;
+ }
+ PJDLOG_ASSERT(cres != NULL);
+ if (resource_needs_restart(cres, nres)) {
+ pjdlog_info("Resource %s configuration was modified, restarting it.",
+ cres->hr_name);
+ role = cres->hr_role;
+ control_set_role(cres, HAST_ROLE_INIT);
+ TAILQ_REMOVE(&cfg->hc_resources, cres, hr_next);
+ free(cres);
+ TAILQ_REMOVE(&newcfg->hc_resources, nres, hr_next);
+ TAILQ_INSERT_TAIL(&cfg->hc_resources, nres, hr_next);
+ control_set_role(nres, role);
+ } else if (resource_needs_reload(cres, nres)) {
+ pjdlog_info("Resource %s configuration was modified, reloading it.",
+ cres->hr_name);
+ strlcpy(cres->hr_remoteaddr, nres->hr_remoteaddr,
+ sizeof(cres->hr_remoteaddr));
+ strlcpy(cres->hr_sourceaddr, nres->hr_sourceaddr,
+ sizeof(cres->hr_sourceaddr));
+ cres->hr_replication = nres->hr_replication;
+ cres->hr_checksum = nres->hr_checksum;
+ cres->hr_compression = nres->hr_compression;
+ cres->hr_timeout = nres->hr_timeout;
+ strlcpy(cres->hr_exec, nres->hr_exec,
+ sizeof(cres->hr_exec));
+ cres->hr_metaflush = nres->hr_metaflush;
+ if (cres->hr_workerpid != 0)
+ resource_reload(cres);
+ }
+ }
+
+ yy_config_free(newcfg);
+ pjdlog_info("Configuration reloaded successfully.");
+ return;
+failed:
+ if (newcfg != NULL) {
+ if (newcfg->hc_controlconn != NULL)
+ proto_close(newcfg->hc_controlconn);
+ while ((nlst = TAILQ_FIRST(&newcfg->hc_listen)) != NULL) {
+ if (nlst->hl_conn != NULL) {
+ TAILQ_FOREACH(clst, &cfg->hc_listen, hl_next) {
+ if (strcmp(nlst->hl_addr,
+ clst->hl_addr) == 0) {
+ break;
+ }
+ }
+ if (clst == NULL || clst->hl_conn == NULL)
+ proto_close(nlst->hl_conn);
+ }
+ TAILQ_REMOVE(&newcfg->hc_listen, nlst, hl_next);
+ free(nlst);
+ }
+ yy_config_free(newcfg);
+ }
+ if (newpfh != NULL)
+ (void)pidfile_remove(newpfh);
+ pjdlog_warning("Configuration not reloaded.");
+}
+
+static void
+terminate_workers(void)
+{
+ struct hast_resource *res;
+
+ pjdlog_info("Termination signal received, exiting.");
+ TAILQ_FOREACH(res, &cfg->hc_resources, hr_next) {
+ if (res->hr_workerpid == 0)
+ continue;
+ pjdlog_info("Terminating worker process (resource=%s, role=%s, pid=%u).",
+ res->hr_name, role2str(res->hr_role), res->hr_workerpid);
+ if (kill(res->hr_workerpid, SIGTERM) == 0)
+ continue;
+ pjdlog_errno(LOG_WARNING,
+ "Unable to send signal to worker process (resource=%s, role=%s, pid=%u).",
+ res->hr_name, role2str(res->hr_role), res->hr_workerpid);
+ }
+}
+
+static void
+listen_accept(struct hastd_listen *lst)
+{
+ struct hast_resource *res;
+ struct proto_conn *conn;
+ struct nv *nvin, *nvout, *nverr;
+ const char *resname;
+ const unsigned char *token;
+ char laddr[256], raddr[256];
+ uint8_t version;
+ size_t size;
+ pid_t pid;
+ int status;
+
+ proto_local_address(lst->hl_conn, laddr, sizeof(laddr));
+ pjdlog_debug(1, "Accepting connection to %s.", laddr);
+
+ if (proto_accept(lst->hl_conn, &conn) == -1) {
+ pjdlog_errno(LOG_ERR, "Unable to accept connection %s", laddr);
+ return;
+ }
+
+ proto_local_address(conn, laddr, sizeof(laddr));
+ proto_remote_address(conn, raddr, sizeof(raddr));
+ pjdlog_info("Connection from %s to %s.", raddr, laddr);
+
+ /* Error in setting timeout is not critical, but why should it fail? */
+ if (proto_timeout(conn, HAST_TIMEOUT) == -1)
+ pjdlog_errno(LOG_WARNING, "Unable to set connection timeout");
+
+ nvin = nvout = nverr = NULL;
+
+ /*
+ * Before receiving any data see if remote host have access to any
+ * resource.
+ */
+ TAILQ_FOREACH(res, &cfg->hc_resources, hr_next) {
+ if (proto_address_match(conn, res->hr_remoteaddr))
+ break;
+ }
+ if (res == NULL) {
+ pjdlog_error("Client %s isn't known.", raddr);
+ goto close;
+ }
+ /* Ok, remote host can access at least one resource. */
+
+ if (hast_proto_recv_hdr(conn, &nvin) == -1) {
+ pjdlog_errno(LOG_ERR, "Unable to receive header from %s",
+ raddr);
+ goto close;
+ }
+
+ resname = nv_get_string(nvin, "resource");
+ if (resname == NULL) {
+ pjdlog_error("No 'resource' field in the header received from %s.",
+ raddr);
+ goto close;
+ }
+ pjdlog_debug(2, "%s: resource=%s", raddr, resname);
+ version = nv_get_uint8(nvin, "version");
+ pjdlog_debug(2, "%s: version=%hhu", raddr, version);
+ if (version == 0) {
+ /*
+ * If no version is sent, it means this is protocol version 1.
+ */
+ version = 1;
+ }
+ token = nv_get_uint8_array(nvin, &size, "token");
+ /*
+ * NULL token means that this is first connection.
+ */
+ if (token != NULL && size != sizeof(res->hr_token)) {
+ pjdlog_error("Received token of invalid size from %s (expected %zu, got %zu).",
+ raddr, sizeof(res->hr_token), size);
+ goto close;
+ }
+
+ /*
+ * From now on we want to send errors to the remote node.
+ */
+ nverr = nv_alloc();
+
+ /* Find resource related to this connection. */
+ TAILQ_FOREACH(res, &cfg->hc_resources, hr_next) {
+ if (strcmp(resname, res->hr_name) == 0)
+ break;
+ }
+ /* Have we found the resource? */
+ if (res == NULL) {
+ pjdlog_error("No resource '%s' as requested by %s.",
+ resname, raddr);
+ nv_add_stringf(nverr, "errmsg", "Resource not configured.");
+ goto fail;
+ }
+
+ /* Now that we know resource name setup log prefix. */
+ pjdlog_prefix_set("[%s] (%s) ", res->hr_name, role2str(res->hr_role));
+
+ /* Does the remote host have access to this resource? */
+ if (!proto_address_match(conn, res->hr_remoteaddr)) {
+ pjdlog_error("Client %s has no access to the resource.", raddr);
+ nv_add_stringf(nverr, "errmsg", "No access to the resource.");
+ goto fail;
+ }
+ /* Is the resource marked as secondary? */
+ if (res->hr_role != HAST_ROLE_SECONDARY) {
+ pjdlog_warning("We act as %s for the resource and not as %s as requested by %s.",
+ role2str(res->hr_role), role2str(HAST_ROLE_SECONDARY),
+ raddr);
+ nv_add_stringf(nverr, "errmsg",
+ "Remote node acts as %s for the resource and not as %s.",
+ role2str(res->hr_role), role2str(HAST_ROLE_SECONDARY));
+ if (res->hr_role == HAST_ROLE_PRIMARY) {
+ /*
+ * If we act as primary request the other side to wait
+ * for us a bit, as we might be finishing cleanups.
+ */
+ nv_add_uint8(nverr, 1, "wait");
+ }
+ goto fail;
+ }
+ /* Does token (if exists) match? */
+ if (token != NULL && memcmp(token, res->hr_token,
+ sizeof(res->hr_token)) != 0) {
+ pjdlog_error("Token received from %s doesn't match.", raddr);
+ nv_add_stringf(nverr, "errmsg", "Token doesn't match.");
+ goto fail;
+ }
+ /*
+ * If there is no token, but we have half-open connection
+ * (only remotein) or full connection (worker process is running)
+ * we have to cancel those and accept the new connection.
+ */
+ if (token == NULL) {
+ PJDLOG_ASSERT(res->hr_remoteout == NULL);
+ pjdlog_debug(1, "Initial connection from %s.", raddr);
+ if (res->hr_workerpid != 0) {
+ PJDLOG_ASSERT(res->hr_remotein == NULL);
+ pjdlog_debug(1,
+ "Worker process exists (pid=%u), stopping it.",
+ (unsigned int)res->hr_workerpid);
+ /* Stop child process. */
+ if (kill(res->hr_workerpid, SIGINT) == -1) {
+ pjdlog_errno(LOG_ERR,
+ "Unable to stop worker process (pid=%u)",
+ (unsigned int)res->hr_workerpid);
+ /*
+ * Other than logging the problem we
+ * ignore it - nothing smart to do.
+ */
+ }
+ /* Wait for it to exit. */
+ else if ((pid = waitpid(res->hr_workerpid,
+ &status, 0)) != res->hr_workerpid) {
+ /* We can only log the problem. */
+ pjdlog_errno(LOG_ERR,
+ "Waiting for worker process (pid=%u) failed",
+ (unsigned int)res->hr_workerpid);
+ } else {
+ child_exit_log(res->hr_workerpid, status);
+ }
+ child_cleanup(res);
+ } else if (res->hr_remotein != NULL) {
+ char oaddr[256];
+
+ proto_remote_address(res->hr_remotein, oaddr,
+ sizeof(oaddr));
+ pjdlog_debug(1,
+ "Canceling half-open connection from %s on connection from %s.",
+ oaddr, raddr);
+ proto_close(res->hr_remotein);
+ res->hr_remotein = NULL;
+ }
+ }
+
+ /*
+ * Checks and cleanups are done.
+ */
+
+ if (token == NULL) {
+ if (version > HAST_PROTO_VERSION) {
+ pjdlog_info("Remote protocol version %hhu is not supported, falling back to version %hhu.",
+ version, (unsigned char)HAST_PROTO_VERSION);
+ version = HAST_PROTO_VERSION;
+ }
+ pjdlog_debug(1, "Negotiated protocol version %hhu.", version);
+ res->hr_version = version;
+ arc4random_buf(res->hr_token, sizeof(res->hr_token));
+ nvout = nv_alloc();
+ nv_add_uint8(nvout, version, "version");
+ nv_add_uint8_array(nvout, res->hr_token,
+ sizeof(res->hr_token), "token");
+ if (nv_error(nvout) != 0) {
+ pjdlog_common(LOG_ERR, 0, nv_error(nvout),
+ "Unable to prepare return header for %s", raddr);
+ nv_add_stringf(nverr, "errmsg",
+ "Remote node was unable to prepare return header: %s.",
+ strerror(nv_error(nvout)));
+ goto fail;
+ }
+ if (hast_proto_send(res, conn, nvout, NULL, 0) == -1) {
+ int error = errno;
+
+ pjdlog_errno(LOG_ERR, "Unable to send response to %s",
+ raddr);
+ nv_add_stringf(nverr, "errmsg",
+ "Remote node was unable to send response: %s.",
+ strerror(error));
+ goto fail;
+ }
+ res->hr_remotein = conn;
+ pjdlog_debug(1, "Incoming connection from %s configured.",
+ raddr);
+ } else {
+ res->hr_remoteout = conn;
+ pjdlog_debug(1, "Outgoing connection to %s configured.", raddr);
+ hastd_secondary(res, nvin);
+ }
+ nv_free(nvin);
+ nv_free(nvout);
+ nv_free(nverr);
+ pjdlog_prefix_set("%s", "");
+ return;
+fail:
+ if (nv_error(nverr) != 0) {
+ pjdlog_common(LOG_ERR, 0, nv_error(nverr),
+ "Unable to prepare error header for %s", raddr);
+ goto close;
+ }
+ if (hast_proto_send(NULL, conn, nverr, NULL, 0) == -1) {
+ pjdlog_errno(LOG_ERR, "Unable to send error to %s", raddr);
+ goto close;
+ }
+close:
+ if (nvin != NULL)
+ nv_free(nvin);
+ if (nvout != NULL)
+ nv_free(nvout);
+ if (nverr != NULL)
+ nv_free(nverr);
+ proto_close(conn);
+ pjdlog_prefix_set("%s", "");
+}
+
+static void
+connection_migrate(struct hast_resource *res)
+{
+ struct proto_conn *conn;
+ int16_t val = 0;
+
+ pjdlog_prefix_set("[%s] (%s) ", res->hr_name, role2str(res->hr_role));
+
+ PJDLOG_ASSERT(res->hr_role == HAST_ROLE_PRIMARY);
+
+ if (proto_recv(res->hr_conn, &val, sizeof(val)) == -1) {
+ pjdlog_errno(LOG_WARNING,
+ "Unable to receive connection command");
+ return;
+ }
+ if (proto_client(res->hr_sourceaddr[0] != '\0' ? res->hr_sourceaddr : NULL,
+ res->hr_remoteaddr, &conn) == -1) {
+ val = errno;
+ pjdlog_errno(LOG_WARNING,
+ "Unable to create outgoing connection to %s",
+ res->hr_remoteaddr);
+ goto out;
+ }
+ if (proto_connect(conn, -1) == -1) {
+ val = errno;
+ pjdlog_errno(LOG_WARNING, "Unable to connect to %s",
+ res->hr_remoteaddr);
+ proto_close(conn);
+ goto out;
+ }
+ val = 0;
+out:
+ if (proto_send(res->hr_conn, &val, sizeof(val)) == -1) {
+ pjdlog_errno(LOG_WARNING,
+ "Unable to send reply to connection request");
+ }
+ if (val == 0 && proto_connection_send(res->hr_conn, conn) == -1)
+ pjdlog_errno(LOG_WARNING, "Unable to send connection");
+
+ pjdlog_prefix_set("%s", "");
+}
+
+static void
+check_signals(void)
+{
+ struct timespec sigtimeout;
+ sigset_t mask;
+ int signo;
+
+ sigtimeout.tv_sec = 0;
+ sigtimeout.tv_nsec = 0;
+
+ PJDLOG_VERIFY(sigemptyset(&mask) == 0);
+ PJDLOG_VERIFY(sigaddset(&mask, SIGHUP) == 0);
+ PJDLOG_VERIFY(sigaddset(&mask, SIGINT) == 0);
+ PJDLOG_VERIFY(sigaddset(&mask, SIGTERM) == 0);
+ PJDLOG_VERIFY(sigaddset(&mask, SIGCHLD) == 0);
+
+ while ((signo = sigtimedwait(&mask, NULL, &sigtimeout)) != -1) {
+ switch (signo) {
+ case SIGINT:
+ case SIGTERM:
+ sigexit_received = true;
+ terminate_workers();
+ proto_close(cfg->hc_controlconn);
+ exit(EX_OK);
+ break;
+ case SIGCHLD:
+ child_exit();
+ break;
+ case SIGHUP:
+ hastd_reload();
+ break;
+ default:
+ PJDLOG_ABORT("Unexpected signal (%d).", signo);
+ }
+ }
+}
+
+static void
+main_loop(void)
+{
+ struct hast_resource *res;
+ struct hastd_listen *lst;
+ struct timeval seltimeout;
+ int fd, maxfd, ret;
+ time_t lastcheck, now;
+ fd_set rfds;
+
+ lastcheck = time(NULL);
+ seltimeout.tv_sec = REPORT_INTERVAL;
+ seltimeout.tv_usec = 0;
+
+ for (;;) {
+ check_signals();
+
+ /* Setup descriptors for select(2). */
+ FD_ZERO(&rfds);
+ maxfd = fd = proto_descriptor(cfg->hc_controlconn);
+ PJDLOG_ASSERT(fd >= 0);
+ FD_SET(fd, &rfds);
+ TAILQ_FOREACH(lst, &cfg->hc_listen, hl_next) {
+ if (lst->hl_conn == NULL)
+ continue;
+ fd = proto_descriptor(lst->hl_conn);
+ PJDLOG_ASSERT(fd >= 0);
+ FD_SET(fd, &rfds);
+ maxfd = fd > maxfd ? fd : maxfd;
+ }
+ TAILQ_FOREACH(res, &cfg->hc_resources, hr_next) {
+ if (res->hr_event == NULL)
+ continue;
+ fd = proto_descriptor(res->hr_event);
+ PJDLOG_ASSERT(fd >= 0);
+ FD_SET(fd, &rfds);
+ maxfd = fd > maxfd ? fd : maxfd;
+ if (res->hr_role == HAST_ROLE_PRIMARY) {
+ /* Only primary workers asks for connections. */
+ PJDLOG_ASSERT(res->hr_conn != NULL);
+ fd = proto_descriptor(res->hr_conn);
+ PJDLOG_ASSERT(fd >= 0);
+ FD_SET(fd, &rfds);
+ maxfd = fd > maxfd ? fd : maxfd;
+ } else {
+ PJDLOG_ASSERT(res->hr_conn == NULL);
+ }
+ }
+
+ PJDLOG_ASSERT(maxfd + 1 <= (int)FD_SETSIZE);
+ ret = select(maxfd + 1, &rfds, NULL, NULL, &seltimeout);
+ now = time(NULL);
+ if (lastcheck + REPORT_INTERVAL <= now) {
+ hook_check();
+ lastcheck = now;
+ }
+ if (ret == 0) {
+ /*
+ * select(2) timed out, so there should be no
+ * descriptors to check.
+ */
+ continue;
+ } else if (ret == -1) {
+ if (errno == EINTR)
+ continue;
+ KEEP_ERRNO((void)pidfile_remove(pfh));
+ pjdlog_exit(EX_OSERR, "select() failed");
+ }
+
+ /*
+ * Check for signals before we do anything to update our
+ * info about terminated workers in the meantime.
+ */
+ check_signals();
+
+ if (FD_ISSET(proto_descriptor(cfg->hc_controlconn), &rfds))
+ control_handle(cfg);
+ TAILQ_FOREACH(lst, &cfg->hc_listen, hl_next) {
+ if (lst->hl_conn == NULL)
+ continue;
+ if (FD_ISSET(proto_descriptor(lst->hl_conn), &rfds))
+ listen_accept(lst);
+ }
+ TAILQ_FOREACH(res, &cfg->hc_resources, hr_next) {
+ if (res->hr_event == NULL)
+ continue;
+ if (FD_ISSET(proto_descriptor(res->hr_event), &rfds)) {
+ if (event_recv(res) == 0)
+ continue;
+ /* The worker process exited? */
+ proto_close(res->hr_event);
+ res->hr_event = NULL;
+ if (res->hr_conn != NULL) {
+ proto_close(res->hr_conn);
+ res->hr_conn = NULL;
+ }
+ continue;
+ }
+ if (res->hr_role == HAST_ROLE_PRIMARY) {
+ PJDLOG_ASSERT(res->hr_conn != NULL);
+ if (FD_ISSET(proto_descriptor(res->hr_conn),
+ &rfds)) {
+ connection_migrate(res);
+ }
+ } else {
+ PJDLOG_ASSERT(res->hr_conn == NULL);
+ }
+ }
+ }
+}
+
+static void
+dummy_sighandler(int sig __unused)
+{
+ /* Nothing to do. */
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct hastd_listen *lst;
+ pid_t otherpid;
+ int debuglevel;
+ sigset_t mask;
+
+ foreground = false;
+ debuglevel = 0;
+
+ for (;;) {
+ int ch;
+
+ ch = getopt(argc, argv, "c:dFhP:");
+ if (ch == -1)
+ break;
+ switch (ch) {
+ case 'c':
+ cfgpath = optarg;
+ break;
+ case 'd':
+ debuglevel++;
+ break;
+ case 'F':
+ foreground = true;
+ break;
+ case 'P':
+ pidfile = optarg;
+ break;
+ case 'h':
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ pjdlog_init(PJDLOG_MODE_STD);
+ pjdlog_debug_set(debuglevel);
+
+ g_gate_load();
+
+ /*
+ * When path to the configuration file is relative, obtain full path,
+ * so we can always find the file, even after daemonizing and changing
+ * working directory to /.
+ */
+ if (cfgpath[0] != '/') {
+ const char *newcfgpath;
+
+ newcfgpath = realpath(cfgpath, NULL);
+ if (newcfgpath == NULL) {
+ pjdlog_exit(EX_CONFIG,
+ "Unable to obtain full path of %s", cfgpath);
+ }
+ cfgpath = newcfgpath;
+ }
+
+ cfg = yy_config_parse(cfgpath, true);
+ PJDLOG_ASSERT(cfg != NULL);
+
+ if (pidfile != NULL) {
+ if (strlcpy(cfg->hc_pidfile, pidfile,
+ sizeof(cfg->hc_pidfile)) >= sizeof(cfg->hc_pidfile)) {
+ pjdlog_exitx(EX_CONFIG, "Pidfile path is too long.");
+ }
+ }
+
+ if (pidfile != NULL || !foreground) {
+ pfh = pidfile_open(cfg->hc_pidfile, 0600, &otherpid);
+ if (pfh == NULL) {
+ if (errno == EEXIST) {
+ pjdlog_exitx(EX_TEMPFAIL,
+ "Another hastd is already running, pidfile: %s, pid: %jd.",
+ cfg->hc_pidfile, (intmax_t)otherpid);
+ }
+ /*
+ * If we cannot create pidfile for other reasons,
+ * only warn.
+ */
+ pjdlog_errno(LOG_WARNING,
+ "Unable to open or create pidfile %s",
+ cfg->hc_pidfile);
+ }
+ }
+
+ /*
+ * Restore default actions for interesting signals in case parent
+ * process (like init(8)) decided to ignore some of them (like SIGHUP).
+ */
+ PJDLOG_VERIFY(signal(SIGHUP, SIG_DFL) != SIG_ERR);
+ PJDLOG_VERIFY(signal(SIGINT, SIG_DFL) != SIG_ERR);
+ PJDLOG_VERIFY(signal(SIGTERM, SIG_DFL) != SIG_ERR);
+ /*
+ * Because SIGCHLD is ignored by default, setup dummy handler for it,
+ * so we can mask it.
+ */
+ PJDLOG_VERIFY(signal(SIGCHLD, dummy_sighandler) != SIG_ERR);
+
+ PJDLOG_VERIFY(sigemptyset(&mask) == 0);
+ PJDLOG_VERIFY(sigaddset(&mask, SIGHUP) == 0);
+ PJDLOG_VERIFY(sigaddset(&mask, SIGINT) == 0);
+ PJDLOG_VERIFY(sigaddset(&mask, SIGTERM) == 0);
+ PJDLOG_VERIFY(sigaddset(&mask, SIGCHLD) == 0);
+ PJDLOG_VERIFY(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);
+
+ /* Listen on control address. */
+ if (proto_server(cfg->hc_controladdr, &cfg->hc_controlconn) == -1) {
+ KEEP_ERRNO((void)pidfile_remove(pfh));
+ pjdlog_exit(EX_OSERR, "Unable to listen on control address %s",
+ cfg->hc_controladdr);
+ }
+ /* Listen for remote connections. */
+ TAILQ_FOREACH(lst, &cfg->hc_listen, hl_next) {
+ if (proto_server(lst->hl_addr, &lst->hl_conn) == -1) {
+ KEEP_ERRNO((void)pidfile_remove(pfh));
+ pjdlog_exit(EX_OSERR, "Unable to listen on address %s",
+ lst->hl_addr);
+ }
+ }
+
+ if (!foreground) {
+ if (daemon(0, 0) == -1) {
+ KEEP_ERRNO((void)pidfile_remove(pfh));
+ pjdlog_exit(EX_OSERR, "Unable to daemonize");
+ }
+
+ /* Start logging to syslog. */
+ pjdlog_mode_set(PJDLOG_MODE_SYSLOG);
+ }
+ if (pidfile != NULL || !foreground) {
+ /* Write PID to a file. */
+ if (pidfile_write(pfh) == -1) {
+ pjdlog_errno(LOG_WARNING,
+ "Unable to write PID to a file %s",
+ cfg->hc_pidfile);
+ } else {
+ pjdlog_debug(1, "PID stored in %s.", cfg->hc_pidfile);
+ }
+ }
+
+ pjdlog_info("Started successfully, running protocol version %d.",
+ HAST_PROTO_VERSION);
+
+ pjdlog_debug(1, "Listening on control address %s.",
+ cfg->hc_controladdr);
+ TAILQ_FOREACH(lst, &cfg->hc_listen, hl_next)
+ pjdlog_info("Listening on address %s.", lst->hl_addr);
+
+ hook_init();
+
+ main_loop();
+
+ exit(0);
+}
diff --git a/sbin/hastd/hastd.h b/sbin/hastd/hastd.h
new file mode 100644
index 0000000..d23e855
--- /dev/null
+++ b/sbin/hastd/hastd.h
@@ -0,0 +1,54 @@
+/*-
+ * Copyright (c) 2009-2010 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _HASTD_H_
+#define _HASTD_H_
+
+#include <sys/param.h>
+#include <libutil.h>
+
+#include <nv.h>
+
+#include "hast.h"
+
+extern const char *cfgpath;
+extern bool sigexit_received;
+extern struct pidfh *pfh;
+
+void descriptors_cleanup(struct hast_resource *res);
+void descriptors_assert(const struct hast_resource *res, int pjdlogmode);
+
+void hastd_primary(struct hast_resource *res);
+void hastd_secondary(struct hast_resource *res, struct nv *nvin);
+
+void primary_config_reload(struct hast_resource *res, struct nv *nv);
+
+#endif /* !_HASTD_H_ */
diff --git a/sbin/hastd/hooks.c b/sbin/hastd/hooks.c
new file mode 100644
index 0000000..b1886ca
--- /dev/null
+++ b/sbin/hastd/hooks.c
@@ -0,0 +1,391 @@
+/*-
+ * Copyright (c) 2010 The FreeBSD Foundation
+ * Copyright (c) 2010 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+ * All rights reserved.
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/sysctl.h>
+#include <sys/wait.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <paths.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include <pjdlog.h>
+
+#include "hooks.h"
+#include "subr.h"
+#include "synch.h"
+
+/* Report processes that are running for too long not often than this value. */
+#define REPORT_INTERVAL 60
+
+/* Are we initialized? */
+static bool hooks_initialized = false;
+
+/*
+ * Keep all processes we forked on a global queue, so we can report nicely
+ * when they finish or report that they are running for a long time.
+ */
+#define HOOKPROC_MAGIC_ALLOCATED 0x80090ca
+#define HOOKPROC_MAGIC_ONLIST 0x80090c0
+struct hookproc {
+ /* Magic. */
+ int hp_magic;
+ /* PID of a forked child. */
+ pid_t hp_pid;
+ /* When process were forked? */
+ time_t hp_birthtime;
+ /* When we logged previous reported? */
+ time_t hp_lastreport;
+ /* Path to executable and all the arguments we passed. */
+ char hp_comm[PATH_MAX];
+ TAILQ_ENTRY(hookproc) hp_next;
+};
+static TAILQ_HEAD(, hookproc) hookprocs;
+static pthread_mutex_t hookprocs_lock;
+
+static void hook_remove(struct hookproc *hp);
+static void hook_free(struct hookproc *hp);
+
+static void
+descriptors(void)
+{
+ int fd;
+
+ /*
+ * Close all (or almost all) descriptors.
+ */
+ if (pjdlog_mode_get() == PJDLOG_MODE_STD) {
+ closefrom(MAX(MAX(STDIN_FILENO, STDOUT_FILENO),
+ STDERR_FILENO) + 1);
+ return;
+ }
+
+ closefrom(0);
+
+ /*
+ * Redirect stdin, stdout and stderr to /dev/null.
+ */
+ fd = open(_PATH_DEVNULL, O_RDONLY);
+ if (fd == -1) {
+ pjdlog_errno(LOG_WARNING, "Unable to open %s for reading",
+ _PATH_DEVNULL);
+ } else if (fd != STDIN_FILENO) {
+ if (dup2(fd, STDIN_FILENO) == -1) {
+ pjdlog_errno(LOG_WARNING,
+ "Unable to duplicate descriptor for stdin");
+ }
+ close(fd);
+ }
+ fd = open(_PATH_DEVNULL, O_WRONLY);
+ if (fd == -1) {
+ pjdlog_errno(LOG_WARNING, "Unable to open %s for writing",
+ _PATH_DEVNULL);
+ } else {
+ if (fd != STDOUT_FILENO && dup2(fd, STDOUT_FILENO) == -1) {
+ pjdlog_errno(LOG_WARNING,
+ "Unable to duplicate descriptor for stdout");
+ }
+ if (fd != STDERR_FILENO && dup2(fd, STDERR_FILENO) == -1) {
+ pjdlog_errno(LOG_WARNING,
+ "Unable to duplicate descriptor for stderr");
+ }
+ if (fd != STDOUT_FILENO && fd != STDERR_FILENO)
+ close(fd);
+ }
+}
+
+void
+hook_init(void)
+{
+
+ PJDLOG_ASSERT(!hooks_initialized);
+
+ mtx_init(&hookprocs_lock);
+ TAILQ_INIT(&hookprocs);
+ hooks_initialized = true;
+}
+
+void
+hook_fini(void)
+{
+ struct hookproc *hp;
+
+ PJDLOG_ASSERT(hooks_initialized);
+
+ mtx_lock(&hookprocs_lock);
+ while ((hp = TAILQ_FIRST(&hookprocs)) != NULL) {
+ PJDLOG_ASSERT(hp->hp_magic == HOOKPROC_MAGIC_ONLIST);
+ PJDLOG_ASSERT(hp->hp_pid > 0);
+
+ hook_remove(hp);
+ hook_free(hp);
+ }
+ mtx_unlock(&hookprocs_lock);
+
+ mtx_destroy(&hookprocs_lock);
+ TAILQ_INIT(&hookprocs);
+ hooks_initialized = false;
+}
+
+static struct hookproc *
+hook_alloc(const char *path, char **args)
+{
+ struct hookproc *hp;
+ unsigned int ii;
+
+ hp = malloc(sizeof(*hp));
+ if (hp == NULL) {
+ pjdlog_error("Unable to allocate %zu bytes of memory for a hook.",
+ sizeof(*hp));
+ return (NULL);
+ }
+
+ hp->hp_pid = 0;
+ hp->hp_birthtime = hp->hp_lastreport = time(NULL);
+ (void)strlcpy(hp->hp_comm, path, sizeof(hp->hp_comm));
+ /* We start at 2nd argument as we don't want to have exec name twice. */
+ for (ii = 1; args[ii] != NULL; ii++) {
+ (void)snprlcat(hp->hp_comm, sizeof(hp->hp_comm), " %s",
+ args[ii]);
+ }
+ if (strlen(hp->hp_comm) >= sizeof(hp->hp_comm) - 1) {
+ pjdlog_error("Exec path too long, correct configuration file.");
+ free(hp);
+ return (NULL);
+ }
+ hp->hp_magic = HOOKPROC_MAGIC_ALLOCATED;
+ return (hp);
+}
+
+static void
+hook_add(struct hookproc *hp, pid_t pid)
+{
+
+ PJDLOG_ASSERT(hp->hp_magic == HOOKPROC_MAGIC_ALLOCATED);
+ PJDLOG_ASSERT(hp->hp_pid == 0);
+
+ hp->hp_pid = pid;
+ mtx_lock(&hookprocs_lock);
+ hp->hp_magic = HOOKPROC_MAGIC_ONLIST;
+ TAILQ_INSERT_TAIL(&hookprocs, hp, hp_next);
+ mtx_unlock(&hookprocs_lock);
+}
+
+static void
+hook_remove(struct hookproc *hp)
+{
+
+ PJDLOG_ASSERT(hp->hp_magic == HOOKPROC_MAGIC_ONLIST);
+ PJDLOG_ASSERT(hp->hp_pid > 0);
+ PJDLOG_ASSERT(mtx_owned(&hookprocs_lock));
+
+ TAILQ_REMOVE(&hookprocs, hp, hp_next);
+ hp->hp_magic = HOOKPROC_MAGIC_ALLOCATED;
+}
+
+static void
+hook_free(struct hookproc *hp)
+{
+
+ PJDLOG_ASSERT(hp->hp_magic == HOOKPROC_MAGIC_ALLOCATED);
+ PJDLOG_ASSERT(hp->hp_pid > 0);
+
+ hp->hp_magic = 0;
+ free(hp);
+}
+
+static struct hookproc *
+hook_find(pid_t pid)
+{
+ struct hookproc *hp;
+
+ PJDLOG_ASSERT(mtx_owned(&hookprocs_lock));
+
+ TAILQ_FOREACH(hp, &hookprocs, hp_next) {
+ PJDLOG_ASSERT(hp->hp_magic == HOOKPROC_MAGIC_ONLIST);
+ PJDLOG_ASSERT(hp->hp_pid > 0);
+
+ if (hp->hp_pid == pid)
+ break;
+ }
+
+ return (hp);
+}
+
+void
+hook_check_one(pid_t pid, int status)
+{
+ struct hookproc *hp;
+
+ mtx_lock(&hookprocs_lock);
+ hp = hook_find(pid);
+ if (hp == NULL) {
+ mtx_unlock(&hookprocs_lock);
+ pjdlog_debug(1, "Unknown process pid=%u", pid);
+ return;
+ }
+ hook_remove(hp);
+ mtx_unlock(&hookprocs_lock);
+ if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
+ pjdlog_debug(1, "Hook exited gracefully (pid=%u, cmd=[%s]).",
+ pid, hp->hp_comm);
+ } else if (WIFSIGNALED(status)) {
+ pjdlog_error("Hook was killed (pid=%u, signal=%d, cmd=[%s]).",
+ pid, WTERMSIG(status), hp->hp_comm);
+ } else {
+ pjdlog_error("Hook exited ungracefully (pid=%u, exitcode=%d, cmd=[%s]).",
+ pid, WIFEXITED(status) ? WEXITSTATUS(status) : -1,
+ hp->hp_comm);
+ }
+ hook_free(hp);
+}
+
+void
+hook_check(void)
+{
+ struct hookproc *hp, *hp2;
+ time_t now;
+
+ PJDLOG_ASSERT(hooks_initialized);
+
+ pjdlog_debug(2, "Checking hooks.");
+
+ /*
+ * Report about processes that are running for a long time.
+ */
+ now = time(NULL);
+ mtx_lock(&hookprocs_lock);
+ TAILQ_FOREACH_SAFE(hp, &hookprocs, hp_next, hp2) {
+ PJDLOG_ASSERT(hp->hp_magic == HOOKPROC_MAGIC_ONLIST);
+ PJDLOG_ASSERT(hp->hp_pid > 0);
+
+ /*
+ * If process doesn't exists we somehow missed it.
+ * Not much can be done expect for logging this situation.
+ */
+ if (kill(hp->hp_pid, 0) == -1 && errno == ESRCH) {
+ pjdlog_warning("Hook disappeared (pid=%u, cmd=[%s]).",
+ hp->hp_pid, hp->hp_comm);
+ hook_remove(hp);
+ hook_free(hp);
+ continue;
+ }
+
+ /*
+ * Skip proccesses younger than 1 minute.
+ */
+ if (now - hp->hp_lastreport < REPORT_INTERVAL)
+ continue;
+
+ /*
+ * Hook is running for too long, report it.
+ */
+ pjdlog_warning("Hook is running for %ju seconds (pid=%u, cmd=[%s]).",
+ (uintmax_t)(now - hp->hp_birthtime), hp->hp_pid,
+ hp->hp_comm);
+ hp->hp_lastreport = now;
+ }
+ mtx_unlock(&hookprocs_lock);
+}
+
+void
+hook_exec(const char *path, ...)
+{
+ va_list ap;
+
+ va_start(ap, path);
+ hook_execv(path, ap);
+ va_end(ap);
+}
+
+void
+hook_execv(const char *path, va_list ap)
+{
+ struct hookproc *hp;
+ char *args[64];
+ unsigned int ii;
+ sigset_t mask;
+ pid_t pid;
+
+ PJDLOG_ASSERT(hooks_initialized);
+
+ if (path == NULL || path[0] == '\0')
+ return;
+
+ memset(args, 0, sizeof(args));
+ args[0] = basename(path);
+ for (ii = 1; ii < sizeof(args) / sizeof(args[0]); ii++) {
+ args[ii] = va_arg(ap, char *);
+ if (args[ii] == NULL)
+ break;
+ }
+ PJDLOG_ASSERT(ii < sizeof(args) / sizeof(args[0]));
+
+ hp = hook_alloc(path, args);
+ if (hp == NULL)
+ return;
+
+ pjdlog_debug(1, "Executing hook: %s", hp->hp_comm);
+
+ pid = fork();
+ switch (pid) {
+ case -1: /* Error. */
+ pjdlog_errno(LOG_ERR, "Unable to fork to execute %s", path);
+ hook_free(hp);
+ return;
+ case 0: /* Child. */
+ descriptors();
+ PJDLOG_VERIFY(sigemptyset(&mask) == 0);
+ PJDLOG_VERIFY(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);
+ /*
+ * Dummy handler set for SIGCHLD in the parent will be restored
+ * to SIG_IGN on execv(3) below, so there is no need to do
+ * anything with it.
+ */
+ execv(path, args);
+ pjdlog_errno(LOG_ERR, "Unable to execute %s", path);
+ exit(EX_SOFTWARE);
+ default: /* Parent. */
+ hook_add(hp, pid);
+ break;
+ }
+}
diff --git a/sbin/hastd/hooks.h b/sbin/hastd/hooks.h
new file mode 100644
index 0000000..4ce435e
--- /dev/null
+++ b/sbin/hastd/hooks.h
@@ -0,0 +1,48 @@
+/*-
+ * Copyright (c) 2010 The FreeBSD Foundation
+ * Copyright (c) 2010 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+ * All rights reserved.
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _HOOKS_H_
+#define _HOOKS_H_
+
+#include <sys/types.h>
+
+#include <stdarg.h>
+#include <stdbool.h>
+
+void hook_init(void);
+void hook_fini(void);
+void hook_check_one(pid_t pid, int status);
+void hook_check(void);
+void hook_exec(const char *path, ...);
+void hook_execv(const char *path, va_list ap);
+
+#endif /* !_HOOKS_H_ */
diff --git a/sbin/hastd/lzf.c b/sbin/hastd/lzf.c
new file mode 100644
index 0000000..cca6a17
--- /dev/null
+++ b/sbin/hastd/lzf.c
@@ -0,0 +1,406 @@
+/*
+ * Copyright (c) 2000-2008 Marc Alexander Lehmann <schmorp@schmorp.de>
+ *
+ * Redistribution and use in source and binary forms, with or without modifica-
+ * tion, 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER-
+ * CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPE-
+ * CIAL, 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 OTH-
+ * ERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License ("GPL") version 2 or any later version,
+ * in which case the provisions of the GPL are applicable instead of
+ * the above. If you wish to allow the use of your version of this file
+ * only under the terms of the GPL and not to allow others to use your
+ * version of this file under the BSD license, indicate your decision
+ * by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL. If you do not delete the
+ * provisions above, a recipient may use your version of this file under
+ * either the BSD or the GPL.
+ */
+
+#include "lzf.h"
+
+#define HSIZE (1 << (HLOG))
+
+/*
+ * don't play with this unless you benchmark!
+ * decompression is not dependent on the hash function
+ * the hashing function might seem strange, just believe me
+ * it works ;)
+ */
+#ifndef FRST
+# define FRST(p) (((p[0]) << 8) | p[1])
+# define NEXT(v,p) (((v) << 8) | p[2])
+# if ULTRA_FAST
+# define IDX(h) ((( h >> (3*8 - HLOG)) - h ) & (HSIZE - 1))
+# elif VERY_FAST
+# define IDX(h) ((( h >> (3*8 - HLOG)) - h*5) & (HSIZE - 1))
+# else
+# define IDX(h) ((((h ^ (h << 5)) >> (3*8 - HLOG)) - h*5) & (HSIZE - 1))
+# endif
+#endif
+/*
+ * IDX works because it is very similar to a multiplicative hash, e.g.
+ * ((h * 57321 >> (3*8 - HLOG)) & (HSIZE - 1))
+ * the latter is also quite fast on newer CPUs, and compresses similarly.
+ *
+ * the next one is also quite good, albeit slow ;)
+ * (int)(cos(h & 0xffffff) * 1e6)
+ */
+
+#if 0
+/* original lzv-like hash function, much worse and thus slower */
+# define FRST(p) (p[0] << 5) ^ p[1]
+# define NEXT(v,p) ((v) << 5) ^ p[2]
+# define IDX(h) ((h) & (HSIZE - 1))
+#endif
+
+#define MAX_LIT (1 << 5)
+#define MAX_OFF (1 << 13)
+#define MAX_REF ((1 << 8) + (1 << 3))
+
+#if __GNUC__ >= 3
+# define expect(expr,value) __builtin_expect ((expr),(value))
+# define inline inline
+#else
+# define expect(expr,value) (expr)
+# define inline static
+#endif
+
+#define expect_false(expr) expect ((expr) != 0, 0)
+#define expect_true(expr) expect ((expr) != 0, 1)
+
+/*
+ * compressed format
+ *
+ * 000LLLLL <L+1> ; literal
+ * LLLooooo oooooooo ; backref L
+ * 111ooooo LLLLLLLL oooooooo ; backref L+7
+ *
+ */
+
+unsigned int
+lzf_compress (const void *const in_data, unsigned int in_len,
+ void *out_data, unsigned int out_len
+#if LZF_STATE_ARG
+ , LZF_STATE htab
+#endif
+ )
+{
+#if !LZF_STATE_ARG
+ LZF_STATE htab;
+#endif
+ const u8 **hslot;
+ const u8 *ip = (const u8 *)in_data;
+ u8 *op = (u8 *)out_data;
+ const u8 *in_end = ip + in_len;
+ u8 *out_end = op + out_len;
+ const u8 *ref;
+
+ /* off requires a type wide enough to hold a general pointer difference.
+ * ISO C doesn't have that (size_t might not be enough and ptrdiff_t only
+ * works for differences within a single object). We also assume that no
+ * no bit pattern traps. Since the only platform that is both non-POSIX
+ * and fails to support both assumptions is windows 64 bit, we make a
+ * special workaround for it.
+ */
+#if defined (WIN32) && defined (_M_X64)
+ unsigned _int64 off; /* workaround for missing POSIX compliance */
+#else
+ unsigned long off;
+#endif
+ unsigned int hval;
+ int lit;
+
+ if (!in_len || !out_len)
+ return 0;
+
+#if INIT_HTAB
+ memset (htab, 0, sizeof (htab));
+# if 0
+ for (hslot = htab; hslot < htab + HSIZE; hslot++)
+ *hslot++ = ip;
+# endif
+#endif
+
+ lit = 0; op++; /* start run */
+
+ hval = FRST (ip);
+ while (ip < in_end - 2)
+ {
+ hval = NEXT (hval, ip);
+ hslot = htab + IDX (hval);
+ ref = *hslot; *hslot = ip;
+
+ if (1
+#if INIT_HTAB
+ && ref < ip /* the next test will actually take care of this, but this is faster */
+#endif
+ && (off = ip - ref - 1) < MAX_OFF
+ && ip + 4 < in_end
+ && ref > (const u8 *)in_data
+#if STRICT_ALIGN
+ && ref[0] == ip[0]
+ && ref[1] == ip[1]
+ && ref[2] == ip[2]
+#else
+ && *(const u16 *)ref == *(const u16 *)ip
+ && ref[2] == ip[2]
+#endif
+ )
+ {
+ /* match found at *ref++ */
+ unsigned int len = 2;
+ unsigned int maxlen = in_end - ip - len;
+ maxlen = maxlen > MAX_REF ? MAX_REF : maxlen;
+
+ if (expect_false (op + 3 + 1 >= out_end)) /* first a faster conservative test */
+ if (op - !lit + 3 + 1 >= out_end) /* second the exact but rare test */
+ return 0;
+
+ op [- lit - 1] = lit - 1; /* stop run */
+ op -= !lit; /* undo run if length is zero */
+
+ for (;;)
+ {
+ if (expect_true (maxlen > 16))
+ {
+ len++; if (ref [len] != ip [len]) break;
+ len++; if (ref [len] != ip [len]) break;
+ len++; if (ref [len] != ip [len]) break;
+ len++; if (ref [len] != ip [len]) break;
+
+ len++; if (ref [len] != ip [len]) break;
+ len++; if (ref [len] != ip [len]) break;
+ len++; if (ref [len] != ip [len]) break;
+ len++; if (ref [len] != ip [len]) break;
+
+ len++; if (ref [len] != ip [len]) break;
+ len++; if (ref [len] != ip [len]) break;
+ len++; if (ref [len] != ip [len]) break;
+ len++; if (ref [len] != ip [len]) break;
+
+ len++; if (ref [len] != ip [len]) break;
+ len++; if (ref [len] != ip [len]) break;
+ len++; if (ref [len] != ip [len]) break;
+ len++; if (ref [len] != ip [len]) break;
+ }
+
+ do
+ len++;
+ while (len < maxlen && ref[len] == ip[len]);
+
+ break;
+ }
+
+ len -= 2; /* len is now #octets - 1 */
+ ip++;
+
+ if (len < 7)
+ {
+ *op++ = (off >> 8) + (len << 5);
+ }
+ else
+ {
+ *op++ = (off >> 8) + ( 7 << 5);
+ *op++ = len - 7;
+ }
+
+ *op++ = off;
+ lit = 0; op++; /* start run */
+
+ ip += len + 1;
+
+ if (expect_false (ip >= in_end - 2))
+ break;
+
+#if ULTRA_FAST || VERY_FAST
+ --ip;
+# if VERY_FAST && !ULTRA_FAST
+ --ip;
+# endif
+ hval = FRST (ip);
+
+ hval = NEXT (hval, ip);
+ htab[IDX (hval)] = ip;
+ ip++;
+
+# if VERY_FAST && !ULTRA_FAST
+ hval = NEXT (hval, ip);
+ htab[IDX (hval)] = ip;
+ ip++;
+# endif
+#else
+ ip -= len + 1;
+
+ do
+ {
+ hval = NEXT (hval, ip);
+ htab[IDX (hval)] = ip;
+ ip++;
+ }
+ while (len--);
+#endif
+ }
+ else
+ {
+ /* one more literal byte we must copy */
+ if (expect_false (op >= out_end))
+ return 0;
+
+ lit++; *op++ = *ip++;
+
+ if (expect_false (lit == MAX_LIT))
+ {
+ op [- lit - 1] = lit - 1; /* stop run */
+ lit = 0; op++; /* start run */
+ }
+ }
+ }
+
+ if (op + 3 > out_end) /* at most 3 bytes can be missing here */
+ return 0;
+
+ while (ip < in_end)
+ {
+ lit++; *op++ = *ip++;
+
+ if (expect_false (lit == MAX_LIT))
+ {
+ op [- lit - 1] = lit - 1; /* stop run */
+ lit = 0; op++; /* start run */
+ }
+ }
+
+ op [- lit - 1] = lit - 1; /* end run */
+ op -= !lit; /* undo run if length is zero */
+
+ return op - (u8 *)out_data;
+}
+
+#if AVOID_ERRNO
+# define SET_ERRNO(n)
+#else
+# include <errno.h>
+# define SET_ERRNO(n) errno = (n)
+#endif
+
+#if (__i386 || __amd64) && __GNUC__ >= 3
+# define lzf_movsb(dst, src, len) \
+ asm ("rep movsb" \
+ : "=D" (dst), "=S" (src), "=c" (len) \
+ : "0" (dst), "1" (src), "2" (len));
+#endif
+
+unsigned int
+lzf_decompress (const void *const in_data, unsigned int in_len,
+ void *out_data, unsigned int out_len)
+{
+ u8 const *ip = (const u8 *)in_data;
+ u8 *op = (u8 *)out_data;
+ u8 const *const in_end = ip + in_len;
+ u8 *const out_end = op + out_len;
+
+ do
+ {
+ unsigned int ctrl = *ip++;
+
+ if (ctrl < (1 << 5)) /* literal run */
+ {
+ ctrl++;
+
+ if (op + ctrl > out_end)
+ {
+ SET_ERRNO (E2BIG);
+ return 0;
+ }
+
+#if CHECK_INPUT
+ if (ip + ctrl > in_end)
+ {
+ SET_ERRNO (EINVAL);
+ return 0;
+ }
+#endif
+
+#ifdef lzf_movsb
+ lzf_movsb (op, ip, ctrl);
+#else
+ do
+ *op++ = *ip++;
+ while (--ctrl);
+#endif
+ }
+ else /* back reference */
+ {
+ unsigned int len = ctrl >> 5;
+
+ u8 *ref = op - ((ctrl & 0x1f) << 8) - 1;
+
+#if CHECK_INPUT
+ if (ip >= in_end)
+ {
+ SET_ERRNO (EINVAL);
+ return 0;
+ }
+#endif
+ if (len == 7)
+ {
+ len += *ip++;
+#if CHECK_INPUT
+ if (ip >= in_end)
+ {
+ SET_ERRNO (EINVAL);
+ return 0;
+ }
+#endif
+ }
+
+ ref -= *ip++;
+
+ if (op + len + 2 > out_end)
+ {
+ SET_ERRNO (E2BIG);
+ return 0;
+ }
+
+ if (ref < (u8 *)out_data)
+ {
+ SET_ERRNO (EINVAL);
+ return 0;
+ }
+
+#ifdef lzf_movsb
+ len += 2;
+ lzf_movsb (op, ref, len);
+#else
+ *op++ = *ref++;
+ *op++ = *ref++;
+
+ do
+ *op++ = *ref++;
+ while (--len);
+#endif
+ }
+ }
+ while (ip < in_end);
+
+ return op - (u8 *)out_data;
+}
+
diff --git a/sbin/hastd/lzf.h b/sbin/hastd/lzf.h
new file mode 100644
index 0000000..d9563ef
--- /dev/null
+++ b/sbin/hastd/lzf.h
@@ -0,0 +1,211 @@
+/*
+ * Copyright (c) 2000-2008 Marc Alexander Lehmann <schmorp@schmorp.de>
+ *
+ * Redistribution and use in source and binary forms, with or without modifica-
+ * tion, 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER-
+ * CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPE-
+ * CIAL, 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 OTH-
+ * ERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License ("GPL") version 2 or any later version,
+ * in which case the provisions of the GPL are applicable instead of
+ * the above. If you wish to allow the use of your version of this file
+ * only under the terms of the GPL and not to allow others to use your
+ * version of this file under the BSD license, indicate your decision
+ * by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL. If you do not delete the
+ * provisions above, a recipient may use your version of this file under
+ * either the BSD or the GPL.
+ */
+
+#ifndef LZF_H
+#define LZF_H
+
+/***********************************************************************
+**
+** lzf -- an extremely fast/free compression/decompression-method
+** http://liblzf.plan9.de/
+**
+** This algorithm is believed to be patent-free.
+**
+***********************************************************************/
+
+#define LZF_VERSION 0x0105 /* 1.5, API version */
+
+/*
+ * Compress in_len bytes stored at the memory block starting at
+ * in_data and write the result to out_data, up to a maximum length
+ * of out_len bytes.
+ *
+ * If the output buffer is not large enough or any error occurs return 0,
+ * otherwise return the number of bytes used, which might be considerably
+ * more than in_len (but less than 104% of the original size), so it
+ * makes sense to always use out_len == in_len - 1), to ensure _some_
+ * compression, and store the data uncompressed otherwise (with a flag, of
+ * course.
+ *
+ * lzf_compress might use different algorithms on different systems and
+ * even different runs, thus might result in different compressed strings
+ * depending on the phase of the moon or similar factors. However, all
+ * these strings are architecture-independent and will result in the
+ * original data when decompressed using lzf_decompress.
+ *
+ * The buffers must not be overlapping.
+ *
+ * If the option LZF_STATE_ARG is enabled, an extra argument must be
+ * supplied which is not reflected in this header file. Refer to lzfP.h
+ * and lzf_c.c.
+ *
+ */
+unsigned int
+lzf_compress (const void *const in_data, unsigned int in_len,
+ void *out_data, unsigned int out_len);
+
+/*
+ * Decompress data compressed with some version of the lzf_compress
+ * function and stored at location in_data and length in_len. The result
+ * will be stored at out_data up to a maximum of out_len characters.
+ *
+ * If the output buffer is not large enough to hold the decompressed
+ * data, a 0 is returned and errno is set to E2BIG. Otherwise the number
+ * of decompressed bytes (i.e. the original length of the data) is
+ * returned.
+ *
+ * If an error in the compressed data is detected, a zero is returned and
+ * errno is set to EINVAL.
+ *
+ * This function is very fast, about as fast as a copying loop.
+ */
+unsigned int
+lzf_decompress (const void *const in_data, unsigned int in_len,
+ void *out_data, unsigned int out_len);
+
+/*
+ * Size of hashtable is (1 << HLOG) * sizeof (char *)
+ * decompression is independent of the hash table size
+ * the difference between 15 and 14 is very small
+ * for small blocks (and 14 is usually a bit faster).
+ * For a low-memory/faster configuration, use HLOG == 13;
+ * For best compression, use 15 or 16 (or more, up to 23).
+ */
+#ifndef HLOG
+# define HLOG 16
+#endif
+
+/*
+ * Sacrifice very little compression quality in favour of compression speed.
+ * This gives almost the same compression as the default code, and is
+ * (very roughly) 15% faster. This is the preferred mode of operation.
+ */
+#ifndef VERY_FAST
+# define VERY_FAST 1
+#endif
+
+/*
+ * Sacrifice some more compression quality in favour of compression speed.
+ * (roughly 1-2% worse compression for large blocks and
+ * 9-10% for small, redundant, blocks and >>20% better speed in both cases)
+ * In short: when in need for speed, enable this for binary data,
+ * possibly disable this for text data.
+ */
+#ifndef ULTRA_FAST
+# define ULTRA_FAST 0
+#endif
+
+/*
+ * Unconditionally aligning does not cost very much, so do it if unsure
+ */
+#ifndef STRICT_ALIGN
+# define STRICT_ALIGN !(defined(__i386) || defined (__amd64))
+#endif
+
+/*
+ * You may choose to pre-set the hash table (might be faster on some
+ * modern cpus and large (>>64k) blocks, and also makes compression
+ * deterministic/repeatable when the configuration otherwise is the same).
+ */
+#ifndef INIT_HTAB
+# define INIT_HTAB 1
+#endif
+
+/*
+ * Avoid assigning values to errno variable? for some embedding purposes
+ * (linux kernel for example), this is necessary. NOTE: this breaks
+ * the documentation in lzf.h.
+ */
+#ifndef AVOID_ERRNO
+# define AVOID_ERRNO 0
+#endif
+
+/*
+ * Wether to pass the LZF_STATE variable as argument, or allocate it
+ * on the stack. For small-stack environments, define this to 1.
+ * NOTE: this breaks the prototype in lzf.h.
+ */
+#ifndef LZF_STATE_ARG
+# define LZF_STATE_ARG 0
+#endif
+
+/*
+ * Wether to add extra checks for input validity in lzf_decompress
+ * and return EINVAL if the input stream has been corrupted. This
+ * only shields against overflowing the input buffer and will not
+ * detect most corrupted streams.
+ * This check is not normally noticeable on modern hardware
+ * (<1% slowdown), but might slow down older cpus considerably.
+ */
+#ifndef CHECK_INPUT
+# define CHECK_INPUT 1
+#endif
+
+/*****************************************************************************/
+/* nothing should be changed below */
+
+typedef unsigned char u8;
+
+typedef const u8 *LZF_STATE[1 << (HLOG)];
+
+#if !STRICT_ALIGN
+/* for unaligned accesses we need a 16 bit datatype. */
+# include <limits.h>
+# if USHRT_MAX == 65535
+ typedef unsigned short u16;
+# elif UINT_MAX == 65535
+ typedef unsigned int u16;
+# else
+# undef STRICT_ALIGN
+# define STRICT_ALIGN 1
+# endif
+#endif
+
+#if ULTRA_FAST
+# if defined(VERY_FAST)
+# undef VERY_FAST
+# endif
+#endif
+
+#if INIT_HTAB
+# ifdef __cplusplus
+# include <cstring>
+# else
+# include <string.h>
+# endif
+#endif
+
+#endif
diff --git a/sbin/hastd/metadata.c b/sbin/hastd/metadata.c
new file mode 100644
index 0000000..6d9f366
--- /dev/null
+++ b/sbin/hastd/metadata.c
@@ -0,0 +1,225 @@
+/*-
+ * Copyright (c) 2009-2010 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+
+#include <ebuf.h>
+#include <nv.h>
+#include <pjdlog.h>
+#include <subr.h>
+
+#include "metadata.h"
+
+int
+metadata_read(struct hast_resource *res, bool openrw)
+{
+ unsigned char *buf;
+ struct ebuf *eb;
+ struct nv *nv;
+ ssize_t done;
+ const char *str;
+ int rerrno;
+ bool opened_here;
+
+ opened_here = false;
+ rerrno = 0;
+
+ /*
+ * Is this first metadata_read() call for this resource?
+ */
+ if (res->hr_localfd == -1) {
+ if (provinfo(res, openrw) == -1) {
+ rerrno = errno;
+ goto fail;
+ }
+ opened_here = true;
+ pjdlog_debug(1, "Obtained info about %s.", res->hr_localpath);
+ if (openrw) {
+ if (flock(res->hr_localfd, LOCK_EX | LOCK_NB) == -1) {
+ rerrno = errno;
+ if (errno == EOPNOTSUPP) {
+ pjdlog_warning("Unable to lock %s (operation not supported), but continuing.",
+ res->hr_localpath);
+ } else {
+ pjdlog_errno(LOG_ERR,
+ "Unable to lock %s",
+ res->hr_localpath);
+ goto fail;
+ }
+ }
+ pjdlog_debug(1, "Locked %s.", res->hr_localpath);
+ }
+ }
+
+ eb = ebuf_alloc(METADATA_SIZE);
+ if (eb == NULL) {
+ rerrno = errno;
+ pjdlog_errno(LOG_ERR,
+ "Unable to allocate memory to read metadata");
+ goto fail;
+ }
+ if (ebuf_add_tail(eb, NULL, METADATA_SIZE) == -1) {
+ rerrno = errno;
+ pjdlog_errno(LOG_ERR,
+ "Unable to allocate memory to read metadata");
+ ebuf_free(eb);
+ goto fail;
+ }
+ buf = ebuf_data(eb, NULL);
+ PJDLOG_ASSERT(buf != NULL);
+ done = pread(res->hr_localfd, buf, METADATA_SIZE, 0);
+ if (done == -1 || done != METADATA_SIZE) {
+ rerrno = errno;
+ pjdlog_errno(LOG_ERR, "Unable to read metadata");
+ ebuf_free(eb);
+ goto fail;
+ }
+ nv = nv_ntoh(eb);
+ if (nv == NULL) {
+ rerrno = errno;
+ pjdlog_errno(LOG_ERR, "Metadata read from %s is invalid",
+ res->hr_localpath);
+ ebuf_free(eb);
+ goto fail;
+ }
+
+ str = nv_get_string(nv, "resource");
+ if (str != NULL && strcmp(str, res->hr_name) != 0) {
+ pjdlog_error("Provider %s is not part of resource %s.",
+ res->hr_localpath, res->hr_name);
+ nv_free(nv);
+ goto fail;
+ }
+
+ res->hr_datasize = nv_get_uint64(nv, "datasize");
+ res->hr_extentsize = (int)nv_get_uint32(nv, "extentsize");
+ res->hr_keepdirty = (int)nv_get_uint32(nv, "keepdirty");
+ res->hr_localoff = nv_get_uint64(nv, "offset");
+ res->hr_resuid = nv_get_uint64(nv, "resuid");
+ if (res->hr_role != HAST_ROLE_PRIMARY) {
+ /* Secondary or init role. */
+ res->hr_secondary_localcnt = nv_get_uint64(nv, "localcnt");
+ res->hr_secondary_remotecnt = nv_get_uint64(nv, "remotecnt");
+ }
+ if (res->hr_role != HAST_ROLE_SECONDARY) {
+ /* Primary or init role. */
+ res->hr_primary_localcnt = nv_get_uint64(nv, "localcnt");
+ res->hr_primary_remotecnt = nv_get_uint64(nv, "remotecnt");
+ }
+ str = nv_get_string(nv, "prevrole");
+ if (str != NULL) {
+ if (strcmp(str, "primary") == 0)
+ res->hr_previous_role = HAST_ROLE_PRIMARY;
+ else if (strcmp(str, "secondary") == 0)
+ res->hr_previous_role = HAST_ROLE_SECONDARY;
+ }
+
+ if (nv_error(nv) != 0) {
+ errno = rerrno = nv_error(nv);
+ pjdlog_errno(LOG_ERR, "Unable to read metadata from %s",
+ res->hr_localpath);
+ nv_free(nv);
+ goto fail;
+ }
+ nv_free(nv);
+ return (0);
+fail:
+ if (opened_here) {
+ close(res->hr_localfd);
+ res->hr_localfd = -1;
+ }
+ errno = rerrno;
+ return (-1);
+}
+
+int
+metadata_write(struct hast_resource *res)
+{
+ struct ebuf *eb;
+ struct nv *nv;
+ unsigned char *buf, *ptr;
+ size_t size;
+ ssize_t done;
+ int ret;
+
+ buf = calloc(1, METADATA_SIZE);
+ if (buf == NULL) {
+ pjdlog_error("Unable to allocate %zu bytes for metadata.",
+ (size_t)METADATA_SIZE);
+ return (-1);
+ }
+
+ ret = -1;
+
+ nv = nv_alloc();
+ nv_add_string(nv, res->hr_name, "resource");
+ nv_add_uint64(nv, (uint64_t)res->hr_datasize, "datasize");
+ nv_add_uint32(nv, (uint32_t)res->hr_extentsize, "extentsize");
+ nv_add_uint32(nv, (uint32_t)res->hr_keepdirty, "keepdirty");
+ nv_add_uint64(nv, (uint64_t)res->hr_localoff, "offset");
+ nv_add_uint64(nv, res->hr_resuid, "resuid");
+ if (res->hr_role == HAST_ROLE_PRIMARY ||
+ res->hr_role == HAST_ROLE_INIT) {
+ nv_add_uint64(nv, res->hr_primary_localcnt, "localcnt");
+ nv_add_uint64(nv, res->hr_primary_remotecnt, "remotecnt");
+ } else /* if (res->hr_role == HAST_ROLE_SECONDARY) */ {
+ PJDLOG_ASSERT(res->hr_role == HAST_ROLE_SECONDARY);
+ nv_add_uint64(nv, res->hr_secondary_localcnt, "localcnt");
+ nv_add_uint64(nv, res->hr_secondary_remotecnt, "remotecnt");
+ }
+ nv_add_string(nv, role2str(res->hr_role), "prevrole");
+ if (nv_error(nv) != 0) {
+ pjdlog_error("Unable to create metadata.");
+ goto end;
+ }
+ res->hr_previous_role = res->hr_role;
+ eb = nv_hton(nv);
+ PJDLOG_ASSERT(eb != NULL);
+ ptr = ebuf_data(eb, &size);
+ PJDLOG_ASSERT(ptr != NULL);
+ PJDLOG_ASSERT(size < METADATA_SIZE);
+ bcopy(ptr, buf, size);
+ done = pwrite(res->hr_localfd, buf, METADATA_SIZE, 0);
+ if (done == -1 || done != METADATA_SIZE) {
+ pjdlog_errno(LOG_ERR, "Unable to write metadata");
+ goto end;
+ }
+ ret = 0;
+end:
+ free(buf);
+ nv_free(nv);
+ return (ret);
+}
diff --git a/sbin/hastd/metadata.h b/sbin/hastd/metadata.h
new file mode 100644
index 0000000..83d35f4
--- /dev/null
+++ b/sbin/hastd/metadata.h
@@ -0,0 +1,48 @@
+/*-
+ * Copyright (c) 2010 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _METADATA_H_
+#define _METADATA_H_
+
+#include <stdbool.h>
+
+#include <hast.h>
+
+/*
+ * Maximum size of metadata.
+ * XXX: We should take sector size into account.
+ */
+#define METADATA_SIZE 4096
+
+int metadata_read(struct hast_resource *res, bool openrw);
+int metadata_write(struct hast_resource *res);
+
+#endif /* !_METADATA_H_ */
diff --git a/sbin/hastd/nv.c b/sbin/hastd/nv.c
new file mode 100644
index 0000000..fefc2df
--- /dev/null
+++ b/sbin/hastd/nv.c
@@ -0,0 +1,966 @@
+/*-
+ * Copyright (c) 2009-2010 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/endian.h>
+
+#include <bitstring.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <ebuf.h>
+#include <pjdlog.h>
+
+#include "nv.h"
+
+#ifndef PJDLOG_ASSERT
+#include <assert.h>
+#define PJDLOG_ASSERT(...) assert(__VA_ARGS__)
+#endif
+#ifndef PJDLOG_ABORT
+#define PJDLOG_ABORT(...) abort()
+#endif
+
+#define NV_TYPE_NONE 0
+
+#define NV_TYPE_INT8 1
+#define NV_TYPE_UINT8 2
+#define NV_TYPE_INT16 3
+#define NV_TYPE_UINT16 4
+#define NV_TYPE_INT32 5
+#define NV_TYPE_UINT32 6
+#define NV_TYPE_INT64 7
+#define NV_TYPE_UINT64 8
+#define NV_TYPE_INT8_ARRAY 9
+#define NV_TYPE_UINT8_ARRAY 10
+#define NV_TYPE_INT16_ARRAY 11
+#define NV_TYPE_UINT16_ARRAY 12
+#define NV_TYPE_INT32_ARRAY 13
+#define NV_TYPE_UINT32_ARRAY 14
+#define NV_TYPE_INT64_ARRAY 15
+#define NV_TYPE_UINT64_ARRAY 16
+#define NV_TYPE_STRING 17
+
+#define NV_TYPE_MASK 0x7f
+#define NV_TYPE_FIRST NV_TYPE_INT8
+#define NV_TYPE_LAST NV_TYPE_STRING
+
+#define NV_ORDER_NETWORK 0x00
+#define NV_ORDER_HOST 0x80
+
+#define NV_ORDER_MASK 0x80
+
+#define NV_MAGIC 0xaea1e
+struct nv {
+ int nv_magic;
+ int nv_error;
+ struct ebuf *nv_ebuf;
+};
+
+struct nvhdr {
+ uint8_t nvh_type;
+ uint8_t nvh_namesize;
+ uint32_t nvh_dsize;
+ char nvh_name[0];
+} __packed;
+#define NVH_DATA(nvh) ((unsigned char *)nvh + NVH_HSIZE(nvh))
+#define NVH_HSIZE(nvh) \
+ (sizeof(struct nvhdr) + roundup2((nvh)->nvh_namesize, 8))
+#define NVH_DSIZE(nvh) \
+ (((nvh)->nvh_type & NV_ORDER_MASK) == NV_ORDER_HOST ? \
+ (nvh)->nvh_dsize : \
+ le32toh((nvh)->nvh_dsize))
+#define NVH_SIZE(nvh) (NVH_HSIZE(nvh) + roundup2(NVH_DSIZE(nvh), 8))
+
+#define NV_CHECK(nv) do { \
+ PJDLOG_ASSERT((nv) != NULL); \
+ PJDLOG_ASSERT((nv)->nv_magic == NV_MAGIC); \
+} while (0)
+
+static void nv_add(struct nv *nv, const unsigned char *value, size_t vsize,
+ int type, const char *name);
+static void nv_addv(struct nv *nv, const unsigned char *value, size_t vsize,
+ int type, const char *namefmt, va_list nameap);
+static struct nvhdr *nv_find(struct nv *nv, int type, const char *namefmt,
+ va_list nameap);
+static void nv_swap(struct nvhdr *nvh, bool tohost);
+
+/*
+ * Allocate and initialize new nv structure.
+ * Return NULL in case of malloc(3) failure.
+ */
+struct nv *
+nv_alloc(void)
+{
+ struct nv *nv;
+
+ nv = malloc(sizeof(*nv));
+ if (nv == NULL)
+ return (NULL);
+ nv->nv_ebuf = ebuf_alloc(0);
+ if (nv->nv_ebuf == NULL) {
+ free(nv);
+ return (NULL);
+ }
+ nv->nv_error = 0;
+ nv->nv_magic = NV_MAGIC;
+ return (nv);
+}
+
+/*
+ * Free the given nv structure.
+ */
+void
+nv_free(struct nv *nv)
+{
+
+ if (nv == NULL)
+ return;
+
+ NV_CHECK(nv);
+
+ nv->nv_magic = 0;
+ ebuf_free(nv->nv_ebuf);
+ free(nv);
+}
+
+/*
+ * Return error for the given nv structure.
+ */
+int
+nv_error(const struct nv *nv)
+{
+
+ if (nv == NULL)
+ return (ENOMEM);
+
+ NV_CHECK(nv);
+
+ return (nv->nv_error);
+}
+
+/*
+ * Set error for the given nv structure and return previous error.
+ */
+int
+nv_set_error(struct nv *nv, int error)
+{
+ int preverr;
+
+ if (nv == NULL)
+ return (ENOMEM);
+
+ NV_CHECK(nv);
+
+ preverr = nv->nv_error;
+ nv->nv_error = error;
+ return (preverr);
+}
+
+/*
+ * Validate correctness of the entire nv structure and all its elements.
+ * If extrap is not NULL, store number of extra bytes at the end of the buffer.
+ */
+int
+nv_validate(struct nv *nv, size_t *extrap)
+{
+ struct nvhdr *nvh;
+ unsigned char *data, *ptr;
+ size_t dsize, size, vsize;
+ int error;
+
+ if (nv == NULL) {
+ errno = ENOMEM;
+ return (-1);
+ }
+
+ NV_CHECK(nv);
+ PJDLOG_ASSERT(nv->nv_error == 0);
+
+ /* TODO: Check that names are unique? */
+
+ error = 0;
+ ptr = ebuf_data(nv->nv_ebuf, &size);
+ while (size > 0) {
+ /*
+ * Zeros at the end of the buffer are acceptable.
+ */
+ if (ptr[0] == '\0')
+ break;
+ /*
+ * Minimum size at this point is size of nvhdr structure, one
+ * character long name plus terminating '\0'.
+ */
+ if (size < sizeof(*nvh) + 2) {
+ error = EINVAL;
+ break;
+ }
+ nvh = (struct nvhdr *)ptr;
+ if (size < NVH_HSIZE(nvh)) {
+ error = EINVAL;
+ break;
+ }
+ if (nvh->nvh_name[nvh->nvh_namesize - 1] != '\0') {
+ error = EINVAL;
+ break;
+ }
+ if (strlen(nvh->nvh_name) !=
+ (size_t)(nvh->nvh_namesize - 1)) {
+ error = EINVAL;
+ break;
+ }
+ if ((nvh->nvh_type & NV_TYPE_MASK) < NV_TYPE_FIRST ||
+ (nvh->nvh_type & NV_TYPE_MASK) > NV_TYPE_LAST) {
+ error = EINVAL;
+ break;
+ }
+ dsize = NVH_DSIZE(nvh);
+ if (dsize == 0) {
+ error = EINVAL;
+ break;
+ }
+ if (size < NVH_SIZE(nvh)) {
+ error = EINVAL;
+ break;
+ }
+ vsize = 0;
+ switch (nvh->nvh_type & NV_TYPE_MASK) {
+ case NV_TYPE_INT8:
+ case NV_TYPE_UINT8:
+ if (vsize == 0)
+ vsize = 1;
+ /* FALLTHROUGH */
+ case NV_TYPE_INT16:
+ case NV_TYPE_UINT16:
+ if (vsize == 0)
+ vsize = 2;
+ /* FALLTHROUGH */
+ case NV_TYPE_INT32:
+ case NV_TYPE_UINT32:
+ if (vsize == 0)
+ vsize = 4;
+ /* FALLTHROUGH */
+ case NV_TYPE_INT64:
+ case NV_TYPE_UINT64:
+ if (vsize == 0)
+ vsize = 8;
+ if (dsize != vsize) {
+ error = EINVAL;
+ break;
+ }
+ break;
+ case NV_TYPE_INT8_ARRAY:
+ case NV_TYPE_UINT8_ARRAY:
+ break;
+ case NV_TYPE_INT16_ARRAY:
+ case NV_TYPE_UINT16_ARRAY:
+ if (vsize == 0)
+ vsize = 2;
+ /* FALLTHROUGH */
+ case NV_TYPE_INT32_ARRAY:
+ case NV_TYPE_UINT32_ARRAY:
+ if (vsize == 0)
+ vsize = 4;
+ /* FALLTHROUGH */
+ case NV_TYPE_INT64_ARRAY:
+ case NV_TYPE_UINT64_ARRAY:
+ if (vsize == 0)
+ vsize = 8;
+ if ((dsize % vsize) != 0) {
+ error = EINVAL;
+ break;
+ }
+ break;
+ case NV_TYPE_STRING:
+ data = NVH_DATA(nvh);
+ if (data[dsize - 1] != '\0') {
+ error = EINVAL;
+ break;
+ }
+ if (strlen((char *)data) != dsize - 1) {
+ error = EINVAL;
+ break;
+ }
+ break;
+ default:
+ PJDLOG_ABORT("invalid condition");
+ }
+ if (error != 0)
+ break;
+ ptr += NVH_SIZE(nvh);
+ size -= NVH_SIZE(nvh);
+ }
+ if (error != 0) {
+ errno = error;
+ if (nv->nv_error == 0)
+ nv->nv_error = error;
+ return (-1);
+ }
+ if (extrap != NULL)
+ *extrap = size;
+ return (0);
+}
+
+/*
+ * Convert the given nv structure to network byte order and return ebuf
+ * structure.
+ */
+struct ebuf *
+nv_hton(struct nv *nv)
+{
+ struct nvhdr *nvh;
+ unsigned char *ptr;
+ size_t size;
+
+ NV_CHECK(nv);
+ PJDLOG_ASSERT(nv->nv_error == 0);
+
+ ptr = ebuf_data(nv->nv_ebuf, &size);
+ while (size > 0) {
+ /*
+ * Minimum size at this point is size of nvhdr structure,
+ * one character long name plus terminating '\0'.
+ */
+ PJDLOG_ASSERT(size >= sizeof(*nvh) + 2);
+ nvh = (struct nvhdr *)ptr;
+ PJDLOG_ASSERT(NVH_SIZE(nvh) <= size);
+ nv_swap(nvh, false);
+ ptr += NVH_SIZE(nvh);
+ size -= NVH_SIZE(nvh);
+ }
+
+ return (nv->nv_ebuf);
+}
+
+/*
+ * Create nv structure based on ebuf received from the network.
+ */
+struct nv *
+nv_ntoh(struct ebuf *eb)
+{
+ struct nv *nv;
+ size_t extra;
+ int rerrno;
+
+ PJDLOG_ASSERT(eb != NULL);
+
+ nv = malloc(sizeof(*nv));
+ if (nv == NULL)
+ return (NULL);
+ nv->nv_error = 0;
+ nv->nv_ebuf = eb;
+ nv->nv_magic = NV_MAGIC;
+
+ if (nv_validate(nv, &extra) == -1) {
+ rerrno = errno;
+ nv->nv_magic = 0;
+ free(nv);
+ errno = rerrno;
+ return (NULL);
+ }
+ /*
+ * Remove extra zeros at the end of the buffer.
+ */
+ ebuf_del_tail(eb, extra);
+
+ return (nv);
+}
+
+#define NV_DEFINE_ADD(type, TYPE) \
+void \
+nv_add_##type(struct nv *nv, type##_t value, const char *namefmt, ...) \
+{ \
+ va_list nameap; \
+ \
+ va_start(nameap, namefmt); \
+ nv_addv(nv, (unsigned char *)&value, sizeof(value), \
+ NV_TYPE_##TYPE, namefmt, nameap); \
+ va_end(nameap); \
+}
+
+NV_DEFINE_ADD(int8, INT8)
+NV_DEFINE_ADD(uint8, UINT8)
+NV_DEFINE_ADD(int16, INT16)
+NV_DEFINE_ADD(uint16, UINT16)
+NV_DEFINE_ADD(int32, INT32)
+NV_DEFINE_ADD(uint32, UINT32)
+NV_DEFINE_ADD(int64, INT64)
+NV_DEFINE_ADD(uint64, UINT64)
+
+#undef NV_DEFINE_ADD
+
+#define NV_DEFINE_ADD_ARRAY(type, TYPE) \
+void \
+nv_add_##type##_array(struct nv *nv, const type##_t *value, \
+ size_t nsize, const char *namefmt, ...) \
+{ \
+ va_list nameap; \
+ \
+ va_start(nameap, namefmt); \
+ nv_addv(nv, (const unsigned char *)value, \
+ sizeof(value[0]) * nsize, NV_TYPE_##TYPE##_ARRAY, namefmt, \
+ nameap); \
+ va_end(nameap); \
+}
+
+NV_DEFINE_ADD_ARRAY(int8, INT8)
+NV_DEFINE_ADD_ARRAY(uint8, UINT8)
+NV_DEFINE_ADD_ARRAY(int16, INT16)
+NV_DEFINE_ADD_ARRAY(uint16, UINT16)
+NV_DEFINE_ADD_ARRAY(int32, INT32)
+NV_DEFINE_ADD_ARRAY(uint32, UINT32)
+NV_DEFINE_ADD_ARRAY(int64, INT64)
+NV_DEFINE_ADD_ARRAY(uint64, UINT64)
+
+#undef NV_DEFINE_ADD_ARRAY
+
+void
+nv_add_string(struct nv *nv, const char *value, const char *namefmt, ...)
+{
+ va_list nameap;
+ size_t size;
+
+ size = strlen(value) + 1;
+
+ va_start(nameap, namefmt);
+ nv_addv(nv, (const unsigned char *)value, size, NV_TYPE_STRING,
+ namefmt, nameap);
+ va_end(nameap);
+}
+
+void
+nv_add_stringf(struct nv *nv, const char *name, const char *valuefmt, ...)
+{
+ va_list valueap;
+
+ va_start(valueap, valuefmt);
+ nv_add_stringv(nv, name, valuefmt, valueap);
+ va_end(valueap);
+}
+
+void
+nv_add_stringv(struct nv *nv, const char *name, const char *valuefmt,
+ va_list valueap)
+{
+ char *value;
+ ssize_t size;
+
+ size = vasprintf(&value, valuefmt, valueap);
+ if (size == -1) {
+ if (nv->nv_error == 0)
+ nv->nv_error = ENOMEM;
+ return;
+ }
+ size++;
+ nv_add(nv, (const unsigned char *)value, size, NV_TYPE_STRING, name);
+ free(value);
+}
+
+#define NV_DEFINE_GET(type, TYPE) \
+type##_t \
+nv_get_##type(struct nv *nv, const char *namefmt, ...) \
+{ \
+ struct nvhdr *nvh; \
+ va_list nameap; \
+ type##_t value; \
+ \
+ va_start(nameap, namefmt); \
+ nvh = nv_find(nv, NV_TYPE_##TYPE, namefmt, nameap); \
+ va_end(nameap); \
+ if (nvh == NULL) \
+ return (0); \
+ PJDLOG_ASSERT((nvh->nvh_type & NV_ORDER_MASK) == NV_ORDER_HOST);\
+ PJDLOG_ASSERT(sizeof(value) == nvh->nvh_dsize); \
+ bcopy(NVH_DATA(nvh), &value, sizeof(value)); \
+ \
+ return (value); \
+}
+
+NV_DEFINE_GET(int8, INT8)
+NV_DEFINE_GET(uint8, UINT8)
+NV_DEFINE_GET(int16, INT16)
+NV_DEFINE_GET(uint16, UINT16)
+NV_DEFINE_GET(int32, INT32)
+NV_DEFINE_GET(uint32, UINT32)
+NV_DEFINE_GET(int64, INT64)
+NV_DEFINE_GET(uint64, UINT64)
+
+#undef NV_DEFINE_GET
+
+#define NV_DEFINE_GET_ARRAY(type, TYPE) \
+const type##_t * \
+nv_get_##type##_array(struct nv *nv, size_t *sizep, \
+ const char *namefmt, ...) \
+{ \
+ struct nvhdr *nvh; \
+ va_list nameap; \
+ \
+ va_start(nameap, namefmt); \
+ nvh = nv_find(nv, NV_TYPE_##TYPE##_ARRAY, namefmt, nameap); \
+ va_end(nameap); \
+ if (nvh == NULL) \
+ return (NULL); \
+ PJDLOG_ASSERT((nvh->nvh_type & NV_ORDER_MASK) == NV_ORDER_HOST);\
+ PJDLOG_ASSERT((nvh->nvh_dsize % sizeof(type##_t)) == 0); \
+ if (sizep != NULL) \
+ *sizep = nvh->nvh_dsize / sizeof(type##_t); \
+ return ((type##_t *)(void *)NVH_DATA(nvh)); \
+}
+
+NV_DEFINE_GET_ARRAY(int8, INT8)
+NV_DEFINE_GET_ARRAY(uint8, UINT8)
+NV_DEFINE_GET_ARRAY(int16, INT16)
+NV_DEFINE_GET_ARRAY(uint16, UINT16)
+NV_DEFINE_GET_ARRAY(int32, INT32)
+NV_DEFINE_GET_ARRAY(uint32, UINT32)
+NV_DEFINE_GET_ARRAY(int64, INT64)
+NV_DEFINE_GET_ARRAY(uint64, UINT64)
+
+#undef NV_DEFINE_GET_ARRAY
+
+const char *
+nv_get_string(struct nv *nv, const char *namefmt, ...)
+{
+ struct nvhdr *nvh;
+ va_list nameap;
+ char *str;
+
+ va_start(nameap, namefmt);
+ nvh = nv_find(nv, NV_TYPE_STRING, namefmt, nameap);
+ va_end(nameap);
+ if (nvh == NULL)
+ return (NULL);
+ PJDLOG_ASSERT((nvh->nvh_type & NV_ORDER_MASK) == NV_ORDER_HOST);
+ PJDLOG_ASSERT(nvh->nvh_dsize >= 1);
+ str = (char *)NVH_DATA(nvh);
+ PJDLOG_ASSERT(str[nvh->nvh_dsize - 1] == '\0');
+ PJDLOG_ASSERT(strlen(str) == nvh->nvh_dsize - 1);
+ return (str);
+}
+
+static bool
+nv_vexists(struct nv *nv, const char *namefmt, va_list nameap)
+{
+ struct nvhdr *nvh;
+ int snverror, serrno;
+
+ if (nv == NULL)
+ return (false);
+
+ serrno = errno;
+ snverror = nv->nv_error;
+
+ nvh = nv_find(nv, NV_TYPE_NONE, namefmt, nameap);
+
+ errno = serrno;
+ nv->nv_error = snverror;
+
+ return (nvh != NULL);
+}
+
+bool
+nv_exists(struct nv *nv, const char *namefmt, ...)
+{
+ va_list nameap;
+ bool ret;
+
+ va_start(nameap, namefmt);
+ ret = nv_vexists(nv, namefmt, nameap);
+ va_end(nameap);
+
+ return (ret);
+}
+
+void
+nv_assert(struct nv *nv, const char *namefmt, ...)
+{
+ va_list nameap;
+
+ va_start(nameap, namefmt);
+ PJDLOG_ASSERT(nv_vexists(nv, namefmt, nameap));
+ va_end(nameap);
+}
+
+/*
+ * Dump content of the nv structure.
+ */
+void
+nv_dump(struct nv *nv)
+{
+ struct nvhdr *nvh;
+ unsigned char *data, *ptr;
+ size_t dsize, size;
+ unsigned int ii;
+ bool swap;
+
+ if (nv_validate(nv, NULL) == -1) {
+ printf("error: %d\n", errno);
+ return;
+ }
+
+ NV_CHECK(nv);
+ PJDLOG_ASSERT(nv->nv_error == 0);
+
+ ptr = ebuf_data(nv->nv_ebuf, &size);
+ while (size > 0) {
+ PJDLOG_ASSERT(size >= sizeof(*nvh) + 2);
+ nvh = (struct nvhdr *)ptr;
+ PJDLOG_ASSERT(size >= NVH_SIZE(nvh));
+ swap = ((nvh->nvh_type & NV_ORDER_MASK) == NV_ORDER_NETWORK);
+ dsize = NVH_DSIZE(nvh);
+ data = NVH_DATA(nvh);
+ printf(" %s", nvh->nvh_name);
+ switch (nvh->nvh_type & NV_TYPE_MASK) {
+ case NV_TYPE_INT8:
+ printf("(int8): %jd", (intmax_t)(*(int8_t *)data));
+ break;
+ case NV_TYPE_UINT8:
+ printf("(uint8): %ju", (uintmax_t)(*(uint8_t *)data));
+ break;
+ case NV_TYPE_INT16:
+ printf("(int16): %jd", swap ?
+ (intmax_t)le16toh(*(int16_t *)(void *)data) :
+ (intmax_t)*(int16_t *)(void *)data);
+ break;
+ case NV_TYPE_UINT16:
+ printf("(uint16): %ju", swap ?
+ (uintmax_t)le16toh(*(uint16_t *)(void *)data) :
+ (uintmax_t)*(uint16_t *)(void *)data);
+ break;
+ case NV_TYPE_INT32:
+ printf("(int32): %jd", swap ?
+ (intmax_t)le32toh(*(int32_t *)(void *)data) :
+ (intmax_t)*(int32_t *)(void *)data);
+ break;
+ case NV_TYPE_UINT32:
+ printf("(uint32): %ju", swap ?
+ (uintmax_t)le32toh(*(uint32_t *)(void *)data) :
+ (uintmax_t)*(uint32_t *)(void *)data);
+ break;
+ case NV_TYPE_INT64:
+ printf("(int64): %jd", swap ?
+ (intmax_t)le64toh(*(int64_t *)(void *)data) :
+ (intmax_t)*(int64_t *)(void *)data);
+ break;
+ case NV_TYPE_UINT64:
+ printf("(uint64): %ju", swap ?
+ (uintmax_t)le64toh(*(uint64_t *)(void *)data) :
+ (uintmax_t)*(uint64_t *)(void *)data);
+ break;
+ case NV_TYPE_INT8_ARRAY:
+ printf("(int8 array):");
+ for (ii = 0; ii < dsize; ii++)
+ printf(" %jd", (intmax_t)((int8_t *)data)[ii]);
+ break;
+ case NV_TYPE_UINT8_ARRAY:
+ printf("(uint8 array):");
+ for (ii = 0; ii < dsize; ii++)
+ printf(" %ju", (uintmax_t)((uint8_t *)data)[ii]);
+ break;
+ case NV_TYPE_INT16_ARRAY:
+ printf("(int16 array):");
+ for (ii = 0; ii < dsize / 2; ii++) {
+ printf(" %jd", swap ?
+ (intmax_t)le16toh(((int16_t *)(void *)data)[ii]) :
+ (intmax_t)((int16_t *)(void *)data)[ii]);
+ }
+ break;
+ case NV_TYPE_UINT16_ARRAY:
+ printf("(uint16 array):");
+ for (ii = 0; ii < dsize / 2; ii++) {
+ printf(" %ju", swap ?
+ (uintmax_t)le16toh(((uint16_t *)(void *)data)[ii]) :
+ (uintmax_t)((uint16_t *)(void *)data)[ii]);
+ }
+ break;
+ case NV_TYPE_INT32_ARRAY:
+ printf("(int32 array):");
+ for (ii = 0; ii < dsize / 4; ii++) {
+ printf(" %jd", swap ?
+ (intmax_t)le32toh(((int32_t *)(void *)data)[ii]) :
+ (intmax_t)((int32_t *)(void *)data)[ii]);
+ }
+ break;
+ case NV_TYPE_UINT32_ARRAY:
+ printf("(uint32 array):");
+ for (ii = 0; ii < dsize / 4; ii++) {
+ printf(" %ju", swap ?
+ (uintmax_t)le32toh(((uint32_t *)(void *)data)[ii]) :
+ (uintmax_t)((uint32_t *)(void *)data)[ii]);
+ }
+ break;
+ case NV_TYPE_INT64_ARRAY:
+ printf("(int64 array):");
+ for (ii = 0; ii < dsize / 8; ii++) {
+ printf(" %ju", swap ?
+ (uintmax_t)le64toh(((uint64_t *)(void *)data)[ii]) :
+ (uintmax_t)((uint64_t *)(void *)data)[ii]);
+ }
+ break;
+ case NV_TYPE_UINT64_ARRAY:
+ printf("(uint64 array):");
+ for (ii = 0; ii < dsize / 8; ii++) {
+ printf(" %ju", swap ?
+ (uintmax_t)le64toh(((uint64_t *)(void *)data)[ii]) :
+ (uintmax_t)((uint64_t *)(void *)data)[ii]);
+ }
+ break;
+ case NV_TYPE_STRING:
+ printf("(string): %s", (char *)data);
+ break;
+ default:
+ PJDLOG_ABORT("invalid condition");
+ }
+ printf("\n");
+ ptr += NVH_SIZE(nvh);
+ size -= NVH_SIZE(nvh);
+ }
+}
+
+/*
+ * Local routines below.
+ */
+
+static void
+nv_add(struct nv *nv, const unsigned char *value, size_t vsize, int type,
+ const char *name)
+{
+ static unsigned char align[7];
+ struct nvhdr *nvh;
+ size_t namesize;
+
+ if (nv == NULL) {
+ errno = ENOMEM;
+ return;
+ }
+
+ NV_CHECK(nv);
+
+ namesize = strlen(name) + 1;
+
+ nvh = malloc(sizeof(*nvh) + roundup2(namesize, 8));
+ if (nvh == NULL) {
+ if (nv->nv_error == 0)
+ nv->nv_error = ENOMEM;
+ return;
+ }
+ nvh->nvh_type = NV_ORDER_HOST | type;
+ nvh->nvh_namesize = (uint8_t)namesize;
+ nvh->nvh_dsize = (uint32_t)vsize;
+ bcopy(name, nvh->nvh_name, namesize);
+
+ /* Add header first. */
+ if (ebuf_add_tail(nv->nv_ebuf, nvh, NVH_HSIZE(nvh)) == -1) {
+ PJDLOG_ASSERT(errno != 0);
+ if (nv->nv_error == 0)
+ nv->nv_error = errno;
+ free(nvh);
+ return;
+ }
+ free(nvh);
+ /* Add the actual data. */
+ if (ebuf_add_tail(nv->nv_ebuf, value, vsize) == -1) {
+ PJDLOG_ASSERT(errno != 0);
+ if (nv->nv_error == 0)
+ nv->nv_error = errno;
+ return;
+ }
+ /* Align the data (if needed). */
+ vsize = roundup2(vsize, 8) - vsize;
+ if (vsize == 0)
+ return;
+ PJDLOG_ASSERT(vsize > 0 && vsize <= sizeof(align));
+ if (ebuf_add_tail(nv->nv_ebuf, align, vsize) == -1) {
+ PJDLOG_ASSERT(errno != 0);
+ if (nv->nv_error == 0)
+ nv->nv_error = errno;
+ return;
+ }
+}
+
+static void
+nv_addv(struct nv *nv, const unsigned char *value, size_t vsize, int type,
+ const char *namefmt, va_list nameap)
+{
+ char name[255];
+ size_t namesize;
+
+ namesize = vsnprintf(name, sizeof(name), namefmt, nameap);
+ PJDLOG_ASSERT(namesize > 0 && namesize < sizeof(name));
+
+ nv_add(nv, value, vsize, type, name);
+}
+
+static struct nvhdr *
+nv_find(struct nv *nv, int type, const char *namefmt, va_list nameap)
+{
+ char name[255];
+ struct nvhdr *nvh;
+ unsigned char *ptr;
+ size_t size, namesize;
+
+ if (nv == NULL) {
+ errno = ENOMEM;
+ return (NULL);
+ }
+
+ NV_CHECK(nv);
+
+ namesize = vsnprintf(name, sizeof(name), namefmt, nameap);
+ PJDLOG_ASSERT(namesize > 0 && namesize < sizeof(name));
+ namesize++;
+
+ ptr = ebuf_data(nv->nv_ebuf, &size);
+ while (size > 0) {
+ PJDLOG_ASSERT(size >= sizeof(*nvh) + 2);
+ nvh = (struct nvhdr *)ptr;
+ PJDLOG_ASSERT(size >= NVH_SIZE(nvh));
+ nv_swap(nvh, true);
+ if (strcmp(nvh->nvh_name, name) == 0) {
+ if (type != NV_TYPE_NONE &&
+ (nvh->nvh_type & NV_TYPE_MASK) != type) {
+ errno = EINVAL;
+ if (nv->nv_error == 0)
+ nv->nv_error = EINVAL;
+ return (NULL);
+ }
+ return (nvh);
+ }
+ ptr += NVH_SIZE(nvh);
+ size -= NVH_SIZE(nvh);
+ }
+ errno = ENOENT;
+ if (nv->nv_error == 0)
+ nv->nv_error = ENOENT;
+ return (NULL);
+}
+
+static void
+nv_swap(struct nvhdr *nvh, bool tohost)
+{
+ unsigned char *data, *end, *p;
+ size_t vsize;
+
+ data = NVH_DATA(nvh);
+ if (tohost) {
+ if ((nvh->nvh_type & NV_ORDER_MASK) == NV_ORDER_HOST)
+ return;
+ nvh->nvh_dsize = le32toh(nvh->nvh_dsize);
+ end = data + nvh->nvh_dsize;
+ nvh->nvh_type &= ~NV_ORDER_MASK;
+ nvh->nvh_type |= NV_ORDER_HOST;
+ } else {
+ if ((nvh->nvh_type & NV_ORDER_MASK) == NV_ORDER_NETWORK)
+ return;
+ end = data + nvh->nvh_dsize;
+ nvh->nvh_dsize = htole32(nvh->nvh_dsize);
+ nvh->nvh_type &= ~NV_ORDER_MASK;
+ nvh->nvh_type |= NV_ORDER_NETWORK;
+ }
+
+ vsize = 0;
+
+ switch (nvh->nvh_type & NV_TYPE_MASK) {
+ case NV_TYPE_INT8:
+ case NV_TYPE_UINT8:
+ case NV_TYPE_INT8_ARRAY:
+ case NV_TYPE_UINT8_ARRAY:
+ break;
+ case NV_TYPE_INT16:
+ case NV_TYPE_UINT16:
+ case NV_TYPE_INT16_ARRAY:
+ case NV_TYPE_UINT16_ARRAY:
+ if (vsize == 0)
+ vsize = 2;
+ /* FALLTHROUGH */
+ case NV_TYPE_INT32:
+ case NV_TYPE_UINT32:
+ case NV_TYPE_INT32_ARRAY:
+ case NV_TYPE_UINT32_ARRAY:
+ if (vsize == 0)
+ vsize = 4;
+ /* FALLTHROUGH */
+ case NV_TYPE_INT64:
+ case NV_TYPE_UINT64:
+ case NV_TYPE_INT64_ARRAY:
+ case NV_TYPE_UINT64_ARRAY:
+ if (vsize == 0)
+ vsize = 8;
+ for (p = data; p < end; p += vsize) {
+ if (tohost) {
+ switch (vsize) {
+ case 2:
+ *(uint16_t *)(void *)p =
+ le16toh(*(uint16_t *)(void *)p);
+ break;
+ case 4:
+ *(uint32_t *)(void *)p =
+ le32toh(*(uint32_t *)(void *)p);
+ break;
+ case 8:
+ *(uint64_t *)(void *)p =
+ le64toh(*(uint64_t *)(void *)p);
+ break;
+ default:
+ PJDLOG_ABORT("invalid condition");
+ }
+ } else {
+ switch (vsize) {
+ case 2:
+ *(uint16_t *)(void *)p =
+ htole16(*(uint16_t *)(void *)p);
+ break;
+ case 4:
+ *(uint32_t *)(void *)p =
+ htole32(*(uint32_t *)(void *)p);
+ break;
+ case 8:
+ *(uint64_t *)(void *)p =
+ htole64(*(uint64_t *)(void *)p);
+ break;
+ default:
+ PJDLOG_ABORT("invalid condition");
+ }
+ }
+ }
+ break;
+ case NV_TYPE_STRING:
+ break;
+ default:
+ PJDLOG_ABORT("unrecognized type");
+ }
+}
diff --git a/sbin/hastd/nv.h b/sbin/hastd/nv.h
new file mode 100644
index 0000000..d49fa5d
--- /dev/null
+++ b/sbin/hastd/nv.h
@@ -0,0 +1,133 @@
+/*-
+ * Copyright (c) 2009-2010 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _NV_H_
+#define _NV_H_
+
+#include <sys/cdefs.h>
+
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include <ebuf.h>
+
+struct nv;
+
+struct nv *nv_alloc(void);
+void nv_free(struct nv *nv);
+int nv_error(const struct nv *nv);
+int nv_set_error(struct nv *nv, int error);
+int nv_validate(struct nv *nv, size_t *extrap);
+
+struct ebuf *nv_hton(struct nv *nv);
+struct nv *nv_ntoh(struct ebuf *eb);
+
+void nv_add_int8(struct nv *nv, int8_t value, const char *namefmt, ...)
+ __printflike(3, 4);
+void nv_add_uint8(struct nv *nv, uint8_t value, const char *namefmt, ...)
+ __printflike(3, 4);
+void nv_add_int16(struct nv *nv, int16_t value, const char *namefmt, ...)
+ __printflike(3, 4);
+void nv_add_uint16(struct nv *nv, uint16_t value, const char *namefmt, ...)
+ __printflike(3, 4);
+void nv_add_int32(struct nv *nv, int32_t value, const char *namefmt, ...)
+ __printflike(3, 4);
+void nv_add_uint32(struct nv *nv, uint32_t value, const char *namefmt, ...)
+ __printflike(3, 4);
+void nv_add_int64(struct nv *nv, int64_t value, const char *namefmt, ...)
+ __printflike(3, 4);
+void nv_add_uint64(struct nv *nv, uint64_t value, const char *namefmt, ...)
+ __printflike(3, 4);
+void nv_add_int8_array(struct nv *nv, const int8_t *value, size_t size,
+ const char *namefmt, ...) __printflike(4, 5);
+void nv_add_uint8_array(struct nv *nv, const uint8_t *value, size_t size,
+ const char *namefmt, ...) __printflike(4, 5);
+void nv_add_int16_array(struct nv *nv, const int16_t *value, size_t size,
+ const char *namefmt, ...) __printflike(4, 5);
+void nv_add_uint16_array(struct nv *nv, const uint16_t *value, size_t size,
+ const char *namefmt, ...) __printflike(4, 5);
+void nv_add_int32_array(struct nv *nv, const int32_t *value, size_t size,
+ const char *namefmt, ...) __printflike(4, 5);
+void nv_add_uint32_array(struct nv *nv, const uint32_t *value, size_t size,
+ const char *namefmt, ...) __printflike(4, 5);
+void nv_add_int64_array(struct nv *nv, const int64_t *value, size_t size,
+ const char *namefmt, ...) __printflike(4, 5);
+void nv_add_uint64_array(struct nv *nv, const uint64_t *value, size_t size,
+ const char *namefmt, ...) __printflike(4, 5);
+void nv_add_string(struct nv *nv, const char *value, const char *namefmt, ...)
+ __printflike(3, 4);
+void nv_add_stringf(struct nv *nv, const char *name, const char *valuefmt, ...)
+ __printflike(3, 4);
+void nv_add_stringv(struct nv *nv, const char *name, const char *valuefmt,
+ va_list valueap) __printflike(3, 0);
+
+int8_t nv_get_int8(struct nv *nv, const char *namefmt, ...)
+ __printflike(2, 3);
+uint8_t nv_get_uint8(struct nv *nv, const char *namefmt, ...)
+ __printflike(2, 3);
+int16_t nv_get_int16(struct nv *nv, const char *namefmt, ...)
+ __printflike(2, 3);
+uint16_t nv_get_uint16(struct nv *nv, const char *namefmt, ...)
+ __printflike(2, 3);
+int32_t nv_get_int32(struct nv *nv, const char *namefmt, ...)
+ __printflike(2, 3);
+uint32_t nv_get_uint32(struct nv *nv, const char *namefmt, ...)
+ __printflike(2, 3);
+int64_t nv_get_int64(struct nv *nv, const char *namefmt, ...)
+ __printflike(2, 3);
+uint64_t nv_get_uint64(struct nv *nv, const char *namefmt, ...)
+ __printflike(2, 3);
+const int8_t *nv_get_int8_array(struct nv *nv, size_t *sizep,
+ const char *namefmt, ...) __printflike(3, 4);
+const uint8_t *nv_get_uint8_array(struct nv *nv, size_t *sizep,
+ const char *namefmt, ...) __printflike(3, 4);
+const int16_t *nv_get_int16_array(struct nv *nv, size_t *sizep,
+ const char *namefmt, ...) __printflike(3, 4);
+const uint16_t *nv_get_uint16_array(struct nv *nv, size_t *sizep,
+ const char *namefmt, ...) __printflike(3, 4);
+const int32_t *nv_get_int32_array(struct nv *nv, size_t *sizep,
+ const char *namefmt, ...) __printflike(3, 4);
+const uint32_t *nv_get_uint32_array(struct nv *nv, size_t *sizep,
+ const char *namefmt, ...) __printflike(3, 4);
+const int64_t *nv_get_int64_array(struct nv *nv, size_t *sizep,
+ const char *namefmt, ...) __printflike(3, 4);
+const uint64_t *nv_get_uint64_array(struct nv *nv, size_t *sizep,
+ const char *namefmt, ...) __printflike(3, 4);
+const char *nv_get_string(struct nv *nv, const char *namefmt, ...)
+ __printflike(2, 3);
+
+bool nv_exists(struct nv *nv, const char *namefmt, ...) __printflike(2, 3);
+void nv_assert(struct nv *nv, const char *namefmt, ...) __printflike(2, 3);
+void nv_dump(struct nv *nv);
+
+#endif /* !_NV_H_ */
diff --git a/sbin/hastd/parse.y b/sbin/hastd/parse.y
new file mode 100644
index 0000000..6bfb537
--- /dev/null
+++ b/sbin/hastd/parse.y
@@ -0,0 +1,1037 @@
+%{
+/*-
+ * Copyright (c) 2009-2010 The FreeBSD Foundation
+ * Copyright (c) 2011 Pawel Jakub Dawidek <pawel@dawidek.net>
+ * All rights reserved.
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/param.h> /* MAXHOSTNAMELEN */
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+
+#include <arpa/inet.h>
+
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include <pjdlog.h>
+
+#include "hast.h"
+
+extern int depth;
+extern int lineno;
+
+extern FILE *yyin;
+extern char *yytext;
+
+static struct hastd_config *lconfig;
+static struct hast_resource *curres;
+static bool mynode, hadmynode;
+
+static char depth0_control[HAST_ADDRSIZE];
+static char depth0_pidfile[PATH_MAX];
+static char depth0_listen_tcp4[HAST_ADDRSIZE];
+static char depth0_listen_tcp6[HAST_ADDRSIZE];
+static TAILQ_HEAD(, hastd_listen) depth0_listen;
+static int depth0_replication;
+static int depth0_checksum;
+static int depth0_compression;
+static int depth0_timeout;
+static char depth0_exec[PATH_MAX];
+static int depth0_metaflush;
+
+static char depth1_provname[PATH_MAX];
+static char depth1_localpath[PATH_MAX];
+static int depth1_metaflush;
+
+extern void yyerror(const char *);
+extern int yylex(void);
+extern void yyrestart(FILE *);
+
+static int isitme(const char *name);
+static bool family_supported(int family);
+static int node_names(char **namesp);
+%}
+
+%token CONTROL PIDFILE LISTEN REPLICATION CHECKSUM COMPRESSION METAFLUSH
+%token TIMEOUT EXEC RESOURCE NAME LOCAL REMOTE SOURCE ON OFF
+%token FULLSYNC MEMSYNC ASYNC NONE CRC32 SHA256 HOLE LZF
+%token NUM STR OB CB
+
+%type <str> remote_str
+%type <num> replication_type
+%type <num> checksum_type
+%type <num> compression_type
+%type <num> boolean
+
+%union
+{
+ int num;
+ char *str;
+}
+
+%token <num> NUM
+%token <str> STR
+
+%%
+
+statements:
+ |
+ statements statement
+ ;
+
+statement:
+ control_statement
+ |
+ pidfile_statement
+ |
+ listen_statement
+ |
+ replication_statement
+ |
+ checksum_statement
+ |
+ compression_statement
+ |
+ timeout_statement
+ |
+ exec_statement
+ |
+ metaflush_statement
+ |
+ node_statement
+ |
+ resource_statement
+ ;
+
+control_statement: CONTROL STR
+ {
+ switch (depth) {
+ case 0:
+ if (strlcpy(depth0_control, $2,
+ sizeof(depth0_control)) >=
+ sizeof(depth0_control)) {
+ pjdlog_error("control argument is too long.");
+ free($2);
+ return (1);
+ }
+ break;
+ case 1:
+ if (!mynode)
+ break;
+ if (strlcpy(lconfig->hc_controladdr, $2,
+ sizeof(lconfig->hc_controladdr)) >=
+ sizeof(lconfig->hc_controladdr)) {
+ pjdlog_error("control argument is too long.");
+ free($2);
+ return (1);
+ }
+ break;
+ default:
+ PJDLOG_ABORT("control at wrong depth level");
+ }
+ free($2);
+ }
+ ;
+
+pidfile_statement: PIDFILE STR
+ {
+ switch (depth) {
+ case 0:
+ if (strlcpy(depth0_pidfile, $2,
+ sizeof(depth0_pidfile)) >=
+ sizeof(depth0_pidfile)) {
+ pjdlog_error("pidfile argument is too long.");
+ free($2);
+ return (1);
+ }
+ break;
+ case 1:
+ if (!mynode)
+ break;
+ if (strlcpy(lconfig->hc_pidfile, $2,
+ sizeof(lconfig->hc_pidfile)) >=
+ sizeof(lconfig->hc_pidfile)) {
+ pjdlog_error("pidfile argument is too long.");
+ free($2);
+ return (1);
+ }
+ break;
+ default:
+ PJDLOG_ABORT("pidfile at wrong depth level");
+ }
+ free($2);
+ }
+ ;
+
+listen_statement: LISTEN STR
+ {
+ struct hastd_listen *lst;
+
+ lst = calloc(1, sizeof(*lst));
+ if (lst == NULL) {
+ pjdlog_error("Unable to allocate memory for listen address.");
+ free($2);
+ return (1);
+ }
+ if (strlcpy(lst->hl_addr, $2, sizeof(lst->hl_addr)) >=
+ sizeof(lst->hl_addr)) {
+ pjdlog_error("listen argument is too long.");
+ free($2);
+ free(lst);
+ return (1);
+ }
+ switch (depth) {
+ case 0:
+ TAILQ_INSERT_TAIL(&depth0_listen, lst, hl_next);
+ break;
+ case 1:
+ if (mynode)
+ TAILQ_INSERT_TAIL(&depth0_listen, lst, hl_next);
+ else
+ free(lst);
+ break;
+ default:
+ PJDLOG_ABORT("listen at wrong depth level");
+ }
+ free($2);
+ }
+ ;
+
+replication_statement: REPLICATION replication_type
+ {
+ switch (depth) {
+ case 0:
+ depth0_replication = $2;
+ break;
+ case 1:
+ PJDLOG_ASSERT(curres != NULL);
+ curres->hr_replication = $2;
+ curres->hr_original_replication = $2;
+ break;
+ default:
+ PJDLOG_ABORT("replication at wrong depth level");
+ }
+ }
+ ;
+
+replication_type:
+ FULLSYNC { $$ = HAST_REPLICATION_FULLSYNC; }
+ |
+ MEMSYNC { $$ = HAST_REPLICATION_MEMSYNC; }
+ |
+ ASYNC { $$ = HAST_REPLICATION_ASYNC; }
+ ;
+
+checksum_statement: CHECKSUM checksum_type
+ {
+ switch (depth) {
+ case 0:
+ depth0_checksum = $2;
+ break;
+ case 1:
+ PJDLOG_ASSERT(curres != NULL);
+ curres->hr_checksum = $2;
+ break;
+ default:
+ PJDLOG_ABORT("checksum at wrong depth level");
+ }
+ }
+ ;
+
+checksum_type:
+ NONE { $$ = HAST_CHECKSUM_NONE; }
+ |
+ CRC32 { $$ = HAST_CHECKSUM_CRC32; }
+ |
+ SHA256 { $$ = HAST_CHECKSUM_SHA256; }
+ ;
+
+compression_statement: COMPRESSION compression_type
+ {
+ switch (depth) {
+ case 0:
+ depth0_compression = $2;
+ break;
+ case 1:
+ PJDLOG_ASSERT(curres != NULL);
+ curres->hr_compression = $2;
+ break;
+ default:
+ PJDLOG_ABORT("compression at wrong depth level");
+ }
+ }
+ ;
+
+compression_type:
+ NONE { $$ = HAST_COMPRESSION_NONE; }
+ |
+ HOLE { $$ = HAST_COMPRESSION_HOLE; }
+ |
+ LZF { $$ = HAST_COMPRESSION_LZF; }
+ ;
+
+timeout_statement: TIMEOUT NUM
+ {
+ if ($2 <= 0) {
+ pjdlog_error("Negative or zero timeout.");
+ return (1);
+ }
+ switch (depth) {
+ case 0:
+ depth0_timeout = $2;
+ break;
+ case 1:
+ PJDLOG_ASSERT(curres != NULL);
+ curres->hr_timeout = $2;
+ break;
+ default:
+ PJDLOG_ABORT("timeout at wrong depth level");
+ }
+ }
+ ;
+
+exec_statement: EXEC STR
+ {
+ switch (depth) {
+ case 0:
+ if (strlcpy(depth0_exec, $2, sizeof(depth0_exec)) >=
+ sizeof(depth0_exec)) {
+ pjdlog_error("Exec path is too long.");
+ free($2);
+ return (1);
+ }
+ break;
+ case 1:
+ PJDLOG_ASSERT(curres != NULL);
+ if (strlcpy(curres->hr_exec, $2,
+ sizeof(curres->hr_exec)) >=
+ sizeof(curres->hr_exec)) {
+ pjdlog_error("Exec path is too long.");
+ free($2);
+ return (1);
+ }
+ break;
+ default:
+ PJDLOG_ABORT("exec at wrong depth level");
+ }
+ free($2);
+ }
+ ;
+
+metaflush_statement: METAFLUSH boolean
+ {
+ switch (depth) {
+ case 0:
+ depth0_metaflush = $2;
+ break;
+ case 1:
+ PJDLOG_ASSERT(curres != NULL);
+ depth1_metaflush = $2;
+ break;
+ case 2:
+ if (!mynode)
+ break;
+ PJDLOG_ASSERT(curres != NULL);
+ curres->hr_metaflush = $2;
+ break;
+ default:
+ PJDLOG_ABORT("metaflush at wrong depth level");
+ }
+ }
+ ;
+
+boolean:
+ ON { $$ = 1; }
+ |
+ OFF { $$ = 0; }
+ ;
+
+node_statement: ON node_start OB node_entries CB
+ {
+ mynode = false;
+ }
+ ;
+
+node_start: STR
+ {
+ switch (isitme($1)) {
+ case -1:
+ free($1);
+ return (1);
+ case 0:
+ break;
+ case 1:
+ mynode = true;
+ break;
+ default:
+ PJDLOG_ABORT("invalid isitme() return value");
+ }
+ free($1);
+ }
+ ;
+
+node_entries:
+ |
+ node_entries node_entry
+ ;
+
+node_entry:
+ control_statement
+ |
+ pidfile_statement
+ |
+ listen_statement
+ ;
+
+resource_statement: RESOURCE resource_start OB resource_entries CB
+ {
+ if (curres != NULL) {
+ /*
+ * There must be section for this node, at least with
+ * remote address configuration.
+ */
+ if (!hadmynode) {
+ char *names;
+
+ if (node_names(&names) != 0)
+ return (1);
+ pjdlog_error("No resource %s configuration for this node (acceptable node names: %s).",
+ curres->hr_name, names);
+ return (1);
+ }
+
+ /*
+ * Let's see if there are some resource-level settings
+ * that we can use for node-level settings.
+ */
+ if (curres->hr_provname[0] == '\0' &&
+ depth1_provname[0] != '\0') {
+ /*
+ * Provider name is not set at node-level,
+ * but is set at resource-level, use it.
+ */
+ strlcpy(curres->hr_provname, depth1_provname,
+ sizeof(curres->hr_provname));
+ }
+ if (curres->hr_localpath[0] == '\0' &&
+ depth1_localpath[0] != '\0') {
+ /*
+ * Path to local provider is not set at
+ * node-level, but is set at resource-level,
+ * use it.
+ */
+ strlcpy(curres->hr_localpath, depth1_localpath,
+ sizeof(curres->hr_localpath));
+ }
+ if (curres->hr_metaflush == -1 && depth1_metaflush != -1) {
+ /*
+ * Metaflush is not set at node-level,
+ * but is set at resource-level, use it.
+ */
+ curres->hr_metaflush = depth1_metaflush;
+ }
+
+ /*
+ * If provider name is not given, use resource name
+ * as provider name.
+ */
+ if (curres->hr_provname[0] == '\0') {
+ strlcpy(curres->hr_provname, curres->hr_name,
+ sizeof(curres->hr_provname));
+ }
+
+ /*
+ * Remote address has to be configured at this point.
+ */
+ if (curres->hr_remoteaddr[0] == '\0') {
+ pjdlog_error("Remote address not configured for resource %s.",
+ curres->hr_name);
+ return (1);
+ }
+ /*
+ * Path to local provider has to be configured at this
+ * point.
+ */
+ if (curres->hr_localpath[0] == '\0') {
+ pjdlog_error("Path to local component not configured for resource %s.",
+ curres->hr_name);
+ return (1);
+ }
+
+ /* Put it onto resource list. */
+ TAILQ_INSERT_TAIL(&lconfig->hc_resources, curres, hr_next);
+ curres = NULL;
+ }
+ }
+ ;
+
+resource_start: STR
+ {
+ /* Check if there is no duplicate entry. */
+ TAILQ_FOREACH(curres, &lconfig->hc_resources, hr_next) {
+ if (strcmp(curres->hr_name, $1) == 0) {
+ pjdlog_error("Resource %s configured more than once.",
+ curres->hr_name);
+ free($1);
+ return (1);
+ }
+ }
+
+ /*
+ * Clear those, so we can tell if they were set at
+ * resource-level or not.
+ */
+ depth1_provname[0] = '\0';
+ depth1_localpath[0] = '\0';
+ depth1_metaflush = -1;
+ hadmynode = false;
+
+ curres = calloc(1, sizeof(*curres));
+ if (curres == NULL) {
+ pjdlog_error("Unable to allocate memory for resource.");
+ free($1);
+ return (1);
+ }
+ if (strlcpy(curres->hr_name, $1,
+ sizeof(curres->hr_name)) >=
+ sizeof(curres->hr_name)) {
+ pjdlog_error("Resource name is too long.");
+ free(curres);
+ free($1);
+ return (1);
+ }
+ free($1);
+ curres->hr_role = HAST_ROLE_INIT;
+ curres->hr_previous_role = HAST_ROLE_INIT;
+ curres->hr_replication = -1;
+ curres->hr_original_replication = -1;
+ curres->hr_checksum = -1;
+ curres->hr_compression = -1;
+ curres->hr_version = 1;
+ curres->hr_timeout = -1;
+ curres->hr_exec[0] = '\0';
+ curres->hr_provname[0] = '\0';
+ curres->hr_localpath[0] = '\0';
+ curres->hr_localfd = -1;
+ curres->hr_localflush = true;
+ curres->hr_metaflush = -1;
+ curres->hr_remoteaddr[0] = '\0';
+ curres->hr_sourceaddr[0] = '\0';
+ curres->hr_ggateunit = -1;
+ }
+ ;
+
+resource_entries:
+ |
+ resource_entries resource_entry
+ ;
+
+resource_entry:
+ replication_statement
+ |
+ checksum_statement
+ |
+ compression_statement
+ |
+ timeout_statement
+ |
+ exec_statement
+ |
+ metaflush_statement
+ |
+ name_statement
+ |
+ local_statement
+ |
+ resource_node_statement
+ ;
+
+name_statement: NAME STR
+ {
+ switch (depth) {
+ case 1:
+ if (strlcpy(depth1_provname, $2,
+ sizeof(depth1_provname)) >=
+ sizeof(depth1_provname)) {
+ pjdlog_error("name argument is too long.");
+ free($2);
+ return (1);
+ }
+ break;
+ case 2:
+ if (!mynode)
+ break;
+ PJDLOG_ASSERT(curres != NULL);
+ if (strlcpy(curres->hr_provname, $2,
+ sizeof(curres->hr_provname)) >=
+ sizeof(curres->hr_provname)) {
+ pjdlog_error("name argument is too long.");
+ free($2);
+ return (1);
+ }
+ break;
+ default:
+ PJDLOG_ABORT("name at wrong depth level");
+ }
+ free($2);
+ }
+ ;
+
+local_statement: LOCAL STR
+ {
+ switch (depth) {
+ case 1:
+ if (strlcpy(depth1_localpath, $2,
+ sizeof(depth1_localpath)) >=
+ sizeof(depth1_localpath)) {
+ pjdlog_error("local argument is too long.");
+ free($2);
+ return (1);
+ }
+ break;
+ case 2:
+ if (!mynode)
+ break;
+ PJDLOG_ASSERT(curres != NULL);
+ if (strlcpy(curres->hr_localpath, $2,
+ sizeof(curres->hr_localpath)) >=
+ sizeof(curres->hr_localpath)) {
+ pjdlog_error("local argument is too long.");
+ free($2);
+ return (1);
+ }
+ break;
+ default:
+ PJDLOG_ABORT("local at wrong depth level");
+ }
+ free($2);
+ }
+ ;
+
+resource_node_statement:ON resource_node_start OB resource_node_entries CB
+ {
+ mynode = false;
+ }
+ ;
+
+resource_node_start: STR
+ {
+ if (curres != NULL) {
+ switch (isitme($1)) {
+ case -1:
+ free($1);
+ return (1);
+ case 0:
+ break;
+ case 1:
+ mynode = hadmynode = true;
+ break;
+ default:
+ PJDLOG_ABORT("invalid isitme() return value");
+ }
+ }
+ free($1);
+ }
+ ;
+
+resource_node_entries:
+ |
+ resource_node_entries resource_node_entry
+ ;
+
+resource_node_entry:
+ name_statement
+ |
+ local_statement
+ |
+ remote_statement
+ |
+ source_statement
+ |
+ metaflush_statement
+ ;
+
+remote_statement: REMOTE remote_str
+ {
+ PJDLOG_ASSERT(depth == 2);
+ if (mynode) {
+ PJDLOG_ASSERT(curres != NULL);
+ if (strlcpy(curres->hr_remoteaddr, $2,
+ sizeof(curres->hr_remoteaddr)) >=
+ sizeof(curres->hr_remoteaddr)) {
+ pjdlog_error("remote argument is too long.");
+ free($2);
+ return (1);
+ }
+ }
+ free($2);
+ }
+ ;
+
+remote_str:
+ NONE { $$ = strdup("none"); }
+ |
+ STR { }
+ ;
+
+source_statement: SOURCE STR
+ {
+ PJDLOG_ASSERT(depth == 2);
+ if (mynode) {
+ PJDLOG_ASSERT(curres != NULL);
+ if (strlcpy(curres->hr_sourceaddr, $2,
+ sizeof(curres->hr_sourceaddr)) >=
+ sizeof(curres->hr_sourceaddr)) {
+ pjdlog_error("source argument is too long.");
+ free($2);
+ return (1);
+ }
+ }
+ free($2);
+ }
+ ;
+
+%%
+
+static int
+isitme(const char *name)
+{
+ char buf[MAXHOSTNAMELEN];
+ unsigned long hostid;
+ char *pos;
+ size_t bufsize;
+
+ /*
+ * First check if the given name matches our full hostname.
+ */
+ if (gethostname(buf, sizeof(buf)) < 0) {
+ pjdlog_errno(LOG_ERR, "gethostname() failed");
+ return (-1);
+ }
+ if (strcmp(buf, name) == 0)
+ return (1);
+
+ /*
+ * Check if it matches first part of the host name.
+ */
+ pos = strchr(buf, '.');
+ if (pos != NULL && (size_t)(pos - buf) == strlen(name) &&
+ strncmp(buf, name, pos - buf) == 0) {
+ return (1);
+ }
+
+ /*
+ * Check if it matches host UUID.
+ */
+ bufsize = sizeof(buf);
+ if (sysctlbyname("kern.hostuuid", buf, &bufsize, NULL, 0) < 0) {
+ pjdlog_errno(LOG_ERR, "sysctlbyname(kern.hostuuid) failed");
+ return (-1);
+ }
+ if (strcasecmp(buf, name) == 0)
+ return (1);
+
+ /*
+ * Check if it matches hostid.
+ */
+ bufsize = sizeof(hostid);
+ if (sysctlbyname("kern.hostid", &hostid, &bufsize, NULL, 0) < 0) {
+ pjdlog_errno(LOG_ERR, "sysctlbyname(kern.hostid) failed");
+ return (-1);
+ }
+ (void)snprintf(buf, sizeof(buf), "hostid%lu", hostid);
+ if (strcmp(buf, name) == 0)
+ return (1);
+
+ /*
+ * Looks like this isn't about us.
+ */
+ return (0);
+}
+
+static bool
+family_supported(int family)
+{
+ int sock;
+
+ sock = socket(family, SOCK_STREAM, 0);
+ if (sock == -1 && errno == EPROTONOSUPPORT)
+ return (false);
+ if (sock >= 0)
+ (void)close(sock);
+ return (true);
+}
+
+static int
+node_names(char **namesp)
+{
+ static char names[MAXHOSTNAMELEN * 3];
+ char buf[MAXHOSTNAMELEN];
+ unsigned long hostid;
+ char *pos;
+ size_t bufsize;
+
+ if (gethostname(buf, sizeof(buf)) < 0) {
+ pjdlog_errno(LOG_ERR, "gethostname() failed");
+ return (-1);
+ }
+
+ /* First component of the host name. */
+ pos = strchr(buf, '.');
+ if (pos != NULL && pos != buf) {
+ (void)strlcpy(names, buf, MIN((size_t)(pos - buf + 1),
+ sizeof(names)));
+ (void)strlcat(names, ", ", sizeof(names));
+ }
+
+ /* Full host name. */
+ (void)strlcat(names, buf, sizeof(names));
+ (void)strlcat(names, ", ", sizeof(names));
+
+ /* Host UUID. */
+ bufsize = sizeof(buf);
+ if (sysctlbyname("kern.hostuuid", buf, &bufsize, NULL, 0) < 0) {
+ pjdlog_errno(LOG_ERR, "sysctlbyname(kern.hostuuid) failed");
+ return (-1);
+ }
+ (void)strlcat(names, buf, sizeof(names));
+ (void)strlcat(names, ", ", sizeof(names));
+
+ /* Host ID. */
+ bufsize = sizeof(hostid);
+ if (sysctlbyname("kern.hostid", &hostid, &bufsize, NULL, 0) < 0) {
+ pjdlog_errno(LOG_ERR, "sysctlbyname(kern.hostid) failed");
+ return (-1);
+ }
+ (void)snprintf(buf, sizeof(buf), "hostid%lu", hostid);
+ (void)strlcat(names, buf, sizeof(names));
+
+ *namesp = names;
+
+ return (0);
+}
+
+void
+yyerror(const char *str)
+{
+
+ pjdlog_error("Unable to parse configuration file at line %d near '%s': %s",
+ lineno, yytext, str);
+}
+
+struct hastd_config *
+yy_config_parse(const char *config, bool exitonerror)
+{
+ int ret;
+
+ curres = NULL;
+ mynode = false;
+ depth = 0;
+ lineno = 0;
+
+ depth0_timeout = HAST_TIMEOUT;
+ depth0_replication = HAST_REPLICATION_MEMSYNC;
+ depth0_checksum = HAST_CHECKSUM_NONE;
+ depth0_compression = HAST_COMPRESSION_HOLE;
+ strlcpy(depth0_control, HAST_CONTROL, sizeof(depth0_control));
+ strlcpy(depth0_pidfile, HASTD_PIDFILE, sizeof(depth0_pidfile));
+ TAILQ_INIT(&depth0_listen);
+ strlcpy(depth0_listen_tcp4, HASTD_LISTEN_TCP4,
+ sizeof(depth0_listen_tcp4));
+ strlcpy(depth0_listen_tcp6, HASTD_LISTEN_TCP6,
+ sizeof(depth0_listen_tcp6));
+ depth0_exec[0] = '\0';
+ depth0_metaflush = 1;
+
+ lconfig = calloc(1, sizeof(*lconfig));
+ if (lconfig == NULL) {
+ pjdlog_error("Unable to allocate memory for configuration.");
+ if (exitonerror)
+ exit(EX_TEMPFAIL);
+ return (NULL);
+ }
+
+ TAILQ_INIT(&lconfig->hc_listen);
+ TAILQ_INIT(&lconfig->hc_resources);
+
+ yyin = fopen(config, "r");
+ if (yyin == NULL) {
+ pjdlog_errno(LOG_ERR, "Unable to open configuration file %s",
+ config);
+ yy_config_free(lconfig);
+ if (exitonerror)
+ exit(EX_OSFILE);
+ return (NULL);
+ }
+ yyrestart(yyin);
+ ret = yyparse();
+ fclose(yyin);
+ if (ret != 0) {
+ yy_config_free(lconfig);
+ if (exitonerror)
+ exit(EX_CONFIG);
+ return (NULL);
+ }
+
+ /*
+ * Let's see if everything is set up.
+ */
+ if (lconfig->hc_controladdr[0] == '\0') {
+ strlcpy(lconfig->hc_controladdr, depth0_control,
+ sizeof(lconfig->hc_controladdr));
+ }
+ if (lconfig->hc_pidfile[0] == '\0') {
+ strlcpy(lconfig->hc_pidfile, depth0_pidfile,
+ sizeof(lconfig->hc_pidfile));
+ }
+ if (!TAILQ_EMPTY(&depth0_listen))
+ TAILQ_CONCAT(&lconfig->hc_listen, &depth0_listen, hl_next);
+ if (TAILQ_EMPTY(&lconfig->hc_listen)) {
+ struct hastd_listen *lst;
+
+ if (family_supported(AF_INET)) {
+ lst = calloc(1, sizeof(*lst));
+ if (lst == NULL) {
+ pjdlog_error("Unable to allocate memory for listen address.");
+ yy_config_free(lconfig);
+ if (exitonerror)
+ exit(EX_TEMPFAIL);
+ return (NULL);
+ }
+ (void)strlcpy(lst->hl_addr, depth0_listen_tcp4,
+ sizeof(lst->hl_addr));
+ TAILQ_INSERT_TAIL(&lconfig->hc_listen, lst, hl_next);
+ } else {
+ pjdlog_debug(1,
+ "No IPv4 support in the kernel, not listening on IPv4 address.");
+ }
+ if (family_supported(AF_INET6)) {
+ lst = calloc(1, sizeof(*lst));
+ if (lst == NULL) {
+ pjdlog_error("Unable to allocate memory for listen address.");
+ yy_config_free(lconfig);
+ if (exitonerror)
+ exit(EX_TEMPFAIL);
+ return (NULL);
+ }
+ (void)strlcpy(lst->hl_addr, depth0_listen_tcp6,
+ sizeof(lst->hl_addr));
+ TAILQ_INSERT_TAIL(&lconfig->hc_listen, lst, hl_next);
+ } else {
+ pjdlog_debug(1,
+ "No IPv6 support in the kernel, not listening on IPv6 address.");
+ }
+ if (TAILQ_EMPTY(&lconfig->hc_listen)) {
+ pjdlog_error("No address to listen on.");
+ yy_config_free(lconfig);
+ if (exitonerror)
+ exit(EX_TEMPFAIL);
+ return (NULL);
+ }
+ }
+ TAILQ_FOREACH(curres, &lconfig->hc_resources, hr_next) {
+ PJDLOG_ASSERT(curres->hr_provname[0] != '\0');
+ PJDLOG_ASSERT(curres->hr_localpath[0] != '\0');
+ PJDLOG_ASSERT(curres->hr_remoteaddr[0] != '\0');
+
+ if (curres->hr_replication == -1) {
+ /*
+ * Replication is not set at resource-level.
+ * Use global or default setting.
+ */
+ curres->hr_replication = depth0_replication;
+ curres->hr_original_replication = depth0_replication;
+ }
+ if (curres->hr_checksum == -1) {
+ /*
+ * Checksum is not set at resource-level.
+ * Use global or default setting.
+ */
+ curres->hr_checksum = depth0_checksum;
+ }
+ if (curres->hr_compression == -1) {
+ /*
+ * Compression is not set at resource-level.
+ * Use global or default setting.
+ */
+ curres->hr_compression = depth0_compression;
+ }
+ if (curres->hr_timeout == -1) {
+ /*
+ * Timeout is not set at resource-level.
+ * Use global or default setting.
+ */
+ curres->hr_timeout = depth0_timeout;
+ }
+ if (curres->hr_exec[0] == '\0') {
+ /*
+ * Exec is not set at resource-level.
+ * Use global or default setting.
+ */
+ strlcpy(curres->hr_exec, depth0_exec,
+ sizeof(curres->hr_exec));
+ }
+ if (curres->hr_metaflush == -1) {
+ /*
+ * Metaflush is not set at resource-level.
+ * Use global or default setting.
+ */
+ curres->hr_metaflush = depth0_metaflush;
+ }
+ }
+
+ return (lconfig);
+}
+
+void
+yy_config_free(struct hastd_config *config)
+{
+ struct hastd_listen *lst;
+ struct hast_resource *res;
+
+ while ((lst = TAILQ_FIRST(&depth0_listen)) != NULL) {
+ TAILQ_REMOVE(&depth0_listen, lst, hl_next);
+ free(lst);
+ }
+ while ((lst = TAILQ_FIRST(&config->hc_listen)) != NULL) {
+ TAILQ_REMOVE(&config->hc_listen, lst, hl_next);
+ free(lst);
+ }
+ while ((res = TAILQ_FIRST(&config->hc_resources)) != NULL) {
+ TAILQ_REMOVE(&config->hc_resources, res, hr_next);
+ free(res);
+ }
+ free(config);
+}
diff --git a/sbin/hastd/pjdlog.c b/sbin/hastd/pjdlog.c
new file mode 100644
index 0000000..bc4018f
--- /dev/null
+++ b/sbin/hastd/pjdlog.c
@@ -0,0 +1,614 @@
+/*-
+ * Copyright (c) 2009-2010 The FreeBSD Foundation
+ * Copyright (c) 2011 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+ * All rights reserved.
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <libutil.h>
+#include <printf.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+
+#include "pjdlog.h"
+
+#define PJDLOG_NEVER_INITIALIZED 0
+#define PJDLOG_NOT_INITIALIZED 1
+#define PJDLOG_INITIALIZED 2
+
+static int pjdlog_initialized = PJDLOG_NEVER_INITIALIZED;
+static int pjdlog_mode, pjdlog_debug_level;
+static char pjdlog_prefix[128];
+
+static int
+pjdlog_printf_arginfo_humanized_number(const struct printf_info *pi __unused,
+ size_t n, int *argt)
+{
+
+ assert(n >= 1);
+ argt[0] = PA_INT | PA_FLAG_INTMAX;
+ return (1);
+}
+
+static int
+pjdlog_printf_render_humanized_number(struct __printf_io *io,
+ const struct printf_info *pi, const void * const *arg)
+{
+ char buf[5];
+ intmax_t num;
+ int ret;
+
+ num = *(const intmax_t *)arg[0];
+ humanize_number(buf, sizeof(buf), (int64_t)num, "", HN_AUTOSCALE,
+ HN_NOSPACE | HN_DECIMAL);
+ ret = __printf_out(io, pi, buf, strlen(buf));
+ __printf_flush(io);
+ return (ret);
+}
+
+static int
+pjdlog_printf_arginfo_sockaddr(const struct printf_info *pi __unused,
+ size_t n, int *argt)
+{
+
+ assert(n >= 1);
+ argt[0] = PA_POINTER;
+ return (1);
+}
+
+static int
+pjdlog_printf_render_sockaddr(struct __printf_io *io,
+ const struct printf_info *pi, const void * const *arg)
+{
+ const struct sockaddr_storage *ss;
+ char buf[64];
+ int ret;
+
+ ss = *(const struct sockaddr_storage * const *)arg[0];
+ switch (ss->ss_family) {
+ case AF_INET:
+ {
+ char addr[INET_ADDRSTRLEN];
+ const struct sockaddr_in *sin;
+ unsigned int port;
+
+ sin = (const struct sockaddr_in *)ss;
+ port = ntohs(sin->sin_port);
+ if (inet_ntop(ss->ss_family, &sin->sin_addr, addr,
+ sizeof(addr)) == NULL) {
+ PJDLOG_ABORT("inet_ntop(AF_INET) failed: %s.",
+ strerror(errno));
+ }
+ snprintf(buf, sizeof(buf), "%s:%u", addr, port);
+ break;
+ }
+ case AF_INET6:
+ {
+ char addr[INET6_ADDRSTRLEN];
+ const struct sockaddr_in6 *sin;
+ unsigned int port;
+
+ sin = (const struct sockaddr_in6 *)ss;
+ port = ntohs(sin->sin6_port);
+ if (inet_ntop(ss->ss_family, &sin->sin6_addr, addr,
+ sizeof(addr)) == NULL) {
+ PJDLOG_ABORT("inet_ntop(AF_INET6) failed: %s.",
+ strerror(errno));
+ }
+ snprintf(buf, sizeof(buf), "[%s]:%u", addr, port);
+ break;
+ }
+ default:
+ snprintf(buf, sizeof(buf), "[unsupported family %hhu]",
+ ss->ss_family);
+ break;
+ }
+ ret = __printf_out(io, pi, buf, strlen(buf));
+ __printf_flush(io);
+ return (ret);
+}
+
+void
+pjdlog_init(int mode)
+{
+ int saved_errno;
+
+ assert(pjdlog_initialized == PJDLOG_NEVER_INITIALIZED ||
+ pjdlog_initialized == PJDLOG_NOT_INITIALIZED);
+ assert(mode == PJDLOG_MODE_STD || mode == PJDLOG_MODE_SYSLOG);
+
+ saved_errno = errno;
+
+ if (pjdlog_initialized == PJDLOG_NEVER_INITIALIZED) {
+ __use_xprintf = 1;
+ register_printf_render_std("T");
+ register_printf_render('N',
+ pjdlog_printf_render_humanized_number,
+ pjdlog_printf_arginfo_humanized_number);
+ register_printf_render('S',
+ pjdlog_printf_render_sockaddr,
+ pjdlog_printf_arginfo_sockaddr);
+ }
+
+ if (mode == PJDLOG_MODE_SYSLOG)
+ openlog(NULL, LOG_PID | LOG_NDELAY, LOG_DAEMON);
+ pjdlog_mode = mode;
+ pjdlog_debug_level = 0;
+ bzero(pjdlog_prefix, sizeof(pjdlog_prefix));
+
+ pjdlog_initialized = PJDLOG_INITIALIZED;
+
+ errno = saved_errno;
+}
+
+void
+pjdlog_fini(void)
+{
+ int saved_errno;
+
+ assert(pjdlog_initialized == PJDLOG_INITIALIZED);
+
+ saved_errno = errno;
+
+ if (pjdlog_mode == PJDLOG_MODE_SYSLOG)
+ closelog();
+
+ pjdlog_initialized = PJDLOG_NOT_INITIALIZED;
+
+ errno = saved_errno;
+}
+
+/*
+ * Configure where the logs should go.
+ * By default they are send to stdout/stderr, but after going into background
+ * (eg. by calling daemon(3)) application is responsible for changing mode to
+ * PJDLOG_MODE_SYSLOG, so logs will be send to syslog.
+ */
+void
+pjdlog_mode_set(int mode)
+{
+ int saved_errno;
+
+ assert(pjdlog_initialized == PJDLOG_INITIALIZED);
+ assert(mode == PJDLOG_MODE_STD || mode == PJDLOG_MODE_SYSLOG);
+
+ if (pjdlog_mode == mode)
+ return;
+
+ saved_errno = errno;
+
+ if (mode == PJDLOG_MODE_SYSLOG)
+ openlog(NULL, LOG_PID | LOG_NDELAY, LOG_DAEMON);
+ else /* if (mode == PJDLOG_MODE_STD) */
+ closelog();
+
+ pjdlog_mode = mode;
+
+ errno = saved_errno;
+}
+
+/*
+ * Return current mode.
+ */
+int
+pjdlog_mode_get(void)
+{
+
+ assert(pjdlog_initialized == PJDLOG_INITIALIZED);
+
+ return (pjdlog_mode);
+}
+
+/*
+ * Set debug level. All the logs above the level specified here will be
+ * ignored.
+ */
+void
+pjdlog_debug_set(int level)
+{
+
+ assert(pjdlog_initialized == PJDLOG_INITIALIZED);
+ assert(level >= 0);
+
+ pjdlog_debug_level = level;
+}
+
+/*
+ * Return current debug level.
+ */
+int
+pjdlog_debug_get(void)
+{
+
+ assert(pjdlog_initialized == PJDLOG_INITIALIZED);
+
+ return (pjdlog_debug_level);
+}
+
+/*
+ * Set prefix that will be used before each log.
+ * Setting prefix to NULL will remove it.
+ */
+void
+pjdlog_prefix_set(const char *fmt, ...)
+{
+ va_list ap;
+
+ assert(pjdlog_initialized == PJDLOG_INITIALIZED);
+
+ va_start(ap, fmt);
+ pjdlogv_prefix_set(fmt, ap);
+ va_end(ap);
+}
+
+/*
+ * Set prefix that will be used before each log.
+ * Setting prefix to NULL will remove it.
+ */
+void
+pjdlogv_prefix_set(const char *fmt, va_list ap)
+{
+ int saved_errno;
+
+ assert(pjdlog_initialized == PJDLOG_INITIALIZED);
+ assert(fmt != NULL);
+
+ saved_errno = errno;
+
+ vsnprintf(pjdlog_prefix, sizeof(pjdlog_prefix), fmt, ap);
+
+ errno = saved_errno;
+}
+
+/*
+ * Convert log level into string.
+ */
+static const char *
+pjdlog_level_string(int loglevel)
+{
+
+ switch (loglevel) {
+ case LOG_EMERG:
+ return ("EMERG");
+ case LOG_ALERT:
+ return ("ALERT");
+ case LOG_CRIT:
+ return ("CRIT");
+ case LOG_ERR:
+ return ("ERROR");
+ case LOG_WARNING:
+ return ("WARNING");
+ case LOG_NOTICE:
+ return ("NOTICE");
+ case LOG_INFO:
+ return ("INFO");
+ case LOG_DEBUG:
+ return ("DEBUG");
+ }
+ assert(!"Invalid log level.");
+ abort(); /* XXX: gcc */
+}
+
+/*
+ * Common log routine.
+ */
+void
+pjdlog_common(int loglevel, int debuglevel, int error, const char *fmt, ...)
+{
+ va_list ap;
+
+ assert(pjdlog_initialized == PJDLOG_INITIALIZED);
+
+ va_start(ap, fmt);
+ pjdlogv_common(loglevel, debuglevel, error, fmt, ap);
+ va_end(ap);
+}
+
+/*
+ * Common log routine, which can handle regular log level as well as debug
+ * level. We decide here where to send the logs (stdout/stderr or syslog).
+ */
+void
+pjdlogv_common(int loglevel, int debuglevel, int error, const char *fmt,
+ va_list ap)
+{
+ int saved_errno;
+
+ assert(pjdlog_initialized == PJDLOG_INITIALIZED);
+ assert(loglevel == LOG_EMERG || loglevel == LOG_ALERT ||
+ loglevel == LOG_CRIT || loglevel == LOG_ERR ||
+ loglevel == LOG_WARNING || loglevel == LOG_NOTICE ||
+ loglevel == LOG_INFO || loglevel == LOG_DEBUG);
+ assert(loglevel != LOG_DEBUG || debuglevel > 0);
+ assert(error >= -1);
+
+ /* Ignore debug above configured level. */
+ if (loglevel == LOG_DEBUG && debuglevel > pjdlog_debug_level)
+ return;
+
+ saved_errno = errno;
+
+ switch (pjdlog_mode) {
+ case PJDLOG_MODE_STD:
+ {
+ FILE *out;
+
+ /*
+ * We send errors and warning to stderr and the rest to stdout.
+ */
+ switch (loglevel) {
+ case LOG_EMERG:
+ case LOG_ALERT:
+ case LOG_CRIT:
+ case LOG_ERR:
+ case LOG_WARNING:
+ out = stderr;
+ break;
+ case LOG_NOTICE:
+ case LOG_INFO:
+ case LOG_DEBUG:
+ out = stdout;
+ break;
+ default:
+ assert(!"Invalid loglevel.");
+ abort(); /* XXX: gcc */
+ }
+
+ fprintf(out, "[%s]", pjdlog_level_string(loglevel));
+ /* Attach debuglevel if this is debug log. */
+ if (loglevel == LOG_DEBUG)
+ fprintf(out, "[%d]", debuglevel);
+ fprintf(out, " %s", pjdlog_prefix);
+ vfprintf(out, fmt, ap);
+ if (error != -1)
+ fprintf(out, ": %s.", strerror(error));
+ fprintf(out, "\n");
+ fflush(out);
+ break;
+ }
+ case PJDLOG_MODE_SYSLOG:
+ {
+ char log[1024];
+ int len;
+
+ len = snprintf(log, sizeof(log), "%s", pjdlog_prefix);
+ if ((size_t)len < sizeof(log))
+ len += vsnprintf(log + len, sizeof(log) - len, fmt, ap);
+ if (error != -1 && (size_t)len < sizeof(log)) {
+ (void)snprintf(log + len, sizeof(log) - len, ": %s.",
+ strerror(error));
+ }
+ syslog(loglevel, "%s", log);
+ break;
+ }
+ default:
+ assert(!"Invalid mode.");
+ }
+
+ errno = saved_errno;
+}
+
+/*
+ * Regular logs.
+ */
+void
+pjdlogv(int loglevel, const char *fmt, va_list ap)
+{
+
+ assert(pjdlog_initialized == PJDLOG_INITIALIZED);
+
+ /* LOG_DEBUG is invalid here, pjdlogv?_debug() should be used. */
+ assert(loglevel == LOG_EMERG || loglevel == LOG_ALERT ||
+ loglevel == LOG_CRIT || loglevel == LOG_ERR ||
+ loglevel == LOG_WARNING || loglevel == LOG_NOTICE ||
+ loglevel == LOG_INFO);
+
+ pjdlogv_common(loglevel, 0, -1, fmt, ap);
+}
+
+/*
+ * Regular logs.
+ */
+void
+pjdlog(int loglevel, const char *fmt, ...)
+{
+ va_list ap;
+
+ assert(pjdlog_initialized == PJDLOG_INITIALIZED);
+
+ va_start(ap, fmt);
+ pjdlogv(loglevel, fmt, ap);
+ va_end(ap);
+}
+
+/*
+ * Debug logs.
+ */
+void
+pjdlogv_debug(int debuglevel, const char *fmt, va_list ap)
+{
+
+ assert(pjdlog_initialized == PJDLOG_INITIALIZED);
+
+ pjdlogv_common(LOG_DEBUG, debuglevel, -1, fmt, ap);
+}
+
+/*
+ * Debug logs.
+ */
+void
+pjdlog_debug(int debuglevel, const char *fmt, ...)
+{
+ va_list ap;
+
+ assert(pjdlog_initialized == PJDLOG_INITIALIZED);
+
+ va_start(ap, fmt);
+ pjdlogv_debug(debuglevel, fmt, ap);
+ va_end(ap);
+}
+
+/*
+ * Error logs with errno logging.
+ */
+void
+pjdlogv_errno(int loglevel, const char *fmt, va_list ap)
+{
+
+ assert(pjdlog_initialized == PJDLOG_INITIALIZED);
+
+ pjdlogv_common(loglevel, 0, errno, fmt, ap);
+}
+
+/*
+ * Error logs with errno logging.
+ */
+void
+pjdlog_errno(int loglevel, const char *fmt, ...)
+{
+ va_list ap;
+
+ assert(pjdlog_initialized == PJDLOG_INITIALIZED);
+
+ va_start(ap, fmt);
+ pjdlogv_errno(loglevel, fmt, ap);
+ va_end(ap);
+}
+
+/*
+ * Log error, errno and exit.
+ */
+void
+pjdlogv_exit(int exitcode, const char *fmt, va_list ap)
+{
+
+ assert(pjdlog_initialized == PJDLOG_INITIALIZED);
+
+ pjdlogv_errno(LOG_ERR, fmt, ap);
+ exit(exitcode);
+ /* NOTREACHED */
+}
+
+/*
+ * Log error, errno and exit.
+ */
+void
+pjdlog_exit(int exitcode, const char *fmt, ...)
+{
+ va_list ap;
+
+ assert(pjdlog_initialized == PJDLOG_INITIALIZED);
+
+ va_start(ap, fmt);
+ pjdlogv_exit(exitcode, fmt, ap);
+ /* NOTREACHED */
+ va_end(ap);
+}
+
+/*
+ * Log error and exit.
+ */
+void
+pjdlogv_exitx(int exitcode, const char *fmt, va_list ap)
+{
+
+ assert(pjdlog_initialized == PJDLOG_INITIALIZED);
+
+ pjdlogv(LOG_ERR, fmt, ap);
+ exit(exitcode);
+ /* NOTREACHED */
+}
+
+/*
+ * Log error and exit.
+ */
+void
+pjdlog_exitx(int exitcode, const char *fmt, ...)
+{
+ va_list ap;
+
+ assert(pjdlog_initialized == PJDLOG_INITIALIZED);
+
+ va_start(ap, fmt);
+ pjdlogv_exitx(exitcode, fmt, ap);
+ /* NOTREACHED */
+ va_end(ap);
+}
+
+/*
+ * Log failure message and exit.
+ */
+void
+pjdlog_abort(const char *func, const char *file, int line,
+ const char *failedexpr, const char *fmt, ...)
+{
+ va_list ap;
+
+ assert(pjdlog_initialized == PJDLOG_INITIALIZED);
+
+ /*
+ * When there is no message we pass __func__ as 'fmt'.
+ * It would be cleaner to pass NULL or "", but gcc generates a warning
+ * for both of those.
+ */
+ if (fmt != func) {
+ va_start(ap, fmt);
+ pjdlogv_critical(fmt, ap);
+ va_end(ap);
+ }
+ if (failedexpr == NULL) {
+ if (func == NULL) {
+ pjdlog_critical("Aborted at file %s, line %d.", file,
+ line);
+ } else {
+ pjdlog_critical("Aborted at function %s, file %s, line %d.",
+ func, file, line);
+ }
+ } else {
+ if (func == NULL) {
+ pjdlog_critical("Assertion failed: (%s), file %s, line %d.",
+ failedexpr, file, line);
+ } else {
+ pjdlog_critical("Assertion failed: (%s), function %s, file %s, line %d.",
+ failedexpr, func, file, line);
+ }
+ }
+ abort();
+}
diff --git a/sbin/hastd/pjdlog.h b/sbin/hastd/pjdlog.h
new file mode 100644
index 0000000..0f01f79
--- /dev/null
+++ b/sbin/hastd/pjdlog.h
@@ -0,0 +1,117 @@
+/*-
+ * Copyright (c) 2009-2010 The FreeBSD Foundation
+ * Copyright (c) 2011 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+ * All rights reserved.
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _PJDLOG_H_
+#define _PJDLOG_H_
+
+#include <sys/cdefs.h>
+
+#include <stdarg.h>
+#include <sysexits.h>
+#include <syslog.h>
+
+#define PJDLOG_MODE_STD 0
+#define PJDLOG_MODE_SYSLOG 1
+
+void pjdlog_init(int mode);
+void pjdlog_fini(void);
+
+void pjdlog_mode_set(int mode);
+int pjdlog_mode_get(void);
+
+void pjdlog_debug_set(int level);
+int pjdlog_debug_get(void);
+
+void pjdlog_prefix_set(const char *fmt, ...) __printflike(1, 2);
+void pjdlogv_prefix_set(const char *fmt, va_list ap) __printflike(1, 0);
+
+void pjdlog_common(int loglevel, int debuglevel, int error, const char *fmt,
+ ...) __printflike(4, 5);
+void pjdlogv_common(int loglevel, int debuglevel, int error, const char *fmt,
+ va_list ap) __printflike(4, 0);
+
+void pjdlog(int loglevel, const char *fmt, ...) __printflike(2, 3);
+void pjdlogv(int loglevel, const char *fmt, va_list ap) __printflike(2, 0);
+
+#define pjdlogv_emergency(fmt, ap) pjdlogv(LOG_EMERG, (fmt), (ap))
+#define pjdlog_emergency(...) pjdlog(LOG_EMERG, __VA_ARGS__)
+#define pjdlogv_alert(fmt, ap) pjdlogv(LOG_ALERT, (fmt), (ap))
+#define pjdlog_alert(...) pjdlog(LOG_ALERT, __VA_ARGS__)
+#define pjdlogv_critical(fmt, ap) pjdlogv(LOG_CRIT, (fmt), (ap))
+#define pjdlog_critical(...) pjdlog(LOG_CRIT, __VA_ARGS__)
+#define pjdlogv_error(fmt, ap) pjdlogv(LOG_ERR, (fmt), (ap))
+#define pjdlog_error(...) pjdlog(LOG_ERR, __VA_ARGS__)
+#define pjdlogv_warning(fmt, ap) pjdlogv(LOG_WARNING, (fmt), (ap))
+#define pjdlog_warning(...) pjdlog(LOG_WARNING, __VA_ARGS__)
+#define pjdlogv_notice(fmt, ap) pjdlogv(LOG_NOTICE, (fmt), (ap))
+#define pjdlog_notice(...) pjdlog(LOG_NOTICE, __VA_ARGS__)
+#define pjdlogv_info(fmt, ap) pjdlogv(LOG_INFO, (fmt), (ap))
+#define pjdlog_info(...) pjdlog(LOG_INFO, __VA_ARGS__)
+
+void pjdlog_debug(int debuglevel, const char *fmt, ...) __printflike(2, 3);
+void pjdlogv_debug(int debuglevel, const char *fmt, va_list ap) __printflike(2, 0);
+
+void pjdlog_errno(int loglevel, const char *fmt, ...) __printflike(2, 3);
+void pjdlogv_errno(int loglevel, const char *fmt, va_list ap) __printflike(2, 0);
+
+void pjdlog_exit(int exitcode, const char *fmt, ...) __printflike(2, 3) __dead2;
+void pjdlogv_exit(int exitcode, const char *fmt, va_list ap) __printflike(2, 0) __dead2;
+
+void pjdlog_exitx(int exitcode, const char *fmt, ...) __printflike(2, 3) __dead2;
+void pjdlogv_exitx(int exitcode, const char *fmt, va_list ap) __printflike(2, 0) __dead2;
+
+void pjdlog_abort(const char *func, const char *file, int line,
+ const char *failedexpr, const char *fmt, ...) __printflike(5, 6) __dead2;
+
+#define PJDLOG_VERIFY(expr) do { \
+ if (!(expr)) { \
+ pjdlog_abort(__func__, __FILE__, __LINE__, #expr, \
+ __func__); \
+ } \
+} while (0)
+#define PJDLOG_RVERIFY(expr, ...) do { \
+ if (!(expr)) { \
+ pjdlog_abort(__func__, __FILE__, __LINE__, #expr, \
+ __VA_ARGS__); \
+ } \
+} while (0)
+#define PJDLOG_ABORT(...) pjdlog_abort(__func__, __FILE__, \
+ __LINE__, NULL, __VA_ARGS__)
+#ifdef NDEBUG
+#define PJDLOG_ASSERT(expr) do { } while (0)
+#define PJDLOG_RASSERT(...) do { } while (0)
+#else
+#define PJDLOG_ASSERT(expr) PJDLOG_VERIFY(expr)
+#define PJDLOG_RASSERT(...) PJDLOG_RVERIFY(__VA_ARGS__)
+#endif
+
+#endif /* !_PJDLOG_H_ */
diff --git a/sbin/hastd/primary.c b/sbin/hastd/primary.c
new file mode 100644
index 0000000..8fa3383
--- /dev/null
+++ b/sbin/hastd/primary.c
@@ -0,0 +1,2451 @@
+/*-
+ * Copyright (c) 2009 The FreeBSD Foundation
+ * Copyright (c) 2010-2011 Pawel Jakub Dawidek <pawel@dawidek.net>
+ * All rights reserved.
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/bio.h>
+#include <sys/disk.h>
+#include <sys/stat.h>
+
+#include <geom/gate/g_gate.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgeom.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include <activemap.h>
+#include <nv.h>
+#include <rangelock.h>
+
+#include "control.h"
+#include "event.h"
+#include "hast.h"
+#include "hast_proto.h"
+#include "hastd.h"
+#include "hooks.h"
+#include "metadata.h"
+#include "proto.h"
+#include "pjdlog.h"
+#include "refcnt.h"
+#include "subr.h"
+#include "synch.h"
+
+/* The is only one remote component for now. */
+#define ISREMOTE(no) ((no) == 1)
+
+struct hio {
+ /*
+ * Number of components we are still waiting for.
+ * When this field goes to 0, we can send the request back to the
+ * kernel. Each component has to decrease this counter by one
+ * even on failure.
+ */
+ refcnt_t hio_countdown;
+ /*
+ * Each component has a place to store its own error.
+ * Once the request is handled by all components we can decide if the
+ * request overall is successful or not.
+ */
+ int *hio_errors;
+ /*
+ * Structure used to communicate with GEOM Gate class.
+ */
+ struct g_gate_ctl_io hio_ggio;
+ /*
+ * Request was already confirmed to GEOM Gate.
+ */
+ bool hio_done;
+ /*
+ * Number of components we are still waiting before sending write
+ * completion ack to GEOM Gate. Used for memsync.
+ */
+ refcnt_t hio_writecount;
+ /*
+ * Memsync request was acknowleged by remote.
+ */
+ bool hio_memsyncacked;
+ /*
+ * Remember replication from the time the request was initiated,
+ * so we won't get confused when replication changes on reload.
+ */
+ int hio_replication;
+ TAILQ_ENTRY(hio) *hio_next;
+};
+#define hio_free_next hio_next[0]
+#define hio_done_next hio_next[0]
+
+/*
+ * Free list holds unused structures. When free list is empty, we have to wait
+ * until some in-progress requests are freed.
+ */
+static TAILQ_HEAD(, hio) hio_free_list;
+static size_t hio_free_list_size;
+static pthread_mutex_t hio_free_list_lock;
+static pthread_cond_t hio_free_list_cond;
+/*
+ * There is one send list for every component. One requests is placed on all
+ * send lists - each component gets the same request, but each component is
+ * responsible for managing his own send list.
+ */
+static TAILQ_HEAD(, hio) *hio_send_list;
+static size_t *hio_send_list_size;
+static pthread_mutex_t *hio_send_list_lock;
+static pthread_cond_t *hio_send_list_cond;
+#define hio_send_local_list_size hio_send_list_size[0]
+#define hio_send_remote_list_size hio_send_list_size[1]
+/*
+ * There is one recv list for every component, although local components don't
+ * use recv lists as local requests are done synchronously.
+ */
+static TAILQ_HEAD(, hio) *hio_recv_list;
+static size_t *hio_recv_list_size;
+static pthread_mutex_t *hio_recv_list_lock;
+static pthread_cond_t *hio_recv_list_cond;
+#define hio_recv_remote_list_size hio_recv_list_size[1]
+/*
+ * Request is placed on done list by the slowest component (the one that
+ * decreased hio_countdown from 1 to 0).
+ */
+static TAILQ_HEAD(, hio) hio_done_list;
+static size_t hio_done_list_size;
+static pthread_mutex_t hio_done_list_lock;
+static pthread_cond_t hio_done_list_cond;
+/*
+ * Structure below are for interaction with sync thread.
+ */
+static bool sync_inprogress;
+static pthread_mutex_t sync_lock;
+static pthread_cond_t sync_cond;
+/*
+ * The lock below allows to synchornize access to remote connections.
+ */
+static pthread_rwlock_t *hio_remote_lock;
+
+/*
+ * Lock to synchronize metadata updates. Also synchronize access to
+ * hr_primary_localcnt and hr_primary_remotecnt fields.
+ */
+static pthread_mutex_t metadata_lock;
+
+/*
+ * Maximum number of outstanding I/O requests.
+ */
+#define HAST_HIO_MAX 256
+/*
+ * Number of components. At this point there are only two components: local
+ * and remote, but in the future it might be possible to use multiple local
+ * and remote components.
+ */
+#define HAST_NCOMPONENTS 2
+
+#define ISCONNECTED(res, no) \
+ ((res)->hr_remotein != NULL && (res)->hr_remoteout != NULL)
+
+#define QUEUE_INSERT1(hio, name, ncomp) do { \
+ mtx_lock(&hio_##name##_list_lock[(ncomp)]); \
+ if (TAILQ_EMPTY(&hio_##name##_list[(ncomp)])) \
+ cv_broadcast(&hio_##name##_list_cond[(ncomp)]); \
+ TAILQ_INSERT_TAIL(&hio_##name##_list[(ncomp)], (hio), \
+ hio_next[(ncomp)]); \
+ hio_##name##_list_size[(ncomp)]++; \
+ mtx_unlock(&hio_##name##_list_lock[(ncomp)]); \
+} while (0)
+#define QUEUE_INSERT2(hio, name) do { \
+ mtx_lock(&hio_##name##_list_lock); \
+ if (TAILQ_EMPTY(&hio_##name##_list)) \
+ cv_broadcast(&hio_##name##_list_cond); \
+ TAILQ_INSERT_TAIL(&hio_##name##_list, (hio), hio_##name##_next);\
+ hio_##name##_list_size++; \
+ mtx_unlock(&hio_##name##_list_lock); \
+} while (0)
+#define QUEUE_TAKE1(hio, name, ncomp, timeout) do { \
+ bool _last; \
+ \
+ mtx_lock(&hio_##name##_list_lock[(ncomp)]); \
+ _last = false; \
+ while (((hio) = TAILQ_FIRST(&hio_##name##_list[(ncomp)])) == NULL && !_last) { \
+ cv_timedwait(&hio_##name##_list_cond[(ncomp)], \
+ &hio_##name##_list_lock[(ncomp)], (timeout)); \
+ if ((timeout) != 0) \
+ _last = true; \
+ } \
+ if (hio != NULL) { \
+ PJDLOG_ASSERT(hio_##name##_list_size[(ncomp)] != 0); \
+ hio_##name##_list_size[(ncomp)]--; \
+ TAILQ_REMOVE(&hio_##name##_list[(ncomp)], (hio), \
+ hio_next[(ncomp)]); \
+ } \
+ mtx_unlock(&hio_##name##_list_lock[(ncomp)]); \
+} while (0)
+#define QUEUE_TAKE2(hio, name) do { \
+ mtx_lock(&hio_##name##_list_lock); \
+ while (((hio) = TAILQ_FIRST(&hio_##name##_list)) == NULL) { \
+ cv_wait(&hio_##name##_list_cond, \
+ &hio_##name##_list_lock); \
+ } \
+ PJDLOG_ASSERT(hio_##name##_list_size != 0); \
+ hio_##name##_list_size--; \
+ TAILQ_REMOVE(&hio_##name##_list, (hio), hio_##name##_next); \
+ mtx_unlock(&hio_##name##_list_lock); \
+} while (0)
+
+#define ISFULLSYNC(hio) ((hio)->hio_replication == HAST_REPLICATION_FULLSYNC)
+#define ISMEMSYNC(hio) ((hio)->hio_replication == HAST_REPLICATION_MEMSYNC)
+#define ISASYNC(hio) ((hio)->hio_replication == HAST_REPLICATION_ASYNC)
+
+#define SYNCREQ(hio) do { \
+ (hio)->hio_ggio.gctl_unit = -1; \
+ (hio)->hio_ggio.gctl_seq = 1; \
+} while (0)
+#define ISSYNCREQ(hio) ((hio)->hio_ggio.gctl_unit == -1)
+#define SYNCREQDONE(hio) do { (hio)->hio_ggio.gctl_unit = -2; } while (0)
+#define ISSYNCREQDONE(hio) ((hio)->hio_ggio.gctl_unit == -2)
+
+#define ISMEMSYNCWRITE(hio) (ISMEMSYNC(hio) && \
+ (hio)->hio_ggio.gctl_cmd == BIO_WRITE && !ISSYNCREQ(hio))
+
+static struct hast_resource *gres;
+
+static pthread_mutex_t range_lock;
+static struct rangelocks *range_regular;
+static bool range_regular_wait;
+static pthread_cond_t range_regular_cond;
+static struct rangelocks *range_sync;
+static bool range_sync_wait;
+static pthread_cond_t range_sync_cond;
+static bool fullystarted;
+
+static void *ggate_recv_thread(void *arg);
+static void *local_send_thread(void *arg);
+static void *remote_send_thread(void *arg);
+static void *remote_recv_thread(void *arg);
+static void *ggate_send_thread(void *arg);
+static void *sync_thread(void *arg);
+static void *guard_thread(void *arg);
+
+static void
+output_status_aux(struct nv *nvout)
+{
+
+ nv_add_uint64(nvout, (uint64_t)hio_free_list_size,
+ "idle_queue_size");
+ nv_add_uint64(nvout, (uint64_t)hio_send_local_list_size,
+ "local_queue_size");
+ nv_add_uint64(nvout, (uint64_t)hio_send_remote_list_size,
+ "send_queue_size");
+ nv_add_uint64(nvout, (uint64_t)hio_recv_remote_list_size,
+ "recv_queue_size");
+ nv_add_uint64(nvout, (uint64_t)hio_done_list_size,
+ "done_queue_size");
+}
+
+static void
+cleanup(struct hast_resource *res)
+{
+ int rerrno;
+
+ /* Remember errno. */
+ rerrno = errno;
+
+ /* Destroy ggate provider if we created one. */
+ if (res->hr_ggateunit >= 0) {
+ struct g_gate_ctl_destroy ggiod;
+
+ bzero(&ggiod, sizeof(ggiod));
+ ggiod.gctl_version = G_GATE_VERSION;
+ ggiod.gctl_unit = res->hr_ggateunit;
+ ggiod.gctl_force = 1;
+ if (ioctl(res->hr_ggatefd, G_GATE_CMD_DESTROY, &ggiod) == -1) {
+ pjdlog_errno(LOG_WARNING,
+ "Unable to destroy hast/%s device",
+ res->hr_provname);
+ }
+ res->hr_ggateunit = -1;
+ }
+
+ /* Restore errno. */
+ errno = rerrno;
+}
+
+static __dead2 void
+primary_exit(int exitcode, const char *fmt, ...)
+{
+ va_list ap;
+
+ PJDLOG_ASSERT(exitcode != EX_OK);
+ va_start(ap, fmt);
+ pjdlogv_errno(LOG_ERR, fmt, ap);
+ va_end(ap);
+ cleanup(gres);
+ exit(exitcode);
+}
+
+static __dead2 void
+primary_exitx(int exitcode, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ pjdlogv(exitcode == EX_OK ? LOG_INFO : LOG_ERR, fmt, ap);
+ va_end(ap);
+ cleanup(gres);
+ exit(exitcode);
+}
+
+static int
+hast_activemap_flush(struct hast_resource *res) __unlocks(res->hr_amp_lock)
+{
+ const unsigned char *buf;
+ size_t size;
+ int ret;
+
+ mtx_lock(&res->hr_amp_diskmap_lock);
+ buf = activemap_bitmap(res->hr_amp, &size);
+ mtx_unlock(&res->hr_amp_lock);
+ PJDLOG_ASSERT(buf != NULL);
+ PJDLOG_ASSERT((size % res->hr_local_sectorsize) == 0);
+ ret = 0;
+ if (pwrite(res->hr_localfd, buf, size, METADATA_SIZE) !=
+ (ssize_t)size) {
+ pjdlog_errno(LOG_ERR, "Unable to flush activemap to disk");
+ res->hr_stat_activemap_write_error++;
+ ret = -1;
+ }
+ if (ret == 0 && res->hr_metaflush == 1 &&
+ g_flush(res->hr_localfd) == -1) {
+ if (errno == EOPNOTSUPP) {
+ pjdlog_warning("The %s provider doesn't support flushing write cache. Disabling it.",
+ res->hr_localpath);
+ res->hr_metaflush = 0;
+ } else {
+ pjdlog_errno(LOG_ERR,
+ "Unable to flush disk cache on activemap update");
+ res->hr_stat_activemap_flush_error++;
+ ret = -1;
+ }
+ }
+ mtx_unlock(&res->hr_amp_diskmap_lock);
+ return (ret);
+}
+
+static bool
+real_remote(const struct hast_resource *res)
+{
+
+ return (strcmp(res->hr_remoteaddr, "none") != 0);
+}
+
+static void
+init_environment(struct hast_resource *res __unused)
+{
+ struct hio *hio;
+ unsigned int ii, ncomps;
+
+ /*
+ * In the future it might be per-resource value.
+ */
+ ncomps = HAST_NCOMPONENTS;
+
+ /*
+ * Allocate memory needed by lists.
+ */
+ hio_send_list = malloc(sizeof(hio_send_list[0]) * ncomps);
+ if (hio_send_list == NULL) {
+ primary_exitx(EX_TEMPFAIL,
+ "Unable to allocate %zu bytes of memory for send lists.",
+ sizeof(hio_send_list[0]) * ncomps);
+ }
+ hio_send_list_size = malloc(sizeof(hio_send_list_size[0]) * ncomps);
+ if (hio_send_list_size == NULL) {
+ primary_exitx(EX_TEMPFAIL,
+ "Unable to allocate %zu bytes of memory for send list counters.",
+ sizeof(hio_send_list_size[0]) * ncomps);
+ }
+ hio_send_list_lock = malloc(sizeof(hio_send_list_lock[0]) * ncomps);
+ if (hio_send_list_lock == NULL) {
+ primary_exitx(EX_TEMPFAIL,
+ "Unable to allocate %zu bytes of memory for send list locks.",
+ sizeof(hio_send_list_lock[0]) * ncomps);
+ }
+ hio_send_list_cond = malloc(sizeof(hio_send_list_cond[0]) * ncomps);
+ if (hio_send_list_cond == NULL) {
+ primary_exitx(EX_TEMPFAIL,
+ "Unable to allocate %zu bytes of memory for send list condition variables.",
+ sizeof(hio_send_list_cond[0]) * ncomps);
+ }
+ hio_recv_list = malloc(sizeof(hio_recv_list[0]) * ncomps);
+ if (hio_recv_list == NULL) {
+ primary_exitx(EX_TEMPFAIL,
+ "Unable to allocate %zu bytes of memory for recv lists.",
+ sizeof(hio_recv_list[0]) * ncomps);
+ }
+ hio_recv_list_size = malloc(sizeof(hio_recv_list_size[0]) * ncomps);
+ if (hio_recv_list_size == NULL) {
+ primary_exitx(EX_TEMPFAIL,
+ "Unable to allocate %zu bytes of memory for recv list counters.",
+ sizeof(hio_recv_list_size[0]) * ncomps);
+ }
+ hio_recv_list_lock = malloc(sizeof(hio_recv_list_lock[0]) * ncomps);
+ if (hio_recv_list_lock == NULL) {
+ primary_exitx(EX_TEMPFAIL,
+ "Unable to allocate %zu bytes of memory for recv list locks.",
+ sizeof(hio_recv_list_lock[0]) * ncomps);
+ }
+ hio_recv_list_cond = malloc(sizeof(hio_recv_list_cond[0]) * ncomps);
+ if (hio_recv_list_cond == NULL) {
+ primary_exitx(EX_TEMPFAIL,
+ "Unable to allocate %zu bytes of memory for recv list condition variables.",
+ sizeof(hio_recv_list_cond[0]) * ncomps);
+ }
+ hio_remote_lock = malloc(sizeof(hio_remote_lock[0]) * ncomps);
+ if (hio_remote_lock == NULL) {
+ primary_exitx(EX_TEMPFAIL,
+ "Unable to allocate %zu bytes of memory for remote connections locks.",
+ sizeof(hio_remote_lock[0]) * ncomps);
+ }
+
+ /*
+ * Initialize lists, their counters, locks and condition variables.
+ */
+ TAILQ_INIT(&hio_free_list);
+ mtx_init(&hio_free_list_lock);
+ cv_init(&hio_free_list_cond);
+ for (ii = 0; ii < HAST_NCOMPONENTS; ii++) {
+ TAILQ_INIT(&hio_send_list[ii]);
+ hio_send_list_size[ii] = 0;
+ mtx_init(&hio_send_list_lock[ii]);
+ cv_init(&hio_send_list_cond[ii]);
+ TAILQ_INIT(&hio_recv_list[ii]);
+ hio_recv_list_size[ii] = 0;
+ mtx_init(&hio_recv_list_lock[ii]);
+ cv_init(&hio_recv_list_cond[ii]);
+ rw_init(&hio_remote_lock[ii]);
+ }
+ TAILQ_INIT(&hio_done_list);
+ mtx_init(&hio_done_list_lock);
+ cv_init(&hio_done_list_cond);
+ mtx_init(&metadata_lock);
+
+ /*
+ * Allocate requests pool and initialize requests.
+ */
+ for (ii = 0; ii < HAST_HIO_MAX; ii++) {
+ hio = malloc(sizeof(*hio));
+ if (hio == NULL) {
+ primary_exitx(EX_TEMPFAIL,
+ "Unable to allocate %zu bytes of memory for hio request.",
+ sizeof(*hio));
+ }
+ refcnt_init(&hio->hio_countdown, 0);
+ hio->hio_errors = malloc(sizeof(hio->hio_errors[0]) * ncomps);
+ if (hio->hio_errors == NULL) {
+ primary_exitx(EX_TEMPFAIL,
+ "Unable allocate %zu bytes of memory for hio errors.",
+ sizeof(hio->hio_errors[0]) * ncomps);
+ }
+ hio->hio_next = malloc(sizeof(hio->hio_next[0]) * ncomps);
+ if (hio->hio_next == NULL) {
+ primary_exitx(EX_TEMPFAIL,
+ "Unable allocate %zu bytes of memory for hio_next field.",
+ sizeof(hio->hio_next[0]) * ncomps);
+ }
+ hio->hio_ggio.gctl_version = G_GATE_VERSION;
+ hio->hio_ggio.gctl_data = malloc(MAXPHYS);
+ if (hio->hio_ggio.gctl_data == NULL) {
+ primary_exitx(EX_TEMPFAIL,
+ "Unable to allocate %zu bytes of memory for gctl_data.",
+ MAXPHYS);
+ }
+ hio->hio_ggio.gctl_length = MAXPHYS;
+ hio->hio_ggio.gctl_error = 0;
+ TAILQ_INSERT_HEAD(&hio_free_list, hio, hio_free_next);
+ hio_free_list_size++;
+ }
+}
+
+static bool
+init_resuid(struct hast_resource *res)
+{
+
+ mtx_lock(&metadata_lock);
+ if (res->hr_resuid != 0) {
+ mtx_unlock(&metadata_lock);
+ return (false);
+ } else {
+ /* Initialize unique resource identifier. */
+ arc4random_buf(&res->hr_resuid, sizeof(res->hr_resuid));
+ mtx_unlock(&metadata_lock);
+ if (metadata_write(res) == -1)
+ exit(EX_NOINPUT);
+ return (true);
+ }
+}
+
+static void
+init_local(struct hast_resource *res)
+{
+ unsigned char *buf;
+ size_t mapsize;
+
+ if (metadata_read(res, true) == -1)
+ exit(EX_NOINPUT);
+ mtx_init(&res->hr_amp_lock);
+ if (activemap_init(&res->hr_amp, res->hr_datasize, res->hr_extentsize,
+ res->hr_local_sectorsize, res->hr_keepdirty) == -1) {
+ primary_exit(EX_TEMPFAIL, "Unable to create activemap");
+ }
+ mtx_init(&range_lock);
+ cv_init(&range_regular_cond);
+ if (rangelock_init(&range_regular) == -1)
+ primary_exit(EX_TEMPFAIL, "Unable to create regular range lock");
+ cv_init(&range_sync_cond);
+ if (rangelock_init(&range_sync) == -1)
+ primary_exit(EX_TEMPFAIL, "Unable to create sync range lock");
+ mapsize = activemap_ondisk_size(res->hr_amp);
+ buf = calloc(1, mapsize);
+ if (buf == NULL) {
+ primary_exitx(EX_TEMPFAIL,
+ "Unable to allocate buffer for activemap.");
+ }
+ if (pread(res->hr_localfd, buf, mapsize, METADATA_SIZE) !=
+ (ssize_t)mapsize) {
+ primary_exit(EX_NOINPUT, "Unable to read activemap");
+ }
+ activemap_copyin(res->hr_amp, buf, mapsize);
+ free(buf);
+ if (res->hr_resuid != 0)
+ return;
+ /*
+ * We're using provider for the first time. Initialize local and remote
+ * counters. We don't initialize resuid here, as we want to do it just
+ * in time. The reason for this is that we want to inform secondary
+ * that there were no writes yet, so there is no need to synchronize
+ * anything.
+ */
+ res->hr_primary_localcnt = 0;
+ res->hr_primary_remotecnt = 0;
+ if (metadata_write(res) == -1)
+ exit(EX_NOINPUT);
+}
+
+static int
+primary_connect(struct hast_resource *res, struct proto_conn **connp)
+{
+ struct proto_conn *conn;
+ int16_t val;
+
+ val = 1;
+ if (proto_send(res->hr_conn, &val, sizeof(val)) == -1) {
+ primary_exit(EX_TEMPFAIL,
+ "Unable to send connection request to parent");
+ }
+ if (proto_recv(res->hr_conn, &val, sizeof(val)) == -1) {
+ primary_exit(EX_TEMPFAIL,
+ "Unable to receive reply to connection request from parent");
+ }
+ if (val != 0) {
+ errno = val;
+ pjdlog_errno(LOG_WARNING, "Unable to connect to %s",
+ res->hr_remoteaddr);
+ return (-1);
+ }
+ if (proto_connection_recv(res->hr_conn, true, &conn) == -1) {
+ primary_exit(EX_TEMPFAIL,
+ "Unable to receive connection from parent");
+ }
+ if (proto_connect_wait(conn, res->hr_timeout) == -1) {
+ pjdlog_errno(LOG_WARNING, "Unable to connect to %s",
+ res->hr_remoteaddr);
+ proto_close(conn);
+ return (-1);
+ }
+ /* Error in setting timeout is not critical, but why should it fail? */
+ if (proto_timeout(conn, res->hr_timeout) == -1)
+ pjdlog_errno(LOG_WARNING, "Unable to set connection timeout");
+
+ *connp = conn;
+
+ return (0);
+}
+
+/*
+ * Function instructs GEOM_GATE to handle reads directly from within the kernel.
+ */
+static void
+enable_direct_reads(struct hast_resource *res)
+{
+ struct g_gate_ctl_modify ggiomodify;
+
+ bzero(&ggiomodify, sizeof(ggiomodify));
+ ggiomodify.gctl_version = G_GATE_VERSION;
+ ggiomodify.gctl_unit = res->hr_ggateunit;
+ ggiomodify.gctl_modify = GG_MODIFY_READPROV | GG_MODIFY_READOFFSET;
+ strlcpy(ggiomodify.gctl_readprov, res->hr_localpath,
+ sizeof(ggiomodify.gctl_readprov));
+ ggiomodify.gctl_readoffset = res->hr_localoff;
+ if (ioctl(res->hr_ggatefd, G_GATE_CMD_MODIFY, &ggiomodify) == 0)
+ pjdlog_debug(1, "Direct reads enabled.");
+ else
+ pjdlog_errno(LOG_WARNING, "Failed to enable direct reads");
+}
+
+static int
+init_remote(struct hast_resource *res, struct proto_conn **inp,
+ struct proto_conn **outp)
+{
+ struct proto_conn *in, *out;
+ struct nv *nvout, *nvin;
+ const unsigned char *token;
+ unsigned char *map;
+ const char *errmsg;
+ int32_t extentsize;
+ int64_t datasize;
+ uint32_t mapsize;
+ uint8_t version;
+ size_t size;
+ int error;
+
+ PJDLOG_ASSERT((inp == NULL && outp == NULL) || (inp != NULL && outp != NULL));
+ PJDLOG_ASSERT(real_remote(res));
+
+ in = out = NULL;
+ errmsg = NULL;
+
+ if (primary_connect(res, &out) == -1)
+ return (ECONNREFUSED);
+
+ error = ECONNABORTED;
+
+ /*
+ * First handshake step.
+ * Setup outgoing connection with remote node.
+ */
+ nvout = nv_alloc();
+ nv_add_string(nvout, res->hr_name, "resource");
+ nv_add_uint8(nvout, HAST_PROTO_VERSION, "version");
+ if (nv_error(nvout) != 0) {
+ pjdlog_common(LOG_WARNING, 0, nv_error(nvout),
+ "Unable to allocate header for connection with %s",
+ res->hr_remoteaddr);
+ nv_free(nvout);
+ goto close;
+ }
+ if (hast_proto_send(res, out, nvout, NULL, 0) == -1) {
+ pjdlog_errno(LOG_WARNING,
+ "Unable to send handshake header to %s",
+ res->hr_remoteaddr);
+ nv_free(nvout);
+ goto close;
+ }
+ nv_free(nvout);
+ if (hast_proto_recv_hdr(out, &nvin) == -1) {
+ pjdlog_errno(LOG_WARNING,
+ "Unable to receive handshake header from %s",
+ res->hr_remoteaddr);
+ goto close;
+ }
+ errmsg = nv_get_string(nvin, "errmsg");
+ if (errmsg != NULL) {
+ pjdlog_warning("%s", errmsg);
+ if (nv_exists(nvin, "wait"))
+ error = EBUSY;
+ nv_free(nvin);
+ goto close;
+ }
+ version = nv_get_uint8(nvin, "version");
+ if (version == 0) {
+ /*
+ * If no version is sent, it means this is protocol version 1.
+ */
+ version = 1;
+ }
+ if (version > HAST_PROTO_VERSION) {
+ pjdlog_warning("Invalid version received (%hhu).", version);
+ nv_free(nvin);
+ goto close;
+ }
+ res->hr_version = version;
+ pjdlog_debug(1, "Negotiated protocol version %d.", res->hr_version);
+ token = nv_get_uint8_array(nvin, &size, "token");
+ if (token == NULL) {
+ pjdlog_warning("Handshake header from %s has no 'token' field.",
+ res->hr_remoteaddr);
+ nv_free(nvin);
+ goto close;
+ }
+ if (size != sizeof(res->hr_token)) {
+ pjdlog_warning("Handshake header from %s contains 'token' of wrong size (got %zu, expected %zu).",
+ res->hr_remoteaddr, size, sizeof(res->hr_token));
+ nv_free(nvin);
+ goto close;
+ }
+ bcopy(token, res->hr_token, sizeof(res->hr_token));
+ nv_free(nvin);
+
+ /*
+ * Second handshake step.
+ * Setup incoming connection with remote node.
+ */
+ if (primary_connect(res, &in) == -1)
+ goto close;
+
+ nvout = nv_alloc();
+ nv_add_string(nvout, res->hr_name, "resource");
+ nv_add_uint8_array(nvout, res->hr_token, sizeof(res->hr_token),
+ "token");
+ if (res->hr_resuid == 0) {
+ /*
+ * The resuid field was not yet initialized.
+ * Because we do synchronization inside init_resuid(), it is
+ * possible that someone already initialized it, the function
+ * will return false then, but if we successfully initialized
+ * it, we will get true. True means that there were no writes
+ * to this resource yet and we want to inform secondary that
+ * synchronization is not needed by sending "virgin" argument.
+ */
+ if (init_resuid(res))
+ nv_add_int8(nvout, 1, "virgin");
+ }
+ nv_add_uint64(nvout, res->hr_resuid, "resuid");
+ nv_add_uint64(nvout, res->hr_primary_localcnt, "localcnt");
+ nv_add_uint64(nvout, res->hr_primary_remotecnt, "remotecnt");
+ if (nv_error(nvout) != 0) {
+ pjdlog_common(LOG_WARNING, 0, nv_error(nvout),
+ "Unable to allocate header for connection with %s",
+ res->hr_remoteaddr);
+ nv_free(nvout);
+ goto close;
+ }
+ if (hast_proto_send(res, in, nvout, NULL, 0) == -1) {
+ pjdlog_errno(LOG_WARNING,
+ "Unable to send handshake header to %s",
+ res->hr_remoteaddr);
+ nv_free(nvout);
+ goto close;
+ }
+ nv_free(nvout);
+ if (hast_proto_recv_hdr(out, &nvin) == -1) {
+ pjdlog_errno(LOG_WARNING,
+ "Unable to receive handshake header from %s",
+ res->hr_remoteaddr);
+ goto close;
+ }
+ errmsg = nv_get_string(nvin, "errmsg");
+ if (errmsg != NULL) {
+ pjdlog_warning("%s", errmsg);
+ nv_free(nvin);
+ goto close;
+ }
+ datasize = nv_get_int64(nvin, "datasize");
+ if (datasize != res->hr_datasize) {
+ pjdlog_warning("Data size differs between nodes (local=%jd, remote=%jd).",
+ (intmax_t)res->hr_datasize, (intmax_t)datasize);
+ nv_free(nvin);
+ goto close;
+ }
+ extentsize = nv_get_int32(nvin, "extentsize");
+ if (extentsize != res->hr_extentsize) {
+ pjdlog_warning("Extent size differs between nodes (local=%zd, remote=%zd).",
+ (ssize_t)res->hr_extentsize, (ssize_t)extentsize);
+ nv_free(nvin);
+ goto close;
+ }
+ res->hr_secondary_localcnt = nv_get_uint64(nvin, "localcnt");
+ res->hr_secondary_remotecnt = nv_get_uint64(nvin, "remotecnt");
+ res->hr_syncsrc = nv_get_uint8(nvin, "syncsrc");
+ if (res->hr_syncsrc == HAST_SYNCSRC_PRIMARY)
+ enable_direct_reads(res);
+ if (nv_exists(nvin, "virgin")) {
+ /*
+ * Secondary was reinitialized, bump localcnt if it is 0 as
+ * only we have the data.
+ */
+ PJDLOG_ASSERT(res->hr_syncsrc == HAST_SYNCSRC_PRIMARY);
+ PJDLOG_ASSERT(res->hr_secondary_localcnt == 0);
+
+ if (res->hr_primary_localcnt == 0) {
+ PJDLOG_ASSERT(res->hr_secondary_remotecnt == 0);
+
+ mtx_lock(&metadata_lock);
+ res->hr_primary_localcnt++;
+ pjdlog_debug(1, "Increasing localcnt to %ju.",
+ (uintmax_t)res->hr_primary_localcnt);
+ (void)metadata_write(res);
+ mtx_unlock(&metadata_lock);
+ }
+ }
+ map = NULL;
+ mapsize = nv_get_uint32(nvin, "mapsize");
+ if (mapsize > 0) {
+ map = malloc(mapsize);
+ if (map == NULL) {
+ pjdlog_error("Unable to allocate memory for remote activemap (mapsize=%ju).",
+ (uintmax_t)mapsize);
+ nv_free(nvin);
+ goto close;
+ }
+ /*
+ * Remote node have some dirty extents on its own, lets
+ * download its activemap.
+ */
+ if (hast_proto_recv_data(res, out, nvin, map,
+ mapsize) == -1) {
+ pjdlog_errno(LOG_ERR,
+ "Unable to receive remote activemap");
+ nv_free(nvin);
+ free(map);
+ goto close;
+ }
+ mtx_lock(&res->hr_amp_lock);
+ /*
+ * Merge local and remote bitmaps.
+ */
+ activemap_merge(res->hr_amp, map, mapsize);
+ free(map);
+ /*
+ * Now that we merged bitmaps from both nodes, flush it to the
+ * disk before we start to synchronize.
+ */
+ (void)hast_activemap_flush(res);
+ }
+ nv_free(nvin);
+#ifdef notyet
+ /* Setup directions. */
+ if (proto_send(out, NULL, 0) == -1)
+ pjdlog_errno(LOG_WARNING, "Unable to set connection direction");
+ if (proto_recv(in, NULL, 0) == -1)
+ pjdlog_errno(LOG_WARNING, "Unable to set connection direction");
+#endif
+ pjdlog_info("Connected to %s.", res->hr_remoteaddr);
+ if (res->hr_original_replication == HAST_REPLICATION_MEMSYNC &&
+ res->hr_version < 2) {
+ pjdlog_warning("The 'memsync' replication mode is not supported by the remote node, falling back to 'fullsync' mode.");
+ res->hr_replication = HAST_REPLICATION_FULLSYNC;
+ } else if (res->hr_replication != res->hr_original_replication) {
+ /*
+ * This is in case hastd disconnected and was upgraded.
+ */
+ res->hr_replication = res->hr_original_replication;
+ }
+ if (inp != NULL && outp != NULL) {
+ *inp = in;
+ *outp = out;
+ } else {
+ res->hr_remotein = in;
+ res->hr_remoteout = out;
+ }
+ event_send(res, EVENT_CONNECT);
+ return (0);
+close:
+ if (errmsg != NULL && strcmp(errmsg, "Split-brain condition!") == 0)
+ event_send(res, EVENT_SPLITBRAIN);
+ proto_close(out);
+ if (in != NULL)
+ proto_close(in);
+ return (error);
+}
+
+static void
+sync_start(void)
+{
+
+ mtx_lock(&sync_lock);
+ sync_inprogress = true;
+ mtx_unlock(&sync_lock);
+ cv_signal(&sync_cond);
+}
+
+static void
+sync_stop(void)
+{
+
+ mtx_lock(&sync_lock);
+ if (sync_inprogress)
+ sync_inprogress = false;
+ mtx_unlock(&sync_lock);
+}
+
+static void
+init_ggate(struct hast_resource *res)
+{
+ struct g_gate_ctl_create ggiocreate;
+ struct g_gate_ctl_cancel ggiocancel;
+
+ /*
+ * We communicate with ggate via /dev/ggctl. Open it.
+ */
+ res->hr_ggatefd = open("/dev/" G_GATE_CTL_NAME, O_RDWR);
+ if (res->hr_ggatefd == -1)
+ primary_exit(EX_OSFILE, "Unable to open /dev/" G_GATE_CTL_NAME);
+ /*
+ * Create provider before trying to connect, as connection failure
+ * is not critical, but may take some time.
+ */
+ bzero(&ggiocreate, sizeof(ggiocreate));
+ ggiocreate.gctl_version = G_GATE_VERSION;
+ ggiocreate.gctl_mediasize = res->hr_datasize;
+ ggiocreate.gctl_sectorsize = res->hr_local_sectorsize;
+ ggiocreate.gctl_flags = 0;
+ ggiocreate.gctl_maxcount = 0;
+ ggiocreate.gctl_timeout = 0;
+ ggiocreate.gctl_unit = G_GATE_NAME_GIVEN;
+ snprintf(ggiocreate.gctl_name, sizeof(ggiocreate.gctl_name), "hast/%s",
+ res->hr_provname);
+ if (ioctl(res->hr_ggatefd, G_GATE_CMD_CREATE, &ggiocreate) == 0) {
+ pjdlog_info("Device hast/%s created.", res->hr_provname);
+ res->hr_ggateunit = ggiocreate.gctl_unit;
+ return;
+ }
+ if (errno != EEXIST) {
+ primary_exit(EX_OSERR, "Unable to create hast/%s device",
+ res->hr_provname);
+ }
+ pjdlog_debug(1,
+ "Device hast/%s already exists, we will try to take it over.",
+ res->hr_provname);
+ /*
+ * If we received EEXIST, we assume that the process who created the
+ * provider died and didn't clean up. In that case we will start from
+ * where he left of.
+ */
+ bzero(&ggiocancel, sizeof(ggiocancel));
+ ggiocancel.gctl_version = G_GATE_VERSION;
+ ggiocancel.gctl_unit = G_GATE_NAME_GIVEN;
+ snprintf(ggiocancel.gctl_name, sizeof(ggiocancel.gctl_name), "hast/%s",
+ res->hr_provname);
+ if (ioctl(res->hr_ggatefd, G_GATE_CMD_CANCEL, &ggiocancel) == 0) {
+ pjdlog_info("Device hast/%s recovered.", res->hr_provname);
+ res->hr_ggateunit = ggiocancel.gctl_unit;
+ return;
+ }
+ primary_exit(EX_OSERR, "Unable to take over hast/%s device",
+ res->hr_provname);
+}
+
+void
+hastd_primary(struct hast_resource *res)
+{
+ pthread_t td;
+ pid_t pid;
+ int error, mode, debuglevel;
+
+ /*
+ * Create communication channel for sending control commands from
+ * parent to child.
+ */
+ if (proto_client(NULL, "socketpair://", &res->hr_ctrl) == -1) {
+ /* TODO: There's no need for this to be fatal error. */
+ KEEP_ERRNO((void)pidfile_remove(pfh));
+ pjdlog_exit(EX_OSERR,
+ "Unable to create control sockets between parent and child");
+ }
+ /*
+ * Create communication channel for sending events from child to parent.
+ */
+ if (proto_client(NULL, "socketpair://", &res->hr_event) == -1) {
+ /* TODO: There's no need for this to be fatal error. */
+ KEEP_ERRNO((void)pidfile_remove(pfh));
+ pjdlog_exit(EX_OSERR,
+ "Unable to create event sockets between child and parent");
+ }
+ /*
+ * Create communication channel for sending connection requests from
+ * child to parent.
+ */
+ if (proto_client(NULL, "socketpair://", &res->hr_conn) == -1) {
+ /* TODO: There's no need for this to be fatal error. */
+ KEEP_ERRNO((void)pidfile_remove(pfh));
+ pjdlog_exit(EX_OSERR,
+ "Unable to create connection sockets between child and parent");
+ }
+
+ pid = fork();
+ if (pid == -1) {
+ /* TODO: There's no need for this to be fatal error. */
+ KEEP_ERRNO((void)pidfile_remove(pfh));
+ pjdlog_exit(EX_TEMPFAIL, "Unable to fork");
+ }
+
+ if (pid > 0) {
+ /* This is parent. */
+ /* Declare that we are receiver. */
+ proto_recv(res->hr_event, NULL, 0);
+ proto_recv(res->hr_conn, NULL, 0);
+ /* Declare that we are sender. */
+ proto_send(res->hr_ctrl, NULL, 0);
+ res->hr_workerpid = pid;
+ return;
+ }
+
+ gres = res;
+ res->output_status_aux = output_status_aux;
+ mode = pjdlog_mode_get();
+ debuglevel = pjdlog_debug_get();
+
+ /* Declare that we are sender. */
+ proto_send(res->hr_event, NULL, 0);
+ proto_send(res->hr_conn, NULL, 0);
+ /* Declare that we are receiver. */
+ proto_recv(res->hr_ctrl, NULL, 0);
+ descriptors_cleanup(res);
+
+ descriptors_assert(res, mode);
+
+ pjdlog_init(mode);
+ pjdlog_debug_set(debuglevel);
+ pjdlog_prefix_set("[%s] (%s) ", res->hr_name, role2str(res->hr_role));
+ setproctitle("%s (%s)", res->hr_name, role2str(res->hr_role));
+
+ init_local(res);
+ init_ggate(res);
+ init_environment(res);
+
+ if (drop_privs(res) != 0) {
+ cleanup(res);
+ exit(EX_CONFIG);
+ }
+ pjdlog_info("Privileges successfully dropped.");
+
+ /*
+ * Create the guard thread first, so we can handle signals from the
+ * very beginning.
+ */
+ error = pthread_create(&td, NULL, guard_thread, res);
+ PJDLOG_ASSERT(error == 0);
+ /*
+ * Create the control thread before sending any event to the parent,
+ * as we can deadlock when parent sends control request to worker,
+ * but worker has no control thread started yet, so parent waits.
+ * In the meantime worker sends an event to the parent, but parent
+ * is unable to handle the event, because it waits for control
+ * request response.
+ */
+ error = pthread_create(&td, NULL, ctrl_thread, res);
+ PJDLOG_ASSERT(error == 0);
+ if (real_remote(res)) {
+ error = init_remote(res, NULL, NULL);
+ if (error == 0) {
+ sync_start();
+ } else if (error == EBUSY) {
+ time_t start = time(NULL);
+
+ pjdlog_warning("Waiting for remote node to become %s for %ds.",
+ role2str(HAST_ROLE_SECONDARY),
+ res->hr_timeout);
+ for (;;) {
+ sleep(1);
+ error = init_remote(res, NULL, NULL);
+ if (error != EBUSY)
+ break;
+ if (time(NULL) > start + res->hr_timeout)
+ break;
+ }
+ if (error == EBUSY) {
+ pjdlog_warning("Remote node is still %s, starting anyway.",
+ role2str(HAST_ROLE_PRIMARY));
+ }
+ }
+ }
+ error = pthread_create(&td, NULL, ggate_recv_thread, res);
+ PJDLOG_ASSERT(error == 0);
+ error = pthread_create(&td, NULL, local_send_thread, res);
+ PJDLOG_ASSERT(error == 0);
+ error = pthread_create(&td, NULL, remote_send_thread, res);
+ PJDLOG_ASSERT(error == 0);
+ error = pthread_create(&td, NULL, remote_recv_thread, res);
+ PJDLOG_ASSERT(error == 0);
+ error = pthread_create(&td, NULL, ggate_send_thread, res);
+ PJDLOG_ASSERT(error == 0);
+ fullystarted = true;
+ (void)sync_thread(res);
+}
+
+static void
+reqlog(int loglevel, int debuglevel, struct g_gate_ctl_io *ggio,
+ const char *fmt, ...)
+{
+ char msg[1024];
+ va_list ap;
+
+ va_start(ap, fmt);
+ (void)vsnprintf(msg, sizeof(msg), fmt, ap);
+ va_end(ap);
+ switch (ggio->gctl_cmd) {
+ case BIO_READ:
+ (void)snprlcat(msg, sizeof(msg), "READ(%ju, %ju).",
+ (uintmax_t)ggio->gctl_offset, (uintmax_t)ggio->gctl_length);
+ break;
+ case BIO_DELETE:
+ (void)snprlcat(msg, sizeof(msg), "DELETE(%ju, %ju).",
+ (uintmax_t)ggio->gctl_offset, (uintmax_t)ggio->gctl_length);
+ break;
+ case BIO_FLUSH:
+ (void)snprlcat(msg, sizeof(msg), "FLUSH.");
+ break;
+ case BIO_WRITE:
+ (void)snprlcat(msg, sizeof(msg), "WRITE(%ju, %ju).",
+ (uintmax_t)ggio->gctl_offset, (uintmax_t)ggio->gctl_length);
+ break;
+ default:
+ (void)snprlcat(msg, sizeof(msg), "UNKNOWN(%u).",
+ (unsigned int)ggio->gctl_cmd);
+ break;
+ }
+ pjdlog_common(loglevel, debuglevel, -1, "%s", msg);
+}
+
+static void
+remote_close(struct hast_resource *res, int ncomp)
+{
+
+ rw_wlock(&hio_remote_lock[ncomp]);
+ /*
+ * Check for a race between dropping rlock and acquiring wlock -
+ * another thread can close connection in-between.
+ */
+ if (!ISCONNECTED(res, ncomp)) {
+ PJDLOG_ASSERT(res->hr_remotein == NULL);
+ PJDLOG_ASSERT(res->hr_remoteout == NULL);
+ rw_unlock(&hio_remote_lock[ncomp]);
+ return;
+ }
+
+ PJDLOG_ASSERT(res->hr_remotein != NULL);
+ PJDLOG_ASSERT(res->hr_remoteout != NULL);
+
+ pjdlog_debug(2, "Closing incoming connection to %s.",
+ res->hr_remoteaddr);
+ proto_close(res->hr_remotein);
+ res->hr_remotein = NULL;
+ pjdlog_debug(2, "Closing outgoing connection to %s.",
+ res->hr_remoteaddr);
+ proto_close(res->hr_remoteout);
+ res->hr_remoteout = NULL;
+
+ rw_unlock(&hio_remote_lock[ncomp]);
+
+ pjdlog_warning("Disconnected from %s.", res->hr_remoteaddr);
+
+ /*
+ * Stop synchronization if in-progress.
+ */
+ sync_stop();
+
+ event_send(res, EVENT_DISCONNECT);
+}
+
+/*
+ * Acknowledge write completion to the kernel, but don't update activemap yet.
+ */
+static void
+write_complete(struct hast_resource *res, struct hio *hio)
+{
+ struct g_gate_ctl_io *ggio;
+ unsigned int ncomp;
+
+ PJDLOG_ASSERT(!hio->hio_done);
+
+ ggio = &hio->hio_ggio;
+ PJDLOG_ASSERT(ggio->gctl_cmd == BIO_WRITE);
+
+ /*
+ * Bump local count if this is first write after
+ * connection failure with remote node.
+ */
+ ncomp = 1;
+ rw_rlock(&hio_remote_lock[ncomp]);
+ if (!ISCONNECTED(res, ncomp)) {
+ mtx_lock(&metadata_lock);
+ if (res->hr_primary_localcnt == res->hr_secondary_remotecnt) {
+ res->hr_primary_localcnt++;
+ pjdlog_debug(1, "Increasing localcnt to %ju.",
+ (uintmax_t)res->hr_primary_localcnt);
+ (void)metadata_write(res);
+ }
+ mtx_unlock(&metadata_lock);
+ }
+ rw_unlock(&hio_remote_lock[ncomp]);
+ if (ioctl(res->hr_ggatefd, G_GATE_CMD_DONE, ggio) == -1)
+ primary_exit(EX_OSERR, "G_GATE_CMD_DONE failed");
+ hio->hio_done = true;
+}
+
+/*
+ * Thread receives ggate I/O requests from the kernel and passes them to
+ * appropriate threads:
+ * WRITE - always goes to both local_send and remote_send threads
+ * READ (when the block is up-to-date on local component) -
+ * only local_send thread
+ * READ (when the block isn't up-to-date on local component) -
+ * only remote_send thread
+ * DELETE - always goes to both local_send and remote_send threads
+ * FLUSH - always goes to both local_send and remote_send threads
+ */
+static void *
+ggate_recv_thread(void *arg)
+{
+ struct hast_resource *res = arg;
+ struct g_gate_ctl_io *ggio;
+ struct hio *hio;
+ unsigned int ii, ncomp, ncomps;
+ int error;
+
+ for (;;) {
+ pjdlog_debug(2, "ggate_recv: Taking free request.");
+ QUEUE_TAKE2(hio, free);
+ pjdlog_debug(2, "ggate_recv: (%p) Got free request.", hio);
+ ggio = &hio->hio_ggio;
+ ggio->gctl_unit = res->hr_ggateunit;
+ ggio->gctl_length = MAXPHYS;
+ ggio->gctl_error = 0;
+ hio->hio_done = false;
+ hio->hio_replication = res->hr_replication;
+ pjdlog_debug(2,
+ "ggate_recv: (%p) Waiting for request from the kernel.",
+ hio);
+ if (ioctl(res->hr_ggatefd, G_GATE_CMD_START, ggio) == -1) {
+ if (sigexit_received)
+ pthread_exit(NULL);
+ primary_exit(EX_OSERR, "G_GATE_CMD_START failed");
+ }
+ error = ggio->gctl_error;
+ switch (error) {
+ case 0:
+ break;
+ case ECANCELED:
+ /* Exit gracefully. */
+ if (!sigexit_received) {
+ pjdlog_debug(2,
+ "ggate_recv: (%p) Received cancel from the kernel.",
+ hio);
+ pjdlog_info("Received cancel from the kernel, exiting.");
+ }
+ pthread_exit(NULL);
+ case ENOMEM:
+ /*
+ * Buffer too small? Impossible, we allocate MAXPHYS
+ * bytes - request can't be bigger than that.
+ */
+ /* FALLTHROUGH */
+ case ENXIO:
+ default:
+ primary_exitx(EX_OSERR, "G_GATE_CMD_START failed: %s.",
+ strerror(error));
+ }
+
+ ncomp = 0;
+ ncomps = HAST_NCOMPONENTS;
+
+ for (ii = 0; ii < ncomps; ii++)
+ hio->hio_errors[ii] = EINVAL;
+ reqlog(LOG_DEBUG, 2, ggio,
+ "ggate_recv: (%p) Request received from the kernel: ",
+ hio);
+
+ /*
+ * Inform all components about new write request.
+ * For read request prefer local component unless the given
+ * range is out-of-date, then use remote component.
+ */
+ switch (ggio->gctl_cmd) {
+ case BIO_READ:
+ res->hr_stat_read++;
+ ncomps = 1;
+ mtx_lock(&metadata_lock);
+ if (res->hr_syncsrc == HAST_SYNCSRC_UNDEF ||
+ res->hr_syncsrc == HAST_SYNCSRC_PRIMARY) {
+ /*
+ * This range is up-to-date on local component,
+ * so handle request locally.
+ */
+ /* Local component is 0 for now. */
+ ncomp = 0;
+ } else /* if (res->hr_syncsrc ==
+ HAST_SYNCSRC_SECONDARY) */ {
+ PJDLOG_ASSERT(res->hr_syncsrc ==
+ HAST_SYNCSRC_SECONDARY);
+ /*
+ * This range is out-of-date on local component,
+ * so send request to the remote node.
+ */
+ /* Remote component is 1 for now. */
+ ncomp = 1;
+ }
+ mtx_unlock(&metadata_lock);
+ break;
+ case BIO_WRITE:
+ res->hr_stat_write++;
+ if (res->hr_resuid == 0 &&
+ res->hr_primary_localcnt == 0) {
+ /* This is first write. */
+ res->hr_primary_localcnt = 1;
+ }
+ for (;;) {
+ mtx_lock(&range_lock);
+ if (rangelock_islocked(range_sync,
+ ggio->gctl_offset, ggio->gctl_length)) {
+ pjdlog_debug(2,
+ "regular: Range offset=%jd length=%zu locked.",
+ (intmax_t)ggio->gctl_offset,
+ (size_t)ggio->gctl_length);
+ range_regular_wait = true;
+ cv_wait(&range_regular_cond, &range_lock);
+ range_regular_wait = false;
+ mtx_unlock(&range_lock);
+ continue;
+ }
+ if (rangelock_add(range_regular,
+ ggio->gctl_offset, ggio->gctl_length) == -1) {
+ mtx_unlock(&range_lock);
+ pjdlog_debug(2,
+ "regular: Range offset=%jd length=%zu is already locked, waiting.",
+ (intmax_t)ggio->gctl_offset,
+ (size_t)ggio->gctl_length);
+ sleep(1);
+ continue;
+ }
+ mtx_unlock(&range_lock);
+ break;
+ }
+ mtx_lock(&res->hr_amp_lock);
+ if (activemap_write_start(res->hr_amp,
+ ggio->gctl_offset, ggio->gctl_length)) {
+ res->hr_stat_activemap_update++;
+ (void)hast_activemap_flush(res);
+ } else {
+ mtx_unlock(&res->hr_amp_lock);
+ }
+ if (ISMEMSYNC(hio)) {
+ hio->hio_memsyncacked = false;
+ refcnt_init(&hio->hio_writecount, ncomps);
+ }
+ break;
+ case BIO_DELETE:
+ res->hr_stat_delete++;
+ break;
+ case BIO_FLUSH:
+ res->hr_stat_flush++;
+ break;
+ }
+ pjdlog_debug(2,
+ "ggate_recv: (%p) Moving request to the send queues.", hio);
+ refcnt_init(&hio->hio_countdown, ncomps);
+ for (ii = ncomp; ii < ncomps; ii++)
+ QUEUE_INSERT1(hio, send, ii);
+ }
+ /* NOTREACHED */
+ return (NULL);
+}
+
+/*
+ * Thread reads from or writes to local component.
+ * If local read fails, it redirects it to remote_send thread.
+ */
+static void *
+local_send_thread(void *arg)
+{
+ struct hast_resource *res = arg;
+ struct g_gate_ctl_io *ggio;
+ struct hio *hio;
+ unsigned int ncomp, rncomp;
+ ssize_t ret;
+
+ /* Local component is 0 for now. */
+ ncomp = 0;
+ /* Remote component is 1 for now. */
+ rncomp = 1;
+
+ for (;;) {
+ pjdlog_debug(2, "local_send: Taking request.");
+ QUEUE_TAKE1(hio, send, ncomp, 0);
+ pjdlog_debug(2, "local_send: (%p) Got request.", hio);
+ ggio = &hio->hio_ggio;
+ switch (ggio->gctl_cmd) {
+ case BIO_READ:
+ ret = pread(res->hr_localfd, ggio->gctl_data,
+ ggio->gctl_length,
+ ggio->gctl_offset + res->hr_localoff);
+ if (ret == ggio->gctl_length)
+ hio->hio_errors[ncomp] = 0;
+ else if (!ISSYNCREQ(hio)) {
+ /*
+ * If READ failed, try to read from remote node.
+ */
+ if (ret == -1) {
+ reqlog(LOG_WARNING, 0, ggio,
+ "Local request failed (%s), trying remote node. ",
+ strerror(errno));
+ } else if (ret != ggio->gctl_length) {
+ reqlog(LOG_WARNING, 0, ggio,
+ "Local request failed (%zd != %jd), trying remote node. ",
+ ret, (intmax_t)ggio->gctl_length);
+ }
+ QUEUE_INSERT1(hio, send, rncomp);
+ continue;
+ }
+ break;
+ case BIO_WRITE:
+ ret = pwrite(res->hr_localfd, ggio->gctl_data,
+ ggio->gctl_length,
+ ggio->gctl_offset + res->hr_localoff);
+ if (ret == -1) {
+ hio->hio_errors[ncomp] = errno;
+ reqlog(LOG_WARNING, 0, ggio,
+ "Local request failed (%s): ",
+ strerror(errno));
+ } else if (ret != ggio->gctl_length) {
+ hio->hio_errors[ncomp] = EIO;
+ reqlog(LOG_WARNING, 0, ggio,
+ "Local request failed (%zd != %jd): ",
+ ret, (intmax_t)ggio->gctl_length);
+ } else {
+ hio->hio_errors[ncomp] = 0;
+ if (ISASYNC(hio)) {
+ ggio->gctl_error = 0;
+ write_complete(res, hio);
+ }
+ }
+ break;
+ case BIO_DELETE:
+ ret = g_delete(res->hr_localfd,
+ ggio->gctl_offset + res->hr_localoff,
+ ggio->gctl_length);
+ if (ret == -1) {
+ hio->hio_errors[ncomp] = errno;
+ reqlog(LOG_WARNING, 0, ggio,
+ "Local request failed (%s): ",
+ strerror(errno));
+ } else {
+ hio->hio_errors[ncomp] = 0;
+ }
+ break;
+ case BIO_FLUSH:
+ if (!res->hr_localflush) {
+ ret = -1;
+ errno = EOPNOTSUPP;
+ break;
+ }
+ ret = g_flush(res->hr_localfd);
+ if (ret == -1) {
+ if (errno == EOPNOTSUPP)
+ res->hr_localflush = false;
+ hio->hio_errors[ncomp] = errno;
+ reqlog(LOG_WARNING, 0, ggio,
+ "Local request failed (%s): ",
+ strerror(errno));
+ } else {
+ hio->hio_errors[ncomp] = 0;
+ }
+ break;
+ }
+ if (ISMEMSYNCWRITE(hio)) {
+ if (refcnt_release(&hio->hio_writecount) == 0) {
+ write_complete(res, hio);
+ }
+ }
+ if (refcnt_release(&hio->hio_countdown) > 0)
+ continue;
+ if (ISSYNCREQ(hio)) {
+ mtx_lock(&sync_lock);
+ SYNCREQDONE(hio);
+ mtx_unlock(&sync_lock);
+ cv_signal(&sync_cond);
+ } else {
+ pjdlog_debug(2,
+ "local_send: (%p) Moving request to the done queue.",
+ hio);
+ QUEUE_INSERT2(hio, done);
+ }
+ }
+ /* NOTREACHED */
+ return (NULL);
+}
+
+static void
+keepalive_send(struct hast_resource *res, unsigned int ncomp)
+{
+ struct nv *nv;
+
+ rw_rlock(&hio_remote_lock[ncomp]);
+
+ if (!ISCONNECTED(res, ncomp)) {
+ rw_unlock(&hio_remote_lock[ncomp]);
+ return;
+ }
+
+ PJDLOG_ASSERT(res->hr_remotein != NULL);
+ PJDLOG_ASSERT(res->hr_remoteout != NULL);
+
+ nv = nv_alloc();
+ nv_add_uint8(nv, HIO_KEEPALIVE, "cmd");
+ if (nv_error(nv) != 0) {
+ rw_unlock(&hio_remote_lock[ncomp]);
+ nv_free(nv);
+ pjdlog_debug(1,
+ "keepalive_send: Unable to prepare header to send.");
+ return;
+ }
+ if (hast_proto_send(res, res->hr_remoteout, nv, NULL, 0) == -1) {
+ rw_unlock(&hio_remote_lock[ncomp]);
+ pjdlog_common(LOG_DEBUG, 1, errno,
+ "keepalive_send: Unable to send request");
+ nv_free(nv);
+ remote_close(res, ncomp);
+ return;
+ }
+
+ rw_unlock(&hio_remote_lock[ncomp]);
+ nv_free(nv);
+ pjdlog_debug(2, "keepalive_send: Request sent.");
+}
+
+/*
+ * Thread sends request to secondary node.
+ */
+static void *
+remote_send_thread(void *arg)
+{
+ struct hast_resource *res = arg;
+ struct g_gate_ctl_io *ggio;
+ time_t lastcheck, now;
+ struct hio *hio;
+ struct nv *nv;
+ unsigned int ncomp;
+ bool wakeup;
+ uint64_t offset, length;
+ uint8_t cmd;
+ void *data;
+
+ /* Remote component is 1 for now. */
+ ncomp = 1;
+ lastcheck = time(NULL);
+
+ for (;;) {
+ pjdlog_debug(2, "remote_send: Taking request.");
+ QUEUE_TAKE1(hio, send, ncomp, HAST_KEEPALIVE);
+ if (hio == NULL) {
+ now = time(NULL);
+ if (lastcheck + HAST_KEEPALIVE <= now) {
+ keepalive_send(res, ncomp);
+ lastcheck = now;
+ }
+ continue;
+ }
+ pjdlog_debug(2, "remote_send: (%p) Got request.", hio);
+ ggio = &hio->hio_ggio;
+ switch (ggio->gctl_cmd) {
+ case BIO_READ:
+ cmd = HIO_READ;
+ data = NULL;
+ offset = ggio->gctl_offset;
+ length = ggio->gctl_length;
+ break;
+ case BIO_WRITE:
+ cmd = HIO_WRITE;
+ data = ggio->gctl_data;
+ offset = ggio->gctl_offset;
+ length = ggio->gctl_length;
+ break;
+ case BIO_DELETE:
+ cmd = HIO_DELETE;
+ data = NULL;
+ offset = ggio->gctl_offset;
+ length = ggio->gctl_length;
+ break;
+ case BIO_FLUSH:
+ cmd = HIO_FLUSH;
+ data = NULL;
+ offset = 0;
+ length = 0;
+ break;
+ default:
+ PJDLOG_ABORT("invalid condition");
+ }
+ nv = nv_alloc();
+ nv_add_uint8(nv, cmd, "cmd");
+ nv_add_uint64(nv, (uint64_t)ggio->gctl_seq, "seq");
+ nv_add_uint64(nv, offset, "offset");
+ nv_add_uint64(nv, length, "length");
+ if (ISMEMSYNCWRITE(hio))
+ nv_add_uint8(nv, 1, "memsync");
+ if (nv_error(nv) != 0) {
+ hio->hio_errors[ncomp] = nv_error(nv);
+ pjdlog_debug(2,
+ "remote_send: (%p) Unable to prepare header to send.",
+ hio);
+ reqlog(LOG_ERR, 0, ggio,
+ "Unable to prepare header to send (%s): ",
+ strerror(nv_error(nv)));
+ /* Move failed request immediately to the done queue. */
+ goto done_queue;
+ }
+ /*
+ * Protect connection from disappearing.
+ */
+ rw_rlock(&hio_remote_lock[ncomp]);
+ if (!ISCONNECTED(res, ncomp)) {
+ rw_unlock(&hio_remote_lock[ncomp]);
+ hio->hio_errors[ncomp] = ENOTCONN;
+ goto done_queue;
+ }
+ /*
+ * Move the request to recv queue before sending it, because
+ * in different order we can get reply before we move request
+ * to recv queue.
+ */
+ pjdlog_debug(2,
+ "remote_send: (%p) Moving request to the recv queue.",
+ hio);
+ mtx_lock(&hio_recv_list_lock[ncomp]);
+ wakeup = TAILQ_EMPTY(&hio_recv_list[ncomp]);
+ TAILQ_INSERT_TAIL(&hio_recv_list[ncomp], hio, hio_next[ncomp]);
+ hio_recv_list_size[ncomp]++;
+ mtx_unlock(&hio_recv_list_lock[ncomp]);
+ if (hast_proto_send(res, res->hr_remoteout, nv, data,
+ data != NULL ? length : 0) == -1) {
+ hio->hio_errors[ncomp] = errno;
+ rw_unlock(&hio_remote_lock[ncomp]);
+ pjdlog_debug(2,
+ "remote_send: (%p) Unable to send request.", hio);
+ reqlog(LOG_ERR, 0, ggio,
+ "Unable to send request (%s): ",
+ strerror(hio->hio_errors[ncomp]));
+ remote_close(res, ncomp);
+ } else {
+ rw_unlock(&hio_remote_lock[ncomp]);
+ }
+ nv_free(nv);
+ if (wakeup)
+ cv_signal(&hio_recv_list_cond[ncomp]);
+ continue;
+done_queue:
+ nv_free(nv);
+ if (ISSYNCREQ(hio)) {
+ if (refcnt_release(&hio->hio_countdown) > 0)
+ continue;
+ mtx_lock(&sync_lock);
+ SYNCREQDONE(hio);
+ mtx_unlock(&sync_lock);
+ cv_signal(&sync_cond);
+ continue;
+ }
+ if (ggio->gctl_cmd == BIO_WRITE) {
+ mtx_lock(&res->hr_amp_lock);
+ if (activemap_need_sync(res->hr_amp, ggio->gctl_offset,
+ ggio->gctl_length)) {
+ (void)hast_activemap_flush(res);
+ } else {
+ mtx_unlock(&res->hr_amp_lock);
+ }
+ if (ISMEMSYNCWRITE(hio)) {
+ if (refcnt_release(&hio->hio_writecount) == 0) {
+ if (hio->hio_errors[0] == 0)
+ write_complete(res, hio);
+ }
+ }
+ }
+ if (refcnt_release(&hio->hio_countdown) > 0)
+ continue;
+ pjdlog_debug(2,
+ "remote_send: (%p) Moving request to the done queue.",
+ hio);
+ QUEUE_INSERT2(hio, done);
+ }
+ /* NOTREACHED */
+ return (NULL);
+}
+
+/*
+ * Thread receives answer from secondary node and passes it to ggate_send
+ * thread.
+ */
+static void *
+remote_recv_thread(void *arg)
+{
+ struct hast_resource *res = arg;
+ struct g_gate_ctl_io *ggio;
+ struct hio *hio;
+ struct nv *nv;
+ unsigned int ncomp;
+ uint64_t seq;
+ bool memsyncack;
+ int error;
+
+ /* Remote component is 1 for now. */
+ ncomp = 1;
+
+ for (;;) {
+ /* Wait until there is anything to receive. */
+ mtx_lock(&hio_recv_list_lock[ncomp]);
+ while (TAILQ_EMPTY(&hio_recv_list[ncomp])) {
+ pjdlog_debug(2, "remote_recv: No requests, waiting.");
+ cv_wait(&hio_recv_list_cond[ncomp],
+ &hio_recv_list_lock[ncomp]);
+ }
+ mtx_unlock(&hio_recv_list_lock[ncomp]);
+
+ memsyncack = false;
+
+ rw_rlock(&hio_remote_lock[ncomp]);
+ if (!ISCONNECTED(res, ncomp)) {
+ rw_unlock(&hio_remote_lock[ncomp]);
+ /*
+ * Connection is dead, so move all pending requests to
+ * the done queue (one-by-one).
+ */
+ mtx_lock(&hio_recv_list_lock[ncomp]);
+ hio = TAILQ_FIRST(&hio_recv_list[ncomp]);
+ PJDLOG_ASSERT(hio != NULL);
+ TAILQ_REMOVE(&hio_recv_list[ncomp], hio,
+ hio_next[ncomp]);
+ hio_recv_list_size[ncomp]--;
+ mtx_unlock(&hio_recv_list_lock[ncomp]);
+ hio->hio_errors[ncomp] = ENOTCONN;
+ goto done_queue;
+ }
+ if (hast_proto_recv_hdr(res->hr_remotein, &nv) == -1) {
+ pjdlog_errno(LOG_ERR,
+ "Unable to receive reply header");
+ rw_unlock(&hio_remote_lock[ncomp]);
+ remote_close(res, ncomp);
+ continue;
+ }
+ rw_unlock(&hio_remote_lock[ncomp]);
+ seq = nv_get_uint64(nv, "seq");
+ if (seq == 0) {
+ pjdlog_error("Header contains no 'seq' field.");
+ nv_free(nv);
+ continue;
+ }
+ memsyncack = nv_exists(nv, "received");
+ mtx_lock(&hio_recv_list_lock[ncomp]);
+ TAILQ_FOREACH(hio, &hio_recv_list[ncomp], hio_next[ncomp]) {
+ if (hio->hio_ggio.gctl_seq == seq) {
+ TAILQ_REMOVE(&hio_recv_list[ncomp], hio,
+ hio_next[ncomp]);
+ hio_recv_list_size[ncomp]--;
+ break;
+ }
+ }
+ mtx_unlock(&hio_recv_list_lock[ncomp]);
+ if (hio == NULL) {
+ pjdlog_error("Found no request matching received 'seq' field (%ju).",
+ (uintmax_t)seq);
+ nv_free(nv);
+ continue;
+ }
+ ggio = &hio->hio_ggio;
+ error = nv_get_int16(nv, "error");
+ if (error != 0) {
+ /* Request failed on remote side. */
+ hio->hio_errors[ncomp] = error;
+ reqlog(LOG_WARNING, 0, ggio,
+ "Remote request failed (%s): ", strerror(error));
+ nv_free(nv);
+ goto done_queue;
+ }
+ switch (ggio->gctl_cmd) {
+ case BIO_READ:
+ rw_rlock(&hio_remote_lock[ncomp]);
+ if (!ISCONNECTED(res, ncomp)) {
+ rw_unlock(&hio_remote_lock[ncomp]);
+ nv_free(nv);
+ goto done_queue;
+ }
+ if (hast_proto_recv_data(res, res->hr_remotein, nv,
+ ggio->gctl_data, ggio->gctl_length) == -1) {
+ hio->hio_errors[ncomp] = errno;
+ pjdlog_errno(LOG_ERR,
+ "Unable to receive reply data");
+ rw_unlock(&hio_remote_lock[ncomp]);
+ nv_free(nv);
+ remote_close(res, ncomp);
+ goto done_queue;
+ }
+ rw_unlock(&hio_remote_lock[ncomp]);
+ break;
+ case BIO_WRITE:
+ case BIO_DELETE:
+ case BIO_FLUSH:
+ break;
+ default:
+ PJDLOG_ABORT("invalid condition");
+ }
+ hio->hio_errors[ncomp] = 0;
+ nv_free(nv);
+done_queue:
+ if (ISMEMSYNCWRITE(hio)) {
+ if (!hio->hio_memsyncacked) {
+ PJDLOG_ASSERT(memsyncack ||
+ hio->hio_errors[ncomp] != 0);
+ /* Remote ack arrived. */
+ if (refcnt_release(&hio->hio_writecount) == 0) {
+ if (hio->hio_errors[0] == 0)
+ write_complete(res, hio);
+ }
+ hio->hio_memsyncacked = true;
+ if (hio->hio_errors[ncomp] == 0) {
+ pjdlog_debug(2,
+ "remote_recv: (%p) Moving request "
+ "back to the recv queue.", hio);
+ mtx_lock(&hio_recv_list_lock[ncomp]);
+ TAILQ_INSERT_TAIL(&hio_recv_list[ncomp],
+ hio, hio_next[ncomp]);
+ hio_recv_list_size[ncomp]++;
+ mtx_unlock(&hio_recv_list_lock[ncomp]);
+ continue;
+ }
+ } else {
+ PJDLOG_ASSERT(!memsyncack);
+ /* Remote final reply arrived. */
+ }
+ }
+ if (refcnt_release(&hio->hio_countdown) > 0)
+ continue;
+ if (ISSYNCREQ(hio)) {
+ mtx_lock(&sync_lock);
+ SYNCREQDONE(hio);
+ mtx_unlock(&sync_lock);
+ cv_signal(&sync_cond);
+ } else {
+ pjdlog_debug(2,
+ "remote_recv: (%p) Moving request to the done queue.",
+ hio);
+ QUEUE_INSERT2(hio, done);
+ }
+ }
+ /* NOTREACHED */
+ return (NULL);
+}
+
+/*
+ * Thread sends answer to the kernel.
+ */
+static void *
+ggate_send_thread(void *arg)
+{
+ struct hast_resource *res = arg;
+ struct g_gate_ctl_io *ggio;
+ struct hio *hio;
+ unsigned int ii, ncomps;
+
+ ncomps = HAST_NCOMPONENTS;
+
+ for (;;) {
+ pjdlog_debug(2, "ggate_send: Taking request.");
+ QUEUE_TAKE2(hio, done);
+ pjdlog_debug(2, "ggate_send: (%p) Got request.", hio);
+ ggio = &hio->hio_ggio;
+ for (ii = 0; ii < ncomps; ii++) {
+ if (hio->hio_errors[ii] == 0) {
+ /*
+ * One successful request is enough to declare
+ * success.
+ */
+ ggio->gctl_error = 0;
+ break;
+ }
+ }
+ if (ii == ncomps) {
+ /*
+ * None of the requests were successful.
+ * Use the error from local component except the
+ * case when we did only remote request.
+ */
+ if (ggio->gctl_cmd == BIO_READ &&
+ res->hr_syncsrc == HAST_SYNCSRC_SECONDARY)
+ ggio->gctl_error = hio->hio_errors[1];
+ else
+ ggio->gctl_error = hio->hio_errors[0];
+ }
+ if (ggio->gctl_error == 0 && ggio->gctl_cmd == BIO_WRITE) {
+ mtx_lock(&res->hr_amp_lock);
+ if (activemap_write_complete(res->hr_amp,
+ ggio->gctl_offset, ggio->gctl_length)) {
+ res->hr_stat_activemap_update++;
+ (void)hast_activemap_flush(res);
+ } else {
+ mtx_unlock(&res->hr_amp_lock);
+ }
+ }
+ if (ggio->gctl_cmd == BIO_WRITE) {
+ /*
+ * Unlock range we locked.
+ */
+ mtx_lock(&range_lock);
+ rangelock_del(range_regular, ggio->gctl_offset,
+ ggio->gctl_length);
+ if (range_sync_wait)
+ cv_signal(&range_sync_cond);
+ mtx_unlock(&range_lock);
+ if (!hio->hio_done)
+ write_complete(res, hio);
+ } else {
+ if (ioctl(res->hr_ggatefd, G_GATE_CMD_DONE, ggio) == -1) {
+ primary_exit(EX_OSERR,
+ "G_GATE_CMD_DONE failed");
+ }
+ }
+ if (hio->hio_errors[0]) {
+ switch (ggio->gctl_cmd) {
+ case BIO_READ:
+ res->hr_stat_read_error++;
+ break;
+ case BIO_WRITE:
+ res->hr_stat_write_error++;
+ break;
+ case BIO_DELETE:
+ res->hr_stat_delete_error++;
+ break;
+ case BIO_FLUSH:
+ res->hr_stat_flush_error++;
+ break;
+ }
+ }
+ pjdlog_debug(2,
+ "ggate_send: (%p) Moving request to the free queue.", hio);
+ QUEUE_INSERT2(hio, free);
+ }
+ /* NOTREACHED */
+ return (NULL);
+}
+
+/*
+ * Thread synchronize local and remote components.
+ */
+static void *
+sync_thread(void *arg __unused)
+{
+ struct hast_resource *res = arg;
+ struct hio *hio;
+ struct g_gate_ctl_io *ggio;
+ struct timeval tstart, tend, tdiff;
+ unsigned int ii, ncomp, ncomps;
+ off_t offset, length, synced;
+ bool dorewind, directreads;
+ int syncext;
+
+ ncomps = HAST_NCOMPONENTS;
+ dorewind = true;
+ synced = 0;
+ offset = -1;
+ directreads = false;
+
+ for (;;) {
+ mtx_lock(&sync_lock);
+ if (offset >= 0 && !sync_inprogress) {
+ gettimeofday(&tend, NULL);
+ timersub(&tend, &tstart, &tdiff);
+ pjdlog_info("Synchronization interrupted after %#.0T. "
+ "%NB synchronized so far.", &tdiff,
+ (intmax_t)synced);
+ event_send(res, EVENT_SYNCINTR);
+ }
+ while (!sync_inprogress) {
+ dorewind = true;
+ synced = 0;
+ cv_wait(&sync_cond, &sync_lock);
+ }
+ mtx_unlock(&sync_lock);
+ /*
+ * Obtain offset at which we should synchronize.
+ * Rewind synchronization if needed.
+ */
+ mtx_lock(&res->hr_amp_lock);
+ if (dorewind)
+ activemap_sync_rewind(res->hr_amp);
+ offset = activemap_sync_offset(res->hr_amp, &length, &syncext);
+ if (syncext != -1) {
+ /*
+ * We synchronized entire syncext extent, we can mark
+ * it as clean now.
+ */
+ if (activemap_extent_complete(res->hr_amp, syncext))
+ (void)hast_activemap_flush(res);
+ else
+ mtx_unlock(&res->hr_amp_lock);
+ } else {
+ mtx_unlock(&res->hr_amp_lock);
+ }
+ if (dorewind) {
+ dorewind = false;
+ if (offset == -1)
+ pjdlog_info("Nodes are in sync.");
+ else {
+ pjdlog_info("Synchronization started. %NB to go.",
+ (intmax_t)(res->hr_extentsize *
+ activemap_ndirty(res->hr_amp)));
+ event_send(res, EVENT_SYNCSTART);
+ gettimeofday(&tstart, NULL);
+ }
+ }
+ if (offset == -1) {
+ sync_stop();
+ pjdlog_debug(1, "Nothing to synchronize.");
+ /*
+ * Synchronization complete, make both localcnt and
+ * remotecnt equal.
+ */
+ ncomp = 1;
+ rw_rlock(&hio_remote_lock[ncomp]);
+ if (ISCONNECTED(res, ncomp)) {
+ if (synced > 0) {
+ int64_t bps;
+
+ gettimeofday(&tend, NULL);
+ timersub(&tend, &tstart, &tdiff);
+ bps = (int64_t)((double)synced /
+ ((double)tdiff.tv_sec +
+ (double)tdiff.tv_usec / 1000000));
+ pjdlog_info("Synchronization complete. "
+ "%NB synchronized in %#.0lT (%NB/sec).",
+ (intmax_t)synced, &tdiff,
+ (intmax_t)bps);
+ event_send(res, EVENT_SYNCDONE);
+ }
+ mtx_lock(&metadata_lock);
+ if (res->hr_syncsrc == HAST_SYNCSRC_SECONDARY)
+ directreads = true;
+ res->hr_syncsrc = HAST_SYNCSRC_UNDEF;
+ res->hr_primary_localcnt =
+ res->hr_secondary_remotecnt;
+ res->hr_primary_remotecnt =
+ res->hr_secondary_localcnt;
+ pjdlog_debug(1,
+ "Setting localcnt to %ju and remotecnt to %ju.",
+ (uintmax_t)res->hr_primary_localcnt,
+ (uintmax_t)res->hr_primary_remotecnt);
+ (void)metadata_write(res);
+ mtx_unlock(&metadata_lock);
+ }
+ rw_unlock(&hio_remote_lock[ncomp]);
+ if (directreads) {
+ directreads = false;
+ enable_direct_reads(res);
+ }
+ continue;
+ }
+ pjdlog_debug(2, "sync: Taking free request.");
+ QUEUE_TAKE2(hio, free);
+ pjdlog_debug(2, "sync: (%p) Got free request.", hio);
+ /*
+ * Lock the range we are going to synchronize. We don't want
+ * race where someone writes between our read and write.
+ */
+ for (;;) {
+ mtx_lock(&range_lock);
+ if (rangelock_islocked(range_regular, offset, length)) {
+ pjdlog_debug(2,
+ "sync: Range offset=%jd length=%jd locked.",
+ (intmax_t)offset, (intmax_t)length);
+ range_sync_wait = true;
+ cv_wait(&range_sync_cond, &range_lock);
+ range_sync_wait = false;
+ mtx_unlock(&range_lock);
+ continue;
+ }
+ if (rangelock_add(range_sync, offset, length) == -1) {
+ mtx_unlock(&range_lock);
+ pjdlog_debug(2,
+ "sync: Range offset=%jd length=%jd is already locked, waiting.",
+ (intmax_t)offset, (intmax_t)length);
+ sleep(1);
+ continue;
+ }
+ mtx_unlock(&range_lock);
+ break;
+ }
+ /*
+ * First read the data from synchronization source.
+ */
+ SYNCREQ(hio);
+ ggio = &hio->hio_ggio;
+ ggio->gctl_cmd = BIO_READ;
+ ggio->gctl_offset = offset;
+ ggio->gctl_length = length;
+ ggio->gctl_error = 0;
+ hio->hio_done = false;
+ hio->hio_replication = res->hr_replication;
+ for (ii = 0; ii < ncomps; ii++)
+ hio->hio_errors[ii] = EINVAL;
+ reqlog(LOG_DEBUG, 2, ggio, "sync: (%p) Sending sync request: ",
+ hio);
+ pjdlog_debug(2, "sync: (%p) Moving request to the send queue.",
+ hio);
+ mtx_lock(&metadata_lock);
+ if (res->hr_syncsrc == HAST_SYNCSRC_PRIMARY) {
+ /*
+ * This range is up-to-date on local component,
+ * so handle request locally.
+ */
+ /* Local component is 0 for now. */
+ ncomp = 0;
+ } else /* if (res->hr_syncsrc == HAST_SYNCSRC_SECONDARY) */ {
+ PJDLOG_ASSERT(res->hr_syncsrc == HAST_SYNCSRC_SECONDARY);
+ /*
+ * This range is out-of-date on local component,
+ * so send request to the remote node.
+ */
+ /* Remote component is 1 for now. */
+ ncomp = 1;
+ }
+ mtx_unlock(&metadata_lock);
+ refcnt_init(&hio->hio_countdown, 1);
+ QUEUE_INSERT1(hio, send, ncomp);
+
+ /*
+ * Let's wait for READ to finish.
+ */
+ mtx_lock(&sync_lock);
+ while (!ISSYNCREQDONE(hio))
+ cv_wait(&sync_cond, &sync_lock);
+ mtx_unlock(&sync_lock);
+
+ if (hio->hio_errors[ncomp] != 0) {
+ pjdlog_error("Unable to read synchronization data: %s.",
+ strerror(hio->hio_errors[ncomp]));
+ goto free_queue;
+ }
+
+ /*
+ * We read the data from synchronization source, now write it
+ * to synchronization target.
+ */
+ SYNCREQ(hio);
+ ggio->gctl_cmd = BIO_WRITE;
+ for (ii = 0; ii < ncomps; ii++)
+ hio->hio_errors[ii] = EINVAL;
+ reqlog(LOG_DEBUG, 2, ggio, "sync: (%p) Sending sync request: ",
+ hio);
+ pjdlog_debug(2, "sync: (%p) Moving request to the send queue.",
+ hio);
+ mtx_lock(&metadata_lock);
+ if (res->hr_syncsrc == HAST_SYNCSRC_PRIMARY) {
+ /*
+ * This range is up-to-date on local component,
+ * so we update remote component.
+ */
+ /* Remote component is 1 for now. */
+ ncomp = 1;
+ } else /* if (res->hr_syncsrc == HAST_SYNCSRC_SECONDARY) */ {
+ PJDLOG_ASSERT(res->hr_syncsrc == HAST_SYNCSRC_SECONDARY);
+ /*
+ * This range is out-of-date on local component,
+ * so we update it.
+ */
+ /* Local component is 0 for now. */
+ ncomp = 0;
+ }
+ mtx_unlock(&metadata_lock);
+
+ pjdlog_debug(2, "sync: (%p) Moving request to the send queue.",
+ hio);
+ refcnt_init(&hio->hio_countdown, 1);
+ QUEUE_INSERT1(hio, send, ncomp);
+
+ /*
+ * Let's wait for WRITE to finish.
+ */
+ mtx_lock(&sync_lock);
+ while (!ISSYNCREQDONE(hio))
+ cv_wait(&sync_cond, &sync_lock);
+ mtx_unlock(&sync_lock);
+
+ if (hio->hio_errors[ncomp] != 0) {
+ pjdlog_error("Unable to write synchronization data: %s.",
+ strerror(hio->hio_errors[ncomp]));
+ goto free_queue;
+ }
+
+ synced += length;
+free_queue:
+ mtx_lock(&range_lock);
+ rangelock_del(range_sync, offset, length);
+ if (range_regular_wait)
+ cv_signal(&range_regular_cond);
+ mtx_unlock(&range_lock);
+ pjdlog_debug(2, "sync: (%p) Moving request to the free queue.",
+ hio);
+ QUEUE_INSERT2(hio, free);
+ }
+ /* NOTREACHED */
+ return (NULL);
+}
+
+void
+primary_config_reload(struct hast_resource *res, struct nv *nv)
+{
+ unsigned int ii, ncomps;
+ int modified, vint;
+ const char *vstr;
+
+ pjdlog_info("Reloading configuration...");
+
+ PJDLOG_ASSERT(res->hr_role == HAST_ROLE_PRIMARY);
+ PJDLOG_ASSERT(gres == res);
+ nv_assert(nv, "remoteaddr");
+ nv_assert(nv, "sourceaddr");
+ nv_assert(nv, "replication");
+ nv_assert(nv, "checksum");
+ nv_assert(nv, "compression");
+ nv_assert(nv, "timeout");
+ nv_assert(nv, "exec");
+ nv_assert(nv, "metaflush");
+
+ ncomps = HAST_NCOMPONENTS;
+
+#define MODIFIED_REMOTEADDR 0x01
+#define MODIFIED_SOURCEADDR 0x02
+#define MODIFIED_REPLICATION 0x04
+#define MODIFIED_CHECKSUM 0x08
+#define MODIFIED_COMPRESSION 0x10
+#define MODIFIED_TIMEOUT 0x20
+#define MODIFIED_EXEC 0x40
+#define MODIFIED_METAFLUSH 0x80
+ modified = 0;
+
+ vstr = nv_get_string(nv, "remoteaddr");
+ if (strcmp(gres->hr_remoteaddr, vstr) != 0) {
+ /*
+ * Don't copy res->hr_remoteaddr to gres just yet.
+ * We want remote_close() to log disconnect from the old
+ * addresses, not from the new ones.
+ */
+ modified |= MODIFIED_REMOTEADDR;
+ }
+ vstr = nv_get_string(nv, "sourceaddr");
+ if (strcmp(gres->hr_sourceaddr, vstr) != 0) {
+ strlcpy(gres->hr_sourceaddr, vstr, sizeof(gres->hr_sourceaddr));
+ modified |= MODIFIED_SOURCEADDR;
+ }
+ vint = nv_get_int32(nv, "replication");
+ if (gres->hr_replication != vint) {
+ gres->hr_replication = vint;
+ modified |= MODIFIED_REPLICATION;
+ }
+ vint = nv_get_int32(nv, "checksum");
+ if (gres->hr_checksum != vint) {
+ gres->hr_checksum = vint;
+ modified |= MODIFIED_CHECKSUM;
+ }
+ vint = nv_get_int32(nv, "compression");
+ if (gres->hr_compression != vint) {
+ gres->hr_compression = vint;
+ modified |= MODIFIED_COMPRESSION;
+ }
+ vint = nv_get_int32(nv, "timeout");
+ if (gres->hr_timeout != vint) {
+ gres->hr_timeout = vint;
+ modified |= MODIFIED_TIMEOUT;
+ }
+ vstr = nv_get_string(nv, "exec");
+ if (strcmp(gres->hr_exec, vstr) != 0) {
+ strlcpy(gres->hr_exec, vstr, sizeof(gres->hr_exec));
+ modified |= MODIFIED_EXEC;
+ }
+ vint = nv_get_int32(nv, "metaflush");
+ if (gres->hr_metaflush != vint) {
+ gres->hr_metaflush = vint;
+ modified |= MODIFIED_METAFLUSH;
+ }
+
+ /*
+ * Change timeout for connected sockets.
+ * Don't bother if we need to reconnect.
+ */
+ if ((modified & MODIFIED_TIMEOUT) != 0 &&
+ (modified & (MODIFIED_REMOTEADDR | MODIFIED_SOURCEADDR)) == 0) {
+ for (ii = 0; ii < ncomps; ii++) {
+ if (!ISREMOTE(ii))
+ continue;
+ rw_rlock(&hio_remote_lock[ii]);
+ if (!ISCONNECTED(gres, ii)) {
+ rw_unlock(&hio_remote_lock[ii]);
+ continue;
+ }
+ rw_unlock(&hio_remote_lock[ii]);
+ if (proto_timeout(gres->hr_remotein,
+ gres->hr_timeout) == -1) {
+ pjdlog_errno(LOG_WARNING,
+ "Unable to set connection timeout");
+ }
+ if (proto_timeout(gres->hr_remoteout,
+ gres->hr_timeout) == -1) {
+ pjdlog_errno(LOG_WARNING,
+ "Unable to set connection timeout");
+ }
+ }
+ }
+ if ((modified & (MODIFIED_REMOTEADDR | MODIFIED_SOURCEADDR)) != 0) {
+ for (ii = 0; ii < ncomps; ii++) {
+ if (!ISREMOTE(ii))
+ continue;
+ remote_close(gres, ii);
+ }
+ if (modified & MODIFIED_REMOTEADDR) {
+ vstr = nv_get_string(nv, "remoteaddr");
+ strlcpy(gres->hr_remoteaddr, vstr,
+ sizeof(gres->hr_remoteaddr));
+ }
+ }
+#undef MODIFIED_REMOTEADDR
+#undef MODIFIED_SOURCEADDR
+#undef MODIFIED_REPLICATION
+#undef MODIFIED_CHECKSUM
+#undef MODIFIED_COMPRESSION
+#undef MODIFIED_TIMEOUT
+#undef MODIFIED_EXEC
+#undef MODIFIED_METAFLUSH
+
+ pjdlog_info("Configuration reloaded successfully.");
+}
+
+static void
+guard_one(struct hast_resource *res, unsigned int ncomp)
+{
+ struct proto_conn *in, *out;
+
+ if (!ISREMOTE(ncomp))
+ return;
+
+ rw_rlock(&hio_remote_lock[ncomp]);
+
+ if (!real_remote(res)) {
+ rw_unlock(&hio_remote_lock[ncomp]);
+ return;
+ }
+
+ if (ISCONNECTED(res, ncomp)) {
+ PJDLOG_ASSERT(res->hr_remotein != NULL);
+ PJDLOG_ASSERT(res->hr_remoteout != NULL);
+ rw_unlock(&hio_remote_lock[ncomp]);
+ pjdlog_debug(2, "remote_guard: Connection to %s is ok.",
+ res->hr_remoteaddr);
+ return;
+ }
+
+ PJDLOG_ASSERT(res->hr_remotein == NULL);
+ PJDLOG_ASSERT(res->hr_remoteout == NULL);
+ /*
+ * Upgrade the lock. It doesn't have to be atomic as no other thread
+ * can change connection status from disconnected to connected.
+ */
+ rw_unlock(&hio_remote_lock[ncomp]);
+ pjdlog_debug(2, "remote_guard: Reconnecting to %s.",
+ res->hr_remoteaddr);
+ in = out = NULL;
+ if (init_remote(res, &in, &out) == 0) {
+ rw_wlock(&hio_remote_lock[ncomp]);
+ PJDLOG_ASSERT(res->hr_remotein == NULL);
+ PJDLOG_ASSERT(res->hr_remoteout == NULL);
+ PJDLOG_ASSERT(in != NULL && out != NULL);
+ res->hr_remotein = in;
+ res->hr_remoteout = out;
+ rw_unlock(&hio_remote_lock[ncomp]);
+ pjdlog_info("Successfully reconnected to %s.",
+ res->hr_remoteaddr);
+ sync_start();
+ } else {
+ /* Both connections should be NULL. */
+ PJDLOG_ASSERT(res->hr_remotein == NULL);
+ PJDLOG_ASSERT(res->hr_remoteout == NULL);
+ PJDLOG_ASSERT(in == NULL && out == NULL);
+ pjdlog_debug(2, "remote_guard: Reconnect to %s failed.",
+ res->hr_remoteaddr);
+ }
+}
+
+/*
+ * Thread guards remote connections and reconnects when needed, handles
+ * signals, etc.
+ */
+static void *
+guard_thread(void *arg)
+{
+ struct hast_resource *res = arg;
+ unsigned int ii, ncomps;
+ struct timespec timeout;
+ time_t lastcheck, now;
+ sigset_t mask;
+ int signo;
+
+ ncomps = HAST_NCOMPONENTS;
+ lastcheck = time(NULL);
+
+ PJDLOG_VERIFY(sigemptyset(&mask) == 0);
+ PJDLOG_VERIFY(sigaddset(&mask, SIGINT) == 0);
+ PJDLOG_VERIFY(sigaddset(&mask, SIGTERM) == 0);
+
+ timeout.tv_sec = HAST_KEEPALIVE;
+ timeout.tv_nsec = 0;
+ signo = -1;
+
+ for (;;) {
+ switch (signo) {
+ case SIGINT:
+ case SIGTERM:
+ sigexit_received = true;
+ primary_exitx(EX_OK,
+ "Termination signal received, exiting.");
+ break;
+ default:
+ break;
+ }
+
+ /*
+ * Don't check connections until we fully started,
+ * as we may still be looping, waiting for remote node
+ * to switch from primary to secondary.
+ */
+ if (fullystarted) {
+ pjdlog_debug(2, "remote_guard: Checking connections.");
+ now = time(NULL);
+ if (lastcheck + HAST_KEEPALIVE <= now) {
+ for (ii = 0; ii < ncomps; ii++)
+ guard_one(res, ii);
+ lastcheck = now;
+ }
+ }
+ signo = sigtimedwait(&mask, NULL, &timeout);
+ }
+ /* NOTREACHED */
+ return (NULL);
+}
diff --git a/sbin/hastd/proto.c b/sbin/hastd/proto.c
new file mode 100644
index 0000000..53bbf7a
--- /dev/null
+++ b/sbin/hastd/proto.c
@@ -0,0 +1,446 @@
+/*-
+ * Copyright (c) 2009-2010 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+
+#include <errno.h>
+#include <stdint.h>
+#include <string.h>
+#include <strings.h>
+
+#include "pjdlog.h"
+#include "proto.h"
+#include "proto_impl.h"
+
+#define PROTO_CONN_MAGIC 0x907041c
+struct proto_conn {
+ int pc_magic;
+ struct proto *pc_proto;
+ void *pc_ctx;
+ int pc_side;
+#define PROTO_SIDE_CLIENT 0
+#define PROTO_SIDE_SERVER_LISTEN 1
+#define PROTO_SIDE_SERVER_WORK 2
+};
+
+static TAILQ_HEAD(, proto) protos = TAILQ_HEAD_INITIALIZER(protos);
+
+void
+proto_register(struct proto *proto, bool isdefault)
+{
+ static bool seen_default = false;
+
+ if (!isdefault)
+ TAILQ_INSERT_HEAD(&protos, proto, prt_next);
+ else {
+ PJDLOG_ASSERT(!seen_default);
+ seen_default = true;
+ TAILQ_INSERT_TAIL(&protos, proto, prt_next);
+ }
+}
+
+static struct proto_conn *
+proto_alloc(struct proto *proto, int side)
+{
+ struct proto_conn *conn;
+
+ PJDLOG_ASSERT(proto != NULL);
+ PJDLOG_ASSERT(side == PROTO_SIDE_CLIENT ||
+ side == PROTO_SIDE_SERVER_LISTEN ||
+ side == PROTO_SIDE_SERVER_WORK);
+
+ conn = malloc(sizeof(*conn));
+ if (conn != NULL) {
+ conn->pc_proto = proto;
+ conn->pc_side = side;
+ conn->pc_magic = PROTO_CONN_MAGIC;
+ }
+ return (conn);
+}
+
+static void
+proto_free(struct proto_conn *conn)
+{
+
+ PJDLOG_ASSERT(conn != NULL);
+ PJDLOG_ASSERT(conn->pc_magic == PROTO_CONN_MAGIC);
+ PJDLOG_ASSERT(conn->pc_side == PROTO_SIDE_CLIENT ||
+ conn->pc_side == PROTO_SIDE_SERVER_LISTEN ||
+ conn->pc_side == PROTO_SIDE_SERVER_WORK);
+ PJDLOG_ASSERT(conn->pc_proto != NULL);
+
+ bzero(conn, sizeof(*conn));
+ free(conn);
+}
+
+static int
+proto_common_setup(const char *srcaddr, const char *dstaddr,
+ struct proto_conn **connp, int side)
+{
+ struct proto *proto;
+ struct proto_conn *conn;
+ void *ctx;
+ int ret;
+
+ PJDLOG_ASSERT(side == PROTO_SIDE_CLIENT ||
+ side == PROTO_SIDE_SERVER_LISTEN);
+
+ TAILQ_FOREACH(proto, &protos, prt_next) {
+ if (side == PROTO_SIDE_CLIENT) {
+ if (proto->prt_client == NULL)
+ ret = -1;
+ else
+ ret = proto->prt_client(srcaddr, dstaddr, &ctx);
+ } else /* if (side == PROTO_SIDE_SERVER_LISTEN) */ {
+ if (proto->prt_server == NULL)
+ ret = -1;
+ else
+ ret = proto->prt_server(dstaddr, &ctx);
+ }
+ /*
+ * ret == 0 - success
+ * ret == -1 - dstaddr is not for this protocol
+ * ret > 0 - right protocol, but an error occurred
+ */
+ if (ret >= 0)
+ break;
+ }
+ if (proto == NULL) {
+ /* Unrecognized address. */
+ errno = EINVAL;
+ return (-1);
+ }
+ if (ret > 0) {
+ /* An error occurred. */
+ errno = ret;
+ return (-1);
+ }
+ conn = proto_alloc(proto, side);
+ if (conn == NULL) {
+ if (proto->prt_close != NULL)
+ proto->prt_close(ctx);
+ errno = ENOMEM;
+ return (-1);
+ }
+ conn->pc_ctx = ctx;
+ *connp = conn;
+
+ return (0);
+}
+
+int
+proto_client(const char *srcaddr, const char *dstaddr,
+ struct proto_conn **connp)
+{
+
+ return (proto_common_setup(srcaddr, dstaddr, connp, PROTO_SIDE_CLIENT));
+}
+
+int
+proto_connect(struct proto_conn *conn, int timeout)
+{
+ int ret;
+
+ PJDLOG_ASSERT(conn != NULL);
+ PJDLOG_ASSERT(conn->pc_magic == PROTO_CONN_MAGIC);
+ PJDLOG_ASSERT(conn->pc_side == PROTO_SIDE_CLIENT);
+ PJDLOG_ASSERT(conn->pc_proto != NULL);
+ PJDLOG_ASSERT(conn->pc_proto->prt_connect != NULL);
+ PJDLOG_ASSERT(timeout >= -1);
+
+ ret = conn->pc_proto->prt_connect(conn->pc_ctx, timeout);
+ if (ret != 0) {
+ errno = ret;
+ return (-1);
+ }
+
+ return (0);
+}
+
+int
+proto_connect_wait(struct proto_conn *conn, int timeout)
+{
+ int ret;
+
+ PJDLOG_ASSERT(conn != NULL);
+ PJDLOG_ASSERT(conn->pc_magic == PROTO_CONN_MAGIC);
+ PJDLOG_ASSERT(conn->pc_side == PROTO_SIDE_CLIENT);
+ PJDLOG_ASSERT(conn->pc_proto != NULL);
+ PJDLOG_ASSERT(conn->pc_proto->prt_connect_wait != NULL);
+ PJDLOG_ASSERT(timeout >= 0);
+
+ ret = conn->pc_proto->prt_connect_wait(conn->pc_ctx, timeout);
+ if (ret != 0) {
+ errno = ret;
+ return (-1);
+ }
+
+ return (0);
+}
+
+int
+proto_server(const char *addr, struct proto_conn **connp)
+{
+
+ return (proto_common_setup(NULL, addr, connp, PROTO_SIDE_SERVER_LISTEN));
+}
+
+int
+proto_accept(struct proto_conn *conn, struct proto_conn **newconnp)
+{
+ struct proto_conn *newconn;
+ int ret;
+
+ PJDLOG_ASSERT(conn != NULL);
+ PJDLOG_ASSERT(conn->pc_magic == PROTO_CONN_MAGIC);
+ PJDLOG_ASSERT(conn->pc_side == PROTO_SIDE_SERVER_LISTEN);
+ PJDLOG_ASSERT(conn->pc_proto != NULL);
+ PJDLOG_ASSERT(conn->pc_proto->prt_accept != NULL);
+
+ newconn = proto_alloc(conn->pc_proto, PROTO_SIDE_SERVER_WORK);
+ if (newconn == NULL)
+ return (-1);
+
+ ret = conn->pc_proto->prt_accept(conn->pc_ctx, &newconn->pc_ctx);
+ if (ret != 0) {
+ proto_free(newconn);
+ errno = ret;
+ return (-1);
+ }
+
+ *newconnp = newconn;
+
+ return (0);
+}
+
+int
+proto_send(const struct proto_conn *conn, const void *data, size_t size)
+{
+ int ret;
+
+ PJDLOG_ASSERT(conn != NULL);
+ PJDLOG_ASSERT(conn->pc_magic == PROTO_CONN_MAGIC);
+ PJDLOG_ASSERT(conn->pc_proto != NULL);
+ PJDLOG_ASSERT(conn->pc_proto->prt_send != NULL);
+
+ ret = conn->pc_proto->prt_send(conn->pc_ctx, data, size, -1);
+ if (ret != 0) {
+ errno = ret;
+ return (-1);
+ }
+ return (0);
+}
+
+int
+proto_recv(const struct proto_conn *conn, void *data, size_t size)
+{
+ int ret;
+
+ PJDLOG_ASSERT(conn != NULL);
+ PJDLOG_ASSERT(conn->pc_magic == PROTO_CONN_MAGIC);
+ PJDLOG_ASSERT(conn->pc_proto != NULL);
+ PJDLOG_ASSERT(conn->pc_proto->prt_recv != NULL);
+
+ ret = conn->pc_proto->prt_recv(conn->pc_ctx, data, size, NULL);
+ if (ret != 0) {
+ errno = ret;
+ return (-1);
+ }
+ return (0);
+}
+
+int
+proto_connection_send(const struct proto_conn *conn, struct proto_conn *mconn)
+{
+ const char *protoname;
+ int ret, fd;
+
+ PJDLOG_ASSERT(conn != NULL);
+ PJDLOG_ASSERT(conn->pc_magic == PROTO_CONN_MAGIC);
+ PJDLOG_ASSERT(conn->pc_proto != NULL);
+ PJDLOG_ASSERT(conn->pc_proto->prt_send != NULL);
+ PJDLOG_ASSERT(mconn != NULL);
+ PJDLOG_ASSERT(mconn->pc_magic == PROTO_CONN_MAGIC);
+ PJDLOG_ASSERT(mconn->pc_proto != NULL);
+ fd = proto_descriptor(mconn);
+ PJDLOG_ASSERT(fd >= 0);
+ protoname = mconn->pc_proto->prt_name;
+ PJDLOG_ASSERT(protoname != NULL);
+
+ ret = conn->pc_proto->prt_send(conn->pc_ctx,
+ (const unsigned char *)protoname, strlen(protoname) + 1, fd);
+ proto_close(mconn);
+ if (ret != 0) {
+ errno = ret;
+ return (-1);
+ }
+ return (0);
+}
+
+int
+proto_connection_recv(const struct proto_conn *conn, bool client,
+ struct proto_conn **newconnp)
+{
+ char protoname[128];
+ struct proto *proto;
+ struct proto_conn *newconn;
+ int ret, fd;
+
+ PJDLOG_ASSERT(conn != NULL);
+ PJDLOG_ASSERT(conn->pc_magic == PROTO_CONN_MAGIC);
+ PJDLOG_ASSERT(conn->pc_proto != NULL);
+ PJDLOG_ASSERT(conn->pc_proto->prt_recv != NULL);
+ PJDLOG_ASSERT(newconnp != NULL);
+
+ bzero(protoname, sizeof(protoname));
+
+ ret = conn->pc_proto->prt_recv(conn->pc_ctx, (unsigned char *)protoname,
+ sizeof(protoname) - 1, &fd);
+ if (ret != 0) {
+ errno = ret;
+ return (-1);
+ }
+
+ PJDLOG_ASSERT(fd >= 0);
+
+ TAILQ_FOREACH(proto, &protos, prt_next) {
+ if (strcmp(proto->prt_name, protoname) == 0)
+ break;
+ }
+ if (proto == NULL) {
+ errno = EINVAL;
+ return (-1);
+ }
+
+ newconn = proto_alloc(proto,
+ client ? PROTO_SIDE_CLIENT : PROTO_SIDE_SERVER_WORK);
+ if (newconn == NULL)
+ return (-1);
+ PJDLOG_ASSERT(newconn->pc_proto->prt_wrap != NULL);
+ ret = newconn->pc_proto->prt_wrap(fd, client, &newconn->pc_ctx);
+ if (ret != 0) {
+ proto_free(newconn);
+ errno = ret;
+ return (-1);
+ }
+
+ *newconnp = newconn;
+
+ return (0);
+}
+
+int
+proto_descriptor(const struct proto_conn *conn)
+{
+
+ PJDLOG_ASSERT(conn != NULL);
+ PJDLOG_ASSERT(conn->pc_magic == PROTO_CONN_MAGIC);
+ PJDLOG_ASSERT(conn->pc_proto != NULL);
+ PJDLOG_ASSERT(conn->pc_proto->prt_descriptor != NULL);
+
+ return (conn->pc_proto->prt_descriptor(conn->pc_ctx));
+}
+
+bool
+proto_address_match(const struct proto_conn *conn, const char *addr)
+{
+
+ PJDLOG_ASSERT(conn != NULL);
+ PJDLOG_ASSERT(conn->pc_magic == PROTO_CONN_MAGIC);
+ PJDLOG_ASSERT(conn->pc_proto != NULL);
+ PJDLOG_ASSERT(conn->pc_proto->prt_address_match != NULL);
+
+ return (conn->pc_proto->prt_address_match(conn->pc_ctx, addr));
+}
+
+void
+proto_local_address(const struct proto_conn *conn, char *addr, size_t size)
+{
+
+ PJDLOG_ASSERT(conn != NULL);
+ PJDLOG_ASSERT(conn->pc_magic == PROTO_CONN_MAGIC);
+ PJDLOG_ASSERT(conn->pc_proto != NULL);
+ PJDLOG_ASSERT(conn->pc_proto->prt_local_address != NULL);
+
+ conn->pc_proto->prt_local_address(conn->pc_ctx, addr, size);
+}
+
+void
+proto_remote_address(const struct proto_conn *conn, char *addr, size_t size)
+{
+
+ PJDLOG_ASSERT(conn != NULL);
+ PJDLOG_ASSERT(conn->pc_magic == PROTO_CONN_MAGIC);
+ PJDLOG_ASSERT(conn->pc_proto != NULL);
+ PJDLOG_ASSERT(conn->pc_proto->prt_remote_address != NULL);
+
+ conn->pc_proto->prt_remote_address(conn->pc_ctx, addr, size);
+}
+
+int
+proto_timeout(const struct proto_conn *conn, int timeout)
+{
+ struct timeval tv;
+ int fd;
+
+ PJDLOG_ASSERT(conn != NULL);
+ PJDLOG_ASSERT(conn->pc_magic == PROTO_CONN_MAGIC);
+ PJDLOG_ASSERT(conn->pc_proto != NULL);
+
+ fd = proto_descriptor(conn);
+ if (fd == -1)
+ return (-1);
+
+ tv.tv_sec = timeout;
+ tv.tv_usec = 0;
+ if (setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) == -1)
+ return (-1);
+ if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) == -1)
+ return (-1);
+
+ return (0);
+}
+
+void
+proto_close(struct proto_conn *conn)
+{
+
+ PJDLOG_ASSERT(conn != NULL);
+ PJDLOG_ASSERT(conn->pc_magic == PROTO_CONN_MAGIC);
+ PJDLOG_ASSERT(conn->pc_proto != NULL);
+ PJDLOG_ASSERT(conn->pc_proto->prt_close != NULL);
+
+ conn->pc_proto->prt_close(conn->pc_ctx);
+ proto_free(conn);
+}
diff --git a/sbin/hastd/proto.h b/sbin/hastd/proto.h
new file mode 100644
index 0000000..1a60e5b
--- /dev/null
+++ b/sbin/hastd/proto.h
@@ -0,0 +1,61 @@
+/*-
+ * Copyright (c) 2009-2010 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _PROTO_H_
+#define _PROTO_H_
+
+#include <stdbool.h> /* bool */
+#include <stdlib.h> /* size_t */
+
+struct proto_conn;
+
+int proto_client(const char *srcaddr, const char *dstaddr,
+ struct proto_conn **connp);
+int proto_connect(struct proto_conn *conn, int timeout);
+int proto_connect_wait(struct proto_conn *conn, int timeout);
+int proto_server(const char *addr, struct proto_conn **connp);
+int proto_accept(struct proto_conn *conn, struct proto_conn **newconnp);
+int proto_send(const struct proto_conn *conn, const void *data, size_t size);
+int proto_recv(const struct proto_conn *conn, void *data, size_t size);
+int proto_connection_send(const struct proto_conn *conn,
+ struct proto_conn *mconn);
+int proto_connection_recv(const struct proto_conn *conn, bool client,
+ struct proto_conn **newconnp);
+int proto_descriptor(const struct proto_conn *conn);
+bool proto_address_match(const struct proto_conn *conn, const char *addr);
+void proto_local_address(const struct proto_conn *conn, char *addr,
+ size_t size);
+void proto_remote_address(const struct proto_conn *conn, char *addr,
+ size_t size);
+int proto_timeout(const struct proto_conn *conn, int timeout);
+void proto_close(struct proto_conn *conn);
+
+#endif /* !_PROTO_H_ */
diff --git a/sbin/hastd/proto_common.c b/sbin/hastd/proto_common.c
new file mode 100644
index 0000000..843366b
--- /dev/null
+++ b/sbin/hastd/proto_common.c
@@ -0,0 +1,232 @@
+/*-
+ * Copyright (c) 2009-2010 The FreeBSD Foundation
+ * Copyright (c) 2011 Pawel Jakub Dawidek <pawel@dawidek.net>
+ * All rights reserved.
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <unistd.h>
+
+#include "pjdlog.h"
+#include "proto_impl.h"
+
+/* Maximum size of packet we want to use when sending data. */
+#ifndef MAX_SEND_SIZE
+#define MAX_SEND_SIZE 32768
+#endif
+
+static bool
+blocking_socket(int sock)
+{
+ int flags;
+
+ flags = fcntl(sock, F_GETFL);
+ PJDLOG_ASSERT(flags >= 0);
+ return ((flags & O_NONBLOCK) == 0);
+}
+
+static int
+proto_descriptor_send(int sock, int fd)
+{
+ unsigned char ctrl[CMSG_SPACE(sizeof(fd))];
+ struct msghdr msg;
+ struct cmsghdr *cmsg;
+
+ PJDLOG_ASSERT(sock >= 0);
+ PJDLOG_ASSERT(fd >= 0);
+
+ bzero(&msg, sizeof(msg));
+ bzero(&ctrl, sizeof(ctrl));
+
+ msg.msg_iov = NULL;
+ msg.msg_iovlen = 0;
+ msg.msg_control = ctrl;
+ msg.msg_controllen = sizeof(ctrl);
+
+ cmsg = CMSG_FIRSTHDR(&msg);
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
+ bcopy(&fd, CMSG_DATA(cmsg), sizeof(fd));
+
+ if (sendmsg(sock, &msg, 0) == -1)
+ return (errno);
+
+ return (0);
+}
+
+int
+proto_common_send(int sock, const unsigned char *data, size_t size, int fd)
+{
+ ssize_t done;
+ size_t sendsize;
+ int errcount = 0;
+
+ PJDLOG_ASSERT(sock >= 0);
+
+ if (data == NULL) {
+ /* The caller is just trying to decide about direction. */
+
+ PJDLOG_ASSERT(size == 0);
+
+ if (shutdown(sock, SHUT_RD) == -1)
+ return (errno);
+ return (0);
+ }
+
+ PJDLOG_ASSERT(data != NULL);
+ PJDLOG_ASSERT(size > 0);
+
+ do {
+ sendsize = size < MAX_SEND_SIZE ? size : MAX_SEND_SIZE;
+ done = send(sock, data, sendsize, MSG_NOSIGNAL);
+ if (done == 0) {
+ return (ENOTCONN);
+ } else if (done == -1) {
+ if (errno == EINTR)
+ continue;
+ if (errno == ENOBUFS) {
+ /*
+ * If there are no buffers we retry.
+ * After each try we increase delay before the
+ * next one and we give up after fifteen times.
+ * This gives 11s of total wait time.
+ */
+ if (errcount == 15) {
+ pjdlog_warning("Getting ENOBUFS errors for 11s on send(), giving up.");
+ } else {
+ if (errcount == 0)
+ pjdlog_warning("Got ENOBUFS error on send(), retrying for a bit.");
+ errcount++;
+ usleep(100000 * errcount);
+ continue;
+ }
+ }
+ /*
+ * If this is blocking socket and we got EAGAIN, this
+ * means the request timed out. Translate errno to
+ * ETIMEDOUT, to give administrator a hint to
+ * eventually increase timeout.
+ */
+ if (errno == EAGAIN && blocking_socket(sock))
+ errno = ETIMEDOUT;
+ return (errno);
+ }
+ data += done;
+ size -= done;
+ } while (size > 0);
+ if (errcount > 0) {
+ pjdlog_info("Data sent successfully after %d ENOBUFS error%s.",
+ errcount, errcount == 1 ? "" : "s");
+ }
+
+ if (fd == -1)
+ return (0);
+ return (proto_descriptor_send(sock, fd));
+}
+
+static int
+proto_descriptor_recv(int sock, int *fdp)
+{
+ unsigned char ctrl[CMSG_SPACE(sizeof(*fdp))];
+ struct msghdr msg;
+ struct cmsghdr *cmsg;
+
+ PJDLOG_ASSERT(sock >= 0);
+ PJDLOG_ASSERT(fdp != NULL);
+
+ bzero(&msg, sizeof(msg));
+ bzero(&ctrl, sizeof(ctrl));
+
+ msg.msg_iov = NULL;
+ msg.msg_iovlen = 0;
+ msg.msg_control = ctrl;
+ msg.msg_controllen = sizeof(ctrl);
+
+ if (recvmsg(sock, &msg, 0) == -1)
+ return (errno);
+
+ cmsg = CMSG_FIRSTHDR(&msg);
+ if (cmsg == NULL || cmsg->cmsg_level != SOL_SOCKET ||
+ cmsg->cmsg_type != SCM_RIGHTS) {
+ return (EINVAL);
+ }
+ bcopy(CMSG_DATA(cmsg), fdp, sizeof(*fdp));
+
+ return (0);
+}
+
+int
+proto_common_recv(int sock, unsigned char *data, size_t size, int *fdp)
+{
+ ssize_t done;
+
+ PJDLOG_ASSERT(sock >= 0);
+
+ if (data == NULL) {
+ /* The caller is just trying to decide about direction. */
+
+ PJDLOG_ASSERT(size == 0);
+
+ if (shutdown(sock, SHUT_WR) == -1)
+ return (errno);
+ return (0);
+ }
+
+ PJDLOG_ASSERT(data != NULL);
+ PJDLOG_ASSERT(size > 0);
+
+ do {
+ done = recv(sock, data, size, MSG_WAITALL);
+ } while (done == -1 && errno == EINTR);
+ if (done == 0) {
+ return (ENOTCONN);
+ } else if (done == -1) {
+ /*
+ * If this is blocking socket and we got EAGAIN, this
+ * means the request timed out. Translate errno to
+ * ETIMEDOUT, to give administrator a hint to
+ * eventually increase timeout.
+ */
+ if (errno == EAGAIN && blocking_socket(sock))
+ errno = ETIMEDOUT;
+ return (errno);
+ }
+ if (fdp == NULL)
+ return (0);
+ return (proto_descriptor_recv(sock, fdp));
+}
diff --git a/sbin/hastd/proto_impl.h b/sbin/hastd/proto_impl.h
new file mode 100644
index 0000000..d62f26f
--- /dev/null
+++ b/sbin/hastd/proto_impl.h
@@ -0,0 +1,79 @@
+/*-
+ * Copyright (c) 2009-2010 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _PROTO_IMPL_H_
+#define _PROTO_IMPL_H_
+
+#include <sys/queue.h>
+
+#include <stdbool.h> /* bool */
+#include <stdlib.h> /* size_t */
+
+#define __constructor __attribute__((constructor))
+
+typedef int prt_client_t(const char *, const char *, void **);
+typedef int prt_connect_t(void *, int);
+typedef int prt_connect_wait_t(void *, int);
+typedef int prt_server_t(const char *, void **);
+typedef int prt_accept_t(void *, void **);
+typedef int prt_wrap_t(int, bool, void **);
+typedef int prt_send_t(void *, const unsigned char *, size_t, int);
+typedef int prt_recv_t(void *, unsigned char *, size_t, int *);
+typedef int prt_descriptor_t(const void *);
+typedef bool prt_address_match_t(const void *, const char *);
+typedef void prt_local_address_t(const void *, char *, size_t);
+typedef void prt_remote_address_t(const void *, char *, size_t);
+typedef void prt_close_t(void *);
+
+struct proto {
+ const char *prt_name;
+ prt_client_t *prt_client;
+ prt_connect_t *prt_connect;
+ prt_connect_wait_t *prt_connect_wait;
+ prt_server_t *prt_server;
+ prt_accept_t *prt_accept;
+ prt_wrap_t *prt_wrap;
+ prt_send_t *prt_send;
+ prt_recv_t *prt_recv;
+ prt_descriptor_t *prt_descriptor;
+ prt_address_match_t *prt_address_match;
+ prt_local_address_t *prt_local_address;
+ prt_remote_address_t *prt_remote_address;
+ prt_close_t *prt_close;
+ TAILQ_ENTRY(proto) prt_next;
+};
+
+void proto_register(struct proto *proto, bool isdefault);
+
+int proto_common_send(int sock, const unsigned char *data, size_t size, int fd);
+int proto_common_recv(int sock, unsigned char *data, size_t size, int *fdp);
+
+#endif /* !_PROTO_IMPL_H_ */
diff --git a/sbin/hastd/proto_socketpair.c b/sbin/hastd/proto_socketpair.c
new file mode 100644
index 0000000..d13caa9
--- /dev/null
+++ b/sbin/hastd/proto_socketpair.c
@@ -0,0 +1,237 @@
+/*-
+ * Copyright (c) 2009-2010 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "pjdlog.h"
+#include "proto_impl.h"
+
+#define SP_CTX_MAGIC 0x50c3741
+struct sp_ctx {
+ int sp_magic;
+ int sp_fd[2];
+ int sp_side;
+#define SP_SIDE_UNDEF 0
+#define SP_SIDE_CLIENT 1
+#define SP_SIDE_SERVER 2
+};
+
+static void sp_close(void *ctx);
+
+static int
+sp_client(const char *srcaddr, const char *dstaddr, void **ctxp)
+{
+ struct sp_ctx *spctx;
+ int ret;
+
+ if (strcmp(dstaddr, "socketpair://") != 0)
+ return (-1);
+
+ PJDLOG_ASSERT(srcaddr == NULL);
+
+ spctx = malloc(sizeof(*spctx));
+ if (spctx == NULL)
+ return (errno);
+
+ if (socketpair(PF_UNIX, SOCK_STREAM, 0, spctx->sp_fd) == -1) {
+ ret = errno;
+ free(spctx);
+ return (ret);
+ }
+
+ spctx->sp_side = SP_SIDE_UNDEF;
+ spctx->sp_magic = SP_CTX_MAGIC;
+ *ctxp = spctx;
+
+ return (0);
+}
+
+static int
+sp_send(void *ctx, const unsigned char *data, size_t size, int fd)
+{
+ struct sp_ctx *spctx = ctx;
+ int sock;
+
+ PJDLOG_ASSERT(spctx != NULL);
+ PJDLOG_ASSERT(spctx->sp_magic == SP_CTX_MAGIC);
+
+ switch (spctx->sp_side) {
+ case SP_SIDE_UNDEF:
+ /*
+ * If the first operation done by the caller is proto_send(),
+ * we assume this is the client.
+ */
+ /* FALLTHROUGH */
+ spctx->sp_side = SP_SIDE_CLIENT;
+ /* Close other end. */
+ close(spctx->sp_fd[1]);
+ spctx->sp_fd[1] = -1;
+ case SP_SIDE_CLIENT:
+ PJDLOG_ASSERT(spctx->sp_fd[0] >= 0);
+ sock = spctx->sp_fd[0];
+ break;
+ case SP_SIDE_SERVER:
+ PJDLOG_ASSERT(spctx->sp_fd[1] >= 0);
+ sock = spctx->sp_fd[1];
+ break;
+ default:
+ PJDLOG_ABORT("Invalid socket side (%d).", spctx->sp_side);
+ }
+
+ /* Someone is just trying to decide about side. */
+ if (data == NULL)
+ return (0);
+
+ return (proto_common_send(sock, data, size, fd));
+}
+
+static int
+sp_recv(void *ctx, unsigned char *data, size_t size, int *fdp)
+{
+ struct sp_ctx *spctx = ctx;
+ int fd;
+
+ PJDLOG_ASSERT(spctx != NULL);
+ PJDLOG_ASSERT(spctx->sp_magic == SP_CTX_MAGIC);
+
+ switch (spctx->sp_side) {
+ case SP_SIDE_UNDEF:
+ /*
+ * If the first operation done by the caller is proto_recv(),
+ * we assume this is the server.
+ */
+ /* FALLTHROUGH */
+ spctx->sp_side = SP_SIDE_SERVER;
+ /* Close other end. */
+ close(spctx->sp_fd[0]);
+ spctx->sp_fd[0] = -1;
+ case SP_SIDE_SERVER:
+ PJDLOG_ASSERT(spctx->sp_fd[1] >= 0);
+ fd = spctx->sp_fd[1];
+ break;
+ case SP_SIDE_CLIENT:
+ PJDLOG_ASSERT(spctx->sp_fd[0] >= 0);
+ fd = spctx->sp_fd[0];
+ break;
+ default:
+ PJDLOG_ABORT("Invalid socket side (%d).", spctx->sp_side);
+ }
+
+ /* Someone is just trying to decide about side. */
+ if (data == NULL)
+ return (0);
+
+ return (proto_common_recv(fd, data, size, fdp));
+}
+
+static int
+sp_descriptor(const void *ctx)
+{
+ const struct sp_ctx *spctx = ctx;
+
+ PJDLOG_ASSERT(spctx != NULL);
+ PJDLOG_ASSERT(spctx->sp_magic == SP_CTX_MAGIC);
+ PJDLOG_ASSERT(spctx->sp_side == SP_SIDE_CLIENT ||
+ spctx->sp_side == SP_SIDE_SERVER);
+
+ switch (spctx->sp_side) {
+ case SP_SIDE_CLIENT:
+ PJDLOG_ASSERT(spctx->sp_fd[0] >= 0);
+ return (spctx->sp_fd[0]);
+ case SP_SIDE_SERVER:
+ PJDLOG_ASSERT(spctx->sp_fd[1] >= 0);
+ return (spctx->sp_fd[1]);
+ }
+
+ PJDLOG_ABORT("Invalid socket side (%d).", spctx->sp_side);
+}
+
+static void
+sp_close(void *ctx)
+{
+ struct sp_ctx *spctx = ctx;
+
+ PJDLOG_ASSERT(spctx != NULL);
+ PJDLOG_ASSERT(spctx->sp_magic == SP_CTX_MAGIC);
+
+ switch (spctx->sp_side) {
+ case SP_SIDE_UNDEF:
+ PJDLOG_ASSERT(spctx->sp_fd[0] >= 0);
+ close(spctx->sp_fd[0]);
+ spctx->sp_fd[0] = -1;
+ PJDLOG_ASSERT(spctx->sp_fd[1] >= 0);
+ close(spctx->sp_fd[1]);
+ spctx->sp_fd[1] = -1;
+ break;
+ case SP_SIDE_CLIENT:
+ PJDLOG_ASSERT(spctx->sp_fd[0] >= 0);
+ close(spctx->sp_fd[0]);
+ spctx->sp_fd[0] = -1;
+ PJDLOG_ASSERT(spctx->sp_fd[1] == -1);
+ break;
+ case SP_SIDE_SERVER:
+ PJDLOG_ASSERT(spctx->sp_fd[1] >= 0);
+ close(spctx->sp_fd[1]);
+ spctx->sp_fd[1] = -1;
+ PJDLOG_ASSERT(spctx->sp_fd[0] == -1);
+ break;
+ default:
+ PJDLOG_ABORT("Invalid socket side (%d).", spctx->sp_side);
+ }
+
+ spctx->sp_magic = 0;
+ free(spctx);
+}
+
+static struct proto sp_proto = {
+ .prt_name = "socketpair",
+ .prt_client = sp_client,
+ .prt_send = sp_send,
+ .prt_recv = sp_recv,
+ .prt_descriptor = sp_descriptor,
+ .prt_close = sp_close
+};
+
+static __constructor void
+sp_ctor(void)
+{
+
+ proto_register(&sp_proto, false);
+}
diff --git a/sbin/hastd/proto_tcp.c b/sbin/hastd/proto_tcp.c
new file mode 100644
index 0000000..6dc0661
--- /dev/null
+++ b/sbin/hastd/proto_tcp.c
@@ -0,0 +1,637 @@
+/*-
+ * Copyright (c) 2009-2010 The FreeBSD Foundation
+ * Copyright (c) 2011 Pawel Jakub Dawidek <pawel@dawidek.net>
+ * All rights reserved.
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h> /* MAXHOSTNAMELEN */
+#include <sys/socket.h>
+
+#include <arpa/inet.h>
+
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "pjdlog.h"
+#include "proto_impl.h"
+#include "subr.h"
+
+#define TCP_CTX_MAGIC 0x7c41c
+struct tcp_ctx {
+ int tc_magic;
+ struct sockaddr_storage tc_sa;
+ int tc_fd;
+ int tc_side;
+#define TCP_SIDE_CLIENT 0
+#define TCP_SIDE_SERVER_LISTEN 1
+#define TCP_SIDE_SERVER_WORK 2
+};
+
+static int tcp_connect_wait(void *ctx, int timeout);
+static void tcp_close(void *ctx);
+
+/*
+ * Function converts the given string to unsigned number.
+ */
+static int
+numfromstr(const char *str, intmax_t minnum, intmax_t maxnum, intmax_t *nump)
+{
+ intmax_t digit, num;
+
+ if (str[0] == '\0')
+ goto invalid; /* Empty string. */
+ num = 0;
+ for (; *str != '\0'; str++) {
+ if (*str < '0' || *str > '9')
+ goto invalid; /* Non-digit character. */
+ digit = *str - '0';
+ if (num > num * 10 + digit)
+ goto invalid; /* Overflow. */
+ num = num * 10 + digit;
+ if (num > maxnum)
+ goto invalid; /* Too big. */
+ }
+ if (num < minnum)
+ goto invalid; /* Too small. */
+ *nump = num;
+ return (0);
+invalid:
+ errno = EINVAL;
+ return (-1);
+}
+
+static int
+tcp_addr(const char *addr, int defport, struct sockaddr_storage *sap)
+{
+ char iporhost[MAXHOSTNAMELEN], portstr[6];
+ struct addrinfo hints;
+ struct addrinfo *res;
+ const char *pp;
+ intmax_t port;
+ size_t size;
+ int error;
+
+ if (addr == NULL)
+ return (-1);
+
+ bzero(&hints, sizeof(hints));
+ hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICSERV;
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+
+ if (strncasecmp(addr, "tcp4://", 7) == 0) {
+ addr += 7;
+ hints.ai_family = PF_INET;
+ } else if (strncasecmp(addr, "tcp6://", 7) == 0) {
+ addr += 7;
+ hints.ai_family = PF_INET6;
+ } else if (strncasecmp(addr, "tcp://", 6) == 0) {
+ addr += 6;
+ } else {
+ /*
+ * Because TCP is the default assume IP or host is given without
+ * prefix.
+ */
+ }
+
+ /*
+ * Extract optional port.
+ * There are three cases to consider.
+ * 1. hostname with port, eg. freefall.freebsd.org:8457
+ * 2. IPv4 address with port, eg. 192.168.0.101:8457
+ * 3. IPv6 address with port, eg. [fe80::1]:8457
+ * We discover IPv6 address by checking for two colons and if port is
+ * given, the address has to start with [.
+ */
+ pp = NULL;
+ if (strchr(addr, ':') != strrchr(addr, ':')) {
+ if (addr[0] == '[')
+ pp = strrchr(addr, ':');
+ } else {
+ pp = strrchr(addr, ':');
+ }
+ if (pp == NULL) {
+ /* Port not given, use the default. */
+ port = defport;
+ } else {
+ if (numfromstr(pp + 1, 1, 65535, &port) == -1)
+ return (errno);
+ }
+ (void)snprintf(portstr, sizeof(portstr), "%jd", (intmax_t)port);
+ /* Extract host name or IP address. */
+ if (pp == NULL) {
+ size = sizeof(iporhost);
+ if (strlcpy(iporhost, addr, size) >= size)
+ return (ENAMETOOLONG);
+ } else if (addr[0] == '[' && pp[-1] == ']') {
+ size = (size_t)(pp - addr - 2 + 1);
+ if (size > sizeof(iporhost))
+ return (ENAMETOOLONG);
+ (void)strlcpy(iporhost, addr + 1, size);
+ } else {
+ size = (size_t)(pp - addr + 1);
+ if (size > sizeof(iporhost))
+ return (ENAMETOOLONG);
+ (void)strlcpy(iporhost, addr, size);
+ }
+
+ error = getaddrinfo(iporhost, portstr, &hints, &res);
+ if (error != 0) {
+ pjdlog_debug(1, "getaddrinfo(%s, %s) failed: %s.", iporhost,
+ portstr, gai_strerror(error));
+ return (EINVAL);
+ }
+ if (res == NULL)
+ return (ENOENT);
+
+ memcpy(sap, res->ai_addr, res->ai_addrlen);
+
+ freeaddrinfo(res);
+
+ return (0);
+}
+
+static int
+tcp_setup_new(const char *addr, int side, void **ctxp)
+{
+ struct tcp_ctx *tctx;
+ int ret, nodelay;
+
+ PJDLOG_ASSERT(addr != NULL);
+ PJDLOG_ASSERT(side == TCP_SIDE_CLIENT ||
+ side == TCP_SIDE_SERVER_LISTEN);
+ PJDLOG_ASSERT(ctxp != NULL);
+
+ tctx = malloc(sizeof(*tctx));
+ if (tctx == NULL)
+ return (errno);
+
+ /* Parse given address. */
+ if ((ret = tcp_addr(addr, PROTO_TCP_DEFAULT_PORT, &tctx->tc_sa)) != 0) {
+ free(tctx);
+ return (ret);
+ }
+
+ PJDLOG_ASSERT(tctx->tc_sa.ss_family != AF_UNSPEC);
+
+ tctx->tc_fd = socket(tctx->tc_sa.ss_family, SOCK_STREAM, 0);
+ if (tctx->tc_fd == -1) {
+ ret = errno;
+ free(tctx);
+ return (ret);
+ }
+
+ PJDLOG_ASSERT(tctx->tc_sa.ss_family != AF_UNSPEC);
+
+ /* Socket settings. */
+ nodelay = 1;
+ if (setsockopt(tctx->tc_fd, IPPROTO_TCP, TCP_NODELAY, &nodelay,
+ sizeof(nodelay)) == -1) {
+ pjdlog_errno(LOG_WARNING, "Unable to set TCP_NOELAY");
+ }
+
+ tctx->tc_side = side;
+ tctx->tc_magic = TCP_CTX_MAGIC;
+ *ctxp = tctx;
+
+ return (0);
+}
+
+static int
+tcp_setup_wrap(int fd, int side, void **ctxp)
+{
+ struct tcp_ctx *tctx;
+
+ PJDLOG_ASSERT(fd >= 0);
+ PJDLOG_ASSERT(side == TCP_SIDE_CLIENT ||
+ side == TCP_SIDE_SERVER_WORK);
+ PJDLOG_ASSERT(ctxp != NULL);
+
+ tctx = malloc(sizeof(*tctx));
+ if (tctx == NULL)
+ return (errno);
+
+ tctx->tc_fd = fd;
+ tctx->tc_sa.ss_family = AF_UNSPEC;
+ tctx->tc_side = side;
+ tctx->tc_magic = TCP_CTX_MAGIC;
+ *ctxp = tctx;
+
+ return (0);
+}
+
+static int
+tcp_client(const char *srcaddr, const char *dstaddr, void **ctxp)
+{
+ struct tcp_ctx *tctx;
+ struct sockaddr_storage sa;
+ int ret;
+
+ ret = tcp_setup_new(dstaddr, TCP_SIDE_CLIENT, ctxp);
+ if (ret != 0)
+ return (ret);
+ tctx = *ctxp;
+ if (srcaddr == NULL)
+ return (0);
+ ret = tcp_addr(srcaddr, 0, &sa);
+ if (ret != 0) {
+ tcp_close(tctx);
+ return (ret);
+ }
+ if (bind(tctx->tc_fd, (struct sockaddr *)&sa, sa.ss_len) == -1) {
+ ret = errno;
+ tcp_close(tctx);
+ return (ret);
+ }
+ return (0);
+}
+
+static int
+tcp_connect(void *ctx, int timeout)
+{
+ struct tcp_ctx *tctx = ctx;
+ int error, flags;
+
+ PJDLOG_ASSERT(tctx != NULL);
+ PJDLOG_ASSERT(tctx->tc_magic == TCP_CTX_MAGIC);
+ PJDLOG_ASSERT(tctx->tc_side == TCP_SIDE_CLIENT);
+ PJDLOG_ASSERT(tctx->tc_fd >= 0);
+ PJDLOG_ASSERT(tctx->tc_sa.ss_family != AF_UNSPEC);
+ PJDLOG_ASSERT(timeout >= -1);
+
+ flags = fcntl(tctx->tc_fd, F_GETFL);
+ if (flags == -1) {
+ pjdlog_common(LOG_DEBUG, 1, errno, "fcntl(F_GETFL) failed");
+ return (errno);
+ }
+ /*
+ * We make socket non-blocking so we can handle connection timeout
+ * manually.
+ */
+ flags |= O_NONBLOCK;
+ if (fcntl(tctx->tc_fd, F_SETFL, flags) == -1) {
+ pjdlog_common(LOG_DEBUG, 1, errno,
+ "fcntl(F_SETFL, O_NONBLOCK) failed");
+ return (errno);
+ }
+
+ if (connect(tctx->tc_fd, (struct sockaddr *)&tctx->tc_sa,
+ tctx->tc_sa.ss_len) == 0) {
+ if (timeout == -1)
+ return (0);
+ error = 0;
+ goto done;
+ }
+ if (errno != EINPROGRESS) {
+ error = errno;
+ pjdlog_common(LOG_DEBUG, 1, errno, "connect() failed");
+ goto done;
+ }
+ if (timeout == -1)
+ return (0);
+ return (tcp_connect_wait(ctx, timeout));
+done:
+ flags &= ~O_NONBLOCK;
+ if (fcntl(tctx->tc_fd, F_SETFL, flags) == -1) {
+ if (error == 0)
+ error = errno;
+ pjdlog_common(LOG_DEBUG, 1, errno,
+ "fcntl(F_SETFL, ~O_NONBLOCK) failed");
+ }
+ return (error);
+}
+
+static int
+tcp_connect_wait(void *ctx, int timeout)
+{
+ struct tcp_ctx *tctx = ctx;
+ struct timeval tv;
+ fd_set fdset;
+ socklen_t esize;
+ int error, flags, ret;
+
+ PJDLOG_ASSERT(tctx != NULL);
+ PJDLOG_ASSERT(tctx->tc_magic == TCP_CTX_MAGIC);
+ PJDLOG_ASSERT(tctx->tc_side == TCP_SIDE_CLIENT);
+ PJDLOG_ASSERT(tctx->tc_fd >= 0);
+ PJDLOG_ASSERT(timeout >= 0);
+
+ tv.tv_sec = timeout;
+ tv.tv_usec = 0;
+again:
+ FD_ZERO(&fdset);
+ FD_SET(tctx->tc_fd, &fdset);
+ ret = select(tctx->tc_fd + 1, NULL, &fdset, NULL, &tv);
+ if (ret == 0) {
+ error = ETIMEDOUT;
+ goto done;
+ } else if (ret == -1) {
+ if (errno == EINTR)
+ goto again;
+ error = errno;
+ pjdlog_common(LOG_DEBUG, 1, errno, "select() failed");
+ goto done;
+ }
+ PJDLOG_ASSERT(ret > 0);
+ PJDLOG_ASSERT(FD_ISSET(tctx->tc_fd, &fdset));
+ esize = sizeof(error);
+ if (getsockopt(tctx->tc_fd, SOL_SOCKET, SO_ERROR, &error,
+ &esize) == -1) {
+ error = errno;
+ pjdlog_common(LOG_DEBUG, 1, errno,
+ "getsockopt(SO_ERROR) failed");
+ goto done;
+ }
+ if (error != 0) {
+ pjdlog_common(LOG_DEBUG, 1, error,
+ "getsockopt(SO_ERROR) returned error");
+ goto done;
+ }
+ error = 0;
+done:
+ flags = fcntl(tctx->tc_fd, F_GETFL);
+ if (flags == -1) {
+ if (error == 0)
+ error = errno;
+ pjdlog_common(LOG_DEBUG, 1, errno, "fcntl(F_GETFL) failed");
+ return (error);
+ }
+ flags &= ~O_NONBLOCK;
+ if (fcntl(tctx->tc_fd, F_SETFL, flags) == -1) {
+ if (error == 0)
+ error = errno;
+ pjdlog_common(LOG_DEBUG, 1, errno,
+ "fcntl(F_SETFL, ~O_NONBLOCK) failed");
+ }
+ return (error);
+}
+
+static int
+tcp_server(const char *addr, void **ctxp)
+{
+ struct tcp_ctx *tctx;
+ int ret, val;
+
+ ret = tcp_setup_new(addr, TCP_SIDE_SERVER_LISTEN, ctxp);
+ if (ret != 0)
+ return (ret);
+
+ tctx = *ctxp;
+
+ val = 1;
+ /* Ignore failure. */
+ (void)setsockopt(tctx->tc_fd, SOL_SOCKET, SO_REUSEADDR, &val,
+ sizeof(val));
+
+ PJDLOG_ASSERT(tctx->tc_sa.ss_family != AF_UNSPEC);
+
+ if (bind(tctx->tc_fd, (struct sockaddr *)&tctx->tc_sa,
+ tctx->tc_sa.ss_len) == -1) {
+ ret = errno;
+ tcp_close(tctx);
+ return (ret);
+ }
+ if (listen(tctx->tc_fd, 8) == -1) {
+ ret = errno;
+ tcp_close(tctx);
+ return (ret);
+ }
+
+ return (0);
+}
+
+static int
+tcp_accept(void *ctx, void **newctxp)
+{
+ struct tcp_ctx *tctx = ctx;
+ struct tcp_ctx *newtctx;
+ socklen_t fromlen;
+ int ret;
+
+ PJDLOG_ASSERT(tctx != NULL);
+ PJDLOG_ASSERT(tctx->tc_magic == TCP_CTX_MAGIC);
+ PJDLOG_ASSERT(tctx->tc_side == TCP_SIDE_SERVER_LISTEN);
+ PJDLOG_ASSERT(tctx->tc_fd >= 0);
+ PJDLOG_ASSERT(tctx->tc_sa.ss_family != AF_UNSPEC);
+
+ newtctx = malloc(sizeof(*newtctx));
+ if (newtctx == NULL)
+ return (errno);
+
+ fromlen = tctx->tc_sa.ss_len;
+ newtctx->tc_fd = accept(tctx->tc_fd, (struct sockaddr *)&tctx->tc_sa,
+ &fromlen);
+ if (newtctx->tc_fd == -1) {
+ ret = errno;
+ free(newtctx);
+ return (ret);
+ }
+
+ newtctx->tc_side = TCP_SIDE_SERVER_WORK;
+ newtctx->tc_magic = TCP_CTX_MAGIC;
+ *newctxp = newtctx;
+
+ return (0);
+}
+
+static int
+tcp_wrap(int fd, bool client, void **ctxp)
+{
+
+ return (tcp_setup_wrap(fd,
+ client ? TCP_SIDE_CLIENT : TCP_SIDE_SERVER_WORK, ctxp));
+}
+
+static int
+tcp_send(void *ctx, const unsigned char *data, size_t size, int fd)
+{
+ struct tcp_ctx *tctx = ctx;
+
+ PJDLOG_ASSERT(tctx != NULL);
+ PJDLOG_ASSERT(tctx->tc_magic == TCP_CTX_MAGIC);
+ PJDLOG_ASSERT(tctx->tc_fd >= 0);
+ PJDLOG_ASSERT(fd == -1);
+
+ return (proto_common_send(tctx->tc_fd, data, size, -1));
+}
+
+static int
+tcp_recv(void *ctx, unsigned char *data, size_t size, int *fdp)
+{
+ struct tcp_ctx *tctx = ctx;
+
+ PJDLOG_ASSERT(tctx != NULL);
+ PJDLOG_ASSERT(tctx->tc_magic == TCP_CTX_MAGIC);
+ PJDLOG_ASSERT(tctx->tc_fd >= 0);
+ PJDLOG_ASSERT(fdp == NULL);
+
+ return (proto_common_recv(tctx->tc_fd, data, size, NULL));
+}
+
+static int
+tcp_descriptor(const void *ctx)
+{
+ const struct tcp_ctx *tctx = ctx;
+
+ PJDLOG_ASSERT(tctx != NULL);
+ PJDLOG_ASSERT(tctx->tc_magic == TCP_CTX_MAGIC);
+
+ return (tctx->tc_fd);
+}
+
+static bool
+tcp_address_match(const void *ctx, const char *addr)
+{
+ const struct tcp_ctx *tctx = ctx;
+ struct sockaddr_storage sa1, sa2;
+ socklen_t salen;
+
+ PJDLOG_ASSERT(tctx != NULL);
+ PJDLOG_ASSERT(tctx->tc_magic == TCP_CTX_MAGIC);
+
+ if (tcp_addr(addr, PROTO_TCP_DEFAULT_PORT, &sa1) != 0)
+ return (false);
+
+ salen = sizeof(sa2);
+ if (getpeername(tctx->tc_fd, (struct sockaddr *)&sa2, &salen) == -1)
+ return (false);
+
+ if (sa1.ss_family != sa2.ss_family || sa1.ss_len != sa2.ss_len)
+ return (false);
+
+ switch (sa1.ss_family) {
+ case AF_INET:
+ {
+ struct sockaddr_in *sin1, *sin2;
+
+ sin1 = (struct sockaddr_in *)&sa1;
+ sin2 = (struct sockaddr_in *)&sa2;
+
+ return (memcmp(&sin1->sin_addr, &sin2->sin_addr,
+ sizeof(sin1->sin_addr)) == 0);
+ }
+ case AF_INET6:
+ {
+ struct sockaddr_in6 *sin1, *sin2;
+
+ sin1 = (struct sockaddr_in6 *)&sa1;
+ sin2 = (struct sockaddr_in6 *)&sa2;
+
+ return (memcmp(&sin1->sin6_addr, &sin2->sin6_addr,
+ sizeof(sin1->sin6_addr)) == 0);
+ }
+ default:
+ return (false);
+ }
+}
+
+static void
+tcp_local_address(const void *ctx, char *addr, size_t size)
+{
+ const struct tcp_ctx *tctx = ctx;
+ struct sockaddr_storage sa;
+ socklen_t salen;
+
+ PJDLOG_ASSERT(tctx != NULL);
+ PJDLOG_ASSERT(tctx->tc_magic == TCP_CTX_MAGIC);
+
+ salen = sizeof(sa);
+ if (getsockname(tctx->tc_fd, (struct sockaddr *)&sa, &salen) == -1) {
+ PJDLOG_VERIFY(strlcpy(addr, "N/A", size) < size);
+ return;
+ }
+ PJDLOG_VERIFY(snprintf(addr, size, "tcp://%S", &sa) < (ssize_t)size);
+}
+
+static void
+tcp_remote_address(const void *ctx, char *addr, size_t size)
+{
+ const struct tcp_ctx *tctx = ctx;
+ struct sockaddr_storage sa;
+ socklen_t salen;
+
+ PJDLOG_ASSERT(tctx != NULL);
+ PJDLOG_ASSERT(tctx->tc_magic == TCP_CTX_MAGIC);
+
+ salen = sizeof(sa);
+ if (getpeername(tctx->tc_fd, (struct sockaddr *)&sa, &salen) == -1) {
+ PJDLOG_VERIFY(strlcpy(addr, "N/A", size) < size);
+ return;
+ }
+ PJDLOG_VERIFY(snprintf(addr, size, "tcp://%S", &sa) < (ssize_t)size);
+}
+
+static void
+tcp_close(void *ctx)
+{
+ struct tcp_ctx *tctx = ctx;
+
+ PJDLOG_ASSERT(tctx != NULL);
+ PJDLOG_ASSERT(tctx->tc_magic == TCP_CTX_MAGIC);
+
+ if (tctx->tc_fd >= 0)
+ close(tctx->tc_fd);
+ tctx->tc_magic = 0;
+ free(tctx);
+}
+
+static struct proto tcp_proto = {
+ .prt_name = "tcp",
+ .prt_client = tcp_client,
+ .prt_connect = tcp_connect,
+ .prt_connect_wait = tcp_connect_wait,
+ .prt_server = tcp_server,
+ .prt_accept = tcp_accept,
+ .prt_wrap = tcp_wrap,
+ .prt_send = tcp_send,
+ .prt_recv = tcp_recv,
+ .prt_descriptor = tcp_descriptor,
+ .prt_address_match = tcp_address_match,
+ .prt_local_address = tcp_local_address,
+ .prt_remote_address = tcp_remote_address,
+ .prt_close = tcp_close
+};
+
+static __constructor void
+tcp_ctor(void)
+{
+
+ proto_register(&tcp_proto, true);
+}
diff --git a/sbin/hastd/proto_uds.c b/sbin/hastd/proto_uds.c
new file mode 100644
index 0000000..087b788
--- /dev/null
+++ b/sbin/hastd/proto_uds.c
@@ -0,0 +1,361 @@
+/*-
+ * Copyright (c) 2009-2010 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/* UDS - UNIX Domain Socket */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "pjdlog.h"
+#include "proto_impl.h"
+
+#define UDS_CTX_MAGIC 0xd541c
+struct uds_ctx {
+ int uc_magic;
+ struct sockaddr_un uc_sun;
+ int uc_fd;
+ int uc_side;
+#define UDS_SIDE_CLIENT 0
+#define UDS_SIDE_SERVER_LISTEN 1
+#define UDS_SIDE_SERVER_WORK 2
+ pid_t uc_owner;
+};
+
+static void uds_close(void *ctx);
+
+static int
+uds_addr(const char *addr, struct sockaddr_un *sunp)
+{
+
+ if (addr == NULL)
+ return (-1);
+
+ if (strncasecmp(addr, "uds://", 6) == 0)
+ addr += 6;
+ else if (strncasecmp(addr, "unix://", 7) == 0)
+ addr += 7;
+ else if (addr[0] == '/' && /* If it starts from /... */
+ strstr(addr, "://") == NULL)/* ...and there is no prefix... */
+ ; /* ...we assume its us. */
+ else
+ return (-1);
+
+ sunp->sun_family = AF_UNIX;
+ if (strlcpy(sunp->sun_path, addr, sizeof(sunp->sun_path)) >=
+ sizeof(sunp->sun_path)) {
+ return (ENAMETOOLONG);
+ }
+ sunp->sun_len = SUN_LEN(sunp);
+
+ return (0);
+}
+
+static int
+uds_common_setup(const char *addr, void **ctxp, int side)
+{
+ struct uds_ctx *uctx;
+ int ret;
+
+ uctx = malloc(sizeof(*uctx));
+ if (uctx == NULL)
+ return (errno);
+
+ /* Parse given address. */
+ if ((ret = uds_addr(addr, &uctx->uc_sun)) != 0) {
+ free(uctx);
+ return (ret);
+ }
+
+ uctx->uc_fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (uctx->uc_fd == -1) {
+ ret = errno;
+ free(uctx);
+ return (ret);
+ }
+
+ uctx->uc_side = side;
+ uctx->uc_owner = 0;
+ uctx->uc_magic = UDS_CTX_MAGIC;
+ *ctxp = uctx;
+
+ return (0);
+}
+
+static int
+uds_client(const char *srcaddr, const char *dstaddr, void **ctxp)
+{
+ int ret;
+
+ ret = uds_common_setup(dstaddr, ctxp, UDS_SIDE_CLIENT);
+ if (ret != 0)
+ return (ret);
+
+ PJDLOG_ASSERT(srcaddr == NULL);
+
+ return (0);
+}
+
+static int
+uds_connect(void *ctx, int timeout)
+{
+ struct uds_ctx *uctx = ctx;
+
+ PJDLOG_ASSERT(uctx != NULL);
+ PJDLOG_ASSERT(uctx->uc_magic == UDS_CTX_MAGIC);
+ PJDLOG_ASSERT(uctx->uc_side == UDS_SIDE_CLIENT);
+ PJDLOG_ASSERT(uctx->uc_fd >= 0);
+ PJDLOG_ASSERT(timeout >= -1);
+
+ if (connect(uctx->uc_fd, (struct sockaddr *)&uctx->uc_sun,
+ sizeof(uctx->uc_sun)) == -1) {
+ return (errno);
+ }
+
+ return (0);
+}
+
+static int
+uds_connect_wait(void *ctx, int timeout)
+{
+ struct uds_ctx *uctx = ctx;
+
+ PJDLOG_ASSERT(uctx != NULL);
+ PJDLOG_ASSERT(uctx->uc_magic == UDS_CTX_MAGIC);
+ PJDLOG_ASSERT(uctx->uc_side == UDS_SIDE_CLIENT);
+ PJDLOG_ASSERT(uctx->uc_fd >= 0);
+ PJDLOG_ASSERT(timeout >= 0);
+
+ return (0);
+}
+
+static int
+uds_server(const char *addr, void **ctxp)
+{
+ struct uds_ctx *uctx;
+ int ret;
+
+ ret = uds_common_setup(addr, ctxp, UDS_SIDE_SERVER_LISTEN);
+ if (ret != 0)
+ return (ret);
+
+ uctx = *ctxp;
+
+ (void)unlink(uctx->uc_sun.sun_path);
+ if (bind(uctx->uc_fd, (struct sockaddr *)&uctx->uc_sun,
+ sizeof(uctx->uc_sun)) == -1) {
+ ret = errno;
+ uds_close(uctx);
+ return (ret);
+ }
+ uctx->uc_owner = getpid();
+ if (listen(uctx->uc_fd, 8) == -1) {
+ ret = errno;
+ uds_close(uctx);
+ return (ret);
+ }
+
+ return (0);
+}
+
+static int
+uds_accept(void *ctx, void **newctxp)
+{
+ struct uds_ctx *uctx = ctx;
+ struct uds_ctx *newuctx;
+ socklen_t fromlen;
+ int ret;
+
+ PJDLOG_ASSERT(uctx != NULL);
+ PJDLOG_ASSERT(uctx->uc_magic == UDS_CTX_MAGIC);
+ PJDLOG_ASSERT(uctx->uc_side == UDS_SIDE_SERVER_LISTEN);
+ PJDLOG_ASSERT(uctx->uc_fd >= 0);
+
+ newuctx = malloc(sizeof(*newuctx));
+ if (newuctx == NULL)
+ return (errno);
+
+ fromlen = sizeof(newuctx->uc_sun);
+ newuctx->uc_fd = accept(uctx->uc_fd,
+ (struct sockaddr *)&newuctx->uc_sun, &fromlen);
+ if (newuctx->uc_fd == -1) {
+ ret = errno;
+ free(newuctx);
+ return (ret);
+ }
+
+ newuctx->uc_side = UDS_SIDE_SERVER_WORK;
+ newuctx->uc_magic = UDS_CTX_MAGIC;
+ *newctxp = newuctx;
+
+ return (0);
+}
+
+static int
+uds_send(void *ctx, const unsigned char *data, size_t size, int fd)
+{
+ struct uds_ctx *uctx = ctx;
+
+ PJDLOG_ASSERT(uctx != NULL);
+ PJDLOG_ASSERT(uctx->uc_magic == UDS_CTX_MAGIC);
+ PJDLOG_ASSERT(uctx->uc_fd >= 0);
+
+ return (proto_common_send(uctx->uc_fd, data, size, fd));
+}
+
+static int
+uds_recv(void *ctx, unsigned char *data, size_t size, int *fdp)
+{
+ struct uds_ctx *uctx = ctx;
+
+ PJDLOG_ASSERT(uctx != NULL);
+ PJDLOG_ASSERT(uctx->uc_magic == UDS_CTX_MAGIC);
+ PJDLOG_ASSERT(uctx->uc_fd >= 0);
+
+ return (proto_common_recv(uctx->uc_fd, data, size, fdp));
+}
+
+static int
+uds_descriptor(const void *ctx)
+{
+ const struct uds_ctx *uctx = ctx;
+
+ PJDLOG_ASSERT(uctx != NULL);
+ PJDLOG_ASSERT(uctx->uc_magic == UDS_CTX_MAGIC);
+
+ return (uctx->uc_fd);
+}
+
+static void
+uds_local_address(const void *ctx, char *addr, size_t size)
+{
+ const struct uds_ctx *uctx = ctx;
+ struct sockaddr_un sun;
+ socklen_t sunlen;
+
+ PJDLOG_ASSERT(uctx != NULL);
+ PJDLOG_ASSERT(uctx->uc_magic == UDS_CTX_MAGIC);
+ PJDLOG_ASSERT(addr != NULL);
+
+ sunlen = sizeof(sun);
+ if (getsockname(uctx->uc_fd, (struct sockaddr *)&sun, &sunlen) == -1) {
+ PJDLOG_VERIFY(strlcpy(addr, "N/A", size) < size);
+ return;
+ }
+ PJDLOG_ASSERT(sun.sun_family == AF_UNIX);
+ if (sun.sun_path[0] == '\0') {
+ PJDLOG_VERIFY(strlcpy(addr, "N/A", size) < size);
+ return;
+ }
+ PJDLOG_VERIFY(snprintf(addr, size, "uds://%s", sun.sun_path) < (ssize_t)size);
+}
+
+static void
+uds_remote_address(const void *ctx, char *addr, size_t size)
+{
+ const struct uds_ctx *uctx = ctx;
+ struct sockaddr_un sun;
+ socklen_t sunlen;
+
+ PJDLOG_ASSERT(uctx != NULL);
+ PJDLOG_ASSERT(uctx->uc_magic == UDS_CTX_MAGIC);
+ PJDLOG_ASSERT(addr != NULL);
+
+ sunlen = sizeof(sun);
+ if (getpeername(uctx->uc_fd, (struct sockaddr *)&sun, &sunlen) == -1) {
+ PJDLOG_VERIFY(strlcpy(addr, "N/A", size) < size);
+ return;
+ }
+ PJDLOG_ASSERT(sun.sun_family == AF_UNIX);
+ if (sun.sun_path[0] == '\0') {
+ PJDLOG_VERIFY(strlcpy(addr, "N/A", size) < size);
+ return;
+ }
+ snprintf(addr, size, "uds://%s", sun.sun_path);
+}
+
+static void
+uds_close(void *ctx)
+{
+ struct uds_ctx *uctx = ctx;
+
+ PJDLOG_ASSERT(uctx != NULL);
+ PJDLOG_ASSERT(uctx->uc_magic == UDS_CTX_MAGIC);
+
+ if (uctx->uc_fd >= 0)
+ close(uctx->uc_fd);
+ /*
+ * Unlink the socket only if we are the owner and this is descriptor
+ * we listen on.
+ */
+ if (uctx->uc_side == UDS_SIDE_SERVER_LISTEN &&
+ uctx->uc_owner == getpid()) {
+ PJDLOG_ASSERT(uctx->uc_sun.sun_path[0] != '\0');
+ if (unlink(uctx->uc_sun.sun_path) == -1) {
+ pjdlog_errno(LOG_WARNING,
+ "Unable to unlink socket file %s",
+ uctx->uc_sun.sun_path);
+ }
+ }
+ uctx->uc_owner = 0;
+ uctx->uc_magic = 0;
+ free(uctx);
+}
+
+static struct proto uds_proto = {
+ .prt_name = "uds",
+ .prt_client = uds_client,
+ .prt_connect = uds_connect,
+ .prt_connect_wait = uds_connect_wait,
+ .prt_server = uds_server,
+ .prt_accept = uds_accept,
+ .prt_send = uds_send,
+ .prt_recv = uds_recv,
+ .prt_descriptor = uds_descriptor,
+ .prt_local_address = uds_local_address,
+ .prt_remote_address = uds_remote_address,
+ .prt_close = uds_close
+};
+
+static __constructor void
+uds_ctor(void)
+{
+
+ proto_register(&uds_proto, false);
+}
diff --git a/sbin/hastd/rangelock.c b/sbin/hastd/rangelock.c
new file mode 100644
index 0000000..e14c5b8
--- /dev/null
+++ b/sbin/hastd/rangelock.c
@@ -0,0 +1,141 @@
+/*-
+ * Copyright (c) 2010 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/queue.h>
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <pjdlog.h>
+
+#include "rangelock.h"
+
+#ifndef PJDLOG_ASSERT
+#include <assert.h>
+#define PJDLOG_ASSERT(...) assert(__VA_ARGS__)
+#endif
+
+#define RANGELOCKS_MAGIC 0x94310c
+struct rangelocks {
+ int rls_magic; /* Magic value. */
+ TAILQ_HEAD(, rlock) rls_locks; /* List of locked ranges. */
+};
+
+struct rlock {
+ off_t rl_start;
+ off_t rl_end;
+ TAILQ_ENTRY(rlock) rl_next;
+};
+
+int
+rangelock_init(struct rangelocks **rlsp)
+{
+ struct rangelocks *rls;
+
+ PJDLOG_ASSERT(rlsp != NULL);
+
+ rls = malloc(sizeof(*rls));
+ if (rls == NULL)
+ return (-1);
+
+ TAILQ_INIT(&rls->rls_locks);
+
+ rls->rls_magic = RANGELOCKS_MAGIC;
+ *rlsp = rls;
+
+ return (0);
+}
+
+void
+rangelock_free(struct rangelocks *rls)
+{
+ struct rlock *rl;
+
+ PJDLOG_ASSERT(rls->rls_magic == RANGELOCKS_MAGIC);
+
+ rls->rls_magic = 0;
+
+ while ((rl = TAILQ_FIRST(&rls->rls_locks)) != NULL) {
+ TAILQ_REMOVE(&rls->rls_locks, rl, rl_next);
+ free(rl);
+ }
+ free(rls);
+}
+
+int
+rangelock_add(struct rangelocks *rls, off_t offset, off_t length)
+{
+ struct rlock *rl;
+
+ PJDLOG_ASSERT(rls->rls_magic == RANGELOCKS_MAGIC);
+
+ rl = malloc(sizeof(*rl));
+ if (rl == NULL)
+ return (-1);
+ rl->rl_start = offset;
+ rl->rl_end = offset + length;
+ TAILQ_INSERT_TAIL(&rls->rls_locks, rl, rl_next);
+ return (0);
+}
+
+void
+rangelock_del(struct rangelocks *rls, off_t offset, off_t length)
+{
+ struct rlock *rl;
+
+ PJDLOG_ASSERT(rls->rls_magic == RANGELOCKS_MAGIC);
+
+ TAILQ_FOREACH(rl, &rls->rls_locks, rl_next) {
+ if (rl->rl_start == offset && rl->rl_end == offset + length)
+ break;
+ }
+ PJDLOG_ASSERT(rl != NULL);
+ TAILQ_REMOVE(&rls->rls_locks, rl, rl_next);
+ free(rl);
+}
+
+bool
+rangelock_islocked(struct rangelocks *rls, off_t offset, off_t length)
+{
+ struct rlock *rl;
+ off_t end;
+
+ PJDLOG_ASSERT(rls->rls_magic == RANGELOCKS_MAGIC);
+
+ end = offset + length;
+ TAILQ_FOREACH(rl, &rls->rls_locks, rl_next) {
+ if (rl->rl_start < end && rl->rl_end > offset)
+ break;
+ }
+ return (rl != NULL);
+}
diff --git a/sbin/hastd/rangelock.h b/sbin/hastd/rangelock.h
new file mode 100644
index 0000000..2ad9895
--- /dev/null
+++ b/sbin/hastd/rangelock.h
@@ -0,0 +1,46 @@
+/*-
+ * Copyright (c) 2010 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _RANGELOCK_H_
+#define _RANGELOCK_H_
+
+#include <stdbool.h>
+#include <unistd.h>
+
+struct rangelocks;
+
+int rangelock_init(struct rangelocks **rlsp);
+void rangelock_free(struct rangelocks *rls);
+int rangelock_add(struct rangelocks *rls, off_t offset, off_t length);
+void rangelock_del(struct rangelocks *rls, off_t offset, off_t length);
+bool rangelock_islocked(struct rangelocks *rls, off_t offset, off_t length);
+
+#endif /* !_RANGELOCK_H_ */
diff --git a/sbin/hastd/refcnt.h b/sbin/hastd/refcnt.h
new file mode 100644
index 0000000..11801cb
--- /dev/null
+++ b/sbin/hastd/refcnt.h
@@ -0,0 +1,63 @@
+/*-
+ * Copyright (c) 2005 John Baldwin <jhb@FreeBSD.org>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef __REFCNT_H__
+#define __REFCNT_H__
+
+#include <machine/atomic.h>
+
+#include "pjdlog.h"
+
+typedef unsigned int refcnt_t;
+
+static __inline void
+refcnt_init(refcnt_t *count, unsigned int v)
+{
+
+ *count = v;
+}
+
+static __inline void
+refcnt_acquire(refcnt_t *count)
+{
+
+ atomic_add_acq_int(count, 1);
+}
+
+static __inline unsigned int
+refcnt_release(refcnt_t *count)
+{
+ unsigned int old;
+
+ /* XXX: Should this have a rel membar? */
+ old = atomic_fetchadd_int(count, -1);
+ PJDLOG_ASSERT(old > 0);
+ return (old - 1);
+}
+
+#endif /* ! __REFCNT_H__ */
diff --git a/sbin/hastd/secondary.c b/sbin/hastd/secondary.c
new file mode 100644
index 0000000..5e1207a
--- /dev/null
+++ b/sbin/hastd/secondary.c
@@ -0,0 +1,929 @@
+/*-
+ * Copyright (c) 2009-2010 The FreeBSD Foundation
+ * Copyright (c) 2010 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+ * All rights reserved.
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/time.h>
+#include <sys/bio.h>
+#include <sys/disk.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgeom.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include <activemap.h>
+#include <nv.h>
+#include <pjdlog.h>
+
+#include "control.h"
+#include "event.h"
+#include "hast.h"
+#include "hast_proto.h"
+#include "hastd.h"
+#include "hooks.h"
+#include "metadata.h"
+#include "proto.h"
+#include "subr.h"
+#include "synch.h"
+
+struct hio {
+ uint64_t hio_seq;
+ int hio_error;
+ void *hio_data;
+ uint8_t hio_cmd;
+ uint64_t hio_offset;
+ uint64_t hio_length;
+ bool hio_memsync;
+ TAILQ_ENTRY(hio) hio_next;
+};
+
+static struct hast_resource *gres;
+
+/*
+ * Free list holds unused structures. When free list is empty, we have to wait
+ * until some in-progress requests are freed.
+ */
+static TAILQ_HEAD(, hio) hio_free_list;
+static size_t hio_free_list_size;
+static pthread_mutex_t hio_free_list_lock;
+static pthread_cond_t hio_free_list_cond;
+/*
+ * Disk thread (the one that does I/O requests) takes requests from this list.
+ */
+static TAILQ_HEAD(, hio) hio_disk_list;
+static size_t hio_disk_list_size;
+static pthread_mutex_t hio_disk_list_lock;
+static pthread_cond_t hio_disk_list_cond;
+/*
+ * Thread that sends requests back to primary takes requests from this list.
+ */
+static TAILQ_HEAD(, hio) hio_send_list;
+static size_t hio_send_list_size;
+static pthread_mutex_t hio_send_list_lock;
+static pthread_cond_t hio_send_list_cond;
+
+/*
+ * Maximum number of outstanding I/O requests.
+ */
+#define HAST_HIO_MAX 256
+
+static void *recv_thread(void *arg);
+static void *disk_thread(void *arg);
+static void *send_thread(void *arg);
+
+#define QUEUE_INSERT(name, hio) do { \
+ mtx_lock(&hio_##name##_list_lock); \
+ if (TAILQ_EMPTY(&hio_##name##_list)) \
+ cv_broadcast(&hio_##name##_list_cond); \
+ TAILQ_INSERT_TAIL(&hio_##name##_list, (hio), hio_next); \
+ hio_##name##_list_size++; \
+ mtx_unlock(&hio_##name##_list_lock); \
+} while (0)
+#define QUEUE_TAKE(name, hio) do { \
+ mtx_lock(&hio_##name##_list_lock); \
+ while (((hio) = TAILQ_FIRST(&hio_##name##_list)) == NULL) { \
+ cv_wait(&hio_##name##_list_cond, \
+ &hio_##name##_list_lock); \
+ } \
+ PJDLOG_ASSERT(hio_##name##_list_size != 0); \
+ hio_##name##_list_size--; \
+ TAILQ_REMOVE(&hio_##name##_list, (hio), hio_next); \
+ mtx_unlock(&hio_##name##_list_lock); \
+} while (0)
+
+static void
+output_status_aux(struct nv *nvout)
+{
+
+ nv_add_uint64(nvout, (uint64_t)hio_free_list_size, "idle_queue_size");
+ nv_add_uint64(nvout, (uint64_t)hio_disk_list_size, "local_queue_size");
+ nv_add_uint64(nvout, (uint64_t)hio_send_list_size, "send_queue_size");
+}
+
+static void
+hio_clear(struct hio *hio)
+{
+
+ hio->hio_seq = 0;
+ hio->hio_error = 0;
+ hio->hio_cmd = HIO_UNDEF;
+ hio->hio_offset = 0;
+ hio->hio_length = 0;
+ hio->hio_memsync = false;
+}
+
+static void
+hio_copy(const struct hio *srchio, struct hio *dsthio)
+{
+
+ /*
+ * We don't copy hio_error, hio_data and hio_next fields.
+ */
+
+ dsthio->hio_seq = srchio->hio_seq;
+ dsthio->hio_cmd = srchio->hio_cmd;
+ dsthio->hio_offset = srchio->hio_offset;
+ dsthio->hio_length = srchio->hio_length;
+ dsthio->hio_memsync = srchio->hio_memsync;
+}
+
+static void
+init_environment(void)
+{
+ struct hio *hio;
+ unsigned int ii;
+
+ /*
+ * Initialize lists, their locks and theirs condition variables.
+ */
+ TAILQ_INIT(&hio_free_list);
+ mtx_init(&hio_free_list_lock);
+ cv_init(&hio_free_list_cond);
+ TAILQ_INIT(&hio_disk_list);
+ mtx_init(&hio_disk_list_lock);
+ cv_init(&hio_disk_list_cond);
+ TAILQ_INIT(&hio_send_list);
+ mtx_init(&hio_send_list_lock);
+ cv_init(&hio_send_list_cond);
+
+ /*
+ * Allocate requests pool and initialize requests.
+ */
+ for (ii = 0; ii < HAST_HIO_MAX; ii++) {
+ hio = malloc(sizeof(*hio));
+ if (hio == NULL) {
+ pjdlog_exitx(EX_TEMPFAIL,
+ "Unable to allocate memory (%zu bytes) for hio request.",
+ sizeof(*hio));
+ }
+ hio->hio_data = malloc(MAXPHYS);
+ if (hio->hio_data == NULL) {
+ pjdlog_exitx(EX_TEMPFAIL,
+ "Unable to allocate memory (%zu bytes) for gctl_data.",
+ (size_t)MAXPHYS);
+ }
+ hio_clear(hio);
+ TAILQ_INSERT_HEAD(&hio_free_list, hio, hio_next);
+ hio_free_list_size++;
+ }
+}
+
+static void
+init_local(struct hast_resource *res)
+{
+
+ if (metadata_read(res, true) == -1)
+ exit(EX_NOINPUT);
+}
+
+static void
+init_remote(struct hast_resource *res, struct nv *nvin)
+{
+ uint64_t resuid;
+ struct nv *nvout;
+ unsigned char *map;
+ size_t mapsize;
+
+#ifdef notyet
+ /* Setup direction. */
+ if (proto_send(res->hr_remoteout, NULL, 0) == -1)
+ pjdlog_errno(LOG_WARNING, "Unable to set connection direction");
+#endif
+
+ nvout = nv_alloc();
+ nv_add_int64(nvout, (int64_t)res->hr_datasize, "datasize");
+ nv_add_int32(nvout, (int32_t)res->hr_extentsize, "extentsize");
+ resuid = nv_get_uint64(nvin, "resuid");
+ res->hr_primary_localcnt = nv_get_uint64(nvin, "localcnt");
+ res->hr_primary_remotecnt = nv_get_uint64(nvin, "remotecnt");
+ nv_add_uint64(nvout, res->hr_secondary_localcnt, "localcnt");
+ nv_add_uint64(nvout, res->hr_secondary_remotecnt, "remotecnt");
+ mapsize = activemap_calc_ondisk_size(res->hr_local_mediasize -
+ METADATA_SIZE, res->hr_extentsize, res->hr_local_sectorsize);
+ map = malloc(mapsize);
+ if (map == NULL) {
+ pjdlog_exitx(EX_TEMPFAIL,
+ "Unable to allocate memory (%zu bytes) for activemap.",
+ mapsize);
+ }
+ /*
+ * When we work as primary and secondary is missing we will increase
+ * localcnt in our metadata. When secondary is connected and synced
+ * we make localcnt be equal to remotecnt, which means nodes are more
+ * or less in sync.
+ * Split-brain condition is when both nodes are not able to communicate
+ * and are both configured as primary nodes. In turn, they can both
+ * make incompatible changes to the data and we have to detect that.
+ * Under split-brain condition we will increase our localcnt on first
+ * write and remote node will increase its localcnt on first write.
+ * When we connect we can see that primary's localcnt is greater than
+ * our remotecnt (primary was modified while we weren't watching) and
+ * our localcnt is greater than primary's remotecnt (we were modified
+ * while primary wasn't watching).
+ * There are many possible combinations which are all gathered below.
+ * Don't pay too much attention to exact numbers, the more important
+ * is to compare them. We compare secondary's local with primary's
+ * remote and secondary's remote with primary's local.
+ * Note that every case where primary's localcnt is smaller than
+ * secondary's remotecnt and where secondary's localcnt is smaller than
+ * primary's remotecnt should be impossible in practise. We will perform
+ * full synchronization then. Those cases are marked with an asterisk.
+ * Regular synchronization means that only extents marked as dirty are
+ * synchronized (regular synchronization).
+ *
+ * SECONDARY METADATA PRIMARY METADATA
+ * local=3 remote=3 local=2 remote=2* ?! Full sync from secondary.
+ * local=3 remote=3 local=2 remote=3* ?! Full sync from primary.
+ * local=3 remote=3 local=2 remote=4* ?! Full sync from primary.
+ * local=3 remote=3 local=3 remote=2 Primary is out-of-date,
+ * regular sync from secondary.
+ * local=3 remote=3 local=3 remote=3 Regular sync just in case.
+ * local=3 remote=3 local=3 remote=4* ?! Full sync from primary.
+ * local=3 remote=3 local=4 remote=2 Split-brain condition.
+ * local=3 remote=3 local=4 remote=3 Secondary out-of-date,
+ * regular sync from primary.
+ * local=3 remote=3 local=4 remote=4* ?! Full sync from primary.
+ */
+ if (res->hr_resuid == 0) {
+ /*
+ * Provider is used for the first time. If primary node done no
+ * writes yet as well (we will find "virgin" argument) then
+ * there is no need to synchronize anything. If primary node
+ * done any writes already we have to synchronize everything.
+ */
+ PJDLOG_ASSERT(res->hr_secondary_localcnt == 0);
+ res->hr_resuid = resuid;
+ if (metadata_write(res) == -1)
+ exit(EX_NOINPUT);
+ if (nv_exists(nvin, "virgin")) {
+ free(map);
+ map = NULL;
+ mapsize = 0;
+ } else {
+ memset(map, 0xff, mapsize);
+ }
+ nv_add_int8(nvout, 1, "virgin");
+ nv_add_uint8(nvout, HAST_SYNCSRC_PRIMARY, "syncsrc");
+ } else if (res->hr_resuid != resuid) {
+ char errmsg[256];
+
+ free(map);
+ (void)snprintf(errmsg, sizeof(errmsg),
+ "Resource unique ID mismatch (primary=%ju, secondary=%ju).",
+ (uintmax_t)resuid, (uintmax_t)res->hr_resuid);
+ pjdlog_error("%s", errmsg);
+ nv_add_string(nvout, errmsg, "errmsg");
+ if (hast_proto_send(res, res->hr_remotein, nvout,
+ NULL, 0) == -1) {
+ pjdlog_exit(EX_TEMPFAIL,
+ "Unable to send response to %s",
+ res->hr_remoteaddr);
+ }
+ nv_free(nvout);
+ exit(EX_CONFIG);
+ } else if (
+ /* Is primary out-of-date? */
+ (res->hr_secondary_localcnt > res->hr_primary_remotecnt &&
+ res->hr_secondary_remotecnt == res->hr_primary_localcnt) ||
+ /* Are the nodes more or less in sync? */
+ (res->hr_secondary_localcnt == res->hr_primary_remotecnt &&
+ res->hr_secondary_remotecnt == res->hr_primary_localcnt) ||
+ /* Is secondary out-of-date? */
+ (res->hr_secondary_localcnt == res->hr_primary_remotecnt &&
+ res->hr_secondary_remotecnt < res->hr_primary_localcnt)) {
+ /*
+ * Nodes are more or less in sync or one of the nodes is
+ * out-of-date.
+ * It doesn't matter at this point which one, we just have to
+ * send out local bitmap to the remote node.
+ */
+ if (pread(res->hr_localfd, map, mapsize, METADATA_SIZE) !=
+ (ssize_t)mapsize) {
+ pjdlog_exit(LOG_ERR, "Unable to read activemap");
+ }
+ if (res->hr_secondary_localcnt > res->hr_primary_remotecnt &&
+ res->hr_secondary_remotecnt == res->hr_primary_localcnt) {
+ /* Primary is out-of-date, sync from secondary. */
+ nv_add_uint8(nvout, HAST_SYNCSRC_SECONDARY, "syncsrc");
+ } else {
+ /*
+ * Secondary is out-of-date or counts match.
+ * Sync from primary.
+ */
+ nv_add_uint8(nvout, HAST_SYNCSRC_PRIMARY, "syncsrc");
+ }
+ } else if (res->hr_secondary_localcnt > res->hr_primary_remotecnt &&
+ res->hr_primary_localcnt > res->hr_secondary_remotecnt) {
+ /*
+ * Not good, we have split-brain condition.
+ */
+ free(map);
+ pjdlog_error("Split-brain detected, exiting.");
+ nv_add_string(nvout, "Split-brain condition!", "errmsg");
+ if (hast_proto_send(res, res->hr_remotein, nvout,
+ NULL, 0) == -1) {
+ pjdlog_exit(EX_TEMPFAIL,
+ "Unable to send response to %s",
+ res->hr_remoteaddr);
+ }
+ nv_free(nvout);
+ /* Exit on split-brain. */
+ event_send(res, EVENT_SPLITBRAIN);
+ exit(EX_CONFIG);
+ } else /* if (res->hr_secondary_localcnt < res->hr_primary_remotecnt ||
+ res->hr_primary_localcnt < res->hr_secondary_remotecnt) */ {
+ /*
+ * This should never happen in practise, but we will perform
+ * full synchronization.
+ */
+ PJDLOG_ASSERT(res->hr_secondary_localcnt < res->hr_primary_remotecnt ||
+ res->hr_primary_localcnt < res->hr_secondary_remotecnt);
+ mapsize = activemap_calc_ondisk_size(res->hr_local_mediasize -
+ METADATA_SIZE, res->hr_extentsize,
+ res->hr_local_sectorsize);
+ memset(map, 0xff, mapsize);
+ if (res->hr_secondary_localcnt > res->hr_primary_remotecnt) {
+ /* In this one of five cases sync from secondary. */
+ nv_add_uint8(nvout, HAST_SYNCSRC_SECONDARY, "syncsrc");
+ } else {
+ /* For the rest four cases sync from primary. */
+ nv_add_uint8(nvout, HAST_SYNCSRC_PRIMARY, "syncsrc");
+ }
+ pjdlog_warning("This should never happen, asking for full synchronization (primary(local=%ju, remote=%ju), secondary(local=%ju, remote=%ju)).",
+ (uintmax_t)res->hr_primary_localcnt,
+ (uintmax_t)res->hr_primary_remotecnt,
+ (uintmax_t)res->hr_secondary_localcnt,
+ (uintmax_t)res->hr_secondary_remotecnt);
+ }
+ nv_add_uint32(nvout, (uint32_t)mapsize, "mapsize");
+ if (hast_proto_send(res, res->hr_remotein, nvout, map, mapsize) == -1) {
+ pjdlog_exit(EX_TEMPFAIL, "Unable to send activemap to %s",
+ res->hr_remoteaddr);
+ }
+ if (map != NULL)
+ free(map);
+ nv_free(nvout);
+#ifdef notyet
+ /* Setup direction. */
+ if (proto_recv(res->hr_remotein, NULL, 0) == -1)
+ pjdlog_errno(LOG_WARNING, "Unable to set connection direction");
+#endif
+}
+
+void
+hastd_secondary(struct hast_resource *res, struct nv *nvin)
+{
+ sigset_t mask;
+ pthread_t td;
+ pid_t pid;
+ int error, mode, debuglevel;
+
+ /*
+ * Create communication channel between parent and child.
+ */
+ if (proto_client(NULL, "socketpair://", &res->hr_ctrl) == -1) {
+ KEEP_ERRNO((void)pidfile_remove(pfh));
+ pjdlog_exit(EX_OSERR,
+ "Unable to create control sockets between parent and child");
+ }
+ /*
+ * Create communication channel between child and parent.
+ */
+ if (proto_client(NULL, "socketpair://", &res->hr_event) == -1) {
+ KEEP_ERRNO((void)pidfile_remove(pfh));
+ pjdlog_exit(EX_OSERR,
+ "Unable to create event sockets between child and parent");
+ }
+
+ pid = fork();
+ if (pid == -1) {
+ KEEP_ERRNO((void)pidfile_remove(pfh));
+ pjdlog_exit(EX_OSERR, "Unable to fork");
+ }
+
+ if (pid > 0) {
+ /* This is parent. */
+ proto_close(res->hr_remotein);
+ res->hr_remotein = NULL;
+ proto_close(res->hr_remoteout);
+ res->hr_remoteout = NULL;
+ /* Declare that we are receiver. */
+ proto_recv(res->hr_event, NULL, 0);
+ /* Declare that we are sender. */
+ proto_send(res->hr_ctrl, NULL, 0);
+ res->hr_workerpid = pid;
+ return;
+ }
+
+ gres = res;
+ res->output_status_aux = output_status_aux;
+ mode = pjdlog_mode_get();
+ debuglevel = pjdlog_debug_get();
+
+ /* Declare that we are sender. */
+ proto_send(res->hr_event, NULL, 0);
+ /* Declare that we are receiver. */
+ proto_recv(res->hr_ctrl, NULL, 0);
+ descriptors_cleanup(res);
+
+ descriptors_assert(res, mode);
+
+ pjdlog_init(mode);
+ pjdlog_debug_set(debuglevel);
+ pjdlog_prefix_set("[%s] (%s) ", res->hr_name, role2str(res->hr_role));
+ setproctitle("%s (%s)", res->hr_name, role2str(res->hr_role));
+
+ PJDLOG_VERIFY(sigemptyset(&mask) == 0);
+ PJDLOG_VERIFY(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);
+
+ /* Error in setting timeout is not critical, but why should it fail? */
+ if (proto_timeout(res->hr_remotein, 2 * HAST_KEEPALIVE) == -1)
+ pjdlog_errno(LOG_WARNING, "Unable to set connection timeout");
+ if (proto_timeout(res->hr_remoteout, res->hr_timeout) == -1)
+ pjdlog_errno(LOG_WARNING, "Unable to set connection timeout");
+
+ init_local(res);
+ init_environment();
+
+ if (drop_privs(res) != 0)
+ exit(EX_CONFIG);
+ pjdlog_info("Privileges successfully dropped.");
+
+ /*
+ * Create the control thread before sending any event to the parent,
+ * as we can deadlock when parent sends control request to worker,
+ * but worker has no control thread started yet, so parent waits.
+ * In the meantime worker sends an event to the parent, but parent
+ * is unable to handle the event, because it waits for control
+ * request response.
+ */
+ error = pthread_create(&td, NULL, ctrl_thread, res);
+ PJDLOG_ASSERT(error == 0);
+
+ init_remote(res, nvin);
+ event_send(res, EVENT_CONNECT);
+
+ error = pthread_create(&td, NULL, recv_thread, res);
+ PJDLOG_ASSERT(error == 0);
+ error = pthread_create(&td, NULL, disk_thread, res);
+ PJDLOG_ASSERT(error == 0);
+ (void)send_thread(res);
+}
+
+static void
+reqlog(int loglevel, int debuglevel, int error, struct hio *hio,
+ const char *fmt, ...)
+{
+ char msg[1024];
+ va_list ap;
+ int len;
+
+ va_start(ap, fmt);
+ len = vsnprintf(msg, sizeof(msg), fmt, ap);
+ va_end(ap);
+ if ((size_t)len < sizeof(msg)) {
+ switch (hio->hio_cmd) {
+ case HIO_READ:
+ (void)snprintf(msg + len, sizeof(msg) - len,
+ "READ(%ju, %ju).", (uintmax_t)hio->hio_offset,
+ (uintmax_t)hio->hio_length);
+ break;
+ case HIO_DELETE:
+ (void)snprintf(msg + len, sizeof(msg) - len,
+ "DELETE(%ju, %ju).", (uintmax_t)hio->hio_offset,
+ (uintmax_t)hio->hio_length);
+ break;
+ case HIO_FLUSH:
+ (void)snprintf(msg + len, sizeof(msg) - len, "FLUSH.");
+ break;
+ case HIO_WRITE:
+ (void)snprintf(msg + len, sizeof(msg) - len,
+ "WRITE(%ju, %ju).", (uintmax_t)hio->hio_offset,
+ (uintmax_t)hio->hio_length);
+ break;
+ case HIO_KEEPALIVE:
+ (void)snprintf(msg + len, sizeof(msg) - len, "KEEPALIVE.");
+ break;
+ default:
+ (void)snprintf(msg + len, sizeof(msg) - len,
+ "UNKNOWN(%u).", (unsigned int)hio->hio_cmd);
+ break;
+ }
+ }
+ pjdlog_common(loglevel, debuglevel, error, "%s", msg);
+}
+
+static int
+requnpack(struct hast_resource *res, struct hio *hio, struct nv *nv)
+{
+
+ hio->hio_cmd = nv_get_uint8(nv, "cmd");
+ if (hio->hio_cmd == 0) {
+ pjdlog_error("Header contains no 'cmd' field.");
+ hio->hio_error = EINVAL;
+ goto end;
+ }
+ if (hio->hio_cmd != HIO_KEEPALIVE) {
+ hio->hio_seq = nv_get_uint64(nv, "seq");
+ if (hio->hio_seq == 0) {
+ pjdlog_error("Header contains no 'seq' field.");
+ hio->hio_error = EINVAL;
+ goto end;
+ }
+ }
+ switch (hio->hio_cmd) {
+ case HIO_FLUSH:
+ case HIO_KEEPALIVE:
+ break;
+ case HIO_WRITE:
+ hio->hio_memsync = nv_exists(nv, "memsync");
+ /* FALLTHROUGH */
+ case HIO_READ:
+ case HIO_DELETE:
+ hio->hio_offset = nv_get_uint64(nv, "offset");
+ if (nv_error(nv) != 0) {
+ pjdlog_error("Header is missing 'offset' field.");
+ hio->hio_error = EINVAL;
+ goto end;
+ }
+ hio->hio_length = nv_get_uint64(nv, "length");
+ if (nv_error(nv) != 0) {
+ pjdlog_error("Header is missing 'length' field.");
+ hio->hio_error = EINVAL;
+ goto end;
+ }
+ if (hio->hio_length == 0) {
+ pjdlog_error("Data length is zero.");
+ hio->hio_error = EINVAL;
+ goto end;
+ }
+ if (hio->hio_cmd != HIO_DELETE && hio->hio_length > MAXPHYS) {
+ pjdlog_error("Data length is too large (%ju > %ju).",
+ (uintmax_t)hio->hio_length, (uintmax_t)MAXPHYS);
+ hio->hio_error = EINVAL;
+ goto end;
+ }
+ if ((hio->hio_offset % res->hr_local_sectorsize) != 0) {
+ pjdlog_error("Offset %ju is not multiple of sector size.",
+ (uintmax_t)hio->hio_offset);
+ hio->hio_error = EINVAL;
+ goto end;
+ }
+ if ((hio->hio_length % res->hr_local_sectorsize) != 0) {
+ pjdlog_error("Length %ju is not multiple of sector size.",
+ (uintmax_t)hio->hio_length);
+ hio->hio_error = EINVAL;
+ goto end;
+ }
+ if (hio->hio_offset + hio->hio_length >
+ (uint64_t)res->hr_datasize) {
+ pjdlog_error("Data offset is too large (%ju > %ju).",
+ (uintmax_t)(hio->hio_offset + hio->hio_length),
+ (uintmax_t)res->hr_datasize);
+ hio->hio_error = EINVAL;
+ goto end;
+ }
+ break;
+ default:
+ pjdlog_error("Header contains invalid 'cmd' (%hhu).",
+ hio->hio_cmd);
+ hio->hio_error = EINVAL;
+ goto end;
+ }
+ hio->hio_error = 0;
+end:
+ return (hio->hio_error);
+}
+
+static __dead2 void
+secondary_exit(int exitcode, const char *fmt, ...)
+{
+ va_list ap;
+
+ PJDLOG_ASSERT(exitcode != EX_OK);
+ va_start(ap, fmt);
+ pjdlogv_errno(LOG_ERR, fmt, ap);
+ va_end(ap);
+ event_send(gres, EVENT_DISCONNECT);
+ exit(exitcode);
+}
+
+/*
+ * Thread receives requests from the primary node.
+ */
+static void *
+recv_thread(void *arg)
+{
+ struct hast_resource *res = arg;
+ struct hio *hio, *mshio;
+ struct nv *nv;
+
+ for (;;) {
+ pjdlog_debug(2, "recv: Taking free request.");
+ QUEUE_TAKE(free, hio);
+ pjdlog_debug(2, "recv: (%p) Got request.", hio);
+ if (hast_proto_recv_hdr(res->hr_remotein, &nv) == -1) {
+ secondary_exit(EX_TEMPFAIL,
+ "Unable to receive request header");
+ }
+ if (requnpack(res, hio, nv) != 0) {
+ nv_free(nv);
+ pjdlog_debug(2,
+ "recv: (%p) Moving request to the send queue.",
+ hio);
+ QUEUE_INSERT(send, hio);
+ continue;
+ }
+ switch (hio->hio_cmd) {
+ case HIO_READ:
+ res->hr_stat_read++;
+ break;
+ case HIO_WRITE:
+ res->hr_stat_write++;
+ break;
+ case HIO_DELETE:
+ res->hr_stat_delete++;
+ break;
+ case HIO_FLUSH:
+ res->hr_stat_flush++;
+ break;
+ case HIO_KEEPALIVE:
+ break;
+ default:
+ PJDLOG_ABORT("Unexpected command (cmd=%hhu).",
+ hio->hio_cmd);
+ }
+ reqlog(LOG_DEBUG, 2, -1, hio,
+ "recv: (%p) Got request header: ", hio);
+ if (hio->hio_cmd == HIO_KEEPALIVE) {
+ nv_free(nv);
+ pjdlog_debug(2,
+ "recv: (%p) Moving request to the free queue.",
+ hio);
+ hio_clear(hio);
+ QUEUE_INSERT(free, hio);
+ continue;
+ } else if (hio->hio_cmd == HIO_WRITE) {
+ if (hast_proto_recv_data(res, res->hr_remotein, nv,
+ hio->hio_data, MAXPHYS) == -1) {
+ secondary_exit(EX_TEMPFAIL,
+ "Unable to receive request data");
+ }
+ if (hio->hio_memsync) {
+ /*
+ * For memsync requests we expect two replies.
+ * Clone the hio so we can handle both of them.
+ */
+ pjdlog_debug(2, "recv: Taking free request.");
+ QUEUE_TAKE(free, mshio);
+ pjdlog_debug(2, "recv: (%p) Got request.",
+ mshio);
+ hio_copy(hio, mshio);
+ mshio->hio_error = 0;
+ /*
+ * We want to keep 'memsync' tag only on the
+ * request going onto send queue (mshio).
+ */
+ hio->hio_memsync = false;
+ pjdlog_debug(2,
+ "recv: (%p) Moving memsync request to the send queue.",
+ mshio);
+ QUEUE_INSERT(send, mshio);
+ }
+ }
+ nv_free(nv);
+ pjdlog_debug(2, "recv: (%p) Moving request to the disk queue.",
+ hio);
+ QUEUE_INSERT(disk, hio);
+ }
+ /* NOTREACHED */
+ return (NULL);
+}
+
+/*
+ * Thread reads from or writes to local component and also handles DELETE and
+ * FLUSH requests.
+ */
+static void *
+disk_thread(void *arg)
+{
+ struct hast_resource *res = arg;
+ struct hio *hio;
+ ssize_t ret;
+ bool clear_activemap, logerror;
+
+ clear_activemap = true;
+
+ for (;;) {
+ pjdlog_debug(2, "disk: Taking request.");
+ QUEUE_TAKE(disk, hio);
+ while (clear_activemap) {
+ unsigned char *map;
+ size_t mapsize;
+
+ /*
+ * When first request is received, it means that primary
+ * already received our activemap, merged it and stored
+ * locally. We can now safely clear our activemap.
+ */
+ mapsize =
+ activemap_calc_ondisk_size(res->hr_local_mediasize -
+ METADATA_SIZE, res->hr_extentsize,
+ res->hr_local_sectorsize);
+ map = calloc(1, mapsize);
+ if (map == NULL) {
+ pjdlog_warning("Unable to allocate memory to clear local activemap.");
+ break;
+ }
+ if (pwrite(res->hr_localfd, map, mapsize,
+ METADATA_SIZE) != (ssize_t)mapsize) {
+ pjdlog_errno(LOG_WARNING,
+ "Unable to store cleared activemap");
+ free(map);
+ res->hr_stat_activemap_write_error++;
+ break;
+ }
+ free(map);
+ clear_activemap = false;
+ pjdlog_debug(1, "Local activemap cleared.");
+ break;
+ }
+ reqlog(LOG_DEBUG, 2, -1, hio, "disk: (%p) Got request: ", hio);
+ logerror = true;
+ /* Handle the actual request. */
+ switch (hio->hio_cmd) {
+ case HIO_READ:
+ ret = pread(res->hr_localfd, hio->hio_data,
+ hio->hio_length,
+ hio->hio_offset + res->hr_localoff);
+ if (ret == -1)
+ hio->hio_error = errno;
+ else if (ret != (int64_t)hio->hio_length)
+ hio->hio_error = EIO;
+ else
+ hio->hio_error = 0;
+ break;
+ case HIO_WRITE:
+ ret = pwrite(res->hr_localfd, hio->hio_data,
+ hio->hio_length,
+ hio->hio_offset + res->hr_localoff);
+ if (ret == -1)
+ hio->hio_error = errno;
+ else if (ret != (int64_t)hio->hio_length)
+ hio->hio_error = EIO;
+ else
+ hio->hio_error = 0;
+ break;
+ case HIO_DELETE:
+ ret = g_delete(res->hr_localfd,
+ hio->hio_offset + res->hr_localoff,
+ hio->hio_length);
+ if (ret == -1)
+ hio->hio_error = errno;
+ else
+ hio->hio_error = 0;
+ break;
+ case HIO_FLUSH:
+ if (!res->hr_localflush) {
+ ret = -1;
+ hio->hio_error = EOPNOTSUPP;
+ logerror = false;
+ break;
+ }
+ ret = g_flush(res->hr_localfd);
+ if (ret == -1) {
+ if (errno == EOPNOTSUPP)
+ res->hr_localflush = false;
+ hio->hio_error = errno;
+ } else {
+ hio->hio_error = 0;
+ }
+ break;
+ default:
+ PJDLOG_ABORT("Unexpected command (cmd=%hhu).",
+ hio->hio_cmd);
+ }
+ if (logerror && hio->hio_error != 0) {
+ reqlog(LOG_ERR, 0, hio->hio_error, hio,
+ "Request failed: ");
+ }
+ pjdlog_debug(2, "disk: (%p) Moving request to the send queue.",
+ hio);
+ QUEUE_INSERT(send, hio);
+ }
+ /* NOTREACHED */
+ return (NULL);
+}
+
+/*
+ * Thread sends requests back to primary node.
+ */
+static void *
+send_thread(void *arg)
+{
+ struct hast_resource *res = arg;
+ struct nv *nvout;
+ struct hio *hio;
+ void *data;
+ size_t length;
+
+ for (;;) {
+ pjdlog_debug(2, "send: Taking request.");
+ QUEUE_TAKE(send, hio);
+ reqlog(LOG_DEBUG, 2, -1, hio, "send: (%p) Got request: ", hio);
+ nvout = nv_alloc();
+ /* Copy sequence number. */
+ nv_add_uint64(nvout, hio->hio_seq, "seq");
+ if (hio->hio_memsync) {
+ PJDLOG_ASSERT(hio->hio_cmd == HIO_WRITE);
+ nv_add_int8(nvout, 1, "received");
+ }
+ switch (hio->hio_cmd) {
+ case HIO_READ:
+ if (hio->hio_error == 0) {
+ data = hio->hio_data;
+ length = hio->hio_length;
+ break;
+ }
+ /*
+ * We send no data in case of an error.
+ */
+ /* FALLTHROUGH */
+ case HIO_DELETE:
+ case HIO_FLUSH:
+ case HIO_WRITE:
+ data = NULL;
+ length = 0;
+ break;
+ default:
+ PJDLOG_ABORT("Unexpected command (cmd=%hhu).",
+ hio->hio_cmd);
+ }
+ if (hio->hio_error != 0) {
+ switch (hio->hio_cmd) {
+ case HIO_READ:
+ res->hr_stat_read_error++;
+ break;
+ case HIO_WRITE:
+ res->hr_stat_write_error++;
+ break;
+ case HIO_DELETE:
+ res->hr_stat_delete_error++;
+ break;
+ case HIO_FLUSH:
+ res->hr_stat_flush_error++;
+ break;
+ }
+ nv_add_int16(nvout, hio->hio_error, "error");
+ }
+ if (hast_proto_send(res, res->hr_remoteout, nvout, data,
+ length) == -1) {
+ secondary_exit(EX_TEMPFAIL, "Unable to send reply");
+ }
+ nv_free(nvout);
+ pjdlog_debug(2, "send: (%p) Moving request to the free queue.",
+ hio);
+ hio_clear(hio);
+ QUEUE_INSERT(free, hio);
+ }
+ /* NOTREACHED */
+ return (NULL);
+}
diff --git a/sbin/hastd/subr.c b/sbin/hastd/subr.c
new file mode 100644
index 0000000..738e5f2
--- /dev/null
+++ b/sbin/hastd/subr.c
@@ -0,0 +1,299 @@
+/*-
+ * Copyright (c) 2010 The FreeBSD Foundation
+ * Copyright (c) 2011 Pawel Jakub Dawidek <pawel@dawidek.net>
+ * All rights reserved.
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/disk.h>
+#include <sys/ioctl.h>
+#include <sys/jail.h>
+#include <sys/stat.h>
+#ifdef HAVE_CAPSICUM
+#include <sys/capsicum.h>
+#include <geom/gate/g_gate.h>
+#endif
+
+#include <errno.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <pjdlog.h>
+
+#include "hast.h"
+#include "subr.h"
+
+int
+vsnprlcat(char *str, size_t size, const char *fmt, va_list ap)
+{
+ size_t len;
+
+ len = strlen(str);
+ return (vsnprintf(str + len, size - len, fmt, ap));
+}
+
+int
+snprlcat(char *str, size_t size, const char *fmt, ...)
+{
+ va_list ap;
+ int result;
+
+ va_start(ap, fmt);
+ result = vsnprlcat(str, size, fmt, ap);
+ va_end(ap);
+ return (result);
+}
+
+int
+provinfo(struct hast_resource *res, bool dowrite)
+{
+ struct stat sb;
+
+ PJDLOG_ASSERT(res->hr_localpath != NULL &&
+ res->hr_localpath[0] != '\0');
+
+ if (res->hr_localfd == -1) {
+ res->hr_localfd = open(res->hr_localpath,
+ dowrite ? O_RDWR : O_RDONLY);
+ if (res->hr_localfd == -1) {
+ pjdlog_errno(LOG_ERR, "Unable to open %s",
+ res->hr_localpath);
+ return (-1);
+ }
+ }
+ if (fstat(res->hr_localfd, &sb) == -1) {
+ pjdlog_errno(LOG_ERR, "Unable to stat %s", res->hr_localpath);
+ return (-1);
+ }
+ if (S_ISCHR(sb.st_mode)) {
+ /*
+ * If this is character device, it is most likely GEOM provider.
+ */
+ if (ioctl(res->hr_localfd, DIOCGMEDIASIZE,
+ &res->hr_local_mediasize) == -1) {
+ pjdlog_errno(LOG_ERR,
+ "Unable obtain provider %s mediasize",
+ res->hr_localpath);
+ return (-1);
+ }
+ if (ioctl(res->hr_localfd, DIOCGSECTORSIZE,
+ &res->hr_local_sectorsize) == -1) {
+ pjdlog_errno(LOG_ERR,
+ "Unable obtain provider %s sectorsize",
+ res->hr_localpath);
+ return (-1);
+ }
+ } else if (S_ISREG(sb.st_mode)) {
+ /*
+ * We also support regular files for which we hardcode
+ * sector size of 512 bytes.
+ */
+ res->hr_local_mediasize = sb.st_size;
+ res->hr_local_sectorsize = 512;
+ } else {
+ /*
+ * We support no other file types.
+ */
+ pjdlog_error("%s is neither GEOM provider nor regular file.",
+ res->hr_localpath);
+ errno = EFTYPE;
+ return (-1);
+ }
+ return (0);
+}
+
+const char *
+role2str(int role)
+{
+
+ switch (role) {
+ case HAST_ROLE_INIT:
+ return ("init");
+ case HAST_ROLE_PRIMARY:
+ return ("primary");
+ case HAST_ROLE_SECONDARY:
+ return ("secondary");
+ }
+ return ("unknown");
+}
+
+int
+drop_privs(const struct hast_resource *res)
+{
+ char jailhost[sizeof(res->hr_name) * 2];
+ struct jail jailst;
+ struct passwd *pw;
+ uid_t ruid, euid, suid;
+ gid_t rgid, egid, sgid;
+ gid_t gidset[1];
+ bool capsicum, jailed;
+
+ /*
+ * According to getpwnam(3) we have to clear errno before calling the
+ * function to be able to distinguish between an error and missing
+ * entry (with is not treated as error by getpwnam(3)).
+ */
+ errno = 0;
+ pw = getpwnam(HAST_USER);
+ if (pw == NULL) {
+ if (errno != 0) {
+ pjdlog_errno(LOG_ERR,
+ "Unable to find info about '%s' user", HAST_USER);
+ return (-1);
+ } else {
+ pjdlog_error("'%s' user doesn't exist.", HAST_USER);
+ errno = ENOENT;
+ return (-1);
+ }
+ }
+
+ bzero(&jailst, sizeof(jailst));
+ jailst.version = JAIL_API_VERSION;
+ jailst.path = pw->pw_dir;
+ if (res == NULL) {
+ (void)snprintf(jailhost, sizeof(jailhost), "hastctl");
+ } else {
+ (void)snprintf(jailhost, sizeof(jailhost), "hastd: %s (%s)",
+ res->hr_name, role2str(res->hr_role));
+ }
+ jailst.hostname = jailhost;
+ jailst.jailname = NULL;
+ jailst.ip4s = 0;
+ jailst.ip4 = NULL;
+ jailst.ip6s = 0;
+ jailst.ip6 = NULL;
+ if (jail(&jailst) >= 0) {
+ jailed = true;
+ } else {
+ jailed = false;
+ pjdlog_errno(LOG_WARNING,
+ "Unable to jail to directory to %s", pw->pw_dir);
+ if (chroot(pw->pw_dir) == -1) {
+ pjdlog_errno(LOG_ERR,
+ "Unable to change root directory to %s",
+ pw->pw_dir);
+ return (-1);
+ }
+ }
+ PJDLOG_VERIFY(chdir("/") == 0);
+ gidset[0] = pw->pw_gid;
+ if (setgroups(1, gidset) == -1) {
+ pjdlog_errno(LOG_ERR, "Unable to set groups to gid %u",
+ (unsigned int)pw->pw_gid);
+ return (-1);
+ }
+ if (setgid(pw->pw_gid) == -1) {
+ pjdlog_errno(LOG_ERR, "Unable to set gid to %u",
+ (unsigned int)pw->pw_gid);
+ return (-1);
+ }
+ if (setuid(pw->pw_uid) == -1) {
+ pjdlog_errno(LOG_ERR, "Unable to set uid to %u",
+ (unsigned int)pw->pw_uid);
+ return (-1);
+ }
+
+#ifdef HAVE_CAPSICUM
+ capsicum = (cap_enter() == 0);
+ if (!capsicum) {
+ pjdlog_common(LOG_DEBUG, 1, errno,
+ "Unable to sandbox using capsicum");
+ } else if (res != NULL) {
+ cap_rights_t rights;
+ static const unsigned long geomcmds[] = {
+ DIOCGDELETE,
+ DIOCGFLUSH
+ };
+
+ PJDLOG_ASSERT(res->hr_role == HAST_ROLE_PRIMARY ||
+ res->hr_role == HAST_ROLE_SECONDARY);
+
+ cap_rights_init(&rights, CAP_FLOCK, CAP_IOCTL, CAP_PREAD,
+ CAP_PWRITE);
+ if (cap_rights_limit(res->hr_localfd, &rights) == -1) {
+ pjdlog_errno(LOG_ERR,
+ "Unable to limit capability rights on local descriptor");
+ }
+ if (cap_ioctls_limit(res->hr_localfd, geomcmds,
+ sizeof(geomcmds) / sizeof(geomcmds[0])) == -1) {
+ pjdlog_errno(LOG_ERR,
+ "Unable to limit allowed GEOM ioctls");
+ }
+
+ if (res->hr_role == HAST_ROLE_PRIMARY) {
+ static const unsigned long ggatecmds[] = {
+ G_GATE_CMD_MODIFY,
+ G_GATE_CMD_START,
+ G_GATE_CMD_DONE,
+ G_GATE_CMD_DESTROY
+ };
+
+ cap_rights_init(&rights, CAP_IOCTL);
+ if (cap_rights_limit(res->hr_ggatefd, &rights) == -1) {
+ pjdlog_errno(LOG_ERR,
+ "Unable to limit capability rights to CAP_IOCTL on ggate descriptor");
+ }
+ if (cap_ioctls_limit(res->hr_ggatefd, ggatecmds,
+ sizeof(ggatecmds) / sizeof(ggatecmds[0])) == -1) {
+ pjdlog_errno(LOG_ERR,
+ "Unable to limit allowed ggate ioctls");
+ }
+ }
+ }
+#else
+ capsicum = false;
+#endif
+
+ /*
+ * Better be sure that everything succeeded.
+ */
+ PJDLOG_VERIFY(getresuid(&ruid, &euid, &suid) == 0);
+ PJDLOG_VERIFY(ruid == pw->pw_uid);
+ PJDLOG_VERIFY(euid == pw->pw_uid);
+ PJDLOG_VERIFY(suid == pw->pw_uid);
+ PJDLOG_VERIFY(getresgid(&rgid, &egid, &sgid) == 0);
+ PJDLOG_VERIFY(rgid == pw->pw_gid);
+ PJDLOG_VERIFY(egid == pw->pw_gid);
+ PJDLOG_VERIFY(sgid == pw->pw_gid);
+ PJDLOG_VERIFY(getgroups(0, NULL) == 1);
+ PJDLOG_VERIFY(getgroups(1, gidset) == 1);
+ PJDLOG_VERIFY(gidset[0] == pw->pw_gid);
+
+ pjdlog_debug(1,
+ "Privileges successfully dropped using %s%s+setgid+setuid.",
+ capsicum ? "capsicum+" : "", jailed ? "jail" : "chroot");
+
+ return (0);
+}
diff --git a/sbin/hastd/subr.h b/sbin/hastd/subr.h
new file mode 100644
index 0000000..c765754
--- /dev/null
+++ b/sbin/hastd/subr.h
@@ -0,0 +1,56 @@
+/*-
+ * Copyright (c) 2010 The FreeBSD Foundation
+ * Copyright (c) 2011 Pawel Jakub Dawidek <pawel@dawidek.net>
+ * All rights reserved.
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _SUBR_H_
+#define _SUBR_H_
+
+#include <sys/types.h>
+#include <stdbool.h>
+
+#include "hast.h"
+
+#define KEEP_ERRNO(work) do { \
+ int _rerrno; \
+ \
+ _rerrno = errno; \
+ work; \
+ errno = _rerrno; \
+} while (0)
+
+int vsnprlcat(char *str, size_t size, const char *fmt, va_list ap);
+int snprlcat(char *str, size_t size, const char *fmt, ...);
+
+int provinfo(struct hast_resource *res, bool dowrite);
+const char *role2str(int role);
+int drop_privs(const struct hast_resource *res);
+
+#endif /* !_SUBR_H_ */
diff --git a/sbin/hastd/synch.h b/sbin/hastd/synch.h
new file mode 100644
index 0000000..db4d83b
--- /dev/null
+++ b/sbin/hastd/synch.h
@@ -0,0 +1,195 @@
+/*-
+ * Copyright (c) 2009-2010 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _SYNCH_H_
+#define _SYNCH_H_
+
+#include <errno.h>
+#include <pthread.h>
+#include <pthread_np.h>
+#include <stdbool.h>
+#include <time.h>
+
+#include <pjdlog.h>
+
+#ifndef PJDLOG_ASSERT
+#include <assert.h>
+#define PJDLOG_ASSERT(...) assert(__VA_ARGS__)
+#endif
+
+static __inline void
+mtx_init(pthread_mutex_t *lock) __requires_unlocked(*lock)
+{
+ int error;
+
+ error = pthread_mutex_init(lock, NULL);
+ PJDLOG_ASSERT(error == 0);
+}
+static __inline void
+mtx_destroy(pthread_mutex_t *lock) __requires_unlocked(*lock)
+{
+ int error;
+
+ error = pthread_mutex_destroy(lock);
+ PJDLOG_ASSERT(error == 0);
+}
+static __inline void
+mtx_lock(pthread_mutex_t *lock) __locks_exclusive(*lock)
+{
+ int error;
+
+ error = pthread_mutex_lock(lock);
+ PJDLOG_ASSERT(error == 0);
+}
+static __inline bool
+mtx_trylock(pthread_mutex_t *lock) __trylocks_exclusive(true, *lock)
+{
+ int error;
+
+ error = pthread_mutex_trylock(lock);
+ PJDLOG_ASSERT(error == 0 || error == EBUSY);
+ return (error == 0);
+}
+static __inline void
+mtx_unlock(pthread_mutex_t *lock) __unlocks(*lock)
+{
+ int error;
+
+ error = pthread_mutex_unlock(lock);
+ PJDLOG_ASSERT(error == 0);
+}
+static __inline bool
+mtx_owned(pthread_mutex_t *lock)
+{
+
+ return (pthread_mutex_isowned_np(lock) != 0);
+}
+
+static __inline void
+rw_init(pthread_rwlock_t *lock) __requires_unlocked(*lock)
+{
+ int error;
+
+ error = pthread_rwlock_init(lock, NULL);
+ PJDLOG_ASSERT(error == 0);
+}
+static __inline void
+rw_destroy(pthread_rwlock_t *lock) __requires_unlocked(*lock)
+{
+ int error;
+
+ error = pthread_rwlock_destroy(lock);
+ PJDLOG_ASSERT(error == 0);
+}
+static __inline void
+rw_rlock(pthread_rwlock_t *lock) __locks_shared(*lock)
+{
+ int error;
+
+ error = pthread_rwlock_rdlock(lock);
+ PJDLOG_ASSERT(error == 0);
+}
+static __inline void
+rw_wlock(pthread_rwlock_t *lock) __locks_exclusive(*lock)
+{
+ int error;
+
+ error = pthread_rwlock_wrlock(lock);
+ PJDLOG_ASSERT(error == 0);
+}
+static __inline void
+rw_unlock(pthread_rwlock_t *lock) __unlocks(*lock)
+{
+ int error;
+
+ error = pthread_rwlock_unlock(lock);
+ PJDLOG_ASSERT(error == 0);
+}
+
+static __inline void
+cv_init(pthread_cond_t *cv)
+{
+ pthread_condattr_t attr;
+ int error;
+
+ error = pthread_condattr_init(&attr);
+ PJDLOG_ASSERT(error == 0);
+ error = pthread_condattr_setclock(&attr, CLOCK_MONOTONIC);
+ PJDLOG_ASSERT(error == 0);
+ error = pthread_cond_init(cv, &attr);
+ PJDLOG_ASSERT(error == 0);
+ error = pthread_condattr_destroy(&attr);
+ PJDLOG_ASSERT(error == 0);
+}
+static __inline void
+cv_wait(pthread_cond_t *cv, pthread_mutex_t *lock) __requires_exclusive(*lock)
+{
+ int error;
+
+ error = pthread_cond_wait(cv, lock);
+ PJDLOG_ASSERT(error == 0);
+}
+static __inline bool
+cv_timedwait(pthread_cond_t *cv, pthread_mutex_t *lock, int timeout)
+ __requires_exclusive(*lock)
+{
+ struct timespec ts;
+ int error;
+
+ if (timeout == 0) {
+ cv_wait(cv, lock);
+ return (false);
+ }
+
+ error = clock_gettime(CLOCK_MONOTONIC, &ts);
+ PJDLOG_ASSERT(error == 0);
+ ts.tv_sec += timeout;
+ error = pthread_cond_timedwait(cv, lock, &ts);
+ PJDLOG_ASSERT(error == 0 || error == ETIMEDOUT);
+ return (error == ETIMEDOUT);
+}
+static __inline void
+cv_signal(pthread_cond_t *cv)
+{
+ int error;
+
+ error = pthread_cond_signal(cv);
+ PJDLOG_ASSERT(error == 0);
+}
+static __inline void
+cv_broadcast(pthread_cond_t *cv)
+{
+ int error;
+
+ error = pthread_cond_broadcast(cv);
+ PJDLOG_ASSERT(error == 0);
+}
+#endif /* !_SYNCH_H_ */
diff --git a/sbin/hastd/token.l b/sbin/hastd/token.l
new file mode 100644
index 0000000..e8f6760
--- /dev/null
+++ b/sbin/hastd/token.l
@@ -0,0 +1,86 @@
+%{
+/*-
+ * Copyright (c) 2009-2010 The FreeBSD Foundation
+ * Copyright (c) 2011 Pawel Jakub Dawidek <pawel@dawidek.net>
+ * All rights reserved.
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+ *
+ * $FreeBSD$
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include "hast.h"
+
+#include "y.tab.h"
+
+int depth;
+int lineno;
+
+#define DP do { } while (0)
+#define YY_DECL int yylex(void)
+
+extern int yylex(void);
+%}
+
+%option noinput
+%option nounput
+%option noyywrap
+
+%%
+control { DP; return CONTROL; }
+pidfile { DP; return PIDFILE; }
+listen { DP; return LISTEN; }
+replication { DP; return REPLICATION; }
+checksum { DP; return CHECKSUM; }
+compression { DP; return COMPRESSION; }
+timeout { DP; return TIMEOUT; }
+exec { DP; return EXEC; }
+metaflush { DP; return METAFLUSH; }
+resource { DP; return RESOURCE; }
+name { DP; return NAME; }
+local { DP; return LOCAL; }
+remote { DP; return REMOTE; }
+source { DP; return SOURCE; }
+on { DP; return ON; }
+off { DP; return OFF; }
+fullsync { DP; return FULLSYNC; }
+memsync { DP; return MEMSYNC; }
+async { DP; return ASYNC; }
+none { DP; return NONE; }
+crc32 { DP; return CRC32; }
+sha256 { DP; return SHA256; }
+hole { DP; return HOLE; }
+lzf { DP; return LZF; }
+[0-9]+ { DP; yylval.num = atoi(yytext); return NUM; }
+[a-zA-Z0-9\.\-_/\:\[\]]+ { DP; yylval.str = strdup(yytext); return STR; }
+\{ { DP; depth++; return OB; }
+\} { DP; depth--; return CB; }
+#.*$ /* ignore comments */;
+\n { lineno++; }
+[ \t]+ /* ignore whitespace */;
+%%
diff --git a/sbin/ifconfig/Makefile b/sbin/ifconfig/Makefile
new file mode 100644
index 0000000..ac7faf9
--- /dev/null
+++ b/sbin/ifconfig/Makefile
@@ -0,0 +1,72 @@
+# From: @(#)Makefile 8.1 (Berkeley) 6/5/93
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+PROG= ifconfig
+
+SRCS= ifconfig.c # base support
+
+#
+# NB: The order here defines the order in which the constructors
+# are called. This in turn defines the default order in which
+# status is displayed. Probably should add a priority mechanism
+# to the registration process so we don't depend on this aspect
+# of the toolchain.
+#
+SRCS+= af_link.c # LLC support
+.if ${MK_INET_SUPPORT} != "no"
+SRCS+= af_inet.c # IPv4 support
+.endif
+.if ${MK_INET6_SUPPORT} != "no"
+SRCS+= af_inet6.c # IPv6 support
+.endif
+.if ${MK_INET6_SUPPORT} != "no"
+SRCS+= af_nd6.c # ND6 support
+.endif
+
+SRCS+= ifclone.c # clone device support
+SRCS+= ifmac.c # MAC support
+SRCS+= ifmedia.c # SIOC[GS]IFMEDIA support
+SRCS+= iffib.c # non-default FIB support
+SRCS+= ifvlan.c # SIOC[GS]ETVLAN support
+SRCS+= ifvxlan.c # VXLAN support
+SRCS+= ifgre.c # GRE keys etc
+SRCS+= ifgif.c # GIF reversed header workaround
+
+SRCS+= sfp.c # SFP/SFP+ information
+LIBADD+= m
+
+SRCS+= ifieee80211.c regdomain.c # SIOC[GS]IEEE80211 support
+LIBADD+= bsdxml sbuf
+
+SRCS+= carp.c # SIOC[GS]VH support
+SRCS+= ifgroup.c # ...
+.if ${MK_PF} != "no"
+SRCS+= ifpfsync.c # pfsync(4) support
+.endif
+
+SRCS+= ifbridge.c # bridge support
+SRCS+= iflagg.c # lagg support
+
+.if ${MK_INET6_SUPPORT} != "no"
+CFLAGS+= -DINET6
+.endif
+.if ${MK_INET_SUPPORT} != "no"
+CFLAGS+= -DINET
+.endif
+.if ${MK_JAIL} != "no" && !defined(RELEASE_CRUNCH) && !defined(RESCUE)
+CFLAGS+= -DJAIL
+LIBADD+= jail
+.endif
+
+MAN= ifconfig.8
+
+CFLAGS+= -Wall -Wmissing-prototypes -Wcast-qual -Wwrite-strings -Wnested-externs
+WARNS?= 2
+
+.if ${MK_TESTS} != "no"
+SUBDIR+= tests
+.endif
+
+.include <bsd.prog.mk>
diff --git a/sbin/ifconfig/af_inet.c b/sbin/ifconfig/af_inet.c
new file mode 100644
index 0000000..3c3a757
--- /dev/null
+++ b/sbin/ifconfig/af_inet.c
@@ -0,0 +1,211 @@
+/*
+ * Copyright (c) 1983, 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.
+ * 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 lint
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <net/if.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <ifaddrs.h>
+
+#include <netinet/in.h>
+#include <netinet/in_var.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include "ifconfig.h"
+
+static struct in_aliasreq in_addreq;
+static struct ifreq in_ridreq;
+
+static void
+in_status(int s __unused, const struct ifaddrs *ifa)
+{
+ struct sockaddr_in *sin, null_sin;
+
+ memset(&null_sin, 0, sizeof(null_sin));
+
+ sin = (struct sockaddr_in *)ifa->ifa_addr;
+ if (sin == NULL)
+ return;
+
+ printf("\tinet %s ", inet_ntoa(sin->sin_addr));
+
+ if (ifa->ifa_flags & IFF_POINTOPOINT) {
+ sin = (struct sockaddr_in *)ifa->ifa_dstaddr;
+ if (sin == NULL)
+ sin = &null_sin;
+ printf("--> %s ", inet_ntoa(sin->sin_addr));
+ }
+
+ sin = (struct sockaddr_in *)ifa->ifa_netmask;
+ if (sin == NULL)
+ sin = &null_sin;
+ printf("netmask 0x%lx ", (unsigned long)ntohl(sin->sin_addr.s_addr));
+
+ if (ifa->ifa_flags & IFF_BROADCAST) {
+ sin = (struct sockaddr_in *)ifa->ifa_broadaddr;
+ if (sin != NULL && sin->sin_addr.s_addr != 0)
+ printf("broadcast %s ", inet_ntoa(sin->sin_addr));
+ }
+
+ print_vhid(ifa, " ");
+
+ putchar('\n');
+}
+
+#define SIN(x) ((struct sockaddr_in *) &(x))
+static struct sockaddr_in *sintab[] = {
+ SIN(in_ridreq.ifr_addr), SIN(in_addreq.ifra_addr),
+ SIN(in_addreq.ifra_mask), SIN(in_addreq.ifra_broadaddr)
+};
+
+static void
+in_getaddr(const char *s, int which)
+{
+#define MIN(a,b) ((a)<(b)?(a):(b))
+ struct sockaddr_in *sin = sintab[which];
+ struct hostent *hp;
+ struct netent *np;
+
+ sin->sin_len = sizeof(*sin);
+ sin->sin_family = AF_INET;
+
+ if (which == ADDR) {
+ char *p = NULL;
+
+ if((p = strrchr(s, '/')) != NULL) {
+ const char *errstr;
+ /* address is `name/masklen' */
+ int masklen;
+ struct sockaddr_in *min = sintab[MASK];
+ *p = '\0';
+ if (!isdigit(*(p + 1)))
+ errstr = "invalid";
+ else
+ masklen = (int)strtonum(p + 1, 0, 32, &errstr);
+ if (errstr != NULL) {
+ *p = '/';
+ errx(1, "%s: bad value (width %s)", s, errstr);
+ }
+ min->sin_family = AF_INET;
+ min->sin_len = sizeof(*min);
+ min->sin_addr.s_addr = htonl(~((1LL << (32 - masklen)) - 1) &
+ 0xffffffff);
+ }
+ }
+
+ if (inet_aton(s, &sin->sin_addr))
+ return;
+ if ((hp = gethostbyname(s)) != 0)
+ bcopy(hp->h_addr, (char *)&sin->sin_addr,
+ MIN((size_t)hp->h_length, sizeof(sin->sin_addr)));
+ else if ((np = getnetbyname(s)) != 0)
+ sin->sin_addr = inet_makeaddr(np->n_net, INADDR_ANY);
+ else
+ errx(1, "%s: bad value", s);
+#undef MIN
+}
+
+static void
+in_status_tunnel(int s)
+{
+ char src[NI_MAXHOST];
+ char dst[NI_MAXHOST];
+ struct ifreq ifr;
+ const struct sockaddr *sa = (const struct sockaddr *) &ifr.ifr_addr;
+
+ memset(&ifr, 0, sizeof(ifr));
+ strncpy(ifr.ifr_name, name, IFNAMSIZ);
+
+ if (ioctl(s, SIOCGIFPSRCADDR, (caddr_t)&ifr) < 0)
+ return;
+ if (sa->sa_family != AF_INET)
+ return;
+ if (getnameinfo(sa, sa->sa_len, src, sizeof(src), 0, 0, NI_NUMERICHOST) != 0)
+ src[0] = '\0';
+
+ if (ioctl(s, SIOCGIFPDSTADDR, (caddr_t)&ifr) < 0)
+ return;
+ if (sa->sa_family != AF_INET)
+ return;
+ if (getnameinfo(sa, sa->sa_len, dst, sizeof(dst), 0, 0, NI_NUMERICHOST) != 0)
+ dst[0] = '\0';
+
+ printf("\ttunnel inet %s --> %s\n", src, dst);
+}
+
+static void
+in_set_tunnel(int s, struct addrinfo *srcres, struct addrinfo *dstres)
+{
+ struct in_aliasreq addreq;
+
+ memset(&addreq, 0, sizeof(addreq));
+ strncpy(addreq.ifra_name, name, IFNAMSIZ);
+ memcpy(&addreq.ifra_addr, srcres->ai_addr, srcres->ai_addr->sa_len);
+ memcpy(&addreq.ifra_dstaddr, dstres->ai_addr, dstres->ai_addr->sa_len);
+
+ if (ioctl(s, SIOCSIFPHYADDR, &addreq) < 0)
+ warn("SIOCSIFPHYADDR");
+}
+
+static struct afswtch af_inet = {
+ .af_name = "inet",
+ .af_af = AF_INET,
+ .af_status = in_status,
+ .af_getaddr = in_getaddr,
+ .af_status_tunnel = in_status_tunnel,
+ .af_settunnel = in_set_tunnel,
+ .af_difaddr = SIOCDIFADDR,
+ .af_aifaddr = SIOCAIFADDR,
+ .af_ridreq = &in_ridreq,
+ .af_addreq = &in_addreq,
+};
+
+static __constructor void
+inet_ctor(void)
+{
+
+#ifndef RESCUE
+ if (!feature_present("inet"))
+ return;
+#endif
+ af_register(&af_inet);
+}
diff --git a/sbin/ifconfig/af_inet6.c b/sbin/ifconfig/af_inet6.c
new file mode 100644
index 0000000..8a18401
--- /dev/null
+++ b/sbin/ifconfig/af_inet6.c
@@ -0,0 +1,539 @@
+/*
+ * Copyright (c) 1983, 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.
+ * 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 lint
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <net/if.h>
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+#include <ifaddrs.h>
+
+#include <arpa/inet.h>
+
+#include <netinet/in.h>
+#include <netinet/in_var.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include <netinet6/nd6.h> /* Define ND6_INFINITE_LIFETIME */
+
+#include "ifconfig.h"
+
+static struct in6_ifreq in6_ridreq;
+static struct in6_aliasreq in6_addreq =
+ { .ifra_flags = 0,
+ .ifra_lifetime = { 0, 0, ND6_INFINITE_LIFETIME, ND6_INFINITE_LIFETIME } };
+static int ip6lifetime;
+
+static int prefix(void *, int);
+static char *sec2str(time_t);
+static int explicit_prefix = 0;
+
+extern void setnd6flags(const char *, int, int, const struct afswtch *);
+extern void setnd6defif(const char *, int, int, const struct afswtch *);
+extern void nd6_status(int);
+
+static char addr_buf[MAXHOSTNAMELEN *2 + 1]; /*for getnameinfo()*/
+
+static void
+setifprefixlen(const char *addr, int dummy __unused, int s,
+ const struct afswtch *afp)
+{
+ if (afp->af_getprefix != NULL)
+ afp->af_getprefix(addr, MASK);
+ explicit_prefix = 1;
+}
+
+static void
+setip6flags(const char *dummyaddr __unused, int flag, int dummysoc __unused,
+ const struct afswtch *afp)
+{
+ if (afp->af_af != AF_INET6)
+ err(1, "address flags can be set only for inet6 addresses");
+
+ if (flag < 0)
+ in6_addreq.ifra_flags &= ~(-flag);
+ else
+ in6_addreq.ifra_flags |= flag;
+}
+
+static void
+setip6lifetime(const char *cmd, const char *val, int s,
+ const struct afswtch *afp)
+{
+ struct timespec now;
+ time_t newval;
+ char *ep;
+
+ clock_gettime(CLOCK_MONOTONIC_FAST, &now);
+ newval = (time_t)strtoul(val, &ep, 0);
+ if (val == ep)
+ errx(1, "invalid %s", cmd);
+ if (afp->af_af != AF_INET6)
+ errx(1, "%s not allowed for the AF", cmd);
+ if (strcmp(cmd, "vltime") == 0) {
+ in6_addreq.ifra_lifetime.ia6t_expire = now.tv_sec + newval;
+ in6_addreq.ifra_lifetime.ia6t_vltime = newval;
+ } else if (strcmp(cmd, "pltime") == 0) {
+ in6_addreq.ifra_lifetime.ia6t_preferred = now.tv_sec + newval;
+ in6_addreq.ifra_lifetime.ia6t_pltime = newval;
+ }
+}
+
+static void
+setip6pltime(const char *seconds, int dummy __unused, int s,
+ const struct afswtch *afp)
+{
+ setip6lifetime("pltime", seconds, s, afp);
+}
+
+static void
+setip6vltime(const char *seconds, int dummy __unused, int s,
+ const struct afswtch *afp)
+{
+ setip6lifetime("vltime", seconds, s, afp);
+}
+
+static void
+setip6eui64(const char *cmd, int dummy __unused, int s,
+ const struct afswtch *afp)
+{
+ struct ifaddrs *ifap, *ifa;
+ const struct sockaddr_in6 *sin6 = NULL;
+ const struct in6_addr *lladdr = NULL;
+ struct in6_addr *in6;
+
+ if (afp->af_af != AF_INET6)
+ errx(EXIT_FAILURE, "%s not allowed for the AF", cmd);
+ in6 = (struct in6_addr *)&in6_addreq.ifra_addr.sin6_addr;
+ if (memcmp(&in6addr_any.s6_addr[8], &in6->s6_addr[8], 8) != 0)
+ errx(EXIT_FAILURE, "interface index is already filled");
+ if (getifaddrs(&ifap) != 0)
+ err(EXIT_FAILURE, "getifaddrs");
+ for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
+ if (ifa->ifa_addr->sa_family == AF_INET6 &&
+ strcmp(ifa->ifa_name, name) == 0) {
+ sin6 = (const struct sockaddr_in6 *)ifa->ifa_addr;
+ if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) {
+ lladdr = &sin6->sin6_addr;
+ break;
+ }
+ }
+ }
+ if (!lladdr)
+ errx(EXIT_FAILURE, "could not determine link local address");
+
+ memcpy(&in6->s6_addr[8], &lladdr->s6_addr[8], 8);
+
+ freeifaddrs(ifap);
+}
+
+static void
+in6_status(int s __unused, const struct ifaddrs *ifa)
+{
+ struct sockaddr_in6 *sin, null_sin;
+ struct in6_ifreq ifr6;
+ int s6;
+ u_int32_t flags6;
+ struct in6_addrlifetime lifetime;
+ struct timespec now;
+ int error;
+
+ clock_gettime(CLOCK_MONOTONIC_FAST, &now);
+
+ memset(&null_sin, 0, sizeof(null_sin));
+
+ sin = (struct sockaddr_in6 *)ifa->ifa_addr;
+ if (sin == NULL)
+ return;
+
+ strncpy(ifr6.ifr_name, ifr.ifr_name, sizeof(ifr.ifr_name));
+ if ((s6 = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) {
+ warn("socket(AF_INET6,SOCK_DGRAM)");
+ return;
+ }
+ ifr6.ifr_addr = *sin;
+ if (ioctl(s6, SIOCGIFAFLAG_IN6, &ifr6) < 0) {
+ warn("ioctl(SIOCGIFAFLAG_IN6)");
+ close(s6);
+ return;
+ }
+ flags6 = ifr6.ifr_ifru.ifru_flags6;
+ memset(&lifetime, 0, sizeof(lifetime));
+ ifr6.ifr_addr = *sin;
+ if (ioctl(s6, SIOCGIFALIFETIME_IN6, &ifr6) < 0) {
+ warn("ioctl(SIOCGIFALIFETIME_IN6)");
+ close(s6);
+ return;
+ }
+ lifetime = ifr6.ifr_ifru.ifru_lifetime;
+ close(s6);
+
+ error = getnameinfo((struct sockaddr *)sin, sin->sin6_len, addr_buf,
+ sizeof(addr_buf), NULL, 0, NI_NUMERICHOST);
+ if (error != 0)
+ inet_ntop(AF_INET6, &sin->sin6_addr, addr_buf,
+ sizeof(addr_buf));
+ printf("\tinet6 %s ", addr_buf);
+
+ if (ifa->ifa_flags & IFF_POINTOPOINT) {
+ sin = (struct sockaddr_in6 *)ifa->ifa_dstaddr;
+ /*
+ * some of the interfaces do not have valid destination
+ * address.
+ */
+ if (sin != NULL && sin->sin6_family == AF_INET6) {
+ int error;
+
+ error = getnameinfo((struct sockaddr *)sin,
+ sin->sin6_len, addr_buf,
+ sizeof(addr_buf), NULL, 0,
+ NI_NUMERICHOST);
+ if (error != 0)
+ inet_ntop(AF_INET6, &sin->sin6_addr, addr_buf,
+ sizeof(addr_buf));
+ printf("--> %s ", addr_buf);
+ }
+ }
+
+ sin = (struct sockaddr_in6 *)ifa->ifa_netmask;
+ if (sin == NULL)
+ sin = &null_sin;
+ printf("prefixlen %d ", prefix(&sin->sin6_addr,
+ sizeof(struct in6_addr)));
+
+ if ((flags6 & IN6_IFF_ANYCAST) != 0)
+ printf("anycast ");
+ if ((flags6 & IN6_IFF_TENTATIVE) != 0)
+ printf("tentative ");
+ if ((flags6 & IN6_IFF_DUPLICATED) != 0)
+ printf("duplicated ");
+ if ((flags6 & IN6_IFF_DETACHED) != 0)
+ printf("detached ");
+ if ((flags6 & IN6_IFF_DEPRECATED) != 0)
+ printf("deprecated ");
+ if ((flags6 & IN6_IFF_AUTOCONF) != 0)
+ printf("autoconf ");
+ if ((flags6 & IN6_IFF_TEMPORARY) != 0)
+ printf("temporary ");
+ if ((flags6 & IN6_IFF_PREFER_SOURCE) != 0)
+ printf("prefer_source ");
+
+ if (((struct sockaddr_in6 *)(ifa->ifa_addr))->sin6_scope_id)
+ printf("scopeid 0x%x ",
+ ((struct sockaddr_in6 *)(ifa->ifa_addr))->sin6_scope_id);
+
+ if (ip6lifetime && (lifetime.ia6t_preferred || lifetime.ia6t_expire)) {
+ printf("pltime ");
+ if (lifetime.ia6t_preferred) {
+ printf("%s ", lifetime.ia6t_preferred < now.tv_sec
+ ? "0" :
+ sec2str(lifetime.ia6t_preferred - now.tv_sec));
+ } else
+ printf("infty ");
+
+ printf("vltime ");
+ if (lifetime.ia6t_expire) {
+ printf("%s ", lifetime.ia6t_expire < now.tv_sec
+ ? "0" :
+ sec2str(lifetime.ia6t_expire - now.tv_sec));
+ } else
+ printf("infty ");
+ }
+
+ print_vhid(ifa, " ");
+
+ putchar('\n');
+}
+
+#define SIN6(x) ((struct sockaddr_in6 *) &(x))
+static struct sockaddr_in6 *sin6tab[] = {
+ SIN6(in6_ridreq.ifr_addr), SIN6(in6_addreq.ifra_addr),
+ SIN6(in6_addreq.ifra_prefixmask), SIN6(in6_addreq.ifra_dstaddr)
+};
+
+static void
+in6_getprefix(const char *plen, int which)
+{
+ struct sockaddr_in6 *sin = sin6tab[which];
+ u_char *cp;
+ int len = atoi(plen);
+
+ if ((len < 0) || (len > 128))
+ errx(1, "%s: bad value", plen);
+ sin->sin6_len = sizeof(*sin);
+ if (which != MASK)
+ sin->sin6_family = AF_INET6;
+ if ((len == 0) || (len == 128)) {
+ memset(&sin->sin6_addr, 0xff, sizeof(struct in6_addr));
+ return;
+ }
+ memset((void *)&sin->sin6_addr, 0x00, sizeof(sin->sin6_addr));
+ for (cp = (u_char *)&sin->sin6_addr; len > 7; len -= 8)
+ *cp++ = 0xff;
+ *cp = 0xff << (8 - len);
+}
+
+static void
+in6_getaddr(const char *s, int which)
+{
+ struct sockaddr_in6 *sin = sin6tab[which];
+ struct addrinfo hints, *res;
+ int error = -1;
+
+ newaddr &= 1;
+
+ sin->sin6_len = sizeof(*sin);
+ if (which != MASK)
+ sin->sin6_family = AF_INET6;
+
+ if (which == ADDR) {
+ char *p = NULL;
+ if((p = strrchr(s, '/')) != NULL) {
+ *p = '\0';
+ in6_getprefix(p + 1, MASK);
+ explicit_prefix = 1;
+ }
+ }
+
+ if (sin->sin6_family == AF_INET6) {
+ bzero(&hints, sizeof(struct addrinfo));
+ hints.ai_family = AF_INET6;
+ error = getaddrinfo(s, NULL, &hints, &res);
+ }
+ if (error != 0) {
+ if (inet_pton(AF_INET6, s, &sin->sin6_addr) != 1)
+ errx(1, "%s: bad value", s);
+ } else
+ bcopy(res->ai_addr, sin, res->ai_addrlen);
+}
+
+static int
+prefix(void *val, int size)
+{
+ u_char *name = (u_char *)val;
+ int byte, bit, plen = 0;
+
+ for (byte = 0; byte < size; byte++, plen += 8)
+ if (name[byte] != 0xff)
+ break;
+ if (byte == size)
+ return (plen);
+ for (bit = 7; bit != 0; bit--, plen++)
+ if (!(name[byte] & (1 << bit)))
+ break;
+ for (; bit != 0; bit--)
+ if (name[byte] & (1 << bit))
+ return(0);
+ byte++;
+ for (; byte < size; byte++)
+ if (name[byte])
+ return(0);
+ return (plen);
+}
+
+static char *
+sec2str(time_t total)
+{
+ static char result[256];
+ int days, hours, mins, secs;
+ int first = 1;
+ char *p = result;
+
+ if (0) {
+ days = total / 3600 / 24;
+ hours = (total / 3600) % 24;
+ mins = (total / 60) % 60;
+ secs = total % 60;
+
+ if (days) {
+ first = 0;
+ p += sprintf(p, "%dd", days);
+ }
+ if (!first || hours) {
+ first = 0;
+ p += sprintf(p, "%dh", hours);
+ }
+ if (!first || mins) {
+ first = 0;
+ p += sprintf(p, "%dm", mins);
+ }
+ sprintf(p, "%ds", secs);
+ } else
+ sprintf(result, "%lu", (unsigned long)total);
+
+ return(result);
+}
+
+static void
+in6_postproc(int s, const struct afswtch *afp)
+{
+ if (explicit_prefix == 0) {
+ /* Aggregatable address architecture defines all prefixes
+ are 64. So, it is convenient to set prefixlen to 64 if
+ it is not specified. */
+ setifprefixlen("64", 0, s, afp);
+ /* in6_getprefix("64", MASK) if MASK is available here... */
+ }
+}
+
+static void
+in6_status_tunnel(int s)
+{
+ char src[NI_MAXHOST];
+ char dst[NI_MAXHOST];
+ struct in6_ifreq in6_ifr;
+ const struct sockaddr *sa = (const struct sockaddr *) &in6_ifr.ifr_addr;
+
+ memset(&in6_ifr, 0, sizeof(in6_ifr));
+ strncpy(in6_ifr.ifr_name, name, IFNAMSIZ);
+
+ if (ioctl(s, SIOCGIFPSRCADDR_IN6, (caddr_t)&in6_ifr) < 0)
+ return;
+ if (sa->sa_family != AF_INET6)
+ return;
+ if (getnameinfo(sa, sa->sa_len, src, sizeof(src), 0, 0,
+ NI_NUMERICHOST) != 0)
+ src[0] = '\0';
+
+ if (ioctl(s, SIOCGIFPDSTADDR_IN6, (caddr_t)&in6_ifr) < 0)
+ return;
+ if (sa->sa_family != AF_INET6)
+ return;
+ if (getnameinfo(sa, sa->sa_len, dst, sizeof(dst), 0, 0,
+ NI_NUMERICHOST) != 0)
+ dst[0] = '\0';
+
+ printf("\ttunnel inet6 %s --> %s\n", src, dst);
+}
+
+static void
+in6_set_tunnel(int s, struct addrinfo *srcres, struct addrinfo *dstres)
+{
+ struct in6_aliasreq in6_addreq;
+
+ memset(&in6_addreq, 0, sizeof(in6_addreq));
+ strncpy(in6_addreq.ifra_name, name, IFNAMSIZ);
+ memcpy(&in6_addreq.ifra_addr, srcres->ai_addr, srcres->ai_addr->sa_len);
+ memcpy(&in6_addreq.ifra_dstaddr, dstres->ai_addr,
+ dstres->ai_addr->sa_len);
+
+ if (ioctl(s, SIOCSIFPHYADDR_IN6, &in6_addreq) < 0)
+ warn("SIOCSIFPHYADDR_IN6");
+}
+
+static struct cmd inet6_cmds[] = {
+ DEF_CMD_ARG("prefixlen", setifprefixlen),
+ DEF_CMD("anycast", IN6_IFF_ANYCAST, setip6flags),
+ DEF_CMD("tentative", IN6_IFF_TENTATIVE, setip6flags),
+ DEF_CMD("-tentative", -IN6_IFF_TENTATIVE, setip6flags),
+ DEF_CMD("deprecated", IN6_IFF_DEPRECATED, setip6flags),
+ DEF_CMD("-deprecated", -IN6_IFF_DEPRECATED, setip6flags),
+ DEF_CMD("autoconf", IN6_IFF_AUTOCONF, setip6flags),
+ DEF_CMD("-autoconf", -IN6_IFF_AUTOCONF, setip6flags),
+ DEF_CMD("prefer_source",IN6_IFF_PREFER_SOURCE, setip6flags),
+ DEF_CMD("-prefer_source",-IN6_IFF_PREFER_SOURCE,setip6flags),
+ DEF_CMD("accept_rtadv", ND6_IFF_ACCEPT_RTADV, setnd6flags),
+ DEF_CMD("-accept_rtadv",-ND6_IFF_ACCEPT_RTADV, setnd6flags),
+ DEF_CMD("no_radr", ND6_IFF_NO_RADR, setnd6flags),
+ DEF_CMD("-no_radr", -ND6_IFF_NO_RADR, setnd6flags),
+ DEF_CMD("defaultif", 1, setnd6defif),
+ DEF_CMD("-defaultif", -1, setnd6defif),
+ DEF_CMD("ifdisabled", ND6_IFF_IFDISABLED, setnd6flags),
+ DEF_CMD("-ifdisabled", -ND6_IFF_IFDISABLED, setnd6flags),
+ DEF_CMD("nud", ND6_IFF_PERFORMNUD, setnd6flags),
+ DEF_CMD("-nud", -ND6_IFF_PERFORMNUD, setnd6flags),
+ DEF_CMD("auto_linklocal",ND6_IFF_AUTO_LINKLOCAL,setnd6flags),
+ DEF_CMD("-auto_linklocal",-ND6_IFF_AUTO_LINKLOCAL,setnd6flags),
+ DEF_CMD("no_prefer_iface",ND6_IFF_NO_PREFER_IFACE,setnd6flags),
+ DEF_CMD("-no_prefer_iface",-ND6_IFF_NO_PREFER_IFACE,setnd6flags),
+ DEF_CMD("no_dad", ND6_IFF_NO_DAD, setnd6flags),
+ DEF_CMD("-no_dad", -ND6_IFF_NO_DAD, setnd6flags),
+ DEF_CMD("ignoreloop", ND6_IFF_IGNORELOOP, setnd6flags),
+ DEF_CMD("-ignoreloop", -ND6_IFF_IGNORELOOP, setnd6flags),
+ DEF_CMD_ARG("pltime", setip6pltime),
+ DEF_CMD_ARG("vltime", setip6vltime),
+ DEF_CMD("eui64", 0, setip6eui64),
+};
+
+static struct afswtch af_inet6 = {
+ .af_name = "inet6",
+ .af_af = AF_INET6,
+ .af_status = in6_status,
+ .af_getaddr = in6_getaddr,
+ .af_getprefix = in6_getprefix,
+ .af_other_status = nd6_status,
+ .af_postproc = in6_postproc,
+ .af_status_tunnel = in6_status_tunnel,
+ .af_settunnel = in6_set_tunnel,
+ .af_difaddr = SIOCDIFADDR_IN6,
+ .af_aifaddr = SIOCAIFADDR_IN6,
+ .af_ridreq = &in6_addreq,
+ .af_addreq = &in6_addreq,
+};
+
+static void
+in6_Lopt_cb(const char *optarg __unused)
+{
+ ip6lifetime++; /* print IPv6 address lifetime */
+}
+static struct option in6_Lopt = {
+ .opt = "L",
+ .opt_usage = "[-L]",
+ .cb = in6_Lopt_cb
+};
+
+static __constructor void
+inet6_ctor(void)
+{
+#define N(a) (sizeof(a) / sizeof(a[0]))
+ size_t i;
+
+#ifndef RESCUE
+ if (!feature_present("inet6"))
+ return;
+#endif
+
+ for (i = 0; i < N(inet6_cmds); i++)
+ cmd_register(&inet6_cmds[i]);
+ af_register(&af_inet6);
+ opt_register(&in6_Lopt);
+#undef N
+}
diff --git a/sbin/ifconfig/af_link.c b/sbin/ifconfig/af_link.c
new file mode 100644
index 0000000..4a4b661
--- /dev/null
+++ b/sbin/ifconfig/af_link.c
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 1983, 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.
+ * 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 lint
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <net/if.h>
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ifaddrs.h>
+
+#include <net/if_dl.h>
+#include <net/if_types.h>
+#include <net/ethernet.h>
+
+#include "ifconfig.h"
+
+static struct ifreq link_ridreq;
+
+static void
+link_status(int s __unused, const struct ifaddrs *ifa)
+{
+ /* XXX no const 'cuz LLADDR is defined wrong */
+ struct sockaddr_dl *sdl = (struct sockaddr_dl *) ifa->ifa_addr;
+
+ if (sdl != NULL && sdl->sdl_alen > 0) {
+ if ((sdl->sdl_type == IFT_ETHER ||
+ sdl->sdl_type == IFT_L2VLAN ||
+ sdl->sdl_type == IFT_BRIDGE) &&
+ sdl->sdl_alen == ETHER_ADDR_LEN)
+ printf("\tether %s\n",
+ ether_ntoa((struct ether_addr *)LLADDR(sdl)));
+ else {
+ int n = sdl->sdl_nlen > 0 ? sdl->sdl_nlen + 1 : 0;
+
+ printf("\tlladdr %s\n", link_ntoa(sdl) + n);
+ }
+ }
+}
+
+static void
+link_getaddr(const char *addr, int which)
+{
+ char *temp;
+ struct sockaddr_dl sdl;
+ struct sockaddr *sa = &link_ridreq.ifr_addr;
+
+ if (which != ADDR)
+ errx(1, "can't set link-level netmask or broadcast");
+ if ((temp = malloc(strlen(addr) + 2)) == NULL)
+ errx(1, "malloc failed");
+ temp[0] = ':';
+ strcpy(temp + 1, addr);
+ sdl.sdl_len = sizeof(sdl);
+ link_addr(temp, &sdl);
+ free(temp);
+ if (sdl.sdl_alen > sizeof(sa->sa_data))
+ errx(1, "malformed link-level address");
+ sa->sa_family = AF_LINK;
+ sa->sa_len = sdl.sdl_alen;
+ bcopy(LLADDR(&sdl), sa->sa_data, sdl.sdl_alen);
+}
+
+static struct afswtch af_link = {
+ .af_name = "link",
+ .af_af = AF_LINK,
+ .af_status = link_status,
+ .af_getaddr = link_getaddr,
+ .af_aifaddr = SIOCSIFLLADDR,
+ .af_addreq = &link_ridreq,
+};
+static struct afswtch af_ether = {
+ .af_name = "ether",
+ .af_af = AF_LINK,
+ .af_status = link_status,
+ .af_getaddr = link_getaddr,
+ .af_aifaddr = SIOCSIFLLADDR,
+ .af_addreq = &link_ridreq,
+};
+static struct afswtch af_lladdr = {
+ .af_name = "lladdr",
+ .af_af = AF_LINK,
+ .af_status = link_status,
+ .af_getaddr = link_getaddr,
+ .af_aifaddr = SIOCSIFLLADDR,
+ .af_addreq = &link_ridreq,
+};
+
+static __constructor void
+link_ctor(void)
+{
+ af_register(&af_link);
+ af_register(&af_ether);
+ af_register(&af_lladdr);
+}
diff --git a/sbin/ifconfig/af_nd6.c b/sbin/ifconfig/af_nd6.c
new file mode 100644
index 0000000..9a1be79
--- /dev/null
+++ b/sbin/ifconfig/af_nd6.c
@@ -0,0 +1,169 @@
+/*
+ * Copyright (c) 2009 Hiroki Sato. 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.
+ *
+ * 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 lint
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+#include <net/if.h>
+#include <net/route.h>
+
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <ifaddrs.h>
+
+#include <arpa/inet.h>
+
+#include <netinet/in.h>
+#include <netinet/in_var.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include <netinet6/nd6.h>
+
+#include "ifconfig.h"
+
+#define MAX_SYSCTL_TRY 5
+#define ND6BITS "\020\001PERFORMNUD\002ACCEPT_RTADV\003PREFER_SOURCE" \
+ "\004IFDISABLED\005DONT_SET_IFROUTE\006AUTO_LINKLOCAL" \
+ "\007NO_RADR\010NO_PREFER_IFACE\011IGNORELOOP\012NO_DAD" \
+ "\020DEFAULTIF"
+
+static int isnd6defif(int);
+void setnd6flags(const char *, int, int, const struct afswtch *);
+void setnd6defif(const char *, int, int, const struct afswtch *);
+void nd6_status(int);
+
+void
+setnd6flags(const char *dummyaddr __unused,
+ int d, int s,
+ const struct afswtch *afp)
+{
+ struct in6_ndireq nd;
+ int error;
+
+ memset(&nd, 0, sizeof(nd));
+ strncpy(nd.ifname, ifr.ifr_name, sizeof(nd.ifname));
+ error = ioctl(s, SIOCGIFINFO_IN6, &nd);
+ if (error) {
+ warn("ioctl(SIOCGIFINFO_IN6)");
+ return;
+ }
+ if (d < 0)
+ nd.ndi.flags &= ~(-d);
+ else
+ nd.ndi.flags |= d;
+ error = ioctl(s, SIOCSIFINFO_IN6, (caddr_t)&nd);
+ if (error)
+ warn("ioctl(SIOCSIFINFO_IN6)");
+}
+
+void
+setnd6defif(const char *dummyaddr __unused,
+ int d, int s,
+ const struct afswtch *afp)
+{
+ struct in6_ndifreq ndifreq;
+ int ifindex;
+ int error;
+
+ memset(&ndifreq, 0, sizeof(ndifreq));
+ strncpy(ndifreq.ifname, ifr.ifr_name, sizeof(ndifreq.ifname));
+
+ if (d < 0) {
+ if (isnd6defif(s)) {
+ /* ifindex = 0 means to remove default if */
+ ifindex = 0;
+ } else
+ return;
+ } else if ((ifindex = if_nametoindex(ndifreq.ifname)) == 0) {
+ warn("if_nametoindex(%s)", ndifreq.ifname);
+ return;
+ }
+
+ ndifreq.ifindex = ifindex;
+ error = ioctl(s, SIOCSDEFIFACE_IN6, (caddr_t)&ndifreq);
+ if (error)
+ warn("ioctl(SIOCSDEFIFACE_IN6)");
+}
+
+static int
+isnd6defif(int s)
+{
+ struct in6_ndifreq ndifreq;
+ unsigned int ifindex;
+ int error;
+
+ memset(&ndifreq, 0, sizeof(ndifreq));
+ strncpy(ndifreq.ifname, ifr.ifr_name, sizeof(ndifreq.ifname));
+
+ ifindex = if_nametoindex(ndifreq.ifname);
+ error = ioctl(s, SIOCGDEFIFACE_IN6, (caddr_t)&ndifreq);
+ if (error) {
+ warn("ioctl(SIOCGDEFIFACE_IN6)");
+ return (error);
+ }
+ return (ndifreq.ifindex == ifindex);
+}
+
+void
+nd6_status(int s)
+{
+ struct in6_ndireq nd;
+ int s6;
+ int error;
+ int isdefif;
+
+ memset(&nd, 0, sizeof(nd));
+ strncpy(nd.ifname, ifr.ifr_name, sizeof(nd.ifname));
+ if ((s6 = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) {
+ if (errno != EAFNOSUPPORT && errno != EPROTONOSUPPORT)
+ warn("socket(AF_INET6, SOCK_DGRAM)");
+ return;
+ }
+ error = ioctl(s6, SIOCGIFINFO_IN6, &nd);
+ if (error) {
+ if (errno != EPFNOSUPPORT)
+ warn("ioctl(SIOCGIFINFO_IN6)");
+ close(s6);
+ return;
+ }
+ isdefif = isnd6defif(s6);
+ close(s6);
+ if (nd.ndi.flags == 0 && !isdefif)
+ return;
+ printb("\tnd6 options",
+ (unsigned int)(nd.ndi.flags | (isdefif << 15)), ND6BITS);
+ putchar('\n');
+}
diff --git a/sbin/ifconfig/carp.c b/sbin/ifconfig/carp.c
new file mode 100644
index 0000000..adff153
--- /dev/null
+++ b/sbin/ifconfig/carp.c
@@ -0,0 +1,227 @@
+/* $FreeBSD$ */
+/* from $OpenBSD: ifconfig.c,v 1.82 2003/10/19 05:43:35 mcbride Exp $ */
+
+/*
+ * Copyright (c) 2002 Michael Shalayeff. All rights reserved.
+ * Copyright (c) 2003 Ryan McBride. 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.
+ *
+ * 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 OR HIS RELATIVES 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 MIND, 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 <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <netinet/in_var.h>
+#include <netinet/ip_carp.h>
+
+#include <ctype.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <err.h>
+#include <errno.h>
+
+#include "ifconfig.h"
+
+static const char *carp_states[] = { CARP_STATES };
+
+static void carp_status(int s);
+static void setcarp_vhid(const char *, int, int, const struct afswtch *rafp);
+static void setcarp_callback(int, void *);
+static void setcarp_advbase(const char *,int, int, const struct afswtch *rafp);
+static void setcarp_advskew(const char *, int, int, const struct afswtch *rafp);
+static void setcarp_passwd(const char *, int, int, const struct afswtch *rafp);
+
+static int carpr_vhid = -1;
+static int carpr_advskew = -1;
+static int carpr_advbase = -1;
+static int carpr_state = -1;
+static unsigned char const *carpr_key;
+
+static void
+carp_status(int s)
+{
+ struct carpreq carpr[CARP_MAXVHID];
+ int i;
+
+ bzero(carpr, sizeof(struct carpreq) * CARP_MAXVHID);
+ carpr[0].carpr_count = CARP_MAXVHID;
+ ifr.ifr_data = (caddr_t)&carpr;
+
+ if (ioctl(s, SIOCGVH, (caddr_t)&ifr) == -1)
+ return;
+
+ for (i = 0; i < carpr[0].carpr_count; i++) {
+ printf("\tcarp: %s vhid %d advbase %d advskew %d",
+ carp_states[carpr[i].carpr_state], carpr[i].carpr_vhid,
+ carpr[i].carpr_advbase, carpr[i].carpr_advskew);
+ if (printkeys && carpr[i].carpr_key[0] != '\0')
+ printf(" key \"%s\"\n", carpr[i].carpr_key);
+ else
+ printf("\n");
+ }
+}
+
+static void
+setcarp_vhid(const char *val, int d, int s, const struct afswtch *afp)
+{
+
+ carpr_vhid = atoi(val);
+
+ if (carpr_vhid <= 0 || carpr_vhid > CARP_MAXVHID)
+ errx(1, "vhid must be greater than 0 and less than %u",
+ CARP_MAXVHID);
+
+ switch (afp->af_af) {
+#ifdef INET
+ case AF_INET:
+ {
+ struct in_aliasreq *ifra;
+
+ ifra = (struct in_aliasreq *)afp->af_addreq;
+ ifra->ifra_vhid = carpr_vhid;
+ break;
+ }
+#endif
+#ifdef INET6
+ case AF_INET6:
+ {
+ struct in6_aliasreq *ifra;
+
+ ifra = (struct in6_aliasreq *)afp->af_addreq;
+ ifra->ifra_vhid = carpr_vhid;
+ break;
+ }
+#endif
+ default:
+ errx(1, "%s doesn't support carp(4)", afp->af_name);
+ }
+
+ callback_register(setcarp_callback, NULL);
+}
+
+static void
+setcarp_callback(int s, void *arg __unused)
+{
+ struct carpreq carpr;
+
+ bzero(&carpr, sizeof(struct carpreq));
+ carpr.carpr_vhid = carpr_vhid;
+ carpr.carpr_count = 1;
+ ifr.ifr_data = (caddr_t)&carpr;
+
+ if (ioctl(s, SIOCGVH, (caddr_t)&ifr) == -1 && errno != ENOENT)
+ err(1, "SIOCGVH");
+
+ if (carpr_key != NULL)
+ /* XXX Should hash the password into the key here? */
+ strlcpy(carpr.carpr_key, carpr_key, CARP_KEY_LEN);
+ if (carpr_advskew > -1)
+ carpr.carpr_advskew = carpr_advskew;
+ if (carpr_advbase > -1)
+ carpr.carpr_advbase = carpr_advbase;
+ if (carpr_state > -1)
+ carpr.carpr_state = carpr_state;
+
+ if (ioctl(s, SIOCSVH, (caddr_t)&ifr) == -1)
+ err(1, "SIOCSVH");
+}
+
+static void
+setcarp_passwd(const char *val, int d, int s, const struct afswtch *afp)
+{
+
+ if (carpr_vhid == -1)
+ errx(1, "passwd requires vhid");
+
+ carpr_key = val;
+}
+
+static void
+setcarp_advskew(const char *val, int d, int s, const struct afswtch *afp)
+{
+
+ if (carpr_vhid == -1)
+ errx(1, "advskew requires vhid");
+
+ carpr_advskew = atoi(val);
+}
+
+static void
+setcarp_advbase(const char *val, int d, int s, const struct afswtch *afp)
+{
+
+ if (carpr_vhid == -1)
+ errx(1, "advbase requires vhid");
+
+ carpr_advbase = atoi(val);
+}
+
+static void
+setcarp_state(const char *val, int d, int s, const struct afswtch *afp)
+{
+ int i;
+
+ if (carpr_vhid == -1)
+ errx(1, "state requires vhid");
+
+ for (i = 0; i <= CARP_MAXSTATE; i++)
+ if (strcasecmp(carp_states[i], val) == 0) {
+ carpr_state = i;
+ return;
+ }
+
+ errx(1, "unknown state");
+}
+
+static struct cmd carp_cmds[] = {
+ DEF_CMD_ARG("advbase", setcarp_advbase),
+ DEF_CMD_ARG("advskew", setcarp_advskew),
+ DEF_CMD_ARG("pass", setcarp_passwd),
+ DEF_CMD_ARG("vhid", setcarp_vhid),
+ DEF_CMD_ARG("state", setcarp_state),
+};
+static struct afswtch af_carp = {
+ .af_name = "af_carp",
+ .af_af = AF_UNSPEC,
+ .af_other_status = carp_status,
+};
+
+static __constructor void
+carp_ctor(void)
+{
+#define N(a) (sizeof(a) / sizeof(a[0]))
+ int i;
+
+ for (i = 0; i < N(carp_cmds); i++)
+ cmd_register(&carp_cmds[i]);
+ af_register(&af_carp);
+#undef N
+}
diff --git a/sbin/ifconfig/ifbridge.c b/sbin/ifconfig/ifbridge.c
new file mode 100644
index 0000000..65c3317
--- /dev/null
+++ b/sbin/ifconfig/ifbridge.c
@@ -0,0 +1,759 @@
+/*-
+ * Copyright 2001 Wasabi Systems, Inc.
+ * All rights reserved.
+ *
+ * Written by Jason R. Thorpe for Wasabi Systems, Inc.
+ *
+ * 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 for the NetBSD Project by
+ * Wasabi Systems, Inc.
+ * 4. The name of Wasabi Systems, Inc. may not be used to endorse
+ * or promote products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY WASABI SYSTEMS, INC. ``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 WASABI SYSTEMS, INC
+ * 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 lint
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <net/ethernet.h>
+#include <net/if.h>
+#include <net/if_bridgevar.h>
+#include <net/route.h>
+
+#include <ctype.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <err.h>
+#include <errno.h>
+
+#include "ifconfig.h"
+
+#define PV2ID(pv, epri, eaddr) do { \
+ epri = pv >> 48; \
+ eaddr[0] = pv >> 40; \
+ eaddr[1] = pv >> 32; \
+ eaddr[2] = pv >> 24; \
+ eaddr[3] = pv >> 16; \
+ eaddr[4] = pv >> 8; \
+ eaddr[5] = pv >> 0; \
+} while (0)
+
+static const char *stpstates[] = {
+ "disabled",
+ "listening",
+ "learning",
+ "forwarding",
+ "blocking",
+ "discarding"
+};
+static const char *stpproto[] = {
+ "stp",
+ "-",
+ "rstp"
+};
+static const char *stproles[] = {
+ "disabled",
+ "root",
+ "designated",
+ "alternate",
+ "backup"
+};
+
+static int
+get_val(const char *cp, u_long *valp)
+{
+ char *endptr;
+ u_long val;
+
+ errno = 0;
+ val = strtoul(cp, &endptr, 0);
+ if (cp[0] == '\0' || endptr[0] != '\0' || errno == ERANGE)
+ return (-1);
+
+ *valp = val;
+ return (0);
+}
+
+static int
+do_cmd(int sock, u_long op, void *arg, size_t argsize, int set)
+{
+ struct ifdrv ifd;
+
+ memset(&ifd, 0, sizeof(ifd));
+
+ strlcpy(ifd.ifd_name, ifr.ifr_name, sizeof(ifd.ifd_name));
+ ifd.ifd_cmd = op;
+ ifd.ifd_len = argsize;
+ ifd.ifd_data = arg;
+
+ return (ioctl(sock, set ? SIOCSDRVSPEC : SIOCGDRVSPEC, &ifd));
+}
+
+static void
+do_bridgeflag(int sock, const char *ifs, int flag, int set)
+{
+ struct ifbreq req;
+
+ strlcpy(req.ifbr_ifsname, ifs, sizeof(req.ifbr_ifsname));
+
+ if (do_cmd(sock, BRDGGIFFLGS, &req, sizeof(req), 0) < 0)
+ err(1, "unable to get bridge flags");
+
+ if (set)
+ req.ifbr_ifsflags |= flag;
+ else
+ req.ifbr_ifsflags &= ~flag;
+
+ if (do_cmd(sock, BRDGSIFFLGS, &req, sizeof(req), 1) < 0)
+ err(1, "unable to set bridge flags");
+}
+
+static void
+bridge_interfaces(int s, const char *prefix)
+{
+ struct ifbifconf bifc;
+ struct ifbreq *req;
+ char *inbuf = NULL, *ninbuf;
+ char *p, *pad;
+ int i, len = 8192;
+
+ pad = strdup(prefix);
+ if (pad == NULL)
+ err(1, "strdup");
+ /* replace the prefix with whitespace */
+ for (p = pad; *p != '\0'; p++) {
+ if(isprint(*p))
+ *p = ' ';
+ }
+
+ for (;;) {
+ ninbuf = realloc(inbuf, len);
+ if (ninbuf == NULL)
+ err(1, "unable to allocate interface buffer");
+ bifc.ifbic_len = len;
+ bifc.ifbic_buf = inbuf = ninbuf;
+ if (do_cmd(s, BRDGGIFS, &bifc, sizeof(bifc), 0) < 0)
+ err(1, "unable to get interface list");
+ if ((bifc.ifbic_len + sizeof(*req)) < len)
+ break;
+ len *= 2;
+ }
+
+ for (i = 0; i < bifc.ifbic_len / sizeof(*req); i++) {
+ req = bifc.ifbic_req + i;
+ printf("%s%s ", prefix, req->ifbr_ifsname);
+ printb("flags", req->ifbr_ifsflags, IFBIFBITS);
+ printf("\n");
+
+ printf("%s", pad);
+ printf("ifmaxaddr %u", req->ifbr_addrmax);
+ printf(" port %u priority %u", req->ifbr_portno,
+ req->ifbr_priority);
+ printf(" path cost %u", req->ifbr_path_cost);
+
+ if (req->ifbr_ifsflags & IFBIF_STP) {
+ if (req->ifbr_proto <
+ sizeof(stpproto) / sizeof(stpproto[0]))
+ printf(" proto %s", stpproto[req->ifbr_proto]);
+ else
+ printf(" <unknown proto %d>",
+ req->ifbr_proto);
+
+ printf("\n%s", pad);
+ if (req->ifbr_role <
+ sizeof(stproles) / sizeof(stproles[0]))
+ printf("role %s", stproles[req->ifbr_role]);
+ else
+ printf("<unknown role %d>",
+ req->ifbr_role);
+ if (req->ifbr_state <
+ sizeof(stpstates) / sizeof(stpstates[0]))
+ printf(" state %s", stpstates[req->ifbr_state]);
+ else
+ printf(" <unknown state %d>",
+ req->ifbr_state);
+ }
+ printf("\n");
+ }
+
+ free(inbuf);
+}
+
+static void
+bridge_addresses(int s, const char *prefix)
+{
+ struct ifbaconf ifbac;
+ struct ifbareq *ifba;
+ char *inbuf = NULL, *ninbuf;
+ int i, len = 8192;
+ struct ether_addr ea;
+
+ for (;;) {
+ ninbuf = realloc(inbuf, len);
+ if (ninbuf == NULL)
+ err(1, "unable to allocate address buffer");
+ ifbac.ifbac_len = len;
+ ifbac.ifbac_buf = inbuf = ninbuf;
+ if (do_cmd(s, BRDGRTS, &ifbac, sizeof(ifbac), 0) < 0)
+ err(1, "unable to get address cache");
+ if ((ifbac.ifbac_len + sizeof(*ifba)) < len)
+ break;
+ len *= 2;
+ }
+
+ for (i = 0; i < ifbac.ifbac_len / sizeof(*ifba); i++) {
+ ifba = ifbac.ifbac_req + i;
+ memcpy(ea.octet, ifba->ifba_dst,
+ sizeof(ea.octet));
+ printf("%s%s Vlan%d %s %lu ", prefix, ether_ntoa(&ea),
+ ifba->ifba_vlan, ifba->ifba_ifsname, ifba->ifba_expire);
+ printb("flags", ifba->ifba_flags, IFBAFBITS);
+ printf("\n");
+ }
+
+ free(inbuf);
+}
+
+static void
+bridge_status(int s)
+{
+ struct ifbropreq ifbp;
+ struct ifbrparam param;
+ u_int16_t pri;
+ u_int8_t ht, fd, ma, hc, pro;
+ u_int8_t lladdr[ETHER_ADDR_LEN];
+ u_int16_t bprio;
+ u_int32_t csize, ctime;
+
+ if (do_cmd(s, BRDGGCACHE, &param, sizeof(param), 0) < 0)
+ return;
+ csize = param.ifbrp_csize;
+ if (do_cmd(s, BRDGGTO, &param, sizeof(param), 0) < 0)
+ return;
+ ctime = param.ifbrp_ctime;
+ if (do_cmd(s, BRDGPARAM, &ifbp, sizeof(ifbp), 0) < 0)
+ return;
+ pri = ifbp.ifbop_priority;
+ pro = ifbp.ifbop_protocol;
+ ht = ifbp.ifbop_hellotime;
+ fd = ifbp.ifbop_fwddelay;
+ hc = ifbp.ifbop_holdcount;
+ ma = ifbp.ifbop_maxage;
+
+ PV2ID(ifbp.ifbop_bridgeid, bprio, lladdr);
+ printf("\tid %s priority %u hellotime %u fwddelay %u\n",
+ ether_ntoa((struct ether_addr *)lladdr), pri, ht, fd);
+ printf("\tmaxage %u holdcnt %u proto %s maxaddr %u timeout %u\n",
+ ma, hc, stpproto[pro], csize, ctime);
+
+ PV2ID(ifbp.ifbop_designated_root, bprio, lladdr);
+ printf("\troot id %s priority %d ifcost %u port %u\n",
+ ether_ntoa((struct ether_addr *)lladdr), bprio,
+ ifbp.ifbop_root_path_cost, ifbp.ifbop_root_port & 0xfff);
+
+ bridge_interfaces(s, "\tmember: ");
+
+ return;
+
+}
+
+static void
+setbridge_add(const char *val, int d, int s, const struct afswtch *afp)
+{
+ struct ifbreq req;
+
+ memset(&req, 0, sizeof(req));
+ strlcpy(req.ifbr_ifsname, val, sizeof(req.ifbr_ifsname));
+ if (do_cmd(s, BRDGADD, &req, sizeof(req), 1) < 0)
+ err(1, "BRDGADD %s", val);
+}
+
+static void
+setbridge_delete(const char *val, int d, int s, const struct afswtch *afp)
+{
+ struct ifbreq req;
+
+ memset(&req, 0, sizeof(req));
+ strlcpy(req.ifbr_ifsname, val, sizeof(req.ifbr_ifsname));
+ if (do_cmd(s, BRDGDEL, &req, sizeof(req), 1) < 0)
+ err(1, "BRDGDEL %s", val);
+}
+
+static void
+setbridge_discover(const char *val, int d, int s, const struct afswtch *afp)
+{
+
+ do_bridgeflag(s, val, IFBIF_DISCOVER, 1);
+}
+
+static void
+unsetbridge_discover(const char *val, int d, int s, const struct afswtch *afp)
+{
+
+ do_bridgeflag(s, val, IFBIF_DISCOVER, 0);
+}
+
+static void
+setbridge_learn(const char *val, int d, int s, const struct afswtch *afp)
+{
+
+ do_bridgeflag(s, val, IFBIF_LEARNING, 1);
+}
+
+static void
+unsetbridge_learn(const char *val, int d, int s, const struct afswtch *afp)
+{
+
+ do_bridgeflag(s, val, IFBIF_LEARNING, 0);
+}
+
+static void
+setbridge_sticky(const char *val, int d, int s, const struct afswtch *afp)
+{
+
+ do_bridgeflag(s, val, IFBIF_STICKY, 1);
+}
+
+static void
+unsetbridge_sticky(const char *val, int d, int s, const struct afswtch *afp)
+{
+
+ do_bridgeflag(s, val, IFBIF_STICKY, 0);
+}
+
+static void
+setbridge_span(const char *val, int d, int s, const struct afswtch *afp)
+{
+ struct ifbreq req;
+
+ memset(&req, 0, sizeof(req));
+ strlcpy(req.ifbr_ifsname, val, sizeof(req.ifbr_ifsname));
+ if (do_cmd(s, BRDGADDS, &req, sizeof(req), 1) < 0)
+ err(1, "BRDGADDS %s", val);
+}
+
+static void
+unsetbridge_span(const char *val, int d, int s, const struct afswtch *afp)
+{
+ struct ifbreq req;
+
+ memset(&req, 0, sizeof(req));
+ strlcpy(req.ifbr_ifsname, val, sizeof(req.ifbr_ifsname));
+ if (do_cmd(s, BRDGDELS, &req, sizeof(req), 1) < 0)
+ err(1, "BRDGDELS %s", val);
+}
+
+static void
+setbridge_stp(const char *val, int d, int s, const struct afswtch *afp)
+{
+
+ do_bridgeflag(s, val, IFBIF_STP, 1);
+}
+
+static void
+unsetbridge_stp(const char *val, int d, int s, const struct afswtch *afp)
+{
+
+ do_bridgeflag(s, val, IFBIF_STP, 0);
+}
+
+static void
+setbridge_edge(const char *val, int d, int s, const struct afswtch *afp)
+{
+ do_bridgeflag(s, val, IFBIF_BSTP_EDGE, 1);
+}
+
+static void
+unsetbridge_edge(const char *val, int d, int s, const struct afswtch *afp)
+{
+ do_bridgeflag(s, val, IFBIF_BSTP_EDGE, 0);
+}
+
+static void
+setbridge_autoedge(const char *val, int d, int s, const struct afswtch *afp)
+{
+ do_bridgeflag(s, val, IFBIF_BSTP_AUTOEDGE, 1);
+}
+
+static void
+unsetbridge_autoedge(const char *val, int d, int s, const struct afswtch *afp)
+{
+ do_bridgeflag(s, val, IFBIF_BSTP_AUTOEDGE, 0);
+}
+
+static void
+setbridge_ptp(const char *val, int d, int s, const struct afswtch *afp)
+{
+ do_bridgeflag(s, val, IFBIF_BSTP_PTP, 1);
+}
+
+static void
+unsetbridge_ptp(const char *val, int d, int s, const struct afswtch *afp)
+{
+ do_bridgeflag(s, val, IFBIF_BSTP_PTP, 0);
+}
+
+static void
+setbridge_autoptp(const char *val, int d, int s, const struct afswtch *afp)
+{
+ do_bridgeflag(s, val, IFBIF_BSTP_AUTOPTP, 1);
+}
+
+static void
+unsetbridge_autoptp(const char *val, int d, int s, const struct afswtch *afp)
+{
+ do_bridgeflag(s, val, IFBIF_BSTP_AUTOPTP, 0);
+}
+
+static void
+setbridge_flush(const char *val, int d, int s, const struct afswtch *afp)
+{
+ struct ifbreq req;
+
+ memset(&req, 0, sizeof(req));
+ req.ifbr_ifsflags = IFBF_FLUSHDYN;
+ if (do_cmd(s, BRDGFLUSH, &req, sizeof(req), 1) < 0)
+ err(1, "BRDGFLUSH");
+}
+
+static void
+setbridge_flushall(const char *val, int d, int s, const struct afswtch *afp)
+{
+ struct ifbreq req;
+
+ memset(&req, 0, sizeof(req));
+ req.ifbr_ifsflags = IFBF_FLUSHALL;
+ if (do_cmd(s, BRDGFLUSH, &req, sizeof(req), 1) < 0)
+ err(1, "BRDGFLUSH");
+}
+
+static void
+setbridge_static(const char *val, const char *mac, int s,
+ const struct afswtch *afp)
+{
+ struct ifbareq req;
+ struct ether_addr *ea;
+
+ memset(&req, 0, sizeof(req));
+ strlcpy(req.ifba_ifsname, val, sizeof(req.ifba_ifsname));
+
+ ea = ether_aton(mac);
+ if (ea == NULL)
+ errx(1, "%s: invalid address: %s", val, mac);
+
+ memcpy(req.ifba_dst, ea->octet, sizeof(req.ifba_dst));
+ req.ifba_flags = IFBAF_STATIC;
+ req.ifba_vlan = 1; /* XXX allow user to specify */
+
+ if (do_cmd(s, BRDGSADDR, &req, sizeof(req), 1) < 0)
+ err(1, "BRDGSADDR %s", val);
+}
+
+static void
+setbridge_deladdr(const char *val, int d, int s, const struct afswtch *afp)
+{
+ struct ifbareq req;
+ struct ether_addr *ea;
+
+ memset(&req, 0, sizeof(req));
+
+ ea = ether_aton(val);
+ if (ea == NULL)
+ errx(1, "invalid address: %s", val);
+
+ memcpy(req.ifba_dst, ea->octet, sizeof(req.ifba_dst));
+
+ if (do_cmd(s, BRDGDADDR, &req, sizeof(req), 1) < 0)
+ err(1, "BRDGDADDR %s", val);
+}
+
+static void
+setbridge_addr(const char *val, int d, int s, const struct afswtch *afp)
+{
+
+ bridge_addresses(s, "");
+}
+
+static void
+setbridge_maxaddr(const char *arg, int d, int s, const struct afswtch *afp)
+{
+ struct ifbrparam param;
+ u_long val;
+
+ if (get_val(arg, &val) < 0 || (val & ~0xffffffff) != 0)
+ errx(1, "invalid value: %s", arg);
+
+ param.ifbrp_csize = val & 0xffffffff;
+
+ if (do_cmd(s, BRDGSCACHE, &param, sizeof(param), 1) < 0)
+ err(1, "BRDGSCACHE %s", arg);
+}
+
+static void
+setbridge_hellotime(const char *arg, int d, int s, const struct afswtch *afp)
+{
+ struct ifbrparam param;
+ u_long val;
+
+ if (get_val(arg, &val) < 0 || (val & ~0xff) != 0)
+ errx(1, "invalid value: %s", arg);
+
+ param.ifbrp_hellotime = val & 0xff;
+
+ if (do_cmd(s, BRDGSHT, &param, sizeof(param), 1) < 0)
+ err(1, "BRDGSHT %s", arg);
+}
+
+static void
+setbridge_fwddelay(const char *arg, int d, int s, const struct afswtch *afp)
+{
+ struct ifbrparam param;
+ u_long val;
+
+ if (get_val(arg, &val) < 0 || (val & ~0xff) != 0)
+ errx(1, "invalid value: %s", arg);
+
+ param.ifbrp_fwddelay = val & 0xff;
+
+ if (do_cmd(s, BRDGSFD, &param, sizeof(param), 1) < 0)
+ err(1, "BRDGSFD %s", arg);
+}
+
+static void
+setbridge_maxage(const char *arg, int d, int s, const struct afswtch *afp)
+{
+ struct ifbrparam param;
+ u_long val;
+
+ if (get_val(arg, &val) < 0 || (val & ~0xff) != 0)
+ errx(1, "invalid value: %s", arg);
+
+ param.ifbrp_maxage = val & 0xff;
+
+ if (do_cmd(s, BRDGSMA, &param, sizeof(param), 1) < 0)
+ err(1, "BRDGSMA %s", arg);
+}
+
+static void
+setbridge_priority(const char *arg, int d, int s, const struct afswtch *afp)
+{
+ struct ifbrparam param;
+ u_long val;
+
+ if (get_val(arg, &val) < 0 || (val & ~0xffff) != 0)
+ errx(1, "invalid value: %s", arg);
+
+ param.ifbrp_prio = val & 0xffff;
+
+ if (do_cmd(s, BRDGSPRI, &param, sizeof(param), 1) < 0)
+ err(1, "BRDGSPRI %s", arg);
+}
+
+static void
+setbridge_protocol(const char *arg, int d, int s, const struct afswtch *afp)
+{
+ struct ifbrparam param;
+
+ if (strcasecmp(arg, "stp") == 0) {
+ param.ifbrp_proto = 0;
+ } else if (strcasecmp(arg, "rstp") == 0) {
+ param.ifbrp_proto = 2;
+ } else {
+ errx(1, "unknown stp protocol");
+ }
+
+ if (do_cmd(s, BRDGSPROTO, &param, sizeof(param), 1) < 0)
+ err(1, "BRDGSPROTO %s", arg);
+}
+
+static void
+setbridge_holdcount(const char *arg, int d, int s, const struct afswtch *afp)
+{
+ struct ifbrparam param;
+ u_long val;
+
+ if (get_val(arg, &val) < 0 || (val & ~0xff) != 0)
+ errx(1, "invalid value: %s", arg);
+
+ param.ifbrp_txhc = val & 0xff;
+
+ if (do_cmd(s, BRDGSTXHC, &param, sizeof(param), 1) < 0)
+ err(1, "BRDGSTXHC %s", arg);
+}
+
+static void
+setbridge_ifpriority(const char *ifn, const char *pri, int s,
+ const struct afswtch *afp)
+{
+ struct ifbreq req;
+ u_long val;
+
+ memset(&req, 0, sizeof(req));
+
+ if (get_val(pri, &val) < 0 || (val & ~0xff) != 0)
+ errx(1, "invalid value: %s", pri);
+
+ strlcpy(req.ifbr_ifsname, ifn, sizeof(req.ifbr_ifsname));
+ req.ifbr_priority = val & 0xff;
+
+ if (do_cmd(s, BRDGSIFPRIO, &req, sizeof(req), 1) < 0)
+ err(1, "BRDGSIFPRIO %s", pri);
+}
+
+static void
+setbridge_ifpathcost(const char *ifn, const char *cost, int s,
+ const struct afswtch *afp)
+{
+ struct ifbreq req;
+ u_long val;
+
+ memset(&req, 0, sizeof(req));
+
+ if (get_val(cost, &val) < 0)
+ errx(1, "invalid value: %s", cost);
+
+ strlcpy(req.ifbr_ifsname, ifn, sizeof(req.ifbr_ifsname));
+ req.ifbr_path_cost = val;
+
+ if (do_cmd(s, BRDGSIFCOST, &req, sizeof(req), 1) < 0)
+ err(1, "BRDGSIFCOST %s", cost);
+}
+
+static void
+setbridge_ifmaxaddr(const char *ifn, const char *arg, int s,
+ const struct afswtch *afp)
+{
+ struct ifbreq req;
+ u_long val;
+
+ memset(&req, 0, sizeof(req));
+
+ if (get_val(arg, &val) < 0 || (val & ~0xffffffff) != 0)
+ errx(1, "invalid value: %s", arg);
+
+ strlcpy(req.ifbr_ifsname, ifn, sizeof(req.ifbr_ifsname));
+ req.ifbr_addrmax = val & 0xffffffff;
+
+ if (do_cmd(s, BRDGSIFAMAX, &req, sizeof(req), 1) < 0)
+ err(1, "BRDGSIFAMAX %s", arg);
+}
+
+static void
+setbridge_timeout(const char *arg, int d, int s, const struct afswtch *afp)
+{
+ struct ifbrparam param;
+ u_long val;
+
+ if (get_val(arg, &val) < 0 || (val & ~0xffffffff) != 0)
+ errx(1, "invalid value: %s", arg);
+
+ param.ifbrp_ctime = val & 0xffffffff;
+
+ if (do_cmd(s, BRDGSTO, &param, sizeof(param), 1) < 0)
+ err(1, "BRDGSTO %s", arg);
+}
+
+static void
+setbridge_private(const char *val, int d, int s, const struct afswtch *afp)
+{
+
+ do_bridgeflag(s, val, IFBIF_PRIVATE, 1);
+}
+
+static void
+unsetbridge_private(const char *val, int d, int s, const struct afswtch *afp)
+{
+
+ do_bridgeflag(s, val, IFBIF_PRIVATE, 0);
+}
+
+static struct cmd bridge_cmds[] = {
+ DEF_CMD_ARG("addm", setbridge_add),
+ DEF_CMD_ARG("deletem", setbridge_delete),
+ DEF_CMD_ARG("discover", setbridge_discover),
+ DEF_CMD_ARG("-discover", unsetbridge_discover),
+ DEF_CMD_ARG("learn", setbridge_learn),
+ DEF_CMD_ARG("-learn", unsetbridge_learn),
+ DEF_CMD_ARG("sticky", setbridge_sticky),
+ DEF_CMD_ARG("-sticky", unsetbridge_sticky),
+ DEF_CMD_ARG("span", setbridge_span),
+ DEF_CMD_ARG("-span", unsetbridge_span),
+ DEF_CMD_ARG("stp", setbridge_stp),
+ DEF_CMD_ARG("-stp", unsetbridge_stp),
+ DEF_CMD_ARG("edge", setbridge_edge),
+ DEF_CMD_ARG("-edge", unsetbridge_edge),
+ DEF_CMD_ARG("autoedge", setbridge_autoedge),
+ DEF_CMD_ARG("-autoedge", unsetbridge_autoedge),
+ DEF_CMD_ARG("ptp", setbridge_ptp),
+ DEF_CMD_ARG("-ptp", unsetbridge_ptp),
+ DEF_CMD_ARG("autoptp", setbridge_autoptp),
+ DEF_CMD_ARG("-autoptp", unsetbridge_autoptp),
+ DEF_CMD("flush", 0, setbridge_flush),
+ DEF_CMD("flushall", 0, setbridge_flushall),
+ DEF_CMD_ARG2("static", setbridge_static),
+ DEF_CMD_ARG("deladdr", setbridge_deladdr),
+ DEF_CMD("addr", 1, setbridge_addr),
+ DEF_CMD_ARG("maxaddr", setbridge_maxaddr),
+ DEF_CMD_ARG("hellotime", setbridge_hellotime),
+ DEF_CMD_ARG("fwddelay", setbridge_fwddelay),
+ DEF_CMD_ARG("maxage", setbridge_maxage),
+ DEF_CMD_ARG("priority", setbridge_priority),
+ DEF_CMD_ARG("proto", setbridge_protocol),
+ DEF_CMD_ARG("holdcnt", setbridge_holdcount),
+ DEF_CMD_ARG2("ifpriority", setbridge_ifpriority),
+ DEF_CMD_ARG2("ifpathcost", setbridge_ifpathcost),
+ DEF_CMD_ARG2("ifmaxaddr", setbridge_ifmaxaddr),
+ DEF_CMD_ARG("timeout", setbridge_timeout),
+ DEF_CMD_ARG("private", setbridge_private),
+ DEF_CMD_ARG("-private", unsetbridge_private),
+};
+static struct afswtch af_bridge = {
+ .af_name = "af_bridge",
+ .af_af = AF_UNSPEC,
+ .af_other_status = bridge_status,
+};
+
+static __constructor void
+bridge_ctor(void)
+{
+#define N(a) (sizeof(a) / sizeof(a[0]))
+ int i;
+
+ for (i = 0; i < N(bridge_cmds); i++)
+ cmd_register(&bridge_cmds[i]);
+ af_register(&af_bridge);
+#undef N
+}
diff --git a/sbin/ifconfig/ifclone.c b/sbin/ifconfig/ifclone.c
new file mode 100644
index 0000000..1ce92c8
--- /dev/null
+++ b/sbin/ifconfig/ifclone.c
@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) 1983, 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.
+ * 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 lint
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <net/if.h>
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "ifconfig.h"
+
+static void
+list_cloners(void)
+{
+ struct if_clonereq ifcr;
+ char *cp, *buf;
+ int idx;
+ int s;
+
+ s = socket(AF_LOCAL, SOCK_DGRAM, 0);
+ if (s == -1)
+ err(1, "socket(AF_LOCAL,SOCK_DGRAM)");
+
+ memset(&ifcr, 0, sizeof(ifcr));
+
+ if (ioctl(s, SIOCIFGCLONERS, &ifcr) < 0)
+ err(1, "SIOCIFGCLONERS for count");
+
+ buf = malloc(ifcr.ifcr_total * IFNAMSIZ);
+ if (buf == NULL)
+ err(1, "unable to allocate cloner name buffer");
+
+ ifcr.ifcr_count = ifcr.ifcr_total;
+ ifcr.ifcr_buffer = buf;
+
+ if (ioctl(s, SIOCIFGCLONERS, &ifcr) < 0)
+ err(1, "SIOCIFGCLONERS for names");
+
+ /*
+ * In case some disappeared in the mean time, clamp it down.
+ */
+ if (ifcr.ifcr_count > ifcr.ifcr_total)
+ ifcr.ifcr_count = ifcr.ifcr_total;
+
+ for (cp = buf, idx = 0; idx < ifcr.ifcr_count; idx++, cp += IFNAMSIZ) {
+ if (idx > 0)
+ putchar(' ');
+ printf("%s", cp);
+ }
+
+ putchar('\n');
+ free(buf);
+}
+
+struct clone_defcb {
+ char ifprefix[IFNAMSIZ];
+ clone_callback_func *clone_cb;
+ SLIST_ENTRY(clone_defcb) next;
+};
+
+static SLIST_HEAD(, clone_defcb) clone_defcbh =
+ SLIST_HEAD_INITIALIZER(clone_defcbh);
+
+void
+clone_setdefcallback(const char *ifprefix, clone_callback_func *p)
+{
+ struct clone_defcb *dcp;
+
+ dcp = malloc(sizeof(*dcp));
+ strlcpy(dcp->ifprefix, ifprefix, IFNAMSIZ-1);
+ dcp->clone_cb = p;
+ SLIST_INSERT_HEAD(&clone_defcbh, dcp, next);
+}
+
+/*
+ * Do the actual clone operation. Any parameters must have been
+ * setup by now. If a callback has been setup to do the work
+ * then defer to it; otherwise do a simple create operation with
+ * no parameters.
+ */
+static void
+ifclonecreate(int s, void *arg)
+{
+ struct ifreq ifr;
+ struct clone_defcb *dcp;
+ clone_callback_func *clone_cb = NULL;
+
+ memset(&ifr, 0, sizeof(ifr));
+ (void) strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
+
+ if (clone_cb == NULL) {
+ /* Try to find a default callback */
+ SLIST_FOREACH(dcp, &clone_defcbh, next) {
+ if (strncmp(dcp->ifprefix, ifr.ifr_name,
+ strlen(dcp->ifprefix)) == 0) {
+ clone_cb = dcp->clone_cb;
+ break;
+ }
+ }
+ }
+ if (clone_cb == NULL) {
+ /* NB: no parameters */
+ if (ioctl(s, SIOCIFCREATE2, &ifr) < 0)
+ err(1, "SIOCIFCREATE2");
+ } else {
+ clone_cb(s, &ifr);
+ }
+
+ /*
+ * If we get a different name back than we put in, print it.
+ */
+ if (strncmp(name, ifr.ifr_name, sizeof(name)) != 0) {
+ strlcpy(name, ifr.ifr_name, sizeof(name));
+ printf("%s\n", name);
+ }
+}
+
+static
+DECL_CMD_FUNC(clone_create, arg, d)
+{
+ callback_register(ifclonecreate, NULL);
+}
+
+static
+DECL_CMD_FUNC(clone_destroy, arg, d)
+{
+ (void) strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
+ if (ioctl(s, SIOCIFDESTROY, &ifr) < 0)
+ err(1, "SIOCIFDESTROY");
+}
+
+static struct cmd clone_cmds[] = {
+ DEF_CLONE_CMD("create", 0, clone_create),
+ DEF_CMD("destroy", 0, clone_destroy),
+ DEF_CLONE_CMD("plumb", 0, clone_create),
+ DEF_CMD("unplumb", 0, clone_destroy),
+};
+
+static void
+clone_Copt_cb(const char *optarg __unused)
+{
+ list_cloners();
+ exit(0);
+}
+static struct option clone_Copt = { .opt = "C", .opt_usage = "[-C]", .cb = clone_Copt_cb };
+
+static __constructor void
+clone_ctor(void)
+{
+#define N(a) (sizeof(a) / sizeof(a[0]))
+ size_t i;
+
+ for (i = 0; i < N(clone_cmds); i++)
+ cmd_register(&clone_cmds[i]);
+ opt_register(&clone_Copt);
+#undef N
+}
diff --git a/sbin/ifconfig/ifconfig.8 b/sbin/ifconfig/ifconfig.8
new file mode 100644
index 0000000..1e4870d
--- /dev/null
+++ b/sbin/ifconfig/ifconfig.8
@@ -0,0 +1,2858 @@
+.\" Copyright (c) 1983, 1991, 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.
+.\" 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.
+.\"
+.\" From: @(#)ifconfig.8 8.3 (Berkeley) 1/5/94
+.\" $FreeBSD$
+.\"
+.Dd March 6, 2015
+.Dt IFCONFIG 8
+.Os
+.Sh NAME
+.Nm ifconfig
+.Nd configure network interface parameters
+.Sh SYNOPSIS
+.Nm
+.Op Fl L
+.Op Fl k
+.Op Fl m
+.Op Fl n
+.Ar interface
+.Op Cm create
+.Ar address_family
+.Oo
+.Ar address
+.Op Ar dest_address
+.Oc
+.Op Ar parameters
+.Nm
+.Ar interface
+.Cm destroy
+.Nm
+.Fl a
+.Op Fl L
+.Op Fl d
+.Op Fl m
+.Op Fl u
+.Op Fl v
+.Op Ar address_family
+.Nm
+.Fl l
+.Op Fl d
+.Op Fl u
+.Op Ar address_family
+.Nm
+.Op Fl L
+.Op Fl d
+.Op Fl k
+.Op Fl m
+.Op Fl u
+.Op Fl v
+.Op Fl C
+.Nm
+.Op Fl g Ar groupname
+.Sh DESCRIPTION
+The
+.Nm
+utility is used to assign an address
+to a network interface and/or configure
+network interface parameters.
+The
+.Nm
+utility must be used at boot time to define the network address
+of each interface present on a machine; it may also be used at
+a later time to redefine an interface's address
+or other operating parameters.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Ar address
+For the
+.Tn DARPA Ns -Internet
+family,
+the address is either a host name present in the host name data
+base,
+.Xr hosts 5 ,
+or a
+.Tn DARPA
+Internet address expressed in the Internet standard
+.Dq dot notation .
+.Pp
+It is also possible to use the CIDR notation (also known as the
+slash notation) to include the netmask.
+That is, one can specify an address like
+.Li 192.168.0.1/16 .
+.Pp
+For the
+.Dq inet6
+family, it is also possible to specify the prefix length using the slash
+notation, like
+.Li ::1/128 .
+See the
+.Cm prefixlen
+parameter below for more information.
+.\" For the Xerox Network Systems(tm) family,
+.\" addresses are
+.\" .Ar net:a.b.c.d.e.f ,
+.\" where
+.\" .Ar net
+.\" is the assigned network number (in decimal),
+.\" and each of the six bytes of the host number,
+.\" .Ar a
+.\" through
+.\" .Ar f ,
+.\" are specified in hexadecimal.
+.\" The host number may be omitted on IEEE 802 protocol
+.\" (Ethernet, FDDI, and Token Ring) interfaces,
+.\" which use the hardware physical address,
+.\" and on interfaces other than the first.
+.\" For the
+.\" .Tn ISO
+.\" family, addresses are specified as a long hexadecimal string,
+.\" as in the Xerox family.
+.\" However, two consecutive dots imply a zero
+.\" byte, and the dots are optional, if the user wishes to (carefully)
+.\" count out long strings of digits in network byte order.
+.Pp
+The link-level
+.Pq Dq link
+address
+is specified as a series of colon-separated hex digits.
+This can be used to, for example,
+set a new MAC address on an Ethernet interface, though the
+mechanism used is not Ethernet specific.
+If the interface is already
+up when this option is used, it will be briefly brought down and
+then brought back up again in order to ensure that the receive
+filter in the underlying Ethernet hardware is properly reprogrammed.
+.It Ar address_family
+Specify the
+address family
+which affects interpretation of the remaining parameters.
+Since an interface can receive transmissions in differing protocols
+with different naming schemes, specifying the address family is recommended.
+The address or protocol families currently
+supported are
+.Dq inet ,
+.Dq inet6 ,
+and
+.Dq link .
+The default if available is
+.Dq inet
+or otherwise
+.Dq link .
+.Dq ether
+and
+.Dq lladdr
+are synonyms for
+.Dq link .
+When using the
+.Fl l
+flag, the
+.Dq ether
+address family has special meaning and is no longer synonymous with
+.Dq link
+or
+.Dq lladdr .
+Specifying
+.Fl l Dq ether
+will list only Ethernet interfaces, excluding all other interface types,
+including the loopback interface.
+.It Ar dest_address
+Specify the address of the correspondent on the other end
+of a point to point link.
+.It Ar interface
+This
+parameter is a string of the form
+.Dq name unit ,
+for example,
+.Dq Li ed0 .
+.It Ar groupname
+List the interfaces in the given group.
+.El
+.Pp
+The following parameters may be set with
+.Nm :
+.Bl -tag -width indent
+.It Cm add
+Another name for the
+.Cm alias
+parameter.
+Introduced for compatibility
+with
+.Bsx .
+.It Cm alias
+Establish an additional network address for this interface.
+This is sometimes useful when changing network numbers, and
+one wishes to accept packets addressed to the old interface.
+If the address is on the same subnet as the first network address
+for this interface, a non-conflicting netmask must be given.
+Usually
+.Li 0xffffffff
+is most appropriate.
+.It Fl alias
+Remove the network address specified.
+This would be used if you incorrectly specified an alias, or it
+was no longer needed.
+If you have incorrectly set an NS address having the side effect
+of specifying the host portion, removing all NS addresses will
+allow you to respecify the host portion.
+.It Cm anycast
+(Inet6 only.)
+Specify that the address configured is an anycast address.
+Based on the current specification,
+only routers may configure anycast addresses.
+Anycast address will not be used as source address of any of outgoing
+IPv6 packets.
+.It Cm arp
+Enable the use of the Address Resolution Protocol
+.Pq Xr arp 4
+in mapping
+between network level addresses and link level addresses (default).
+This is currently implemented for mapping between
+.Tn DARPA
+Internet
+addresses and
+.Tn IEEE
+802 48-bit MAC addresses (Ethernet, FDDI, and Token Ring addresses).
+.It Fl arp
+Disable the use of the Address Resolution Protocol
+.Pq Xr arp 4 .
+.It Cm staticarp
+If the Address Resolution Protocol is enabled,
+the host will only reply to requests for its addresses,
+and will never send any requests.
+.It Fl staticarp
+If the Address Resolution Protocol is enabled,
+the host will perform normally,
+sending out requests and listening for replies.
+.It Cm broadcast
+(Inet only.)
+Specify the address to use to represent broadcasts to the
+network.
+The default broadcast address is the address with a host part of all 1's.
+.It Cm debug
+Enable driver dependent debugging code; usually, this turns on
+extra console error logging.
+.It Fl debug
+Disable driver dependent debugging code.
+.It Cm promisc
+Put interface into permanently promiscuous mode.
+.It Fl promisc
+Disable permanently promiscuous mode.
+.It Cm delete
+Another name for the
+.Fl alias
+parameter.
+.It Cm description Ar value , Cm descr Ar value
+Specify a description of the interface.
+This can be used to label interfaces in situations where they may
+otherwise be difficult to distinguish.
+.It Cm -description , Cm -descr
+Clear the interface description.
+.It Cm down
+Mark an interface
+.Dq down .
+When an interface is marked
+.Dq down ,
+the system will not attempt to
+transmit messages through that interface.
+If possible, the interface will be reset to disable reception as well.
+This action does not automatically disable routes using the interface.
+.It Cm group Ar group-name
+Assign the interface to a
+.Dq group .
+Any interface can be in multiple groups.
+.Pp
+Cloned interfaces are members of their interface family group by default.
+For example, a PPP interface such as
+.Em ppp0
+is a member of the PPP interface family group,
+.Em ppp .
+.\" The interface(s) the default route(s) point to are members of the
+.\" .Em egress
+.\" interface group.
+.It Cm -group Ar group-name
+Remove the interface from the given
+.Dq group .
+.It Cm eui64
+(Inet6 only.)
+Fill interface index
+(lowermost 64bit of an IPv6 address)
+automatically.
+.It Cm fib Ar fib_number
+Specify interface FIB.
+A FIB
+.Ar fib_number
+is assigned to all frames or packets received on that interface.
+The FIB is not inherited, e.g., vlans or other sub-interfaces will use
+the default FIB (0) irrespective of the parent interface's FIB.
+The kernel needs to be tuned to support more than the default FIB
+using the
+.Va ROUTETABLES
+kernel configuration option, or the
+.Va net.fibs
+tunable.
+.It Cm maclabel Ar label
+If Mandatory Access Control support is enabled in the kernel,
+set the MAC label to
+.Ar label .
+.\" (see
+.\" .Xr maclabel 7 ) .
+.It Cm media Ar type
+If the driver supports the media selection system, set the media type
+of the interface to
+.Ar type .
+Some interfaces support the mutually exclusive use of one of several
+different physical media connectors.
+For example, a 10Mbit/s Ethernet
+interface might support the use of either
+.Tn AUI
+or twisted pair connectors.
+Setting the media type to
+.Cm 10base5/AUI
+would change the currently active connector to the AUI port.
+Setting it to
+.Cm 10baseT/UTP
+would activate twisted pair.
+Refer to the interfaces' driver
+specific documentation or man page for a complete list of the
+available types.
+.It Cm mediaopt Ar opts
+If the driver supports the media selection system, set the specified
+media options on the interface.
+The
+.Ar opts
+argument
+is a comma delimited list of options to apply to the interface.
+Refer to the interfaces' driver specific man page for a complete
+list of available options.
+.It Fl mediaopt Ar opts
+If the driver supports the media selection system, disable the
+specified media options on the interface.
+.It Cm mode Ar mode
+If the driver supports the media selection system, set the specified
+operating mode on the interface to
+.Ar mode .
+For IEEE 802.11 wireless interfaces that support multiple operating modes
+this directive is used to select between 802.11a
+.Pq Cm 11a ,
+802.11b
+.Pq Cm 11b ,
+and 802.11g
+.Pq Cm 11g
+operating modes.
+.It Cm inst Ar minst , Cm instance Ar minst
+Set the media instance to
+.Ar minst .
+This is useful for devices which have multiple physical layer interfaces
+.Pq PHYs .
+.It Cm name Ar name
+Set the interface name to
+.Ar name .
+.It Cm rxcsum , txcsum , rxcsum6 , txcsum6
+If the driver supports user-configurable checksum offloading,
+enable receive (or transmit) checksum offloading on the interface.
+The feature can be turned on selectively per protocol family.
+Use
+.Cm rxcsum6 , txcsum6
+for
+.Xr ip6 4
+or
+.Cm rxcsum , txcsum
+otherwise.
+Some drivers may not be able to enable these flags independently
+of each other, so setting one may also set the other.
+The driver will offload as much checksum work as it can reliably
+support, the exact level of offloading varies between drivers.
+.It Fl rxcsum , txcsum , rxcsum6 , txcsum6
+If the driver supports user-configurable checksum offloading,
+disable receive (or transmit) checksum offloading on the interface.
+The feature can be turned off selectively per protocol family.
+Use
+.Fl rxcsum6 , txcsum6
+for
+.Xr ip6 4
+or
+.Fl rxcsum , txcsum
+otherwise.
+These settings may not always be independent of each other.
+.It Cm tso
+If the driver supports
+.Xr tcp 4
+segmentation offloading, enable TSO on the interface.
+Some drivers may not be able to support TSO for
+.Xr ip 4
+and
+.Xr ip6 4
+packets, so they may enable only one of them.
+.It Fl tso
+If the driver supports
+.Xr tcp 4
+segmentation offloading, disable TSO on the interface.
+It will always disable TSO for
+.Xr ip 4
+and
+.Xr ip6 4 .
+.It Cm tso6 , tso4
+If the driver supports
+.Xr tcp 4
+segmentation offloading for
+.Xr ip6 4
+or
+.Xr ip 4
+use one of these to selectively enabled it only for one protocol family.
+.It Fl tso6 , tso4
+If the driver supports
+.Xr tcp 4
+segmentation offloading for
+.Xr ip6 4
+or
+.Xr ip 4
+use one of these to selectively disable it only for one protocol family.
+.It Cm lro
+If the driver supports
+.Xr tcp 4
+large receive offloading, enable LRO on the interface.
+.It Fl lro
+If the driver supports
+.Xr tcp 4
+large receive offloading, disable LRO on the interface.
+.It Cm wol , wol_ucast , wol_mcast , wol_magic
+Enable Wake On Lan (WOL) support, if available.
+WOL is a facility whereby a machine in a low power state may be woken
+in response to a received packet.
+There are three types of packets that may wake a system:
+ucast (directed solely to the machine's mac address),
+mcast (directed to a broadcast or multicast address),
+or
+magic (unicast or multicast frames with a ``magic contents'').
+Not all devices support WOL, those that do indicate the mechanisms
+they support in their capabilities.
+.Cm wol
+is a synonym for enabling all available WOL mechanisms.
+To disable WOL use
+.Fl wol .
+.It Cm vlanmtu , vlanhwtag, vlanhwfilter, vlanhwcsum, vlanhwtso
+If the driver offers user-configurable VLAN support, enable
+reception of extended frames, tag processing in hardware,
+frame filtering in hardware, checksum offloading, or TSO on VLAN,
+respectively.
+Note that this must be issued on a physical interface associated with
+.Xr vlan 4 ,
+not on a
+.Xr vlan 4
+interface itself.
+.It Fl vlanmtu , vlanhwtag, vlanhwfilter, vlanhwtso
+If the driver offers user-configurable VLAN support, disable
+reception of extended frames, tag processing in hardware,
+frame filtering in hardware, or TSO on VLAN,
+respectively.
+.It Cm vnet Ar jail
+Move the interface to the
+.Xr jail 8 ,
+specified by name or JID.
+If the jail has a virtual network stack, the interface will disappear
+from the current environment and become visible to the jail.
+.It Fl vnet Ar jail
+Reclaim the interface from the
+.Xr jail 8 ,
+specified by name or JID.
+If the jail has a virtual network stack, the interface will disappear
+from the jail, and become visible to the current network environment.
+.It Cm polling
+Turn on
+.Xr polling 4
+feature and disable interrupts on the interface, if driver supports
+this mode.
+.It Fl polling
+Turn off
+.Xr polling 4
+feature and enable interrupt mode on the interface.
+.It Cm create
+Create the specified network pseudo-device.
+If the interface is given without a unit number, try to create a new
+device with an arbitrary unit number.
+If creation of an arbitrary device is successful, the new device name is
+printed to standard output unless the interface is renamed or destroyed
+in the same
+.Nm
+invocation.
+.It Cm destroy
+Destroy the specified network pseudo-device.
+.It Cm plumb
+Another name for the
+.Cm create
+parameter.
+Included for
+.Tn Solaris
+compatibility.
+.It Cm unplumb
+Another name for the
+.Cm destroy
+parameter.
+Included for
+.Tn Solaris
+compatibility.
+.It Cm metric Ar n
+Set the routing metric of the interface to
+.Ar n ,
+default 0.
+The routing metric is used by the routing protocol
+.Pq Xr routed 8 .
+Higher metrics have the effect of making a route
+less favorable; metrics are counted as additional hops
+to the destination network or host.
+.It Cm mtu Ar n
+Set the maximum transmission unit of the interface to
+.Ar n ,
+default is interface specific.
+The MTU is used to limit the size of packets that are transmitted on an
+interface.
+Not all interfaces support setting the MTU, and some interfaces have
+range restrictions.
+.It Cm netmask Ar mask
+.\" (Inet and ISO.)
+(Inet only.)
+Specify how much of the address to reserve for subdividing
+networks into sub-networks.
+The mask includes the network part of the local address
+and the subnet part, which is taken from the host field of the address.
+The mask can be specified as a single hexadecimal number
+with a leading
+.Ql 0x ,
+with a dot-notation Internet address,
+or with a pseudo-network name listed in the network table
+.Xr networks 5 .
+The mask contains 1's for the bit positions in the 32-bit address
+which are to be used for the network and subnet parts,
+and 0's for the host part.
+The mask should contain at least the standard network portion,
+and the subnet field should be contiguous with the network
+portion.
+.Pp
+The netmask can also be specified in CIDR notation after the address.
+See the
+.Ar address
+option above for more information.
+.It Cm prefixlen Ar len
+(Inet6 only.)
+Specify that
+.Ar len
+bits are reserved for subdividing networks into sub-networks.
+The
+.Ar len
+must be integer, and for syntactical reason it must be between 0 to 128.
+It is almost always 64 under the current IPv6 assignment rule.
+If the parameter is omitted, 64 is used.
+.Pp
+The prefix can also be specified using the slash notation after the address.
+See the
+.Ar address
+option above for more information.
+.It Cm remove
+Another name for the
+.Fl alias
+parameter.
+Introduced for compatibility
+with
+.Bsx .
+.Sm off
+.It Cm link Op Cm 0 No - Cm 2
+.Sm on
+Enable special processing of the link level of the interface.
+These three options are interface specific in actual effect, however,
+they are in general used to select special modes of operation.
+An example
+of this is to enable SLIP compression, or to select the connector type
+for some Ethernet cards.
+Refer to the man page for the specific driver
+for more information.
+.Sm off
+.It Fl link Op Cm 0 No - Cm 2
+.Sm on
+Disable special processing at the link level with the specified interface.
+.It Cm monitor
+Put the interface in monitor mode.
+No packets are transmitted, and received packets are discarded after
+.Xr bpf 4
+processing.
+.It Fl monitor
+Take the interface out of monitor mode.
+.It Cm up
+Mark an interface
+.Dq up .
+This may be used to enable an interface after an
+.Dq Nm Cm down .
+It happens automatically when setting the first address on an interface.
+If the interface was reset when previously marked down,
+the hardware will be re-initialized.
+.El
+.Pp
+The following parameters are for ICMPv6 Neighbor Discovery Protocol.
+Note that the address family keyword
+.Dq Li inet6
+is needed for them:
+.Bl -tag -width indent
+.It Cm accept_rtadv
+Set a flag to enable accepting ICMPv6 Router Advertisement messages.
+The
+.Xr sysctl 8
+variable
+.Va net.inet6.ip6.accept_rtadv
+controls whether this flag is set by default or not.
+.It Cm -accept_rtadv
+Clear a flag
+.Cm accept_rtadv .
+.It Cm no_radr
+Set a flag to control whether routers from which the system accepts
+Router Advertisement messages will be added to the Default Router List
+or not.
+When the
+.Cm accept_rtadv
+flag is disabled, this flag has no effect.
+The
+.Xr sysctl 8
+variable
+.Va net.inet6.ip6.no_radr
+controls whether this flag is set by default or not.
+.It Cm -no_radr
+Clear a flag
+.Cm no_radr .
+.It Cm auto_linklocal
+Set a flag to perform automatic link-local address configuration when
+the interface becomes available.
+The
+.Xr sysctl 8
+variable
+.Va net.inet6.ip6.auto_linklocal
+controls whether this flag is set by default or not.
+.It Cm -auto_linklocal
+Clear a flag
+.Cm auto_linklocal .
+.It Cm defaultif
+Set the specified interface as the default route when there is no
+default router.
+.It Cm -defaultif
+Clear a flag
+.Cm defaultif .
+.It Cm ifdisabled
+Set a flag to disable all of IPv6 network communications on the
+specified interface.
+Note that if there are already configured IPv6
+addresses on that interface, all of them are marked as
+.Dq tentative
+and DAD will be performed when this flag is cleared.
+.It Cm -ifdisabled
+Clear a flag
+.Cm ifdisabled .
+When this flag is cleared and
+.Cm auto_linklocal
+flag is enabled, automatic configuration of a link-local address is
+performed.
+.It Cm nud
+Set a flag to enable Neighbor Unreachability Detection.
+.It Cm -nud
+Clear a flag
+.Cm nud .
+.It Cm no_prefer_iface
+Set a flag to not honor rule 5 of source address selection in RFC 3484.
+In practice this means the address on the outgoing interface will not be
+preferred, effectively yielding the decision to the address selection
+policy table, configurable with
+.Xr ip6addrctl 8 .
+.It Cm -no_prefer_iface
+Clear a flag
+.Cm no_prefer_iface .
+.It Cm no_dad
+Set a flag to disable Duplicate Address Detection.
+.It Cm -no_dad
+Clear a flag
+.Cm no_dad .
+.It Cm ignoreloop
+Set a flag to disable loopback detection in Enhanced Duplicate Address
+Detection Algorithm.
+When this flag is set,
+Duplicate Address Detection will stop in a finite number of probings
+even if a loopback configuration is detected.
+.It Cm -ignoreloop
+Clear a flag
+.Cm ignoreloop .
+.El
+.Pp
+The following parameters are specific for IPv6 addresses.
+Note that the address family keyword
+.Dq Li inet6
+is needed for them:
+.Bl -tag -width indent
+.It Cm prefer_source
+Set a flag to prefer address as a candidate of the source address for
+outgoing packets.
+.It Cm -prefer_source
+Clear a flag
+.Cm prefer_source .
+.El
+.Pp
+The following parameters are specific to cloning
+IEEE 802.11 wireless interfaces with the
+.Cm create
+request:
+.Bl -tag -width indent
+.It Cm wlandev Ar device
+Use
+.Ar device
+as the parent for the cloned device.
+.It Cm wlanmode Ar mode
+Specify the operating mode for this cloned device.
+.Ar mode
+is one of
+.Cm sta ,
+.Cm ahdemo
+(or
+.Cm adhoc-demo ),
+.Cm ibss ,
+(or
+.Cm adhoc ),
+.Cm ap ,
+(or
+.Cm hostap ),
+.Cm wds ,
+.Cm tdma ,
+.Cm mesh ,
+and
+.Cm monitor .
+The operating mode of a cloned interface cannot be changed.
+The
+.Cm tdma
+mode is actually implemented as an
+.Cm adhoc-demo
+interface with special properties.
+.It Cm wlanbssid Ar bssid
+The 802.11 mac address to use for the bssid.
+This must be specified at create time for a legacy
+.Cm wds
+device.
+.It Cm wlanaddr Ar address
+The local mac address.
+If this is not specified then a mac address will automatically be assigned
+to the cloned device.
+Typically this address is the same as the address of the parent device
+but if the
+.Cm bssid
+parameter is specified then the driver will craft a unique address for
+the device (if supported).
+.It Cm wdslegacy
+Mark a
+.Cm wds
+device as operating in ``legacy mode''.
+Legacy
+.Cm wds
+devices have a fixed peer relationship and do not, for example, roam
+if their peer stops communicating.
+For completeness a Dynamic WDS (DWDS) interface may marked as
+.Fl wdslegacy .
+.It Cm bssid
+Request a unique local mac address for the cloned device.
+This is only possible if the device supports multiple mac addresses.
+To force use of the parent's mac address use
+.Fl bssid .
+.It Cm beacons
+Mark the cloned interface as depending on hardware support to
+track received beacons.
+To have beacons tracked in software use
+.Fl beacons .
+For
+.Cm hostap
+mode
+.Fl beacons
+can also be used to indicate no beacons should
+be transmitted; this can be useful when creating a WDS configuration but
+.Cm wds
+interfaces can only be created as companions to an access point.
+.El
+.Pp
+The following parameters are specific to IEEE 802.11 wireless interfaces
+cloned with a
+.Cm create
+operation:
+.Bl -tag -width indent
+.It Cm ampdu
+Enable sending and receiving AMPDU frames when using 802.11n (default).
+The 802.11n specification states a compliant station must be capable
+of receiving AMPDU frames but transmission is optional.
+Use
+.Fl ampdu
+to disable all use of AMPDU with 802.11n.
+For testing and/or to work around interoperability problems one can use
+.Cm ampdutx
+and
+.Cm ampdurx
+to control use of AMPDU in one direction.
+.It Cm ampdudensity Ar density
+Set the AMPDU density parameter used when operating with 802.11n.
+This parameter controls the inter-packet gap for AMPDU frames.
+The sending device normally controls this setting but a receiving station
+may request wider gaps.
+Legal values for
+.Ar density
+are 0, .25, .5, 1, 2, 4, 8, and 16 (microseconds).
+A value of
+.Cm -
+is treated the same as 0.
+.It Cm ampdulimit Ar limit
+Set the limit on packet size for receiving AMPDU frames when operating
+with 802.11n.
+Legal values for
+.Ar limit
+are 8192, 16384, 32768, and 65536 but one can also specify
+just the unique prefix: 8, 16, 32, 64.
+Note the sender may limit the size of AMPDU frames to be less
+than the maximum specified by the receiving station.
+.It Cm amsdu
+Enable sending and receiving AMSDU frames when using 802.11n.
+By default AMSDU is received but not transmitted.
+Use
+.Fl amsdu
+to disable all use of AMSDU with 802.11n.
+For testing and/or to work around interoperability problems one can use
+.Cm amsdutx
+and
+.Cm amsdurx
+to control use of AMSDU in one direction.
+.It Cm amsdulimit Ar limit
+Set the limit on packet size for sending and receiving AMSDU frames
+when operating with 802.11n.
+Legal values for
+.Ar limit
+are 7935 and 3839 (bytes).
+Note the sender may limit the size of AMSDU frames to be less
+than the maximum specified by the receiving station.
+Note also that devices are not required to support the 7935 limit,
+only 3839 is required by the specification and the larger value
+may require more memory to be dedicated to support functionality
+that is rarely used.
+.It Cm apbridge
+When operating as an access point, pass packets between
+wireless clients directly (default).
+To instead let them pass up through the
+system and be forwarded using some other mechanism, use
+.Fl apbridge .
+Disabling the internal bridging
+is useful when traffic is to be processed with
+packet filtering.
+.It Cm authmode Ar mode
+Set the desired authentication mode in infrastructure mode.
+Not all adapters support all modes.
+The set of
+valid modes is
+.Cm none , open , shared
+(shared key),
+.Cm 8021x
+(IEEE 802.1x),
+and
+.Cm wpa
+(IEEE WPA/WPA2/802.11i).
+The
+.Cm 8021x
+and
+.Cm wpa
+modes are only useful when using an authentication service
+(a supplicant for client operation or an authenticator when
+operating as an access point).
+Modes are case insensitive.
+.It Cm bgscan
+Enable background scanning when operating as a station.
+Background scanning is a technique whereby a station associated to
+an access point will temporarily leave the channel to scan for
+neighboring stations.
+This allows a station to maintain a cache of nearby access points
+so that roaming between access points can be done without
+a lengthy scan operation.
+Background scanning is done only when a station is not busy and
+any outbound traffic will cancel a scan operation.
+Background scanning should never cause packets to be lost though
+there may be some small latency if outbound traffic interrupts a
+scan operation.
+By default background scanning is enabled if the device is capable.
+To disable background scanning, use
+.Fl bgscan .
+Background scanning is controlled by the
+.Cm bgscanidle
+and
+.Cm bgscanintvl
+parameters.
+Background scanning must be enabled for roaming; this is an artifact
+of the current implementation and may not be required in the future.
+.It Cm bgscanidle Ar idletime
+Set the minimum time a station must be idle (not transmitting or
+receiving frames) before a background scan is initiated.
+The
+.Ar idletime
+parameter is specified in milliseconds.
+By default a station must be idle at least 250 milliseconds before
+a background scan is initiated.
+The idle time may not be set to less than 100 milliseconds.
+.It Cm bgscanintvl Ar interval
+Set the interval at which background scanning is attempted.
+The
+.Ar interval
+parameter is specified in seconds.
+By default a background scan is considered every 300 seconds (5 minutes).
+The
+.Ar interval
+may not be set to less than 15 seconds.
+.It Cm bintval Ar interval
+Set the interval at which beacon frames are sent when operating in
+ad-hoc or ap mode.
+The
+.Ar interval
+parameter is specified in TU's (1024 usecs).
+By default beacon frames are transmitted every 100 TU's.
+.It Cm bmissthreshold Ar count
+Set the number of consecutive missed beacons at which the station
+will attempt to roam (i.e., search for a new access point).
+The
+.Ar count
+parameter must be in the range 1 to 255; though the
+upper bound may be reduced according to device capabilities.
+The default threshold is 7 consecutive missed beacons; but
+this may be overridden by the device driver.
+Another name for the
+.Cm bmissthreshold
+parameter is
+.Cm bmiss .
+.It Cm bssid Ar address
+Specify the MAC address of the access point to use when operating
+as a station in a BSS network.
+This overrides any automatic selection done by the system.
+To disable a previously selected access point, supply
+.Cm any , none ,
+or
+.Cm -
+for the address.
+This option is useful when more than one access point uses the same SSID.
+Another name for the
+.Cm bssid
+parameter is
+.Cm ap .
+.It Cm burst
+Enable packet bursting.
+Packet bursting is a transmission technique whereby the wireless
+medium is acquired once to send multiple frames and the interframe
+spacing is reduced.
+This technique can significantly increase throughput by reducing
+transmission overhead.
+Packet bursting is supported by the 802.11e QoS specification
+and some devices that do not support QoS may still be capable.
+By default packet bursting is enabled if a device is capable
+of doing it.
+To disable packet bursting, use
+.Fl burst .
+.It Cm chanlist Ar channels
+Set the desired channels to use when scanning for access
+points, neighbors in an IBSS network, or looking for unoccupied
+channels when operating as an access point.
+The set of channels is specified as a comma-separated list with
+each element in the list representing either a single channel number or a range
+of the form
+.Dq Li a-b .
+Channel numbers must be in the range 1 to 255 and be permissible
+according to the operating characteristics of the device.
+.It Cm channel Ar number
+Set a single desired channel.
+Channels range from 1 to 255, but the exact selection available
+depends on the region your adaptor was manufactured for.
+Setting
+the channel to
+.Li any ,
+or
+.Cm -
+will clear any desired channel and, if the device is marked up,
+force a scan for a channel to operate on.
+Alternatively the frequency, in megahertz, may be specified
+instead of the channel number.
+.Pp
+When there are several ways to use a channel the channel
+number/frequency may be appended with attributes to clarify.
+For example, if a device is capable of operating on channel 6
+with 802.11n and 802.11g then one can specify that g-only use
+should be used by specifying ``6:g''.
+Similarly the channel width can be specified by appending it
+with ``/''; e.g., ``6/40'' specifies a 40MHz wide channel,
+These attributes can be combined as in: ``6:ht/40''.
+The full set of flags specified following a ``:'' are:
+.Cm a
+(802.11a),
+.Cm b
+(802.11b),
+.Cm d
+(Atheros Dynamic Turbo mode),
+.Cm g
+(802.11g),
+.Cm h
+or
+.Cm n
+(802.11n aka HT),
+.Cm s
+(Atheros Static Turbo mode),
+and
+.Cm t
+(Atheros Dynamic Turbo mode, or appended to ``st'' and ``dt'').
+The full set of channel widths following a '/' are:
+.Cm 5
+(5MHz aka quarter-rate channel),
+.Cm 10
+(10MHz aka half-rate channel),
+.Cm 20
+(20MHz mostly for use in specifying ht20),
+and
+.Cm 40
+(40MHz mostly for use in specifying ht40).
+In addition,
+a 40MHz HT channel specification may include the location
+of the extension channel by appending ``+'' or ``-'' for above and below,
+respectively; e.g., ``2437:ht/40+'' specifies 40MHz wide HT operation
+with the center channel at frequency 2437 and the extension channel above.
+.It Cm country Ar name
+Set the country code to use in calculating the regulatory constraints
+for operation.
+In particular the set of available channels, how the wireless device
+will operation on the channels, and the maximum transmit power that
+can be used on a channel are defined by this setting.
+Country/Region codes are specified as a 2-character abbreviation
+defined by ISO 3166 or using a longer, but possibly ambiguous, spelling;
+e.g., "ES" and "Spain".
+The set of country codes are taken from
+.Pa /etc/regdomain.xml
+and can also
+be viewed with the ``list countries'' request.
+Note that not all devices support changing the country code from a default
+setting; typically stored in EEPROM.
+See also
+.Cm regdomain ,
+.Cm indoor ,
+.Cm outdoor ,
+and
+.Cm anywhere .
+.It Cm dfs
+Enable Dynamic Frequency Selection (DFS) as specified in 802.11h.
+DFS embodies several facilities including detection of overlapping
+radar signals, dynamic transmit power control, and channel selection
+according to a least-congested criteria.
+DFS support is mandatory for some 5GHz frequencies in certain
+locales (e.g., ETSI).
+By default DFS is enabled according to the regulatory definitions
+specified in
+.Pa /etc/regdomain.xml
+and the current country code, regdomain,
+and channel.
+Note the underlying device (and driver) must support radar detection
+for full DFS support to work.
+To be fully compliant with the local regulatory agency frequencies that
+require DFS should not be used unless it is fully supported.
+Use
+.Fl dfs
+to disable this functionality for testing.
+.It Cm dotd
+Enable support for the 802.11d specification (default).
+When this support is enabled in station mode, beacon frames that advertise
+a country code different than the currently configured country code will
+cause an event to be dispatched to user applications.
+This event can be used by the station to adopt that country code and
+operate according to the associated regulatory constraints.
+When operating as an access point with 802.11d enabled the beacon and
+probe response frames transmitted will advertise the current regulatory
+domain settings.
+To disable 802.11d use
+.Fl dotd .
+.It Cm doth
+Enable 802.11h support including spectrum management.
+When 802.11h is enabled beacon and probe response frames will have
+the SpectrumMgt bit set in the capabilities field and
+country and power constraint information elements will be present.
+802.11h support also includes handling Channel Switch Announcements (CSA)
+which are a mechanism to coordinate channel changes by an access point.
+By default 802.11h is enabled if the device is capable.
+To disable 802.11h use
+.Fl doth .
+.It Cm deftxkey Ar index
+Set the default key to use for transmission.
+Typically this is only set when using WEP encryption.
+Note that you must set a default transmit key
+for the system to know which key to use in encrypting outbound traffic.
+The
+.Cm weptxkey
+is an alias for this request; it is provided for backwards compatibility.
+.It Cm dtimperiod Ar period
+Set the
+DTIM
+period for transmitting buffered multicast data frames when
+operating in ap mode.
+The
+.Ar period
+specifies the number of beacon intervals between DTIM
+and must be in the range 1 to 15.
+By default DTIM is 1 (i.e., DTIM occurs at each beacon).
+.It Cm quiet
+Enable the use of quiet IE.
+Hostap will use this to silence other
+stations to reduce interference for radar detection when
+operating on 5GHz frequency and doth support is enabled.
+Use
+.Fl quiet
+to disable this functionality.
+.It Cm quiet_period Ar period
+Set the QUIET
+.Ar period
+to the number of beacon intervals between the start of regularly
+scheduled quiet intervals defined by Quiet element.
+.It Cm quiet_count Ar count
+Set the QUIET
+.Ar count
+to the number of TBTTs until the beacon interval during which the
+next quiet interval shall start.
+A value of 1 indicates the quiet
+interval will start during the beacon interval starting at the next
+TBTT.
+A value 0 is reserved.
+.It Cm quiet_offset Ar offset
+Set the QUIET
+.Ar offset
+to the offset of the start of the quiet interval from the TBTT
+specified by the Quiet count, expressed in TUs.
+The value of the
+.Ar offset
+shall be less than one beacon interval.
+.It Cm quiet_duration Ar dur
+Set the QUIET
+.Ar dur
+to the duration of the Quiet interval, expressed in TUs.
+The value should be less than beacon interval.
+.It Cm dturbo
+Enable the use of Atheros Dynamic Turbo mode when communicating with
+another Dynamic Turbo-capable station.
+Dynamic Turbo mode is an Atheros-specific mechanism by which
+stations switch between normal 802.11 operation and a ``boosted''
+mode in which a 40MHz wide channel is used for communication.
+Stations using Dynamic Turbo mode operate boosted only when the
+channel is free of non-dturbo stations; when a non-dturbo station
+is identified on the channel all stations will automatically drop
+back to normal operation.
+By default, Dynamic Turbo mode is not enabled, even if the device is capable.
+Note that turbo mode (dynamic or static) is only allowed on some
+channels depending on the regulatory constraints; use the
+.Cm list chan
+command to identify the channels where turbo mode may be used.
+To disable Dynamic Turbo mode use
+.Fl dturbo .
+.It Cm dwds
+Enable Dynamic WDS (DWDS) support.
+DWDS is a facility by which 4-address traffic can be carried between
+stations operating in infrastructure mode.
+A station first associates to an access point and authenticates using
+normal procedures (e.g., WPA).
+Then 4-address frames are passed to carry traffic for stations
+operating on either side of the wireless link.
+DWDS extends the normal WDS mechanism by leveraging existing security
+protocols and eliminating static binding.
+.Pp
+When DWDS is enabled on an access point 4-address frames received from
+an authorized station will generate a ``DWDS discovery'' event to user
+applications.
+This event should be used to create a WDS interface that is bound
+to the remote station (and usually plumbed into a bridge).
+Once the WDS interface is up and running 4-address traffic then logically
+flows through that interface.
+.Pp
+When DWDS is enabled on a station, traffic with a destination address
+different from the peer station are encapsulated in a 4-address frame
+and transmitted to the peer.
+All 4-address traffic uses the security information of the stations
+(e.g., cryptographic keys).
+A station is associated using 802.11n facilities may transport
+4-address traffic using these same mechanisms; this depends on available
+resources and capabilities of the device.
+The DWDS implementation guards against layer 2 routing loops of
+multicast traffic.
+.It Cm ff
+Enable the use of Atheros Fast Frames when communicating with
+another Fast Frames-capable station.
+Fast Frames are an encapsulation technique by which two 802.3
+frames are transmitted in a single 802.11 frame.
+This can noticeably improve throughput but requires that the
+receiving station understand how to decapsulate the frame.
+Fast frame use is negotiated using the Atheros 802.11 vendor-specific
+protocol extension so enabling use is safe when communicating with
+non-Atheros devices.
+By default, use of fast frames is enabled if the device is capable.
+To explicitly disable fast frames, use
+.Fl ff .
+.It Cm fragthreshold Ar length
+Set the threshold for which transmitted frames are broken into fragments.
+The
+.Ar length
+argument is the frame size in bytes and must be in the range 256 to 2346.
+Setting
+.Ar length
+to
+.Li 2346 ,
+.Cm any ,
+or
+.Cm -
+disables transmit fragmentation.
+Not all adapters honor the fragmentation threshold.
+.It Cm hidessid
+When operating as an access point, do not broadcast the SSID
+in beacon frames or respond to probe request frames unless
+they are directed to the ap (i.e., they include the ap's SSID).
+By default, the SSID is included in beacon frames and
+undirected probe request frames are answered.
+To re-enable the broadcast of the SSID etc., use
+.Fl hidessid .
+.It Cm ht
+Enable use of High Throughput (HT) when using 802.11n (default).
+The 802.11n specification includes mechanisms for operation
+on 20MHz and 40MHz wide channels using different signalling mechanisms
+than specified in 802.11b, 802.11g, and 802.11a.
+Stations negotiate use of these facilities, termed HT20 and HT40,
+when they associate.
+To disable all use of 802.11n use
+.Fl ht .
+To disable use of HT20 (e.g., to force only HT40 use) use
+.Fl ht20 .
+To disable use of HT40 use
+.Fl ht40 .
+.Pp
+HT configuration is used to ``auto promote'' operation
+when several choices are available.
+For example, if a station associates to an 11n-capable access point
+it controls whether the station uses legacy operation, HT20, or HT40.
+When an 11n-capable device is setup as an access point and
+Auto Channel Selection is used to locate a channel to operate on,
+HT configuration controls whether legacy, HT20, or HT40 operation is setup
+on the selected channel.
+If a fixed channel is specified for a station then HT configuration can
+be given as part of the channel specification; e.g., 6:ht/20 to setup
+HT20 operation on channel 6.
+.It Cm htcompat
+Enable use of compatibility support for pre-802.11n devices (default).
+The 802.11n protocol specification went through several incompatible iterations.
+Some vendors implemented 11n support to older specifications that
+will not interoperate with a purely 11n-compliant station.
+In particular the information elements included in management frames
+for old devices are different.
+When compatibility support is enabled both standard and compatible data
+will be provided.
+Stations that associate using the compatibility mechanisms are flagged
+in ``list sta''.
+To disable compatibility support use
+.Fl htcompat .
+.It Cm htprotmode Ar technique
+For interfaces operating in 802.11n, use the specified
+.Ar technique
+for protecting HT frames in a mixed legacy/HT network.
+The set of valid techniques is
+.Cm off ,
+and
+.Cm rts
+(RTS/CTS, default).
+Technique names are case insensitive.
+.It Cm inact
+Enable inactivity processing for stations associated to an
+access point (default).
+When operating as an access point the 802.11 layer monitors
+the activity of each associated station.
+When a station is inactive for 5 minutes it will send several
+``probe frames'' to see if the station is still present.
+If no response is received then the station is deauthenticated.
+Applications that prefer to handle this work can disable this
+facility by using
+.Fl inact .
+.It Cm indoor
+Set the location to use in calculating regulatory constraints.
+The location is also advertised in beacon and probe response frames
+when 802.11d is enabled with
+.Cm dotd .
+See also
+.Cm outdoor ,
+.Cm anywhere ,
+.Cm country ,
+and
+.Cm regdomain .
+.It Cm list active
+Display the list of channels available for use taking into account
+any restrictions set with the
+.Cm chanlist
+directive.
+See the description of
+.Cm list chan
+for more information.
+.It Cm list caps
+Display the adaptor's capabilities, including the operating
+modes supported.
+.It Cm list chan
+Display the list of channels available for use.
+Channels are shown with their IEEE channel number, equivalent
+frequency, and usage modes.
+Channels identified as
+.Ql 11g
+are also usable in
+.Ql 11b
+mode.
+Channels identified as
+.Ql 11a Turbo
+may be used only for Atheros' Static Turbo mode
+(specified with
+. Cm mediaopt turbo ) .
+Channels marked with a
+.Ql *
+have a regulatory constraint that they be passively scanned.
+This means a station is not permitted to transmit on the channel until
+it identifies the channel is being used for 802.11 communication;
+typically by hearing a beacon frame from an access point operating
+on the channel.
+.Cm list freq
+is another way of requesting this information.
+By default a compacted list of channels is displayed; if the
+.Fl v
+option is specified then all channels are shown.
+.It Cm list countries
+Display the set of country codes and regulatory domains that can be
+used in regulatory configuration.
+.It Cm list mac
+Display the current MAC Access Control List state.
+Each address is prefixed with a character that indicates the
+current policy applied to it:
+.Ql +
+indicates the address is allowed access,
+.Ql -
+indicates the address is denied access,
+.Ql *
+indicates the address is present but the current policy open
+(so the ACL is not consulted).
+.It Cm list mesh
+Displays the mesh routing table, used for forwarding packets on a mesh
+network.
+.It Cm list regdomain
+Display the current regulatory settings including the available channels
+and transmit power caps.
+.It Cm list roam
+Display the parameters that govern roaming operation.
+.It Cm list txparam
+Display the parameters that govern transmit operation.
+.It Cm list txpower
+Display the transmit power caps for each channel.
+.It Cm list scan
+Display the access points and/or ad-hoc neighbors
+located in the vicinity.
+This information may be updated automatically by the adapter
+with a
+.Cm scan
+request or through background scanning.
+Depending on the capabilities of the stations the following
+flags can be included in the output:
+.Bl -tag -width 3n
+.It Li A
+Authorized.
+Indicates that the station is permitted to send/receive data frames.
+.It Li E
+Extended Rate Phy (ERP).
+Indicates that the station is operating in an 802.11g network
+using extended transmit rates.
+.It Li H
+High Throughput (HT).
+Indicates that the station is using HT transmit rates.
+If a `+' follows immediately after then the station associated
+using deprecated mechanisms supported only when
+.Cm htcompat
+is enabled.
+.It Li P
+Power Save.
+Indicates that the station is operating in power save mode.
+.It Li Q
+Quality of Service (QoS).
+Indicates that the station is using QoS encapsulation for
+data frame.
+QoS encapsulation is enabled only when WME mode is enabled.
+.It Li S
+Short Preamble.
+Indicates that the station is doing short preamble to optionally
+improve throughput performance with 802.11g and 802.11b.
+.It Li T
+Transitional Security Network (TSN).
+Indicates that the station associated using TSN; see also
+.Cm tsn
+below.
+.It Li W
+Wi-Fi Protected Setup (WPS).
+Indicates that the station associated using WPS.
+.El
+.Pp
+By default interesting information elements captured from the neighboring
+stations are displayed at the end of each row.
+Possible elements include:
+.Cm WME
+(station supports WME),
+.Cm WPA
+(station supports WPA),
+.Cm WPS
+(station supports WPS),
+.Cm RSN
+(station supports 802.11i/RSN),
+.Cm HTCAP
+(station supports 802.11n/HT communication),
+.Cm ATH
+(station supports Atheros protocol extensions),
+.Cm VEN
+(station supports unknown vendor-specific extensions).
+If the
+.Fl v
+flag is used all the information elements and their
+contents will be shown.
+Specifying the
+.Fl v
+flag also enables display of long SSIDs.
+The
+.Cm list ap
+command is another way of requesting this information.
+.It Cm list sta
+When operating as an access point display the stations that are
+currently associated.
+When operating in ad-hoc mode display stations identified as
+neighbors in the IBSS.
+When operating in mesh mode display stations identified as
+neighbors in the MBSS.
+When operating in station mode display the access point.
+Capabilities advertised by the stations are described under
+the
+.Cm scan
+request.
+Depending on the capabilities of the stations the following
+flags can be included in the output:
+.Bl -tag -width 3n
+.It Li A
+Authorized.
+Indicates that the station is permitted to send/receive data frames.
+.It Li E
+Extended Rate Phy (ERP).
+Indicates that the station is operating in an 802.11g network
+using extended transmit rates.
+.It Li H
+High Throughput (HT).
+Indicates that the station is using HT transmit rates.
+If a `+' follows immediately after then the station associated
+using deprecated mechanisms supported only when
+.Cm htcompat
+is enabled.
+.It Li P
+Power Save.
+Indicates that the station is operating in power save mode.
+.It Li Q
+Quality of Service (QoS).
+Indicates that the station is using QoS encapsulation for
+data frame.
+QoS encapsulation is enabled only when WME mode is enabled.
+.It Li S
+Short Preamble.
+Indicates that the station is doing short preamble to optionally
+improve throughput performance with 802.11g and 802.11b.
+.It Li T
+Transitional Security Network (TSN).
+Indicates that the station associated using TSN; see also
+.Cm tsn
+below.
+.It Li W
+Wi-Fi Protected Setup (WPS).
+Indicates that the station associated using WPS.
+.El
+.Pp
+By default information elements received from associated stations
+are displayed in a short form; the
+.Fl v
+flag causes this information to be displayed symbolically.
+.It Cm list wme
+Display the current channel parameters to use when operating in WME mode.
+If the
+.Fl v
+option is specified then both channel and BSS parameters are displayed
+for each AC (first channel, then BSS).
+When WME mode is enabled for an adaptor this information will be
+displayed with the regular status; this command is mostly useful
+for examining parameters when WME mode is disabled.
+See the description of the
+.Cm wme
+directive for information on the various parameters.
+.It Cm maxretry Ar count
+Set the maximum number of tries to use in sending unicast frames.
+The default setting is 6 but drivers may override this with a value
+they choose.
+.It Cm mcastrate Ar rate
+Set the rate for transmitting multicast/broadcast frames.
+Rates are specified as megabits/second in decimal; e.g.,\& 5.5 for 5.5 Mb/s.
+This rate should be valid for the current operating conditions;
+if an invalid rate is specified drivers are free to chose an
+appropriate rate.
+.It Cm mgtrate Ar rate
+Set the rate for transmitting management and/or control frames.
+Rates are specified as megabits/second in decimal; e.g.,\& 5.5 for 5.5 Mb/s.
+.It Cm outdoor
+Set the location to use in calculating regulatory constraints.
+The location is also advertised in beacon and probe response frames
+when 802.11d is enabled with
+.Cm dotd .
+See also
+.Cm anywhere ,
+.Cm country ,
+.Cm indoor ,
+and
+.Cm regdomain .
+.It Cm powersave
+Enable powersave operation.
+When operating as a client, the station will conserve power by
+periodically turning off the radio and listening for
+messages from the access point telling it there are packets waiting.
+The station must then retrieve the packets.
+Not all devices support power save operation as a client.
+The 802.11 specification requires that all access points support
+power save but some drivers do not.
+Use
+.Fl powersave
+to disable powersave operation when operating as a client.
+.It Cm powersavesleep Ar sleep
+Set the desired max powersave sleep time in TU's (1024 usecs).
+By default the max powersave sleep time is 100 TU's.
+.It Cm protmode Ar technique
+For interfaces operating in 802.11g, use the specified
+.Ar technique
+for protecting OFDM frames in a mixed 11b/11g network.
+The set of valid techniques is
+.Cm off , cts
+(CTS to self),
+and
+.Cm rtscts
+(RTS/CTS).
+Technique names are case insensitive.
+Not all devices support
+.Cm cts
+as a protection technique.
+.It Cm pureg
+When operating as an access point in 802.11g mode allow only
+11g-capable stations to associate (11b-only stations are not
+permitted to associate).
+To allow both 11g and 11b-only stations to associate, use
+.Fl pureg .
+.It Cm puren
+When operating as an access point in 802.11n mode allow only
+HT-capable stations to associate (legacy stations are not
+permitted to associate).
+To allow both HT and legacy stations to associate, use
+.Fl puren .
+.It Cm regdomain Ar sku
+Set the regulatory domain to use in calculating the regulatory constraints
+for operation.
+In particular the set of available channels, how the wireless device
+will operation on the channels, and the maximum transmit power that
+can be used on a channel are defined by this setting.
+Regdomain codes (SKU's) are taken from
+.Pa /etc/regdomain.xml
+and can also
+be viewed with the ``list countries'' request.
+Note that not all devices support changing the regdomain from a default
+setting; typically stored in EEPROM.
+See also
+.Cm country ,
+.Cm indoor ,
+.Cm outdoor ,
+and
+.Cm anywhere .
+.It Cm rifs
+Enable use of Reduced InterFrame Spacing (RIFS) when operating in 802.11n
+on an HT channel.
+Note that RIFS must be supported by both the station and access point
+for it to be used.
+To disable RIFS use
+.Fl rifs .
+.It Cm roam:rate Ar rate
+Set the threshold for controlling roaming when operating in a BSS.
+The
+.Ar rate
+parameter specifies the transmit rate in megabits
+at which roaming should be considered.
+If the current transmit rate drops below this setting and background scanning
+is enabled, then the system will check if a more desirable access point is
+available and switch over to it.
+The current scan cache contents are used if they are considered
+valid according to the
+.Cm scanvalid
+parameter; otherwise a background scan operation is triggered before
+any selection occurs.
+Each channel type has a separate rate threshold; the default values are:
+12 Mb/s (11a), 2 Mb/s (11b), 2 Mb/s (11g), MCS 1 (11na, 11ng).
+.It Cm roam:rssi Ar rssi
+Set the threshold for controlling roaming when operating in a BSS.
+The
+.Ar rssi
+parameter specifies the receive signal strength in dBm units
+at which roaming should be considered.
+If the current rssi drops below this setting and background scanning
+is enabled, then the system will check if a more desirable access point is
+available and switch over to it.
+The current scan cache contents are used if they are considered
+valid according to the
+.Cm scanvalid
+parameter; otherwise a background scan operation is triggered before
+any selection occurs.
+Each channel type has a separate rssi threshold; the default values are
+all 7 dBm.
+.It Cm roaming Ar mode
+When operating as a station, control how the system will
+behave when communication with the current access point
+is broken.
+The
+.Ar mode
+argument may be one of
+.Cm device
+(leave it to the hardware device to decide),
+.Cm auto
+(handle either in the device or the operating system\[em]as appropriate),
+.Cm manual
+(do nothing until explicitly instructed).
+By default, the device is left to handle this if it is
+capable; otherwise, the operating system will automatically
+attempt to reestablish communication.
+Manual mode is used by applications such as
+.Xr wpa_supplicant 8
+that want to control the selection of an access point.
+.It Cm rtsthreshold Ar length
+Set the threshold for which
+transmitted frames are preceded by transmission of an
+RTS
+control frame.
+The
+.Ar length
+argument
+is the frame size in bytes and must be in the range 1 to 2346.
+Setting
+.Ar length
+to
+.Li 2346 ,
+.Cm any ,
+or
+.Cm -
+disables transmission of RTS frames.
+Not all adapters support setting the RTS threshold.
+.It Cm scan
+Initiate a scan of neighboring stations, wait for it to complete, and
+display all stations found.
+Only the super-user can initiate a scan.
+See
+.Cm list scan
+for information on the display.
+By default a background scan is done; otherwise a foreground
+scan is done and the station may roam to a different access point.
+The
+.Cm list scan
+request can be used to show recent scan results without
+initiating a new scan.
+.It Cm scanvalid Ar threshold
+Set the maximum time the scan cache contents are considered valid;
+i.e., will be used without first triggering a scan operation to
+refresh the data.
+The
+.Ar threshold
+parameter is specified in seconds and defaults to 60 seconds.
+The minimum setting for
+.Ar threshold
+is 10 seconds.
+One should take care setting this threshold; if it is set too low
+then attempts to roam to another access point may trigger unnecessary
+background scan operations.
+.It Cm shortgi
+Enable use of Short Guard Interval when operating in 802.11n
+on an HT channel.
+NB: this currently enables Short GI on both HT40 and HT20 channels.
+To disable Short GI use
+.Fl shortgi .
+.It Cm smps
+Enable use of Static Spatial Multiplexing Power Save (SMPS)
+when operating in 802.11n.
+A station operating with Static SMPS maintains only a single
+receive chain active (this can significantly reduce power consumption).
+To disable SMPS use
+.Fl smps .
+.It Cm smpsdyn
+Enable use of Dynamic Spatial Multiplexing Power Save (SMPS)
+when operating in 802.11n.
+A station operating with Dynamic SMPS maintains only a single
+receive chain active but switches to multiple receive chains when it
+receives an RTS frame (this can significantly reduce power consumption).
+Note that stations cannot distinguish between RTS/CTS intended to
+enable multiple receive chains and those used for other purposes.
+To disable SMPS use
+.Fl smps .
+.It Cm ssid Ar ssid
+Set the desired Service Set Identifier (aka network name).
+The SSID is a string up to 32 characters
+in length and may be specified as either a normal string or in
+hexadecimal when preceded by
+.Ql 0x .
+Additionally, the SSID may be cleared by setting it to
+.Ql - .
+.It Cm tdmaslot Ar slot
+When operating with TDMA, use the specified
+.Ar slot
+configuration.
+The
+.Ar slot
+is a number between 0 and the maximum number of slots in the BSS.
+Note that a station configured as slot 0 is a master and
+will broadcast beacon frames advertising the BSS;
+stations configured to use other slots will always
+scan to locate a master before they ever transmit.
+By default
+.Cm tdmaslot
+is set to 1.
+.It Cm tdmaslotcnt Ar cnt
+When operating with TDMA, setup a BSS with
+.Ar cnt
+slots.
+The slot count may be at most 8.
+The current implementation is only tested with two stations
+(i.e., point to point applications).
+This setting is only meaningful when a station is configured as slot 0;
+other stations adopt this setting from the BSS they join.
+By default
+.Cm tdmaslotcnt
+is set to 2.
+.It Cm tdmaslotlen Ar len
+When operating with TDMA, setup a BSS such that each station has a slot
+.Ar len
+microseconds long.
+The slot length must be at least 150 microseconds (1/8 TU)
+and no more than 65 milliseconds.
+Note that setting too small a slot length may result in poor channel
+bandwidth utilization due to factors such as timer granularity and
+guard time.
+This setting is only meaningful when a station is configured as slot 0;
+other stations adopt this setting from the BSS they join.
+By default
+.Cm tdmaslotlen
+is set to 10 milliseconds.
+.It Cm tdmabintval Ar intval
+When operating with TDMA, setup a BSS such that beacons are transmitted every
+.Ar intval
+superframes to synchronize the TDMA slot timing.
+A superframe is defined as the number of slots times the slot length; e.g.,
+a BSS with two slots of 10 milliseconds has a 20 millisecond superframe.
+The beacon interval may not be zero.
+A lower setting of
+.Cm tdmabintval
+causes the timers to be resynchronized more often; this can be help if
+significant timer drift is observed.
+By default
+.Cm tdmabintval
+is set to 5.
+.It Cm tsn
+When operating as an access point with WPA/802.11i allow legacy
+stations to associate using static key WEP and open authentication.
+To disallow legacy station use of WEP, use
+.Fl tsn .
+.It Cm txpower Ar power
+Set the power used to transmit frames.
+The
+.Ar power
+argument is specified in .5 dBm units.
+Out of range values are truncated.
+Typically only a few discreet power settings are available and
+the driver will use the setting closest to the specified value.
+Not all adapters support changing the transmit power.
+.It Cm ucastrate Ar rate
+Set a fixed rate for transmitting unicast frames.
+Rates are specified as megabits/second in decimal; e.g.,\& 5.5 for 5.5 Mb/s.
+This rate should be valid for the current operating conditions;
+if an invalid rate is specified drivers are free to chose an
+appropriate rate.
+.It Cm wepmode Ar mode
+Set the desired WEP mode.
+Not all adapters support all modes.
+The set of valid modes is
+.Cm off , on ,
+and
+.Cm mixed .
+The
+.Cm mixed
+mode explicitly tells the adaptor to allow association with access
+points which allow both encrypted and unencrypted traffic.
+On these adapters,
+.Cm on
+means that the access point must only allow encrypted connections.
+On other adapters,
+.Cm on
+is generally another name for
+.Cm mixed .
+Modes are case insensitive.
+.It Cm weptxkey Ar index
+Set the WEP key to be used for transmission.
+This is the same as setting the default transmission key with
+.Cm deftxkey .
+.It Cm wepkey Ar key Ns | Ns Ar index : Ns Ar key
+Set the selected WEP key.
+If an
+.Ar index
+is not given, key 1 is set.
+A WEP key will be either 5 or 13
+characters (40 or 104 bits) depending on the local network and the
+capabilities of the adaptor.
+It may be specified either as a plain
+string or as a string of hexadecimal digits preceded by
+.Ql 0x .
+For maximum portability, hex keys are recommended;
+the mapping of text keys to WEP encryption is usually driver-specific.
+In particular, the
+.Tn Windows
+drivers do this mapping differently to
+.Fx .
+A key may be cleared by setting it to
+.Ql - .
+If WEP is supported then there are at least four keys.
+Some adapters support more than four keys.
+If that is the case, then the first four keys
+(1-4) will be the standard temporary keys and any others will be adaptor
+specific keys such as permanent keys stored in NVRAM.
+.Pp
+Note that you must set a default transmit key with
+.Cm deftxkey
+for the system to know which key to use in encrypting outbound traffic.
+.It Cm wme
+Enable Wireless Multimedia Extensions (WME) support, if available,
+for the specified interface.
+WME is a subset of the IEEE 802.11e standard to support the
+efficient communication of realtime and multimedia data.
+To disable WME support, use
+.Fl wme .
+Another name for this parameter is
+.Cm wmm .
+.Pp
+The following parameters are meaningful only when WME support is in use.
+Parameters are specified per-AC (Access Category) and
+split into those that are used by a station when acting
+as an access point and those for client stations in the BSS.
+The latter are received from the access point and may not be changed
+(at the station).
+The following Access Categories are recognized:
+.Pp
+.Bl -tag -width ".Cm AC_BK" -compact
+.It Cm AC_BE
+(or
+.Cm BE )
+best effort delivery,
+.It Cm AC_BK
+(or
+.Cm BK )
+background traffic,
+.It Cm AC_VI
+(or
+.Cm VI )
+video traffic,
+.It Cm AC_VO
+(or
+.Cm VO )
+voice traffic.
+.El
+.Pp
+AC parameters are case-insensitive.
+Traffic classification is done in the operating system using the
+vlan priority associated with data frames or the
+ToS (Type of Service) indication in IP-encapsulated frames.
+If neither information is present, traffic is assigned to the
+Best Effort (BE) category.
+.Bl -tag -width indent
+.It Cm ack Ar ac
+Set the ACK policy for QoS transmissions by the local station;
+this controls whether or not data frames transmitted by a station
+require an ACK response from the receiving station.
+To disable waiting for an ACK use
+.Fl ack .
+This parameter is applied only to the local station.
+.It Cm acm Ar ac
+Enable the Admission Control Mandatory (ACM) mechanism
+for transmissions by the local station.
+To disable the ACM use
+.Fl acm .
+On stations in a BSS this parameter is read-only and indicates
+the setting received from the access point.
+NB: ACM is not supported right now.
+.It Cm aifs Ar ac Ar count
+Set the Arbitration Inter Frame Spacing (AIFS)
+channel access parameter to use for transmissions
+by the local station.
+On stations in a BSS this parameter is read-only and indicates
+the setting received from the access point.
+.It Cm cwmin Ar ac Ar count
+Set the CWmin channel access parameter to use for transmissions
+by the local station.
+On stations in a BSS this parameter is read-only and indicates
+the setting received from the access point.
+.It Cm cwmax Ar ac Ar count
+Set the CWmax channel access parameter to use for transmissions
+by the local station.
+On stations in a BSS this parameter is read-only and indicates
+the setting received from the access point.
+.It Cm txoplimit Ar ac Ar limit
+Set the Transmission Opportunity Limit channel access parameter
+to use for transmissions by the local station.
+This parameter defines an interval of time when a WME station
+has the right to initiate transmissions onto the wireless medium.
+On stations in a BSS this parameter is read-only and indicates
+the setting received from the access point.
+.It Cm bss:aifs Ar ac Ar count
+Set the AIFS channel access parameter to send to stations in a BSS.
+This parameter is meaningful only when operating in ap mode.
+.It Cm bss:cwmin Ar ac Ar count
+Set the CWmin channel access parameter to send to stations in a BSS.
+This parameter is meaningful only when operating in ap mode.
+.It Cm bss:cwmax Ar ac Ar count
+Set the CWmax channel access parameter to send to stations in a BSS.
+This parameter is meaningful only when operating in ap mode.
+.It Cm bss:txoplimit Ar ac Ar limit
+Set the TxOpLimit channel access parameter to send to stations in a BSS.
+This parameter is meaningful only when operating in ap mode.
+.El
+.It Cm wps
+Enable Wireless Privacy Subscriber support.
+Note that WPS support requires a WPS-capable supplicant.
+To disable this function use
+.Fl wps .
+.El
+.Pp
+The following parameters support an optional access control list
+feature available with some adapters when operating in ap mode; see
+.Xr wlan_acl 4 .
+This facility allows an access point to accept/deny association
+requests based on the MAC address of the station.
+Note that this feature does not significantly enhance security
+as MAC address spoofing is easy to do.
+.Bl -tag -width indent
+.It Cm mac:add Ar address
+Add the specified MAC address to the database.
+Depending on the policy setting association requests from the
+specified station will be allowed or denied.
+.It Cm mac:allow
+Set the ACL policy to permit association only by
+stations registered in the database.
+.It Cm mac:del Ar address
+Delete the specified MAC address from the database.
+.It Cm mac:deny
+Set the ACL policy to deny association only by
+stations registered in the database.
+.It Cm mac:kick Ar address
+Force the specified station to be deauthenticated.
+This typically is done to block a station after updating the
+address database.
+.It Cm mac:open
+Set the ACL policy to allow all stations to associate.
+.It Cm mac:flush
+Delete all entries in the database.
+.It Cm mac:radius
+Set the ACL policy to permit association only by
+stations approved by a RADIUS server.
+Note that this feature requires the
+.Xr hostapd 8
+program be configured to do the right thing
+as it handles the RADIUS processing
+(and marks stations as authorized).
+.El
+.Pp
+The following parameters are related to a wireless interface operating in mesh
+mode:
+.Bl -tag -width indent
+.It Cm meshid Ar meshid
+Set the desired Mesh Identifier.
+The Mesh ID is a string up to 32 characters in length.
+A mesh interface must have a Mesh Identifier specified
+to reach an operational state.
+.It Cm meshttl Ar ttl
+Set the desired ``time to live'' for mesh forwarded packets;
+this is the number of hops a packet may be forwarded before
+it is discarded.
+The default setting for
+.Cm meshttl
+is 31.
+.It Cm meshpeering
+Enable or disable peering with neighbor mesh stations.
+Stations must peer before any data packets can be exchanged.
+By default
+.Cm meshpeering
+is enabled.
+.It Cm meshforward
+Enable or disable forwarding packets by a mesh interface.
+By default
+.Cm meshforward
+is enabled.
+.It Cm meshgate
+This attribute specifies whether or not the mesh STA activates mesh gate
+announcements.
+By default
+.Cm meshgate
+is disabled.
+.It Cm meshmetric Ar protocol
+Set the specified
+.Ar protocol
+as the link metric protocol used on a mesh network.
+The default protocol is called
+.Ar AIRTIME .
+The mesh interface will restart after changing this setting.
+.It Cm meshpath Ar protocol
+Set the specified
+.Ar protocol
+as the path selection protocol used on a mesh network.
+The only available protocol at the moment is called
+.Ar HWMP
+(Hybrid Wireless Mesh Protocol).
+The mesh interface will restart after changing this setting.
+.It Cm hwmprootmode Ar mode
+Stations on a mesh network can operate as ``root nodes.''
+Root nodes try to find paths to all mesh nodes and advertise themselves
+regularly.
+When there is a root mesh node on a network, other mesh nodes can setup
+paths between themselves faster because they can use the root node
+to find the destination.
+This path may not be the best, but on-demand
+routing will eventually find the best path.
+The following modes are recognized:
+.Pp
+.Bl -tag -width ".Cm PROACTIVE" -compact
+.It Cm DISABLED
+Disable root mode.
+.It Cm NORMAL
+Send broadcast path requests every two seconds.
+Nodes on the mesh without a path to this root mesh station with try to
+discover a path to us.
+.It Cm PROACTIVE
+Send broadcast path requests every two seconds and every node must reply
+with a path reply even if it already has a path to this root mesh station.
+.It Cm RANN
+Send broadcast root announcement (RANN) frames.
+Nodes on the mesh without a path to this root mesh station with try to
+discover a path to us.
+.El
+By default
+.Cm hwmprootmode
+is set to
+.Ar DISABLED .
+.It Cm hwmpmaxhops Ar cnt
+Set the maximum number of hops allowed in an HMWP path to
+.Ar cnt .
+The default setting for
+.Cm hwmpmaxhops
+is 31.
+.El
+.Pp
+The following parameters are for compatibility with other systems:
+.Bl -tag -width indent
+.It Cm nwid Ar ssid
+Another name for the
+.Cm ssid
+parameter.
+Included for
+.Nx
+compatibility.
+.It Cm stationname Ar name
+Set the name of this station.
+The station name is not part of the IEEE 802.11
+protocol though some interfaces support it.
+As such it only
+seems to be meaningful to identical or virtually identical equipment.
+Setting the station name is identical in syntax to setting the SSID.
+One can also use
+.Cm station
+for
+.Bsx
+compatibility.
+.It Cm wep
+Another way of saying
+.Cm wepmode on .
+Included for
+.Bsx
+compatibility.
+.It Fl wep
+Another way of saying
+.Cm wepmode off .
+Included for
+.Bsx
+compatibility.
+.It Cm nwkey key
+Another way of saying:
+.Dq Li "wepmode on weptxkey 1 wepkey 1:key wepkey 2:- wepkey 3:- wepkey 4:-" .
+Included for
+.Nx
+compatibility.
+.It Cm nwkey Xo
+.Sm off
+.Ar n : k1 , k2 , k3 , k4
+.Sm on
+.Xc
+Another way of saying
+.Dq Li "wepmode on weptxkey n wepkey 1:k1 wepkey 2:k2 wepkey 3:k3 wepkey 4:k4" .
+Included for
+.Nx
+compatibility.
+.It Fl nwkey
+Another way of saying
+.Cm wepmode off .
+Included for
+.Nx
+compatibility.
+.El
+.Pp
+The following parameters are specific to bridge interfaces:
+.Bl -tag -width indent
+.It Cm addm Ar interface
+Add the interface named by
+.Ar interface
+as a member of the bridge.
+The interface is put into promiscuous mode
+so that it can receive every packet sent on the network.
+.It Cm deletem Ar interface
+Remove the interface named by
+.Ar interface
+from the bridge.
+Promiscuous mode is disabled on the interface when
+it is removed from the bridge.
+.It Cm maxaddr Ar size
+Set the size of the bridge address cache to
+.Ar size .
+The default is 2000 entries.
+.It Cm timeout Ar seconds
+Set the timeout of address cache entries to
+.Ar seconds
+seconds.
+If
+.Ar seconds
+is zero, then address cache entries will not be expired.
+The default is 1200 seconds.
+.It Cm addr
+Display the addresses that have been learned by the bridge.
+.It Cm static Ar interface-name Ar address
+Add a static entry into the address cache pointing to
+.Ar interface-name .
+Static entries are never aged out of the cache or re-placed, even if the
+address is seen on a different interface.
+.It Cm deladdr Ar address
+Delete
+.Ar address
+from the address cache.
+.It Cm flush
+Delete all dynamically-learned addresses from the address cache.
+.It Cm flushall
+Delete all addresses, including static addresses, from the address cache.
+.It Cm discover Ar interface
+Mark an interface as a
+.Dq discovering
+interface.
+When the bridge has no address cache entry
+(either dynamic or static)
+for the destination address of a packet,
+the bridge will forward the packet to all
+member interfaces marked as
+.Dq discovering .
+This is the default for all interfaces added to a bridge.
+.It Cm -discover Ar interface
+Clear the
+.Dq discovering
+attribute on a member interface.
+For packets without the
+.Dq discovering
+attribute, the only packets forwarded on the interface are broadcast
+or multicast packets and packets for which the destination address
+is known to be on the interface's segment.
+.It Cm learn Ar interface
+Mark an interface as a
+.Dq learning
+interface.
+When a packet arrives on such an interface, the source
+address of the packet is entered into the address cache as being a
+destination address on the interface's segment.
+This is the default for all interfaces added to a bridge.
+.It Cm -learn Ar interface
+Clear the
+.Dq learning
+attribute on a member interface.
+.It Cm sticky Ar interface
+Mark an interface as a
+.Dq sticky
+interface.
+Dynamically learned address entries are treated at static once entered into
+the cache.
+Sticky entries are never aged out of the cache or replaced, even if the
+address is seen on a different interface.
+.It Cm -sticky Ar interface
+Clear the
+.Dq sticky
+attribute on a member interface.
+.It Cm private Ar interface
+Mark an interface as a
+.Dq private
+interface.
+A private interface does not forward any traffic to any other port that is also
+a private interface.
+.It Cm -private Ar interface
+Clear the
+.Dq private
+attribute on a member interface.
+.It Cm span Ar interface
+Add the interface named by
+.Ar interface
+as a span port on the bridge.
+Span ports transmit a copy of every frame received by the bridge.
+This is most useful for snooping a bridged network passively on
+another host connected to one of the span ports of the bridge.
+.It Cm -span Ar interface
+Delete the interface named by
+.Ar interface
+from the list of span ports of the bridge.
+.It Cm stp Ar interface
+Enable Spanning Tree protocol on
+.Ar interface .
+The
+.Xr if_bridge 4
+driver has support for the IEEE 802.1D Spanning Tree protocol (STP).
+Spanning Tree is used to detect and remove loops in a network topology.
+.It Cm -stp Ar interface
+Disable Spanning Tree protocol on
+.Ar interface .
+This is the default for all interfaces added to a bridge.
+.It Cm edge Ar interface
+Set
+.Ar interface
+as an edge port.
+An edge port connects directly to end stations cannot create bridging
+loops in the network, this allows it to transition straight to forwarding.
+.It Cm -edge Ar interface
+Disable edge status on
+.Ar interface .
+.It Cm autoedge Ar interface
+Allow
+.Ar interface
+to automatically detect edge status.
+This is the default for all interfaces added to a bridge.
+.It Cm -autoedge Ar interface
+Disable automatic edge status on
+.Ar interface .
+.It Cm ptp Ar interface
+Set the
+.Ar interface
+as a point to point link.
+This is required for straight transitions to forwarding and
+should be enabled on a direct link to another RSTP capable switch.
+.It Cm -ptp Ar interface
+Disable point to point link status on
+.Ar interface .
+This should be disabled for a half duplex link and for an interface
+connected to a shared network segment,
+like a hub or a wireless network.
+.It Cm autoptp Ar interface
+Automatically detect the point to point status on
+.Ar interface
+by checking the full duplex link status.
+This is the default for interfaces added to the bridge.
+.It Cm -autoptp Ar interface
+Disable automatic point to point link detection on
+.Ar interface .
+.It Cm maxage Ar seconds
+Set the time that a Spanning Tree protocol configuration is valid.
+The default is 20 seconds.
+The minimum is 6 seconds and the maximum is 40 seconds.
+.It Cm fwddelay Ar seconds
+Set the time that must pass before an interface begins forwarding
+packets when Spanning Tree is enabled.
+The default is 15 seconds.
+The minimum is 4 seconds and the maximum is 30 seconds.
+.It Cm hellotime Ar seconds
+Set the time between broadcasting of Spanning Tree protocol
+configuration messages.
+The hello time may only be changed when operating in legacy stp mode.
+The default is 2 seconds.
+The minimum is 1 second and the maximum is 2 seconds.
+.It Cm priority Ar value
+Set the bridge priority for Spanning Tree.
+The default is 32768.
+The minimum is 0 and the maximum is 61440.
+.It Cm proto Ar value
+Set the Spanning Tree protocol.
+The default is rstp.
+The available options are stp and rstp.
+.It Cm holdcnt Ar value
+Set the transmit hold count for Spanning Tree.
+This is the number of packets transmitted before being rate limited.
+The default is 6.
+The minimum is 1 and the maximum is 10.
+.It Cm ifpriority Ar interface Ar value
+Set the Spanning Tree priority of
+.Ar interface
+to
+.Ar value .
+The default is 128.
+The minimum is 0 and the maximum is 240.
+.It Cm ifpathcost Ar interface Ar value
+Set the Spanning Tree path cost of
+.Ar interface
+to
+.Ar value .
+The default is calculated from the link speed.
+To change a previously selected path cost back to automatic, set the
+cost to 0.
+The minimum is 1 and the maximum is 200000000.
+.It Cm ifmaxaddr Ar interface Ar size
+Set the maximum number of hosts allowed from an interface, packets with unknown
+source addresses are dropped until an existing host cache entry expires or is
+removed.
+Set to 0 to disable.
+.El
+.Pp
+The following parameters are specific to lagg interfaces:
+.Bl -tag -width indent
+.It Cm laggport Ar interface
+Add the interface named by
+.Ar interface
+as a port of the aggregation interface.
+.It Cm -laggport Ar interface
+Remove the interface named by
+.Ar interface
+from the aggregation interface.
+.It Cm laggproto Ar proto
+Set the aggregation protocol.
+The default is
+.Li failover .
+The available options are
+.Li failover ,
+.Li lacp ,
+.Li loadbalance ,
+.Li roundrobin ,
+.Li broadcast
+and
+.Li none .
+.It Cm lagghash Ar option Ns Oo , Ns Ar option Oc
+Set the packet layers to hash for aggregation protocols which load balance.
+The default is
+.Dq l2,l3,l4 .
+The options can be combined using commas.
+.Pp
+.Bl -tag -width ".Cm l2" -compact
+.It Cm l2
+src/dst mac address and optional vlan number.
+.It Cm l3
+src/dst address for IPv4 or IPv6.
+.It Cm l4
+src/dst port for TCP/UDP/SCTP.
+.El
+.It Cm use_flowid
+Enable local hash computation for RSS hash on the interface.
+The
+.Li loadbalance
+and
+.Li lacp
+modes will use the RSS hash from the network card if available
+to avoid computing one, this may give poor traffic distribution
+if the hash is invalid or uses less of the protocol header information.
+.Cm use_flowid
+disables use of RSS hash from the network card.
+The default value can be set via the
+.Va net.link.lagg.default_use_flowid
+.Xr sysctl 8
+variable.
+.Li 0
+means
+.Dq disabled
+and
+.Li 1
+means
+.Dq enabled .
+.It Cm -use_flowid
+Disable local hash computation for RSS hash on the interface.
+.It Cm flowid_shift Ar number
+Set a shift parameter for RSS local hash computation.
+Hash is calculated by using flowid bits in a packet header mbuf
+which are shifted by the number of this parameter.
+.El
+.Pp
+The following parameters are specific to IP tunnel interfaces,
+.Xr gif 4 :
+.Bl -tag -width indent
+.It Cm tunnel Ar src_addr dest_addr
+Configure the physical source and destination address for IP tunnel
+interfaces.
+The arguments
+.Ar src_addr
+and
+.Ar dest_addr
+are interpreted as the outer source/destination for the encapsulating
+IPv4/IPv6 header.
+.It Fl tunnel
+Unconfigure the physical source and destination address for IP tunnel
+interfaces previously configured with
+.Cm tunnel .
+.It Cm deletetunnel
+Another name for the
+.Fl tunnel
+parameter.
+.It Cm accept_rev_ethip_ver
+Set a flag to accept both correct EtherIP packets and ones
+with reversed version field.
+Enabled by default.
+This is for backward compatibility with
+.Fx 6.1 ,
+6.2, 6.3, 7.0, and 7.1.
+.It Cm -accept_rev_ethip_ver
+Clear a flag
+.Cm accept_rev_ethip_ver .
+.It Cm send_rev_ethip_ver
+Set a flag to send EtherIP packets with reversed version
+field intentionally.
+Disabled by default.
+This is for backward compatibility with
+.Fx 6.1 ,
+6.2, 6.3, 7.0, and 7.1.
+.It Cm -send_rev_ethip_ver
+Clear a flag
+.Cm send_rev_ethip_ver .
+.El
+.Pp
+The following parameters are specific to GRE tunnel interfaces,
+.Xr gre 4 :
+.Bl -tag -width indent
+.It Cm grekey Ar key
+Configure the GRE key to be used for outgoing packets.
+Note that
+.Xr gre 4 will always accept GRE packets with invalid or absent keys.
+This command will result in a four byte MTU reduction on the interface.
+.El
+.Pp
+The following parameters are specific to
+.Xr pfsync 4
+interfaces:
+.Bl -tag -width indent
+.It Cm syncdev Ar iface
+Use the specified interface
+to send and receive pfsync state synchronisation messages.
+.It Fl syncdev
+Stop sending pfsync state synchronisation messages over the network.
+.It Cm syncpeer Ar peer_address
+Make the pfsync link point-to-point rather than using
+multicast to broadcast the state synchronisation messages.
+The peer_address is the IP address of the other host taking part in
+the pfsync cluster.
+.It Fl syncpeer
+Broadcast the packets using multicast.
+.It Cm maxupd Ar n
+Set the maximum number of updates for a single state which
+can be collapsed into one.
+This is an 8-bit number; the default value is 128.
+.It Cm defer
+Defer transmission of the first packet in a state until a peer has
+acknowledged that the associated state has been inserted.
+.It Fl defer
+Do not defer the first packet in a state.
+This is the default.
+.El
+.Pp
+The following parameters are specific to
+.Xr vlan 4
+interfaces:
+.Bl -tag -width indent
+.It Cm vlan Ar vlan_tag
+Set the VLAN tag value to
+.Ar vlan_tag .
+This value is a 12-bit VLAN Identifier (VID) which is used to create an 802.1Q
+VLAN header for packets sent from the
+.Xr vlan 4
+interface.
+Note that
+.Cm vlan
+and
+.Cm vlandev
+must both be set at the same time.
+.It Cm vlandev Ar iface
+Associate the physical interface
+.Ar iface
+with a
+.Xr vlan 4
+interface.
+Packets transmitted through the
+.Xr vlan 4
+interface will be
+diverted to the specified physical interface
+.Ar iface
+with 802.1Q VLAN encapsulation.
+Packets with 802.1Q encapsulation received
+by the parent interface with the correct VLAN Identifier will be diverted to
+the associated
+.Xr vlan 4
+pseudo-interface.
+The
+.Xr vlan 4
+interface is assigned a
+copy of the parent interface's flags and the parent's Ethernet address.
+The
+.Cm vlandev
+and
+.Cm vlan
+must both be set at the same time.
+If the
+.Xr vlan 4
+interface already has
+a physical interface associated with it, this command will fail.
+To
+change the association to another physical interface, the existing
+association must be cleared first.
+.Pp
+Note: if the hardware tagging capability
+is set on the parent interface, the
+.Xr vlan 4
+pseudo
+interface's behavior changes:
+the
+.Xr vlan 4
+interface recognizes that the
+parent interface supports insertion and extraction of VLAN tags on its
+own (usually in firmware) and that it should pass packets to and from
+the parent unaltered.
+.It Fl vlandev Op Ar iface
+If the driver is a
+.Xr vlan 4
+pseudo device, disassociate the parent interface from it.
+This breaks the link between the
+.Xr vlan 4
+interface and its parent,
+clears its VLAN Identifier, flags and its link address and shuts the interface
+down.
+The
+.Ar iface
+argument is useless and hence deprecated.
+.El
+.Pp
+The following parameters are used to configure
+.Xr vxlan 4
+interfaces.
+.Bl -tag -width indent
+.It Cm vxlanid Ar identifier
+This value is a 24-bit VXLAN Network Identifier (VNI) that identifies the
+virtual network segment membership of the interface.
+.It Cm vxlanlocal Ar address
+The source address used in the encapsulating IPv4/IPv6 header.
+The address should already be assigned to an existing interface.
+When the interface is configured in unicast mode, the listening socket
+is bound to this address.
+.It Cm vxlanremote Ar address
+The interface can be configured in a unicast, or point-to-point, mode
+to create a tunnel between two hosts.
+This is the IP address of the remote end of the tunnel.
+.It Cm vxlangroup Ar address
+The interface can be configured in a multicast mode
+to create a virtual network of hosts.
+This is the IP multicast group address the interface will join.
+.It Cm vxlanlocalport Ar port
+The port number the interface will listen on.
+The default port number is 4789.
+.It Cm vxlanremoteport Ar port
+The destination port number used in the encapsulating IPv4/IPv6 header.
+The remote host should be listening on this port.
+The default port number is 4789.
+Note some other implementations, such as Linux,
+do not default to the IANA assigned port,
+but instead listen on port 8472.
+.It Cm vxlanportrange Ar low high
+The range of source ports used in the encapsulating IPv4/IPv6 header.
+The port selected within the range is based on a hash of the inner frame.
+A range is useful to provide entropy within the outer IP header
+for more effective load balancing.
+The default range is between the
+.Xr sysctl 8
+variables
+.Va net.inet.ip.portrange.first
+and
+.Va net.inet.ip.portrange.last
+.It Cm vxlantimeout Ar timeout
+The maximum time, in seconds, before an entry in the forwarding table
+is pruned.
+The default is 1200 seconds (20 minutes).
+.It Cm vxlanmaxaddr Ar max
+The maximum number of entries in the forwarding table.
+The default is 2000.
+.It Cm vxlandev Ar dev
+When the interface is configured in multicast mode, the
+.Cm dev
+interface is used to transmit IP multicast packets.
+.It Cm vxlanttl Ar ttl
+The TTL used in the encapsulating IPv4/IPv6 header.
+The default is 64.
+.It Cm vxlanlearn
+The source IP address and inner source Ethernet MAC address of
+received packets are used to dynamically populate the forwarding table.
+When in multicast mode, an entry in the forwarding table allows the
+interface to send the frame directly to the remote host instead of
+broadcasting the frame to the multicast group.
+This is the default.
+.It Fl vxlanlearn
+The forwarding table is not populated by recevied packets.
+.It Cm vxlanflush
+Delete all dynamically-learned addresses from the forwarding table.
+.It Cm vxlanflushall
+Delete all addresses, including static addresses, from the forwarding table.
+.El
+.Pp
+The following parameters are used to configure
+.Xr carp 4
+protocol on an interface:
+.Bl -tag -width indent
+.It Cm vhid Ar n
+Set the virtual host ID.
+This is a required setting to initiate
+.Xr carp 4 .
+If the virtual host ID does not exist yet, it is created and attached to the
+interface, otherwise configuration of an existing vhid is adjusted.
+If the
+.Cm vhid
+keyword is supplied along with an
+.Dq inet6
+or
+.Dq inet
+address, then this address is configured to be run under control of the
+specified vhid.
+Whenever a last address that refers to a particular vhid is removed from an
+interface, the vhid is automatically removed from interface and destroyed.
+Any other configuration parameters for the
+.Xr carp 4
+protocol should be supplied along with the
+.Cm vhid
+keyword.
+Acceptable values for vhid are 1 to 255.
+.It Cm advbase Ar seconds
+Specifies the base of the advertisement interval in seconds.
+The acceptable values are 1 to 255.
+The default value is 1.
+.It Cm advskew Ar interval
+Specifies the skew to add to the base advertisement interval to
+make one host advertise slower than another host.
+It is specified in 1/256 of seconds.
+The acceptable values are 1 to 254.
+The default value is 0.
+.It Cm pass Ar phrase
+Set the authentication key to
+.Ar phrase .
+.It Cm state Ar MASTER|BACKUP
+Forcibly change state of a given vhid.
+.El
+.Pp
+The
+.Nm
+utility displays the current configuration for a network interface
+when no optional parameters are supplied.
+If a protocol family is specified,
+.Nm
+will report only the details specific to that protocol family.
+.Pp
+If the
+.Fl m
+flag is passed before an interface name,
+.Nm
+will display the capability list and all
+of the supported media for the specified interface.
+If
+.Fl L
+flag is supplied, address lifetime is displayed for IPv6 addresses,
+as time offset string.
+.Pp
+Optionally, the
+.Fl a
+flag may be used instead of an interface name.
+This flag instructs
+.Nm
+to display information about all interfaces in the system.
+The
+.Fl d
+flag limits this to interfaces that are down, and
+.Fl u
+limits this to interfaces that are up.
+When no arguments are given,
+.Fl a
+is implied.
+.Pp
+The
+.Fl l
+flag may be used to list all available interfaces on the system, with
+no other additional information.
+If an
+.Ar address_family
+is specified, only interfaces of that type will be listed.
+.Fl l Dq ether
+will list only Ethernet adapters, excluding the loopback interface.
+Use of this flag is mutually exclusive
+with all other flags and commands, except for
+.Fl d
+(only list interfaces that are down)
+and
+.Fl u
+(only list interfaces that are up).
+.Pp
+The
+.Fl v
+flag may be used to get more verbose status for an interface.
+.Pp
+The
+.Fl C
+flag may be used to list all of the interface cloners available on
+the system, with no additional information.
+Use of this flag is mutually exclusive with all other flags and commands.
+.Pp
+The
+.Fl k
+flag causes keying information for the interface, if available, to be
+printed.
+For example, the values of 802.11 WEP keys and
+.Xr carp 4
+passphrases will be printed, if accessible to the current user.
+This information is not printed by default, as it may be considered
+sensitive.
+.Pp
+If the network interface driver is not present in the kernel then
+.Nm
+will attempt to load it.
+The
+.Fl n
+flag disables this behavior.
+.Pp
+Only the super-user may modify the configuration of a network interface.
+.Sh EXAMPLES
+Assign the IPv4 address
+.Li 192.0.2.10 ,
+with a network mask of
+.Li 255.255.255.0 ,
+to the interface
+.Li fxp0 :
+.Dl # ifconfig fxp0 inet 192.0.2.10 netmask 255.255.255.0
+.Pp
+Add the IPv4 address
+.Li 192.0.2.45 ,
+with the CIDR network prefix
+.Li /28 ,
+to the interface
+.Li ed0 ,
+using
+.Cm add
+as a synonym for the canonical form of the option
+.Cm alias :
+.Dl # ifconfig ed0 inet 192.0.2.45/28 add
+.Pp
+Remove the IPv4 address
+.Li 192.0.2.45
+from the interface
+.Li ed0 :
+.Dl # ifconfig ed0 inet 192.0.2.45 -alias
+.Pp
+Enable IPv6 functionality of the interface:
+.Dl # ifconfig em0 inet6 -ifdisabled
+.Pp
+Add the IPv6 address
+.Li 2001:DB8:DBDB::123/48
+to the interface
+.Li em0 :
+.Dl # ifconfig em0 inet6 2001:db8:bdbd::123 prefixlen 48 alias
+Note that lower case hexadecimal IPv6 addresses are acceptable.
+.Pp
+Remove the IPv6 address added in the above example,
+using the
+.Li /
+character as shorthand for the network prefix,
+and using
+.Cm delete
+as a synonym for the canonical form of the option
+.Fl alias :
+.Dl # ifconfig em0 inet6 2001:db8:bdbd::123/48 delete
+.Pp
+Configure a single CARP redundant address on igb0, and then switch it
+to be master:
+.Dl # ifconfig igb0 vhid 1 10.0.0.1/24 pass foobar up
+.Dl # ifconfig igb0 vhid 1 state master
+.Pp
+Configure the interface
+.Li xl0 ,
+to use 100baseTX, full duplex Ethernet media options:
+.Dl # ifconfig xl0 media 100baseTX mediaopt full-duplex
+.Pp
+Label the em0 interface as an uplink:
+.Dl # ifconfig em0 description \&"Uplink to Gigabit Switch 2\&"
+.Pp
+Create the software network interface
+.Li gif1 :
+.Dl # ifconfig gif1 create
+.Pp
+Destroy the software network interface
+.Li gif1 :
+.Dl # ifconfig gif1 destroy
+.Pp
+Display available wireless networks using
+.Li wlan0 :
+.Dl # ifconfig wlan0 list scan
+.Sh DIAGNOSTICS
+Messages indicating the specified interface does not exist, the
+requested address is unknown, or the user is not privileged and
+tried to alter an interface's configuration.
+.Sh SEE ALSO
+.Xr netstat 1 ,
+.Xr carp 4 ,
+.Xr gif 4 ,
+.Xr netintro 4 ,
+.Xr pfsync 4 ,
+.Xr polling 4 ,
+.Xr vlan 4 ,
+.Xr vxlan 4 ,
+.Xr devd.conf 5 ,
+.\" .Xr eon 5 ,
+.Xr devd 8 ,
+.Xr jail 8 ,
+.Xr rc 8 ,
+.Xr routed 8 ,
+.Xr sysctl 8
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Bx 4.2 .
+.Sh BUGS
+Basic IPv6 node operation requires a link-local address on each
+interface configured for IPv6.
+Normally, such an address is automatically configured by the
+kernel on each interface added to the system or enabled; this behavior may
+be disabled by setting per-interface flag
+.Cm -auto_linklocal .
+The default value of this flag is 1 and can be disabled by using the sysctl
+MIB variable
+.Va net.inet6.ip6.auto_linklocal .
+.Pp
+Do not configure IPv6 addresses with no link-local address by using
+.Nm .
+It can result in unexpected behaviors of the kernel.
diff --git a/sbin/ifconfig/ifconfig.c b/sbin/ifconfig/ifconfig.c
new file mode 100644
index 0000000..4a79992
--- /dev/null
+++ b/sbin/ifconfig/ifconfig.c
@@ -0,0 +1,1399 @@
+/*
+ * Copyright (c) 1983, 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.
+ * 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 lint
+static const char copyright[] =
+"@(#) Copyright (c) 1983, 1993\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)ifconfig.c 8.2 (Berkeley) 2/16/94";
+#endif
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/module.h>
+#include <sys/linker.h>
+#include <sys/queue.h>
+
+#include <net/ethernet.h>
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <net/if_types.h>
+#include <net/route.h>
+
+/* IP */
+#include <netinet/in.h>
+#include <netinet/in_var.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include <ifaddrs.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#ifdef JAIL
+#include <jail.h>
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "ifconfig.h"
+
+/*
+ * Since "struct ifreq" is composed of various union members, callers
+ * should pay special attention to interpret the value.
+ * (.e.g. little/big endian difference in the structure.)
+ */
+struct ifreq ifr;
+
+char name[IFNAMSIZ];
+char *descr = NULL;
+size_t descrlen = 64;
+int setaddr;
+int setmask;
+int doalias;
+int clearaddr;
+int newaddr = 1;
+int verbose;
+int noload;
+
+int supmedia = 0;
+int printkeys = 0; /* Print keying material for interfaces. */
+
+static int ifconfig(int argc, char *const *argv, int iscreate,
+ const struct afswtch *afp);
+static void status(const struct afswtch *afp, const struct sockaddr_dl *sdl,
+ struct ifaddrs *ifa);
+static void tunnel_status(int s);
+static void usage(void);
+
+static struct afswtch *af_getbyname(const char *name);
+static struct afswtch *af_getbyfamily(int af);
+static void af_other_status(int);
+
+static struct option *opts = NULL;
+
+struct ifa_order_elt {
+ int if_order;
+ int af_orders[255];
+ struct ifaddrs *ifa;
+ TAILQ_ENTRY(ifa_order_elt) link;
+};
+
+TAILQ_HEAD(ifa_queue, ifa_order_elt);
+
+void
+opt_register(struct option *p)
+{
+ p->next = opts;
+ opts = p;
+}
+
+static void
+usage(void)
+{
+ char options[1024];
+ struct option *p;
+
+ /* XXX not right but close enough for now */
+ options[0] = '\0';
+ for (p = opts; p != NULL; p = p->next) {
+ strlcat(options, p->opt_usage, sizeof(options));
+ strlcat(options, " ", sizeof(options));
+ }
+
+ fprintf(stderr,
+ "usage: ifconfig %sinterface address_family [address [dest_address]]\n"
+ " [parameters]\n"
+ " ifconfig interface create\n"
+ " ifconfig -a %s[-d] [-m] [-u] [-v] [address_family]\n"
+ " ifconfig -l [-d] [-u] [address_family]\n"
+ " ifconfig %s[-d] [-m] [-u] [-v]\n",
+ options, options, options);
+ exit(1);
+}
+
+#define ORDERS_SIZE(x) sizeof(x) / sizeof(x[0])
+
+static int
+calcorders(struct ifaddrs *ifa, struct ifa_queue *q)
+{
+ struct ifaddrs *prev;
+ struct ifa_order_elt *cur;
+ unsigned int ord, af, ifa_ord;
+
+ prev = NULL;
+ cur = NULL;
+ ord = 0;
+ ifa_ord = 0;
+
+ while (ifa != NULL) {
+ if (prev == NULL ||
+ strcmp(ifa->ifa_name, prev->ifa_name) != 0) {
+ cur = calloc(1, sizeof(*cur));
+
+ if (cur == NULL)
+ return (-1);
+
+ TAILQ_INSERT_TAIL(q, cur, link);
+ cur->if_order = ifa_ord ++;
+ cur->ifa = ifa;
+ ord = 0;
+ }
+
+ if (ifa->ifa_addr) {
+ af = ifa->ifa_addr->sa_family;
+
+ if (af < ORDERS_SIZE(cur->af_orders) &&
+ cur->af_orders[af] == 0)
+ cur->af_orders[af] = ++ord;
+ }
+ prev = ifa;
+ ifa = ifa->ifa_next;
+ }
+
+ return (0);
+}
+
+static int
+cmpifaddrs(struct ifaddrs *a, struct ifaddrs *b, struct ifa_queue *q)
+{
+ struct ifa_order_elt *cur, *e1, *e2;
+ unsigned int af1, af2;
+ int ret;
+
+ e1 = e2 = NULL;
+
+ ret = strcmp(a->ifa_name, b->ifa_name);
+ if (ret != 0) {
+ TAILQ_FOREACH(cur, q, link) {
+ if (e1 && e2)
+ break;
+
+ if (strcmp(cur->ifa->ifa_name, a->ifa_name) == 0)
+ e1 = cur;
+ else if (strcmp(cur->ifa->ifa_name, b->ifa_name) == 0)
+ e2 = cur;
+ }
+
+ if (!e1 || !e2)
+ return (0);
+ else
+ return (e1->if_order - e2->if_order);
+
+ } else if (a->ifa_addr != NULL && b->ifa_addr != NULL) {
+ TAILQ_FOREACH(cur, q, link) {
+ if (strcmp(cur->ifa->ifa_name, a->ifa_name) == 0) {
+ e1 = cur;
+ break;
+ }
+ }
+
+ if (!e1)
+ return (0);
+
+ af1 = a->ifa_addr->sa_family;
+ af2 = b->ifa_addr->sa_family;
+
+ if (af1 < ORDERS_SIZE(e1->af_orders) &&
+ af2 < ORDERS_SIZE(e1->af_orders))
+ return (e1->af_orders[af1] - e1->af_orders[af2]);
+ }
+
+ return (0);
+}
+
+#undef ORDERS_SIZE
+
+static struct ifaddrs *
+sortifaddrs(struct ifaddrs *list,
+ int (*compare)(struct ifaddrs *, struct ifaddrs *, struct ifa_queue *),
+ struct ifa_queue *q)
+{
+ struct ifaddrs *right, *temp, *last, *result, *next, *tail;
+
+ right = list;
+ temp = list;
+ last = list;
+ result = NULL;
+ next = NULL;
+ tail = NULL;
+
+ if (!list || !list->ifa_next)
+ return (list);
+
+ while (temp && temp->ifa_next) {
+ last = right;
+ right = right->ifa_next;
+ temp = temp->ifa_next->ifa_next;
+ }
+
+ last->ifa_next = NULL;
+
+ list = sortifaddrs(list, compare, q);
+ right = sortifaddrs(right, compare, q);
+
+ while (list || right) {
+
+ if (!right) {
+ next = list;
+ list = list->ifa_next;
+ } else if (!list) {
+ next = right;
+ right = right->ifa_next;
+ } else if (compare(list, right, q) <= 0) {
+ next = list;
+ list = list->ifa_next;
+ } else {
+ next = right;
+ right = right->ifa_next;
+ }
+
+ if (!result)
+ result = next;
+ else
+ tail->ifa_next = next;
+
+ tail = next;
+ }
+
+ return (result);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int c, all, namesonly, downonly, uponly;
+ const struct afswtch *afp = NULL;
+ int ifindex;
+ struct ifaddrs *ifap, *sifap, *ifa;
+ struct ifreq paifr;
+ const struct sockaddr_dl *sdl;
+ char options[1024], *cp, *namecp = NULL;
+ struct ifa_queue q = TAILQ_HEAD_INITIALIZER(q);
+ struct ifa_order_elt *cur, *tmp;
+ const char *ifname;
+ struct option *p;
+ size_t iflen;
+
+ all = downonly = uponly = namesonly = noload = verbose = 0;
+
+ /* Parse leading line options */
+ strlcpy(options, "adklmnuv", sizeof(options));
+ for (p = opts; p != NULL; p = p->next)
+ strlcat(options, p->opt, sizeof(options));
+ while ((c = getopt(argc, argv, options)) != -1) {
+ switch (c) {
+ case 'a': /* scan all interfaces */
+ all++;
+ break;
+ case 'd': /* restrict scan to "down" interfaces */
+ downonly++;
+ break;
+ case 'k':
+ printkeys++;
+ break;
+ case 'l': /* scan interface names only */
+ namesonly++;
+ break;
+ case 'm': /* show media choices in status */
+ supmedia = 1;
+ break;
+ case 'n': /* suppress module loading */
+ noload++;
+ break;
+ case 'u': /* restrict scan to "up" interfaces */
+ uponly++;
+ break;
+ case 'v':
+ verbose++;
+ break;
+ default:
+ for (p = opts; p != NULL; p = p->next)
+ if (p->opt[0] == c) {
+ p->cb(optarg);
+ break;
+ }
+ if (p == NULL)
+ usage();
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* -l cannot be used with -a or -m */
+ if (namesonly && (all || supmedia))
+ usage();
+
+ /* nonsense.. */
+ if (uponly && downonly)
+ usage();
+
+ /* no arguments is equivalent to '-a' */
+ if (!namesonly && argc < 1)
+ all = 1;
+
+ /* -a and -l allow an address family arg to limit the output */
+ if (all || namesonly) {
+ if (argc > 1)
+ usage();
+
+ ifname = NULL;
+ ifindex = 0;
+ if (argc == 1) {
+ afp = af_getbyname(*argv);
+ if (afp == NULL) {
+ warnx("Address family '%s' unknown.", *argv);
+ usage();
+ }
+ if (afp->af_name != NULL)
+ argc--, argv++;
+ /* leave with afp non-zero */
+ }
+ } else {
+ /* not listing, need an argument */
+ if (argc < 1)
+ usage();
+
+ ifname = *argv;
+ argc--, argv++;
+
+ /* check and maybe load support for this interface */
+ ifmaybeload(ifname);
+
+ ifindex = if_nametoindex(ifname);
+ if (ifindex == 0) {
+ /*
+ * NOTE: We must special-case the `create' command
+ * right here as we would otherwise fail when trying
+ * to find the interface.
+ */
+ if (argc > 0 && (strcmp(argv[0], "create") == 0 ||
+ strcmp(argv[0], "plumb") == 0)) {
+ iflen = strlcpy(name, ifname, sizeof(name));
+ if (iflen >= sizeof(name))
+ errx(1, "%s: cloning name too long",
+ ifname);
+ ifconfig(argc, argv, 1, NULL);
+ exit(0);
+ }
+#ifdef JAIL
+ /*
+ * NOTE: We have to special-case the `-vnet' command
+ * right here as we would otherwise fail when trying
+ * to find the interface as it lives in another vnet.
+ */
+ if (argc > 0 && (strcmp(argv[0], "-vnet") == 0)) {
+ iflen = strlcpy(name, ifname, sizeof(name));
+ if (iflen >= sizeof(name))
+ errx(1, "%s: interface name too long",
+ ifname);
+ ifconfig(argc, argv, 0, NULL);
+ exit(0);
+ }
+#endif
+ errx(1, "interface %s does not exist", ifname);
+ }
+ }
+
+ /* Check for address family */
+ if (argc > 0) {
+ afp = af_getbyname(*argv);
+ if (afp != NULL)
+ argc--, argv++;
+ }
+
+ if (getifaddrs(&ifap) != 0)
+ err(EXIT_FAILURE, "getifaddrs");
+
+ cp = NULL;
+
+ if (calcorders(ifap, &q) != 0)
+ err(EXIT_FAILURE, "calcorders");
+
+ sifap = sortifaddrs(ifap, cmpifaddrs, &q);
+
+ TAILQ_FOREACH_SAFE(cur, &q, link, tmp)
+ free(cur);
+
+ ifindex = 0;
+ for (ifa = sifap; ifa; ifa = ifa->ifa_next) {
+ memset(&paifr, 0, sizeof(paifr));
+ strncpy(paifr.ifr_name, ifa->ifa_name, sizeof(paifr.ifr_name));
+ if (sizeof(paifr.ifr_addr) >= ifa->ifa_addr->sa_len) {
+ memcpy(&paifr.ifr_addr, ifa->ifa_addr,
+ ifa->ifa_addr->sa_len);
+ }
+
+ if (ifname != NULL && strcmp(ifname, ifa->ifa_name) != 0)
+ continue;
+ if (ifa->ifa_addr->sa_family == AF_LINK)
+ sdl = (const struct sockaddr_dl *) ifa->ifa_addr;
+ else
+ sdl = NULL;
+ if (cp != NULL && strcmp(cp, ifa->ifa_name) == 0 && !namesonly)
+ continue;
+ iflen = strlcpy(name, ifa->ifa_name, sizeof(name));
+ if (iflen >= sizeof(name)) {
+ warnx("%s: interface name too long, skipping",
+ ifa->ifa_name);
+ continue;
+ }
+ cp = ifa->ifa_name;
+
+ if ((ifa->ifa_flags & IFF_CANTCONFIG) != 0)
+ continue;
+ if (downonly && (ifa->ifa_flags & IFF_UP) != 0)
+ continue;
+ if (uponly && (ifa->ifa_flags & IFF_UP) == 0)
+ continue;
+ /*
+ * Are we just listing the interfaces?
+ */
+ if (namesonly) {
+ if (namecp == cp)
+ continue;
+ if (afp != NULL) {
+ /* special case for "ether" address family */
+ if (!strcmp(afp->af_name, "ether")) {
+ if (sdl == NULL ||
+ (sdl->sdl_type != IFT_ETHER &&
+ sdl->sdl_type != IFT_L2VLAN &&
+ sdl->sdl_type != IFT_BRIDGE) ||
+ sdl->sdl_alen != ETHER_ADDR_LEN)
+ continue;
+ } else {
+ if (ifa->ifa_addr->sa_family
+ != afp->af_af)
+ continue;
+ }
+ }
+ namecp = cp;
+ ifindex++;
+ if (ifindex > 1)
+ printf(" ");
+ fputs(name, stdout);
+ continue;
+ }
+ ifindex++;
+
+ if (argc > 0)
+ ifconfig(argc, argv, 0, afp);
+ else
+ status(afp, sdl, ifa);
+ }
+ if (namesonly)
+ printf("\n");
+ freeifaddrs(ifap);
+
+ exit(0);
+}
+
+static struct afswtch *afs = NULL;
+
+void
+af_register(struct afswtch *p)
+{
+ p->af_next = afs;
+ afs = p;
+}
+
+static struct afswtch *
+af_getbyname(const char *name)
+{
+ struct afswtch *afp;
+
+ for (afp = afs; afp != NULL; afp = afp->af_next)
+ if (strcmp(afp->af_name, name) == 0)
+ return afp;
+ return NULL;
+}
+
+static struct afswtch *
+af_getbyfamily(int af)
+{
+ struct afswtch *afp;
+
+ for (afp = afs; afp != NULL; afp = afp->af_next)
+ if (afp->af_af == af)
+ return afp;
+ return NULL;
+}
+
+static void
+af_other_status(int s)
+{
+ struct afswtch *afp;
+ uint8_t afmask[howmany(AF_MAX, NBBY)];
+
+ memset(afmask, 0, sizeof(afmask));
+ for (afp = afs; afp != NULL; afp = afp->af_next) {
+ if (afp->af_other_status == NULL)
+ continue;
+ if (afp->af_af != AF_UNSPEC && isset(afmask, afp->af_af))
+ continue;
+ afp->af_other_status(s);
+ setbit(afmask, afp->af_af);
+ }
+}
+
+static void
+af_all_tunnel_status(int s)
+{
+ struct afswtch *afp;
+ uint8_t afmask[howmany(AF_MAX, NBBY)];
+
+ memset(afmask, 0, sizeof(afmask));
+ for (afp = afs; afp != NULL; afp = afp->af_next) {
+ if (afp->af_status_tunnel == NULL)
+ continue;
+ if (afp->af_af != AF_UNSPEC && isset(afmask, afp->af_af))
+ continue;
+ afp->af_status_tunnel(s);
+ setbit(afmask, afp->af_af);
+ }
+}
+
+static struct cmd *cmds = NULL;
+
+void
+cmd_register(struct cmd *p)
+{
+ p->c_next = cmds;
+ cmds = p;
+}
+
+static const struct cmd *
+cmd_lookup(const char *name, int iscreate)
+{
+#define N(a) (sizeof(a)/sizeof(a[0]))
+ const struct cmd *p;
+
+ for (p = cmds; p != NULL; p = p->c_next)
+ if (strcmp(name, p->c_name) == 0) {
+ if (iscreate) {
+ if (p->c_iscloneop)
+ return p;
+ } else {
+ if (!p->c_iscloneop)
+ return p;
+ }
+ }
+ return NULL;
+#undef N
+}
+
+struct callback {
+ callback_func *cb_func;
+ void *cb_arg;
+ struct callback *cb_next;
+};
+static struct callback *callbacks = NULL;
+
+void
+callback_register(callback_func *func, void *arg)
+{
+ struct callback *cb;
+
+ cb = malloc(sizeof(struct callback));
+ if (cb == NULL)
+ errx(1, "unable to allocate memory for callback");
+ cb->cb_func = func;
+ cb->cb_arg = arg;
+ cb->cb_next = callbacks;
+ callbacks = cb;
+}
+
+/* specially-handled commands */
+static void setifaddr(const char *, int, int, const struct afswtch *);
+static const struct cmd setifaddr_cmd = DEF_CMD("ifaddr", 0, setifaddr);
+
+static void setifdstaddr(const char *, int, int, const struct afswtch *);
+static const struct cmd setifdstaddr_cmd =
+ DEF_CMD("ifdstaddr", 0, setifdstaddr);
+
+static int
+ifconfig(int argc, char *const *argv, int iscreate, const struct afswtch *uafp)
+{
+ const struct afswtch *afp, *nafp;
+ const struct cmd *p;
+ struct callback *cb;
+ int s;
+
+ strncpy(ifr.ifr_name, name, sizeof ifr.ifr_name);
+ afp = NULL;
+ if (uafp != NULL)
+ afp = uafp;
+ /*
+ * This is the historical "accident" allowing users to configure IPv4
+ * addresses without the "inet" keyword which while a nice feature has
+ * proven to complicate other things. We cannot remove this but only
+ * make sure we will never have a similar implicit default for IPv6 or
+ * any other address familiy. We need a fallback though for
+ * ifconfig IF up/down etc. to work without INET support as people
+ * never used ifconfig IF link up/down, etc. either.
+ */
+#ifndef RESCUE
+#ifdef INET
+ if (afp == NULL && feature_present("inet"))
+ afp = af_getbyname("inet");
+#endif
+#endif
+ if (afp == NULL)
+ afp = af_getbyname("link");
+ if (afp == NULL) {
+ warnx("Please specify an address_family.");
+ usage();
+ }
+top:
+ ifr.ifr_addr.sa_family =
+ afp->af_af == AF_LINK || afp->af_af == AF_UNSPEC ?
+ AF_LOCAL : afp->af_af;
+
+ if ((s = socket(ifr.ifr_addr.sa_family, SOCK_DGRAM, 0)) < 0 &&
+ (uafp != NULL || errno != EAFNOSUPPORT ||
+ (s = socket(AF_LOCAL, SOCK_DGRAM, 0)) < 0))
+ err(1, "socket(family %u,SOCK_DGRAM", ifr.ifr_addr.sa_family);
+
+ while (argc > 0) {
+ p = cmd_lookup(*argv, iscreate);
+ if (iscreate && p == NULL) {
+ /*
+ * Push the clone create callback so the new
+ * device is created and can be used for any
+ * remaining arguments.
+ */
+ cb = callbacks;
+ if (cb == NULL)
+ errx(1, "internal error, no callback");
+ callbacks = cb->cb_next;
+ cb->cb_func(s, cb->cb_arg);
+ iscreate = 0;
+ /*
+ * Handle any address family spec that
+ * immediately follows and potentially
+ * recreate the socket.
+ */
+ nafp = af_getbyname(*argv);
+ if (nafp != NULL) {
+ argc--, argv++;
+ if (nafp != afp) {
+ close(s);
+ afp = nafp;
+ goto top;
+ }
+ }
+ /*
+ * Look for a normal parameter.
+ */
+ continue;
+ }
+ if (p == NULL) {
+ /*
+ * Not a recognized command, choose between setting
+ * the interface address and the dst address.
+ */
+ p = (setaddr ? &setifdstaddr_cmd : &setifaddr_cmd);
+ }
+ if (p->c_u.c_func || p->c_u.c_func2) {
+ if (p->c_parameter == NEXTARG) {
+ if (argv[1] == NULL)
+ errx(1, "'%s' requires argument",
+ p->c_name);
+ p->c_u.c_func(argv[1], 0, s, afp);
+ argc--, argv++;
+ } else if (p->c_parameter == OPTARG) {
+ p->c_u.c_func(argv[1], 0, s, afp);
+ if (argv[1] != NULL)
+ argc--, argv++;
+ } else if (p->c_parameter == NEXTARG2) {
+ if (argc < 3)
+ errx(1, "'%s' requires 2 arguments",
+ p->c_name);
+ p->c_u.c_func2(argv[1], argv[2], s, afp);
+ argc -= 2, argv += 2;
+ } else
+ p->c_u.c_func(*argv, p->c_parameter, s, afp);
+ }
+ argc--, argv++;
+ }
+
+ /*
+ * Do any post argument processing required by the address family.
+ */
+ if (afp->af_postproc != NULL)
+ afp->af_postproc(s, afp);
+ /*
+ * Do deferred callbacks registered while processing
+ * command-line arguments.
+ */
+ for (cb = callbacks; cb != NULL; cb = cb->cb_next)
+ cb->cb_func(s, cb->cb_arg);
+ /*
+ * Do deferred operations.
+ */
+ if (clearaddr) {
+ if (afp->af_ridreq == NULL || afp->af_difaddr == 0) {
+ warnx("interface %s cannot change %s addresses!",
+ name, afp->af_name);
+ clearaddr = 0;
+ }
+ }
+ if (clearaddr) {
+ int ret;
+ strncpy(afp->af_ridreq, name, sizeof ifr.ifr_name);
+ ret = ioctl(s, afp->af_difaddr, afp->af_ridreq);
+ if (ret < 0) {
+ if (errno == EADDRNOTAVAIL && (doalias >= 0)) {
+ /* means no previous address for interface */
+ } else
+ Perror("ioctl (SIOCDIFADDR)");
+ }
+ }
+ if (newaddr) {
+ if (afp->af_addreq == NULL || afp->af_aifaddr == 0) {
+ warnx("interface %s cannot change %s addresses!",
+ name, afp->af_name);
+ newaddr = 0;
+ }
+ }
+ if (newaddr && (setaddr || setmask)) {
+ strncpy(afp->af_addreq, name, sizeof ifr.ifr_name);
+ if (ioctl(s, afp->af_aifaddr, afp->af_addreq) < 0)
+ Perror("ioctl (SIOCAIFADDR)");
+ }
+
+ close(s);
+ return(0);
+}
+
+/*ARGSUSED*/
+static void
+setifaddr(const char *addr, int param, int s, const struct afswtch *afp)
+{
+ if (afp->af_getaddr == NULL)
+ return;
+ /*
+ * Delay the ioctl to set the interface addr until flags are all set.
+ * The address interpretation may depend on the flags,
+ * and the flags may change when the address is set.
+ */
+ setaddr++;
+ if (doalias == 0 && afp->af_af != AF_LINK)
+ clearaddr = 1;
+ afp->af_getaddr(addr, (doalias >= 0 ? ADDR : RIDADDR));
+}
+
+static void
+settunnel(const char *src, const char *dst, int s, const struct afswtch *afp)
+{
+ struct addrinfo *srcres, *dstres;
+ int ecode;
+
+ if (afp->af_settunnel == NULL) {
+ warn("address family %s does not support tunnel setup",
+ afp->af_name);
+ return;
+ }
+
+ if ((ecode = getaddrinfo(src, NULL, NULL, &srcres)) != 0)
+ errx(1, "error in parsing address string: %s",
+ gai_strerror(ecode));
+
+ if ((ecode = getaddrinfo(dst, NULL, NULL, &dstres)) != 0)
+ errx(1, "error in parsing address string: %s",
+ gai_strerror(ecode));
+
+ if (srcres->ai_addr->sa_family != dstres->ai_addr->sa_family)
+ errx(1,
+ "source and destination address families do not match");
+
+ afp->af_settunnel(s, srcres, dstres);
+
+ freeaddrinfo(srcres);
+ freeaddrinfo(dstres);
+}
+
+/* ARGSUSED */
+static void
+deletetunnel(const char *vname, int param, int s, const struct afswtch *afp)
+{
+
+ if (ioctl(s, SIOCDIFPHYADDR, &ifr) < 0)
+ err(1, "SIOCDIFPHYADDR");
+}
+
+#ifdef JAIL
+static void
+setifvnet(const char *jname, int dummy __unused, int s,
+ const struct afswtch *afp)
+{
+ struct ifreq my_ifr;
+
+ memcpy(&my_ifr, &ifr, sizeof(my_ifr));
+ my_ifr.ifr_jid = jail_getid(jname);
+ if (my_ifr.ifr_jid < 0)
+ errx(1, "%s", jail_errmsg);
+ if (ioctl(s, SIOCSIFVNET, &my_ifr) < 0)
+ err(1, "SIOCSIFVNET");
+}
+
+static void
+setifrvnet(const char *jname, int dummy __unused, int s,
+ const struct afswtch *afp)
+{
+ struct ifreq my_ifr;
+
+ memcpy(&my_ifr, &ifr, sizeof(my_ifr));
+ my_ifr.ifr_jid = jail_getid(jname);
+ if (my_ifr.ifr_jid < 0)
+ errx(1, "%s", jail_errmsg);
+ if (ioctl(s, SIOCSIFRVNET, &my_ifr) < 0)
+ err(1, "SIOCSIFRVNET(%d, %s)", my_ifr.ifr_jid, my_ifr.ifr_name);
+}
+#endif
+
+static void
+setifnetmask(const char *addr, int dummy __unused, int s,
+ const struct afswtch *afp)
+{
+ if (afp->af_getaddr != NULL) {
+ setmask++;
+ afp->af_getaddr(addr, MASK);
+ }
+}
+
+static void
+setifbroadaddr(const char *addr, int dummy __unused, int s,
+ const struct afswtch *afp)
+{
+ if (afp->af_getaddr != NULL)
+ afp->af_getaddr(addr, DSTADDR);
+}
+
+static void
+notealias(const char *addr, int param, int s, const struct afswtch *afp)
+{
+#define rqtosa(x) (&(((struct ifreq *)(afp->x))->ifr_addr))
+ if (setaddr && doalias == 0 && param < 0)
+ if (afp->af_addreq != NULL && afp->af_ridreq != NULL)
+ bcopy((caddr_t)rqtosa(af_addreq),
+ (caddr_t)rqtosa(af_ridreq),
+ rqtosa(af_addreq)->sa_len);
+ doalias = param;
+ if (param < 0) {
+ clearaddr = 1;
+ newaddr = 0;
+ } else
+ clearaddr = 0;
+#undef rqtosa
+}
+
+/*ARGSUSED*/
+static void
+setifdstaddr(const char *addr, int param __unused, int s,
+ const struct afswtch *afp)
+{
+ if (afp->af_getaddr != NULL)
+ afp->af_getaddr(addr, DSTADDR);
+}
+
+/*
+ * Note: doing an SIOCIGIFFLAGS scribbles on the union portion
+ * of the ifreq structure, which may confuse other parts of ifconfig.
+ * Make a private copy so we can avoid that.
+ */
+static void
+setifflags(const char *vname, int value, int s, const struct afswtch *afp)
+{
+ struct ifreq my_ifr;
+ int flags;
+
+ memset(&my_ifr, 0, sizeof(my_ifr));
+ (void) strlcpy(my_ifr.ifr_name, name, sizeof(my_ifr.ifr_name));
+
+ if (ioctl(s, SIOCGIFFLAGS, (caddr_t)&my_ifr) < 0) {
+ Perror("ioctl (SIOCGIFFLAGS)");
+ exit(1);
+ }
+ flags = (my_ifr.ifr_flags & 0xffff) | (my_ifr.ifr_flagshigh << 16);
+
+ if (value < 0) {
+ value = -value;
+ flags &= ~value;
+ } else
+ flags |= value;
+ my_ifr.ifr_flags = flags & 0xffff;
+ my_ifr.ifr_flagshigh = flags >> 16;
+ if (ioctl(s, SIOCSIFFLAGS, (caddr_t)&my_ifr) < 0)
+ Perror(vname);
+}
+
+void
+setifcap(const char *vname, int value, int s, const struct afswtch *afp)
+{
+ int flags;
+
+ if (ioctl(s, SIOCGIFCAP, (caddr_t)&ifr) < 0) {
+ Perror("ioctl (SIOCGIFCAP)");
+ exit(1);
+ }
+ flags = ifr.ifr_curcap;
+ if (value < 0) {
+ value = -value;
+ flags &= ~value;
+ } else
+ flags |= value;
+ flags &= ifr.ifr_reqcap;
+ ifr.ifr_reqcap = flags;
+ if (ioctl(s, SIOCSIFCAP, (caddr_t)&ifr) < 0)
+ Perror(vname);
+}
+
+static void
+setifmetric(const char *val, int dummy __unused, int s,
+ const struct afswtch *afp)
+{
+ strncpy(ifr.ifr_name, name, sizeof (ifr.ifr_name));
+ ifr.ifr_metric = atoi(val);
+ if (ioctl(s, SIOCSIFMETRIC, (caddr_t)&ifr) < 0)
+ warn("ioctl (set metric)");
+}
+
+static void
+setifmtu(const char *val, int dummy __unused, int s,
+ const struct afswtch *afp)
+{
+ strncpy(ifr.ifr_name, name, sizeof (ifr.ifr_name));
+ ifr.ifr_mtu = atoi(val);
+ if (ioctl(s, SIOCSIFMTU, (caddr_t)&ifr) < 0)
+ warn("ioctl (set mtu)");
+}
+
+static void
+setifname(const char *val, int dummy __unused, int s,
+ const struct afswtch *afp)
+{
+ char *newname;
+
+ newname = strdup(val);
+ if (newname == NULL) {
+ warn("no memory to set ifname");
+ return;
+ }
+ ifr.ifr_data = newname;
+ if (ioctl(s, SIOCSIFNAME, (caddr_t)&ifr) < 0) {
+ warn("ioctl (set name)");
+ free(newname);
+ return;
+ }
+ strlcpy(name, newname, sizeof(name));
+ free(newname);
+}
+
+/* ARGSUSED */
+static void
+setifdescr(const char *val, int dummy __unused, int s,
+ const struct afswtch *afp)
+{
+ char *newdescr;
+
+ ifr.ifr_buffer.length = strlen(val) + 1;
+ if (ifr.ifr_buffer.length == 1) {
+ ifr.ifr_buffer.buffer = newdescr = NULL;
+ ifr.ifr_buffer.length = 0;
+ } else {
+ newdescr = strdup(val);
+ ifr.ifr_buffer.buffer = newdescr;
+ if (newdescr == NULL) {
+ warn("no memory to set ifdescr");
+ return;
+ }
+ }
+
+ if (ioctl(s, SIOCSIFDESCR, (caddr_t)&ifr) < 0)
+ warn("ioctl (set descr)");
+
+ free(newdescr);
+}
+
+/* ARGSUSED */
+static void
+unsetifdescr(const char *val, int value, int s, const struct afswtch *afp)
+{
+
+ setifdescr("", 0, s, 0);
+}
+
+#define IFFBITS \
+"\020\1UP\2BROADCAST\3DEBUG\4LOOPBACK\5POINTOPOINT\7RUNNING" \
+"\10NOARP\11PROMISC\12ALLMULTI\13OACTIVE\14SIMPLEX\15LINK0\16LINK1\17LINK2" \
+"\20MULTICAST\22PPROMISC\23MONITOR\24STATICARP"
+
+#define IFCAPBITS \
+"\020\1RXCSUM\2TXCSUM\3NETCONS\4VLAN_MTU\5VLAN_HWTAGGING\6JUMBO_MTU\7POLLING" \
+"\10VLAN_HWCSUM\11TSO4\12TSO6\13LRO\14WOL_UCAST\15WOL_MCAST\16WOL_MAGIC" \
+"\17TOE4\20TOE6\21VLAN_HWFILTER\23VLAN_HWTSO\24LINKSTATE\25NETMAP" \
+"\26RXCSUM_IPV6\27TXCSUM_IPV6"
+
+/*
+ * Print the status of the interface. If an address family was
+ * specified, show only it; otherwise, show them all.
+ */
+static void
+status(const struct afswtch *afp, const struct sockaddr_dl *sdl,
+ struct ifaddrs *ifa)
+{
+ struct ifaddrs *ift;
+ int allfamilies, s;
+ struct ifstat ifs;
+
+ if (afp == NULL) {
+ allfamilies = 1;
+ ifr.ifr_addr.sa_family = AF_LOCAL;
+ } else {
+ allfamilies = 0;
+ ifr.ifr_addr.sa_family =
+ afp->af_af == AF_LINK ? AF_LOCAL : afp->af_af;
+ }
+ strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
+
+ s = socket(ifr.ifr_addr.sa_family, SOCK_DGRAM, 0);
+ if (s < 0)
+ err(1, "socket(family %u,SOCK_DGRAM)", ifr.ifr_addr.sa_family);
+
+ printf("%s: ", name);
+ printb("flags", ifa->ifa_flags, IFFBITS);
+ if (ioctl(s, SIOCGIFMETRIC, &ifr) != -1)
+ printf(" metric %d", ifr.ifr_metric);
+ if (ioctl(s, SIOCGIFMTU, &ifr) != -1)
+ printf(" mtu %d", ifr.ifr_mtu);
+ putchar('\n');
+
+ for (;;) {
+ if ((descr = reallocf(descr, descrlen)) != NULL) {
+ ifr.ifr_buffer.buffer = descr;
+ ifr.ifr_buffer.length = descrlen;
+ if (ioctl(s, SIOCGIFDESCR, &ifr) == 0) {
+ if (ifr.ifr_buffer.buffer == descr) {
+ if (strlen(descr) > 0)
+ printf("\tdescription: %s\n",
+ descr);
+ } else if (ifr.ifr_buffer.length > descrlen) {
+ descrlen = ifr.ifr_buffer.length;
+ continue;
+ }
+ }
+ } else
+ warn("unable to allocate memory for interface"
+ "description");
+ break;
+ }
+
+ if (ioctl(s, SIOCGIFCAP, (caddr_t)&ifr) == 0) {
+ if (ifr.ifr_curcap != 0) {
+ printb("\toptions", ifr.ifr_curcap, IFCAPBITS);
+ putchar('\n');
+ }
+ if (supmedia && ifr.ifr_reqcap != 0) {
+ printb("\tcapabilities", ifr.ifr_reqcap, IFCAPBITS);
+ putchar('\n');
+ }
+ }
+
+ tunnel_status(s);
+
+ for (ift = ifa; ift != NULL; ift = ift->ifa_next) {
+ if (ift->ifa_addr == NULL)
+ continue;
+ if (strcmp(ifa->ifa_name, ift->ifa_name) != 0)
+ continue;
+ if (allfamilies) {
+ const struct afswtch *p;
+ p = af_getbyfamily(ift->ifa_addr->sa_family);
+ if (p != NULL && p->af_status != NULL)
+ p->af_status(s, ift);
+ } else if (afp->af_af == ift->ifa_addr->sa_family)
+ afp->af_status(s, ift);
+ }
+#if 0
+ if (allfamilies || afp->af_af == AF_LINK) {
+ const struct afswtch *lafp;
+
+ /*
+ * Hack; the link level address is received separately
+ * from the routing information so any address is not
+ * handled above. Cobble together an entry and invoke
+ * the status method specially.
+ */
+ lafp = af_getbyname("lladdr");
+ if (lafp != NULL) {
+ info.rti_info[RTAX_IFA] = (struct sockaddr *)sdl;
+ lafp->af_status(s, &info);
+ }
+ }
+#endif
+ if (allfamilies)
+ af_other_status(s);
+ else if (afp->af_other_status != NULL)
+ afp->af_other_status(s);
+
+ strncpy(ifs.ifs_name, name, sizeof ifs.ifs_name);
+ if (ioctl(s, SIOCGIFSTATUS, &ifs) == 0)
+ printf("%s", ifs.ascii);
+
+ if (verbose > 0)
+ sfp_status(s, &ifr, verbose);
+
+ close(s);
+ return;
+}
+
+static void
+tunnel_status(int s)
+{
+ af_all_tunnel_status(s);
+}
+
+void
+Perror(const char *cmd)
+{
+ switch (errno) {
+
+ case ENXIO:
+ errx(1, "%s: no such interface", cmd);
+ break;
+
+ case EPERM:
+ errx(1, "%s: permission denied", cmd);
+ break;
+
+ default:
+ err(1, "%s", cmd);
+ }
+}
+
+/*
+ * Print a value a la the %b format of the kernel's printf
+ */
+void
+printb(const char *s, unsigned v, const char *bits)
+{
+ int i, any = 0;
+ char c;
+
+ if (bits && *bits == 8)
+ printf("%s=%o", s, v);
+ else
+ printf("%s=%x", s, v);
+ bits++;
+ if (bits) {
+ putchar('<');
+ while ((i = *bits++) != '\0') {
+ if (v & (1 << (i-1))) {
+ if (any)
+ putchar(',');
+ any = 1;
+ for (; (c = *bits) > 32; bits++)
+ putchar(c);
+ } else
+ for (; *bits > 32; bits++)
+ ;
+ }
+ putchar('>');
+ }
+}
+
+void
+print_vhid(const struct ifaddrs *ifa, const char *s)
+{
+ struct if_data *ifd;
+
+ if (ifa->ifa_data == NULL)
+ return;
+
+ ifd = ifa->ifa_data;
+ if (ifd->ifi_vhid == 0)
+ return;
+
+ printf("vhid %d ", ifd->ifi_vhid);
+}
+
+void
+ifmaybeload(const char *name)
+{
+#define MOD_PREFIX_LEN 3 /* "if_" */
+ struct module_stat mstat;
+ int fileid, modid;
+ char ifkind[IFNAMSIZ + MOD_PREFIX_LEN], ifname[IFNAMSIZ], *dp;
+ const char *cp;
+
+ /* loading suppressed by the user */
+ if (noload)
+ return;
+
+ /* trim the interface number off the end */
+ strlcpy(ifname, name, sizeof(ifname));
+ for (dp = ifname; *dp != 0; dp++)
+ if (isdigit(*dp)) {
+ *dp = 0;
+ break;
+ }
+
+ /* turn interface and unit into module name */
+ strlcpy(ifkind, "if_", sizeof(ifkind));
+ strlcat(ifkind, ifname, sizeof(ifkind));
+
+ /* scan files in kernel */
+ mstat.version = sizeof(struct module_stat);
+ for (fileid = kldnext(0); fileid > 0; fileid = kldnext(fileid)) {
+ /* scan modules in file */
+ for (modid = kldfirstmod(fileid); modid > 0;
+ modid = modfnext(modid)) {
+ if (modstat(modid, &mstat) < 0)
+ continue;
+ /* strip bus name if present */
+ if ((cp = strchr(mstat.name, '/')) != NULL) {
+ cp++;
+ } else {
+ cp = mstat.name;
+ }
+ /* already loaded? */
+ if (strcmp(ifname, cp) == 0 ||
+ strcmp(ifkind, cp) == 0)
+ return;
+ }
+ }
+
+ /* not present, we should try to load it */
+ kldload(ifkind);
+}
+
+static struct cmd basic_cmds[] = {
+ DEF_CMD("up", IFF_UP, setifflags),
+ DEF_CMD("down", -IFF_UP, setifflags),
+ DEF_CMD("arp", -IFF_NOARP, setifflags),
+ DEF_CMD("-arp", IFF_NOARP, setifflags),
+ DEF_CMD("debug", IFF_DEBUG, setifflags),
+ DEF_CMD("-debug", -IFF_DEBUG, setifflags),
+ DEF_CMD_ARG("description", setifdescr),
+ DEF_CMD_ARG("descr", setifdescr),
+ DEF_CMD("-description", 0, unsetifdescr),
+ DEF_CMD("-descr", 0, unsetifdescr),
+ DEF_CMD("promisc", IFF_PPROMISC, setifflags),
+ DEF_CMD("-promisc", -IFF_PPROMISC, setifflags),
+ DEF_CMD("add", IFF_UP, notealias),
+ DEF_CMD("alias", IFF_UP, notealias),
+ DEF_CMD("-alias", -IFF_UP, notealias),
+ DEF_CMD("delete", -IFF_UP, notealias),
+ DEF_CMD("remove", -IFF_UP, notealias),
+#ifdef notdef
+#define EN_SWABIPS 0x1000
+ DEF_CMD("swabips", EN_SWABIPS, setifflags),
+ DEF_CMD("-swabips", -EN_SWABIPS, setifflags),
+#endif
+ DEF_CMD_ARG("netmask", setifnetmask),
+ DEF_CMD_ARG("metric", setifmetric),
+ DEF_CMD_ARG("broadcast", setifbroadaddr),
+ DEF_CMD_ARG2("tunnel", settunnel),
+ DEF_CMD("-tunnel", 0, deletetunnel),
+ DEF_CMD("deletetunnel", 0, deletetunnel),
+#ifdef JAIL
+ DEF_CMD_ARG("vnet", setifvnet),
+ DEF_CMD_ARG("-vnet", setifrvnet),
+#endif
+ DEF_CMD("link0", IFF_LINK0, setifflags),
+ DEF_CMD("-link0", -IFF_LINK0, setifflags),
+ DEF_CMD("link1", IFF_LINK1, setifflags),
+ DEF_CMD("-link1", -IFF_LINK1, setifflags),
+ DEF_CMD("link2", IFF_LINK2, setifflags),
+ DEF_CMD("-link2", -IFF_LINK2, setifflags),
+ DEF_CMD("monitor", IFF_MONITOR, setifflags),
+ DEF_CMD("-monitor", -IFF_MONITOR, setifflags),
+ DEF_CMD("staticarp", IFF_STATICARP, setifflags),
+ DEF_CMD("-staticarp", -IFF_STATICARP, setifflags),
+ DEF_CMD("rxcsum6", IFCAP_RXCSUM_IPV6, setifcap),
+ DEF_CMD("-rxcsum6", -IFCAP_RXCSUM_IPV6, setifcap),
+ DEF_CMD("txcsum6", IFCAP_TXCSUM_IPV6, setifcap),
+ DEF_CMD("-txcsum6", -IFCAP_TXCSUM_IPV6, setifcap),
+ DEF_CMD("rxcsum", IFCAP_RXCSUM, setifcap),
+ DEF_CMD("-rxcsum", -IFCAP_RXCSUM, setifcap),
+ DEF_CMD("txcsum", IFCAP_TXCSUM, setifcap),
+ DEF_CMD("-txcsum", -IFCAP_TXCSUM, setifcap),
+ DEF_CMD("netcons", IFCAP_NETCONS, setifcap),
+ DEF_CMD("-netcons", -IFCAP_NETCONS, setifcap),
+ DEF_CMD("polling", IFCAP_POLLING, setifcap),
+ DEF_CMD("-polling", -IFCAP_POLLING, setifcap),
+ DEF_CMD("tso6", IFCAP_TSO6, setifcap),
+ DEF_CMD("-tso6", -IFCAP_TSO6, setifcap),
+ DEF_CMD("tso4", IFCAP_TSO4, setifcap),
+ DEF_CMD("-tso4", -IFCAP_TSO4, setifcap),
+ DEF_CMD("tso", IFCAP_TSO, setifcap),
+ DEF_CMD("-tso", -IFCAP_TSO, setifcap),
+ DEF_CMD("toe", IFCAP_TOE, setifcap),
+ DEF_CMD("-toe", -IFCAP_TOE, setifcap),
+ DEF_CMD("lro", IFCAP_LRO, setifcap),
+ DEF_CMD("-lro", -IFCAP_LRO, setifcap),
+ DEF_CMD("wol", IFCAP_WOL, setifcap),
+ DEF_CMD("-wol", -IFCAP_WOL, setifcap),
+ DEF_CMD("wol_ucast", IFCAP_WOL_UCAST, setifcap),
+ DEF_CMD("-wol_ucast", -IFCAP_WOL_UCAST, setifcap),
+ DEF_CMD("wol_mcast", IFCAP_WOL_MCAST, setifcap),
+ DEF_CMD("-wol_mcast", -IFCAP_WOL_MCAST, setifcap),
+ DEF_CMD("wol_magic", IFCAP_WOL_MAGIC, setifcap),
+ DEF_CMD("-wol_magic", -IFCAP_WOL_MAGIC, setifcap),
+ DEF_CMD("normal", -IFF_LINK0, setifflags),
+ DEF_CMD("compress", IFF_LINK0, setifflags),
+ DEF_CMD("noicmp", IFF_LINK1, setifflags),
+ DEF_CMD_ARG("mtu", setifmtu),
+ DEF_CMD_ARG("name", setifname),
+};
+
+static __constructor void
+ifconfig_ctor(void)
+{
+#define N(a) (sizeof(a) / sizeof(a[0]))
+ size_t i;
+
+ for (i = 0; i < N(basic_cmds); i++)
+ cmd_register(&basic_cmds[i]);
+#undef N
+}
diff --git a/sbin/ifconfig/ifconfig.h b/sbin/ifconfig/ifconfig.h
new file mode 100644
index 0000000..6df9acf
--- /dev/null
+++ b/sbin/ifconfig/ifconfig.h
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 1997 Peter Wemm.
+ * 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 for the FreeBSD Project
+ * by Peter Wemm.
+ * 4. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * so there!
+ *
+ * $FreeBSD$
+ */
+
+#define __constructor __attribute__((constructor))
+
+struct afswtch;
+struct cmd;
+
+typedef void c_func(const char *cmd, int arg, int s, const struct afswtch *afp);
+typedef void c_func2(const char *arg1, const char *arg2, int s, const struct afswtch *afp);
+
+struct cmd {
+ const char *c_name;
+ int c_parameter;
+#define NEXTARG 0xffffff /* has following arg */
+#define NEXTARG2 0xfffffe /* has 2 following args */
+#define OPTARG 0xfffffd /* has optional following arg */
+ union {
+ c_func *c_func;
+ c_func2 *c_func2;
+ } c_u;
+ int c_iscloneop;
+ struct cmd *c_next;
+};
+void cmd_register(struct cmd *);
+
+typedef void callback_func(int s, void *);
+void callback_register(callback_func *, void *);
+
+/*
+ * Macros for declaring command functions and initializing entries.
+ */
+#define DECL_CMD_FUNC(name, cmd, arg) \
+ void name(const char *cmd, int arg, int s, const struct afswtch *afp)
+#define DECL_CMD_FUNC2(name, arg1, arg2) \
+ void name(const char *arg1, const char *arg2, int s, const struct afswtch *afp)
+
+#define DEF_CMD(name, param, func) { name, param, { .c_func = func }, 0, NULL }
+#define DEF_CMD_ARG(name, func) { name, NEXTARG, { .c_func = func }, 0, NULL }
+#define DEF_CMD_OPTARG(name, func) { name, OPTARG, { .c_func = func }, 0, NULL }
+#define DEF_CMD_ARG2(name, func) { name, NEXTARG2, { .c_func2 = func }, 0, NULL }
+#define DEF_CLONE_CMD(name, param, func) { name, param, { .c_func = func }, 1, NULL }
+#define DEF_CLONE_CMD_ARG(name, func) { name, NEXTARG, { .c_func = func }, 1, NULL }
+#define DEF_CLONE_CMD_ARG2(name, func) { name, NEXTARG2, { .c_func2 = func }, 1, NULL }
+
+struct ifaddrs;
+struct addrinfo;
+
+enum {
+ RIDADDR,
+ ADDR,
+ MASK,
+ DSTADDR,
+};
+
+struct afswtch {
+ const char *af_name; /* as given on cmd line, e.g. "inet" */
+ short af_af; /* AF_* */
+ /*
+ * Status is handled one of two ways; if there is an
+ * address associated with the interface then the
+ * associated address family af_status method is invoked
+ * with the appropriate addressin info. Otherwise, if
+ * all possible info is to be displayed and af_other_status
+ * is defined then it is invoked after all address status
+ * is presented.
+ */
+ void (*af_status)(int, const struct ifaddrs *);
+ void (*af_other_status)(int);
+ /* parse address method */
+ void (*af_getaddr)(const char *, int);
+ /* parse prefix method (IPv6) */
+ void (*af_getprefix)(const char *, int);
+ void (*af_postproc)(int s, const struct afswtch *);
+ u_long af_difaddr; /* set dst if address ioctl */
+ u_long af_aifaddr; /* set if address ioctl */
+ void *af_ridreq; /* */
+ void *af_addreq; /* */
+ struct afswtch *af_next;
+
+ /* XXX doesn't fit model */
+ void (*af_status_tunnel)(int);
+ void (*af_settunnel)(int s, struct addrinfo *srcres,
+ struct addrinfo *dstres);
+};
+void af_register(struct afswtch *);
+
+struct option {
+ const char *opt;
+ const char *opt_usage;
+ void (*cb)(const char *arg);
+ struct option *next;
+};
+void opt_register(struct option *);
+
+extern struct ifreq ifr;
+extern char name[IFNAMSIZ]; /* name of interface */
+extern int allmedia;
+extern int supmedia;
+extern int printkeys;
+extern int newaddr;
+extern int verbose;
+
+void setifcap(const char *, int value, int s, const struct afswtch *);
+
+void Perror(const char *cmd);
+void printb(const char *s, unsigned value, const char *bits);
+
+void ifmaybeload(const char *name);
+
+typedef void clone_callback_func(int, struct ifreq *);
+void clone_setdefcallback(const char *, clone_callback_func *);
+
+void sfp_status(int s, struct ifreq *ifr, int verbose);
+
+/*
+ * XXX expose this so modules that neeed to know of any pending
+ * operations on ifmedia can avoid cmd line ordering confusion.
+ */
+struct ifmediareq *ifmedia_getstate(int s);
+
+void print_vhid(const struct ifaddrs *, const char *);
+
diff --git a/sbin/ifconfig/iffib.c b/sbin/ifconfig/iffib.c
new file mode 100644
index 0000000..f3498b4
--- /dev/null
+++ b/sbin/ifconfig/iffib.c
@@ -0,0 +1,103 @@
+/*-
+ * Copyright (c) 2011 Alexander V. Chernikov
+ * Copyright (c) 2011 Christian S.J. Peron
+ * Copyright (c) 2011 Bjoern A. Zeeb
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+
+#include <net/if.h>
+#include <net/route.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <err.h>
+
+#include "ifconfig.h"
+
+static void
+fib_status(int s)
+{
+ struct ifreq ifr;
+
+ memset(&ifr, 0, sizeof(ifr));
+ strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
+
+ if (ioctl(s, SIOCGIFFIB, (caddr_t)&ifr) < 0)
+ return;
+
+ /* Ignore if it is the default. */
+ if (ifr.ifr_fib == 0)
+ return;
+
+ printf("\tfib: %u\n", ifr.ifr_fib);
+}
+
+static void
+setiffib(const char *val, int dummy __unused, int s,
+ const struct afswtch *afp)
+{
+ unsigned long fib;
+ char *ep;
+
+ fib = strtoul(val, &ep, 0);
+ if (*ep != '\0' || fib > UINT_MAX) {
+ warn("fib %s not valid", val);
+ return;
+ }
+
+ strncpy(ifr.ifr_name, name, sizeof (ifr.ifr_name));
+ ifr.ifr_fib = fib;
+ if (ioctl(s, SIOCSIFFIB, (caddr_t)&ifr) < 0)
+ warn("ioctl (SIOCSIFFIB)");
+}
+
+static struct cmd fib_cmds[] = {
+ DEF_CMD_ARG("fib", setiffib),
+};
+
+static struct afswtch af_fib = {
+ .af_name = "af_fib",
+ .af_af = AF_UNSPEC,
+ .af_other_status = fib_status,
+};
+
+static __constructor void
+fib_ctor(void)
+{
+#define N(a) (sizeof(a) / sizeof(a[0]))
+ size_t i;
+
+ for (i = 0; i < N(fib_cmds); i++)
+ cmd_register(&fib_cmds[i]);
+ af_register(&af_fib);
+#undef N
+}
diff --git a/sbin/ifconfig/ifgif.c b/sbin/ifconfig/ifgif.c
new file mode 100644
index 0000000..f91508b
--- /dev/null
+++ b/sbin/ifconfig/ifgif.c
@@ -0,0 +1,118 @@
+/*-
+ * Copyright (c) 2009 Hiroki Sato. 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.
+ *
+ * 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 OR HIS RELATIVES 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 MIND, 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 lint
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif
+
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <net/ethernet.h>
+#include <net/if.h>
+#include <net/if_gif.h>
+#include <net/route.h>
+
+#include <ctype.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <err.h>
+#include <errno.h>
+
+#include "ifconfig.h"
+
+#define GIFBITS "\020\1ACCEPT_REV_ETHIP_VER\5SEND_REV_ETHIP_VER"
+
+static void gif_status(int);
+
+static void
+gif_status(int s)
+{
+ int opts;
+
+ ifr.ifr_data = (caddr_t)&opts;
+ if (ioctl(s, GIFGOPTS, &ifr) == -1)
+ return;
+ if (opts == 0)
+ return;
+ printb("\toptions", opts, GIFBITS);
+ putchar('\n');
+}
+
+static void
+setgifopts(const char *val,
+ int d, int s, const struct afswtch *afp)
+{
+ int opts;
+
+ ifr.ifr_data = (caddr_t)&opts;
+ if (ioctl(s, GIFGOPTS, &ifr) == -1) {
+ warn("ioctl(GIFGOPTS)");
+ return;
+ }
+
+ if (d < 0)
+ opts &= ~(-d);
+ else
+ opts |= d;
+
+ if (ioctl(s, GIFSOPTS, &ifr) == -1) {
+ warn("ioctl(GIFSOPTS)");
+ return;
+ }
+}
+
+static struct cmd gif_cmds[] = {
+ DEF_CMD("accept_rev_ethip_ver", GIF_ACCEPT_REVETHIP, setgifopts),
+ DEF_CMD("-accept_rev_ethip_ver",-GIF_ACCEPT_REVETHIP, setgifopts),
+ DEF_CMD("send_rev_ethip_ver", GIF_SEND_REVETHIP, setgifopts),
+ DEF_CMD("-send_rev_ethip_ver", -GIF_SEND_REVETHIP, setgifopts),
+};
+
+static struct afswtch af_gif = {
+ .af_name = "af_gif",
+ .af_af = AF_UNSPEC,
+ .af_other_status = gif_status,
+};
+
+static __constructor void
+gif_ctor(void)
+{
+#define N(a) (sizeof(a) / sizeof(a[0]))
+ size_t i;
+
+ for (i = 0; i < N(gif_cmds); i++)
+ cmd_register(&gif_cmds[i]);
+ af_register(&af_gif);
+#undef N
+}
diff --git a/sbin/ifconfig/ifgre.c b/sbin/ifconfig/ifgre.c
new file mode 100644
index 0000000..3ac7454
--- /dev/null
+++ b/sbin/ifconfig/ifgre.c
@@ -0,0 +1,123 @@
+/*-
+ * Copyright (c) 2008 Andrew Thompson. 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.
+ *
+ * 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 OR HIS RELATIVES 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 MIND, 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 <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <net/if.h>
+#include <net/if_gre.h>
+
+#include <ctype.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <err.h>
+
+#include "ifconfig.h"
+
+#define GREBITS "\020\01ENABLE_CSUM\02ENABLE_SEQ"
+
+static void gre_status(int s);
+
+static void
+gre_status(int s)
+{
+ uint32_t opts = 0;
+
+ ifr.ifr_data = (caddr_t)&opts;
+ if (ioctl(s, GREGKEY, &ifr) == 0)
+ if (opts != 0)
+ printf("\tgrekey: 0x%x (%u)\n", opts, opts);
+ opts = 0;
+ if (ioctl(s, GREGOPTS, &ifr) != 0 || opts == 0)
+ return;
+ printb("\toptions", opts, GREBITS);
+ putchar('\n');
+}
+
+static void
+setifgrekey(const char *val, int dummy __unused, int s,
+ const struct afswtch *afp)
+{
+ uint32_t grekey = strtol(val, NULL, 0);
+
+ strncpy(ifr.ifr_name, name, sizeof (ifr.ifr_name));
+ ifr.ifr_data = (caddr_t)&grekey;
+ if (ioctl(s, GRESKEY, (caddr_t)&ifr) < 0)
+ warn("ioctl (set grekey)");
+}
+
+static void
+setifgreopts(const char *val, int d, int s, const struct afswtch *afp)
+{
+ uint32_t opts;
+
+ ifr.ifr_data = (caddr_t)&opts;
+ if (ioctl(s, GREGOPTS, &ifr) == -1) {
+ warn("ioctl(GREGOPTS)");
+ return;
+ }
+
+ if (d < 0)
+ opts &= ~(-d);
+ else
+ opts |= d;
+
+ if (ioctl(s, GRESOPTS, &ifr) == -1) {
+ warn("ioctl(GIFSOPTS)");
+ return;
+ }
+}
+
+
+static struct cmd gre_cmds[] = {
+ DEF_CMD_ARG("grekey", setifgrekey),
+ DEF_CMD("enable_csum", GRE_ENABLE_CSUM, setifgreopts),
+ DEF_CMD("-enable_csum",-GRE_ENABLE_CSUM,setifgreopts),
+ DEF_CMD("enable_seq", GRE_ENABLE_SEQ, setifgreopts),
+ DEF_CMD("-enable_seq",-GRE_ENABLE_SEQ, setifgreopts),
+};
+static struct afswtch af_gre = {
+ .af_name = "af_gre",
+ .af_af = AF_UNSPEC,
+ .af_other_status = gre_status,
+};
+
+static __constructor void
+gre_ctor(void)
+{
+#define N(a) (sizeof(a) / sizeof(a[0]))
+ size_t i;
+
+ for (i = 0; i < N(gre_cmds); i++)
+ cmd_register(&gre_cmds[i]);
+ af_register(&af_gre);
+#undef N
+}
diff --git a/sbin/ifconfig/ifgroup.c b/sbin/ifconfig/ifgroup.c
new file mode 100644
index 0000000..e3f271d
--- /dev/null
+++ b/sbin/ifconfig/ifgroup.c
@@ -0,0 +1,185 @@
+/*-
+ * Copyright (c) 2006 Max Laier. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <net/if.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "ifconfig.h"
+
+/* ARGSUSED */
+static void
+setifgroup(const char *group_name, int d, int s, const struct afswtch *rafp)
+{
+ struct ifgroupreq ifgr;
+
+ memset(&ifgr, 0, sizeof(ifgr));
+ strlcpy(ifgr.ifgr_name, name, IFNAMSIZ);
+
+ if (group_name[0] && isdigit(group_name[strlen(group_name) - 1]))
+ errx(1, "setifgroup: group names may not end in a digit");
+
+ if (strlcpy(ifgr.ifgr_group, group_name, IFNAMSIZ) >= IFNAMSIZ)
+ errx(1, "setifgroup: group name too long");
+ if (ioctl(s, SIOCAIFGROUP, (caddr_t)&ifgr) == -1 && errno != EEXIST)
+ err(1," SIOCAIFGROUP");
+}
+
+/* ARGSUSED */
+static void
+unsetifgroup(const char *group_name, int d, int s, const struct afswtch *rafp)
+{
+ struct ifgroupreq ifgr;
+
+ memset(&ifgr, 0, sizeof(ifgr));
+ strlcpy(ifgr.ifgr_name, name, IFNAMSIZ);
+
+ if (group_name[0] && isdigit(group_name[strlen(group_name) - 1]))
+ errx(1, "unsetifgroup: group names may not end in a digit");
+
+ if (strlcpy(ifgr.ifgr_group, group_name, IFNAMSIZ) >= IFNAMSIZ)
+ errx(1, "unsetifgroup: group name too long");
+ if (ioctl(s, SIOCDIFGROUP, (caddr_t)&ifgr) == -1 && errno != ENOENT)
+ err(1, "SIOCDIFGROUP");
+}
+
+static void
+getifgroups(int s)
+{
+ int len, cnt;
+ struct ifgroupreq ifgr;
+ struct ifg_req *ifg;
+
+ memset(&ifgr, 0, sizeof(ifgr));
+ strlcpy(ifgr.ifgr_name, name, IFNAMSIZ);
+
+ if (ioctl(s, SIOCGIFGROUP, (caddr_t)&ifgr) == -1) {
+ if (errno == EINVAL || errno == ENOTTY)
+ return;
+ else
+ err(1, "SIOCGIFGROUP");
+ }
+
+ len = ifgr.ifgr_len;
+ ifgr.ifgr_groups =
+ (struct ifg_req *)calloc(len / sizeof(struct ifg_req),
+ sizeof(struct ifg_req));
+ if (ifgr.ifgr_groups == NULL)
+ err(1, "getifgroups");
+ if (ioctl(s, SIOCGIFGROUP, (caddr_t)&ifgr) == -1)
+ err(1, "SIOCGIFGROUP");
+
+ cnt = 0;
+ ifg = ifgr.ifgr_groups;
+ for (; ifg && len >= sizeof(struct ifg_req); ifg++) {
+ len -= sizeof(struct ifg_req);
+ if (strcmp(ifg->ifgrq_group, "all")) {
+ if (cnt == 0)
+ printf("\tgroups: ");
+ cnt++;
+ printf("%s ", ifg->ifgrq_group);
+ }
+ }
+ if (cnt)
+ printf("\n");
+
+ free(ifgr.ifgr_groups);
+}
+
+static void
+printgroup(const char *groupname)
+{
+ struct ifgroupreq ifgr;
+ struct ifg_req *ifg;
+ int len, cnt = 0;
+ int s;
+
+ s = socket(AF_LOCAL, SOCK_DGRAM, 0);
+ if (s == -1)
+ err(1, "socket(AF_LOCAL,SOCK_DGRAM)");
+ bzero(&ifgr, sizeof(ifgr));
+ strlcpy(ifgr.ifgr_name, groupname, sizeof(ifgr.ifgr_name));
+ if (ioctl(s, SIOCGIFGMEMB, (caddr_t)&ifgr) == -1) {
+ if (errno == EINVAL || errno == ENOTTY ||
+ errno == ENOENT)
+ exit(0);
+ else
+ err(1, "SIOCGIFGMEMB");
+ }
+
+ len = ifgr.ifgr_len;
+ if ((ifgr.ifgr_groups = calloc(1, len)) == NULL)
+ err(1, "printgroup");
+ if (ioctl(s, SIOCGIFGMEMB, (caddr_t)&ifgr) == -1)
+ err(1, "SIOCGIFGMEMB");
+
+ for (ifg = ifgr.ifgr_groups; ifg && len >= sizeof(struct ifg_req);
+ ifg++) {
+ len -= sizeof(struct ifg_req);
+ printf("%s\n", ifg->ifgrq_member);
+ cnt++;
+ }
+ free(ifgr.ifgr_groups);
+
+ exit(0);
+}
+
+static struct cmd group_cmds[] = {
+ DEF_CMD_ARG("group", setifgroup),
+ DEF_CMD_ARG("-group", unsetifgroup),
+};
+static struct afswtch af_group = {
+ .af_name = "af_group",
+ .af_af = AF_UNSPEC,
+ .af_other_status = getifgroups,
+};
+static struct option group_gopt = { "g:", "[-g groupname]", printgroup };
+
+static __constructor void
+group_ctor(void)
+{
+#define N(a) (sizeof(a) / sizeof(a[0]))
+ int i;
+
+ for (i = 0; i < N(group_cmds); i++)
+ cmd_register(&group_cmds[i]);
+ af_register(&af_group);
+ opt_register(&group_gopt);
+#undef N
+}
diff --git a/sbin/ifconfig/ifieee80211.c b/sbin/ifconfig/ifieee80211.c
new file mode 100644
index 0000000..d0b4917
--- /dev/null
+++ b/sbin/ifconfig/ifieee80211.c
@@ -0,0 +1,5331 @@
+/*
+ * Copyright 2001 The Aerospace Corporation. 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 Aerospace Corporation may not be used to endorse or
+ * promote products derived from this software.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AEROSPACE CORPORATION ``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 AEROSPACE CORPORATION 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.
+ *
+ * $FreeBSD$
+ */
+
+/*-
+ * Copyright (c) 1997, 1998, 2000 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Jason R. Thorpe of the Numerical Aerospace Simulation Facility,
+ * NASA Ames Research Center.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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 <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+#include <sys/time.h>
+
+#include <net/ethernet.h>
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <net/if_types.h>
+#include <net/if_media.h>
+#include <net/route.h>
+
+#include <net80211/ieee80211_ioctl.h>
+#include <net80211/ieee80211_freebsd.h>
+#include <net80211/ieee80211_superg.h>
+#include <net80211/ieee80211_tdma.h>
+#include <net80211/ieee80211_mesh.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <stddef.h> /* NB: for offsetof */
+
+#include "ifconfig.h"
+#include "regdomain.h"
+
+#ifndef IEEE80211_FIXED_RATE_NONE
+#define IEEE80211_FIXED_RATE_NONE 0xff
+#endif
+
+/* XXX need these publicly defined or similar */
+#ifndef IEEE80211_NODE_AUTH
+#define IEEE80211_NODE_AUTH 0x000001 /* authorized for data */
+#define IEEE80211_NODE_QOS 0x000002 /* QoS enabled */
+#define IEEE80211_NODE_ERP 0x000004 /* ERP enabled */
+#define IEEE80211_NODE_PWR_MGT 0x000010 /* power save mode enabled */
+#define IEEE80211_NODE_AREF 0x000020 /* authentication ref held */
+#define IEEE80211_NODE_HT 0x000040 /* HT enabled */
+#define IEEE80211_NODE_HTCOMPAT 0x000080 /* HT setup w/ vendor OUI's */
+#define IEEE80211_NODE_WPS 0x000100 /* WPS association */
+#define IEEE80211_NODE_TSN 0x000200 /* TSN association */
+#define IEEE80211_NODE_AMPDU_RX 0x000400 /* AMPDU rx enabled */
+#define IEEE80211_NODE_AMPDU_TX 0x000800 /* AMPDU tx enabled */
+#define IEEE80211_NODE_MIMO_PS 0x001000 /* MIMO power save enabled */
+#define IEEE80211_NODE_MIMO_RTS 0x002000 /* send RTS in MIMO PS */
+#define IEEE80211_NODE_RIFS 0x004000 /* RIFS enabled */
+#define IEEE80211_NODE_SGI20 0x008000 /* Short GI in HT20 enabled */
+#define IEEE80211_NODE_SGI40 0x010000 /* Short GI in HT40 enabled */
+#define IEEE80211_NODE_ASSOCID 0x020000 /* xmit requires associd */
+#define IEEE80211_NODE_AMSDU_RX 0x040000 /* AMSDU rx enabled */
+#define IEEE80211_NODE_AMSDU_TX 0x080000 /* AMSDU tx enabled */
+#endif
+
+#define MAXCHAN 1536 /* max 1.5K channels */
+
+#define MAXCOL 78
+static int col;
+static char spacer;
+
+static void LINE_INIT(char c);
+static void LINE_BREAK(void);
+static void LINE_CHECK(const char *fmt, ...);
+
+static const char *modename[IEEE80211_MODE_MAX] = {
+ [IEEE80211_MODE_AUTO] = "auto",
+ [IEEE80211_MODE_11A] = "11a",
+ [IEEE80211_MODE_11B] = "11b",
+ [IEEE80211_MODE_11G] = "11g",
+ [IEEE80211_MODE_FH] = "fh",
+ [IEEE80211_MODE_TURBO_A] = "turboA",
+ [IEEE80211_MODE_TURBO_G] = "turboG",
+ [IEEE80211_MODE_STURBO_A] = "sturbo",
+ [IEEE80211_MODE_11NA] = "11na",
+ [IEEE80211_MODE_11NG] = "11ng",
+ [IEEE80211_MODE_HALF] = "half",
+ [IEEE80211_MODE_QUARTER] = "quarter"
+};
+
+static void set80211(int s, int type, int val, int len, void *data);
+static int get80211(int s, int type, void *data, int len);
+static int get80211len(int s, int type, void *data, int len, int *plen);
+static int get80211val(int s, int type, int *val);
+static const char *get_string(const char *val, const char *sep,
+ u_int8_t *buf, int *lenp);
+static void print_string(const u_int8_t *buf, int len);
+static void print_regdomain(const struct ieee80211_regdomain *, int);
+static void print_channels(int, const struct ieee80211req_chaninfo *,
+ int allchans, int verbose);
+static void regdomain_makechannels(struct ieee80211_regdomain_req *,
+ const struct ieee80211_devcaps_req *);
+static const char *mesh_linkstate_string(uint8_t state);
+
+static struct ieee80211req_chaninfo *chaninfo;
+static struct ieee80211_regdomain regdomain;
+static int gotregdomain = 0;
+static struct ieee80211_roamparams_req roamparams;
+static int gotroam = 0;
+static struct ieee80211_txparams_req txparams;
+static int gottxparams = 0;
+static struct ieee80211_channel curchan;
+static int gotcurchan = 0;
+static struct ifmediareq *ifmr;
+static int htconf = 0;
+static int gothtconf = 0;
+
+static void
+gethtconf(int s)
+{
+ if (gothtconf)
+ return;
+ if (get80211val(s, IEEE80211_IOC_HTCONF, &htconf) < 0)
+ warn("unable to get HT configuration information");
+ gothtconf = 1;
+}
+
+/*
+ * Collect channel info from the kernel. We use this (mostly)
+ * to handle mapping between frequency and IEEE channel number.
+ */
+static void
+getchaninfo(int s)
+{
+ if (chaninfo != NULL)
+ return;
+ chaninfo = malloc(IEEE80211_CHANINFO_SIZE(MAXCHAN));
+ if (chaninfo == NULL)
+ errx(1, "no space for channel list");
+ if (get80211(s, IEEE80211_IOC_CHANINFO, chaninfo,
+ IEEE80211_CHANINFO_SIZE(MAXCHAN)) < 0)
+ err(1, "unable to get channel information");
+ ifmr = ifmedia_getstate(s);
+ gethtconf(s);
+}
+
+static struct regdata *
+getregdata(void)
+{
+ static struct regdata *rdp = NULL;
+ if (rdp == NULL) {
+ rdp = lib80211_alloc_regdata();
+ if (rdp == NULL)
+ errx(-1, "missing or corrupted regdomain database");
+ }
+ return rdp;
+}
+
+/*
+ * Given the channel at index i with attributes from,
+ * check if there is a channel with attributes to in
+ * the channel table. With suitable attributes this
+ * allows the caller to look for promotion; e.g. from
+ * 11b > 11g.
+ */
+static int
+canpromote(int i, int from, int to)
+{
+ const struct ieee80211_channel *fc = &chaninfo->ic_chans[i];
+ int j;
+
+ if ((fc->ic_flags & from) != from)
+ return i;
+ /* NB: quick check exploiting ordering of chans w/ same frequency */
+ if (i+1 < chaninfo->ic_nchans &&
+ chaninfo->ic_chans[i+1].ic_freq == fc->ic_freq &&
+ (chaninfo->ic_chans[i+1].ic_flags & to) == to)
+ return i+1;
+ /* brute force search in case channel list is not ordered */
+ for (j = 0; j < chaninfo->ic_nchans; j++) {
+ const struct ieee80211_channel *tc = &chaninfo->ic_chans[j];
+ if (j != i &&
+ tc->ic_freq == fc->ic_freq && (tc->ic_flags & to) == to)
+ return j;
+ }
+ return i;
+}
+
+/*
+ * Handle channel promotion. When a channel is specified with
+ * only a frequency we want to promote it to the ``best'' channel
+ * available. The channel list has separate entries for 11b, 11g,
+ * 11a, and 11n[ga] channels so specifying a frequency w/o any
+ * attributes requires we upgrade, e.g. from 11b -> 11g. This
+ * gets complicated when the channel is specified on the same
+ * command line with a media request that constrains the available
+ * channe list (e.g. mode 11a); we want to honor that to avoid
+ * confusing behaviour.
+ */
+static int
+promote(int i)
+{
+ /*
+ * Query the current mode of the interface in case it's
+ * constrained (e.g. to 11a). We must do this carefully
+ * as there may be a pending ifmedia request in which case
+ * asking the kernel will give us the wrong answer. This
+ * is an unfortunate side-effect of the way ifconfig is
+ * structure for modularity (yech).
+ *
+ * NB: ifmr is actually setup in getchaninfo (above); we
+ * assume it's called coincident with to this call so
+ * we have a ``current setting''; otherwise we must pass
+ * the socket descriptor down to here so we can make
+ * the ifmedia_getstate call ourselves.
+ */
+ int chanmode = ifmr != NULL ? IFM_MODE(ifmr->ifm_current) : IFM_AUTO;
+
+ /* when ambiguous promote to ``best'' */
+ /* NB: we abitrarily pick HT40+ over HT40- */
+ if (chanmode != IFM_IEEE80211_11B)
+ i = canpromote(i, IEEE80211_CHAN_B, IEEE80211_CHAN_G);
+ if (chanmode != IFM_IEEE80211_11G && (htconf & 1)) {
+ i = canpromote(i, IEEE80211_CHAN_G,
+ IEEE80211_CHAN_G | IEEE80211_CHAN_HT20);
+ if (htconf & 2) {
+ i = canpromote(i, IEEE80211_CHAN_G,
+ IEEE80211_CHAN_G | IEEE80211_CHAN_HT40D);
+ i = canpromote(i, IEEE80211_CHAN_G,
+ IEEE80211_CHAN_G | IEEE80211_CHAN_HT40U);
+ }
+ }
+ if (chanmode != IFM_IEEE80211_11A && (htconf & 1)) {
+ i = canpromote(i, IEEE80211_CHAN_A,
+ IEEE80211_CHAN_A | IEEE80211_CHAN_HT20);
+ if (htconf & 2) {
+ i = canpromote(i, IEEE80211_CHAN_A,
+ IEEE80211_CHAN_A | IEEE80211_CHAN_HT40D);
+ i = canpromote(i, IEEE80211_CHAN_A,
+ IEEE80211_CHAN_A | IEEE80211_CHAN_HT40U);
+ }
+ }
+ return i;
+}
+
+static void
+mapfreq(struct ieee80211_channel *chan, int freq, int flags)
+{
+ int i;
+
+ for (i = 0; i < chaninfo->ic_nchans; i++) {
+ const struct ieee80211_channel *c = &chaninfo->ic_chans[i];
+
+ if (c->ic_freq == freq && (c->ic_flags & flags) == flags) {
+ if (flags == 0) {
+ /* when ambiguous promote to ``best'' */
+ c = &chaninfo->ic_chans[promote(i)];
+ }
+ *chan = *c;
+ return;
+ }
+ }
+ errx(1, "unknown/undefined frequency %u/0x%x", freq, flags);
+}
+
+static void
+mapchan(struct ieee80211_channel *chan, int ieee, int flags)
+{
+ int i;
+
+ for (i = 0; i < chaninfo->ic_nchans; i++) {
+ const struct ieee80211_channel *c = &chaninfo->ic_chans[i];
+
+ if (c->ic_ieee == ieee && (c->ic_flags & flags) == flags) {
+ if (flags == 0) {
+ /* when ambiguous promote to ``best'' */
+ c = &chaninfo->ic_chans[promote(i)];
+ }
+ *chan = *c;
+ return;
+ }
+ }
+ errx(1, "unknown/undefined channel number %d flags 0x%x", ieee, flags);
+}
+
+static const struct ieee80211_channel *
+getcurchan(int s)
+{
+ if (gotcurchan)
+ return &curchan;
+ if (get80211(s, IEEE80211_IOC_CURCHAN, &curchan, sizeof(curchan)) < 0) {
+ int val;
+ /* fall back to legacy ioctl */
+ if (get80211val(s, IEEE80211_IOC_CHANNEL, &val) < 0)
+ err(-1, "cannot figure out current channel");
+ getchaninfo(s);
+ mapchan(&curchan, val, 0);
+ }
+ gotcurchan = 1;
+ return &curchan;
+}
+
+static enum ieee80211_phymode
+chan2mode(const struct ieee80211_channel *c)
+{
+ if (IEEE80211_IS_CHAN_HTA(c))
+ return IEEE80211_MODE_11NA;
+ if (IEEE80211_IS_CHAN_HTG(c))
+ return IEEE80211_MODE_11NG;
+ if (IEEE80211_IS_CHAN_108A(c))
+ return IEEE80211_MODE_TURBO_A;
+ if (IEEE80211_IS_CHAN_108G(c))
+ return IEEE80211_MODE_TURBO_G;
+ if (IEEE80211_IS_CHAN_ST(c))
+ return IEEE80211_MODE_STURBO_A;
+ if (IEEE80211_IS_CHAN_FHSS(c))
+ return IEEE80211_MODE_FH;
+ if (IEEE80211_IS_CHAN_HALF(c))
+ return IEEE80211_MODE_HALF;
+ if (IEEE80211_IS_CHAN_QUARTER(c))
+ return IEEE80211_MODE_QUARTER;
+ if (IEEE80211_IS_CHAN_A(c))
+ return IEEE80211_MODE_11A;
+ if (IEEE80211_IS_CHAN_ANYG(c))
+ return IEEE80211_MODE_11G;
+ if (IEEE80211_IS_CHAN_B(c))
+ return IEEE80211_MODE_11B;
+ return IEEE80211_MODE_AUTO;
+}
+
+static void
+getroam(int s)
+{
+ if (gotroam)
+ return;
+ if (get80211(s, IEEE80211_IOC_ROAM,
+ &roamparams, sizeof(roamparams)) < 0)
+ err(1, "unable to get roaming parameters");
+ gotroam = 1;
+}
+
+static void
+setroam_cb(int s, void *arg)
+{
+ struct ieee80211_roamparams_req *roam = arg;
+ set80211(s, IEEE80211_IOC_ROAM, 0, sizeof(*roam), roam);
+}
+
+static void
+gettxparams(int s)
+{
+ if (gottxparams)
+ return;
+ if (get80211(s, IEEE80211_IOC_TXPARAMS,
+ &txparams, sizeof(txparams)) < 0)
+ err(1, "unable to get transmit parameters");
+ gottxparams = 1;
+}
+
+static void
+settxparams_cb(int s, void *arg)
+{
+ struct ieee80211_txparams_req *txp = arg;
+ set80211(s, IEEE80211_IOC_TXPARAMS, 0, sizeof(*txp), txp);
+}
+
+static void
+getregdomain(int s)
+{
+ if (gotregdomain)
+ return;
+ if (get80211(s, IEEE80211_IOC_REGDOMAIN,
+ &regdomain, sizeof(regdomain)) < 0)
+ err(1, "unable to get regulatory domain info");
+ gotregdomain = 1;
+}
+
+static void
+getdevcaps(int s, struct ieee80211_devcaps_req *dc)
+{
+ if (get80211(s, IEEE80211_IOC_DEVCAPS, dc,
+ IEEE80211_DEVCAPS_SPACE(dc)) < 0)
+ err(1, "unable to get device capabilities");
+}
+
+static void
+setregdomain_cb(int s, void *arg)
+{
+ struct ieee80211_regdomain_req *req;
+ struct ieee80211_regdomain *rd = arg;
+ struct ieee80211_devcaps_req *dc;
+ struct regdata *rdp = getregdata();
+
+ if (rd->country != NO_COUNTRY) {
+ const struct country *cc;
+ /*
+ * Check current country seting to make sure it's
+ * compatible with the new regdomain. If not, then
+ * override it with any default country for this
+ * SKU. If we cannot arrange a match, then abort.
+ */
+ cc = lib80211_country_findbycc(rdp, rd->country);
+ if (cc == NULL)
+ errx(1, "unknown ISO country code %d", rd->country);
+ if (cc->rd->sku != rd->regdomain) {
+ const struct regdomain *rp;
+ /*
+ * Check if country is incompatible with regdomain.
+ * To enable multiple regdomains for a country code
+ * we permit a mismatch between the regdomain and
+ * the country's associated regdomain when the
+ * regdomain is setup w/o a default country. For
+ * example, US is bound to the FCC regdomain but
+ * we allow US to be combined with FCC3 because FCC3
+ * has not default country. This allows bogus
+ * combinations like FCC3+DK which are resolved when
+ * constructing the channel list by deferring to the
+ * regdomain to construct the channel list.
+ */
+ rp = lib80211_regdomain_findbysku(rdp, rd->regdomain);
+ if (rp == NULL)
+ errx(1, "country %s (%s) is not usable with "
+ "regdomain %d", cc->isoname, cc->name,
+ rd->regdomain);
+ else if (rp->cc != NULL && rp->cc != cc)
+ errx(1, "country %s (%s) is not usable with "
+ "regdomain %s", cc->isoname, cc->name,
+ rp->name);
+ }
+ }
+ /*
+ * Fetch the device capabilities and calculate the
+ * full set of netbands for which we request a new
+ * channel list be constructed. Once that's done we
+ * push the regdomain info + channel list to the kernel.
+ */
+ dc = malloc(IEEE80211_DEVCAPS_SIZE(MAXCHAN));
+ if (dc == NULL)
+ errx(1, "no space for device capabilities");
+ dc->dc_chaninfo.ic_nchans = MAXCHAN;
+ getdevcaps(s, dc);
+#if 0
+ if (verbose) {
+ printf("drivercaps: 0x%x\n", dc->dc_drivercaps);
+ printf("cryptocaps: 0x%x\n", dc->dc_cryptocaps);
+ printf("htcaps : 0x%x\n", dc->dc_htcaps);
+ memcpy(chaninfo, &dc->dc_chaninfo,
+ IEEE80211_CHANINFO_SPACE(&dc->dc_chaninfo));
+ print_channels(s, &dc->dc_chaninfo, 1/*allchans*/, 1/*verbose*/);
+ }
+#endif
+ req = malloc(IEEE80211_REGDOMAIN_SIZE(dc->dc_chaninfo.ic_nchans));
+ if (req == NULL)
+ errx(1, "no space for regdomain request");
+ req->rd = *rd;
+ regdomain_makechannels(req, dc);
+ if (verbose) {
+ LINE_INIT(':');
+ print_regdomain(rd, 1/*verbose*/);
+ LINE_BREAK();
+ /* blech, reallocate channel list for new data */
+ if (chaninfo != NULL)
+ free(chaninfo);
+ chaninfo = malloc(IEEE80211_CHANINFO_SPACE(&req->chaninfo));
+ if (chaninfo == NULL)
+ errx(1, "no space for channel list");
+ memcpy(chaninfo, &req->chaninfo,
+ IEEE80211_CHANINFO_SPACE(&req->chaninfo));
+ print_channels(s, &req->chaninfo, 1/*allchans*/, 1/*verbose*/);
+ }
+ if (req->chaninfo.ic_nchans == 0)
+ errx(1, "no channels calculated");
+ set80211(s, IEEE80211_IOC_REGDOMAIN, 0,
+ IEEE80211_REGDOMAIN_SPACE(req), req);
+ free(req);
+ free(dc);
+}
+
+static int
+ieee80211_mhz2ieee(int freq, int flags)
+{
+ struct ieee80211_channel chan;
+ mapfreq(&chan, freq, flags);
+ return chan.ic_ieee;
+}
+
+static int
+isanyarg(const char *arg)
+{
+ return (strncmp(arg, "-", 1) == 0 ||
+ strncasecmp(arg, "any", 3) == 0 || strncasecmp(arg, "off", 3) == 0);
+}
+
+static void
+set80211ssid(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ int ssid;
+ int len;
+ u_int8_t data[IEEE80211_NWID_LEN];
+
+ ssid = 0;
+ len = strlen(val);
+ if (len > 2 && isdigit((int)val[0]) && val[1] == ':') {
+ ssid = atoi(val)-1;
+ val += 2;
+ }
+
+ bzero(data, sizeof(data));
+ len = sizeof(data);
+ if (get_string(val, NULL, data, &len) == NULL)
+ exit(1);
+
+ set80211(s, IEEE80211_IOC_SSID, ssid, len, data);
+}
+
+static void
+set80211meshid(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ int len;
+ u_int8_t data[IEEE80211_NWID_LEN];
+
+ memset(data, 0, sizeof(data));
+ len = sizeof(data);
+ if (get_string(val, NULL, data, &len) == NULL)
+ exit(1);
+
+ set80211(s, IEEE80211_IOC_MESH_ID, 0, len, data);
+}
+
+static void
+set80211stationname(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ int len;
+ u_int8_t data[33];
+
+ bzero(data, sizeof(data));
+ len = sizeof(data);
+ get_string(val, NULL, data, &len);
+
+ set80211(s, IEEE80211_IOC_STATIONNAME, 0, len, data);
+}
+
+/*
+ * Parse a channel specification for attributes/flags.
+ * The syntax is:
+ * freq/xx channel width (5,10,20,40,40+,40-)
+ * freq:mode channel mode (a,b,g,h,n,t,s,d)
+ *
+ * These can be combined in either order; e.g. 2437:ng/40.
+ * Modes are case insensitive.
+ *
+ * The result is not validated here; it's assumed to be
+ * checked against the channel table fetched from the kernel.
+ */
+static int
+getchannelflags(const char *val, int freq)
+{
+#define _CHAN_HT 0x80000000
+ const char *cp;
+ int flags;
+
+ flags = 0;
+
+ cp = strchr(val, ':');
+ if (cp != NULL) {
+ for (cp++; isalpha((int) *cp); cp++) {
+ /* accept mixed case */
+ int c = *cp;
+ if (isupper(c))
+ c = tolower(c);
+ switch (c) {
+ case 'a': /* 802.11a */
+ flags |= IEEE80211_CHAN_A;
+ break;
+ case 'b': /* 802.11b */
+ flags |= IEEE80211_CHAN_B;
+ break;
+ case 'g': /* 802.11g */
+ flags |= IEEE80211_CHAN_G;
+ break;
+ case 'h': /* ht = 802.11n */
+ case 'n': /* 802.11n */
+ flags |= _CHAN_HT; /* NB: private */
+ break;
+ case 'd': /* dt = Atheros Dynamic Turbo */
+ flags |= IEEE80211_CHAN_TURBO;
+ break;
+ case 't': /* ht, dt, st, t */
+ /* dt and unadorned t specify Dynamic Turbo */
+ if ((flags & (IEEE80211_CHAN_STURBO|_CHAN_HT)) == 0)
+ flags |= IEEE80211_CHAN_TURBO;
+ break;
+ case 's': /* st = Atheros Static Turbo */
+ flags |= IEEE80211_CHAN_STURBO;
+ break;
+ default:
+ errx(-1, "%s: Invalid channel attribute %c\n",
+ val, *cp);
+ }
+ }
+ }
+ cp = strchr(val, '/');
+ if (cp != NULL) {
+ char *ep;
+ u_long cw = strtoul(cp+1, &ep, 10);
+
+ switch (cw) {
+ case 5:
+ flags |= IEEE80211_CHAN_QUARTER;
+ break;
+ case 10:
+ flags |= IEEE80211_CHAN_HALF;
+ break;
+ case 20:
+ /* NB: this may be removed below */
+ flags |= IEEE80211_CHAN_HT20;
+ break;
+ case 40:
+ if (ep != NULL && *ep == '+')
+ flags |= IEEE80211_CHAN_HT40U;
+ else if (ep != NULL && *ep == '-')
+ flags |= IEEE80211_CHAN_HT40D;
+ break;
+ default:
+ errx(-1, "%s: Invalid channel width\n", val);
+ }
+ }
+ /*
+ * Cleanup specifications.
+ */
+ if ((flags & _CHAN_HT) == 0) {
+ /*
+ * If user specified freq/20 or freq/40 quietly remove
+ * HT cw attributes depending on channel use. To give
+ * an explicit 20/40 width for an HT channel you must
+ * indicate it is an HT channel since all HT channels
+ * are also usable for legacy operation; e.g. freq:n/40.
+ */
+ flags &= ~IEEE80211_CHAN_HT;
+ } else {
+ /*
+ * Remove private indicator that this is an HT channel
+ * and if no explicit channel width has been given
+ * provide the default settings.
+ */
+ flags &= ~_CHAN_HT;
+ if ((flags & IEEE80211_CHAN_HT) == 0) {
+ struct ieee80211_channel chan;
+ /*
+ * Consult the channel list to see if we can use
+ * HT40+ or HT40- (if both the map routines choose).
+ */
+ if (freq > 255)
+ mapfreq(&chan, freq, 0);
+ else
+ mapchan(&chan, freq, 0);
+ flags |= (chan.ic_flags & IEEE80211_CHAN_HT);
+ }
+ }
+ return flags;
+#undef _CHAN_HT
+}
+
+static void
+getchannel(int s, struct ieee80211_channel *chan, const char *val)
+{
+ int v, flags;
+ char *eptr;
+
+ memset(chan, 0, sizeof(*chan));
+ if (isanyarg(val)) {
+ chan->ic_freq = IEEE80211_CHAN_ANY;
+ return;
+ }
+ getchaninfo(s);
+ errno = 0;
+ v = strtol(val, &eptr, 10);
+ if (val[0] == '\0' || val == eptr || errno == ERANGE ||
+ /* channel may be suffixed with nothing, :flag, or /width */
+ (eptr[0] != '\0' && eptr[0] != ':' && eptr[0] != '/'))
+ errx(1, "invalid channel specification%s",
+ errno == ERANGE ? " (out of range)" : "");
+ flags = getchannelflags(val, v);
+ if (v > 255) { /* treat as frequency */
+ mapfreq(chan, v, flags);
+ } else {
+ mapchan(chan, v, flags);
+ }
+}
+
+static void
+set80211channel(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ struct ieee80211_channel chan;
+
+ getchannel(s, &chan, val);
+ set80211(s, IEEE80211_IOC_CURCHAN, 0, sizeof(chan), &chan);
+}
+
+static void
+set80211chanswitch(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ struct ieee80211_chanswitch_req csr;
+
+ getchannel(s, &csr.csa_chan, val);
+ csr.csa_mode = 1;
+ csr.csa_count = 5;
+ set80211(s, IEEE80211_IOC_CHANSWITCH, 0, sizeof(csr), &csr);
+}
+
+static void
+set80211authmode(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ int mode;
+
+ if (strcasecmp(val, "none") == 0) {
+ mode = IEEE80211_AUTH_NONE;
+ } else if (strcasecmp(val, "open") == 0) {
+ mode = IEEE80211_AUTH_OPEN;
+ } else if (strcasecmp(val, "shared") == 0) {
+ mode = IEEE80211_AUTH_SHARED;
+ } else if (strcasecmp(val, "8021x") == 0) {
+ mode = IEEE80211_AUTH_8021X;
+ } else if (strcasecmp(val, "wpa") == 0) {
+ mode = IEEE80211_AUTH_WPA;
+ } else {
+ errx(1, "unknown authmode");
+ }
+
+ set80211(s, IEEE80211_IOC_AUTHMODE, mode, 0, NULL);
+}
+
+static void
+set80211powersavemode(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ int mode;
+
+ if (strcasecmp(val, "off") == 0) {
+ mode = IEEE80211_POWERSAVE_OFF;
+ } else if (strcasecmp(val, "on") == 0) {
+ mode = IEEE80211_POWERSAVE_ON;
+ } else if (strcasecmp(val, "cam") == 0) {
+ mode = IEEE80211_POWERSAVE_CAM;
+ } else if (strcasecmp(val, "psp") == 0) {
+ mode = IEEE80211_POWERSAVE_PSP;
+ } else if (strcasecmp(val, "psp-cam") == 0) {
+ mode = IEEE80211_POWERSAVE_PSP_CAM;
+ } else {
+ errx(1, "unknown powersavemode");
+ }
+
+ set80211(s, IEEE80211_IOC_POWERSAVE, mode, 0, NULL);
+}
+
+static void
+set80211powersave(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ if (d == 0)
+ set80211(s, IEEE80211_IOC_POWERSAVE, IEEE80211_POWERSAVE_OFF,
+ 0, NULL);
+ else
+ set80211(s, IEEE80211_IOC_POWERSAVE, IEEE80211_POWERSAVE_ON,
+ 0, NULL);
+}
+
+static void
+set80211powersavesleep(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ set80211(s, IEEE80211_IOC_POWERSAVESLEEP, atoi(val), 0, NULL);
+}
+
+static void
+set80211wepmode(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ int mode;
+
+ if (strcasecmp(val, "off") == 0) {
+ mode = IEEE80211_WEP_OFF;
+ } else if (strcasecmp(val, "on") == 0) {
+ mode = IEEE80211_WEP_ON;
+ } else if (strcasecmp(val, "mixed") == 0) {
+ mode = IEEE80211_WEP_MIXED;
+ } else {
+ errx(1, "unknown wep mode");
+ }
+
+ set80211(s, IEEE80211_IOC_WEP, mode, 0, NULL);
+}
+
+static void
+set80211wep(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ set80211(s, IEEE80211_IOC_WEP, d, 0, NULL);
+}
+
+static int
+isundefarg(const char *arg)
+{
+ return (strcmp(arg, "-") == 0 || strncasecmp(arg, "undef", 5) == 0);
+}
+
+static void
+set80211weptxkey(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ if (isundefarg(val))
+ set80211(s, IEEE80211_IOC_WEPTXKEY, IEEE80211_KEYIX_NONE, 0, NULL);
+ else
+ set80211(s, IEEE80211_IOC_WEPTXKEY, atoi(val)-1, 0, NULL);
+}
+
+static void
+set80211wepkey(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ int key = 0;
+ int len;
+ u_int8_t data[IEEE80211_KEYBUF_SIZE];
+
+ if (isdigit((int)val[0]) && val[1] == ':') {
+ key = atoi(val)-1;
+ val += 2;
+ }
+
+ bzero(data, sizeof(data));
+ len = sizeof(data);
+ get_string(val, NULL, data, &len);
+
+ set80211(s, IEEE80211_IOC_WEPKEY, key, len, data);
+}
+
+/*
+ * This function is purely a NetBSD compatibility interface. The NetBSD
+ * interface is too inflexible, but it's there so we'll support it since
+ * it's not all that hard.
+ */
+static void
+set80211nwkey(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ int txkey;
+ int i, len;
+ u_int8_t data[IEEE80211_KEYBUF_SIZE];
+
+ set80211(s, IEEE80211_IOC_WEP, IEEE80211_WEP_ON, 0, NULL);
+
+ if (isdigit((int)val[0]) && val[1] == ':') {
+ txkey = val[0]-'0'-1;
+ val += 2;
+
+ for (i = 0; i < 4; i++) {
+ bzero(data, sizeof(data));
+ len = sizeof(data);
+ val = get_string(val, ",", data, &len);
+ if (val == NULL)
+ exit(1);
+
+ set80211(s, IEEE80211_IOC_WEPKEY, i, len, data);
+ }
+ } else {
+ bzero(data, sizeof(data));
+ len = sizeof(data);
+ get_string(val, NULL, data, &len);
+ txkey = 0;
+
+ set80211(s, IEEE80211_IOC_WEPKEY, 0, len, data);
+
+ bzero(data, sizeof(data));
+ for (i = 1; i < 4; i++)
+ set80211(s, IEEE80211_IOC_WEPKEY, i, 0, data);
+ }
+
+ set80211(s, IEEE80211_IOC_WEPTXKEY, txkey, 0, NULL);
+}
+
+static void
+set80211rtsthreshold(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ set80211(s, IEEE80211_IOC_RTSTHRESHOLD,
+ isundefarg(val) ? IEEE80211_RTS_MAX : atoi(val), 0, NULL);
+}
+
+static void
+set80211protmode(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ int mode;
+
+ if (strcasecmp(val, "off") == 0) {
+ mode = IEEE80211_PROTMODE_OFF;
+ } else if (strcasecmp(val, "cts") == 0) {
+ mode = IEEE80211_PROTMODE_CTS;
+ } else if (strncasecmp(val, "rtscts", 3) == 0) {
+ mode = IEEE80211_PROTMODE_RTSCTS;
+ } else {
+ errx(1, "unknown protection mode");
+ }
+
+ set80211(s, IEEE80211_IOC_PROTMODE, mode, 0, NULL);
+}
+
+static void
+set80211htprotmode(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ int mode;
+
+ if (strcasecmp(val, "off") == 0) {
+ mode = IEEE80211_PROTMODE_OFF;
+ } else if (strncasecmp(val, "rts", 3) == 0) {
+ mode = IEEE80211_PROTMODE_RTSCTS;
+ } else {
+ errx(1, "unknown protection mode");
+ }
+
+ set80211(s, IEEE80211_IOC_HTPROTMODE, mode, 0, NULL);
+}
+
+static void
+set80211txpower(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ double v = atof(val);
+ int txpow;
+
+ txpow = (int) (2*v);
+ if (txpow != 2*v)
+ errx(-1, "invalid tx power (must be .5 dBm units)");
+ set80211(s, IEEE80211_IOC_TXPOWER, txpow, 0, NULL);
+}
+
+#define IEEE80211_ROAMING_DEVICE 0
+#define IEEE80211_ROAMING_AUTO 1
+#define IEEE80211_ROAMING_MANUAL 2
+
+static void
+set80211roaming(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ int mode;
+
+ if (strcasecmp(val, "device") == 0) {
+ mode = IEEE80211_ROAMING_DEVICE;
+ } else if (strcasecmp(val, "auto") == 0) {
+ mode = IEEE80211_ROAMING_AUTO;
+ } else if (strcasecmp(val, "manual") == 0) {
+ mode = IEEE80211_ROAMING_MANUAL;
+ } else {
+ errx(1, "unknown roaming mode");
+ }
+ set80211(s, IEEE80211_IOC_ROAMING, mode, 0, NULL);
+}
+
+static void
+set80211wme(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ set80211(s, IEEE80211_IOC_WME, d, 0, NULL);
+}
+
+static void
+set80211hidessid(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ set80211(s, IEEE80211_IOC_HIDESSID, d, 0, NULL);
+}
+
+static void
+set80211apbridge(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ set80211(s, IEEE80211_IOC_APBRIDGE, d, 0, NULL);
+}
+
+static void
+set80211fastframes(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ set80211(s, IEEE80211_IOC_FF, d, 0, NULL);
+}
+
+static void
+set80211dturbo(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ set80211(s, IEEE80211_IOC_TURBOP, d, 0, NULL);
+}
+
+static void
+set80211chanlist(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ struct ieee80211req_chanlist chanlist;
+ char *temp, *cp, *tp;
+
+ temp = malloc(strlen(val) + 1);
+ if (temp == NULL)
+ errx(1, "malloc failed");
+ strcpy(temp, val);
+ memset(&chanlist, 0, sizeof(chanlist));
+ cp = temp;
+ for (;;) {
+ int first, last, f, c;
+
+ tp = strchr(cp, ',');
+ if (tp != NULL)
+ *tp++ = '\0';
+ switch (sscanf(cp, "%u-%u", &first, &last)) {
+ case 1:
+ if (first > IEEE80211_CHAN_MAX)
+ errx(-1, "channel %u out of range, max %u",
+ first, IEEE80211_CHAN_MAX);
+ setbit(chanlist.ic_channels, first);
+ break;
+ case 2:
+ if (first > IEEE80211_CHAN_MAX)
+ errx(-1, "channel %u out of range, max %u",
+ first, IEEE80211_CHAN_MAX);
+ if (last > IEEE80211_CHAN_MAX)
+ errx(-1, "channel %u out of range, max %u",
+ last, IEEE80211_CHAN_MAX);
+ if (first > last)
+ errx(-1, "void channel range, %u > %u",
+ first, last);
+ for (f = first; f <= last; f++)
+ setbit(chanlist.ic_channels, f);
+ break;
+ }
+ if (tp == NULL)
+ break;
+ c = *tp;
+ while (isspace(c))
+ tp++;
+ if (!isdigit(c))
+ break;
+ cp = tp;
+ }
+ set80211(s, IEEE80211_IOC_CHANLIST, 0, sizeof(chanlist), &chanlist);
+}
+
+static void
+set80211bssid(const char *val, int d, int s, const struct afswtch *rafp)
+{
+
+ if (!isanyarg(val)) {
+ char *temp;
+ struct sockaddr_dl sdl;
+
+ temp = malloc(strlen(val) + 2); /* ':' and '\0' */
+ if (temp == NULL)
+ errx(1, "malloc failed");
+ temp[0] = ':';
+ strcpy(temp + 1, val);
+ sdl.sdl_len = sizeof(sdl);
+ link_addr(temp, &sdl);
+ free(temp);
+ if (sdl.sdl_alen != IEEE80211_ADDR_LEN)
+ errx(1, "malformed link-level address");
+ set80211(s, IEEE80211_IOC_BSSID, 0,
+ IEEE80211_ADDR_LEN, LLADDR(&sdl));
+ } else {
+ uint8_t zerobssid[IEEE80211_ADDR_LEN];
+ memset(zerobssid, 0, sizeof(zerobssid));
+ set80211(s, IEEE80211_IOC_BSSID, 0,
+ IEEE80211_ADDR_LEN, zerobssid);
+ }
+}
+
+static int
+getac(const char *ac)
+{
+ if (strcasecmp(ac, "ac_be") == 0 || strcasecmp(ac, "be") == 0)
+ return WME_AC_BE;
+ if (strcasecmp(ac, "ac_bk") == 0 || strcasecmp(ac, "bk") == 0)
+ return WME_AC_BK;
+ if (strcasecmp(ac, "ac_vi") == 0 || strcasecmp(ac, "vi") == 0)
+ return WME_AC_VI;
+ if (strcasecmp(ac, "ac_vo") == 0 || strcasecmp(ac, "vo") == 0)
+ return WME_AC_VO;
+ errx(1, "unknown wme access class %s", ac);
+}
+
+static
+DECL_CMD_FUNC2(set80211cwmin, ac, val)
+{
+ set80211(s, IEEE80211_IOC_WME_CWMIN, atoi(val), getac(ac), NULL);
+}
+
+static
+DECL_CMD_FUNC2(set80211cwmax, ac, val)
+{
+ set80211(s, IEEE80211_IOC_WME_CWMAX, atoi(val), getac(ac), NULL);
+}
+
+static
+DECL_CMD_FUNC2(set80211aifs, ac, val)
+{
+ set80211(s, IEEE80211_IOC_WME_AIFS, atoi(val), getac(ac), NULL);
+}
+
+static
+DECL_CMD_FUNC2(set80211txoplimit, ac, val)
+{
+ set80211(s, IEEE80211_IOC_WME_TXOPLIMIT, atoi(val), getac(ac), NULL);
+}
+
+static
+DECL_CMD_FUNC(set80211acm, ac, d)
+{
+ set80211(s, IEEE80211_IOC_WME_ACM, 1, getac(ac), NULL);
+}
+static
+DECL_CMD_FUNC(set80211noacm, ac, d)
+{
+ set80211(s, IEEE80211_IOC_WME_ACM, 0, getac(ac), NULL);
+}
+
+static
+DECL_CMD_FUNC(set80211ackpolicy, ac, d)
+{
+ set80211(s, IEEE80211_IOC_WME_ACKPOLICY, 1, getac(ac), NULL);
+}
+static
+DECL_CMD_FUNC(set80211noackpolicy, ac, d)
+{
+ set80211(s, IEEE80211_IOC_WME_ACKPOLICY, 0, getac(ac), NULL);
+}
+
+static
+DECL_CMD_FUNC2(set80211bsscwmin, ac, val)
+{
+ set80211(s, IEEE80211_IOC_WME_CWMIN, atoi(val),
+ getac(ac)|IEEE80211_WMEPARAM_BSS, NULL);
+}
+
+static
+DECL_CMD_FUNC2(set80211bsscwmax, ac, val)
+{
+ set80211(s, IEEE80211_IOC_WME_CWMAX, atoi(val),
+ getac(ac)|IEEE80211_WMEPARAM_BSS, NULL);
+}
+
+static
+DECL_CMD_FUNC2(set80211bssaifs, ac, val)
+{
+ set80211(s, IEEE80211_IOC_WME_AIFS, atoi(val),
+ getac(ac)|IEEE80211_WMEPARAM_BSS, NULL);
+}
+
+static
+DECL_CMD_FUNC2(set80211bsstxoplimit, ac, val)
+{
+ set80211(s, IEEE80211_IOC_WME_TXOPLIMIT, atoi(val),
+ getac(ac)|IEEE80211_WMEPARAM_BSS, NULL);
+}
+
+static
+DECL_CMD_FUNC(set80211dtimperiod, val, d)
+{
+ set80211(s, IEEE80211_IOC_DTIM_PERIOD, atoi(val), 0, NULL);
+}
+
+static
+DECL_CMD_FUNC(set80211bintval, val, d)
+{
+ set80211(s, IEEE80211_IOC_BEACON_INTERVAL, atoi(val), 0, NULL);
+}
+
+static void
+set80211macmac(int s, int op, const char *val)
+{
+ char *temp;
+ struct sockaddr_dl sdl;
+
+ temp = malloc(strlen(val) + 2); /* ':' and '\0' */
+ if (temp == NULL)
+ errx(1, "malloc failed");
+ temp[0] = ':';
+ strcpy(temp + 1, val);
+ sdl.sdl_len = sizeof(sdl);
+ link_addr(temp, &sdl);
+ free(temp);
+ if (sdl.sdl_alen != IEEE80211_ADDR_LEN)
+ errx(1, "malformed link-level address");
+ set80211(s, op, 0, IEEE80211_ADDR_LEN, LLADDR(&sdl));
+}
+
+static
+DECL_CMD_FUNC(set80211addmac, val, d)
+{
+ set80211macmac(s, IEEE80211_IOC_ADDMAC, val);
+}
+
+static
+DECL_CMD_FUNC(set80211delmac, val, d)
+{
+ set80211macmac(s, IEEE80211_IOC_DELMAC, val);
+}
+
+static
+DECL_CMD_FUNC(set80211kickmac, val, d)
+{
+ char *temp;
+ struct sockaddr_dl sdl;
+ struct ieee80211req_mlme mlme;
+
+ temp = malloc(strlen(val) + 2); /* ':' and '\0' */
+ if (temp == NULL)
+ errx(1, "malloc failed");
+ temp[0] = ':';
+ strcpy(temp + 1, val);
+ sdl.sdl_len = sizeof(sdl);
+ link_addr(temp, &sdl);
+ free(temp);
+ if (sdl.sdl_alen != IEEE80211_ADDR_LEN)
+ errx(1, "malformed link-level address");
+ memset(&mlme, 0, sizeof(mlme));
+ mlme.im_op = IEEE80211_MLME_DEAUTH;
+ mlme.im_reason = IEEE80211_REASON_AUTH_EXPIRE;
+ memcpy(mlme.im_macaddr, LLADDR(&sdl), IEEE80211_ADDR_LEN);
+ set80211(s, IEEE80211_IOC_MLME, 0, sizeof(mlme), &mlme);
+}
+
+static
+DECL_CMD_FUNC(set80211maccmd, val, d)
+{
+ set80211(s, IEEE80211_IOC_MACCMD, d, 0, NULL);
+}
+
+static void
+set80211meshrtmac(int s, int req, const char *val)
+{
+ char *temp;
+ struct sockaddr_dl sdl;
+
+ temp = malloc(strlen(val) + 2); /* ':' and '\0' */
+ if (temp == NULL)
+ errx(1, "malloc failed");
+ temp[0] = ':';
+ strcpy(temp + 1, val);
+ sdl.sdl_len = sizeof(sdl);
+ link_addr(temp, &sdl);
+ free(temp);
+ if (sdl.sdl_alen != IEEE80211_ADDR_LEN)
+ errx(1, "malformed link-level address");
+ set80211(s, IEEE80211_IOC_MESH_RTCMD, req,
+ IEEE80211_ADDR_LEN, LLADDR(&sdl));
+}
+
+static
+DECL_CMD_FUNC(set80211addmeshrt, val, d)
+{
+ set80211meshrtmac(s, IEEE80211_MESH_RTCMD_ADD, val);
+}
+
+static
+DECL_CMD_FUNC(set80211delmeshrt, val, d)
+{
+ set80211meshrtmac(s, IEEE80211_MESH_RTCMD_DELETE, val);
+}
+
+static
+DECL_CMD_FUNC(set80211meshrtcmd, val, d)
+{
+ set80211(s, IEEE80211_IOC_MESH_RTCMD, d, 0, NULL);
+}
+
+static
+DECL_CMD_FUNC(set80211hwmprootmode, val, d)
+{
+ int mode;
+
+ if (strcasecmp(val, "normal") == 0)
+ mode = IEEE80211_HWMP_ROOTMODE_NORMAL;
+ else if (strcasecmp(val, "proactive") == 0)
+ mode = IEEE80211_HWMP_ROOTMODE_PROACTIVE;
+ else if (strcasecmp(val, "rann") == 0)
+ mode = IEEE80211_HWMP_ROOTMODE_RANN;
+ else
+ mode = IEEE80211_HWMP_ROOTMODE_DISABLED;
+ set80211(s, IEEE80211_IOC_HWMP_ROOTMODE, mode, 0, NULL);
+}
+
+static
+DECL_CMD_FUNC(set80211hwmpmaxhops, val, d)
+{
+ set80211(s, IEEE80211_IOC_HWMP_MAXHOPS, atoi(val), 0, NULL);
+}
+
+static void
+set80211pureg(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ set80211(s, IEEE80211_IOC_PUREG, d, 0, NULL);
+}
+
+static void
+set80211quiet(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ set80211(s, IEEE80211_IOC_QUIET, d, 0, NULL);
+}
+
+static
+DECL_CMD_FUNC(set80211quietperiod, val, d)
+{
+ set80211(s, IEEE80211_IOC_QUIET_PERIOD, atoi(val), 0, NULL);
+}
+
+static
+DECL_CMD_FUNC(set80211quietcount, val, d)
+{
+ set80211(s, IEEE80211_IOC_QUIET_COUNT, atoi(val), 0, NULL);
+}
+
+static
+DECL_CMD_FUNC(set80211quietduration, val, d)
+{
+ set80211(s, IEEE80211_IOC_QUIET_DUR, atoi(val), 0, NULL);
+}
+
+static
+DECL_CMD_FUNC(set80211quietoffset, val, d)
+{
+ set80211(s, IEEE80211_IOC_QUIET_OFFSET, atoi(val), 0, NULL);
+}
+
+static void
+set80211bgscan(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ set80211(s, IEEE80211_IOC_BGSCAN, d, 0, NULL);
+}
+
+static
+DECL_CMD_FUNC(set80211bgscanidle, val, d)
+{
+ set80211(s, IEEE80211_IOC_BGSCAN_IDLE, atoi(val), 0, NULL);
+}
+
+static
+DECL_CMD_FUNC(set80211bgscanintvl, val, d)
+{
+ set80211(s, IEEE80211_IOC_BGSCAN_INTERVAL, atoi(val), 0, NULL);
+}
+
+static
+DECL_CMD_FUNC(set80211scanvalid, val, d)
+{
+ set80211(s, IEEE80211_IOC_SCANVALID, atoi(val), 0, NULL);
+}
+
+/*
+ * Parse an optional trailing specification of which netbands
+ * to apply a parameter to. This is basically the same syntax
+ * as used for channels but you can concatenate to specify
+ * multiple. For example:
+ * 14:abg apply to 11a, 11b, and 11g
+ * 6:ht apply to 11na and 11ng
+ * We don't make a big effort to catch silly things; this is
+ * really a convenience mechanism.
+ */
+static int
+getmodeflags(const char *val)
+{
+ const char *cp;
+ int flags;
+
+ flags = 0;
+
+ cp = strchr(val, ':');
+ if (cp != NULL) {
+ for (cp++; isalpha((int) *cp); cp++) {
+ /* accept mixed case */
+ int c = *cp;
+ if (isupper(c))
+ c = tolower(c);
+ switch (c) {
+ case 'a': /* 802.11a */
+ flags |= IEEE80211_CHAN_A;
+ break;
+ case 'b': /* 802.11b */
+ flags |= IEEE80211_CHAN_B;
+ break;
+ case 'g': /* 802.11g */
+ flags |= IEEE80211_CHAN_G;
+ break;
+ case 'n': /* 802.11n */
+ flags |= IEEE80211_CHAN_HT;
+ break;
+ case 'd': /* dt = Atheros Dynamic Turbo */
+ flags |= IEEE80211_CHAN_TURBO;
+ break;
+ case 't': /* ht, dt, st, t */
+ /* dt and unadorned t specify Dynamic Turbo */
+ if ((flags & (IEEE80211_CHAN_STURBO|IEEE80211_CHAN_HT)) == 0)
+ flags |= IEEE80211_CHAN_TURBO;
+ break;
+ case 's': /* st = Atheros Static Turbo */
+ flags |= IEEE80211_CHAN_STURBO;
+ break;
+ case 'h': /* 1/2-width channels */
+ flags |= IEEE80211_CHAN_HALF;
+ break;
+ case 'q': /* 1/4-width channels */
+ flags |= IEEE80211_CHAN_QUARTER;
+ break;
+ default:
+ errx(-1, "%s: Invalid mode attribute %c\n",
+ val, *cp);
+ }
+ }
+ }
+ return flags;
+}
+
+#define IEEE80211_CHAN_HTA (IEEE80211_CHAN_HT|IEEE80211_CHAN_5GHZ)
+#define IEEE80211_CHAN_HTG (IEEE80211_CHAN_HT|IEEE80211_CHAN_2GHZ)
+
+#define _APPLY(_flags, _base, _param, _v) do { \
+ if (_flags & IEEE80211_CHAN_HT) { \
+ if ((_flags & (IEEE80211_CHAN_5GHZ|IEEE80211_CHAN_2GHZ)) == 0) {\
+ _base.params[IEEE80211_MODE_11NA]._param = _v; \
+ _base.params[IEEE80211_MODE_11NG]._param = _v; \
+ } else if (_flags & IEEE80211_CHAN_5GHZ) \
+ _base.params[IEEE80211_MODE_11NA]._param = _v; \
+ else \
+ _base.params[IEEE80211_MODE_11NG]._param = _v; \
+ } \
+ if (_flags & IEEE80211_CHAN_TURBO) { \
+ if ((_flags & (IEEE80211_CHAN_5GHZ|IEEE80211_CHAN_2GHZ)) == 0) {\
+ _base.params[IEEE80211_MODE_TURBO_A]._param = _v; \
+ _base.params[IEEE80211_MODE_TURBO_G]._param = _v; \
+ } else if (_flags & IEEE80211_CHAN_5GHZ) \
+ _base.params[IEEE80211_MODE_TURBO_A]._param = _v; \
+ else \
+ _base.params[IEEE80211_MODE_TURBO_G]._param = _v; \
+ } \
+ if (_flags & IEEE80211_CHAN_STURBO) \
+ _base.params[IEEE80211_MODE_STURBO_A]._param = _v; \
+ if ((_flags & IEEE80211_CHAN_A) == IEEE80211_CHAN_A) \
+ _base.params[IEEE80211_MODE_11A]._param = _v; \
+ if ((_flags & IEEE80211_CHAN_G) == IEEE80211_CHAN_G) \
+ _base.params[IEEE80211_MODE_11G]._param = _v; \
+ if ((_flags & IEEE80211_CHAN_B) == IEEE80211_CHAN_B) \
+ _base.params[IEEE80211_MODE_11B]._param = _v; \
+ if (_flags & IEEE80211_CHAN_HALF) \
+ _base.params[IEEE80211_MODE_HALF]._param = _v; \
+ if (_flags & IEEE80211_CHAN_QUARTER) \
+ _base.params[IEEE80211_MODE_QUARTER]._param = _v; \
+} while (0)
+#define _APPLY1(_flags, _base, _param, _v) do { \
+ if (_flags & IEEE80211_CHAN_HT) { \
+ if (_flags & IEEE80211_CHAN_5GHZ) \
+ _base.params[IEEE80211_MODE_11NA]._param = _v; \
+ else \
+ _base.params[IEEE80211_MODE_11NG]._param = _v; \
+ } else if ((_flags & IEEE80211_CHAN_108A) == IEEE80211_CHAN_108A) \
+ _base.params[IEEE80211_MODE_TURBO_A]._param = _v; \
+ else if ((_flags & IEEE80211_CHAN_108G) == IEEE80211_CHAN_108G) \
+ _base.params[IEEE80211_MODE_TURBO_G]._param = _v; \
+ else if ((_flags & IEEE80211_CHAN_ST) == IEEE80211_CHAN_ST) \
+ _base.params[IEEE80211_MODE_STURBO_A]._param = _v; \
+ else if (_flags & IEEE80211_CHAN_HALF) \
+ _base.params[IEEE80211_MODE_HALF]._param = _v; \
+ else if (_flags & IEEE80211_CHAN_QUARTER) \
+ _base.params[IEEE80211_MODE_QUARTER]._param = _v; \
+ else if ((_flags & IEEE80211_CHAN_A) == IEEE80211_CHAN_A) \
+ _base.params[IEEE80211_MODE_11A]._param = _v; \
+ else if ((_flags & IEEE80211_CHAN_G) == IEEE80211_CHAN_G) \
+ _base.params[IEEE80211_MODE_11G]._param = _v; \
+ else if ((_flags & IEEE80211_CHAN_B) == IEEE80211_CHAN_B) \
+ _base.params[IEEE80211_MODE_11B]._param = _v; \
+} while (0)
+#define _APPLY_RATE(_flags, _base, _param, _v) do { \
+ if (_flags & IEEE80211_CHAN_HT) { \
+ (_v) = (_v / 2) | IEEE80211_RATE_MCS; \
+ } \
+ _APPLY(_flags, _base, _param, _v); \
+} while (0)
+#define _APPLY_RATE1(_flags, _base, _param, _v) do { \
+ if (_flags & IEEE80211_CHAN_HT) { \
+ (_v) = (_v / 2) | IEEE80211_RATE_MCS; \
+ } \
+ _APPLY1(_flags, _base, _param, _v); \
+} while (0)
+
+static
+DECL_CMD_FUNC(set80211roamrssi, val, d)
+{
+ double v = atof(val);
+ int rssi, flags;
+
+ rssi = (int) (2*v);
+ if (rssi != 2*v)
+ errx(-1, "invalid rssi (must be .5 dBm units)");
+ flags = getmodeflags(val);
+ getroam(s);
+ if (flags == 0) { /* NB: no flags => current channel */
+ flags = getcurchan(s)->ic_flags;
+ _APPLY1(flags, roamparams, rssi, rssi);
+ } else
+ _APPLY(flags, roamparams, rssi, rssi);
+ callback_register(setroam_cb, &roamparams);
+}
+
+static int
+getrate(const char *val, const char *tag)
+{
+ double v = atof(val);
+ int rate;
+
+ rate = (int) (2*v);
+ if (rate != 2*v)
+ errx(-1, "invalid %s rate (must be .5 Mb/s units)", tag);
+ return rate; /* NB: returns 2x the specified value */
+}
+
+static
+DECL_CMD_FUNC(set80211roamrate, val, d)
+{
+ int rate, flags;
+
+ rate = getrate(val, "roam");
+ flags = getmodeflags(val);
+ getroam(s);
+ if (flags == 0) { /* NB: no flags => current channel */
+ flags = getcurchan(s)->ic_flags;
+ _APPLY_RATE1(flags, roamparams, rate, rate);
+ } else
+ _APPLY_RATE(flags, roamparams, rate, rate);
+ callback_register(setroam_cb, &roamparams);
+}
+
+static
+DECL_CMD_FUNC(set80211mcastrate, val, d)
+{
+ int rate, flags;
+
+ rate = getrate(val, "mcast");
+ flags = getmodeflags(val);
+ gettxparams(s);
+ if (flags == 0) { /* NB: no flags => current channel */
+ flags = getcurchan(s)->ic_flags;
+ _APPLY_RATE1(flags, txparams, mcastrate, rate);
+ } else
+ _APPLY_RATE(flags, txparams, mcastrate, rate);
+ callback_register(settxparams_cb, &txparams);
+}
+
+static
+DECL_CMD_FUNC(set80211mgtrate, val, d)
+{
+ int rate, flags;
+
+ rate = getrate(val, "mgmt");
+ flags = getmodeflags(val);
+ gettxparams(s);
+ if (flags == 0) { /* NB: no flags => current channel */
+ flags = getcurchan(s)->ic_flags;
+ _APPLY_RATE1(flags, txparams, mgmtrate, rate);
+ } else
+ _APPLY_RATE(flags, txparams, mgmtrate, rate);
+ callback_register(settxparams_cb, &txparams);
+}
+
+static
+DECL_CMD_FUNC(set80211ucastrate, val, d)
+{
+ int flags;
+
+ gettxparams(s);
+ flags = getmodeflags(val);
+ if (isanyarg(val)) {
+ if (flags == 0) { /* NB: no flags => current channel */
+ flags = getcurchan(s)->ic_flags;
+ _APPLY1(flags, txparams, ucastrate,
+ IEEE80211_FIXED_RATE_NONE);
+ } else
+ _APPLY(flags, txparams, ucastrate,
+ IEEE80211_FIXED_RATE_NONE);
+ } else {
+ int rate = getrate(val, "ucast");
+ if (flags == 0) { /* NB: no flags => current channel */
+ flags = getcurchan(s)->ic_flags;
+ _APPLY_RATE1(flags, txparams, ucastrate, rate);
+ } else
+ _APPLY_RATE(flags, txparams, ucastrate, rate);
+ }
+ callback_register(settxparams_cb, &txparams);
+}
+
+static
+DECL_CMD_FUNC(set80211maxretry, val, d)
+{
+ int v = atoi(val), flags;
+
+ flags = getmodeflags(val);
+ gettxparams(s);
+ if (flags == 0) { /* NB: no flags => current channel */
+ flags = getcurchan(s)->ic_flags;
+ _APPLY1(flags, txparams, maxretry, v);
+ } else
+ _APPLY(flags, txparams, maxretry, v);
+ callback_register(settxparams_cb, &txparams);
+}
+#undef _APPLY_RATE
+#undef _APPLY
+#undef IEEE80211_CHAN_HTA
+#undef IEEE80211_CHAN_HTG
+
+static
+DECL_CMD_FUNC(set80211fragthreshold, val, d)
+{
+ set80211(s, IEEE80211_IOC_FRAGTHRESHOLD,
+ isundefarg(val) ? IEEE80211_FRAG_MAX : atoi(val), 0, NULL);
+}
+
+static
+DECL_CMD_FUNC(set80211bmissthreshold, val, d)
+{
+ set80211(s, IEEE80211_IOC_BMISSTHRESHOLD,
+ isundefarg(val) ? IEEE80211_HWBMISS_MAX : atoi(val), 0, NULL);
+}
+
+static void
+set80211burst(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ set80211(s, IEEE80211_IOC_BURST, d, 0, NULL);
+}
+
+static void
+set80211doth(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ set80211(s, IEEE80211_IOC_DOTH, d, 0, NULL);
+}
+
+static void
+set80211dfs(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ set80211(s, IEEE80211_IOC_DFS, d, 0, NULL);
+}
+
+static void
+set80211shortgi(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ set80211(s, IEEE80211_IOC_SHORTGI,
+ d ? (IEEE80211_HTCAP_SHORTGI20 | IEEE80211_HTCAP_SHORTGI40) : 0,
+ 0, NULL);
+}
+
+static void
+set80211ampdu(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ int ampdu;
+
+ if (get80211val(s, IEEE80211_IOC_AMPDU, &ampdu) < 0)
+ errx(-1, "cannot get AMPDU setting");
+ if (d < 0) {
+ d = -d;
+ ampdu &= ~d;
+ } else
+ ampdu |= d;
+ set80211(s, IEEE80211_IOC_AMPDU, ampdu, 0, NULL);
+}
+
+static
+DECL_CMD_FUNC(set80211ampdulimit, val, d)
+{
+ int v;
+
+ switch (atoi(val)) {
+ case 8:
+ case 8*1024:
+ v = IEEE80211_HTCAP_MAXRXAMPDU_8K;
+ break;
+ case 16:
+ case 16*1024:
+ v = IEEE80211_HTCAP_MAXRXAMPDU_16K;
+ break;
+ case 32:
+ case 32*1024:
+ v = IEEE80211_HTCAP_MAXRXAMPDU_32K;
+ break;
+ case 64:
+ case 64*1024:
+ v = IEEE80211_HTCAP_MAXRXAMPDU_64K;
+ break;
+ default:
+ errx(-1, "invalid A-MPDU limit %s", val);
+ }
+ set80211(s, IEEE80211_IOC_AMPDU_LIMIT, v, 0, NULL);
+}
+
+static
+DECL_CMD_FUNC(set80211ampdudensity, val, d)
+{
+ int v;
+
+ if (isanyarg(val) || strcasecmp(val, "na") == 0)
+ v = IEEE80211_HTCAP_MPDUDENSITY_NA;
+ else switch ((int)(atof(val)*4)) {
+ case 0:
+ v = IEEE80211_HTCAP_MPDUDENSITY_NA;
+ break;
+ case 1:
+ v = IEEE80211_HTCAP_MPDUDENSITY_025;
+ break;
+ case 2:
+ v = IEEE80211_HTCAP_MPDUDENSITY_05;
+ break;
+ case 4:
+ v = IEEE80211_HTCAP_MPDUDENSITY_1;
+ break;
+ case 8:
+ v = IEEE80211_HTCAP_MPDUDENSITY_2;
+ break;
+ case 16:
+ v = IEEE80211_HTCAP_MPDUDENSITY_4;
+ break;
+ case 32:
+ v = IEEE80211_HTCAP_MPDUDENSITY_8;
+ break;
+ case 64:
+ v = IEEE80211_HTCAP_MPDUDENSITY_16;
+ break;
+ default:
+ errx(-1, "invalid A-MPDU density %s", val);
+ }
+ set80211(s, IEEE80211_IOC_AMPDU_DENSITY, v, 0, NULL);
+}
+
+static void
+set80211amsdu(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ int amsdu;
+
+ if (get80211val(s, IEEE80211_IOC_AMSDU, &amsdu) < 0)
+ err(-1, "cannot get AMSDU setting");
+ if (d < 0) {
+ d = -d;
+ amsdu &= ~d;
+ } else
+ amsdu |= d;
+ set80211(s, IEEE80211_IOC_AMSDU, amsdu, 0, NULL);
+}
+
+static
+DECL_CMD_FUNC(set80211amsdulimit, val, d)
+{
+ set80211(s, IEEE80211_IOC_AMSDU_LIMIT, atoi(val), 0, NULL);
+}
+
+static void
+set80211puren(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ set80211(s, IEEE80211_IOC_PUREN, d, 0, NULL);
+}
+
+static void
+set80211htcompat(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ set80211(s, IEEE80211_IOC_HTCOMPAT, d, 0, NULL);
+}
+
+static void
+set80211htconf(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ set80211(s, IEEE80211_IOC_HTCONF, d, 0, NULL);
+ htconf = d;
+}
+
+static void
+set80211dwds(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ set80211(s, IEEE80211_IOC_DWDS, d, 0, NULL);
+}
+
+static void
+set80211inact(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ set80211(s, IEEE80211_IOC_INACTIVITY, d, 0, NULL);
+}
+
+static void
+set80211tsn(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ set80211(s, IEEE80211_IOC_TSN, d, 0, NULL);
+}
+
+static void
+set80211dotd(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ set80211(s, IEEE80211_IOC_DOTD, d, 0, NULL);
+}
+
+static void
+set80211smps(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ set80211(s, IEEE80211_IOC_SMPS, d, 0, NULL);
+}
+
+static void
+set80211rifs(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ set80211(s, IEEE80211_IOC_RIFS, d, 0, NULL);
+}
+
+static
+DECL_CMD_FUNC(set80211tdmaslot, val, d)
+{
+ set80211(s, IEEE80211_IOC_TDMA_SLOT, atoi(val), 0, NULL);
+}
+
+static
+DECL_CMD_FUNC(set80211tdmaslotcnt, val, d)
+{
+ set80211(s, IEEE80211_IOC_TDMA_SLOTCNT, atoi(val), 0, NULL);
+}
+
+static
+DECL_CMD_FUNC(set80211tdmaslotlen, val, d)
+{
+ set80211(s, IEEE80211_IOC_TDMA_SLOTLEN, atoi(val), 0, NULL);
+}
+
+static
+DECL_CMD_FUNC(set80211tdmabintval, val, d)
+{
+ set80211(s, IEEE80211_IOC_TDMA_BINTERVAL, atoi(val), 0, NULL);
+}
+
+static
+DECL_CMD_FUNC(set80211meshttl, val, d)
+{
+ set80211(s, IEEE80211_IOC_MESH_TTL, atoi(val), 0, NULL);
+}
+
+static
+DECL_CMD_FUNC(set80211meshforward, val, d)
+{
+ set80211(s, IEEE80211_IOC_MESH_FWRD, d, 0, NULL);
+}
+
+static
+DECL_CMD_FUNC(set80211meshgate, val, d)
+{
+ set80211(s, IEEE80211_IOC_MESH_GATE, d, 0, NULL);
+}
+
+static
+DECL_CMD_FUNC(set80211meshpeering, val, d)
+{
+ set80211(s, IEEE80211_IOC_MESH_AP, d, 0, NULL);
+}
+
+static
+DECL_CMD_FUNC(set80211meshmetric, val, d)
+{
+ char v[12];
+
+ memcpy(v, val, sizeof(v));
+ set80211(s, IEEE80211_IOC_MESH_PR_METRIC, 0, 0, v);
+}
+
+static
+DECL_CMD_FUNC(set80211meshpath, val, d)
+{
+ char v[12];
+
+ memcpy(v, val, sizeof(v));
+ set80211(s, IEEE80211_IOC_MESH_PR_PATH, 0, 0, v);
+}
+
+static int
+regdomain_sort(const void *a, const void *b)
+{
+#define CHAN_ALL \
+ (IEEE80211_CHAN_ALLTURBO|IEEE80211_CHAN_HALF|IEEE80211_CHAN_QUARTER)
+ const struct ieee80211_channel *ca = a;
+ const struct ieee80211_channel *cb = b;
+
+ return ca->ic_freq == cb->ic_freq ?
+ (ca->ic_flags & CHAN_ALL) - (cb->ic_flags & CHAN_ALL) :
+ ca->ic_freq - cb->ic_freq;
+#undef CHAN_ALL
+}
+
+static const struct ieee80211_channel *
+chanlookup(const struct ieee80211_channel chans[], int nchans,
+ int freq, int flags)
+{
+ int i;
+
+ flags &= IEEE80211_CHAN_ALLTURBO;
+ for (i = 0; i < nchans; i++) {
+ const struct ieee80211_channel *c = &chans[i];
+ if (c->ic_freq == freq &&
+ (c->ic_flags & IEEE80211_CHAN_ALLTURBO) == flags)
+ return c;
+ }
+ return NULL;
+}
+
+static int
+chanfind(const struct ieee80211_channel chans[], int nchans, int flags)
+{
+ int i;
+
+ for (i = 0; i < nchans; i++) {
+ const struct ieee80211_channel *c = &chans[i];
+ if ((c->ic_flags & flags) == flags)
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * Check channel compatibility.
+ */
+static int
+checkchan(const struct ieee80211req_chaninfo *avail, int freq, int flags)
+{
+ flags &= ~REQ_FLAGS;
+ /*
+ * Check if exact channel is in the calibration table;
+ * everything below is to deal with channels that we
+ * want to include but that are not explicitly listed.
+ */
+ if (flags & IEEE80211_CHAN_HT40) {
+ /* NB: we use an HT40 channel center that matches HT20 */
+ flags = (flags &~ IEEE80211_CHAN_HT40) | IEEE80211_CHAN_HT20;
+ }
+ if (chanlookup(avail->ic_chans, avail->ic_nchans, freq, flags) != NULL)
+ return 1;
+ if (flags & IEEE80211_CHAN_GSM) {
+ /*
+ * XXX GSM frequency mapping is handled in the kernel
+ * so we cannot find them in the calibration table;
+ * just accept the channel and the kernel will reject
+ * the channel list if it's wrong.
+ */
+ return 1;
+ }
+ /*
+ * If this is a 1/2 or 1/4 width channel allow it if a full
+ * width channel is present for this frequency, and the device
+ * supports fractional channels on this band. This is a hack
+ * that avoids bloating the calibration table; it may be better
+ * by per-band attributes though (we are effectively calculating
+ * this attribute by scanning the channel list ourself).
+ */
+ if ((flags & (IEEE80211_CHAN_HALF | IEEE80211_CHAN_QUARTER)) == 0)
+ return 0;
+ if (chanlookup(avail->ic_chans, avail->ic_nchans, freq,
+ flags &~ (IEEE80211_CHAN_HALF | IEEE80211_CHAN_QUARTER)) == NULL)
+ return 0;
+ if (flags & IEEE80211_CHAN_HALF) {
+ return chanfind(avail->ic_chans, avail->ic_nchans,
+ IEEE80211_CHAN_HALF |
+ (flags & (IEEE80211_CHAN_2GHZ | IEEE80211_CHAN_5GHZ)));
+ } else {
+ return chanfind(avail->ic_chans, avail->ic_nchans,
+ IEEE80211_CHAN_QUARTER |
+ (flags & (IEEE80211_CHAN_2GHZ | IEEE80211_CHAN_5GHZ)));
+ }
+}
+
+static void
+regdomain_addchans(struct ieee80211req_chaninfo *ci,
+ const netband_head *bands,
+ const struct ieee80211_regdomain *reg,
+ uint32_t chanFlags,
+ const struct ieee80211req_chaninfo *avail)
+{
+ const struct netband *nb;
+ const struct freqband *b;
+ struct ieee80211_channel *c, *prev;
+ int freq, hi_adj, lo_adj, channelSep;
+ uint32_t flags;
+
+ hi_adj = (chanFlags & IEEE80211_CHAN_HT40U) ? -20 : 0;
+ lo_adj = (chanFlags & IEEE80211_CHAN_HT40D) ? 20 : 0;
+ channelSep = (chanFlags & IEEE80211_CHAN_2GHZ) ? 0 : 40;
+ LIST_FOREACH(nb, bands, next) {
+ b = nb->band;
+ if (verbose) {
+ printf("%s:", __func__);
+ printb(" chanFlags", chanFlags, IEEE80211_CHAN_BITS);
+ printb(" bandFlags", nb->flags | b->flags,
+ IEEE80211_CHAN_BITS);
+ putchar('\n');
+ }
+ prev = NULL;
+ for (freq = b->freqStart + lo_adj;
+ freq <= b->freqEnd + hi_adj; freq += b->chanSep) {
+ /*
+ * Construct flags for the new channel. We take
+ * the attributes from the band descriptions except
+ * for HT40 which is enabled generically (i.e. +/-
+ * extension channel) in the band description and
+ * then constrained according by channel separation.
+ */
+ flags = nb->flags | b->flags;
+ if (flags & IEEE80211_CHAN_HT) {
+ /*
+ * HT channels are generated specially; we're
+ * called to add HT20, HT40+, and HT40- chan's
+ * so we need to expand only band specs for
+ * the HT channel type being added.
+ */
+ if ((chanFlags & IEEE80211_CHAN_HT20) &&
+ (flags & IEEE80211_CHAN_HT20) == 0) {
+ if (verbose)
+ printf("%u: skip, not an "
+ "HT20 channel\n", freq);
+ continue;
+ }
+ if ((chanFlags & IEEE80211_CHAN_HT40) &&
+ (flags & IEEE80211_CHAN_HT40) == 0) {
+ if (verbose)
+ printf("%u: skip, not an "
+ "HT40 channel\n", freq);
+ continue;
+ }
+ /* NB: HT attribute comes from caller */
+ flags &= ~IEEE80211_CHAN_HT;
+ flags |= chanFlags & IEEE80211_CHAN_HT;
+ }
+ /*
+ * Check if device can operate on this frequency.
+ */
+ if (!checkchan(avail, freq, flags)) {
+ if (verbose) {
+ printf("%u: skip, ", freq);
+ printb("flags", flags,
+ IEEE80211_CHAN_BITS);
+ printf(" not available\n");
+ }
+ continue;
+ }
+ if ((flags & REQ_ECM) && !reg->ecm) {
+ if (verbose)
+ printf("%u: skip, ECM channel\n", freq);
+ continue;
+ }
+ if ((flags & REQ_INDOOR) && reg->location == 'O') {
+ if (verbose)
+ printf("%u: skip, indoor channel\n",
+ freq);
+ continue;
+ }
+ if ((flags & REQ_OUTDOOR) && reg->location == 'I') {
+ if (verbose)
+ printf("%u: skip, outdoor channel\n",
+ freq);
+ continue;
+ }
+ if ((flags & IEEE80211_CHAN_HT40) &&
+ prev != NULL && (freq - prev->ic_freq) < channelSep) {
+ if (verbose)
+ printf("%u: skip, only %u channel "
+ "separation, need %d\n", freq,
+ freq - prev->ic_freq, channelSep);
+ continue;
+ }
+ if (ci->ic_nchans == IEEE80211_CHAN_MAX) {
+ if (verbose)
+ printf("%u: skip, channel table full\n",
+ freq);
+ break;
+ }
+ c = &ci->ic_chans[ci->ic_nchans++];
+ memset(c, 0, sizeof(*c));
+ c->ic_freq = freq;
+ c->ic_flags = flags;
+ if (c->ic_flags & IEEE80211_CHAN_DFS)
+ c->ic_maxregpower = nb->maxPowerDFS;
+ else
+ c->ic_maxregpower = nb->maxPower;
+ if (verbose) {
+ printf("[%3d] add freq %u ",
+ ci->ic_nchans-1, c->ic_freq);
+ printb("flags", c->ic_flags, IEEE80211_CHAN_BITS);
+ printf(" power %u\n", c->ic_maxregpower);
+ }
+ /* NB: kernel fills in other fields */
+ prev = c;
+ }
+ }
+}
+
+static void
+regdomain_makechannels(
+ struct ieee80211_regdomain_req *req,
+ const struct ieee80211_devcaps_req *dc)
+{
+ struct regdata *rdp = getregdata();
+ const struct country *cc;
+ const struct ieee80211_regdomain *reg = &req->rd;
+ struct ieee80211req_chaninfo *ci = &req->chaninfo;
+ const struct regdomain *rd;
+
+ /*
+ * Locate construction table for new channel list. We treat
+ * the regdomain/SKU as definitive so a country can be in
+ * multiple with different properties (e.g. US in FCC+FCC3).
+ * If no regdomain is specified then we fallback on the country
+ * code to find the associated regdomain since countries always
+ * belong to at least one regdomain.
+ */
+ if (reg->regdomain == 0) {
+ cc = lib80211_country_findbycc(rdp, reg->country);
+ if (cc == NULL)
+ errx(1, "internal error, country %d not found",
+ reg->country);
+ rd = cc->rd;
+ } else
+ rd = lib80211_regdomain_findbysku(rdp, reg->regdomain);
+ if (rd == NULL)
+ errx(1, "internal error, regdomain %d not found",
+ reg->regdomain);
+ if (rd->sku != SKU_DEBUG) {
+ /*
+ * regdomain_addchans incrememnts the channel count for
+ * each channel it adds so initialize ic_nchans to zero.
+ * Note that we know we have enough space to hold all possible
+ * channels because the devcaps list size was used to
+ * allocate our request.
+ */
+ ci->ic_nchans = 0;
+ if (!LIST_EMPTY(&rd->bands_11b))
+ regdomain_addchans(ci, &rd->bands_11b, reg,
+ IEEE80211_CHAN_B, &dc->dc_chaninfo);
+ if (!LIST_EMPTY(&rd->bands_11g))
+ regdomain_addchans(ci, &rd->bands_11g, reg,
+ IEEE80211_CHAN_G, &dc->dc_chaninfo);
+ if (!LIST_EMPTY(&rd->bands_11a))
+ regdomain_addchans(ci, &rd->bands_11a, reg,
+ IEEE80211_CHAN_A, &dc->dc_chaninfo);
+ if (!LIST_EMPTY(&rd->bands_11na) && dc->dc_htcaps != 0) {
+ regdomain_addchans(ci, &rd->bands_11na, reg,
+ IEEE80211_CHAN_A | IEEE80211_CHAN_HT20,
+ &dc->dc_chaninfo);
+ if (dc->dc_htcaps & IEEE80211_HTCAP_CHWIDTH40) {
+ regdomain_addchans(ci, &rd->bands_11na, reg,
+ IEEE80211_CHAN_A | IEEE80211_CHAN_HT40U,
+ &dc->dc_chaninfo);
+ regdomain_addchans(ci, &rd->bands_11na, reg,
+ IEEE80211_CHAN_A | IEEE80211_CHAN_HT40D,
+ &dc->dc_chaninfo);
+ }
+ }
+ if (!LIST_EMPTY(&rd->bands_11ng) && dc->dc_htcaps != 0) {
+ regdomain_addchans(ci, &rd->bands_11ng, reg,
+ IEEE80211_CHAN_G | IEEE80211_CHAN_HT20,
+ &dc->dc_chaninfo);
+ if (dc->dc_htcaps & IEEE80211_HTCAP_CHWIDTH40) {
+ regdomain_addchans(ci, &rd->bands_11ng, reg,
+ IEEE80211_CHAN_G | IEEE80211_CHAN_HT40U,
+ &dc->dc_chaninfo);
+ regdomain_addchans(ci, &rd->bands_11ng, reg,
+ IEEE80211_CHAN_G | IEEE80211_CHAN_HT40D,
+ &dc->dc_chaninfo);
+ }
+ }
+ qsort(ci->ic_chans, ci->ic_nchans, sizeof(ci->ic_chans[0]),
+ regdomain_sort);
+ } else
+ memcpy(ci, &dc->dc_chaninfo,
+ IEEE80211_CHANINFO_SPACE(&dc->dc_chaninfo));
+}
+
+static void
+list_countries(void)
+{
+ struct regdata *rdp = getregdata();
+ const struct country *cp;
+ const struct regdomain *dp;
+ int i;
+
+ i = 0;
+ printf("\nCountry codes:\n");
+ LIST_FOREACH(cp, &rdp->countries, next) {
+ printf("%2s %-15.15s%s", cp->isoname,
+ cp->name, ((i+1)%4) == 0 ? "\n" : " ");
+ i++;
+ }
+ i = 0;
+ printf("\nRegulatory domains:\n");
+ LIST_FOREACH(dp, &rdp->domains, next) {
+ printf("%-15.15s%s", dp->name, ((i+1)%4) == 0 ? "\n" : " ");
+ i++;
+ }
+ printf("\n");
+}
+
+static void
+defaultcountry(const struct regdomain *rd)
+{
+ struct regdata *rdp = getregdata();
+ const struct country *cc;
+
+ cc = lib80211_country_findbycc(rdp, rd->cc->code);
+ if (cc == NULL)
+ errx(1, "internal error, ISO country code %d not "
+ "defined for regdomain %s", rd->cc->code, rd->name);
+ regdomain.country = cc->code;
+ regdomain.isocc[0] = cc->isoname[0];
+ regdomain.isocc[1] = cc->isoname[1];
+}
+
+static
+DECL_CMD_FUNC(set80211regdomain, val, d)
+{
+ struct regdata *rdp = getregdata();
+ const struct regdomain *rd;
+
+ rd = lib80211_regdomain_findbyname(rdp, val);
+ if (rd == NULL) {
+ char *eptr;
+ long sku = strtol(val, &eptr, 0);
+
+ if (eptr != val)
+ rd = lib80211_regdomain_findbysku(rdp, sku);
+ if (eptr == val || rd == NULL)
+ errx(1, "unknown regdomain %s", val);
+ }
+ getregdomain(s);
+ regdomain.regdomain = rd->sku;
+ if (regdomain.country == 0 && rd->cc != NULL) {
+ /*
+ * No country code setup and there's a default
+ * one for this regdomain fill it in.
+ */
+ defaultcountry(rd);
+ }
+ callback_register(setregdomain_cb, &regdomain);
+}
+
+static
+DECL_CMD_FUNC(set80211country, val, d)
+{
+ struct regdata *rdp = getregdata();
+ const struct country *cc;
+
+ cc = lib80211_country_findbyname(rdp, val);
+ if (cc == NULL) {
+ char *eptr;
+ long code = strtol(val, &eptr, 0);
+
+ if (eptr != val)
+ cc = lib80211_country_findbycc(rdp, code);
+ if (eptr == val || cc == NULL)
+ errx(1, "unknown ISO country code %s", val);
+ }
+ getregdomain(s);
+ regdomain.regdomain = cc->rd->sku;
+ regdomain.country = cc->code;
+ regdomain.isocc[0] = cc->isoname[0];
+ regdomain.isocc[1] = cc->isoname[1];
+ callback_register(setregdomain_cb, &regdomain);
+}
+
+static void
+set80211location(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ getregdomain(s);
+ regdomain.location = d;
+ callback_register(setregdomain_cb, &regdomain);
+}
+
+static void
+set80211ecm(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ getregdomain(s);
+ regdomain.ecm = d;
+ callback_register(setregdomain_cb, &regdomain);
+}
+
+static void
+LINE_INIT(char c)
+{
+ spacer = c;
+ if (c == '\t')
+ col = 8;
+ else
+ col = 1;
+}
+
+static void
+LINE_BREAK(void)
+{
+ if (spacer != '\t') {
+ printf("\n");
+ spacer = '\t';
+ }
+ col = 8; /* 8-col tab */
+}
+
+static void
+LINE_CHECK(const char *fmt, ...)
+{
+ char buf[80];
+ va_list ap;
+ int n;
+
+ va_start(ap, fmt);
+ n = vsnprintf(buf+1, sizeof(buf)-1, fmt, ap);
+ va_end(ap);
+ col += 1+n;
+ if (col > MAXCOL) {
+ LINE_BREAK();
+ col += n;
+ }
+ buf[0] = spacer;
+ printf("%s", buf);
+ spacer = ' ';
+}
+
+static int
+getmaxrate(const uint8_t rates[15], uint8_t nrates)
+{
+ int i, maxrate = -1;
+
+ for (i = 0; i < nrates; i++) {
+ int rate = rates[i] & IEEE80211_RATE_VAL;
+ if (rate > maxrate)
+ maxrate = rate;
+ }
+ return maxrate / 2;
+}
+
+static const char *
+getcaps(int capinfo)
+{
+ static char capstring[32];
+ char *cp = capstring;
+
+ if (capinfo & IEEE80211_CAPINFO_ESS)
+ *cp++ = 'E';
+ if (capinfo & IEEE80211_CAPINFO_IBSS)
+ *cp++ = 'I';
+ if (capinfo & IEEE80211_CAPINFO_CF_POLLABLE)
+ *cp++ = 'c';
+ if (capinfo & IEEE80211_CAPINFO_CF_POLLREQ)
+ *cp++ = 'C';
+ if (capinfo & IEEE80211_CAPINFO_PRIVACY)
+ *cp++ = 'P';
+ if (capinfo & IEEE80211_CAPINFO_SHORT_PREAMBLE)
+ *cp++ = 'S';
+ if (capinfo & IEEE80211_CAPINFO_PBCC)
+ *cp++ = 'B';
+ if (capinfo & IEEE80211_CAPINFO_CHNL_AGILITY)
+ *cp++ = 'A';
+ if (capinfo & IEEE80211_CAPINFO_SHORT_SLOTTIME)
+ *cp++ = 's';
+ if (capinfo & IEEE80211_CAPINFO_RSN)
+ *cp++ = 'R';
+ if (capinfo & IEEE80211_CAPINFO_DSSSOFDM)
+ *cp++ = 'D';
+ *cp = '\0';
+ return capstring;
+}
+
+static const char *
+getflags(int flags)
+{
+ static char flagstring[32];
+ char *cp = flagstring;
+
+ if (flags & IEEE80211_NODE_AUTH)
+ *cp++ = 'A';
+ if (flags & IEEE80211_NODE_QOS)
+ *cp++ = 'Q';
+ if (flags & IEEE80211_NODE_ERP)
+ *cp++ = 'E';
+ if (flags & IEEE80211_NODE_PWR_MGT)
+ *cp++ = 'P';
+ if (flags & IEEE80211_NODE_HT) {
+ *cp++ = 'H';
+ if (flags & IEEE80211_NODE_HTCOMPAT)
+ *cp++ = '+';
+ }
+ if (flags & IEEE80211_NODE_WPS)
+ *cp++ = 'W';
+ if (flags & IEEE80211_NODE_TSN)
+ *cp++ = 'N';
+ if (flags & IEEE80211_NODE_AMPDU_TX)
+ *cp++ = 'T';
+ if (flags & IEEE80211_NODE_AMPDU_RX)
+ *cp++ = 'R';
+ if (flags & IEEE80211_NODE_MIMO_PS) {
+ *cp++ = 'M';
+ if (flags & IEEE80211_NODE_MIMO_RTS)
+ *cp++ = '+';
+ }
+ if (flags & IEEE80211_NODE_RIFS)
+ *cp++ = 'I';
+ if (flags & IEEE80211_NODE_SGI40) {
+ *cp++ = 'S';
+ if (flags & IEEE80211_NODE_SGI20)
+ *cp++ = '+';
+ } else if (flags & IEEE80211_NODE_SGI20)
+ *cp++ = 's';
+ if (flags & IEEE80211_NODE_AMSDU_TX)
+ *cp++ = 't';
+ if (flags & IEEE80211_NODE_AMSDU_RX)
+ *cp++ = 'r';
+ *cp = '\0';
+ return flagstring;
+}
+
+static void
+printie(const char* tag, const uint8_t *ie, size_t ielen, int maxlen)
+{
+ printf("%s", tag);
+ if (verbose) {
+ maxlen -= strlen(tag)+2;
+ if (2*ielen > maxlen)
+ maxlen--;
+ printf("<");
+ for (; ielen > 0; ie++, ielen--) {
+ if (maxlen-- <= 0)
+ break;
+ printf("%02x", *ie);
+ }
+ if (ielen != 0)
+ printf("-");
+ printf(">");
+ }
+}
+
+#define LE_READ_2(p) \
+ ((u_int16_t) \
+ ((((const u_int8_t *)(p))[0] ) | \
+ (((const u_int8_t *)(p))[1] << 8)))
+#define LE_READ_4(p) \
+ ((u_int32_t) \
+ ((((const u_int8_t *)(p))[0] ) | \
+ (((const u_int8_t *)(p))[1] << 8) | \
+ (((const u_int8_t *)(p))[2] << 16) | \
+ (((const u_int8_t *)(p))[3] << 24)))
+
+/*
+ * NB: The decoding routines assume a properly formatted ie
+ * which should be safe as the kernel only retains them
+ * if they parse ok.
+ */
+
+static void
+printwmeparam(const char *tag, const u_int8_t *ie, size_t ielen, int maxlen)
+{
+#define MS(_v, _f) (((_v) & _f) >> _f##_S)
+ static const char *acnames[] = { "BE", "BK", "VO", "VI" };
+ const struct ieee80211_wme_param *wme =
+ (const struct ieee80211_wme_param *) ie;
+ int i;
+
+ printf("%s", tag);
+ if (!verbose)
+ return;
+ printf("<qosinfo 0x%x", wme->param_qosInfo);
+ ie += offsetof(struct ieee80211_wme_param, params_acParams);
+ for (i = 0; i < WME_NUM_AC; i++) {
+ const struct ieee80211_wme_acparams *ac =
+ &wme->params_acParams[i];
+
+ printf(" %s[%saifsn %u cwmin %u cwmax %u txop %u]"
+ , acnames[i]
+ , MS(ac->acp_aci_aifsn, WME_PARAM_ACM) ? "acm " : ""
+ , MS(ac->acp_aci_aifsn, WME_PARAM_AIFSN)
+ , MS(ac->acp_logcwminmax, WME_PARAM_LOGCWMIN)
+ , MS(ac->acp_logcwminmax, WME_PARAM_LOGCWMAX)
+ , LE_READ_2(&ac->acp_txop)
+ );
+ }
+ printf(">");
+#undef MS
+}
+
+static void
+printwmeinfo(const char *tag, const u_int8_t *ie, size_t ielen, int maxlen)
+{
+ printf("%s", tag);
+ if (verbose) {
+ const struct ieee80211_wme_info *wme =
+ (const struct ieee80211_wme_info *) ie;
+ printf("<version 0x%x info 0x%x>",
+ wme->wme_version, wme->wme_info);
+ }
+}
+
+static void
+printhtcap(const char *tag, const u_int8_t *ie, size_t ielen, int maxlen)
+{
+ printf("%s", tag);
+ if (verbose) {
+ const struct ieee80211_ie_htcap *htcap =
+ (const struct ieee80211_ie_htcap *) ie;
+ const char *sep;
+ int i, j;
+
+ printf("<cap 0x%x param 0x%x",
+ LE_READ_2(&htcap->hc_cap), htcap->hc_param);
+ printf(" mcsset[");
+ sep = "";
+ for (i = 0; i < IEEE80211_HTRATE_MAXSIZE; i++)
+ if (isset(htcap->hc_mcsset, i)) {
+ for (j = i+1; j < IEEE80211_HTRATE_MAXSIZE; j++)
+ if (isclr(htcap->hc_mcsset, j))
+ break;
+ j--;
+ if (i == j)
+ printf("%s%u", sep, i);
+ else
+ printf("%s%u-%u", sep, i, j);
+ i += j-i;
+ sep = ",";
+ }
+ printf("] extcap 0x%x txbf 0x%x antenna 0x%x>",
+ LE_READ_2(&htcap->hc_extcap),
+ LE_READ_4(&htcap->hc_txbf),
+ htcap->hc_antenna);
+ }
+}
+
+static void
+printhtinfo(const char *tag, const u_int8_t *ie, size_t ielen, int maxlen)
+{
+ printf("%s", tag);
+ if (verbose) {
+ const struct ieee80211_ie_htinfo *htinfo =
+ (const struct ieee80211_ie_htinfo *) ie;
+ const char *sep;
+ int i, j;
+
+ printf("<ctl %u, %x,%x,%x,%x", htinfo->hi_ctrlchannel,
+ htinfo->hi_byte1, htinfo->hi_byte2, htinfo->hi_byte3,
+ LE_READ_2(&htinfo->hi_byte45));
+ printf(" basicmcs[");
+ sep = "";
+ for (i = 0; i < IEEE80211_HTRATE_MAXSIZE; i++)
+ if (isset(htinfo->hi_basicmcsset, i)) {
+ for (j = i+1; j < IEEE80211_HTRATE_MAXSIZE; j++)
+ if (isclr(htinfo->hi_basicmcsset, j))
+ break;
+ j--;
+ if (i == j)
+ printf("%s%u", sep, i);
+ else
+ printf("%s%u-%u", sep, i, j);
+ i += j-i;
+ sep = ",";
+ }
+ printf("]>");
+ }
+}
+
+static void
+printathie(const char *tag, const u_int8_t *ie, size_t ielen, int maxlen)
+{
+
+ printf("%s", tag);
+ if (verbose) {
+ const struct ieee80211_ath_ie *ath =
+ (const struct ieee80211_ath_ie *)ie;
+
+ printf("<");
+ if (ath->ath_capability & ATHEROS_CAP_TURBO_PRIME)
+ printf("DTURBO,");
+ if (ath->ath_capability & ATHEROS_CAP_COMPRESSION)
+ printf("COMP,");
+ if (ath->ath_capability & ATHEROS_CAP_FAST_FRAME)
+ printf("FF,");
+ if (ath->ath_capability & ATHEROS_CAP_XR)
+ printf("XR,");
+ if (ath->ath_capability & ATHEROS_CAP_AR)
+ printf("AR,");
+ if (ath->ath_capability & ATHEROS_CAP_BURST)
+ printf("BURST,");
+ if (ath->ath_capability & ATHEROS_CAP_WME)
+ printf("WME,");
+ if (ath->ath_capability & ATHEROS_CAP_BOOST)
+ printf("BOOST,");
+ printf("0x%x>", LE_READ_2(ath->ath_defkeyix));
+ }
+}
+
+
+static void
+printmeshconf(const char *tag, const uint8_t *ie, size_t ielen, int maxlen)
+{
+#define MATCHOUI(field, oui, string) \
+do { \
+ if (memcmp(field, oui, 4) == 0) \
+ printf("%s", string); \
+} while (0)
+
+ printf("%s", tag);
+ if (verbose) {
+ const struct ieee80211_meshconf_ie *mconf =
+ (const struct ieee80211_meshconf_ie *)ie;
+ printf("<PATH:");
+ if (mconf->conf_pselid == IEEE80211_MESHCONF_PATH_HWMP)
+ printf("HWMP");
+ else
+ printf("UNKNOWN");
+ printf(" LINK:");
+ if (mconf->conf_pmetid == IEEE80211_MESHCONF_METRIC_AIRTIME)
+ printf("AIRTIME");
+ else
+ printf("UNKNOWN");
+ printf(" CONGESTION:");
+ if (mconf->conf_ccid == IEEE80211_MESHCONF_CC_DISABLED)
+ printf("DISABLED");
+ else
+ printf("UNKNOWN");
+ printf(" SYNC:");
+ if (mconf->conf_syncid == IEEE80211_MESHCONF_SYNC_NEIGHOFF)
+ printf("NEIGHOFF");
+ else
+ printf("UNKNOWN");
+ printf(" AUTH:");
+ if (mconf->conf_authid == IEEE80211_MESHCONF_AUTH_DISABLED)
+ printf("DISABLED");
+ else
+ printf("UNKNOWN");
+ printf(" FORM:0x%x CAPS:0x%x>", mconf->conf_form,
+ mconf->conf_cap);
+ }
+#undef MATCHOUI
+}
+
+static const char *
+wpa_cipher(const u_int8_t *sel)
+{
+#define WPA_SEL(x) (((x)<<24)|WPA_OUI)
+ u_int32_t w = LE_READ_4(sel);
+
+ switch (w) {
+ case WPA_SEL(WPA_CSE_NULL):
+ return "NONE";
+ case WPA_SEL(WPA_CSE_WEP40):
+ return "WEP40";
+ case WPA_SEL(WPA_CSE_WEP104):
+ return "WEP104";
+ case WPA_SEL(WPA_CSE_TKIP):
+ return "TKIP";
+ case WPA_SEL(WPA_CSE_CCMP):
+ return "AES-CCMP";
+ }
+ return "?"; /* NB: so 1<< is discarded */
+#undef WPA_SEL
+}
+
+static const char *
+wpa_keymgmt(const u_int8_t *sel)
+{
+#define WPA_SEL(x) (((x)<<24)|WPA_OUI)
+ u_int32_t w = LE_READ_4(sel);
+
+ switch (w) {
+ case WPA_SEL(WPA_ASE_8021X_UNSPEC):
+ return "8021X-UNSPEC";
+ case WPA_SEL(WPA_ASE_8021X_PSK):
+ return "8021X-PSK";
+ case WPA_SEL(WPA_ASE_NONE):
+ return "NONE";
+ }
+ return "?";
+#undef WPA_SEL
+}
+
+static void
+printwpaie(const char *tag, const u_int8_t *ie, size_t ielen, int maxlen)
+{
+ u_int8_t len = ie[1];
+
+ printf("%s", tag);
+ if (verbose) {
+ const char *sep;
+ int n;
+
+ ie += 6, len -= 4; /* NB: len is payload only */
+
+ printf("<v%u", LE_READ_2(ie));
+ ie += 2, len -= 2;
+
+ printf(" mc:%s", wpa_cipher(ie));
+ ie += 4, len -= 4;
+
+ /* unicast ciphers */
+ n = LE_READ_2(ie);
+ ie += 2, len -= 2;
+ sep = " uc:";
+ for (; n > 0; n--) {
+ printf("%s%s", sep, wpa_cipher(ie));
+ ie += 4, len -= 4;
+ sep = "+";
+ }
+
+ /* key management algorithms */
+ n = LE_READ_2(ie);
+ ie += 2, len -= 2;
+ sep = " km:";
+ for (; n > 0; n--) {
+ printf("%s%s", sep, wpa_keymgmt(ie));
+ ie += 4, len -= 4;
+ sep = "+";
+ }
+
+ if (len > 2) /* optional capabilities */
+ printf(", caps 0x%x", LE_READ_2(ie));
+ printf(">");
+ }
+}
+
+static const char *
+rsn_cipher(const u_int8_t *sel)
+{
+#define RSN_SEL(x) (((x)<<24)|RSN_OUI)
+ u_int32_t w = LE_READ_4(sel);
+
+ switch (w) {
+ case RSN_SEL(RSN_CSE_NULL):
+ return "NONE";
+ case RSN_SEL(RSN_CSE_WEP40):
+ return "WEP40";
+ case RSN_SEL(RSN_CSE_WEP104):
+ return "WEP104";
+ case RSN_SEL(RSN_CSE_TKIP):
+ return "TKIP";
+ case RSN_SEL(RSN_CSE_CCMP):
+ return "AES-CCMP";
+ case RSN_SEL(RSN_CSE_WRAP):
+ return "AES-OCB";
+ }
+ return "?";
+#undef WPA_SEL
+}
+
+static const char *
+rsn_keymgmt(const u_int8_t *sel)
+{
+#define RSN_SEL(x) (((x)<<24)|RSN_OUI)
+ u_int32_t w = LE_READ_4(sel);
+
+ switch (w) {
+ case RSN_SEL(RSN_ASE_8021X_UNSPEC):
+ return "8021X-UNSPEC";
+ case RSN_SEL(RSN_ASE_8021X_PSK):
+ return "8021X-PSK";
+ case RSN_SEL(RSN_ASE_NONE):
+ return "NONE";
+ }
+ return "?";
+#undef RSN_SEL
+}
+
+static void
+printrsnie(const char *tag, const u_int8_t *ie, size_t ielen, int maxlen)
+{
+ printf("%s", tag);
+ if (verbose) {
+ const char *sep;
+ int n;
+
+ ie += 2, ielen -= 2;
+
+ printf("<v%u", LE_READ_2(ie));
+ ie += 2, ielen -= 2;
+
+ printf(" mc:%s", rsn_cipher(ie));
+ ie += 4, ielen -= 4;
+
+ /* unicast ciphers */
+ n = LE_READ_2(ie);
+ ie += 2, ielen -= 2;
+ sep = " uc:";
+ for (; n > 0; n--) {
+ printf("%s%s", sep, rsn_cipher(ie));
+ ie += 4, ielen -= 4;
+ sep = "+";
+ }
+
+ /* key management algorithms */
+ n = LE_READ_2(ie);
+ ie += 2, ielen -= 2;
+ sep = " km:";
+ for (; n > 0; n--) {
+ printf("%s%s", sep, rsn_keymgmt(ie));
+ ie += 4, ielen -= 4;
+ sep = "+";
+ }
+
+ if (ielen > 2) /* optional capabilities */
+ printf(", caps 0x%x", LE_READ_2(ie));
+ /* XXXPMKID */
+ printf(">");
+ }
+}
+
+/* XXX move to a public include file */
+#define IEEE80211_WPS_DEV_PASS_ID 0x1012
+#define IEEE80211_WPS_SELECTED_REG 0x1041
+#define IEEE80211_WPS_SETUP_STATE 0x1044
+#define IEEE80211_WPS_UUID_E 0x1047
+#define IEEE80211_WPS_VERSION 0x104a
+
+#define BE_READ_2(p) \
+ ((u_int16_t) \
+ ((((const u_int8_t *)(p))[1] ) | \
+ (((const u_int8_t *)(p))[0] << 8)))
+
+static void
+printwpsie(const char *tag, const u_int8_t *ie, size_t ielen, int maxlen)
+{
+#define N(a) (sizeof(a) / sizeof(a[0]))
+ u_int8_t len = ie[1];
+
+ printf("%s", tag);
+ if (verbose) {
+ static const char *dev_pass_id[] = {
+ "D", /* Default (PIN) */
+ "U", /* User-specified */
+ "M", /* Machine-specified */
+ "K", /* Rekey */
+ "P", /* PushButton */
+ "R" /* Registrar-specified */
+ };
+ int n;
+
+ ie +=6, len -= 4; /* NB: len is payload only */
+
+ /* WPS IE in Beacon and Probe Resp frames have different fields */
+ printf("<");
+ while (len) {
+ uint16_t tlv_type = BE_READ_2(ie);
+ uint16_t tlv_len = BE_READ_2(ie + 2);
+
+ ie += 4, len -= 4;
+
+ switch (tlv_type) {
+ case IEEE80211_WPS_VERSION:
+ printf("v:%d.%d", *ie >> 4, *ie & 0xf);
+ break;
+ case IEEE80211_WPS_SETUP_STATE:
+ /* Only 1 and 2 are valid */
+ if (*ie == 0 || *ie >= 3)
+ printf(" state:B");
+ else
+ printf(" st:%s", *ie == 1 ? "N" : "C");
+ break;
+ case IEEE80211_WPS_SELECTED_REG:
+ printf(" sel:%s", *ie ? "T" : "F");
+ break;
+ case IEEE80211_WPS_DEV_PASS_ID:
+ n = LE_READ_2(ie);
+ if (n < N(dev_pass_id))
+ printf(" dpi:%s", dev_pass_id[n]);
+ break;
+ case IEEE80211_WPS_UUID_E:
+ printf(" uuid-e:");
+ for (n = 0; n < (tlv_len - 1); n++)
+ printf("%02x-", ie[n]);
+ printf("%02x", ie[n]);
+ break;
+ }
+ ie += tlv_len, len -= tlv_len;
+ }
+ printf(">");
+ }
+#undef N
+}
+
+static void
+printtdmaie(const char *tag, const u_int8_t *ie, size_t ielen, int maxlen)
+{
+ printf("%s", tag);
+ if (verbose && ielen >= sizeof(struct ieee80211_tdma_param)) {
+ const struct ieee80211_tdma_param *tdma =
+ (const struct ieee80211_tdma_param *) ie;
+
+ /* XXX tstamp */
+ printf("<v%u slot:%u slotcnt:%u slotlen:%u bintval:%u inuse:0x%x>",
+ tdma->tdma_version, tdma->tdma_slot, tdma->tdma_slotcnt,
+ LE_READ_2(&tdma->tdma_slotlen), tdma->tdma_bintval,
+ tdma->tdma_inuse[0]);
+ }
+}
+
+/*
+ * Copy the ssid string contents into buf, truncating to fit. If the
+ * ssid is entirely printable then just copy intact. Otherwise convert
+ * to hexadecimal. If the result is truncated then replace the last
+ * three characters with "...".
+ */
+static int
+copy_essid(char buf[], size_t bufsize, const u_int8_t *essid, size_t essid_len)
+{
+ const u_int8_t *p;
+ size_t maxlen;
+ int i;
+
+ if (essid_len > bufsize)
+ maxlen = bufsize;
+ else
+ maxlen = essid_len;
+ /* determine printable or not */
+ for (i = 0, p = essid; i < maxlen; i++, p++) {
+ if (*p < ' ' || *p > 0x7e)
+ break;
+ }
+ if (i != maxlen) { /* not printable, print as hex */
+ if (bufsize < 3)
+ return 0;
+ strlcpy(buf, "0x", bufsize);
+ bufsize -= 2;
+ p = essid;
+ for (i = 0; i < maxlen && bufsize >= 2; i++) {
+ sprintf(&buf[2+2*i], "%02x", p[i]);
+ bufsize -= 2;
+ }
+ if (i != essid_len)
+ memcpy(&buf[2+2*i-3], "...", 3);
+ } else { /* printable, truncate as needed */
+ memcpy(buf, essid, maxlen);
+ if (maxlen != essid_len)
+ memcpy(&buf[maxlen-3], "...", 3);
+ }
+ return maxlen;
+}
+
+static void
+printssid(const char *tag, const u_int8_t *ie, size_t ielen, int maxlen)
+{
+ char ssid[2*IEEE80211_NWID_LEN+1];
+
+ printf("%s<%.*s>", tag, copy_essid(ssid, maxlen, ie+2, ie[1]), ssid);
+}
+
+static void
+printrates(const char *tag, const u_int8_t *ie, size_t ielen, int maxlen)
+{
+ const char *sep;
+ int i;
+
+ printf("%s", tag);
+ sep = "<";
+ for (i = 2; i < ielen; i++) {
+ printf("%s%s%d", sep,
+ ie[i] & IEEE80211_RATE_BASIC ? "B" : "",
+ ie[i] & IEEE80211_RATE_VAL);
+ sep = ",";
+ }
+ printf(">");
+}
+
+static void
+printcountry(const char *tag, const u_int8_t *ie, size_t ielen, int maxlen)
+{
+ const struct ieee80211_country_ie *cie =
+ (const struct ieee80211_country_ie *) ie;
+ int i, nbands, schan, nchan;
+
+ printf("%s<%c%c%c", tag, cie->cc[0], cie->cc[1], cie->cc[2]);
+ nbands = (cie->len - 3) / sizeof(cie->band[0]);
+ for (i = 0; i < nbands; i++) {
+ schan = cie->band[i].schan;
+ nchan = cie->band[i].nchan;
+ if (nchan != 1)
+ printf(" %u-%u,%u", schan, schan + nchan-1,
+ cie->band[i].maxtxpwr);
+ else
+ printf(" %u,%u", schan, cie->band[i].maxtxpwr);
+ }
+ printf(">");
+}
+
+/* unaligned little endian access */
+#define LE_READ_4(p) \
+ ((u_int32_t) \
+ ((((const u_int8_t *)(p))[0] ) | \
+ (((const u_int8_t *)(p))[1] << 8) | \
+ (((const u_int8_t *)(p))[2] << 16) | \
+ (((const u_int8_t *)(p))[3] << 24)))
+
+static __inline int
+iswpaoui(const u_int8_t *frm)
+{
+ return frm[1] > 3 && LE_READ_4(frm+2) == ((WPA_OUI_TYPE<<24)|WPA_OUI);
+}
+
+static __inline int
+iswmeinfo(const u_int8_t *frm)
+{
+ return frm[1] > 5 && LE_READ_4(frm+2) == ((WME_OUI_TYPE<<24)|WME_OUI) &&
+ frm[6] == WME_INFO_OUI_SUBTYPE;
+}
+
+static __inline int
+iswmeparam(const u_int8_t *frm)
+{
+ return frm[1] > 5 && LE_READ_4(frm+2) == ((WME_OUI_TYPE<<24)|WME_OUI) &&
+ frm[6] == WME_PARAM_OUI_SUBTYPE;
+}
+
+static __inline int
+isatherosoui(const u_int8_t *frm)
+{
+ return frm[1] > 3 && LE_READ_4(frm+2) == ((ATH_OUI_TYPE<<24)|ATH_OUI);
+}
+
+static __inline int
+istdmaoui(const uint8_t *frm)
+{
+ return frm[1] > 3 && LE_READ_4(frm+2) == ((TDMA_OUI_TYPE<<24)|TDMA_OUI);
+}
+
+static __inline int
+iswpsoui(const uint8_t *frm)
+{
+ return frm[1] > 3 && LE_READ_4(frm+2) == ((WPS_OUI_TYPE<<24)|WPA_OUI);
+}
+
+static const char *
+iename(int elemid)
+{
+ switch (elemid) {
+ case IEEE80211_ELEMID_FHPARMS: return " FHPARMS";
+ case IEEE80211_ELEMID_CFPARMS: return " CFPARMS";
+ case IEEE80211_ELEMID_TIM: return " TIM";
+ case IEEE80211_ELEMID_IBSSPARMS:return " IBSSPARMS";
+ case IEEE80211_ELEMID_CHALLENGE:return " CHALLENGE";
+ case IEEE80211_ELEMID_PWRCNSTR: return " PWRCNSTR";
+ case IEEE80211_ELEMID_PWRCAP: return " PWRCAP";
+ case IEEE80211_ELEMID_TPCREQ: return " TPCREQ";
+ case IEEE80211_ELEMID_TPCREP: return " TPCREP";
+ case IEEE80211_ELEMID_SUPPCHAN: return " SUPPCHAN";
+ case IEEE80211_ELEMID_CSA: return " CSA";
+ case IEEE80211_ELEMID_MEASREQ: return " MEASREQ";
+ case IEEE80211_ELEMID_MEASREP: return " MEASREP";
+ case IEEE80211_ELEMID_QUIET: return " QUIET";
+ case IEEE80211_ELEMID_IBSSDFS: return " IBSSDFS";
+ case IEEE80211_ELEMID_TPC: return " TPC";
+ case IEEE80211_ELEMID_CCKM: return " CCKM";
+ }
+ return " ???";
+}
+
+static void
+printies(const u_int8_t *vp, int ielen, int maxcols)
+{
+ while (ielen > 0) {
+ switch (vp[0]) {
+ case IEEE80211_ELEMID_SSID:
+ if (verbose)
+ printssid(" SSID", vp, 2+vp[1], maxcols);
+ break;
+ case IEEE80211_ELEMID_RATES:
+ case IEEE80211_ELEMID_XRATES:
+ if (verbose)
+ printrates(vp[0] == IEEE80211_ELEMID_RATES ?
+ " RATES" : " XRATES", vp, 2+vp[1], maxcols);
+ break;
+ case IEEE80211_ELEMID_DSPARMS:
+ if (verbose)
+ printf(" DSPARMS<%u>", vp[2]);
+ break;
+ case IEEE80211_ELEMID_COUNTRY:
+ if (verbose)
+ printcountry(" COUNTRY", vp, 2+vp[1], maxcols);
+ break;
+ case IEEE80211_ELEMID_ERP:
+ if (verbose)
+ printf(" ERP<0x%x>", vp[2]);
+ break;
+ case IEEE80211_ELEMID_VENDOR:
+ if (iswpaoui(vp))
+ printwpaie(" WPA", vp, 2+vp[1], maxcols);
+ else if (iswmeinfo(vp))
+ printwmeinfo(" WME", vp, 2+vp[1], maxcols);
+ else if (iswmeparam(vp))
+ printwmeparam(" WME", vp, 2+vp[1], maxcols);
+ else if (isatherosoui(vp))
+ printathie(" ATH", vp, 2+vp[1], maxcols);
+ else if (iswpsoui(vp))
+ printwpsie(" WPS", vp, 2+vp[1], maxcols);
+ else if (istdmaoui(vp))
+ printtdmaie(" TDMA", vp, 2+vp[1], maxcols);
+ else if (verbose)
+ printie(" VEN", vp, 2+vp[1], maxcols);
+ break;
+ case IEEE80211_ELEMID_RSN:
+ printrsnie(" RSN", vp, 2+vp[1], maxcols);
+ break;
+ case IEEE80211_ELEMID_HTCAP:
+ printhtcap(" HTCAP", vp, 2+vp[1], maxcols);
+ break;
+ case IEEE80211_ELEMID_HTINFO:
+ if (verbose)
+ printhtinfo(" HTINFO", vp, 2+vp[1], maxcols);
+ break;
+ case IEEE80211_ELEMID_MESHID:
+ if (verbose)
+ printssid(" MESHID", vp, 2+vp[1], maxcols);
+ break;
+ case IEEE80211_ELEMID_MESHCONF:
+ printmeshconf(" MESHCONF", vp, 2+vp[1], maxcols);
+ break;
+ default:
+ if (verbose)
+ printie(iename(vp[0]), vp, 2+vp[1], maxcols);
+ break;
+ }
+ ielen -= 2+vp[1];
+ vp += 2+vp[1];
+ }
+}
+
+static void
+printmimo(const struct ieee80211_mimo_info *mi)
+{
+ /* NB: don't muddy display unless there's something to show */
+ if (mi->rssi[0] != 0 || mi->rssi[1] != 0 || mi->rssi[2] != 0) {
+ /* XXX ignore EVM for now */
+ printf(" (rssi %d:%d:%d nf %d:%d:%d)",
+ mi->rssi[0], mi->rssi[1], mi->rssi[2],
+ mi->noise[0], mi->noise[1], mi->noise[2]);
+ }
+}
+
+static void
+list_scan(int s)
+{
+ uint8_t buf[24*1024];
+ char ssid[IEEE80211_NWID_LEN+1];
+ const uint8_t *cp;
+ int len, ssidmax, idlen;
+
+ if (get80211len(s, IEEE80211_IOC_SCAN_RESULTS, buf, sizeof(buf), &len) < 0)
+ errx(1, "unable to get scan results");
+ if (len < sizeof(struct ieee80211req_scan_result))
+ return;
+
+ getchaninfo(s);
+
+ ssidmax = verbose ? IEEE80211_NWID_LEN - 1 : 14;
+ printf("%-*.*s %-17.17s %4s %4s %-7s %3s %4s\n"
+ , ssidmax, ssidmax, "SSID/MESH ID"
+ , "BSSID"
+ , "CHAN"
+ , "RATE"
+ , " S:N"
+ , "INT"
+ , "CAPS"
+ );
+ cp = buf;
+ do {
+ const struct ieee80211req_scan_result *sr;
+ const uint8_t *vp, *idp;
+
+ sr = (const struct ieee80211req_scan_result *) cp;
+ vp = cp + sr->isr_ie_off;
+ if (sr->isr_meshid_len) {
+ idp = vp + sr->isr_ssid_len;
+ idlen = sr->isr_meshid_len;
+ } else {
+ idp = vp;
+ idlen = sr->isr_ssid_len;
+ }
+ printf("%-*.*s %s %3d %3dM %3d:%-3d %3d %-4.4s"
+ , ssidmax
+ , copy_essid(ssid, ssidmax, idp, idlen)
+ , ssid
+ , ether_ntoa((const struct ether_addr *) sr->isr_bssid)
+ , ieee80211_mhz2ieee(sr->isr_freq, sr->isr_flags)
+ , getmaxrate(sr->isr_rates, sr->isr_nrates)
+ , (sr->isr_rssi/2)+sr->isr_noise, sr->isr_noise
+ , sr->isr_intval
+ , getcaps(sr->isr_capinfo)
+ );
+ printies(vp + sr->isr_ssid_len + sr->isr_meshid_len,
+ sr->isr_ie_len, 24);
+ printf("\n");
+ cp += sr->isr_len, len -= sr->isr_len;
+ } while (len >= sizeof(struct ieee80211req_scan_result));
+}
+
+static void
+scan_and_wait(int s)
+{
+ struct ieee80211_scan_req sr;
+ struct ieee80211req ireq;
+ int sroute;
+
+ sroute = socket(PF_ROUTE, SOCK_RAW, 0);
+ if (sroute < 0) {
+ perror("socket(PF_ROUTE,SOCK_RAW)");
+ return;
+ }
+ (void) memset(&ireq, 0, sizeof(ireq));
+ (void) strncpy(ireq.i_name, name, sizeof(ireq.i_name));
+ ireq.i_type = IEEE80211_IOC_SCAN_REQ;
+
+ memset(&sr, 0, sizeof(sr));
+ sr.sr_flags = IEEE80211_IOC_SCAN_ACTIVE
+ | IEEE80211_IOC_SCAN_BGSCAN
+ | IEEE80211_IOC_SCAN_NOPICK
+ | IEEE80211_IOC_SCAN_ONCE;
+ sr.sr_duration = IEEE80211_IOC_SCAN_FOREVER;
+ sr.sr_nssid = 0;
+
+ ireq.i_data = &sr;
+ ireq.i_len = sizeof(sr);
+ /*
+ * NB: only root can trigger a scan so ignore errors. Also ignore
+ * possible errors from net80211, even if no new scan could be
+ * started there might still be a valid scan cache.
+ */
+ if (ioctl(s, SIOCS80211, &ireq) == 0) {
+ char buf[2048];
+ struct if_announcemsghdr *ifan;
+ struct rt_msghdr *rtm;
+
+ do {
+ if (read(sroute, buf, sizeof(buf)) < 0) {
+ perror("read(PF_ROUTE)");
+ break;
+ }
+ rtm = (struct rt_msghdr *) buf;
+ if (rtm->rtm_version != RTM_VERSION)
+ break;
+ ifan = (struct if_announcemsghdr *) rtm;
+ } while (rtm->rtm_type != RTM_IEEE80211 ||
+ ifan->ifan_what != RTM_IEEE80211_SCAN);
+ }
+ close(sroute);
+}
+
+static
+DECL_CMD_FUNC(set80211scan, val, d)
+{
+ scan_and_wait(s);
+ list_scan(s);
+}
+
+static enum ieee80211_opmode get80211opmode(int s);
+
+static int
+gettxseq(const struct ieee80211req_sta_info *si)
+{
+ int i, txseq;
+
+ if ((si->isi_state & IEEE80211_NODE_QOS) == 0)
+ return si->isi_txseqs[0];
+ /* XXX not right but usually what folks want */
+ txseq = 0;
+ for (i = 0; i < IEEE80211_TID_SIZE; i++)
+ if (si->isi_txseqs[i] > txseq)
+ txseq = si->isi_txseqs[i];
+ return txseq;
+}
+
+static int
+getrxseq(const struct ieee80211req_sta_info *si)
+{
+ int i, rxseq;
+
+ if ((si->isi_state & IEEE80211_NODE_QOS) == 0)
+ return si->isi_rxseqs[0];
+ /* XXX not right but usually what folks want */
+ rxseq = 0;
+ for (i = 0; i < IEEE80211_TID_SIZE; i++)
+ if (si->isi_rxseqs[i] > rxseq)
+ rxseq = si->isi_rxseqs[i];
+ return rxseq;
+}
+
+static void
+list_stations(int s)
+{
+ union {
+ struct ieee80211req_sta_req req;
+ uint8_t buf[24*1024];
+ } u;
+ enum ieee80211_opmode opmode = get80211opmode(s);
+ const uint8_t *cp;
+ int len;
+
+ /* broadcast address =>'s get all stations */
+ (void) memset(u.req.is_u.macaddr, 0xff, IEEE80211_ADDR_LEN);
+ if (opmode == IEEE80211_M_STA) {
+ /*
+ * Get information about the associated AP.
+ */
+ (void) get80211(s, IEEE80211_IOC_BSSID,
+ u.req.is_u.macaddr, IEEE80211_ADDR_LEN);
+ }
+ if (get80211len(s, IEEE80211_IOC_STA_INFO, &u, sizeof(u), &len) < 0)
+ errx(1, "unable to get station information");
+ if (len < sizeof(struct ieee80211req_sta_info))
+ return;
+
+ getchaninfo(s);
+
+ if (opmode == IEEE80211_M_MBSS)
+ printf("%-17.17s %4s %5s %5s %7s %4s %4s %4s %6s %6s\n"
+ , "ADDR"
+ , "CHAN"
+ , "LOCAL"
+ , "PEER"
+ , "STATE"
+ , "RATE"
+ , "RSSI"
+ , "IDLE"
+ , "TXSEQ"
+ , "RXSEQ"
+ );
+ else
+ printf("%-17.17s %4s %4s %4s %4s %4s %6s %6s %4s %-7s\n"
+ , "ADDR"
+ , "AID"
+ , "CHAN"
+ , "RATE"
+ , "RSSI"
+ , "IDLE"
+ , "TXSEQ"
+ , "RXSEQ"
+ , "CAPS"
+ , "FLAG"
+ );
+ cp = (const uint8_t *) u.req.info;
+ do {
+ const struct ieee80211req_sta_info *si;
+
+ si = (const struct ieee80211req_sta_info *) cp;
+ if (si->isi_len < sizeof(*si))
+ break;
+ if (opmode == IEEE80211_M_MBSS)
+ printf("%s %4d %5x %5x %7.7s %3dM %4.1f %4d %6d %6d"
+ , ether_ntoa((const struct ether_addr*)
+ si->isi_macaddr)
+ , ieee80211_mhz2ieee(si->isi_freq,
+ si->isi_flags)
+ , si->isi_localid
+ , si->isi_peerid
+ , mesh_linkstate_string(si->isi_peerstate)
+ , si->isi_txmbps/2
+ , si->isi_rssi/2.
+ , si->isi_inact
+ , gettxseq(si)
+ , getrxseq(si)
+ );
+ else
+ printf("%s %4u %4d %3dM %4.1f %4d %6d %6d %-4.4s %-7.7s"
+ , ether_ntoa((const struct ether_addr*)
+ si->isi_macaddr)
+ , IEEE80211_AID(si->isi_associd)
+ , ieee80211_mhz2ieee(si->isi_freq,
+ si->isi_flags)
+ , si->isi_txmbps/2
+ , si->isi_rssi/2.
+ , si->isi_inact
+ , gettxseq(si)
+ , getrxseq(si)
+ , getcaps(si->isi_capinfo)
+ , getflags(si->isi_state)
+ );
+ printies(cp + si->isi_ie_off, si->isi_ie_len, 24);
+ printmimo(&si->isi_mimo);
+ printf("\n");
+ cp += si->isi_len, len -= si->isi_len;
+ } while (len >= sizeof(struct ieee80211req_sta_info));
+}
+
+static const char *
+mesh_linkstate_string(uint8_t state)
+{
+#define N(a) (sizeof(a) / sizeof(a[0]))
+ static const char *state_names[] = {
+ [0] = "IDLE",
+ [1] = "OPEN-TX",
+ [2] = "OPEN-RX",
+ [3] = "CONF-RX",
+ [4] = "ESTAB",
+ [5] = "HOLDING",
+ };
+
+ if (state >= N(state_names)) {
+ static char buf[10];
+ snprintf(buf, sizeof(buf), "#%u", state);
+ return buf;
+ } else
+ return state_names[state];
+#undef N
+}
+
+static const char *
+get_chaninfo(const struct ieee80211_channel *c, int precise,
+ char buf[], size_t bsize)
+{
+ buf[0] = '\0';
+ if (IEEE80211_IS_CHAN_FHSS(c))
+ strlcat(buf, " FHSS", bsize);
+ if (IEEE80211_IS_CHAN_A(c))
+ strlcat(buf, " 11a", bsize);
+ else if (IEEE80211_IS_CHAN_ANYG(c))
+ strlcat(buf, " 11g", bsize);
+ else if (IEEE80211_IS_CHAN_B(c))
+ strlcat(buf, " 11b", bsize);
+ if (IEEE80211_IS_CHAN_HALF(c))
+ strlcat(buf, "/10MHz", bsize);
+ if (IEEE80211_IS_CHAN_QUARTER(c))
+ strlcat(buf, "/5MHz", bsize);
+ if (IEEE80211_IS_CHAN_TURBO(c))
+ strlcat(buf, " Turbo", bsize);
+ if (precise) {
+ if (IEEE80211_IS_CHAN_HT20(c))
+ strlcat(buf, " ht/20", bsize);
+ else if (IEEE80211_IS_CHAN_HT40D(c))
+ strlcat(buf, " ht/40-", bsize);
+ else if (IEEE80211_IS_CHAN_HT40U(c))
+ strlcat(buf, " ht/40+", bsize);
+ } else {
+ if (IEEE80211_IS_CHAN_HT(c))
+ strlcat(buf, " ht", bsize);
+ }
+ return buf;
+}
+
+static void
+print_chaninfo(const struct ieee80211_channel *c, int verb)
+{
+ char buf[14];
+
+ if (verb)
+ printf("Channel %3u : %u%c%c%c%c%c MHz%-14.14s",
+ ieee80211_mhz2ieee(c->ic_freq, c->ic_flags), c->ic_freq,
+ IEEE80211_IS_CHAN_PASSIVE(c) ? '*' : ' ',
+ IEEE80211_IS_CHAN_DFS(c) ? 'D' : ' ',
+ IEEE80211_IS_CHAN_RADAR(c) ? 'R' : ' ',
+ IEEE80211_IS_CHAN_CWINT(c) ? 'I' : ' ',
+ IEEE80211_IS_CHAN_CACDONE(c) ? 'C' : ' ',
+ get_chaninfo(c, verb, buf, sizeof(buf)));
+ else
+ printf("Channel %3u : %u%c MHz%-14.14s",
+ ieee80211_mhz2ieee(c->ic_freq, c->ic_flags), c->ic_freq,
+ IEEE80211_IS_CHAN_PASSIVE(c) ? '*' : ' ',
+ get_chaninfo(c, verb, buf, sizeof(buf)));
+
+}
+
+static int
+chanpref(const struct ieee80211_channel *c)
+{
+ if (IEEE80211_IS_CHAN_HT40(c))
+ return 40;
+ if (IEEE80211_IS_CHAN_HT20(c))
+ return 30;
+ if (IEEE80211_IS_CHAN_HALF(c))
+ return 10;
+ if (IEEE80211_IS_CHAN_QUARTER(c))
+ return 5;
+ if (IEEE80211_IS_CHAN_TURBO(c))
+ return 25;
+ if (IEEE80211_IS_CHAN_A(c))
+ return 20;
+ if (IEEE80211_IS_CHAN_G(c))
+ return 20;
+ if (IEEE80211_IS_CHAN_B(c))
+ return 15;
+ if (IEEE80211_IS_CHAN_PUREG(c))
+ return 15;
+ return 0;
+}
+
+static void
+print_channels(int s, const struct ieee80211req_chaninfo *chans,
+ int allchans, int verb)
+{
+ struct ieee80211req_chaninfo *achans;
+ uint8_t reported[IEEE80211_CHAN_BYTES];
+ const struct ieee80211_channel *c;
+ int i, half;
+
+ achans = malloc(IEEE80211_CHANINFO_SPACE(chans));
+ if (achans == NULL)
+ errx(1, "no space for active channel list");
+ achans->ic_nchans = 0;
+ memset(reported, 0, sizeof(reported));
+ if (!allchans) {
+ struct ieee80211req_chanlist active;
+
+ if (get80211(s, IEEE80211_IOC_CHANLIST, &active, sizeof(active)) < 0)
+ errx(1, "unable to get active channel list");
+ for (i = 0; i < chans->ic_nchans; i++) {
+ c = &chans->ic_chans[i];
+ if (!isset(active.ic_channels, c->ic_ieee))
+ continue;
+ /*
+ * Suppress compatible duplicates unless
+ * verbose. The kernel gives us it's
+ * complete channel list which has separate
+ * entries for 11g/11b and 11a/turbo.
+ */
+ if (isset(reported, c->ic_ieee) && !verb) {
+ /* XXX we assume duplicates are adjacent */
+ achans->ic_chans[achans->ic_nchans-1] = *c;
+ } else {
+ achans->ic_chans[achans->ic_nchans++] = *c;
+ setbit(reported, c->ic_ieee);
+ }
+ }
+ } else {
+ for (i = 0; i < chans->ic_nchans; i++) {
+ c = &chans->ic_chans[i];
+ /* suppress duplicates as above */
+ if (isset(reported, c->ic_ieee) && !verb) {
+ /* XXX we assume duplicates are adjacent */
+ struct ieee80211_channel *a =
+ &achans->ic_chans[achans->ic_nchans-1];
+ if (chanpref(c) > chanpref(a))
+ *a = *c;
+ } else {
+ achans->ic_chans[achans->ic_nchans++] = *c;
+ setbit(reported, c->ic_ieee);
+ }
+ }
+ }
+ half = achans->ic_nchans / 2;
+ if (achans->ic_nchans % 2)
+ half++;
+
+ for (i = 0; i < achans->ic_nchans / 2; i++) {
+ print_chaninfo(&achans->ic_chans[i], verb);
+ print_chaninfo(&achans->ic_chans[half+i], verb);
+ printf("\n");
+ }
+ if (achans->ic_nchans % 2) {
+ print_chaninfo(&achans->ic_chans[i], verb);
+ printf("\n");
+ }
+ free(achans);
+}
+
+static void
+list_channels(int s, int allchans)
+{
+ getchaninfo(s);
+ print_channels(s, chaninfo, allchans, verbose);
+}
+
+static void
+print_txpow(const struct ieee80211_channel *c)
+{
+ printf("Channel %3u : %u MHz %3.1f reg %2d ",
+ c->ic_ieee, c->ic_freq,
+ c->ic_maxpower/2., c->ic_maxregpower);
+}
+
+static void
+print_txpow_verbose(const struct ieee80211_channel *c)
+{
+ print_chaninfo(c, 1);
+ printf("min %4.1f dBm max %3.1f dBm reg %2d dBm",
+ c->ic_minpower/2., c->ic_maxpower/2., c->ic_maxregpower);
+ /* indicate where regulatory cap limits power use */
+ if (c->ic_maxpower > 2*c->ic_maxregpower)
+ printf(" <");
+}
+
+static void
+list_txpow(int s)
+{
+ struct ieee80211req_chaninfo *achans;
+ uint8_t reported[IEEE80211_CHAN_BYTES];
+ struct ieee80211_channel *c, *prev;
+ int i, half;
+
+ getchaninfo(s);
+ achans = malloc(IEEE80211_CHANINFO_SPACE(chaninfo));
+ if (achans == NULL)
+ errx(1, "no space for active channel list");
+ achans->ic_nchans = 0;
+ memset(reported, 0, sizeof(reported));
+ for (i = 0; i < chaninfo->ic_nchans; i++) {
+ c = &chaninfo->ic_chans[i];
+ /* suppress duplicates as above */
+ if (isset(reported, c->ic_ieee) && !verbose) {
+ /* XXX we assume duplicates are adjacent */
+ prev = &achans->ic_chans[achans->ic_nchans-1];
+ /* display highest power on channel */
+ if (c->ic_maxpower > prev->ic_maxpower)
+ *prev = *c;
+ } else {
+ achans->ic_chans[achans->ic_nchans++] = *c;
+ setbit(reported, c->ic_ieee);
+ }
+ }
+ if (!verbose) {
+ half = achans->ic_nchans / 2;
+ if (achans->ic_nchans % 2)
+ half++;
+
+ for (i = 0; i < achans->ic_nchans / 2; i++) {
+ print_txpow(&achans->ic_chans[i]);
+ print_txpow(&achans->ic_chans[half+i]);
+ printf("\n");
+ }
+ if (achans->ic_nchans % 2) {
+ print_txpow(&achans->ic_chans[i]);
+ printf("\n");
+ }
+ } else {
+ for (i = 0; i < achans->ic_nchans; i++) {
+ print_txpow_verbose(&achans->ic_chans[i]);
+ printf("\n");
+ }
+ }
+ free(achans);
+}
+
+static void
+list_keys(int s)
+{
+}
+
+#define IEEE80211_C_BITS \
+ "\20\1STA\002803ENCAP\7FF\10TURBOP\11IBSS\12PMGT" \
+ "\13HOSTAP\14AHDEMO\15SWRETRY\16TXPMGT\17SHSLOT\20SHPREAMBLE" \
+ "\21MONITOR\22DFS\23MBSS\30WPA1\31WPA2\32BURST\33WME\34WDS\36BGSCAN" \
+ "\37TXFRAG\40TDMA"
+
+static void
+list_capabilities(int s)
+{
+ struct ieee80211_devcaps_req *dc;
+
+ if (verbose)
+ dc = malloc(IEEE80211_DEVCAPS_SIZE(MAXCHAN));
+ else
+ dc = malloc(IEEE80211_DEVCAPS_SIZE(1));
+ if (dc == NULL)
+ errx(1, "no space for device capabilities");
+ dc->dc_chaninfo.ic_nchans = verbose ? MAXCHAN : 1;
+ getdevcaps(s, dc);
+ printb("drivercaps", dc->dc_drivercaps, IEEE80211_C_BITS);
+ if (dc->dc_cryptocaps != 0 || verbose) {
+ putchar('\n');
+ printb("cryptocaps", dc->dc_cryptocaps, IEEE80211_CRYPTO_BITS);
+ }
+ if (dc->dc_htcaps != 0 || verbose) {
+ putchar('\n');
+ printb("htcaps", dc->dc_htcaps, IEEE80211_HTCAP_BITS);
+ }
+ putchar('\n');
+ if (verbose) {
+ chaninfo = &dc->dc_chaninfo; /* XXX */
+ print_channels(s, &dc->dc_chaninfo, 1/*allchans*/, verbose);
+ }
+ free(dc);
+}
+
+static int
+get80211wme(int s, int param, int ac, int *val)
+{
+ struct ieee80211req ireq;
+
+ (void) memset(&ireq, 0, sizeof(ireq));
+ (void) strncpy(ireq.i_name, name, sizeof(ireq.i_name));
+ ireq.i_type = param;
+ ireq.i_len = ac;
+ if (ioctl(s, SIOCG80211, &ireq) < 0) {
+ warn("cannot get WME parameter %d, ac %d%s",
+ param, ac & IEEE80211_WMEPARAM_VAL,
+ ac & IEEE80211_WMEPARAM_BSS ? " (BSS)" : "");
+ return -1;
+ }
+ *val = ireq.i_val;
+ return 0;
+}
+
+static void
+list_wme_aci(int s, const char *tag, int ac)
+{
+ int val;
+
+ printf("\t%s", tag);
+
+ /* show WME BSS parameters */
+ if (get80211wme(s, IEEE80211_IOC_WME_CWMIN, ac, &val) != -1)
+ printf(" cwmin %2u", val);
+ if (get80211wme(s, IEEE80211_IOC_WME_CWMAX, ac, &val) != -1)
+ printf(" cwmax %2u", val);
+ if (get80211wme(s, IEEE80211_IOC_WME_AIFS, ac, &val) != -1)
+ printf(" aifs %2u", val);
+ if (get80211wme(s, IEEE80211_IOC_WME_TXOPLIMIT, ac, &val) != -1)
+ printf(" txopLimit %3u", val);
+ if (get80211wme(s, IEEE80211_IOC_WME_ACM, ac, &val) != -1) {
+ if (val)
+ printf(" acm");
+ else if (verbose)
+ printf(" -acm");
+ }
+ /* !BSS only */
+ if ((ac & IEEE80211_WMEPARAM_BSS) == 0) {
+ if (get80211wme(s, IEEE80211_IOC_WME_ACKPOLICY, ac, &val) != -1) {
+ if (!val)
+ printf(" -ack");
+ else if (verbose)
+ printf(" ack");
+ }
+ }
+ printf("\n");
+}
+
+static void
+list_wme(int s)
+{
+ static const char *acnames[] = { "AC_BE", "AC_BK", "AC_VI", "AC_VO" };
+ int ac;
+
+ if (verbose) {
+ /* display both BSS and local settings */
+ for (ac = WME_AC_BE; ac <= WME_AC_VO; ac++) {
+ again:
+ if (ac & IEEE80211_WMEPARAM_BSS)
+ list_wme_aci(s, " ", ac);
+ else
+ list_wme_aci(s, acnames[ac], ac);
+ if ((ac & IEEE80211_WMEPARAM_BSS) == 0) {
+ ac |= IEEE80211_WMEPARAM_BSS;
+ goto again;
+ } else
+ ac &= ~IEEE80211_WMEPARAM_BSS;
+ }
+ } else {
+ /* display only channel settings */
+ for (ac = WME_AC_BE; ac <= WME_AC_VO; ac++)
+ list_wme_aci(s, acnames[ac], ac);
+ }
+}
+
+static void
+list_roam(int s)
+{
+ const struct ieee80211_roamparam *rp;
+ int mode;
+
+ getroam(s);
+ for (mode = IEEE80211_MODE_11A; mode < IEEE80211_MODE_MAX; mode++) {
+ rp = &roamparams.params[mode];
+ if (rp->rssi == 0 && rp->rate == 0)
+ continue;
+ if (mode == IEEE80211_MODE_11NA || mode == IEEE80211_MODE_11NG) {
+ if (rp->rssi & 1)
+ LINE_CHECK("roam:%-7.7s rssi %2u.5dBm MCS %2u ",
+ modename[mode], rp->rssi/2,
+ rp->rate &~ IEEE80211_RATE_MCS);
+ else
+ LINE_CHECK("roam:%-7.7s rssi %4udBm MCS %2u ",
+ modename[mode], rp->rssi/2,
+ rp->rate &~ IEEE80211_RATE_MCS);
+ } else {
+ if (rp->rssi & 1)
+ LINE_CHECK("roam:%-7.7s rssi %2u.5dBm rate %2u Mb/s",
+ modename[mode], rp->rssi/2, rp->rate/2);
+ else
+ LINE_CHECK("roam:%-7.7s rssi %4udBm rate %2u Mb/s",
+ modename[mode], rp->rssi/2, rp->rate/2);
+ }
+ }
+}
+
+static void
+list_txparams(int s)
+{
+ const struct ieee80211_txparam *tp;
+ int mode;
+
+ gettxparams(s);
+ for (mode = IEEE80211_MODE_11A; mode < IEEE80211_MODE_MAX; mode++) {
+ tp = &txparams.params[mode];
+ if (tp->mgmtrate == 0 && tp->mcastrate == 0)
+ continue;
+ if (mode == IEEE80211_MODE_11NA || mode == IEEE80211_MODE_11NG) {
+ if (tp->ucastrate == IEEE80211_FIXED_RATE_NONE)
+ LINE_CHECK("%-7.7s ucast NONE mgmt %2u MCS "
+ "mcast %2u MCS maxretry %u",
+ modename[mode],
+ tp->mgmtrate &~ IEEE80211_RATE_MCS,
+ tp->mcastrate &~ IEEE80211_RATE_MCS,
+ tp->maxretry);
+ else
+ LINE_CHECK("%-7.7s ucast %2u MCS mgmt %2u MCS "
+ "mcast %2u MCS maxretry %u",
+ modename[mode],
+ tp->ucastrate &~ IEEE80211_RATE_MCS,
+ tp->mgmtrate &~ IEEE80211_RATE_MCS,
+ tp->mcastrate &~ IEEE80211_RATE_MCS,
+ tp->maxretry);
+ } else {
+ if (tp->ucastrate == IEEE80211_FIXED_RATE_NONE)
+ LINE_CHECK("%-7.7s ucast NONE mgmt %2u Mb/s "
+ "mcast %2u Mb/s maxretry %u",
+ modename[mode],
+ tp->mgmtrate/2,
+ tp->mcastrate/2, tp->maxretry);
+ else
+ LINE_CHECK("%-7.7s ucast %2u Mb/s mgmt %2u Mb/s "
+ "mcast %2u Mb/s maxretry %u",
+ modename[mode],
+ tp->ucastrate/2, tp->mgmtrate/2,
+ tp->mcastrate/2, tp->maxretry);
+ }
+ }
+}
+
+static void
+printpolicy(int policy)
+{
+ switch (policy) {
+ case IEEE80211_MACCMD_POLICY_OPEN:
+ printf("policy: open\n");
+ break;
+ case IEEE80211_MACCMD_POLICY_ALLOW:
+ printf("policy: allow\n");
+ break;
+ case IEEE80211_MACCMD_POLICY_DENY:
+ printf("policy: deny\n");
+ break;
+ case IEEE80211_MACCMD_POLICY_RADIUS:
+ printf("policy: radius\n");
+ break;
+ default:
+ printf("policy: unknown (%u)\n", policy);
+ break;
+ }
+}
+
+static void
+list_mac(int s)
+{
+ struct ieee80211req ireq;
+ struct ieee80211req_maclist *acllist;
+ int i, nacls, policy, len;
+ uint8_t *data;
+ char c;
+
+ (void) memset(&ireq, 0, sizeof(ireq));
+ (void) strncpy(ireq.i_name, name, sizeof(ireq.i_name)); /* XXX ?? */
+ ireq.i_type = IEEE80211_IOC_MACCMD;
+ ireq.i_val = IEEE80211_MACCMD_POLICY;
+ if (ioctl(s, SIOCG80211, &ireq) < 0) {
+ if (errno == EINVAL) {
+ printf("No acl policy loaded\n");
+ return;
+ }
+ err(1, "unable to get mac policy");
+ }
+ policy = ireq.i_val;
+ if (policy == IEEE80211_MACCMD_POLICY_OPEN) {
+ c = '*';
+ } else if (policy == IEEE80211_MACCMD_POLICY_ALLOW) {
+ c = '+';
+ } else if (policy == IEEE80211_MACCMD_POLICY_DENY) {
+ c = '-';
+ } else if (policy == IEEE80211_MACCMD_POLICY_RADIUS) {
+ c = 'r'; /* NB: should never have entries */
+ } else {
+ printf("policy: unknown (%u)\n", policy);
+ c = '?';
+ }
+ if (verbose || c == '?')
+ printpolicy(policy);
+
+ ireq.i_val = IEEE80211_MACCMD_LIST;
+ ireq.i_len = 0;
+ if (ioctl(s, SIOCG80211, &ireq) < 0)
+ err(1, "unable to get mac acl list size");
+ if (ireq.i_len == 0) { /* NB: no acls */
+ if (!(verbose || c == '?'))
+ printpolicy(policy);
+ return;
+ }
+ len = ireq.i_len;
+
+ data = malloc(len);
+ if (data == NULL)
+ err(1, "out of memory for acl list");
+
+ ireq.i_data = data;
+ if (ioctl(s, SIOCG80211, &ireq) < 0)
+ err(1, "unable to get mac acl list");
+ nacls = len / sizeof(*acllist);
+ acllist = (struct ieee80211req_maclist *) data;
+ for (i = 0; i < nacls; i++)
+ printf("%c%s\n", c, ether_ntoa(
+ (const struct ether_addr *) acllist[i].ml_macaddr));
+ free(data);
+}
+
+static void
+print_regdomain(const struct ieee80211_regdomain *reg, int verb)
+{
+ if ((reg->regdomain != 0 &&
+ reg->regdomain != reg->country) || verb) {
+ const struct regdomain *rd =
+ lib80211_regdomain_findbysku(getregdata(), reg->regdomain);
+ if (rd == NULL)
+ LINE_CHECK("regdomain %d", reg->regdomain);
+ else
+ LINE_CHECK("regdomain %s", rd->name);
+ }
+ if (reg->country != 0 || verb) {
+ const struct country *cc =
+ lib80211_country_findbycc(getregdata(), reg->country);
+ if (cc == NULL)
+ LINE_CHECK("country %d", reg->country);
+ else
+ LINE_CHECK("country %s", cc->isoname);
+ }
+ if (reg->location == 'I')
+ LINE_CHECK("indoor");
+ else if (reg->location == 'O')
+ LINE_CHECK("outdoor");
+ else if (verb)
+ LINE_CHECK("anywhere");
+ if (reg->ecm)
+ LINE_CHECK("ecm");
+ else if (verb)
+ LINE_CHECK("-ecm");
+}
+
+static void
+list_regdomain(int s, int channelsalso)
+{
+ getregdomain(s);
+ if (channelsalso) {
+ getchaninfo(s);
+ spacer = ':';
+ print_regdomain(&regdomain, 1);
+ LINE_BREAK();
+ print_channels(s, chaninfo, 1/*allchans*/, 1/*verbose*/);
+ } else
+ print_regdomain(&regdomain, verbose);
+}
+
+static void
+list_mesh(int s)
+{
+ struct ieee80211req ireq;
+ struct ieee80211req_mesh_route routes[128];
+ struct ieee80211req_mesh_route *rt;
+
+ (void) memset(&ireq, 0, sizeof(ireq));
+ (void) strncpy(ireq.i_name, name, sizeof(ireq.i_name));
+ ireq.i_type = IEEE80211_IOC_MESH_RTCMD;
+ ireq.i_val = IEEE80211_MESH_RTCMD_LIST;
+ ireq.i_data = &routes;
+ ireq.i_len = sizeof(routes);
+ if (ioctl(s, SIOCG80211, &ireq) < 0)
+ err(1, "unable to get the Mesh routing table");
+
+ printf("%-17.17s %-17.17s %4s %4s %4s %6s %s\n"
+ , "DEST"
+ , "NEXT HOP"
+ , "HOPS"
+ , "METRIC"
+ , "LIFETIME"
+ , "MSEQ"
+ , "FLAGS");
+
+ for (rt = &routes[0]; rt - &routes[0] < ireq.i_len / sizeof(*rt); rt++){
+ printf("%s ",
+ ether_ntoa((const struct ether_addr *)rt->imr_dest));
+ printf("%s %4u %4u %6u %6u %c%c\n",
+ ether_ntoa((const struct ether_addr *)rt->imr_nexthop),
+ rt->imr_nhops, rt->imr_metric, rt->imr_lifetime,
+ rt->imr_lastmseq,
+ (rt->imr_flags & IEEE80211_MESHRT_FLAGS_DISCOVER) ?
+ 'D' :
+ (rt->imr_flags & IEEE80211_MESHRT_FLAGS_VALID) ?
+ 'V' : '!',
+ (rt->imr_flags & IEEE80211_MESHRT_FLAGS_PROXY) ?
+ 'P' :
+ (rt->imr_flags & IEEE80211_MESHRT_FLAGS_GATE) ?
+ 'G' :' ');
+ }
+}
+
+static
+DECL_CMD_FUNC(set80211list, arg, d)
+{
+#define iseq(a,b) (strncasecmp(a,b,sizeof(b)-1) == 0)
+
+ LINE_INIT('\t');
+
+ if (iseq(arg, "sta"))
+ list_stations(s);
+ else if (iseq(arg, "scan") || iseq(arg, "ap"))
+ list_scan(s);
+ else if (iseq(arg, "chan") || iseq(arg, "freq"))
+ list_channels(s, 1);
+ else if (iseq(arg, "active"))
+ list_channels(s, 0);
+ else if (iseq(arg, "keys"))
+ list_keys(s);
+ else if (iseq(arg, "caps"))
+ list_capabilities(s);
+ else if (iseq(arg, "wme") || iseq(arg, "wmm"))
+ list_wme(s);
+ else if (iseq(arg, "mac"))
+ list_mac(s);
+ else if (iseq(arg, "txpow"))
+ list_txpow(s);
+ else if (iseq(arg, "roam"))
+ list_roam(s);
+ else if (iseq(arg, "txparam") || iseq(arg, "txparm"))
+ list_txparams(s);
+ else if (iseq(arg, "regdomain"))
+ list_regdomain(s, 1);
+ else if (iseq(arg, "countries"))
+ list_countries();
+ else if (iseq(arg, "mesh"))
+ list_mesh(s);
+ else
+ errx(1, "Don't know how to list %s for %s", arg, name);
+ LINE_BREAK();
+#undef iseq
+}
+
+static enum ieee80211_opmode
+get80211opmode(int s)
+{
+ struct ifmediareq ifmr;
+
+ (void) memset(&ifmr, 0, sizeof(ifmr));
+ (void) strncpy(ifmr.ifm_name, name, sizeof(ifmr.ifm_name));
+
+ if (ioctl(s, SIOCGIFMEDIA, (caddr_t)&ifmr) >= 0) {
+ if (ifmr.ifm_current & IFM_IEEE80211_ADHOC) {
+ if (ifmr.ifm_current & IFM_FLAG0)
+ return IEEE80211_M_AHDEMO;
+ else
+ return IEEE80211_M_IBSS;
+ }
+ if (ifmr.ifm_current & IFM_IEEE80211_HOSTAP)
+ return IEEE80211_M_HOSTAP;
+ if (ifmr.ifm_current & IFM_IEEE80211_MONITOR)
+ return IEEE80211_M_MONITOR;
+ if (ifmr.ifm_current & IFM_IEEE80211_MBSS)
+ return IEEE80211_M_MBSS;
+ }
+ return IEEE80211_M_STA;
+}
+
+#if 0
+static void
+printcipher(int s, struct ieee80211req *ireq, int keylenop)
+{
+ switch (ireq->i_val) {
+ case IEEE80211_CIPHER_WEP:
+ ireq->i_type = keylenop;
+ if (ioctl(s, SIOCG80211, ireq) != -1)
+ printf("WEP-%s",
+ ireq->i_len <= 5 ? "40" :
+ ireq->i_len <= 13 ? "104" : "128");
+ else
+ printf("WEP");
+ break;
+ case IEEE80211_CIPHER_TKIP:
+ printf("TKIP");
+ break;
+ case IEEE80211_CIPHER_AES_OCB:
+ printf("AES-OCB");
+ break;
+ case IEEE80211_CIPHER_AES_CCM:
+ printf("AES-CCM");
+ break;
+ case IEEE80211_CIPHER_CKIP:
+ printf("CKIP");
+ break;
+ case IEEE80211_CIPHER_NONE:
+ printf("NONE");
+ break;
+ default:
+ printf("UNKNOWN (0x%x)", ireq->i_val);
+ break;
+ }
+}
+#endif
+
+static void
+printkey(const struct ieee80211req_key *ik)
+{
+ static const uint8_t zerodata[IEEE80211_KEYBUF_SIZE];
+ int keylen = ik->ik_keylen;
+ int printcontents;
+
+ printcontents = printkeys &&
+ (memcmp(ik->ik_keydata, zerodata, keylen) != 0 || verbose);
+ if (printcontents)
+ LINE_BREAK();
+ switch (ik->ik_type) {
+ case IEEE80211_CIPHER_WEP:
+ /* compatibility */
+ LINE_CHECK("wepkey %u:%s", ik->ik_keyix+1,
+ keylen <= 5 ? "40-bit" :
+ keylen <= 13 ? "104-bit" : "128-bit");
+ break;
+ case IEEE80211_CIPHER_TKIP:
+ if (keylen > 128/8)
+ keylen -= 128/8; /* ignore MIC for now */
+ LINE_CHECK("TKIP %u:%u-bit", ik->ik_keyix+1, 8*keylen);
+ break;
+ case IEEE80211_CIPHER_AES_OCB:
+ LINE_CHECK("AES-OCB %u:%u-bit", ik->ik_keyix+1, 8*keylen);
+ break;
+ case IEEE80211_CIPHER_AES_CCM:
+ LINE_CHECK("AES-CCM %u:%u-bit", ik->ik_keyix+1, 8*keylen);
+ break;
+ case IEEE80211_CIPHER_CKIP:
+ LINE_CHECK("CKIP %u:%u-bit", ik->ik_keyix+1, 8*keylen);
+ break;
+ case IEEE80211_CIPHER_NONE:
+ LINE_CHECK("NULL %u:%u-bit", ik->ik_keyix+1, 8*keylen);
+ break;
+ default:
+ LINE_CHECK("UNKNOWN (0x%x) %u:%u-bit",
+ ik->ik_type, ik->ik_keyix+1, 8*keylen);
+ break;
+ }
+ if (printcontents) {
+ int i;
+
+ printf(" <");
+ for (i = 0; i < keylen; i++)
+ printf("%02x", ik->ik_keydata[i]);
+ printf(">");
+ if (ik->ik_type != IEEE80211_CIPHER_WEP &&
+ (ik->ik_keyrsc != 0 || verbose))
+ printf(" rsc %ju", (uintmax_t)ik->ik_keyrsc);
+ if (ik->ik_type != IEEE80211_CIPHER_WEP &&
+ (ik->ik_keytsc != 0 || verbose))
+ printf(" tsc %ju", (uintmax_t)ik->ik_keytsc);
+ if (ik->ik_flags != 0 && verbose) {
+ const char *sep = " ";
+
+ if (ik->ik_flags & IEEE80211_KEY_XMIT)
+ printf("%stx", sep), sep = "+";
+ if (ik->ik_flags & IEEE80211_KEY_RECV)
+ printf("%srx", sep), sep = "+";
+ if (ik->ik_flags & IEEE80211_KEY_DEFAULT)
+ printf("%sdef", sep), sep = "+";
+ }
+ LINE_BREAK();
+ }
+}
+
+static void
+printrate(const char *tag, int v, int defrate, int defmcs)
+{
+ if ((v & IEEE80211_RATE_MCS) == 0) {
+ if (v != defrate) {
+ if (v & 1)
+ LINE_CHECK("%s %d.5", tag, v/2);
+ else
+ LINE_CHECK("%s %d", tag, v/2);
+ }
+ } else {
+ if (v != defmcs)
+ LINE_CHECK("%s %d", tag, v &~ 0x80);
+ }
+}
+
+static int
+getid(int s, int ix, void *data, size_t len, int *plen, int mesh)
+{
+ struct ieee80211req ireq;
+
+ (void) memset(&ireq, 0, sizeof(ireq));
+ (void) strncpy(ireq.i_name, name, sizeof(ireq.i_name));
+ ireq.i_type = (!mesh) ? IEEE80211_IOC_SSID : IEEE80211_IOC_MESH_ID;
+ ireq.i_val = ix;
+ ireq.i_data = data;
+ ireq.i_len = len;
+ if (ioctl(s, SIOCG80211, &ireq) < 0)
+ return -1;
+ *plen = ireq.i_len;
+ return 0;
+}
+
+static void
+ieee80211_status(int s)
+{
+ static const uint8_t zerobssid[IEEE80211_ADDR_LEN];
+ enum ieee80211_opmode opmode = get80211opmode(s);
+ int i, num, wpa, wme, bgscan, bgscaninterval, val, len, wepmode;
+ uint8_t data[32];
+ const struct ieee80211_channel *c;
+ const struct ieee80211_roamparam *rp;
+ const struct ieee80211_txparam *tp;
+
+ if (getid(s, -1, data, sizeof(data), &len, 0) < 0) {
+ /* If we can't get the SSID, this isn't an 802.11 device. */
+ return;
+ }
+
+ /*
+ * Invalidate cached state so printing status for multiple
+ * if's doesn't reuse the first interfaces' cached state.
+ */
+ gotcurchan = 0;
+ gotroam = 0;
+ gottxparams = 0;
+ gothtconf = 0;
+ gotregdomain = 0;
+
+ printf("\t");
+ if (opmode == IEEE80211_M_MBSS) {
+ printf("meshid ");
+ getid(s, 0, data, sizeof(data), &len, 1);
+ print_string(data, len);
+ } else {
+ if (get80211val(s, IEEE80211_IOC_NUMSSIDS, &num) < 0)
+ num = 0;
+ printf("ssid ");
+ if (num > 1) {
+ for (i = 0; i < num; i++) {
+ if (getid(s, i, data, sizeof(data), &len, 0) >= 0 && len > 0) {
+ printf(" %d:", i + 1);
+ print_string(data, len);
+ }
+ }
+ } else
+ print_string(data, len);
+ }
+ c = getcurchan(s);
+ if (c->ic_freq != IEEE80211_CHAN_ANY) {
+ char buf[14];
+ printf(" channel %d (%u MHz%s)", c->ic_ieee, c->ic_freq,
+ get_chaninfo(c, 1, buf, sizeof(buf)));
+ } else if (verbose)
+ printf(" channel UNDEF");
+
+ if (get80211(s, IEEE80211_IOC_BSSID, data, IEEE80211_ADDR_LEN) >= 0 &&
+ (memcmp(data, zerobssid, sizeof(zerobssid)) != 0 || verbose))
+ printf(" bssid %s", ether_ntoa((struct ether_addr *)data));
+
+ if (get80211len(s, IEEE80211_IOC_STATIONNAME, data, sizeof(data), &len) != -1) {
+ printf("\n\tstationname ");
+ print_string(data, len);
+ }
+
+ spacer = ' '; /* force first break */
+ LINE_BREAK();
+
+ list_regdomain(s, 0);
+
+ wpa = 0;
+ if (get80211val(s, IEEE80211_IOC_AUTHMODE, &val) != -1) {
+ switch (val) {
+ case IEEE80211_AUTH_NONE:
+ LINE_CHECK("authmode NONE");
+ break;
+ case IEEE80211_AUTH_OPEN:
+ LINE_CHECK("authmode OPEN");
+ break;
+ case IEEE80211_AUTH_SHARED:
+ LINE_CHECK("authmode SHARED");
+ break;
+ case IEEE80211_AUTH_8021X:
+ LINE_CHECK("authmode 802.1x");
+ break;
+ case IEEE80211_AUTH_WPA:
+ if (get80211val(s, IEEE80211_IOC_WPA, &wpa) < 0)
+ wpa = 1; /* default to WPA1 */
+ switch (wpa) {
+ case 2:
+ LINE_CHECK("authmode WPA2/802.11i");
+ break;
+ case 3:
+ LINE_CHECK("authmode WPA1+WPA2/802.11i");
+ break;
+ default:
+ LINE_CHECK("authmode WPA");
+ break;
+ }
+ break;
+ case IEEE80211_AUTH_AUTO:
+ LINE_CHECK("authmode AUTO");
+ break;
+ default:
+ LINE_CHECK("authmode UNKNOWN (0x%x)", val);
+ break;
+ }
+ }
+
+ if (wpa || verbose) {
+ if (get80211val(s, IEEE80211_IOC_WPS, &val) != -1) {
+ if (val)
+ LINE_CHECK("wps");
+ else if (verbose)
+ LINE_CHECK("-wps");
+ }
+ if (get80211val(s, IEEE80211_IOC_TSN, &val) != -1) {
+ if (val)
+ LINE_CHECK("tsn");
+ else if (verbose)
+ LINE_CHECK("-tsn");
+ }
+ if (ioctl(s, IEEE80211_IOC_COUNTERMEASURES, &val) != -1) {
+ if (val)
+ LINE_CHECK("countermeasures");
+ else if (verbose)
+ LINE_CHECK("-countermeasures");
+ }
+#if 0
+ /* XXX not interesting with WPA done in user space */
+ ireq.i_type = IEEE80211_IOC_KEYMGTALGS;
+ if (ioctl(s, SIOCG80211, &ireq) != -1) {
+ }
+
+ ireq.i_type = IEEE80211_IOC_MCASTCIPHER;
+ if (ioctl(s, SIOCG80211, &ireq) != -1) {
+ LINE_CHECK("mcastcipher ");
+ printcipher(s, &ireq, IEEE80211_IOC_MCASTKEYLEN);
+ spacer = ' ';
+ }
+
+ ireq.i_type = IEEE80211_IOC_UCASTCIPHER;
+ if (ioctl(s, SIOCG80211, &ireq) != -1) {
+ LINE_CHECK("ucastcipher ");
+ printcipher(s, &ireq, IEEE80211_IOC_UCASTKEYLEN);
+ }
+
+ if (wpa & 2) {
+ ireq.i_type = IEEE80211_IOC_RSNCAPS;
+ if (ioctl(s, SIOCG80211, &ireq) != -1) {
+ LINE_CHECK("RSN caps 0x%x", ireq.i_val);
+ spacer = ' ';
+ }
+ }
+
+ ireq.i_type = IEEE80211_IOC_UCASTCIPHERS;
+ if (ioctl(s, SIOCG80211, &ireq) != -1) {
+ }
+#endif
+ }
+
+ if (get80211val(s, IEEE80211_IOC_WEP, &wepmode) != -1 &&
+ wepmode != IEEE80211_WEP_NOSUP) {
+
+ switch (wepmode) {
+ case IEEE80211_WEP_OFF:
+ LINE_CHECK("privacy OFF");
+ break;
+ case IEEE80211_WEP_ON:
+ LINE_CHECK("privacy ON");
+ break;
+ case IEEE80211_WEP_MIXED:
+ LINE_CHECK("privacy MIXED");
+ break;
+ default:
+ LINE_CHECK("privacy UNKNOWN (0x%x)", wepmode);
+ break;
+ }
+
+ /*
+ * If we get here then we've got WEP support so we need
+ * to print WEP status.
+ */
+
+ if (get80211val(s, IEEE80211_IOC_WEPTXKEY, &val) < 0) {
+ warn("WEP support, but no tx key!");
+ goto end;
+ }
+ if (val != -1)
+ LINE_CHECK("deftxkey %d", val+1);
+ else if (wepmode != IEEE80211_WEP_OFF || verbose)
+ LINE_CHECK("deftxkey UNDEF");
+
+ if (get80211val(s, IEEE80211_IOC_NUMWEPKEYS, &num) < 0) {
+ warn("WEP support, but no NUMWEPKEYS support!");
+ goto end;
+ }
+
+ for (i = 0; i < num; i++) {
+ struct ieee80211req_key ik;
+
+ memset(&ik, 0, sizeof(ik));
+ ik.ik_keyix = i;
+ if (get80211(s, IEEE80211_IOC_WPAKEY, &ik, sizeof(ik)) < 0) {
+ warn("WEP support, but can get keys!");
+ goto end;
+ }
+ if (ik.ik_keylen != 0) {
+ if (verbose)
+ LINE_BREAK();
+ printkey(&ik);
+ }
+ }
+end:
+ ;
+ }
+
+ if (get80211val(s, IEEE80211_IOC_POWERSAVE, &val) != -1 &&
+ val != IEEE80211_POWERSAVE_NOSUP ) {
+ if (val != IEEE80211_POWERSAVE_OFF || verbose) {
+ switch (val) {
+ case IEEE80211_POWERSAVE_OFF:
+ LINE_CHECK("powersavemode OFF");
+ break;
+ case IEEE80211_POWERSAVE_CAM:
+ LINE_CHECK("powersavemode CAM");
+ break;
+ case IEEE80211_POWERSAVE_PSP:
+ LINE_CHECK("powersavemode PSP");
+ break;
+ case IEEE80211_POWERSAVE_PSP_CAM:
+ LINE_CHECK("powersavemode PSP-CAM");
+ break;
+ }
+ if (get80211val(s, IEEE80211_IOC_POWERSAVESLEEP, &val) != -1)
+ LINE_CHECK("powersavesleep %d", val);
+ }
+ }
+
+ if (get80211val(s, IEEE80211_IOC_TXPOWER, &val) != -1) {
+ if (val & 1)
+ LINE_CHECK("txpower %d.5", val/2);
+ else
+ LINE_CHECK("txpower %d", val/2);
+ }
+ if (verbose) {
+ if (get80211val(s, IEEE80211_IOC_TXPOWMAX, &val) != -1)
+ LINE_CHECK("txpowmax %.1f", val/2.);
+ }
+
+ if (get80211val(s, IEEE80211_IOC_DOTD, &val) != -1) {
+ if (val)
+ LINE_CHECK("dotd");
+ else if (verbose)
+ LINE_CHECK("-dotd");
+ }
+
+ if (get80211val(s, IEEE80211_IOC_RTSTHRESHOLD, &val) != -1) {
+ if (val != IEEE80211_RTS_MAX || verbose)
+ LINE_CHECK("rtsthreshold %d", val);
+ }
+
+ if (get80211val(s, IEEE80211_IOC_FRAGTHRESHOLD, &val) != -1) {
+ if (val != IEEE80211_FRAG_MAX || verbose)
+ LINE_CHECK("fragthreshold %d", val);
+ }
+ if (opmode == IEEE80211_M_STA || verbose) {
+ if (get80211val(s, IEEE80211_IOC_BMISSTHRESHOLD, &val) != -1) {
+ if (val != IEEE80211_HWBMISS_MAX || verbose)
+ LINE_CHECK("bmiss %d", val);
+ }
+ }
+
+ if (!verbose) {
+ gettxparams(s);
+ tp = &txparams.params[chan2mode(c)];
+ printrate("ucastrate", tp->ucastrate,
+ IEEE80211_FIXED_RATE_NONE, IEEE80211_FIXED_RATE_NONE);
+ printrate("mcastrate", tp->mcastrate, 2*1,
+ IEEE80211_RATE_MCS|0);
+ printrate("mgmtrate", tp->mgmtrate, 2*1,
+ IEEE80211_RATE_MCS|0);
+ if (tp->maxretry != 6) /* XXX */
+ LINE_CHECK("maxretry %d", tp->maxretry);
+ } else {
+ LINE_BREAK();
+ list_txparams(s);
+ }
+
+ bgscaninterval = -1;
+ (void) get80211val(s, IEEE80211_IOC_BGSCAN_INTERVAL, &bgscaninterval);
+
+ if (get80211val(s, IEEE80211_IOC_SCANVALID, &val) != -1) {
+ if (val != bgscaninterval || verbose)
+ LINE_CHECK("scanvalid %u", val);
+ }
+
+ bgscan = 0;
+ if (get80211val(s, IEEE80211_IOC_BGSCAN, &bgscan) != -1) {
+ if (bgscan)
+ LINE_CHECK("bgscan");
+ else if (verbose)
+ LINE_CHECK("-bgscan");
+ }
+ if (bgscan || verbose) {
+ if (bgscaninterval != -1)
+ LINE_CHECK("bgscanintvl %u", bgscaninterval);
+ if (get80211val(s, IEEE80211_IOC_BGSCAN_IDLE, &val) != -1)
+ LINE_CHECK("bgscanidle %u", val);
+ if (!verbose) {
+ getroam(s);
+ rp = &roamparams.params[chan2mode(c)];
+ if (rp->rssi & 1)
+ LINE_CHECK("roam:rssi %u.5", rp->rssi/2);
+ else
+ LINE_CHECK("roam:rssi %u", rp->rssi/2);
+ LINE_CHECK("roam:rate %u", rp->rate/2);
+ } else {
+ LINE_BREAK();
+ list_roam(s);
+ LINE_BREAK();
+ }
+ }
+
+ if (IEEE80211_IS_CHAN_ANYG(c) || verbose) {
+ if (get80211val(s, IEEE80211_IOC_PUREG, &val) != -1) {
+ if (val)
+ LINE_CHECK("pureg");
+ else if (verbose)
+ LINE_CHECK("-pureg");
+ }
+ if (get80211val(s, IEEE80211_IOC_PROTMODE, &val) != -1) {
+ switch (val) {
+ case IEEE80211_PROTMODE_OFF:
+ LINE_CHECK("protmode OFF");
+ break;
+ case IEEE80211_PROTMODE_CTS:
+ LINE_CHECK("protmode CTS");
+ break;
+ case IEEE80211_PROTMODE_RTSCTS:
+ LINE_CHECK("protmode RTSCTS");
+ break;
+ default:
+ LINE_CHECK("protmode UNKNOWN (0x%x)", val);
+ break;
+ }
+ }
+ }
+
+ if (IEEE80211_IS_CHAN_HT(c) || verbose) {
+ gethtconf(s);
+ switch (htconf & 3) {
+ case 0:
+ case 2:
+ LINE_CHECK("-ht");
+ break;
+ case 1:
+ LINE_CHECK("ht20");
+ break;
+ case 3:
+ if (verbose)
+ LINE_CHECK("ht");
+ break;
+ }
+ if (get80211val(s, IEEE80211_IOC_HTCOMPAT, &val) != -1) {
+ if (!val)
+ LINE_CHECK("-htcompat");
+ else if (verbose)
+ LINE_CHECK("htcompat");
+ }
+ if (get80211val(s, IEEE80211_IOC_AMPDU, &val) != -1) {
+ switch (val) {
+ case 0:
+ LINE_CHECK("-ampdu");
+ break;
+ case 1:
+ LINE_CHECK("ampdutx -ampdurx");
+ break;
+ case 2:
+ LINE_CHECK("-ampdutx ampdurx");
+ break;
+ case 3:
+ if (verbose)
+ LINE_CHECK("ampdu");
+ break;
+ }
+ }
+ if (get80211val(s, IEEE80211_IOC_AMPDU_LIMIT, &val) != -1) {
+ switch (val) {
+ case IEEE80211_HTCAP_MAXRXAMPDU_8K:
+ LINE_CHECK("ampdulimit 8k");
+ break;
+ case IEEE80211_HTCAP_MAXRXAMPDU_16K:
+ LINE_CHECK("ampdulimit 16k");
+ break;
+ case IEEE80211_HTCAP_MAXRXAMPDU_32K:
+ LINE_CHECK("ampdulimit 32k");
+ break;
+ case IEEE80211_HTCAP_MAXRXAMPDU_64K:
+ LINE_CHECK("ampdulimit 64k");
+ break;
+ }
+ }
+ if (get80211val(s, IEEE80211_IOC_AMPDU_DENSITY, &val) != -1) {
+ switch (val) {
+ case IEEE80211_HTCAP_MPDUDENSITY_NA:
+ if (verbose)
+ LINE_CHECK("ampdudensity NA");
+ break;
+ case IEEE80211_HTCAP_MPDUDENSITY_025:
+ LINE_CHECK("ampdudensity .25");
+ break;
+ case IEEE80211_HTCAP_MPDUDENSITY_05:
+ LINE_CHECK("ampdudensity .5");
+ break;
+ case IEEE80211_HTCAP_MPDUDENSITY_1:
+ LINE_CHECK("ampdudensity 1");
+ break;
+ case IEEE80211_HTCAP_MPDUDENSITY_2:
+ LINE_CHECK("ampdudensity 2");
+ break;
+ case IEEE80211_HTCAP_MPDUDENSITY_4:
+ LINE_CHECK("ampdudensity 4");
+ break;
+ case IEEE80211_HTCAP_MPDUDENSITY_8:
+ LINE_CHECK("ampdudensity 8");
+ break;
+ case IEEE80211_HTCAP_MPDUDENSITY_16:
+ LINE_CHECK("ampdudensity 16");
+ break;
+ }
+ }
+ if (get80211val(s, IEEE80211_IOC_AMSDU, &val) != -1) {
+ switch (val) {
+ case 0:
+ LINE_CHECK("-amsdu");
+ break;
+ case 1:
+ LINE_CHECK("amsdutx -amsdurx");
+ break;
+ case 2:
+ LINE_CHECK("-amsdutx amsdurx");
+ break;
+ case 3:
+ if (verbose)
+ LINE_CHECK("amsdu");
+ break;
+ }
+ }
+ /* XXX amsdu limit */
+ if (get80211val(s, IEEE80211_IOC_SHORTGI, &val) != -1) {
+ if (val)
+ LINE_CHECK("shortgi");
+ else if (verbose)
+ LINE_CHECK("-shortgi");
+ }
+ if (get80211val(s, IEEE80211_IOC_HTPROTMODE, &val) != -1) {
+ if (val == IEEE80211_PROTMODE_OFF)
+ LINE_CHECK("htprotmode OFF");
+ else if (val != IEEE80211_PROTMODE_RTSCTS)
+ LINE_CHECK("htprotmode UNKNOWN (0x%x)", val);
+ else if (verbose)
+ LINE_CHECK("htprotmode RTSCTS");
+ }
+ if (get80211val(s, IEEE80211_IOC_PUREN, &val) != -1) {
+ if (val)
+ LINE_CHECK("puren");
+ else if (verbose)
+ LINE_CHECK("-puren");
+ }
+ if (get80211val(s, IEEE80211_IOC_SMPS, &val) != -1) {
+ if (val == IEEE80211_HTCAP_SMPS_DYNAMIC)
+ LINE_CHECK("smpsdyn");
+ else if (val == IEEE80211_HTCAP_SMPS_ENA)
+ LINE_CHECK("smps");
+ else if (verbose)
+ LINE_CHECK("-smps");
+ }
+ if (get80211val(s, IEEE80211_IOC_RIFS, &val) != -1) {
+ if (val)
+ LINE_CHECK("rifs");
+ else if (verbose)
+ LINE_CHECK("-rifs");
+ }
+ }
+
+ if (get80211val(s, IEEE80211_IOC_WME, &wme) != -1) {
+ if (wme)
+ LINE_CHECK("wme");
+ else if (verbose)
+ LINE_CHECK("-wme");
+ } else
+ wme = 0;
+
+ if (get80211val(s, IEEE80211_IOC_BURST, &val) != -1) {
+ if (val)
+ LINE_CHECK("burst");
+ else if (verbose)
+ LINE_CHECK("-burst");
+ }
+
+ if (get80211val(s, IEEE80211_IOC_FF, &val) != -1) {
+ if (val)
+ LINE_CHECK("ff");
+ else if (verbose)
+ LINE_CHECK("-ff");
+ }
+ if (get80211val(s, IEEE80211_IOC_TURBOP, &val) != -1) {
+ if (val)
+ LINE_CHECK("dturbo");
+ else if (verbose)
+ LINE_CHECK("-dturbo");
+ }
+ if (get80211val(s, IEEE80211_IOC_DWDS, &val) != -1) {
+ if (val)
+ LINE_CHECK("dwds");
+ else if (verbose)
+ LINE_CHECK("-dwds");
+ }
+
+ if (opmode == IEEE80211_M_HOSTAP) {
+ if (get80211val(s, IEEE80211_IOC_HIDESSID, &val) != -1) {
+ if (val)
+ LINE_CHECK("hidessid");
+ else if (verbose)
+ LINE_CHECK("-hidessid");
+ }
+ if (get80211val(s, IEEE80211_IOC_APBRIDGE, &val) != -1) {
+ if (!val)
+ LINE_CHECK("-apbridge");
+ else if (verbose)
+ LINE_CHECK("apbridge");
+ }
+ if (get80211val(s, IEEE80211_IOC_DTIM_PERIOD, &val) != -1)
+ LINE_CHECK("dtimperiod %u", val);
+
+ if (get80211val(s, IEEE80211_IOC_DOTH, &val) != -1) {
+ if (!val)
+ LINE_CHECK("-doth");
+ else if (verbose)
+ LINE_CHECK("doth");
+ }
+ if (get80211val(s, IEEE80211_IOC_DFS, &val) != -1) {
+ if (!val)
+ LINE_CHECK("-dfs");
+ else if (verbose)
+ LINE_CHECK("dfs");
+ }
+ if (get80211val(s, IEEE80211_IOC_INACTIVITY, &val) != -1) {
+ if (!val)
+ LINE_CHECK("-inact");
+ else if (verbose)
+ LINE_CHECK("inact");
+ }
+ } else {
+ if (get80211val(s, IEEE80211_IOC_ROAMING, &val) != -1) {
+ if (val != IEEE80211_ROAMING_AUTO || verbose) {
+ switch (val) {
+ case IEEE80211_ROAMING_DEVICE:
+ LINE_CHECK("roaming DEVICE");
+ break;
+ case IEEE80211_ROAMING_AUTO:
+ LINE_CHECK("roaming AUTO");
+ break;
+ case IEEE80211_ROAMING_MANUAL:
+ LINE_CHECK("roaming MANUAL");
+ break;
+ default:
+ LINE_CHECK("roaming UNKNOWN (0x%x)",
+ val);
+ break;
+ }
+ }
+ }
+ }
+
+ if (opmode == IEEE80211_M_AHDEMO) {
+ if (get80211val(s, IEEE80211_IOC_TDMA_SLOT, &val) != -1)
+ LINE_CHECK("tdmaslot %u", val);
+ if (get80211val(s, IEEE80211_IOC_TDMA_SLOTCNT, &val) != -1)
+ LINE_CHECK("tdmaslotcnt %u", val);
+ if (get80211val(s, IEEE80211_IOC_TDMA_SLOTLEN, &val) != -1)
+ LINE_CHECK("tdmaslotlen %u", val);
+ if (get80211val(s, IEEE80211_IOC_TDMA_BINTERVAL, &val) != -1)
+ LINE_CHECK("tdmabintval %u", val);
+ } else if (get80211val(s, IEEE80211_IOC_BEACON_INTERVAL, &val) != -1) {
+ /* XXX default define not visible */
+ if (val != 100 || verbose)
+ LINE_CHECK("bintval %u", val);
+ }
+
+ if (wme && verbose) {
+ LINE_BREAK();
+ list_wme(s);
+ }
+
+ if (opmode == IEEE80211_M_MBSS) {
+ if (get80211val(s, IEEE80211_IOC_MESH_TTL, &val) != -1) {
+ LINE_CHECK("meshttl %u", val);
+ }
+ if (get80211val(s, IEEE80211_IOC_MESH_AP, &val) != -1) {
+ if (val)
+ LINE_CHECK("meshpeering");
+ else
+ LINE_CHECK("-meshpeering");
+ }
+ if (get80211val(s, IEEE80211_IOC_MESH_FWRD, &val) != -1) {
+ if (val)
+ LINE_CHECK("meshforward");
+ else
+ LINE_CHECK("-meshforward");
+ }
+ if (get80211val(s, IEEE80211_IOC_MESH_GATE, &val) != -1) {
+ if (val)
+ LINE_CHECK("meshgate");
+ else
+ LINE_CHECK("-meshgate");
+ }
+ if (get80211len(s, IEEE80211_IOC_MESH_PR_METRIC, data, 12,
+ &len) != -1) {
+ data[len] = '\0';
+ LINE_CHECK("meshmetric %s", data);
+ }
+ if (get80211len(s, IEEE80211_IOC_MESH_PR_PATH, data, 12,
+ &len) != -1) {
+ data[len] = '\0';
+ LINE_CHECK("meshpath %s", data);
+ }
+ if (get80211val(s, IEEE80211_IOC_HWMP_ROOTMODE, &val) != -1) {
+ switch (val) {
+ case IEEE80211_HWMP_ROOTMODE_DISABLED:
+ LINE_CHECK("hwmprootmode DISABLED");
+ break;
+ case IEEE80211_HWMP_ROOTMODE_NORMAL:
+ LINE_CHECK("hwmprootmode NORMAL");
+ break;
+ case IEEE80211_HWMP_ROOTMODE_PROACTIVE:
+ LINE_CHECK("hwmprootmode PROACTIVE");
+ break;
+ case IEEE80211_HWMP_ROOTMODE_RANN:
+ LINE_CHECK("hwmprootmode RANN");
+ break;
+ default:
+ LINE_CHECK("hwmprootmode UNKNOWN(%d)", val);
+ break;
+ }
+ }
+ if (get80211val(s, IEEE80211_IOC_HWMP_MAXHOPS, &val) != -1) {
+ LINE_CHECK("hwmpmaxhops %u", val);
+ }
+ }
+
+ LINE_BREAK();
+}
+
+static int
+get80211(int s, int type, void *data, int len)
+{
+ struct ieee80211req ireq;
+
+ (void) memset(&ireq, 0, sizeof(ireq));
+ (void) strncpy(ireq.i_name, name, sizeof(ireq.i_name));
+ ireq.i_type = type;
+ ireq.i_data = data;
+ ireq.i_len = len;
+ return ioctl(s, SIOCG80211, &ireq);
+}
+
+static int
+get80211len(int s, int type, void *data, int len, int *plen)
+{
+ struct ieee80211req ireq;
+
+ (void) memset(&ireq, 0, sizeof(ireq));
+ (void) strncpy(ireq.i_name, name, sizeof(ireq.i_name));
+ ireq.i_type = type;
+ ireq.i_len = len;
+ assert(ireq.i_len == len); /* NB: check for 16-bit truncation */
+ ireq.i_data = data;
+ if (ioctl(s, SIOCG80211, &ireq) < 0)
+ return -1;
+ *plen = ireq.i_len;
+ return 0;
+}
+
+static int
+get80211val(int s, int type, int *val)
+{
+ struct ieee80211req ireq;
+
+ (void) memset(&ireq, 0, sizeof(ireq));
+ (void) strncpy(ireq.i_name, name, sizeof(ireq.i_name));
+ ireq.i_type = type;
+ if (ioctl(s, SIOCG80211, &ireq) < 0)
+ return -1;
+ *val = ireq.i_val;
+ return 0;
+}
+
+static void
+set80211(int s, int type, int val, int len, void *data)
+{
+ struct ieee80211req ireq;
+
+ (void) memset(&ireq, 0, sizeof(ireq));
+ (void) strncpy(ireq.i_name, name, sizeof(ireq.i_name));
+ ireq.i_type = type;
+ ireq.i_val = val;
+ ireq.i_len = len;
+ assert(ireq.i_len == len); /* NB: check for 16-bit truncation */
+ ireq.i_data = data;
+ if (ioctl(s, SIOCS80211, &ireq) < 0)
+ err(1, "SIOCS80211");
+}
+
+static const char *
+get_string(const char *val, const char *sep, u_int8_t *buf, int *lenp)
+{
+ int len;
+ int hexstr;
+ u_int8_t *p;
+
+ len = *lenp;
+ p = buf;
+ hexstr = (val[0] == '0' && tolower((u_char)val[1]) == 'x');
+ if (hexstr)
+ val += 2;
+ for (;;) {
+ if (*val == '\0')
+ break;
+ if (sep != NULL && strchr(sep, *val) != NULL) {
+ val++;
+ break;
+ }
+ if (hexstr) {
+ if (!isxdigit((u_char)val[0])) {
+ warnx("bad hexadecimal digits");
+ return NULL;
+ }
+ if (!isxdigit((u_char)val[1])) {
+ warnx("odd count hexadecimal digits");
+ return NULL;
+ }
+ }
+ if (p >= buf + len) {
+ if (hexstr)
+ warnx("hexadecimal digits too long");
+ else
+ warnx("string too long");
+ return NULL;
+ }
+ if (hexstr) {
+#define tohex(x) (isdigit(x) ? (x) - '0' : tolower(x) - 'a' + 10)
+ *p++ = (tohex((u_char)val[0]) << 4) |
+ tohex((u_char)val[1]);
+#undef tohex
+ val += 2;
+ } else
+ *p++ = *val++;
+ }
+ len = p - buf;
+ /* The string "-" is treated as the empty string. */
+ if (!hexstr && len == 1 && buf[0] == '-') {
+ len = 0;
+ memset(buf, 0, *lenp);
+ } else if (len < *lenp)
+ memset(p, 0, *lenp - len);
+ *lenp = len;
+ return val;
+}
+
+static void
+print_string(const u_int8_t *buf, int len)
+{
+ int i;
+ int hasspc;
+
+ i = 0;
+ hasspc = 0;
+ for (; i < len; i++) {
+ if (!isprint(buf[i]) && buf[i] != '\0')
+ break;
+ if (isspace(buf[i]))
+ hasspc++;
+ }
+ if (i == len) {
+ if (hasspc || len == 0 || buf[0] == '\0')
+ printf("\"%.*s\"", len, buf);
+ else
+ printf("%.*s", len, buf);
+ } else {
+ printf("0x");
+ for (i = 0; i < len; i++)
+ printf("%02x", buf[i]);
+ }
+}
+
+/*
+ * Virtual AP cloning support.
+ */
+static struct ieee80211_clone_params params = {
+ .icp_opmode = IEEE80211_M_STA, /* default to station mode */
+};
+
+static void
+wlan_create(int s, struct ifreq *ifr)
+{
+ static const uint8_t zerobssid[IEEE80211_ADDR_LEN];
+
+ if (params.icp_parent[0] == '\0')
+ errx(1, "must specify a parent device (wlandev) when creating "
+ "a wlan device");
+ if (params.icp_opmode == IEEE80211_M_WDS &&
+ memcmp(params.icp_bssid, zerobssid, sizeof(zerobssid)) == 0)
+ errx(1, "no bssid specified for WDS (use wlanbssid)");
+ ifr->ifr_data = (caddr_t) &params;
+ if (ioctl(s, SIOCIFCREATE2, ifr) < 0)
+ err(1, "SIOCIFCREATE2");
+}
+
+static
+DECL_CMD_FUNC(set80211clone_wlandev, arg, d)
+{
+ strlcpy(params.icp_parent, arg, IFNAMSIZ);
+}
+
+static
+DECL_CMD_FUNC(set80211clone_wlanbssid, arg, d)
+{
+ const struct ether_addr *ea;
+
+ ea = ether_aton(arg);
+ if (ea == NULL)
+ errx(1, "%s: cannot parse bssid", arg);
+ memcpy(params.icp_bssid, ea->octet, IEEE80211_ADDR_LEN);
+}
+
+static
+DECL_CMD_FUNC(set80211clone_wlanaddr, arg, d)
+{
+ const struct ether_addr *ea;
+
+ ea = ether_aton(arg);
+ if (ea == NULL)
+ errx(1, "%s: cannot parse address", arg);
+ memcpy(params.icp_macaddr, ea->octet, IEEE80211_ADDR_LEN);
+ params.icp_flags |= IEEE80211_CLONE_MACADDR;
+}
+
+static
+DECL_CMD_FUNC(set80211clone_wlanmode, arg, d)
+{
+#define iseq(a,b) (strncasecmp(a,b,sizeof(b)-1) == 0)
+ if (iseq(arg, "sta"))
+ params.icp_opmode = IEEE80211_M_STA;
+ else if (iseq(arg, "ahdemo") || iseq(arg, "adhoc-demo"))
+ params.icp_opmode = IEEE80211_M_AHDEMO;
+ else if (iseq(arg, "ibss") || iseq(arg, "adhoc"))
+ params.icp_opmode = IEEE80211_M_IBSS;
+ else if (iseq(arg, "ap") || iseq(arg, "host"))
+ params.icp_opmode = IEEE80211_M_HOSTAP;
+ else if (iseq(arg, "wds"))
+ params.icp_opmode = IEEE80211_M_WDS;
+ else if (iseq(arg, "monitor"))
+ params.icp_opmode = IEEE80211_M_MONITOR;
+ else if (iseq(arg, "tdma")) {
+ params.icp_opmode = IEEE80211_M_AHDEMO;
+ params.icp_flags |= IEEE80211_CLONE_TDMA;
+ } else if (iseq(arg, "mesh") || iseq(arg, "mp")) /* mesh point */
+ params.icp_opmode = IEEE80211_M_MBSS;
+ else
+ errx(1, "Don't know to create %s for %s", arg, name);
+#undef iseq
+}
+
+static void
+set80211clone_beacons(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ /* NB: inverted sense */
+ if (d)
+ params.icp_flags &= ~IEEE80211_CLONE_NOBEACONS;
+ else
+ params.icp_flags |= IEEE80211_CLONE_NOBEACONS;
+}
+
+static void
+set80211clone_bssid(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ if (d)
+ params.icp_flags |= IEEE80211_CLONE_BSSID;
+ else
+ params.icp_flags &= ~IEEE80211_CLONE_BSSID;
+}
+
+static void
+set80211clone_wdslegacy(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ if (d)
+ params.icp_flags |= IEEE80211_CLONE_WDSLEGACY;
+ else
+ params.icp_flags &= ~IEEE80211_CLONE_WDSLEGACY;
+}
+
+static struct cmd ieee80211_cmds[] = {
+ DEF_CMD_ARG("ssid", set80211ssid),
+ DEF_CMD_ARG("nwid", set80211ssid),
+ DEF_CMD_ARG("meshid", set80211meshid),
+ DEF_CMD_ARG("stationname", set80211stationname),
+ DEF_CMD_ARG("station", set80211stationname), /* BSD/OS */
+ DEF_CMD_ARG("channel", set80211channel),
+ DEF_CMD_ARG("authmode", set80211authmode),
+ DEF_CMD_ARG("powersavemode", set80211powersavemode),
+ DEF_CMD("powersave", 1, set80211powersave),
+ DEF_CMD("-powersave", 0, set80211powersave),
+ DEF_CMD_ARG("powersavesleep", set80211powersavesleep),
+ DEF_CMD_ARG("wepmode", set80211wepmode),
+ DEF_CMD("wep", 1, set80211wep),
+ DEF_CMD("-wep", 0, set80211wep),
+ DEF_CMD_ARG("deftxkey", set80211weptxkey),
+ DEF_CMD_ARG("weptxkey", set80211weptxkey),
+ DEF_CMD_ARG("wepkey", set80211wepkey),
+ DEF_CMD_ARG("nwkey", set80211nwkey), /* NetBSD */
+ DEF_CMD("-nwkey", 0, set80211wep), /* NetBSD */
+ DEF_CMD_ARG("rtsthreshold", set80211rtsthreshold),
+ DEF_CMD_ARG("protmode", set80211protmode),
+ DEF_CMD_ARG("txpower", set80211txpower),
+ DEF_CMD_ARG("roaming", set80211roaming),
+ DEF_CMD("wme", 1, set80211wme),
+ DEF_CMD("-wme", 0, set80211wme),
+ DEF_CMD("wmm", 1, set80211wme),
+ DEF_CMD("-wmm", 0, set80211wme),
+ DEF_CMD("hidessid", 1, set80211hidessid),
+ DEF_CMD("-hidessid", 0, set80211hidessid),
+ DEF_CMD("apbridge", 1, set80211apbridge),
+ DEF_CMD("-apbridge", 0, set80211apbridge),
+ DEF_CMD_ARG("chanlist", set80211chanlist),
+ DEF_CMD_ARG("bssid", set80211bssid),
+ DEF_CMD_ARG("ap", set80211bssid),
+ DEF_CMD("scan", 0, set80211scan),
+ DEF_CMD_ARG("list", set80211list),
+ DEF_CMD_ARG2("cwmin", set80211cwmin),
+ DEF_CMD_ARG2("cwmax", set80211cwmax),
+ DEF_CMD_ARG2("aifs", set80211aifs),
+ DEF_CMD_ARG2("txoplimit", set80211txoplimit),
+ DEF_CMD_ARG("acm", set80211acm),
+ DEF_CMD_ARG("-acm", set80211noacm),
+ DEF_CMD_ARG("ack", set80211ackpolicy),
+ DEF_CMD_ARG("-ack", set80211noackpolicy),
+ DEF_CMD_ARG2("bss:cwmin", set80211bsscwmin),
+ DEF_CMD_ARG2("bss:cwmax", set80211bsscwmax),
+ DEF_CMD_ARG2("bss:aifs", set80211bssaifs),
+ DEF_CMD_ARG2("bss:txoplimit", set80211bsstxoplimit),
+ DEF_CMD_ARG("dtimperiod", set80211dtimperiod),
+ DEF_CMD_ARG("bintval", set80211bintval),
+ DEF_CMD("mac:open", IEEE80211_MACCMD_POLICY_OPEN, set80211maccmd),
+ DEF_CMD("mac:allow", IEEE80211_MACCMD_POLICY_ALLOW, set80211maccmd),
+ DEF_CMD("mac:deny", IEEE80211_MACCMD_POLICY_DENY, set80211maccmd),
+ DEF_CMD("mac:radius", IEEE80211_MACCMD_POLICY_RADIUS, set80211maccmd),
+ DEF_CMD("mac:flush", IEEE80211_MACCMD_FLUSH, set80211maccmd),
+ DEF_CMD("mac:detach", IEEE80211_MACCMD_DETACH, set80211maccmd),
+ DEF_CMD_ARG("mac:add", set80211addmac),
+ DEF_CMD_ARG("mac:del", set80211delmac),
+ DEF_CMD_ARG("mac:kick", set80211kickmac),
+ DEF_CMD("pureg", 1, set80211pureg),
+ DEF_CMD("-pureg", 0, set80211pureg),
+ DEF_CMD("ff", 1, set80211fastframes),
+ DEF_CMD("-ff", 0, set80211fastframes),
+ DEF_CMD("dturbo", 1, set80211dturbo),
+ DEF_CMD("-dturbo", 0, set80211dturbo),
+ DEF_CMD("bgscan", 1, set80211bgscan),
+ DEF_CMD("-bgscan", 0, set80211bgscan),
+ DEF_CMD_ARG("bgscanidle", set80211bgscanidle),
+ DEF_CMD_ARG("bgscanintvl", set80211bgscanintvl),
+ DEF_CMD_ARG("scanvalid", set80211scanvalid),
+ DEF_CMD("quiet", 1, set80211quiet),
+ DEF_CMD("-quiet", 0, set80211quiet),
+ DEF_CMD_ARG("quiet_count", set80211quietcount),
+ DEF_CMD_ARG("quiet_period", set80211quietperiod),
+ DEF_CMD_ARG("quiet_dur", set80211quietduration),
+ DEF_CMD_ARG("quiet_offset", set80211quietoffset),
+ DEF_CMD_ARG("roam:rssi", set80211roamrssi),
+ DEF_CMD_ARG("roam:rate", set80211roamrate),
+ DEF_CMD_ARG("mcastrate", set80211mcastrate),
+ DEF_CMD_ARG("ucastrate", set80211ucastrate),
+ DEF_CMD_ARG("mgtrate", set80211mgtrate),
+ DEF_CMD_ARG("mgmtrate", set80211mgtrate),
+ DEF_CMD_ARG("maxretry", set80211maxretry),
+ DEF_CMD_ARG("fragthreshold", set80211fragthreshold),
+ DEF_CMD("burst", 1, set80211burst),
+ DEF_CMD("-burst", 0, set80211burst),
+ DEF_CMD_ARG("bmiss", set80211bmissthreshold),
+ DEF_CMD_ARG("bmissthreshold", set80211bmissthreshold),
+ DEF_CMD("shortgi", 1, set80211shortgi),
+ DEF_CMD("-shortgi", 0, set80211shortgi),
+ DEF_CMD("ampdurx", 2, set80211ampdu),
+ DEF_CMD("-ampdurx", -2, set80211ampdu),
+ DEF_CMD("ampdutx", 1, set80211ampdu),
+ DEF_CMD("-ampdutx", -1, set80211ampdu),
+ DEF_CMD("ampdu", 3, set80211ampdu), /* NB: tx+rx */
+ DEF_CMD("-ampdu", -3, set80211ampdu),
+ DEF_CMD_ARG("ampdulimit", set80211ampdulimit),
+ DEF_CMD_ARG("ampdudensity", set80211ampdudensity),
+ DEF_CMD("amsdurx", 2, set80211amsdu),
+ DEF_CMD("-amsdurx", -2, set80211amsdu),
+ DEF_CMD("amsdutx", 1, set80211amsdu),
+ DEF_CMD("-amsdutx", -1, set80211amsdu),
+ DEF_CMD("amsdu", 3, set80211amsdu), /* NB: tx+rx */
+ DEF_CMD("-amsdu", -3, set80211amsdu),
+ DEF_CMD_ARG("amsdulimit", set80211amsdulimit),
+ DEF_CMD("puren", 1, set80211puren),
+ DEF_CMD("-puren", 0, set80211puren),
+ DEF_CMD("doth", 1, set80211doth),
+ DEF_CMD("-doth", 0, set80211doth),
+ DEF_CMD("dfs", 1, set80211dfs),
+ DEF_CMD("-dfs", 0, set80211dfs),
+ DEF_CMD("htcompat", 1, set80211htcompat),
+ DEF_CMD("-htcompat", 0, set80211htcompat),
+ DEF_CMD("dwds", 1, set80211dwds),
+ DEF_CMD("-dwds", 0, set80211dwds),
+ DEF_CMD("inact", 1, set80211inact),
+ DEF_CMD("-inact", 0, set80211inact),
+ DEF_CMD("tsn", 1, set80211tsn),
+ DEF_CMD("-tsn", 0, set80211tsn),
+ DEF_CMD_ARG("regdomain", set80211regdomain),
+ DEF_CMD_ARG("country", set80211country),
+ DEF_CMD("indoor", 'I', set80211location),
+ DEF_CMD("-indoor", 'O', set80211location),
+ DEF_CMD("outdoor", 'O', set80211location),
+ DEF_CMD("-outdoor", 'I', set80211location),
+ DEF_CMD("anywhere", ' ', set80211location),
+ DEF_CMD("ecm", 1, set80211ecm),
+ DEF_CMD("-ecm", 0, set80211ecm),
+ DEF_CMD("dotd", 1, set80211dotd),
+ DEF_CMD("-dotd", 0, set80211dotd),
+ DEF_CMD_ARG("htprotmode", set80211htprotmode),
+ DEF_CMD("ht20", 1, set80211htconf),
+ DEF_CMD("-ht20", 0, set80211htconf),
+ DEF_CMD("ht40", 3, set80211htconf), /* NB: 20+40 */
+ DEF_CMD("-ht40", 0, set80211htconf),
+ DEF_CMD("ht", 3, set80211htconf), /* NB: 20+40 */
+ DEF_CMD("-ht", 0, set80211htconf),
+ DEF_CMD("rifs", 1, set80211rifs),
+ DEF_CMD("-rifs", 0, set80211rifs),
+ DEF_CMD("smps", IEEE80211_HTCAP_SMPS_ENA, set80211smps),
+ DEF_CMD("smpsdyn", IEEE80211_HTCAP_SMPS_DYNAMIC, set80211smps),
+ DEF_CMD("-smps", IEEE80211_HTCAP_SMPS_OFF, set80211smps),
+ /* XXX for testing */
+ DEF_CMD_ARG("chanswitch", set80211chanswitch),
+
+ DEF_CMD_ARG("tdmaslot", set80211tdmaslot),
+ DEF_CMD_ARG("tdmaslotcnt", set80211tdmaslotcnt),
+ DEF_CMD_ARG("tdmaslotlen", set80211tdmaslotlen),
+ DEF_CMD_ARG("tdmabintval", set80211tdmabintval),
+
+ DEF_CMD_ARG("meshttl", set80211meshttl),
+ DEF_CMD("meshforward", 1, set80211meshforward),
+ DEF_CMD("-meshforward", 0, set80211meshforward),
+ DEF_CMD("meshgate", 1, set80211meshgate),
+ DEF_CMD("-meshgate", 0, set80211meshgate),
+ DEF_CMD("meshpeering", 1, set80211meshpeering),
+ DEF_CMD("-meshpeering", 0, set80211meshpeering),
+ DEF_CMD_ARG("meshmetric", set80211meshmetric),
+ DEF_CMD_ARG("meshpath", set80211meshpath),
+ DEF_CMD("meshrt:flush", IEEE80211_MESH_RTCMD_FLUSH, set80211meshrtcmd),
+ DEF_CMD_ARG("meshrt:add", set80211addmeshrt),
+ DEF_CMD_ARG("meshrt:del", set80211delmeshrt),
+ DEF_CMD_ARG("hwmprootmode", set80211hwmprootmode),
+ DEF_CMD_ARG("hwmpmaxhops", set80211hwmpmaxhops),
+
+ /* vap cloning support */
+ DEF_CLONE_CMD_ARG("wlanaddr", set80211clone_wlanaddr),
+ DEF_CLONE_CMD_ARG("wlanbssid", set80211clone_wlanbssid),
+ DEF_CLONE_CMD_ARG("wlandev", set80211clone_wlandev),
+ DEF_CLONE_CMD_ARG("wlanmode", set80211clone_wlanmode),
+ DEF_CLONE_CMD("beacons", 1, set80211clone_beacons),
+ DEF_CLONE_CMD("-beacons", 0, set80211clone_beacons),
+ DEF_CLONE_CMD("bssid", 1, set80211clone_bssid),
+ DEF_CLONE_CMD("-bssid", 0, set80211clone_bssid),
+ DEF_CLONE_CMD("wdslegacy", 1, set80211clone_wdslegacy),
+ DEF_CLONE_CMD("-wdslegacy", 0, set80211clone_wdslegacy),
+};
+static struct afswtch af_ieee80211 = {
+ .af_name = "af_ieee80211",
+ .af_af = AF_UNSPEC,
+ .af_other_status = ieee80211_status,
+};
+
+static __constructor void
+ieee80211_ctor(void)
+{
+#define N(a) (sizeof(a) / sizeof(a[0]))
+ int i;
+
+ for (i = 0; i < N(ieee80211_cmds); i++)
+ cmd_register(&ieee80211_cmds[i]);
+ af_register(&af_ieee80211);
+ clone_setdefcallback("wlan", wlan_create);
+#undef N
+}
diff --git a/sbin/ifconfig/iflagg.c b/sbin/ifconfig/iflagg.c
new file mode 100644
index 0000000..51a6faa
--- /dev/null
+++ b/sbin/ifconfig/iflagg.c
@@ -0,0 +1,314 @@
+/*-
+ */
+
+#ifndef lint
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <net/ethernet.h>
+#include <net/if.h>
+#include <net/if_lagg.h>
+#include <net/ieee8023ad_lacp.h>
+#include <net/route.h>
+
+#include <ctype.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <err.h>
+#include <errno.h>
+
+#include "ifconfig.h"
+
+char lacpbuf[120]; /* LACP peer '[(a,a,a),(p,p,p)]' */
+
+static void
+setlaggport(const char *val, int d, int s, const struct afswtch *afp)
+{
+ struct lagg_reqport rp;
+
+ bzero(&rp, sizeof(rp));
+ strlcpy(rp.rp_ifname, name, sizeof(rp.rp_ifname));
+ strlcpy(rp.rp_portname, val, sizeof(rp.rp_portname));
+
+ /* Don't choke if the port is already in this lagg. */
+ if (ioctl(s, SIOCSLAGGPORT, &rp) && errno != EEXIST)
+ err(1, "SIOCSLAGGPORT");
+}
+
+static void
+unsetlaggport(const char *val, int d, int s, const struct afswtch *afp)
+{
+ struct lagg_reqport rp;
+
+ bzero(&rp, sizeof(rp));
+ strlcpy(rp.rp_ifname, name, sizeof(rp.rp_ifname));
+ strlcpy(rp.rp_portname, val, sizeof(rp.rp_portname));
+
+ if (ioctl(s, SIOCSLAGGDELPORT, &rp))
+ err(1, "SIOCSLAGGDELPORT");
+}
+
+static void
+setlaggproto(const char *val, int d, int s, const struct afswtch *afp)
+{
+ struct lagg_protos lpr[] = LAGG_PROTOS;
+ struct lagg_reqall ra;
+ int i;
+
+ bzero(&ra, sizeof(ra));
+ ra.ra_proto = LAGG_PROTO_MAX;
+
+ for (i = 0; i < nitems(lpr); i++) {
+ if (strcmp(val, lpr[i].lpr_name) == 0) {
+ ra.ra_proto = lpr[i].lpr_proto;
+ break;
+ }
+ }
+ if (ra.ra_proto == LAGG_PROTO_MAX)
+ errx(1, "Invalid aggregation protocol: %s", val);
+
+ strlcpy(ra.ra_ifname, name, sizeof(ra.ra_ifname));
+ if (ioctl(s, SIOCSLAGG, &ra) != 0)
+ err(1, "SIOCSLAGG");
+}
+
+static void
+setlaggflowidshift(const char *val, int d, int s, const struct afswtch *afp)
+{
+ struct lagg_reqopts ro;
+
+ bzero(&ro, sizeof(ro));
+ ro.ro_opts = LAGG_OPT_FLOWIDSHIFT;
+ strlcpy(ro.ro_ifname, name, sizeof(ro.ro_ifname));
+ ro.ro_flowid_shift = (int)strtol(val, NULL, 10);
+ if (ro.ro_flowid_shift & ~LAGG_OPT_FLOWIDSHIFT_MASK)
+ errx(1, "Invalid flowid_shift option: %s", val);
+
+ if (ioctl(s, SIOCSLAGGOPTS, &ro) != 0)
+ err(1, "SIOCSLAGGOPTS");
+}
+
+static void
+setlaggsetopt(const char *val, int d, int s, const struct afswtch *afp)
+{
+ struct lagg_reqopts ro;
+
+ bzero(&ro, sizeof(ro));
+ ro.ro_opts = d;
+ switch (ro.ro_opts) {
+ case LAGG_OPT_USE_FLOWID:
+ case -LAGG_OPT_USE_FLOWID:
+ case LAGG_OPT_LACP_STRICT:
+ case -LAGG_OPT_LACP_STRICT:
+ case LAGG_OPT_LACP_TXTEST:
+ case -LAGG_OPT_LACP_TXTEST:
+ case LAGG_OPT_LACP_RXTEST:
+ case -LAGG_OPT_LACP_RXTEST:
+ break;
+ default:
+ err(1, "Invalid lagg option");
+ }
+ strlcpy(ro.ro_ifname, name, sizeof(ro.ro_ifname));
+
+ if (ioctl(s, SIOCSLAGGOPTS, &ro) != 0)
+ err(1, "SIOCSLAGGOPTS");
+}
+
+static void
+setlagghash(const char *val, int d, int s, const struct afswtch *afp)
+{
+ struct lagg_reqflags rf;
+ char *str, *tmp, *tok;
+
+
+ rf.rf_flags = 0;
+ str = tmp = strdup(val);
+ while ((tok = strsep(&tmp, ",")) != NULL) {
+ if (strcmp(tok, "l2") == 0)
+ rf.rf_flags |= LAGG_F_HASHL2;
+ else if (strcmp(tok, "l3") == 0)
+ rf.rf_flags |= LAGG_F_HASHL3;
+ else if (strcmp(tok, "l4") == 0)
+ rf.rf_flags |= LAGG_F_HASHL4;
+ else
+ errx(1, "Invalid lagghash option: %s", tok);
+ }
+ free(str);
+ if (rf.rf_flags == 0)
+ errx(1, "No lagghash options supplied");
+
+ strlcpy(rf.rf_ifname, name, sizeof(rf.rf_ifname));
+ if (ioctl(s, SIOCSLAGGHASH, &rf))
+ err(1, "SIOCSLAGGHASH");
+}
+
+static char *
+lacp_format_mac(const uint8_t *mac, char *buf, size_t buflen)
+{
+ snprintf(buf, buflen, "%02X-%02X-%02X-%02X-%02X-%02X",
+ (int)mac[0], (int)mac[1], (int)mac[2], (int)mac[3],
+ (int)mac[4], (int)mac[5]);
+
+ return (buf);
+}
+
+static char *
+lacp_format_peer(struct lacp_opreq *req, const char *sep)
+{
+ char macbuf1[20];
+ char macbuf2[20];
+
+ snprintf(lacpbuf, sizeof(lacpbuf),
+ "[(%04X,%s,%04X,%04X,%04X),%s(%04X,%s,%04X,%04X,%04X)]",
+ req->actor_prio,
+ lacp_format_mac(req->actor_mac, macbuf1, sizeof(macbuf1)),
+ req->actor_key, req->actor_portprio, req->actor_portno, sep,
+ req->partner_prio,
+ lacp_format_mac(req->partner_mac, macbuf2, sizeof(macbuf2)),
+ req->partner_key, req->partner_portprio, req->partner_portno);
+
+ return(lacpbuf);
+}
+
+static void
+lagg_status(int s)
+{
+ struct lagg_protos lpr[] = LAGG_PROTOS;
+ struct lagg_reqport rp, rpbuf[LAGG_MAX_PORTS];
+ struct lagg_reqall ra;
+ struct lagg_reqopts ro;
+ struct lagg_reqflags rf;
+ struct lacp_opreq *lp;
+ const char *proto = "<unknown>";
+ int i, isport = 0;
+
+ bzero(&rp, sizeof(rp));
+ bzero(&ra, sizeof(ra));
+ bzero(&ro, sizeof(ro));
+
+ strlcpy(rp.rp_ifname, name, sizeof(rp.rp_ifname));
+ strlcpy(rp.rp_portname, name, sizeof(rp.rp_portname));
+
+ if (ioctl(s, SIOCGLAGGPORT, &rp) == 0)
+ isport = 1;
+
+ strlcpy(ra.ra_ifname, name, sizeof(ra.ra_ifname));
+ ra.ra_size = sizeof(rpbuf);
+ ra.ra_port = rpbuf;
+
+ strlcpy(ro.ro_ifname, name, sizeof(ro.ro_ifname));
+ ioctl(s, SIOCGLAGGOPTS, &ro);
+
+ strlcpy(rf.rf_ifname, name, sizeof(rf.rf_ifname));
+ if (ioctl(s, SIOCGLAGGFLAGS, &rf) != 0)
+ rf.rf_flags = 0;
+
+ if (ioctl(s, SIOCGLAGG, &ra) == 0) {
+ lp = (struct lacp_opreq *)&ra.ra_lacpreq;
+
+ for (i = 0; i < nitems(lpr); i++) {
+ if (ra.ra_proto == lpr[i].lpr_proto) {
+ proto = lpr[i].lpr_name;
+ break;
+ }
+ }
+
+ printf("\tlaggproto %s", proto);
+ if (rf.rf_flags & LAGG_F_HASHMASK) {
+ const char *sep = "";
+
+ printf(" lagghash ");
+ if (rf.rf_flags & LAGG_F_HASHL2) {
+ printf("%sl2", sep);
+ sep = ",";
+ }
+ if (rf.rf_flags & LAGG_F_HASHL3) {
+ printf("%sl3", sep);
+ sep = ",";
+ }
+ if (rf.rf_flags & LAGG_F_HASHL4) {
+ printf("%sl4", sep);
+ sep = ",";
+ }
+ }
+ if (isport)
+ printf(" laggdev %s", rp.rp_ifname);
+ putchar('\n');
+ if (verbose) {
+ printf("\tlagg options:\n");
+ printb("\t\tflags", ro.ro_opts, LAGG_OPT_BITS);
+ putchar('\n');
+ printf("\t\tflowid_shift: %d\n", ro.ro_flowid_shift);
+ printf("\tlagg statistics:\n");
+ printf("\t\tactive ports: %d\n", ro.ro_active);
+ printf("\t\tflapping: %u\n", ro.ro_flapping);
+ if (ra.ra_proto == LAGG_PROTO_LACP) {
+ printf("\tlag id: %s\n",
+ lacp_format_peer(lp, "\n\t\t "));
+ }
+ }
+
+ for (i = 0; i < ra.ra_ports; i++) {
+ lp = (struct lacp_opreq *)&rpbuf[i].rp_lacpreq;
+ printf("\tlaggport: %s ", rpbuf[i].rp_portname);
+ printb("flags", rpbuf[i].rp_flags, LAGG_PORT_BITS);
+ if (verbose && ra.ra_proto == LAGG_PROTO_LACP)
+ printb(" state", lp->actor_state,
+ LACP_STATE_BITS);
+ putchar('\n');
+ if (verbose && ra.ra_proto == LAGG_PROTO_LACP)
+ printf("\t\t%s\n",
+ lacp_format_peer(lp, "\n\t\t "));
+ }
+
+ if (0 /* XXX */) {
+ printf("\tsupported aggregation protocols:\n");
+ for (i = 0; i < (sizeof(lpr) / sizeof(lpr[0])); i++)
+ printf("\t\tlaggproto %s\n", lpr[i].lpr_name);
+ }
+ }
+}
+
+static struct cmd lagg_cmds[] = {
+ DEF_CMD_ARG("laggport", setlaggport),
+ DEF_CMD_ARG("-laggport", unsetlaggport),
+ DEF_CMD_ARG("laggproto", setlaggproto),
+ DEF_CMD_ARG("lagghash", setlagghash),
+ DEF_CMD("use_flowid", LAGG_OPT_USE_FLOWID, setlaggsetopt),
+ DEF_CMD("-use_flowid", -LAGG_OPT_USE_FLOWID, setlaggsetopt),
+ DEF_CMD("lacp_strict", LAGG_OPT_LACP_STRICT, setlaggsetopt),
+ DEF_CMD("-lacp_strict", -LAGG_OPT_LACP_STRICT, setlaggsetopt),
+ DEF_CMD("lacp_txtest", LAGG_OPT_LACP_TXTEST, setlaggsetopt),
+ DEF_CMD("-lacp_txtest", -LAGG_OPT_LACP_TXTEST, setlaggsetopt),
+ DEF_CMD("lacp_rxtest", LAGG_OPT_LACP_RXTEST, setlaggsetopt),
+ DEF_CMD("-lacp_rxtest", -LAGG_OPT_LACP_RXTEST, setlaggsetopt),
+ DEF_CMD_ARG("flowid_shift", setlaggflowidshift),
+};
+static struct afswtch af_lagg = {
+ .af_name = "af_lagg",
+ .af_af = AF_UNSPEC,
+ .af_other_status = lagg_status,
+};
+
+static __constructor void
+lagg_ctor(void)
+{
+#define N(a) (sizeof(a) / sizeof(a[0]))
+ int i;
+
+ for (i = 0; i < N(lagg_cmds); i++)
+ cmd_register(&lagg_cmds[i]);
+ af_register(&af_lagg);
+#undef N
+}
diff --git a/sbin/ifconfig/ifmac.c b/sbin/ifconfig/ifmac.c
new file mode 100644
index 0000000..d7e1e5b
--- /dev/null
+++ b/sbin/ifconfig/ifmac.c
@@ -0,0 +1,121 @@
+/*-
+ * Copyright (c) 2001 Networks Associates Technology, Inc.
+ * All rights reserved.
+ *
+ * This software was developed for the FreeBSD Project by NAI Labs, the
+ * Security Research Division of Network Associates, Inc. under
+ * DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), as part of the DARPA
+ * CHATS research program.
+ *
+ * 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 AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/mac.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+
+#include <net/if.h>
+#include <net/route.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "ifconfig.h"
+
+static void
+maclabel_status(int s)
+{
+ struct ifreq ifr;
+ mac_t label;
+ char *label_text;
+
+ memset(&ifr, 0, sizeof(ifr));
+ strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
+
+ if (mac_prepare_ifnet_label(&label) == -1)
+ return;
+ ifr.ifr_ifru.ifru_data = (void *)label;
+ if (ioctl(s, SIOCGIFMAC, &ifr) == -1)
+ goto mac_free;
+
+
+ if (mac_to_text(label, &label_text) == -1)
+ goto mac_free;
+
+ if (strlen(label_text) != 0)
+ printf("\tmaclabel %s\n", label_text);
+ free(label_text);
+
+mac_free:
+ mac_free(label);
+}
+
+static void
+setifmaclabel(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ struct ifreq ifr;
+ mac_t label;
+ int error;
+
+ if (mac_from_text(&label, val) == -1) {
+ perror(val);
+ return;
+ }
+
+ memset(&ifr, 0, sizeof(ifr));
+ strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
+ ifr.ifr_ifru.ifru_data = (void *)label;
+
+ error = ioctl(s, SIOCSIFMAC, &ifr);
+ mac_free(label);
+ if (error == -1)
+ perror("setifmac");
+}
+
+static struct cmd mac_cmds[] = {
+ DEF_CMD_ARG("maclabel", setifmaclabel),
+};
+static struct afswtch af_mac = {
+ .af_name = "af_maclabel",
+ .af_af = AF_UNSPEC,
+ .af_other_status = maclabel_status,
+};
+
+static __constructor void
+mac_ctor(void)
+{
+#define N(a) (sizeof(a) / sizeof(a[0]))
+ size_t i;
+
+ for (i = 0; i < N(mac_cmds); i++)
+ cmd_register(&mac_cmds[i]);
+ af_register(&af_mac);
+#undef N
+}
diff --git a/sbin/ifconfig/ifmedia.c b/sbin/ifconfig/ifmedia.c
new file mode 100644
index 0000000..eee3391
--- /dev/null
+++ b/sbin/ifconfig/ifmedia.c
@@ -0,0 +1,855 @@
+/* $NetBSD: ifconfig.c,v 1.34 1997/04/21 01:17:58 lukem Exp $ */
+/* $FreeBSD$ */
+
+/*
+ * Copyright (c) 1997 Jason R. Thorpe.
+ * 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 for the NetBSD Project
+ * by Jason R. Thorpe.
+ * 4. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 1983, 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.
+ * 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.
+ */
+
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+#include <sys/time.h>
+
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <net/if_types.h>
+#include <net/if_media.h>
+#include <net/route.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "ifconfig.h"
+
+static void domediaopt(const char *, int, int);
+static int get_media_subtype(int, const char *);
+static int get_media_mode(int, const char *);
+static int get_media_options(int, const char *);
+static int lookup_media_word(struct ifmedia_description *, const char *);
+static void print_media_word(int, int);
+static void print_media_word_ifconfig(int);
+
+static struct ifmedia_description *get_toptype_desc(int);
+static struct ifmedia_type_to_subtype *get_toptype_ttos(int);
+static struct ifmedia_description *get_subtype_desc(int,
+ struct ifmedia_type_to_subtype *ttos);
+
+#define IFM_OPMODE(x) \
+ ((x) & (IFM_IEEE80211_ADHOC | IFM_IEEE80211_HOSTAP | \
+ IFM_IEEE80211_IBSS | IFM_IEEE80211_WDS | IFM_IEEE80211_MONITOR | \
+ IFM_IEEE80211_MBSS))
+#define IFM_IEEE80211_STA 0
+
+static void
+media_status(int s)
+{
+ struct ifmediareq ifmr;
+ int *media_list, i;
+ int xmedia = 1;
+
+ (void) memset(&ifmr, 0, sizeof(ifmr));
+ (void) strncpy(ifmr.ifm_name, name, sizeof(ifmr.ifm_name));
+
+ /*
+ * Check if interface supports extended media types.
+ */
+ if (ioctl(s, SIOCGIFXMEDIA, (caddr_t)&ifmr) < 0)
+ xmedia = 0;
+ if (xmedia == 0 && ioctl(s, SIOCGIFMEDIA, (caddr_t)&ifmr) < 0) {
+ /*
+ * Interface doesn't support SIOC{G,S}IFMEDIA.
+ */
+ return;
+ }
+
+ if (ifmr.ifm_count == 0) {
+ warnx("%s: no media types?", name);
+ return;
+ }
+
+ media_list = (int *)malloc(ifmr.ifm_count * sizeof(int));
+ if (media_list == NULL)
+ err(1, "malloc");
+ ifmr.ifm_ulist = media_list;
+
+ if (xmedia) {
+ if (ioctl(s, SIOCGIFXMEDIA, (caddr_t)&ifmr) < 0)
+ err(1, "SIOCGIFXMEDIA");
+ } else {
+ if (ioctl(s, SIOCGIFMEDIA, (caddr_t)&ifmr) < 0)
+ err(1, "SIOCGIFMEDIA");
+ }
+
+ printf("\tmedia: ");
+ print_media_word(ifmr.ifm_current, 1);
+ if (ifmr.ifm_active != ifmr.ifm_current) {
+ putchar(' ');
+ putchar('(');
+ print_media_word(ifmr.ifm_active, 0);
+ putchar(')');
+ }
+
+ putchar('\n');
+
+ if (ifmr.ifm_status & IFM_AVALID) {
+ printf("\tstatus: ");
+ switch (IFM_TYPE(ifmr.ifm_active)) {
+ case IFM_ETHER:
+ case IFM_ATM:
+ if (ifmr.ifm_status & IFM_ACTIVE)
+ printf("active");
+ else
+ printf("no carrier");
+ break;
+
+ case IFM_FDDI:
+ case IFM_TOKEN:
+ if (ifmr.ifm_status & IFM_ACTIVE)
+ printf("inserted");
+ else
+ printf("no ring");
+ break;
+
+ case IFM_IEEE80211:
+ if (ifmr.ifm_status & IFM_ACTIVE) {
+ /* NB: only sta mode associates */
+ if (IFM_OPMODE(ifmr.ifm_active) == IFM_IEEE80211_STA)
+ printf("associated");
+ else
+ printf("running");
+ } else
+ printf("no carrier");
+ break;
+ }
+ putchar('\n');
+ }
+
+ if (ifmr.ifm_count > 0 && supmedia) {
+ printf("\tsupported media:\n");
+ for (i = 0; i < ifmr.ifm_count; i++) {
+ printf("\t\t");
+ print_media_word_ifconfig(media_list[i]);
+ putchar('\n');
+ }
+ }
+
+ free(media_list);
+}
+
+struct ifmediareq *
+ifmedia_getstate(int s)
+{
+ static struct ifmediareq *ifmr = NULL;
+ int *mwords;
+ int xmedia = 1;
+
+ if (ifmr == NULL) {
+ ifmr = (struct ifmediareq *)malloc(sizeof(struct ifmediareq));
+ if (ifmr == NULL)
+ err(1, "malloc");
+
+ (void) memset(ifmr, 0, sizeof(struct ifmediareq));
+ (void) strncpy(ifmr->ifm_name, name,
+ sizeof(ifmr->ifm_name));
+
+ ifmr->ifm_count = 0;
+ ifmr->ifm_ulist = NULL;
+
+ /*
+ * We must go through the motions of reading all
+ * supported media because we need to know both
+ * the current media type and the top-level type.
+ */
+
+ if (ioctl(s, SIOCGIFXMEDIA, (caddr_t)ifmr) < 0) {
+ xmedia = 0;
+ }
+ if (xmedia == 0 && ioctl(s, SIOCGIFMEDIA, (caddr_t)ifmr) < 0) {
+ err(1, "SIOCGIFMEDIA");
+ }
+
+ if (ifmr->ifm_count == 0)
+ errx(1, "%s: no media types?", name);
+
+ mwords = (int *)malloc(ifmr->ifm_count * sizeof(int));
+ if (mwords == NULL)
+ err(1, "malloc");
+
+ ifmr->ifm_ulist = mwords;
+ if (xmedia) {
+ if (ioctl(s, SIOCGIFXMEDIA, (caddr_t)ifmr) < 0)
+ err(1, "SIOCGIFXMEDIA");
+ } else {
+ if (ioctl(s, SIOCGIFMEDIA, (caddr_t)ifmr) < 0)
+ err(1, "SIOCGIFMEDIA");
+ }
+ }
+
+ return ifmr;
+}
+
+static void
+setifmediacallback(int s, void *arg)
+{
+ struct ifmediareq *ifmr = (struct ifmediareq *)arg;
+ static int did_it = 0;
+
+ if (!did_it) {
+ ifr.ifr_media = ifmr->ifm_current;
+ if (ioctl(s, SIOCSIFMEDIA, (caddr_t)&ifr) < 0)
+ err(1, "SIOCSIFMEDIA (media)");
+ free(ifmr->ifm_ulist);
+ free(ifmr);
+ did_it = 1;
+ }
+}
+
+static void
+setmedia(const char *val, int d, int s, const struct afswtch *afp)
+{
+ struct ifmediareq *ifmr;
+ int subtype;
+
+ ifmr = ifmedia_getstate(s);
+
+ /*
+ * We are primarily concerned with the top-level type.
+ * However, "current" may be only IFM_NONE, so we just look
+ * for the top-level type in the first "supported type"
+ * entry.
+ *
+ * (I'm assuming that all supported media types for a given
+ * interface will be the same top-level type..)
+ */
+ subtype = get_media_subtype(IFM_TYPE(ifmr->ifm_ulist[0]), val);
+
+ strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
+ ifr.ifr_media = (ifmr->ifm_current & IFM_IMASK) |
+ IFM_TYPE(ifmr->ifm_ulist[0]) | subtype;
+
+ ifmr->ifm_current = ifr.ifr_media;
+ callback_register(setifmediacallback, (void *)ifmr);
+}
+
+static void
+setmediaopt(const char *val, int d, int s, const struct afswtch *afp)
+{
+
+ domediaopt(val, 0, s);
+}
+
+static void
+unsetmediaopt(const char *val, int d, int s, const struct afswtch *afp)
+{
+
+ domediaopt(val, 1, s);
+}
+
+static void
+domediaopt(const char *val, int clear, int s)
+{
+ struct ifmediareq *ifmr;
+ int options;
+
+ ifmr = ifmedia_getstate(s);
+
+ options = get_media_options(IFM_TYPE(ifmr->ifm_ulist[0]), val);
+
+ strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
+ ifr.ifr_media = ifmr->ifm_current;
+ if (clear)
+ ifr.ifr_media &= ~options;
+ else {
+ if (options & IFM_HDX) {
+ ifr.ifr_media &= ~IFM_FDX;
+ options &= ~IFM_HDX;
+ }
+ ifr.ifr_media |= options;
+ }
+ ifmr->ifm_current = ifr.ifr_media;
+ callback_register(setifmediacallback, (void *)ifmr);
+}
+
+static void
+setmediainst(const char *val, int d, int s, const struct afswtch *afp)
+{
+ struct ifmediareq *ifmr;
+ int inst;
+
+ ifmr = ifmedia_getstate(s);
+
+ inst = atoi(val);
+ if (inst < 0 || inst > (int)IFM_INST_MAX)
+ errx(1, "invalid media instance: %s", val);
+
+ strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
+ ifr.ifr_media = (ifmr->ifm_current & ~IFM_IMASK) | inst << IFM_ISHIFT;
+
+ ifmr->ifm_current = ifr.ifr_media;
+ callback_register(setifmediacallback, (void *)ifmr);
+}
+
+static void
+setmediamode(const char *val, int d, int s, const struct afswtch *afp)
+{
+ struct ifmediareq *ifmr;
+ int mode;
+
+ ifmr = ifmedia_getstate(s);
+
+ mode = get_media_mode(IFM_TYPE(ifmr->ifm_ulist[0]), val);
+
+ strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
+ ifr.ifr_media = (ifmr->ifm_current & ~IFM_MMASK) | mode;
+
+ ifmr->ifm_current = ifr.ifr_media;
+ callback_register(setifmediacallback, (void *)ifmr);
+}
+
+/**********************************************************************
+ * A good chunk of this is duplicated from sys/net/ifmedia.c
+ **********************************************************************/
+
+static struct ifmedia_description ifm_type_descriptions[] =
+ IFM_TYPE_DESCRIPTIONS;
+
+static struct ifmedia_description ifm_subtype_ethernet_descriptions[] =
+ IFM_SUBTYPE_ETHERNET_DESCRIPTIONS;
+
+static struct ifmedia_description ifm_subtype_ethernet_aliases[] =
+ IFM_SUBTYPE_ETHERNET_ALIASES;
+
+static struct ifmedia_description ifm_subtype_ethernet_option_descriptions[] =
+ IFM_SUBTYPE_ETHERNET_OPTION_DESCRIPTIONS;
+
+static struct ifmedia_description ifm_subtype_tokenring_descriptions[] =
+ IFM_SUBTYPE_TOKENRING_DESCRIPTIONS;
+
+static struct ifmedia_description ifm_subtype_tokenring_aliases[] =
+ IFM_SUBTYPE_TOKENRING_ALIASES;
+
+static struct ifmedia_description ifm_subtype_tokenring_option_descriptions[] =
+ IFM_SUBTYPE_TOKENRING_OPTION_DESCRIPTIONS;
+
+static struct ifmedia_description ifm_subtype_fddi_descriptions[] =
+ IFM_SUBTYPE_FDDI_DESCRIPTIONS;
+
+static struct ifmedia_description ifm_subtype_fddi_aliases[] =
+ IFM_SUBTYPE_FDDI_ALIASES;
+
+static struct ifmedia_description ifm_subtype_fddi_option_descriptions[] =
+ IFM_SUBTYPE_FDDI_OPTION_DESCRIPTIONS;
+
+static struct ifmedia_description ifm_subtype_ieee80211_descriptions[] =
+ IFM_SUBTYPE_IEEE80211_DESCRIPTIONS;
+
+static struct ifmedia_description ifm_subtype_ieee80211_aliases[] =
+ IFM_SUBTYPE_IEEE80211_ALIASES;
+
+static struct ifmedia_description ifm_subtype_ieee80211_option_descriptions[] =
+ IFM_SUBTYPE_IEEE80211_OPTION_DESCRIPTIONS;
+
+struct ifmedia_description ifm_subtype_ieee80211_mode_descriptions[] =
+ IFM_SUBTYPE_IEEE80211_MODE_DESCRIPTIONS;
+
+struct ifmedia_description ifm_subtype_ieee80211_mode_aliases[] =
+ IFM_SUBTYPE_IEEE80211_MODE_ALIASES;
+
+static struct ifmedia_description ifm_subtype_atm_descriptions[] =
+ IFM_SUBTYPE_ATM_DESCRIPTIONS;
+
+static struct ifmedia_description ifm_subtype_atm_aliases[] =
+ IFM_SUBTYPE_ATM_ALIASES;
+
+static struct ifmedia_description ifm_subtype_atm_option_descriptions[] =
+ IFM_SUBTYPE_ATM_OPTION_DESCRIPTIONS;
+
+static struct ifmedia_description ifm_subtype_shared_descriptions[] =
+ IFM_SUBTYPE_SHARED_DESCRIPTIONS;
+
+static struct ifmedia_description ifm_subtype_shared_aliases[] =
+ IFM_SUBTYPE_SHARED_ALIASES;
+
+static struct ifmedia_description ifm_shared_option_descriptions[] =
+ IFM_SHARED_OPTION_DESCRIPTIONS;
+
+static struct ifmedia_description ifm_shared_option_aliases[] =
+ IFM_SHARED_OPTION_ALIASES;
+
+struct ifmedia_type_to_subtype {
+ struct {
+ struct ifmedia_description *desc;
+ int alias;
+ } subtypes[5];
+ struct {
+ struct ifmedia_description *desc;
+ int alias;
+ } options[4];
+ struct {
+ struct ifmedia_description *desc;
+ int alias;
+ } modes[3];
+};
+
+/* must be in the same order as IFM_TYPE_DESCRIPTIONS */
+static struct ifmedia_type_to_subtype ifmedia_types_to_subtypes[] = {
+ {
+ {
+ { &ifm_subtype_shared_descriptions[0], 0 },
+ { &ifm_subtype_shared_aliases[0], 1 },
+ { &ifm_subtype_ethernet_descriptions[0], 0 },
+ { &ifm_subtype_ethernet_aliases[0], 1 },
+ { NULL, 0 },
+ },
+ {
+ { &ifm_shared_option_descriptions[0], 0 },
+ { &ifm_shared_option_aliases[0], 1 },
+ { &ifm_subtype_ethernet_option_descriptions[0], 0 },
+ { NULL, 0 },
+ },
+ {
+ { NULL, 0 },
+ },
+ },
+ {
+ {
+ { &ifm_subtype_shared_descriptions[0], 0 },
+ { &ifm_subtype_shared_aliases[0], 1 },
+ { &ifm_subtype_tokenring_descriptions[0], 0 },
+ { &ifm_subtype_tokenring_aliases[0], 1 },
+ { NULL, 0 },
+ },
+ {
+ { &ifm_shared_option_descriptions[0], 0 },
+ { &ifm_shared_option_aliases[0], 1 },
+ { &ifm_subtype_tokenring_option_descriptions[0], 0 },
+ { NULL, 0 },
+ },
+ {
+ { NULL, 0 },
+ },
+ },
+ {
+ {
+ { &ifm_subtype_shared_descriptions[0], 0 },
+ { &ifm_subtype_shared_aliases[0], 1 },
+ { &ifm_subtype_fddi_descriptions[0], 0 },
+ { &ifm_subtype_fddi_aliases[0], 1 },
+ { NULL, 0 },
+ },
+ {
+ { &ifm_shared_option_descriptions[0], 0 },
+ { &ifm_shared_option_aliases[0], 1 },
+ { &ifm_subtype_fddi_option_descriptions[0], 0 },
+ { NULL, 0 },
+ },
+ {
+ { NULL, 0 },
+ },
+ },
+ {
+ {
+ { &ifm_subtype_shared_descriptions[0], 0 },
+ { &ifm_subtype_shared_aliases[0], 1 },
+ { &ifm_subtype_ieee80211_descriptions[0], 0 },
+ { &ifm_subtype_ieee80211_aliases[0], 1 },
+ { NULL, 0 },
+ },
+ {
+ { &ifm_shared_option_descriptions[0], 0 },
+ { &ifm_shared_option_aliases[0], 1 },
+ { &ifm_subtype_ieee80211_option_descriptions[0], 0 },
+ { NULL, 0 },
+ },
+ {
+ { &ifm_subtype_ieee80211_mode_descriptions[0], 0 },
+ { &ifm_subtype_ieee80211_mode_aliases[0], 0 },
+ { NULL, 0 },
+ },
+ },
+ {
+ {
+ { &ifm_subtype_shared_descriptions[0], 0 },
+ { &ifm_subtype_shared_aliases[0], 1 },
+ { &ifm_subtype_atm_descriptions[0], 0 },
+ { &ifm_subtype_atm_aliases[0], 1 },
+ { NULL, 0 },
+ },
+ {
+ { &ifm_shared_option_descriptions[0], 0 },
+ { &ifm_shared_option_aliases[0], 1 },
+ { &ifm_subtype_atm_option_descriptions[0], 0 },
+ { NULL, 0 },
+ },
+ {
+ { NULL, 0 },
+ },
+ },
+};
+
+static int
+get_media_subtype(int type, const char *val)
+{
+ struct ifmedia_description *desc;
+ struct ifmedia_type_to_subtype *ttos;
+ int rval, i;
+
+ /* Find the top-level interface type. */
+ for (desc = ifm_type_descriptions, ttos = ifmedia_types_to_subtypes;
+ desc->ifmt_string != NULL; desc++, ttos++)
+ if (type == desc->ifmt_word)
+ break;
+ if (desc->ifmt_string == NULL)
+ errx(1, "unknown media type 0x%x", type);
+
+ for (i = 0; ttos->subtypes[i].desc != NULL; i++) {
+ rval = lookup_media_word(ttos->subtypes[i].desc, val);
+ if (rval != -1)
+ return (rval);
+ }
+ errx(1, "unknown media subtype: %s", val);
+ /*NOTREACHED*/
+}
+
+static int
+get_media_mode(int type, const char *val)
+{
+ struct ifmedia_description *desc;
+ struct ifmedia_type_to_subtype *ttos;
+ int rval, i;
+
+ /* Find the top-level interface type. */
+ for (desc = ifm_type_descriptions, ttos = ifmedia_types_to_subtypes;
+ desc->ifmt_string != NULL; desc++, ttos++)
+ if (type == desc->ifmt_word)
+ break;
+ if (desc->ifmt_string == NULL)
+ errx(1, "unknown media mode 0x%x", type);
+
+ for (i = 0; ttos->modes[i].desc != NULL; i++) {
+ rval = lookup_media_word(ttos->modes[i].desc, val);
+ if (rval != -1)
+ return (rval);
+ }
+ return -1;
+}
+
+static int
+get_media_options(int type, const char *val)
+{
+ struct ifmedia_description *desc;
+ struct ifmedia_type_to_subtype *ttos;
+ char *optlist, *optptr;
+ int option = 0, i, rval = 0;
+
+ /* We muck with the string, so copy it. */
+ optlist = strdup(val);
+ if (optlist == NULL)
+ err(1, "strdup");
+
+ /* Find the top-level interface type. */
+ for (desc = ifm_type_descriptions, ttos = ifmedia_types_to_subtypes;
+ desc->ifmt_string != NULL; desc++, ttos++)
+ if (type == desc->ifmt_word)
+ break;
+ if (desc->ifmt_string == NULL)
+ errx(1, "unknown media type 0x%x", type);
+
+ /*
+ * Look up the options in the user-provided comma-separated
+ * list.
+ */
+ optptr = optlist;
+ for (; (optptr = strtok(optptr, ",")) != NULL; optptr = NULL) {
+ for (i = 0; ttos->options[i].desc != NULL; i++) {
+ option = lookup_media_word(ttos->options[i].desc, optptr);
+ if (option != -1)
+ break;
+ }
+ if (option == 0)
+ errx(1, "unknown option: %s", optptr);
+ rval |= option;
+ }
+
+ free(optlist);
+ return (rval);
+}
+
+static int
+lookup_media_word(struct ifmedia_description *desc, const char *val)
+{
+
+ for (; desc->ifmt_string != NULL; desc++)
+ if (strcasecmp(desc->ifmt_string, val) == 0)
+ return (desc->ifmt_word);
+
+ return (-1);
+}
+
+static struct ifmedia_description *get_toptype_desc(int ifmw)
+{
+ struct ifmedia_description *desc;
+
+ for (desc = ifm_type_descriptions; desc->ifmt_string != NULL; desc++)
+ if (IFM_TYPE(ifmw) == desc->ifmt_word)
+ break;
+
+ return desc;
+}
+
+static struct ifmedia_type_to_subtype *get_toptype_ttos(int ifmw)
+{
+ struct ifmedia_description *desc;
+ struct ifmedia_type_to_subtype *ttos;
+
+ for (desc = ifm_type_descriptions, ttos = ifmedia_types_to_subtypes;
+ desc->ifmt_string != NULL; desc++, ttos++)
+ if (IFM_TYPE(ifmw) == desc->ifmt_word)
+ break;
+
+ return ttos;
+}
+
+static struct ifmedia_description *get_subtype_desc(int ifmw,
+ struct ifmedia_type_to_subtype *ttos)
+{
+ int i;
+ struct ifmedia_description *desc;
+
+ for (i = 0; ttos->subtypes[i].desc != NULL; i++) {
+ if (ttos->subtypes[i].alias)
+ continue;
+ for (desc = ttos->subtypes[i].desc;
+ desc->ifmt_string != NULL; desc++) {
+ if (IFM_SUBTYPE(ifmw) == desc->ifmt_word)
+ return desc;
+ }
+ }
+
+ return NULL;
+}
+
+static struct ifmedia_description *get_mode_desc(int ifmw,
+ struct ifmedia_type_to_subtype *ttos)
+{
+ int i;
+ struct ifmedia_description *desc;
+
+ for (i = 0; ttos->modes[i].desc != NULL; i++) {
+ if (ttos->modes[i].alias)
+ continue;
+ for (desc = ttos->modes[i].desc;
+ desc->ifmt_string != NULL; desc++) {
+ if (IFM_MODE(ifmw) == desc->ifmt_word)
+ return desc;
+ }
+ }
+
+ return NULL;
+}
+
+static void
+print_media_word(int ifmw, int print_toptype)
+{
+ struct ifmedia_description *desc;
+ struct ifmedia_type_to_subtype *ttos;
+ int seen_option = 0, i;
+
+ /* Find the top-level interface type. */
+ desc = get_toptype_desc(ifmw);
+ ttos = get_toptype_ttos(ifmw);
+ if (desc->ifmt_string == NULL) {
+ printf("<unknown type>");
+ return;
+ } else if (print_toptype) {
+ printf("%s", desc->ifmt_string);
+ }
+
+ /*
+ * Don't print the top-level type; it's not like we can
+ * change it, or anything.
+ */
+
+ /* Find subtype. */
+ desc = get_subtype_desc(ifmw, ttos);
+ if (desc == NULL) {
+ printf("<unknown subtype>");
+ return;
+ }
+
+ if (print_toptype)
+ putchar(' ');
+
+ printf("%s", desc->ifmt_string);
+
+ if (print_toptype) {
+ desc = get_mode_desc(ifmw, ttos);
+ if (desc != NULL && strcasecmp("autoselect", desc->ifmt_string))
+ printf(" mode %s", desc->ifmt_string);
+ }
+
+ /* Find options. */
+ for (i = 0; ttos->options[i].desc != NULL; i++) {
+ if (ttos->options[i].alias)
+ continue;
+ for (desc = ttos->options[i].desc;
+ desc->ifmt_string != NULL; desc++) {
+ if (ifmw & desc->ifmt_word) {
+ if (seen_option == 0)
+ printf(" <");
+ printf("%s%s", seen_option++ ? "," : "",
+ desc->ifmt_string);
+ }
+ }
+ }
+ printf("%s", seen_option ? ">" : "");
+
+ if (print_toptype && IFM_INST(ifmw) != 0)
+ printf(" instance %d", IFM_INST(ifmw));
+}
+
+static void
+print_media_word_ifconfig(int ifmw)
+{
+ struct ifmedia_description *desc;
+ struct ifmedia_type_to_subtype *ttos;
+ int seen_option = 0, i;
+
+ /* Find the top-level interface type. */
+ desc = get_toptype_desc(ifmw);
+ ttos = get_toptype_ttos(ifmw);
+ if (desc->ifmt_string == NULL) {
+ printf("<unknown type>");
+ return;
+ }
+
+ /*
+ * Don't print the top-level type; it's not like we can
+ * change it, or anything.
+ */
+
+ /* Find subtype. */
+ desc = get_subtype_desc(ifmw, ttos);
+ if (desc == NULL) {
+ printf("<unknown subtype>");
+ return;
+ }
+
+ printf("media %s", desc->ifmt_string);
+
+ desc = get_mode_desc(ifmw, ttos);
+ if (desc != NULL)
+ printf(" mode %s", desc->ifmt_string);
+
+ /* Find options. */
+ for (i = 0; ttos->options[i].desc != NULL; i++) {
+ if (ttos->options[i].alias)
+ continue;
+ for (desc = ttos->options[i].desc;
+ desc->ifmt_string != NULL; desc++) {
+ if (ifmw & desc->ifmt_word) {
+ if (seen_option == 0)
+ printf(" mediaopt ");
+ printf("%s%s", seen_option++ ? "," : "",
+ desc->ifmt_string);
+ }
+ }
+ }
+
+ if (IFM_INST(ifmw) != 0)
+ printf(" instance %d", IFM_INST(ifmw));
+}
+
+/**********************************************************************
+ * ...until here.
+ **********************************************************************/
+
+static struct cmd media_cmds[] = {
+ DEF_CMD_ARG("media", setmedia),
+ DEF_CMD_ARG("mode", setmediamode),
+ DEF_CMD_ARG("mediaopt", setmediaopt),
+ DEF_CMD_ARG("-mediaopt",unsetmediaopt),
+ DEF_CMD_ARG("inst", setmediainst),
+ DEF_CMD_ARG("instance", setmediainst),
+};
+static struct afswtch af_media = {
+ .af_name = "af_media",
+ .af_af = AF_UNSPEC,
+ .af_other_status = media_status,
+};
+
+static __constructor void
+ifmedia_ctor(void)
+{
+#define N(a) (sizeof(a) / sizeof(a[0]))
+ size_t i;
+
+ for (i = 0; i < N(media_cmds); i++)
+ cmd_register(&media_cmds[i]);
+ af_register(&af_media);
+#undef N
+}
diff --git a/sbin/ifconfig/ifpfsync.c b/sbin/ifconfig/ifpfsync.c
new file mode 100644
index 0000000..0af89e2
--- /dev/null
+++ b/sbin/ifconfig/ifpfsync.c
@@ -0,0 +1,237 @@
+/*
+ * Copyright (c) 2003 Ryan McBride. All rights reserved.
+ * Copyright (c) 2004 Max Laier. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <net/pfvar.h>
+#include <net/if_pfsync.h>
+#include <net/route.h>
+#include <arpa/inet.h>
+
+#include <err.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "ifconfig.h"
+
+void setpfsync_syncdev(const char *, int, int, const struct afswtch *);
+void unsetpfsync_syncdev(const char *, int, int, const struct afswtch *);
+void setpfsync_syncpeer(const char *, int, int, const struct afswtch *);
+void unsetpfsync_syncpeer(const char *, int, int, const struct afswtch *);
+void setpfsync_syncpeer(const char *, int, int, const struct afswtch *);
+void setpfsync_maxupd(const char *, int, int, const struct afswtch *);
+void setpfsync_defer(const char *, int, int, const struct afswtch *);
+void pfsync_status(int);
+
+void
+setpfsync_syncdev(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ struct pfsyncreq preq;
+
+ bzero((char *)&preq, sizeof(struct pfsyncreq));
+ ifr.ifr_data = (caddr_t)&preq;
+
+ if (ioctl(s, SIOCGETPFSYNC, (caddr_t)&ifr) == -1)
+ err(1, "SIOCGETPFSYNC");
+
+ strlcpy(preq.pfsyncr_syncdev, val, sizeof(preq.pfsyncr_syncdev));
+
+ if (ioctl(s, SIOCSETPFSYNC, (caddr_t)&ifr) == -1)
+ err(1, "SIOCSETPFSYNC");
+}
+
+/* ARGSUSED */
+void
+unsetpfsync_syncdev(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ struct pfsyncreq preq;
+
+ bzero((char *)&preq, sizeof(struct pfsyncreq));
+ ifr.ifr_data = (caddr_t)&preq;
+
+ if (ioctl(s, SIOCGETPFSYNC, (caddr_t)&ifr) == -1)
+ err(1, "SIOCGETPFSYNC");
+
+ bzero((char *)&preq.pfsyncr_syncdev, sizeof(preq.pfsyncr_syncdev));
+
+ if (ioctl(s, SIOCSETPFSYNC, (caddr_t)&ifr) == -1)
+ err(1, "SIOCSETPFSYNC");
+}
+
+/* ARGSUSED */
+void
+setpfsync_syncpeer(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ struct pfsyncreq preq;
+ struct addrinfo hints, *peerres;
+ int ecode;
+
+ bzero((char *)&preq, sizeof(struct pfsyncreq));
+ ifr.ifr_data = (caddr_t)&preq;
+
+ if (ioctl(s, SIOCGETPFSYNC, (caddr_t)&ifr) == -1)
+ err(1, "SIOCGETPFSYNC");
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_INET;
+ hints.ai_socktype = SOCK_DGRAM; /*dummy*/
+
+ if ((ecode = getaddrinfo(val, NULL, &hints, &peerres)) != 0)
+ errx(1, "error in parsing address string: %s",
+ gai_strerror(ecode));
+
+ if (peerres->ai_addr->sa_family != AF_INET)
+ errx(1, "only IPv4 addresses supported for the syncpeer");
+
+ preq.pfsyncr_syncpeer.s_addr = ((struct sockaddr_in *)
+ peerres->ai_addr)->sin_addr.s_addr;
+
+ if (ioctl(s, SIOCSETPFSYNC, (caddr_t)&ifr) == -1)
+ err(1, "SIOCSETPFSYNC");
+}
+
+/* ARGSUSED */
+void
+unsetpfsync_syncpeer(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ struct pfsyncreq preq;
+
+ bzero((char *)&preq, sizeof(struct pfsyncreq));
+ ifr.ifr_data = (caddr_t)&preq;
+
+ if (ioctl(s, SIOCGETPFSYNC, (caddr_t)&ifr) == -1)
+ err(1, "SIOCGETPFSYNC");
+
+ preq.pfsyncr_syncpeer.s_addr = 0;
+
+ if (ioctl(s, SIOCSETPFSYNC, (caddr_t)&ifr) == -1)
+ err(1, "SIOCSETPFSYNC");
+}
+
+/* ARGSUSED */
+void
+setpfsync_maxupd(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ struct pfsyncreq preq;
+ int maxupdates;
+
+ maxupdates = atoi(val);
+ if ((maxupdates < 0) || (maxupdates > 255))
+ errx(1, "maxupd %s: out of range", val);
+
+ memset((char *)&preq, 0, sizeof(struct pfsyncreq));
+ ifr.ifr_data = (caddr_t)&preq;
+
+ if (ioctl(s, SIOCGETPFSYNC, (caddr_t)&ifr) == -1)
+ err(1, "SIOCGETPFSYNC");
+
+ preq.pfsyncr_maxupdates = maxupdates;
+
+ if (ioctl(s, SIOCSETPFSYNC, (caddr_t)&ifr) == -1)
+ err(1, "SIOCSETPFSYNC");
+}
+
+/* ARGSUSED */
+void
+setpfsync_defer(const char *val, int d, int s, const struct afswtch *rafp)
+{
+ struct pfsyncreq preq;
+
+ memset((char *)&preq, 0, sizeof(struct pfsyncreq));
+ ifr.ifr_data = (caddr_t)&preq;
+
+ if (ioctl(s, SIOCGETPFSYNC, (caddr_t)&ifr) == -1)
+ err(1, "SIOCGETPFSYNC");
+
+ preq.pfsyncr_defer = d;
+ if (ioctl(s, SIOCSETPFSYNC, (caddr_t)&ifr) == -1)
+ err(1, "SIOCSETPFSYNC");
+}
+
+void
+pfsync_status(int s)
+{
+ struct pfsyncreq preq;
+
+ bzero((char *)&preq, sizeof(struct pfsyncreq));
+ ifr.ifr_data = (caddr_t)&preq;
+
+ if (ioctl(s, SIOCGETPFSYNC, (caddr_t)&ifr) == -1)
+ return;
+
+ if (preq.pfsyncr_syncdev[0] != '\0' ||
+ preq.pfsyncr_syncpeer.s_addr != INADDR_PFSYNC_GROUP)
+ printf("\t");
+
+ if (preq.pfsyncr_syncdev[0] != '\0')
+ printf("pfsync: syncdev: %s ", preq.pfsyncr_syncdev);
+ if (preq.pfsyncr_syncpeer.s_addr != INADDR_PFSYNC_GROUP)
+ printf("syncpeer: %s ", inet_ntoa(preq.pfsyncr_syncpeer));
+
+ if (preq.pfsyncr_syncdev[0] != '\0' ||
+ preq.pfsyncr_syncpeer.s_addr != INADDR_PFSYNC_GROUP) {
+ printf("maxupd: %d ", preq.pfsyncr_maxupdates);
+ printf("defer: %s\n", preq.pfsyncr_defer ? "on" : "off");
+ }
+}
+
+static struct cmd pfsync_cmds[] = {
+ DEF_CMD_ARG("syncdev", setpfsync_syncdev),
+ DEF_CMD("-syncdev", 1, unsetpfsync_syncdev),
+ DEF_CMD_ARG("syncif", setpfsync_syncdev),
+ DEF_CMD("-syncif", 1, unsetpfsync_syncdev),
+ DEF_CMD_ARG("syncpeer", setpfsync_syncpeer),
+ DEF_CMD("-syncpeer", 1, unsetpfsync_syncpeer),
+ DEF_CMD_ARG("maxupd", setpfsync_maxupd),
+ DEF_CMD("defer", 1, setpfsync_defer),
+ DEF_CMD("-defer", 0, setpfsync_defer),
+};
+static struct afswtch af_pfsync = {
+ .af_name = "af_pfsync",
+ .af_af = AF_UNSPEC,
+ .af_other_status = pfsync_status,
+};
+
+static __constructor void
+pfsync_ctor(void)
+{
+#define N(a) (sizeof(a) / sizeof(a[0]))
+ int i;
+
+ for (i = 0; i < N(pfsync_cmds); i++)
+ cmd_register(&pfsync_cmds[i]);
+ af_register(&af_pfsync);
+#undef N
+}
diff --git a/sbin/ifconfig/ifvlan.c b/sbin/ifconfig/ifvlan.c
new file mode 100644
index 0000000..1a3fbaa
--- /dev/null
+++ b/sbin/ifconfig/ifvlan.c
@@ -0,0 +1,206 @@
+/*
+ * Copyright (c) 1999
+ * Bill Paul <wpaul@ctr.columbia.edu>. 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 Bill Paul.
+ * 4. Neither the name of the author nor the names of any co-contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY Bill Paul 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 Bill Paul OR THE VOICES IN HIS HEAD
+ * 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 <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <net/ethernet.h>
+#include <net/if.h>
+#include <net/if_vlan_var.h>
+#include <net/route.h>
+
+#include <ctype.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <err.h>
+#include <errno.h>
+
+#include "ifconfig.h"
+
+#ifndef lint
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif
+
+#define NOTAG ((u_short) -1)
+
+static struct vlanreq params = {
+ .vlr_tag = NOTAG,
+};
+
+static int
+getvlan(int s, struct ifreq *ifr, struct vlanreq *vreq)
+{
+ bzero((char *)vreq, sizeof(*vreq));
+ ifr->ifr_data = (caddr_t)vreq;
+
+ return ioctl(s, SIOCGETVLAN, (caddr_t)ifr);
+}
+
+static void
+vlan_status(int s)
+{
+ struct vlanreq vreq;
+
+ if (getvlan(s, &ifr, &vreq) != -1)
+ printf("\tvlan: %d parent interface: %s\n",
+ vreq.vlr_tag, vreq.vlr_parent[0] == '\0' ?
+ "<none>" : vreq.vlr_parent);
+}
+
+static void
+vlan_create(int s, struct ifreq *ifr)
+{
+ if (params.vlr_tag != NOTAG || params.vlr_parent[0] != '\0') {
+ /*
+ * One or both parameters were specified, make sure both.
+ */
+ if (params.vlr_tag == NOTAG)
+ errx(1, "must specify a tag for vlan create");
+ if (params.vlr_parent[0] == '\0')
+ errx(1, "must specify a parent device for vlan create");
+ ifr->ifr_data = (caddr_t) &params;
+ }
+ if (ioctl(s, SIOCIFCREATE2, ifr) < 0)
+ err(1, "SIOCIFCREATE2");
+}
+
+static void
+vlan_cb(int s, void *arg)
+{
+ if ((params.vlr_tag != NOTAG) ^ (params.vlr_parent[0] != '\0'))
+ errx(1, "both vlan and vlandev must be specified");
+}
+
+static void
+vlan_set(int s, struct ifreq *ifr)
+{
+ if (params.vlr_tag != NOTAG && params.vlr_parent[0] != '\0') {
+ ifr->ifr_data = (caddr_t) &params;
+ if (ioctl(s, SIOCSETVLAN, (caddr_t)ifr) == -1)
+ err(1, "SIOCSETVLAN");
+ }
+}
+
+static
+DECL_CMD_FUNC(setvlantag, val, d)
+{
+ struct vlanreq vreq;
+ u_long ul;
+ char *endp;
+
+ ul = strtoul(val, &endp, 0);
+ if (*endp != '\0')
+ errx(1, "invalid value for vlan");
+ params.vlr_tag = ul;
+ /* check if the value can be represented in vlr_tag */
+ if (params.vlr_tag != ul)
+ errx(1, "value for vlan out of range");
+
+ if (getvlan(s, &ifr, &vreq) != -1)
+ vlan_set(s, &ifr);
+}
+
+static
+DECL_CMD_FUNC(setvlandev, val, d)
+{
+ struct vlanreq vreq;
+
+ strlcpy(params.vlr_parent, val, sizeof(params.vlr_parent));
+
+ if (getvlan(s, &ifr, &vreq) != -1)
+ vlan_set(s, &ifr);
+}
+
+static
+DECL_CMD_FUNC(unsetvlandev, val, d)
+{
+ struct vlanreq vreq;
+
+ bzero((char *)&vreq, sizeof(struct vlanreq));
+ ifr.ifr_data = (caddr_t)&vreq;
+
+ if (ioctl(s, SIOCGETVLAN, (caddr_t)&ifr) == -1)
+ err(1, "SIOCGETVLAN");
+
+ bzero((char *)&vreq.vlr_parent, sizeof(vreq.vlr_parent));
+ vreq.vlr_tag = 0;
+
+ if (ioctl(s, SIOCSETVLAN, (caddr_t)&ifr) == -1)
+ err(1, "SIOCSETVLAN");
+}
+
+static struct cmd vlan_cmds[] = {
+ DEF_CLONE_CMD_ARG("vlan", setvlantag),
+ DEF_CLONE_CMD_ARG("vlandev", setvlandev),
+ /* NB: non-clone cmds */
+ DEF_CMD_ARG("vlan", setvlantag),
+ DEF_CMD_ARG("vlandev", setvlandev),
+ /* XXX For compatibility. Should become DEF_CMD() some day. */
+ DEF_CMD_OPTARG("-vlandev", unsetvlandev),
+ DEF_CMD("vlanmtu", IFCAP_VLAN_MTU, setifcap),
+ DEF_CMD("-vlanmtu", -IFCAP_VLAN_MTU, setifcap),
+ DEF_CMD("vlanhwtag", IFCAP_VLAN_HWTAGGING, setifcap),
+ DEF_CMD("-vlanhwtag", -IFCAP_VLAN_HWTAGGING, setifcap),
+ DEF_CMD("vlanhwfilter", IFCAP_VLAN_HWFILTER, setifcap),
+ DEF_CMD("-vlanhwfilter", -IFCAP_VLAN_HWFILTER, setifcap),
+ DEF_CMD("-vlanhwtso", -IFCAP_VLAN_HWTSO, setifcap),
+ DEF_CMD("vlanhwtso", IFCAP_VLAN_HWTSO, setifcap),
+ DEF_CMD("vlanhwcsum", IFCAP_VLAN_HWCSUM, setifcap),
+ DEF_CMD("-vlanhwcsum", -IFCAP_VLAN_HWCSUM, setifcap),
+};
+static struct afswtch af_vlan = {
+ .af_name = "af_vlan",
+ .af_af = AF_UNSPEC,
+ .af_other_status = vlan_status,
+};
+
+static __constructor void
+vlan_ctor(void)
+{
+#define N(a) (sizeof(a) / sizeof(a[0]))
+ size_t i;
+
+ for (i = 0; i < N(vlan_cmds); i++)
+ cmd_register(&vlan_cmds[i]);
+ af_register(&af_vlan);
+ callback_register(vlan_cb, NULL);
+ clone_setdefcallback("vlan", vlan_create);
+#undef N
+}
diff --git a/sbin/ifconfig/ifvxlan.c b/sbin/ifconfig/ifvxlan.c
new file mode 100644
index 0000000..48f5331
--- /dev/null
+++ b/sbin/ifconfig/ifvxlan.c
@@ -0,0 +1,647 @@
+/*-
+ * Copyright (c) 2014, Bryan Venteicher <bryanv@FreeBSD.org>
+ * 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 unmodified, 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <netdb.h>
+
+#include <net/ethernet.h>
+#include <net/if.h>
+#include <net/if_vxlan.h>
+#include <net/route.h>
+#include <netinet/in.h>
+
+#include <ctype.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <err.h>
+#include <errno.h>
+
+#include "ifconfig.h"
+
+static struct ifvxlanparam params = {
+ .vxlp_vni = VXLAN_VNI_MAX,
+};
+
+static int
+get_val(const char *cp, u_long *valp)
+{
+ char *endptr;
+ u_long val;
+
+ errno = 0;
+ val = strtoul(cp, &endptr, 0);
+ if (cp[0] == '\0' || endptr[0] != '\0' || errno == ERANGE)
+ return (-1);
+
+ *valp = val;
+ return (0);
+}
+
+static int
+do_cmd(int sock, u_long op, void *arg, size_t argsize, int set)
+{
+ struct ifdrv ifd;
+
+ bzero(&ifd, sizeof(ifd));
+
+ strlcpy(ifd.ifd_name, ifr.ifr_name, sizeof(ifd.ifd_name));
+ ifd.ifd_cmd = op;
+ ifd.ifd_len = argsize;
+ ifd.ifd_data = arg;
+
+ return (ioctl(sock, set ? SIOCSDRVSPEC : SIOCGDRVSPEC, &ifd));
+}
+
+static int
+vxlan_exists(int sock)
+{
+ struct ifvxlancfg cfg;
+
+ bzero(&cfg, sizeof(cfg));
+
+ return (do_cmd(sock, VXLAN_CMD_GET_CONFIG, &cfg, sizeof(cfg), 0) != -1);
+}
+
+static void
+vxlan_status(int s)
+{
+ struct ifvxlancfg cfg;
+ char src[NI_MAXHOST], dst[NI_MAXHOST];
+ char srcport[NI_MAXSERV], dstport[NI_MAXSERV];
+ struct sockaddr *lsa, *rsa;
+ int vni, mc, ipv6;
+
+ bzero(&cfg, sizeof(cfg));
+
+ if (do_cmd(s, VXLAN_CMD_GET_CONFIG, &cfg, sizeof(cfg), 0) < 0)
+ return;
+
+ vni = cfg.vxlc_vni;
+ lsa = &cfg.vxlc_local_sa.sa;
+ rsa = &cfg.vxlc_remote_sa.sa;
+ ipv6 = rsa->sa_family == AF_INET6;
+
+ /* Just report nothing if the network identity isn't set yet. */
+ if (vni >= VXLAN_VNI_MAX)
+ return;
+
+ if (getnameinfo(lsa, lsa->sa_len, src, sizeof(src),
+ srcport, sizeof(srcport), NI_NUMERICHOST | NI_NUMERICSERV) != 0)
+ src[0] = srcport[0] = '\0';
+ if (getnameinfo(rsa, rsa->sa_len, dst, sizeof(dst),
+ dstport, sizeof(dstport), NI_NUMERICHOST | NI_NUMERICSERV) != 0)
+ dst[0] = dstport[0] = '\0';
+
+ if (!ipv6) {
+ struct sockaddr_in *sin = (struct sockaddr_in *)rsa;
+ mc = IN_MULTICAST(ntohl(sin->sin_addr.s_addr));
+ } else {
+ struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)rsa;
+ mc = IN6_IS_ADDR_MULTICAST(&sin6->sin6_addr);
+ }
+
+ printf("\tvxlan vni %d", vni);
+ printf(" local %s%s%s:%s", ipv6 ? "[" : "", src, ipv6 ? "]" : "",
+ srcport);
+ printf(" %s %s%s%s:%s", mc ? "group" : "remote", ipv6 ? "[" : "",
+ dst, ipv6 ? "]" : "", dstport);
+
+ if (verbose) {
+ printf("\n\t\tconfig: ");
+ printf("%slearning portrange %d-%d ttl %d",
+ cfg.vxlc_learn ? "" : "no", cfg.vxlc_port_min,
+ cfg.vxlc_port_max, cfg.vxlc_ttl);
+ printf("\n\t\tftable: ");
+ printf("cnt %d max %d timeout %d",
+ cfg.vxlc_ftable_cnt, cfg.vxlc_ftable_max,
+ cfg.vxlc_ftable_timeout);
+ }
+
+ putchar('\n');
+}
+
+#define _LOCAL_ADDR46 \
+ (VXLAN_PARAM_WITH_LOCAL_ADDR4 | VXLAN_PARAM_WITH_LOCAL_ADDR6)
+#define _REMOTE_ADDR46 \
+ (VXLAN_PARAM_WITH_REMOTE_ADDR4 | VXLAN_PARAM_WITH_REMOTE_ADDR6)
+
+static void
+vxlan_check_params(void)
+{
+
+ if ((params.vxlp_with & _LOCAL_ADDR46) == _LOCAL_ADDR46)
+ errx(1, "cannot specify both local IPv4 and IPv6 addresses");
+ if ((params.vxlp_with & _REMOTE_ADDR46) == _REMOTE_ADDR46)
+ errx(1, "cannot specify both remote IPv4 and IPv6 addresses");
+ if ((params.vxlp_with & VXLAN_PARAM_WITH_LOCAL_ADDR4 &&
+ params.vxlp_with & VXLAN_PARAM_WITH_REMOTE_ADDR6) ||
+ (params.vxlp_with & VXLAN_PARAM_WITH_LOCAL_ADDR6 &&
+ params.vxlp_with & VXLAN_PARAM_WITH_REMOTE_ADDR4))
+ errx(1, "cannot mix IPv4 and IPv6 addresses");
+}
+
+#undef _LOCAL_ADDR46
+#undef _REMOTE_ADDR46
+
+static void
+vxlan_cb(int s, void *arg)
+{
+
+}
+
+static void
+vxlan_create(int s, struct ifreq *ifr)
+{
+
+ vxlan_check_params();
+
+ ifr->ifr_data = (caddr_t) &params;
+ if (ioctl(s, SIOCIFCREATE2, ifr) < 0)
+ err(1, "SIOCIFCREATE2");
+}
+
+static
+DECL_CMD_FUNC(setvxlan_vni, arg, d)
+{
+ struct ifvxlancmd cmd;
+ u_long val;
+
+ if (get_val(arg, &val) < 0 || val >= VXLAN_VNI_MAX)
+ errx(1, "invalid network identifier: %s", arg);
+
+ if (!vxlan_exists(s)) {
+ params.vxlp_with |= VXLAN_PARAM_WITH_VNI;
+ params.vxlp_vni = val;
+ return;
+ }
+
+ bzero(&cmd, sizeof(cmd));
+ cmd.vxlcmd_vni = val;
+
+ if (do_cmd(s, VXLAN_CMD_SET_VNI, &cmd, sizeof(cmd), 1) < 0)
+ err(1, "VXLAN_CMD_SET_VNI");
+}
+
+static
+DECL_CMD_FUNC(setvxlan_local, addr, d)
+{
+ struct ifvxlancmd cmd;
+ struct addrinfo *ai;
+ struct sockaddr *sa;
+ int error;
+
+ bzero(&cmd, sizeof(cmd));
+
+ if ((error = getaddrinfo(addr, NULL, NULL, &ai)) != 0)
+ errx(1, "error in parsing local address string: %s",
+ gai_strerror(error));
+
+ sa = ai->ai_addr;
+
+ switch (ai->ai_family) {
+#ifdef INET
+ case AF_INET: {
+ struct in_addr addr = ((struct sockaddr_in *) sa)->sin_addr;
+
+ if (IN_MULTICAST(ntohl(addr.s_addr)))
+ errx(1, "local address cannot be multicast");
+
+ cmd.vxlcmd_sa.in4.sin_family = AF_INET;
+ cmd.vxlcmd_sa.in4.sin_addr = addr;
+ break;
+ }
+#endif
+#ifdef INET6
+ case AF_INET6: {
+ struct in6_addr *addr = &((struct sockaddr_in6 *)sa)->sin6_addr;
+
+ if (IN6_IS_ADDR_MULTICAST(addr))
+ errx(1, "local address cannot be multicast");
+
+ cmd.vxlcmd_sa.in6.sin6_family = AF_INET6;
+ cmd.vxlcmd_sa.in6.sin6_addr = *addr;
+ break;
+ }
+#endif
+ default:
+ errx(1, "local address %s not supported", addr);
+ }
+
+ freeaddrinfo(ai);
+
+ if (!vxlan_exists(s)) {
+ if (cmd.vxlcmd_sa.sa.sa_family == AF_INET) {
+ params.vxlp_with |= VXLAN_PARAM_WITH_LOCAL_ADDR4;
+ params.vxlp_local_in4 = cmd.vxlcmd_sa.in4.sin_addr;
+ } else {
+ params.vxlp_with |= VXLAN_PARAM_WITH_LOCAL_ADDR6;
+ params.vxlp_local_in6 = cmd.vxlcmd_sa.in6.sin6_addr;
+ }
+ return;
+ }
+
+ if (do_cmd(s, VXLAN_CMD_SET_LOCAL_ADDR, &cmd, sizeof(cmd), 1) < 0)
+ err(1, "VXLAN_CMD_SET_LOCAL_ADDR");
+}
+
+static
+DECL_CMD_FUNC(setvxlan_remote, addr, d)
+{
+ struct ifvxlancmd cmd;
+ struct addrinfo *ai;
+ struct sockaddr *sa;
+ int error;
+
+ bzero(&cmd, sizeof(cmd));
+
+ if ((error = getaddrinfo(addr, NULL, NULL, &ai)) != 0)
+ errx(1, "error in parsing remote address string: %s",
+ gai_strerror(error));
+
+ sa = ai->ai_addr;
+
+ switch (ai->ai_family) {
+#ifdef INET
+ case AF_INET: {
+ struct in_addr addr = ((struct sockaddr_in *)sa)->sin_addr;
+
+ if (IN_MULTICAST(ntohl(addr.s_addr)))
+ errx(1, "remote address cannot be multicast");
+
+ cmd.vxlcmd_sa.in4.sin_family = AF_INET;
+ cmd.vxlcmd_sa.in4.sin_addr = addr;
+ break;
+ }
+#endif
+#ifdef INET6
+ case AF_INET6: {
+ struct in6_addr *addr = &((struct sockaddr_in6 *)sa)->sin6_addr;
+
+ if (IN6_IS_ADDR_MULTICAST(addr))
+ errx(1, "remote address cannot be multicast");
+
+ cmd.vxlcmd_sa.in6.sin6_family = AF_INET6;
+ cmd.vxlcmd_sa.in6.sin6_addr = *addr;
+ break;
+ }
+#endif
+ default:
+ errx(1, "remote address %s not supported", addr);
+ }
+
+ freeaddrinfo(ai);
+
+ if (!vxlan_exists(s)) {
+ if (cmd.vxlcmd_sa.sa.sa_family == AF_INET) {
+ params.vxlp_with |= VXLAN_PARAM_WITH_REMOTE_ADDR4;
+ params.vxlp_remote_in4 = cmd.vxlcmd_sa.in4.sin_addr;
+ } else {
+ params.vxlp_with |= VXLAN_PARAM_WITH_REMOTE_ADDR6;
+ params.vxlp_remote_in6 = cmd.vxlcmd_sa.in6.sin6_addr;
+ }
+ return;
+ }
+
+ if (do_cmd(s, VXLAN_CMD_SET_REMOTE_ADDR, &cmd, sizeof(cmd), 1) < 0)
+ err(1, "VXLAN_CMD_SET_REMOTE_ADDR");
+}
+
+static
+DECL_CMD_FUNC(setvxlan_group, addr, d)
+{
+ struct ifvxlancmd cmd;
+ struct addrinfo *ai;
+ struct sockaddr *sa;
+ int error;
+
+ bzero(&cmd, sizeof(cmd));
+
+ if ((error = getaddrinfo(addr, NULL, NULL, &ai)) != 0)
+ errx(1, "error in parsing group address string: %s",
+ gai_strerror(error));
+
+ sa = ai->ai_addr;
+
+ switch (ai->ai_family) {
+#ifdef INET
+ case AF_INET: {
+ struct in_addr addr = ((struct sockaddr_in *)sa)->sin_addr;
+
+ if (!IN_MULTICAST(ntohl(addr.s_addr)))
+ errx(1, "group address must be multicast");
+
+ cmd.vxlcmd_sa.in4.sin_family = AF_INET;
+ cmd.vxlcmd_sa.in4.sin_addr = addr;
+ break;
+ }
+#endif
+#ifdef INET6
+ case AF_INET6: {
+ struct in6_addr *addr = &((struct sockaddr_in6 *)sa)->sin6_addr;
+
+ if (!IN6_IS_ADDR_MULTICAST(addr))
+ errx(1, "group address must be multicast");
+
+ cmd.vxlcmd_sa.in6.sin6_family = AF_INET6;
+ cmd.vxlcmd_sa.in6.sin6_addr = *addr;
+ break;
+ }
+#endif
+ default:
+ errx(1, "group address %s not supported", addr);
+ }
+
+ freeaddrinfo(ai);
+
+ if (!vxlan_exists(s)) {
+ if (cmd.vxlcmd_sa.sa.sa_family == AF_INET) {
+ params.vxlp_with |= VXLAN_PARAM_WITH_REMOTE_ADDR4;
+ params.vxlp_remote_in4 = cmd.vxlcmd_sa.in4.sin_addr;
+ } else {
+ params.vxlp_with |= VXLAN_PARAM_WITH_REMOTE_ADDR6;
+ params.vxlp_remote_in6 = cmd.vxlcmd_sa.in6.sin6_addr;
+ }
+ return;
+ }
+
+ if (do_cmd(s, VXLAN_CMD_SET_REMOTE_ADDR, &cmd, sizeof(cmd), 1) < 0)
+ err(1, "VXLAN_CMD_SET_REMOTE_ADDR");
+}
+
+static
+DECL_CMD_FUNC(setvxlan_local_port, arg, d)
+{
+ struct ifvxlancmd cmd;
+ u_long val;
+
+ if (get_val(arg, &val) < 0 || val >= UINT16_MAX)
+ errx(1, "invalid local port: %s", arg);
+
+ if (!vxlan_exists(s)) {
+ params.vxlp_with |= VXLAN_PARAM_WITH_LOCAL_PORT;
+ params.vxlp_local_port = val;
+ return;
+ }
+
+ bzero(&cmd, sizeof(cmd));
+ cmd.vxlcmd_port = val;
+
+ if (do_cmd(s, VXLAN_CMD_SET_LOCAL_PORT, &cmd, sizeof(cmd), 1) < 0)
+ err(1, "VXLAN_CMD_SET_LOCAL_PORT");
+}
+
+static
+DECL_CMD_FUNC(setvxlan_remote_port, arg, d)
+{
+ struct ifvxlancmd cmd;
+ u_long val;
+
+ if (get_val(arg, &val) < 0 || val >= UINT16_MAX)
+ errx(1, "invalid remote port: %s", arg);
+
+ if (!vxlan_exists(s)) {
+ params.vxlp_with |= VXLAN_PARAM_WITH_REMOTE_PORT;
+ params.vxlp_remote_port = val;
+ return;
+ }
+
+ bzero(&cmd, sizeof(cmd));
+ cmd.vxlcmd_port = val;
+
+ if (do_cmd(s, VXLAN_CMD_SET_REMOTE_PORT, &cmd, sizeof(cmd), 1) < 0)
+ err(1, "VXLAN_CMD_SET_REMOTE_PORT");
+}
+
+static
+DECL_CMD_FUNC2(setvxlan_port_range, arg1, arg2)
+{
+ struct ifvxlancmd cmd;
+ u_long min, max;
+
+ if (get_val(arg1, &min) < 0 || min >= UINT16_MAX)
+ errx(1, "invalid port range minimum: %s", arg1);
+ if (get_val(arg2, &max) < 0 || max >= UINT16_MAX)
+ errx(1, "invalid port range maximum: %s", arg2);
+ if (max < min)
+ errx(1, "invalid port range");
+
+ if (!vxlan_exists(s)) {
+ params.vxlp_with |= VXLAN_PARAM_WITH_PORT_RANGE;
+ params.vxlp_min_port = min;
+ params.vxlp_max_port = max;
+ return;
+ }
+
+ bzero(&cmd, sizeof(cmd));
+ cmd.vxlcmd_port_min = min;
+ cmd.vxlcmd_port_max = max;
+
+ if (do_cmd(s, VXLAN_CMD_SET_PORT_RANGE, &cmd, sizeof(cmd), 1) < 0)
+ err(1, "VXLAN_CMD_SET_PORT_RANGE");
+}
+
+static
+DECL_CMD_FUNC(setvxlan_timeout, arg, d)
+{
+ struct ifvxlancmd cmd;
+ u_long val;
+
+ if (get_val(arg, &val) < 0 || (val & ~0xFFFFFFFF) != 0)
+ errx(1, "invalid timeout value: %s", arg);
+
+ if (!vxlan_exists(s)) {
+ params.vxlp_with |= VXLAN_PARAM_WITH_FTABLE_TIMEOUT;
+ params.vxlp_ftable_timeout = val & 0xFFFFFFFF;
+ return;
+ }
+
+ bzero(&cmd, sizeof(cmd));
+ cmd.vxlcmd_ftable_timeout = val & 0xFFFFFFFF;
+
+ if (do_cmd(s, VXLAN_CMD_SET_FTABLE_TIMEOUT, &cmd, sizeof(cmd), 1) < 0)
+ err(1, "VXLAN_CMD_SET_FTABLE_TIMEOUT");
+}
+
+static
+DECL_CMD_FUNC(setvxlan_maxaddr, arg, d)
+{
+ struct ifvxlancmd cmd;
+ u_long val;
+
+ if (get_val(arg, &val) < 0 || (val & ~0xFFFFFFFF) != 0)
+ errx(1, "invalid maxaddr value: %s", arg);
+
+ if (!vxlan_exists(s)) {
+ params.vxlp_with |= VXLAN_PARAM_WITH_FTABLE_MAX;
+ params.vxlp_ftable_max = val & 0xFFFFFFFF;
+ return;
+ }
+
+ bzero(&cmd, sizeof(cmd));
+ cmd.vxlcmd_ftable_max = val & 0xFFFFFFFF;
+
+ if (do_cmd(s, VXLAN_CMD_SET_FTABLE_MAX, &cmd, sizeof(cmd), 1) < 0)
+ err(1, "VXLAN_CMD_SET_FTABLE_MAX");
+}
+
+static
+DECL_CMD_FUNC(setvxlan_dev, arg, d)
+{
+ struct ifvxlancmd cmd;
+
+ if (!vxlan_exists(s)) {
+ params.vxlp_with |= VXLAN_PARAM_WITH_MULTICAST_IF;
+ strlcpy(params.vxlp_mc_ifname, arg,
+ sizeof(params.vxlp_mc_ifname));
+ return;
+ }
+
+ bzero(&cmd, sizeof(cmd));
+ strlcpy(cmd.vxlcmd_ifname, arg, sizeof(cmd.vxlcmd_ifname));
+
+ if (do_cmd(s, VXLAN_CMD_SET_MULTICAST_IF, &cmd, sizeof(cmd), 1) < 0)
+ err(1, "VXLAN_CMD_SET_MULTICAST_IF");
+}
+
+static
+DECL_CMD_FUNC(setvxlan_ttl, arg, d)
+{
+ struct ifvxlancmd cmd;
+ u_long val;
+
+ if (get_val(arg, &val) < 0 || val > 256)
+ errx(1, "invalid TTL value: %s", arg);
+
+ if (!vxlan_exists(s)) {
+ params.vxlp_with |= VXLAN_PARAM_WITH_TTL;
+ params.vxlp_ttl = val;
+ return;
+ }
+
+ bzero(&cmd, sizeof(cmd));
+ cmd.vxlcmd_ttl = val;
+
+ if (do_cmd(s, VXLAN_CMD_SET_TTL, &cmd, sizeof(cmd), 1) < 0)
+ err(1, "VXLAN_CMD_SET_TTL");
+}
+
+static
+DECL_CMD_FUNC(setvxlan_learn, arg, d)
+{
+ struct ifvxlancmd cmd;
+
+ if (!vxlan_exists(s)) {
+ params.vxlp_with |= VXLAN_PARAM_WITH_LEARN;
+ params.vxlp_learn = d;
+ return;
+ }
+
+ bzero(&cmd, sizeof(cmd));
+ if (d != 0)
+ cmd.vxlcmd_flags |= VXLAN_CMD_FLAG_LEARN;
+
+ if (do_cmd(s, VXLAN_CMD_SET_LEARN, &cmd, sizeof(cmd), 1) < 0)
+ err(1, "VXLAN_CMD_SET_LEARN");
+}
+
+static void
+setvxlan_flush(const char *val, int d, int s, const struct afswtch *afp)
+{
+ struct ifvxlancmd cmd;
+
+ bzero(&cmd, sizeof(cmd));
+ if (d != 0)
+ cmd.vxlcmd_flags |= VXLAN_CMD_FLAG_FLUSH_ALL;
+
+ if (do_cmd(s, VXLAN_CMD_FLUSH, &cmd, sizeof(cmd), 1) < 0)
+ err(1, "VXLAN_CMD_FLUSH");
+}
+
+static struct cmd vxlan_cmds[] = {
+
+ DEF_CLONE_CMD_ARG("vxlanid", setvxlan_vni),
+ DEF_CLONE_CMD_ARG("vxlanlocal", setvxlan_local),
+ DEF_CLONE_CMD_ARG("vxlanremote", setvxlan_remote),
+ DEF_CLONE_CMD_ARG("vxlangroup", setvxlan_group),
+ DEF_CLONE_CMD_ARG("vxlanlocalport", setvxlan_local_port),
+ DEF_CLONE_CMD_ARG("vxlanremoteport", setvxlan_remote_port),
+ DEF_CLONE_CMD_ARG2("vxlanportrange", setvxlan_port_range),
+ DEF_CLONE_CMD_ARG("vxlantimeout", setvxlan_timeout),
+ DEF_CLONE_CMD_ARG("vxlanmaxaddr", setvxlan_maxaddr),
+ DEF_CLONE_CMD_ARG("vxlandev", setvxlan_dev),
+ DEF_CLONE_CMD_ARG("vxlanttl", setvxlan_ttl),
+ DEF_CLONE_CMD("vxlanlearn", 1, setvxlan_learn),
+ DEF_CLONE_CMD("-vxlanlearn", 0, setvxlan_learn),
+
+ DEF_CMD_ARG("vxlanvni", setvxlan_vni),
+ DEF_CMD_ARG("vxlanlocal", setvxlan_local),
+ DEF_CMD_ARG("vxlanremote", setvxlan_remote),
+ DEF_CMD_ARG("vxlangroup", setvxlan_group),
+ DEF_CMD_ARG("vxlanlocalport", setvxlan_local_port),
+ DEF_CMD_ARG("vxlanremoteport", setvxlan_remote_port),
+ DEF_CMD_ARG2("vxlanportrange", setvxlan_port_range),
+ DEF_CMD_ARG("vxlantimeout", setvxlan_timeout),
+ DEF_CMD_ARG("vxlanmaxaddr", setvxlan_maxaddr),
+ DEF_CMD_ARG("vxlandev", setvxlan_dev),
+ DEF_CMD_ARG("vxlanttl", setvxlan_ttl),
+ DEF_CMD("vxlanlearn", 1, setvxlan_learn),
+ DEF_CMD("-vxlanlearn", 0, setvxlan_learn),
+
+ DEF_CMD("vxlanflush", 0, setvxlan_flush),
+ DEF_CMD("vxlanflushall", 1, setvxlan_flush),
+};
+
+static struct afswtch af_vxlan = {
+ .af_name = "af_vxlan",
+ .af_af = AF_UNSPEC,
+ .af_other_status = vxlan_status,
+};
+
+static __constructor void
+vxlan_ctor(void)
+{
+#define N(a) (sizeof(a) / sizeof(a[0]))
+ size_t i;
+
+ for (i = 0; i < N(vxlan_cmds); i++)
+ cmd_register(&vxlan_cmds[i]);
+ af_register(&af_vxlan);
+ callback_register(vxlan_cb, NULL);
+ clone_setdefcallback("vxlan", vxlan_create);
+#undef N
+}
diff --git a/sbin/ifconfig/regdomain.c b/sbin/ifconfig/regdomain.c
new file mode 100644
index 0000000..4140289
--- /dev/null
+++ b/sbin/ifconfig/regdomain.c
@@ -0,0 +1,705 @@
+/*-
+ * Copyright (c) 2008 Sam Leffler, Errno Consulting
+ * 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.
+ *
+ * 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.
+ */
+#ifndef lint
+static const char rcsid[] = "$FreeBSD$";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/errno.h>
+#include <sys/mman.h>
+#include <sys/sbuf.h>
+#include <sys/stat.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <err.h>
+#include <unistd.h>
+
+#include <bsdxml.h>
+
+#include "regdomain.h"
+
+#include <net80211/_ieee80211.h>
+
+#define MAXLEVEL 20
+
+struct mystate {
+ XML_Parser parser;
+ struct regdata *rdp;
+ struct regdomain *rd; /* current domain */
+ struct netband *netband; /* current netband */
+ struct freqband *freqband; /* current freqband */
+ struct country *country; /* current country */
+ netband_head *curband; /* current netband list */
+ int level;
+ struct sbuf *sbuf[MAXLEVEL];
+ int nident;
+};
+
+struct ident {
+ const void *id;
+ void *p;
+ enum { DOMAIN, COUNTRY, FREQBAND } type;
+};
+
+static void
+start_element(void *data, const char *name, const char **attr)
+{
+#define iseq(a,b) (strcasecmp(a,b) == 0)
+ struct mystate *mt;
+ const void *id, *ref, *mode;
+ int i;
+
+ mt = data;
+ if (++mt->level == MAXLEVEL) {
+ /* XXX force parser to abort */
+ return;
+ }
+ mt->sbuf[mt->level] = sbuf_new_auto();
+ id = ref = mode = NULL;
+ for (i = 0; attr[i] != NULL; i += 2) {
+ if (iseq(attr[i], "id")) {
+ id = attr[i+1];
+ } else if (iseq(attr[i], "ref")) {
+ ref = attr[i+1];
+ } else if (iseq(attr[i], "mode")) {
+ mode = attr[i+1];
+ } else
+ printf("%*.*s[%s = %s]\n", mt->level + 1,
+ mt->level + 1, "", attr[i], attr[i+1]);
+ }
+ if (iseq(name, "rd") && mt->rd == NULL) {
+ if (mt->country == NULL) {
+ mt->rd = calloc(1, sizeof(struct regdomain));
+ mt->rd->name = strdup(id);
+ mt->nident++;
+ LIST_INSERT_HEAD(&mt->rdp->domains, mt->rd, next);
+ } else
+ mt->country->rd = (void *)strdup(ref);
+ return;
+ }
+ if (iseq(name, "defcc") && mt->rd != NULL) {
+ mt->rd->cc = (void *)strdup(ref);
+ return;
+ }
+ if (iseq(name, "netband") && mt->curband == NULL && mt->rd != NULL) {
+ if (mode == NULL) {
+ warnx("no mode for netband at line %ld",
+ XML_GetCurrentLineNumber(mt->parser));
+ return;
+ }
+ if (iseq(mode, "11b"))
+ mt->curband = &mt->rd->bands_11b;
+ else if (iseq(mode, "11g"))
+ mt->curband = &mt->rd->bands_11g;
+ else if (iseq(mode, "11a"))
+ mt->curband = &mt->rd->bands_11a;
+ else if (iseq(mode, "11ng"))
+ mt->curband = &mt->rd->bands_11ng;
+ else if (iseq(mode, "11na"))
+ mt->curband = &mt->rd->bands_11na;
+ else
+ warnx("unknown mode \"%s\" at line %ld",
+ __DECONST(char *, mode),
+ XML_GetCurrentLineNumber(mt->parser));
+ return;
+ }
+ if (iseq(name, "band") && mt->netband == NULL) {
+ if (mt->curband == NULL) {
+ warnx("band without enclosing netband at line %ld",
+ XML_GetCurrentLineNumber(mt->parser));
+ return;
+ }
+ mt->netband = calloc(1, sizeof(struct netband));
+ LIST_INSERT_HEAD(mt->curband, mt->netband, next);
+ return;
+ }
+ if (iseq(name, "freqband") && mt->freqband == NULL && mt->netband != NULL) {
+ /* XXX handle inlines and merge into table? */
+ if (mt->netband->band != NULL) {
+ warnx("duplicate freqband at line %ld ignored",
+ XML_GetCurrentLineNumber(mt->parser));
+ /* XXX complain */
+ } else
+ mt->netband->band = (void *)strdup(ref);
+ return;
+ }
+
+ if (iseq(name, "country") && mt->country == NULL) {
+ mt->country = calloc(1, sizeof(struct country));
+ mt->country->isoname = strdup(id);
+ mt->country->code = NO_COUNTRY;
+ mt->nident++;
+ LIST_INSERT_HEAD(&mt->rdp->countries, mt->country, next);
+ return;
+ }
+
+ if (iseq(name, "freqband") && mt->freqband == NULL) {
+ mt->freqband = calloc(1, sizeof(struct freqband));
+ mt->freqband->id = strdup(id);
+ mt->nident++;
+ LIST_INSERT_HEAD(&mt->rdp->freqbands, mt->freqband, next);
+ return;
+ }
+#undef iseq
+}
+
+static int
+decode_flag(struct mystate *mt, const char *p, int len)
+{
+#define iseq(a,b) (strcasecmp(a,b) == 0)
+ static const struct {
+ const char *name;
+ int len;
+ uint32_t value;
+ } flags[] = {
+#define FLAG(x) { #x, sizeof(#x)-1, x }
+ FLAG(IEEE80211_CHAN_A),
+ FLAG(IEEE80211_CHAN_B),
+ FLAG(IEEE80211_CHAN_G),
+ FLAG(IEEE80211_CHAN_HT20),
+ FLAG(IEEE80211_CHAN_HT40),
+ FLAG(IEEE80211_CHAN_ST),
+ FLAG(IEEE80211_CHAN_TURBO),
+ FLAG(IEEE80211_CHAN_PASSIVE),
+ FLAG(IEEE80211_CHAN_DFS),
+ FLAG(IEEE80211_CHAN_CCK),
+ FLAG(IEEE80211_CHAN_OFDM),
+ FLAG(IEEE80211_CHAN_2GHZ),
+ FLAG(IEEE80211_CHAN_5GHZ),
+ FLAG(IEEE80211_CHAN_DYN),
+ FLAG(IEEE80211_CHAN_GFSK),
+ FLAG(IEEE80211_CHAN_GSM),
+ FLAG(IEEE80211_CHAN_STURBO),
+ FLAG(IEEE80211_CHAN_HALF),
+ FLAG(IEEE80211_CHAN_QUARTER),
+ FLAG(IEEE80211_CHAN_HT40U),
+ FLAG(IEEE80211_CHAN_HT40D),
+ FLAG(IEEE80211_CHAN_4MSXMIT),
+ FLAG(IEEE80211_CHAN_NOADHOC),
+ FLAG(IEEE80211_CHAN_NOHOSTAP),
+ FLAG(IEEE80211_CHAN_11D),
+ FLAG(IEEE80211_CHAN_FHSS),
+ FLAG(IEEE80211_CHAN_PUREG),
+ FLAG(IEEE80211_CHAN_108A),
+ FLAG(IEEE80211_CHAN_108G),
+#undef FLAG
+ { "ECM", 3, REQ_ECM },
+ { "INDOOR", 6, REQ_INDOOR },
+ { "OUTDOOR", 7, REQ_OUTDOOR },
+ };
+ int i;
+
+ for (i = 0; i < sizeof(flags)/sizeof(flags[0]); i++)
+ if (len == flags[i].len && iseq(p, flags[i].name))
+ return flags[i].value;
+ warnx("unknown flag \"%.*s\" at line %ld ignored",
+ len, p, XML_GetCurrentLineNumber(mt->parser));
+ return 0;
+#undef iseq
+}
+
+static void
+end_element(void *data, const char *name)
+{
+#define iseq(a,b) (strcasecmp(a,b) == 0)
+ struct mystate *mt;
+ int len;
+ char *p;
+
+ mt = data;
+ sbuf_finish(mt->sbuf[mt->level]);
+ p = sbuf_data(mt->sbuf[mt->level]);
+ len = sbuf_len(mt->sbuf[mt->level]);
+
+ /* <freqband>...</freqband> */
+ if (iseq(name, "freqstart") && mt->freqband != NULL) {
+ mt->freqband->freqStart = strtoul(p, NULL, 0);
+ goto done;
+ }
+ if (iseq(name, "freqend") && mt->freqband != NULL) {
+ mt->freqband->freqEnd = strtoul(p, NULL, 0);
+ goto done;
+ }
+ if (iseq(name, "chanwidth") && mt->freqband != NULL) {
+ mt->freqband->chanWidth = strtoul(p, NULL, 0);
+ goto done;
+ }
+ if (iseq(name, "chansep") && mt->freqband != NULL) {
+ mt->freqband->chanSep = strtoul(p, NULL, 0);
+ goto done;
+ }
+ if (iseq(name, "flags")) {
+ if (mt->freqband != NULL)
+ mt->freqband->flags |= decode_flag(mt, p, len);
+ else if (mt->netband != NULL)
+ mt->netband->flags |= decode_flag(mt, p, len);
+ else {
+ warnx("flags without freqband or netband at line %ld ignored",
+ XML_GetCurrentLineNumber(mt->parser));
+ }
+ goto done;
+ }
+
+ /* <rd> ... </rd> */
+ if (iseq(name, "name") && mt->rd != NULL) {
+ mt->rd->name = strdup(p);
+ goto done;
+ }
+ if (iseq(name, "sku") && mt->rd != NULL) {
+ mt->rd->sku = strtoul(p, NULL, 0);
+ goto done;
+ }
+ if (iseq(name, "netband") && mt->rd != NULL) {
+ mt->curband = NULL;
+ goto done;
+ }
+
+ /* <band> ... </band> */
+ if (iseq(name, "freqband") && mt->netband != NULL) {
+ /* XXX handle inline freqbands */
+ goto done;
+ }
+ if (iseq(name, "maxpower") && mt->netband != NULL) {
+ mt->netband->maxPower = strtoul(p, NULL, 0);
+ goto done;
+ }
+ if (iseq(name, "maxpowerdfs") && mt->netband != NULL) {
+ mt->netband->maxPowerDFS = strtoul(p, NULL, 0);
+ goto done;
+ }
+ if (iseq(name, "maxantgain") && mt->netband != NULL) {
+ mt->netband->maxAntGain = strtoul(p, NULL, 0);
+ goto done;
+ }
+
+ /* <country>...</country> */
+ if (iseq(name, "isocc") && mt->country != NULL) {
+ mt->country->code = strtoul(p, NULL, 0);
+ goto done;
+ }
+ if (iseq(name, "name") && mt->country != NULL) {
+ mt->country->name = strdup(p);
+ goto done;
+ }
+
+ if (len != 0) {
+ warnx("unexpected XML token \"%s\" data \"%s\" at line %ld",
+ name, p, XML_GetCurrentLineNumber(mt->parser));
+ /* XXX goto done? */
+ }
+ /* </freqband> */
+ if (iseq(name, "freqband") && mt->freqband != NULL) {
+ /* XXX must have start/end frequencies */
+ /* XXX must have channel width/sep */
+ mt->freqband = NULL;
+ goto done;
+ }
+ /* </rd> */
+ if (iseq(name, "rd") && mt->rd != NULL) {
+ mt->rd = NULL;
+ goto done;
+ }
+ /* </band> */
+ if (iseq(name, "band") && mt->netband != NULL) {
+ if (mt->netband->band == NULL) {
+ warnx("no freqbands for band at line %ld",
+ XML_GetCurrentLineNumber(mt->parser));
+ }
+ if (mt->netband->maxPower == 0) {
+ warnx("no maxpower for band at line %ld",
+ XML_GetCurrentLineNumber(mt->parser));
+ }
+ /* default max power w/ DFS to max power */
+ if (mt->netband->maxPowerDFS == 0)
+ mt->netband->maxPowerDFS = mt->netband->maxPower;
+ mt->netband = NULL;
+ goto done;
+ }
+ /* </netband> */
+ if (iseq(name, "netband") && mt->netband != NULL) {
+ mt->curband = NULL;
+ goto done;
+ }
+ /* </country> */
+ if (iseq(name, "country") && mt->country != NULL) {
+ if (mt->country->code == NO_COUNTRY) {
+ warnx("no ISO cc for country at line %ld",
+ XML_GetCurrentLineNumber(mt->parser));
+ }
+ if (mt->country->name == NULL) {
+ warnx("no name for country at line %ld",
+ XML_GetCurrentLineNumber(mt->parser));
+ }
+ if (mt->country->rd == NULL) {
+ warnx("no regdomain reference for country at line %ld",
+ XML_GetCurrentLineNumber(mt->parser));
+ }
+ mt->country = NULL;
+ goto done;
+ }
+done:
+ sbuf_delete(mt->sbuf[mt->level]);
+ mt->sbuf[mt->level--] = NULL;
+#undef iseq
+}
+
+static void
+char_data(void *data, const XML_Char *s, int len)
+{
+ struct mystate *mt;
+ const char *b, *e;
+
+ mt = data;
+
+ b = s;
+ e = s + len-1;
+ for (; isspace(*b) && b < e; b++)
+ ;
+ for (; isspace(*e) && e > b; e++)
+ ;
+ if (e != b || (*b != '\0' && !isspace(*b)))
+ sbuf_bcat(mt->sbuf[mt->level], b, e-b+1);
+}
+
+static void *
+findid(struct regdata *rdp, const void *id, int type)
+{
+ struct ident *ip;
+
+ for (ip = rdp->ident; ip->id != NULL; ip++)
+ if (ip->type == type && strcasecmp(ip->id, id) == 0)
+ return ip->p;
+ return NULL;
+}
+
+/*
+ * Parse an regdomain XML configuration and build the internal representation.
+ */
+int
+lib80211_regdomain_readconfig(struct regdata *rdp, const void *p, size_t len)
+{
+ struct mystate *mt;
+ struct regdomain *dp;
+ struct country *cp;
+ struct freqband *fp;
+ struct netband *nb;
+ const void *id;
+ int i, errors;
+
+ memset(rdp, 0, sizeof(struct regdata));
+ mt = calloc(1, sizeof(struct mystate));
+ if (mt == NULL)
+ return ENOMEM;
+ /* parse the XML input */
+ mt->rdp = rdp;
+ mt->parser = XML_ParserCreate(NULL);
+ XML_SetUserData(mt->parser, mt);
+ XML_SetElementHandler(mt->parser, start_element, end_element);
+ XML_SetCharacterDataHandler(mt->parser, char_data);
+ if (XML_Parse(mt->parser, p, len, 1) != XML_STATUS_OK) {
+ warnx("%s: %s at line %ld", __func__,
+ XML_ErrorString(XML_GetErrorCode(mt->parser)),
+ XML_GetCurrentLineNumber(mt->parser));
+ return -1;
+ }
+ XML_ParserFree(mt->parser);
+
+ /* setup the identifer table */
+ rdp->ident = calloc(sizeof(struct ident), mt->nident + 1);
+ if (rdp->ident == NULL)
+ return ENOMEM;
+ free(mt);
+
+ errors = 0;
+ i = 0;
+ LIST_FOREACH(dp, &rdp->domains, next) {
+ rdp->ident[i].id = dp->name;
+ rdp->ident[i].p = dp;
+ rdp->ident[i].type = DOMAIN;
+ i++;
+ }
+ LIST_FOREACH(fp, &rdp->freqbands, next) {
+ rdp->ident[i].id = fp->id;
+ rdp->ident[i].p = fp;
+ rdp->ident[i].type = FREQBAND;
+ i++;
+ }
+ LIST_FOREACH(cp, &rdp->countries, next) {
+ rdp->ident[i].id = cp->isoname;
+ rdp->ident[i].p = cp;
+ rdp->ident[i].type = COUNTRY;
+ i++;
+ }
+
+ /* patch references */
+ LIST_FOREACH(dp, &rdp->domains, next) {
+ if (dp->cc != NULL) {
+ id = dp->cc;
+ dp->cc = findid(rdp, id, COUNTRY);
+ if (dp->cc == NULL) {
+ warnx("undefined country \"%s\"",
+ __DECONST(char *, id));
+ errors++;
+ }
+ free(__DECONST(char *, id));
+ }
+ LIST_FOREACH(nb, &dp->bands_11b, next) {
+ id = findid(rdp, nb->band, FREQBAND);
+ if (id == NULL) {
+ warnx("undefined 11b band \"%s\"",
+ __DECONST(char *, nb->band));
+ errors++;
+ }
+ nb->band = id;
+ }
+ LIST_FOREACH(nb, &dp->bands_11g, next) {
+ id = findid(rdp, nb->band, FREQBAND);
+ if (id == NULL) {
+ warnx("undefined 11g band \"%s\"",
+ __DECONST(char *, nb->band));
+ errors++;
+ }
+ nb->band = id;
+ }
+ LIST_FOREACH(nb, &dp->bands_11a, next) {
+ id = findid(rdp, nb->band, FREQBAND);
+ if (id == NULL) {
+ warnx("undefined 11a band \"%s\"",
+ __DECONST(char *, nb->band));
+ errors++;
+ }
+ nb->band = id;
+ }
+ LIST_FOREACH(nb, &dp->bands_11ng, next) {
+ id = findid(rdp, nb->band, FREQBAND);
+ if (id == NULL) {
+ warnx("undefined 11ng band \"%s\"",
+ __DECONST(char *, nb->band));
+ errors++;
+ }
+ nb->band = id;
+ }
+ LIST_FOREACH(nb, &dp->bands_11na, next) {
+ id = findid(rdp, nb->band, FREQBAND);
+ if (id == NULL) {
+ warnx("undefined 11na band \"%s\"",
+ __DECONST(char *, nb->band));
+ errors++;
+ }
+ nb->band = id;
+ }
+ }
+ LIST_FOREACH(cp, &rdp->countries, next) {
+ id = cp->rd;
+ cp->rd = findid(rdp, id, DOMAIN);
+ if (cp->rd == NULL) {
+ warnx("undefined country \"%s\"",
+ __DECONST(char *, id));
+ errors++;
+ }
+ free(__DECONST(char *, id));
+ }
+
+ return errors ? EINVAL : 0;
+}
+
+static void
+cleanup_bands(netband_head *head)
+{
+ struct netband *nb;
+
+ for (;;) {
+ nb = LIST_FIRST(head);
+ if (nb == NULL)
+ break;
+ free(nb);
+ }
+}
+
+/*
+ * Cleanup state/resources for a previously parsed regdomain database.
+ */
+void
+lib80211_regdomain_cleanup(struct regdata *rdp)
+{
+
+ free(rdp->ident);
+ rdp->ident = NULL;
+ for (;;) {
+ struct regdomain *dp = LIST_FIRST(&rdp->domains);
+ if (dp == NULL)
+ break;
+ LIST_REMOVE(dp, next);
+ cleanup_bands(&dp->bands_11b);
+ cleanup_bands(&dp->bands_11g);
+ cleanup_bands(&dp->bands_11a);
+ cleanup_bands(&dp->bands_11ng);
+ cleanup_bands(&dp->bands_11na);
+ if (dp->name != NULL)
+ free(__DECONST(char *, dp->name));
+ }
+ for (;;) {
+ struct country *cp = LIST_FIRST(&rdp->countries);
+ if (cp == NULL)
+ break;
+ LIST_REMOVE(cp, next);
+ if (cp->name != NULL)
+ free(__DECONST(char *, cp->name));
+ free(cp);
+ }
+ for (;;) {
+ struct freqband *fp = LIST_FIRST(&rdp->freqbands);
+ if (fp == NULL)
+ break;
+ LIST_REMOVE(fp, next);
+ free(fp);
+ }
+}
+
+struct regdata *
+lib80211_alloc_regdata(void)
+{
+ struct regdata *rdp;
+ struct stat sb;
+ void *xml;
+ int fd;
+
+ rdp = calloc(1, sizeof(struct regdata));
+
+ fd = open(_PATH_REGDOMAIN, O_RDONLY);
+ if (fd < 0) {
+#ifdef DEBUG
+ warn("%s: open(%s)", __func__, _PATH_REGDOMAIN);
+#endif
+ free(rdp);
+ return NULL;
+ }
+ if (fstat(fd, &sb) < 0) {
+#ifdef DEBUG
+ warn("%s: fstat(%s)", __func__, _PATH_REGDOMAIN);
+#endif
+ close(fd);
+ free(rdp);
+ return NULL;
+ }
+ xml = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+ if (xml == MAP_FAILED) {
+#ifdef DEBUG
+ warn("%s: mmap", __func__);
+#endif
+ close(fd);
+ free(rdp);
+ return NULL;
+ }
+ if (lib80211_regdomain_readconfig(rdp, xml, sb.st_size) != 0) {
+#ifdef DEBUG
+ warn("%s: error reading regulatory database", __func__);
+#endif
+ munmap(xml, sb.st_size);
+ close(fd);
+ free(rdp);
+ return NULL;
+ }
+ munmap(xml, sb.st_size);
+ close(fd);
+
+ return rdp;
+}
+
+void
+lib80211_free_regdata(struct regdata *rdp)
+{
+ lib80211_regdomain_cleanup(rdp);
+ free(rdp);
+}
+
+/*
+ * Lookup a regdomain by SKU.
+ */
+const struct regdomain *
+lib80211_regdomain_findbysku(const struct regdata *rdp, enum RegdomainCode sku)
+{
+ const struct regdomain *dp;
+
+ LIST_FOREACH(dp, &rdp->domains, next) {
+ if (dp->sku == sku)
+ return dp;
+ }
+ return NULL;
+}
+
+/*
+ * Lookup a regdomain by name.
+ */
+const struct regdomain *
+lib80211_regdomain_findbyname(const struct regdata *rdp, const char *name)
+{
+ const struct regdomain *dp;
+
+ LIST_FOREACH(dp, &rdp->domains, next) {
+ if (strcasecmp(dp->name, name) == 0)
+ return dp;
+ }
+ return NULL;
+}
+
+/*
+ * Lookup a country by ISO country code.
+ */
+const struct country *
+lib80211_country_findbycc(const struct regdata *rdp, enum ISOCountryCode cc)
+{
+ const struct country *cp;
+
+ LIST_FOREACH(cp, &rdp->countries, next) {
+ if (cp->code == cc)
+ return cp;
+ }
+ return NULL;
+}
+
+/*
+ * Lookup a country by ISO/long name.
+ */
+const struct country *
+lib80211_country_findbyname(const struct regdata *rdp, const char *name)
+{
+ const struct country *cp;
+ int len;
+
+ len = strlen(name);
+ LIST_FOREACH(cp, &rdp->countries, next) {
+ if (strcasecmp(cp->isoname, name) == 0)
+ return cp;
+ }
+ LIST_FOREACH(cp, &rdp->countries, next) {
+ if (strncasecmp(cp->name, name, len) == 0)
+ return cp;
+ }
+ return NULL;
+}
diff --git a/sbin/ifconfig/regdomain.h b/sbin/ifconfig/regdomain.h
new file mode 100644
index 0000000..cfc2be0
--- /dev/null
+++ b/sbin/ifconfig/regdomain.h
@@ -0,0 +1,121 @@
+/*-
+ * Copyright (c) 2007-2008 Sam Leffler, Errno Consulting
+ * 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.
+ *
+ * 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.
+ *
+ * $FreeBSD$
+ */
+#ifndef _LIB80211_H_
+#define _LIB80211_H_
+
+#include <sys/cdefs.h>
+#include <sys/queue.h>
+
+#include <net80211/ieee80211_regdomain.h>
+
+__BEGIN_DECLS
+
+struct freqband {
+ uint16_t freqStart; /* starting frequency (MHz) */
+ uint16_t freqEnd; /* ending frequency (MHz) */
+ uint8_t chanWidth; /* channel width (MHz) */
+ uint8_t chanSep; /* channel sepaaration (MHz) */
+ uint32_t flags; /* common operational constraints */
+
+ const void *id;
+ LIST_ENTRY(freqband) next;
+};
+
+/* private flags, don't pass to os */
+#define REQ_ECM 0x1 /* enable if ECM set */
+#define REQ_INDOOR 0x2 /* enable only for indoor operation */
+#define REQ_OUTDOOR 0x4 /* enable only for outdoor operation */
+
+#define REQ_FLAGS (REQ_ECM|REQ_INDOOR|REQ_OUTDOOR)
+
+struct netband {
+ const struct freqband *band; /* channel list description */
+ uint8_t maxPower; /* regulatory cap on tx power (dBm) */
+ uint8_t maxPowerDFS; /* regulatory cap w/ DFS (dBm) */
+ uint8_t maxAntGain; /* max allowed antenna gain (.5 dBm) */
+ uint32_t flags; /* net80211 channel flags */
+
+ LIST_ENTRY(netband) next;
+};
+typedef LIST_HEAD(, netband) netband_head;
+
+struct country;
+
+struct regdomain {
+ enum RegdomainCode sku; /* regdomain code/SKU */
+ const char *name; /* printable name */
+ const struct country *cc; /* country code for 1-1/default map */
+
+ netband_head bands_11b; /* 11b operation */
+ netband_head bands_11g; /* 11g operation */
+ netband_head bands_11a; /* 11a operation */
+ netband_head bands_11ng;/* 11ng operation */
+ netband_head bands_11na;/* 11na operation */
+
+ LIST_ENTRY(regdomain) next;
+};
+
+struct country {
+ enum ISOCountryCode code;
+#define NO_COUNTRY 0xffff
+ const struct regdomain *rd;
+ const char* isoname;
+ const char* name;
+
+ LIST_ENTRY(country) next;
+};
+
+struct ident;
+
+struct regdata {
+ LIST_HEAD(, country) countries; /* country code table */
+ LIST_HEAD(, regdomain) domains; /* regulatory domains */
+ LIST_HEAD(, freqband) freqbands; /* frequency band table */
+ struct ident *ident; /* identifier table */
+};
+
+#define _PATH_REGDOMAIN "/etc/regdomain.xml"
+
+struct regdata *lib80211_alloc_regdata(void);
+void lib80211_free_regdata(struct regdata *);
+
+int lib80211_regdomain_readconfig(struct regdata *, const void *, size_t);
+void lib80211_regdomain_cleanup(struct regdata *);
+
+const struct regdomain *lib80211_regdomain_findbysku(const struct regdata *,
+ enum RegdomainCode);
+const struct regdomain *lib80211_regdomain_findbyname(const struct regdata *,
+ const char *);
+
+const struct country *lib80211_country_findbycc(const struct regdata *,
+ enum ISOCountryCode);
+const struct country *lib80211_country_findbyname(const struct regdata *,
+ const char *);
+
+__END_DECLS
+
+#endif /* _LIB80211_H_ */
diff --git a/sbin/ifconfig/sfp.c b/sbin/ifconfig/sfp.c
new file mode 100644
index 0000000..d4da8c3
--- /dev/null
+++ b/sbin/ifconfig/sfp.c
@@ -0,0 +1,827 @@
+/*-
+ * Copyright (c) 2014 Alexander V. Chernikov. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <net/if.h>
+#include <net/sff8436.h>
+#include <net/sff8472.h>
+
+#include <math.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "ifconfig.h"
+
+struct i2c_info;
+typedef int (read_i2c)(struct i2c_info *ii, uint8_t addr, uint8_t off,
+ uint8_t len, caddr_t buf);
+
+struct i2c_info {
+ int s;
+ int error;
+ int bshift;
+ int qsfp;
+ int do_diag;
+ struct ifreq *ifr;
+ read_i2c *f;
+ char *textbuf;
+ size_t bufsize;
+ int cfd;
+ int port_id;
+ int chip_id;
+};
+
+static void dump_i2c_data(struct i2c_info *ii, uint8_t addr, uint8_t off,
+ uint8_t len);
+
+struct _nv {
+ int v;
+ const char *n;
+};
+
+const char *find_value(struct _nv *x, int value);
+const char *find_zero_bit(struct _nv *x, int value, int sz);
+
+/* SFF-8472 Rev. 11.4 table 3.4: Connector values */
+static struct _nv conn[] = {
+ { 0x00, "Unknown" },
+ { 0x01, "SC" },
+ { 0x02, "Fibre Channel Style 1 copper" },
+ { 0x03, "Fibre Channel Style 2 copper" },
+ { 0x04, "BNC/TNC" },
+ { 0x05, "Fibre Channel coaxial" },
+ { 0x06, "FiberJack" },
+ { 0x07, "LC" },
+ { 0x08, "MT-RJ" },
+ { 0x09, "MU" },
+ { 0x0A, "SG" },
+ { 0x0B, "Optical pigtail" },
+ { 0x0C, "MPO Parallel Optic" },
+ { 0x20, "HSSDC II" },
+ { 0x21, "Copper pigtail" },
+ { 0x22, "RJ45" },
+ { 0x23, "No separate connector" }, /* SFF-8436 */
+ { 0, NULL }
+};
+
+/* SFF-8472 Rev. 11.4 table 3.5: Transceiver codes */
+/* 10G Ethernet/IB compliance codes, byte 3 */
+static struct _nv eth_10g[] = {
+ { 0x80, "10G Base-ER" },
+ { 0x40, "10G Base-LRM" },
+ { 0x20, "10G Base-LR" },
+ { 0x10, "10G Base-SR" },
+ { 0x08, "1X SX" },
+ { 0x04, "1X LX" },
+ { 0x02, "1X Copper Active" },
+ { 0x01, "1X Copper Passive" },
+ { 0, NULL }
+};
+
+/* Ethernet compliance codes, byte 6 */
+static struct _nv eth_compat[] = {
+ { 0x80, "BASE-PX" },
+ { 0x40, "BASE-BX10" },
+ { 0x20, "100BASE-FX" },
+ { 0x10, "100BASE-LX/LX10" },
+ { 0x08, "1000BASE-T" },
+ { 0x04, "1000BASE-CX" },
+ { 0x02, "1000BASE-LX" },
+ { 0x01, "1000BASE-SX" },
+ { 0, NULL }
+};
+
+/* FC link length, byte 7 */
+static struct _nv fc_len[] = {
+ { 0x80, "very long distance" },
+ { 0x40, "short distance" },
+ { 0x20, "intermediate distance" },
+ { 0x10, "long distance" },
+ { 0x08, "medium distance" },
+ { 0, NULL }
+};
+
+/* Channel/Cable technology, byte 7-8 */
+static struct _nv cab_tech[] = {
+ { 0x0400, "Shortwave laser (SA)" },
+ { 0x0200, "Longwave laser (LC)" },
+ { 0x0100, "Electrical inter-enclosure (EL)" },
+ { 0x80, "Electrical intra-enclosure (EL)" },
+ { 0x40, "Shortwave laser (SN)" },
+ { 0x20, "Shortwave laser (SL)" },
+ { 0x10, "Longwave laser (LL)" },
+ { 0x08, "Active Cable" },
+ { 0x04, "Passive Cable" },
+ { 0, NULL }
+};
+
+/* FC Transmission media, byte 9 */
+static struct _nv fc_media[] = {
+ { 0x80, "Twin Axial Pair" },
+ { 0x40, "Twisted Pair" },
+ { 0x20, "Miniature Coax" },
+ { 0x10, "Viao Coax" },
+ { 0x08, "Miltimode, 62.5um" },
+ { 0x04, "Multimode, 50um" },
+ { 0x02, "" },
+ { 0x01, "Single Mode" },
+ { 0, NULL }
+};
+
+/* FC Speed, byte 10 */
+static struct _nv fc_speed[] = {
+ { 0x80, "1200 MBytes/sec" },
+ { 0x40, "800 MBytes/sec" },
+ { 0x20, "1600 MBytes/sec" },
+ { 0x10, "400 MBytes/sec" },
+ { 0x08, "3200 MBytes/sec" },
+ { 0x04, "200 MBytes/sec" },
+ { 0x01, "100 MBytes/sec" },
+ { 0, NULL }
+};
+
+/* SFF-8436 Rev. 4.8 table 33: Specification compliance */
+
+/* 10/40G Ethernet compliance codes, byte 128 + 3 */
+static struct _nv eth_1040g[] = {
+ { 0x80, "Reserved" },
+ { 0x40, "10GBASE-LRM" },
+ { 0x20, "10GBASE-LR" },
+ { 0x10, "10GBASE-SR" },
+ { 0x08, "40GBASE-CR4" },
+ { 0x04, "40GBASE-SR4" },
+ { 0x02, "40GBASE-LR4" },
+ { 0x01, "40G Active Cable" },
+ { 0, NULL }
+};
+
+const char *
+find_value(struct _nv *x, int value)
+{
+ for (; x->n != NULL; x++)
+ if (x->v == value)
+ return (x->n);
+ return (NULL);
+}
+
+const char *
+find_zero_bit(struct _nv *x, int value, int sz)
+{
+ int v, m;
+ const char *s;
+
+ v = 1;
+ for (v = 1, m = 1 << (8 * sz); v < m; v *= 2) {
+ if ((value & v) == 0)
+ continue;
+ if ((s = find_value(x, value & v)) != NULL) {
+ value &= ~v;
+ return (s);
+ }
+ }
+
+ return (NULL);
+}
+
+static void
+convert_sff_identifier(char *buf, size_t size, uint8_t value)
+{
+ const char *x;
+
+ x = NULL;
+ if (value <= SFF_8024_ID_LAST)
+ x = sff_8024_id[value];
+ else {
+ if (value > 0x80)
+ x = "Vendor specific";
+ else
+ x = "Reserved";
+ }
+
+ snprintf(buf, size, "%s", x);
+}
+
+static void
+convert_sff_connector(char *buf, size_t size, uint8_t value)
+{
+ const char *x;
+
+ if ((x = find_value(conn, value)) == NULL) {
+ if (value >= 0x0D && value <= 0x1F)
+ x = "Unallocated";
+ else if (value >= 0x24 && value <= 0x7F)
+ x = "Unallocated";
+ else
+ x = "Vendor specific";
+ }
+
+ snprintf(buf, size, "%s", x);
+}
+
+static void
+get_sfp_identifier(struct i2c_info *ii, char *buf, size_t size)
+{
+ uint8_t data;
+
+ ii->f(ii, SFF_8472_BASE, SFF_8472_ID, 1, (caddr_t)&data);
+ convert_sff_identifier(buf, size, data);
+}
+
+static void
+get_sfp_connector(struct i2c_info *ii, char *buf, size_t size)
+{
+ uint8_t data;
+
+ ii->f(ii, SFF_8472_BASE, SFF_8472_CONNECTOR, 1, (caddr_t)&data);
+ convert_sff_connector(buf, size, data);
+}
+
+static void
+get_qsfp_identifier(struct i2c_info *ii, char *buf, size_t size)
+{
+ uint8_t data;
+
+ ii->f(ii, SFF_8436_BASE, SFF_8436_ID, 1, (caddr_t)&data);
+ convert_sff_identifier(buf, size, data);
+}
+
+static void
+get_qsfp_connector(struct i2c_info *ii, char *buf, size_t size)
+{
+ uint8_t data;
+
+ ii->f(ii, SFF_8436_BASE, SFF_8436_CONNECTOR, 1, (caddr_t)&data);
+ convert_sff_connector(buf, size, data);
+}
+
+static void
+printf_sfp_transceiver_descr(struct i2c_info *ii, char *buf, size_t size)
+{
+ char xbuf[12];
+ const char *tech_class, *tech_len, *tech_tech, *tech_media, *tech_speed;
+
+ tech_class = NULL;
+ tech_len = NULL;
+ tech_tech = NULL;
+ tech_media = NULL;
+ tech_speed = NULL;
+
+ /* Read bytes 3-10 at once */
+ ii->f(ii, SFF_8472_BASE, SFF_8472_TRANS_START, 8, &xbuf[3]);
+
+ /* Check 10G ethernet first */
+ tech_class = find_zero_bit(eth_10g, xbuf[3], 1);
+ if (tech_class == NULL) {
+ /* No match. Try 1G */
+ tech_class = find_zero_bit(eth_compat, xbuf[6], 1);
+ }
+
+ tech_len = find_zero_bit(fc_len, xbuf[7], 1);
+ tech_tech = find_zero_bit(cab_tech, xbuf[7] << 8 | xbuf[8], 2);
+ tech_media = find_zero_bit(fc_media, xbuf[9], 1);
+ tech_speed = find_zero_bit(fc_speed, xbuf[10], 1);
+
+ printf("Class: %s\n", tech_class);
+ printf("Length: %s\n", tech_len);
+ printf("Tech: %s\n", tech_tech);
+ printf("Media: %s\n", tech_media);
+ printf("Speed: %s\n", tech_speed);
+}
+
+static void
+get_sfp_transceiver_class(struct i2c_info *ii, char *buf, size_t size)
+{
+ const char *tech_class;
+ uint8_t code;
+
+ unsigned char qbuf[8];
+ ii->f(ii, SFF_8472_BASE, SFF_8472_TRANS_START, 8, (caddr_t)qbuf);
+
+ /* Check 10G Ethernet/IB first */
+ ii->f(ii, SFF_8472_BASE, SFF_8472_TRANS_START, 1, (caddr_t)&code);
+ tech_class = find_zero_bit(eth_10g, code, 1);
+ if (tech_class == NULL) {
+ /* No match. Try Ethernet 1G */
+ ii->f(ii, SFF_8472_BASE, SFF_8472_TRANS_START + 3,
+ 1, (caddr_t)&code);
+ tech_class = find_zero_bit(eth_compat, code, 1);
+ }
+
+ if (tech_class == NULL)
+ tech_class = "Unknown";
+
+ snprintf(buf, size, "%s", tech_class);
+}
+
+static void
+get_qsfp_transceiver_class(struct i2c_info *ii, char *buf, size_t size)
+{
+ const char *tech_class;
+ uint8_t code;
+
+ /* Check 10/40G Ethernet class only */
+ ii->f(ii, SFF_8436_BASE, SFF_8436_CODE_E1040G, 1, (caddr_t)&code);
+ tech_class = find_zero_bit(eth_1040g, code, 1);
+ if (tech_class == NULL)
+ tech_class = "Unknown";
+
+ snprintf(buf, size, "%s", tech_class);
+}
+
+/*
+ * Print SFF-8472/SFF-8436 string to supplied buffer.
+ * All (vendor-specific) strings are padded right with '0x20'.
+ */
+static void
+convert_sff_name(char *buf, size_t size, char *xbuf)
+{
+ char *p;
+
+ for (p = &xbuf[16]; *(p - 1) == 0x20; p--)
+ ;
+ *p = '\0';
+ snprintf(buf, size, "%s", xbuf);
+}
+
+static void
+convert_sff_date(char *buf, size_t size, char *xbuf)
+{
+
+ snprintf(buf, size, "20%c%c-%c%c-%c%c", xbuf[0], xbuf[1],
+ xbuf[2], xbuf[3], xbuf[4], xbuf[5]);
+}
+
+static void
+get_sfp_vendor_name(struct i2c_info *ii, char *buf, size_t size)
+{
+ char xbuf[17];
+
+ memset(xbuf, 0, sizeof(xbuf));
+ ii->f(ii, SFF_8472_BASE, SFF_8472_VENDOR_START, 16, xbuf);
+ convert_sff_name(buf, size, xbuf);
+}
+
+static void
+get_sfp_vendor_pn(struct i2c_info *ii, char *buf, size_t size)
+{
+ char xbuf[17];
+
+ memset(xbuf, 0, sizeof(xbuf));
+ ii->f(ii, SFF_8472_BASE, SFF_8472_PN_START, 16, xbuf);
+ convert_sff_name(buf, size, xbuf);
+}
+
+static void
+get_sfp_vendor_sn(struct i2c_info *ii, char *buf, size_t size)
+{
+ char xbuf[17];
+
+ memset(xbuf, 0, sizeof(xbuf));
+ ii->f(ii, SFF_8472_BASE, SFF_8472_SN_START, 16, xbuf);
+ convert_sff_name(buf, size, xbuf);
+}
+
+static void
+get_sfp_vendor_date(struct i2c_info *ii, char *buf, size_t size)
+{
+ char xbuf[6];
+
+ memset(xbuf, 0, sizeof(xbuf));
+ /* Date code, see Table 3.8 for description */
+ ii->f(ii, SFF_8472_BASE, SFF_8472_DATE_START, 6, xbuf);
+ convert_sff_date(buf, size, xbuf);
+}
+
+static void
+get_qsfp_vendor_name(struct i2c_info *ii, char *buf, size_t size)
+{
+ char xbuf[17];
+
+ memset(xbuf, 0, sizeof(xbuf));
+ ii->f(ii, SFF_8436_BASE, SFF_8436_VENDOR_START, 16, xbuf);
+ convert_sff_name(buf, size, xbuf);
+}
+
+static void
+get_qsfp_vendor_pn(struct i2c_info *ii, char *buf, size_t size)
+{
+ char xbuf[17];
+
+ memset(xbuf, 0, sizeof(xbuf));
+ ii->f(ii, SFF_8436_BASE, SFF_8436_PN_START, 16, xbuf);
+ convert_sff_name(buf, size, xbuf);
+}
+
+static void
+get_qsfp_vendor_sn(struct i2c_info *ii, char *buf, size_t size)
+{
+ char xbuf[17];
+
+ memset(xbuf, 0, sizeof(xbuf));
+ ii->f(ii, SFF_8436_BASE, SFF_8436_SN_START, 16, xbuf);
+ convert_sff_name(buf, size, xbuf);
+}
+
+static void
+get_qsfp_vendor_date(struct i2c_info *ii, char *buf, size_t size)
+{
+ char xbuf[6];
+
+ memset(xbuf, 0, sizeof(xbuf));
+ ii->f(ii, SFF_8436_BASE, SFF_8436_DATE_START, 6, xbuf);
+ convert_sff_date(buf, size, xbuf);
+}
+
+static void
+print_sfp_vendor(struct i2c_info *ii, char *buf, size_t size)
+{
+ char xbuf[80];
+
+ memset(xbuf, 0, sizeof(xbuf));
+ if (ii->qsfp != 0) {
+ get_qsfp_vendor_name(ii, xbuf, 20);
+ get_qsfp_vendor_pn(ii, &xbuf[20], 20);
+ get_qsfp_vendor_sn(ii, &xbuf[40], 20);
+ get_qsfp_vendor_date(ii, &xbuf[60], 20);
+ } else {
+ get_sfp_vendor_name(ii, xbuf, 20);
+ get_sfp_vendor_pn(ii, &xbuf[20], 20);
+ get_sfp_vendor_sn(ii, &xbuf[40], 20);
+ get_sfp_vendor_date(ii, &xbuf[60], 20);
+ }
+
+ snprintf(buf, size, "vendor: %s PN: %s SN: %s DATE: %s",
+ xbuf, &xbuf[20], &xbuf[40], &xbuf[60]);
+}
+
+/*
+ * Converts internal templerature (SFF-8472, SFF-8436)
+ * 16-bit unsigned value to human-readable representation:
+ *
+ * Internally measured Module temperature are represented
+ * as a 16-bit signed twos complement value in increments of
+ * 1/256 degrees Celsius, yielding a total range of –128C to +128C
+ * that is considered valid between –40 and +125C.
+ *
+ */
+static void
+convert_sff_temp(char *buf, size_t size, char *xbuf)
+{
+ double d;
+
+ d = (double)(int8_t)xbuf[0];
+ d += (double)(uint8_t)xbuf[1] / 256;
+
+ snprintf(buf, size, "%.2f C", d);
+}
+
+/*
+ * Retrieves supplied voltage (SFF-8472, SFF-8436).
+ * 16-bit usigned value, treated as range 0..+6.55 Volts
+ */
+static void
+convert_sff_voltage(char *buf, size_t size, char *xbuf)
+{
+ double d;
+
+ d = (double)(((uint8_t)xbuf[0] << 8) | (uint8_t)xbuf[1]);
+ snprintf(buf, size, "%.2f Volts", d / 10000);
+}
+
+/*
+ * Converts value in @xbuf to both milliwats and dBm
+ * human representation.
+ */
+static void
+convert_sff_power(struct i2c_info *ii, char *buf, size_t size, char *xbuf)
+{
+ uint16_t mW;
+ double dbm;
+
+ mW = ((uint8_t)xbuf[0] << 8) + (uint8_t)xbuf[1];
+
+ /* Convert mw to dbm */
+ dbm = 10.0 * log10(1.0 * mW / 10000);
+
+ /*
+ * Assume internally-calibrated data.
+ * This is always true for SFF-8346, and explicitly
+ * checked for SFF-8472.
+ */
+
+ /* Table 3.9, bit 5 is set, internally calibrated */
+ snprintf(buf, size, "%d.%02d mW (%.2f dBm)",
+ mW / 10000, (mW % 10000) / 100, dbm);
+}
+
+static void
+get_sfp_temp(struct i2c_info *ii, char *buf, size_t size)
+{
+ char xbuf[2];
+
+ memset(xbuf, 0, sizeof(xbuf));
+ ii->f(ii, SFF_8472_DIAG, SFF_8472_TEMP, 2, xbuf);
+ convert_sff_temp(buf, size, xbuf);
+}
+
+static void
+get_sfp_voltage(struct i2c_info *ii, char *buf, size_t size)
+{
+ char xbuf[2];
+
+ memset(xbuf, 0, sizeof(xbuf));
+ ii->f(ii, SFF_8472_DIAG, SFF_8472_VCC, 2, xbuf);
+ convert_sff_voltage(buf, size, xbuf);
+}
+
+static void
+get_qsfp_temp(struct i2c_info *ii, char *buf, size_t size)
+{
+ char xbuf[2];
+
+ memset(xbuf, 0, sizeof(xbuf));
+ ii->f(ii, SFF_8436_BASE, SFF_8436_TEMP, 2, xbuf);
+ convert_sff_temp(buf, size, xbuf);
+}
+
+static void
+get_qsfp_voltage(struct i2c_info *ii, char *buf, size_t size)
+{
+ char xbuf[2];
+
+ memset(xbuf, 0, sizeof(xbuf));
+ ii->f(ii, SFF_8436_BASE, SFF_8436_VCC, 2, xbuf);
+ convert_sff_voltage(buf, size, xbuf);
+}
+
+static void
+get_sfp_rx_power(struct i2c_info *ii, char *buf, size_t size)
+{
+ char xbuf[2];
+
+ memset(xbuf, 0, sizeof(xbuf));
+ ii->f(ii, SFF_8472_DIAG, SFF_8472_RX_POWER, 2, xbuf);
+ convert_sff_power(ii, buf, size, xbuf);
+}
+
+static void
+get_sfp_tx_power(struct i2c_info *ii, char *buf, size_t size)
+{
+ char xbuf[2];
+
+ memset(xbuf, 0, sizeof(xbuf));
+ ii->f(ii, SFF_8472_DIAG, SFF_8472_TX_POWER, 2, xbuf);
+ convert_sff_power(ii, buf, size, xbuf);
+}
+
+static void
+get_qsfp_rx_power(struct i2c_info *ii, char *buf, size_t size, int chan)
+{
+ char xbuf[2];
+
+ memset(xbuf, 0, sizeof(xbuf));
+ ii->f(ii, SFF_8436_BASE, SFF_8436_RX_CH1_MSB + (chan - 1) * 2, 2, xbuf);
+ convert_sff_power(ii, buf, size, xbuf);
+}
+
+static void
+get_qsfp_tx_power(struct i2c_info *ii, char *buf, size_t size, int chan)
+{
+ char xbuf[2];
+
+ memset(xbuf, 0, sizeof(xbuf));
+ ii->f(ii, SFF_8436_BASE, SFF_8436_TX_CH1_MSB + (chan -1) * 2, 2, xbuf);
+ convert_sff_power(ii, buf, size, xbuf);
+}
+
+/* Generic handler */
+static int
+read_i2c_generic(struct i2c_info *ii, uint8_t addr, uint8_t off, uint8_t len,
+ caddr_t buf)
+{
+ struct ifi2creq req;
+ int i, l;
+
+ if (ii->error != 0)
+ return (ii->error);
+
+ ii->ifr->ifr_data = (caddr_t)&req;
+
+ i = 0;
+ l = 0;
+ memset(&req, 0, sizeof(req));
+ req.dev_addr = addr;
+ req.offset = off;
+ req.len = len;
+
+ while (len > 0) {
+ l = (len > sizeof(req.data)) ? sizeof(req.data) : len;
+ req.len = l;
+ if (ioctl(ii->s, SIOCGI2C, ii->ifr) != 0) {
+ ii->error = errno;
+ return (errno);
+ }
+
+ memcpy(&buf[i], req.data, l);
+ len -= l;
+ i += l;
+ req.offset += l;
+ }
+
+ return (0);
+}
+
+static void
+dump_i2c_data(struct i2c_info *ii, uint8_t addr, uint8_t off, uint8_t len)
+{
+ unsigned char buf[16];
+ int i, read;
+
+ while (len > 0) {
+ memset(buf, 0, sizeof(buf));
+ read = (len > sizeof(buf)) ? sizeof(buf) : len;
+ ii->f(ii, addr, off, read, buf);
+ if (ii->error != 0) {
+ fprintf(stderr, "Error reading i2c info\n");
+ return;
+ }
+
+ printf("\t");
+ for (i = 0; i < read; i++)
+ printf("%02X ", buf[i]);
+ printf("\n");
+ len -= read;
+ off += read;
+ }
+}
+
+static void
+print_qsfp_status(struct i2c_info *ii, int verbose)
+{
+ char buf[80], buf2[40], buf3[40];
+ uint8_t diag_type;
+ int i;
+
+ /* Read diagnostic monitoring type */
+ ii->f(ii, SFF_8436_BASE, SFF_8436_DIAG_TYPE, 1, (caddr_t)&diag_type);
+ if (ii->error != 0)
+ return;
+
+ /*
+ * Read monitoring data it is supplied.
+ * XXX: It is not exactly clear from standard
+ * how one can specify lack of measurements (passive cables case).
+ */
+ if (diag_type != 0)
+ ii->do_diag = 1;
+ ii->qsfp = 1;
+
+ /* Transceiver type */
+ get_qsfp_identifier(ii, buf, sizeof(buf));
+ get_qsfp_transceiver_class(ii, buf2, sizeof(buf2));
+ get_qsfp_connector(ii, buf3, sizeof(buf3));
+ if (ii->error == 0)
+ printf("\tplugged: %s %s (%s)\n", buf, buf2, buf3);
+ print_sfp_vendor(ii, buf, sizeof(buf));
+ if (ii->error == 0)
+ printf("\t%s\n", buf);
+
+ /* Request current measurements if they are provided: */
+ if (ii->do_diag != 0) {
+ get_qsfp_temp(ii, buf, sizeof(buf));
+ get_qsfp_voltage(ii, buf2, sizeof(buf2));
+ printf("\tmodule temperature: %s voltage: %s\n", buf, buf2);
+ for (i = 1; i <= 4; i++) {
+ get_qsfp_rx_power(ii, buf, sizeof(buf), i);
+ get_qsfp_tx_power(ii, buf2, sizeof(buf2), i);
+ printf("\tlane %d: RX: %s TX: %s\n", i, buf, buf2);
+ }
+ }
+
+ if (verbose > 2) {
+ printf("\n\tSFF8436 DUMP (0xA0 128..255 range):\n");
+ dump_i2c_data(ii, SFF_8436_BASE, 128, 128);
+ printf("\n\tSFF8436 DUMP (0xA0 0..81 range):\n");
+ dump_i2c_data(ii, SFF_8436_BASE, 0, 82);
+ }
+}
+
+static void
+print_sfp_status(struct i2c_info *ii, int verbose)
+{
+ char buf[80], buf2[40], buf3[40];
+ uint8_t diag_type, flags;
+
+ /* Read diagnostic monitoring type */
+ ii->f(ii, SFF_8472_BASE, SFF_8472_DIAG_TYPE, 1, (caddr_t)&diag_type);
+ if (ii->error != 0)
+ return;
+
+ /*
+ * Read monitoring data IFF it is supplied AND is
+ * internally calibrated
+ */
+ flags = SFF_8472_DDM_DONE | SFF_8472_DDM_INTERNAL;
+ if ((diag_type & flags) == flags)
+ ii->do_diag = 1;
+
+ /* Transceiver type */
+ get_sfp_identifier(ii, buf, sizeof(buf));
+ get_sfp_transceiver_class(ii, buf2, sizeof(buf2));
+ get_sfp_connector(ii, buf3, sizeof(buf3));
+ if (ii->error == 0)
+ printf("\tplugged: %s %s (%s)\n", buf, buf2, buf3);
+ print_sfp_vendor(ii, buf, sizeof(buf));
+ if (ii->error == 0)
+ printf("\t%s\n", buf);
+
+ if (verbose > 5)
+ printf_sfp_transceiver_descr(ii, buf, sizeof(buf));
+ /*
+ * Request current measurements iff they are provided:
+ */
+ if (ii->do_diag != 0) {
+ get_sfp_temp(ii, buf, sizeof(buf));
+ get_sfp_voltage(ii, buf2, sizeof(buf2));
+ printf("\tmodule temperature: %s Voltage: %s\n", buf, buf2);
+ get_sfp_rx_power(ii, buf, sizeof(buf));
+ get_sfp_tx_power(ii, buf2, sizeof(buf2));
+ printf("\tRX: %s TX: %s\n", buf, buf2);
+ }
+
+ if (verbose > 2) {
+ printf("\n\tSFF8472 DUMP (0xA0 0..127 range):\n");
+ dump_i2c_data(ii, SFF_8472_BASE, 0, 128);
+ }
+}
+
+void
+sfp_status(int s, struct ifreq *ifr, int verbose)
+{
+ struct i2c_info ii;
+ uint8_t id_byte;
+
+ memset(&ii, 0, sizeof(ii));
+ /* Prepare necessary into to pass to NIC handler */
+ ii.s = s;
+ ii.ifr = ifr;
+ ii.f = read_i2c_generic;
+
+ /*
+ * Try to read byte 0 from i2c:
+ * Both SFF-8472 and SFF-8436 use it as
+ * 'identification byte'.
+ * Stop reading status on zero as value -
+ * this might happen in case of empty transceiver slot.
+ */
+ id_byte = 0;
+ ii.f(&ii, SFF_8472_BASE, SFF_8472_ID, 1, (caddr_t)&id_byte);
+ if (ii.error != 0 || id_byte == 0)
+ return;
+
+ switch (id_byte) {
+ case SFF_8024_ID_QSFP:
+ case SFF_8024_ID_QSFPPLUS:
+ print_qsfp_status(&ii, verbose);
+ break;
+ default:
+ print_sfp_status(&ii, verbose);
+ };
+}
+
diff --git a/sbin/ifconfig/tests/Makefile b/sbin/ifconfig/tests/Makefile
new file mode 100644
index 0000000..044e979
--- /dev/null
+++ b/sbin/ifconfig/tests/Makefile
@@ -0,0 +1,13 @@
+# $FreeBSD$
+
+OBJTOP= ${.OBJDIR}/../../..
+SRCTOP= ${.CURDIR}/../../..
+TESTSRC= ${SRCTOP}/contrib/netbsd-tests/sbin/ifconfig
+
+TESTSDIR= ${TESTSBASE}/sbin/ifconfig
+
+NETBSD_ATF_TESTS_SH= nonexistent_test
+
+.include <netbsd-tests.test.mk>
+
+.include <bsd.test.mk>
diff --git a/sbin/init/Makefile b/sbin/init/Makefile
new file mode 100644
index 0000000..6e9c0fc
--- /dev/null
+++ b/sbin/init/Makefile
@@ -0,0 +1,13 @@
+# @(#)Makefile 8.1 (Berkeley) 7/19/93
+# $FreeBSD$
+
+PROG= init
+MAN= init.8
+PRECIOUSPROG=
+INSTALLFLAGS=-b -B.bak
+CFLAGS+=-DDEBUGSHELL -DSECURE -DLOGIN_CAP -DCOMPAT_SYSV_INIT
+LIBADD= util crypt
+
+NO_SHARED?= YES
+
+.include <bsd.prog.mk>
diff --git a/sbin/init/NOTES b/sbin/init/NOTES
new file mode 100644
index 0000000..bf75101
--- /dev/null
+++ b/sbin/init/NOTES
@@ -0,0 +1,112 @@
+POSIX and init:
+--------------
+
+POSIX.1 does not define 'init' but it mentions it in a few places.
+
+B.2.2.2, p205 line 873:
+
+ This is part of the extensive 'job control' glossary entry.
+ This specific reference says that 'init' must by default provide
+ protection from job control signals to jobs it starts --
+ it sets SIGTSTP, SIGTTIN and SIGTTOU to SIG_IGN.
+
+B.2.2.2, p206 line 889:
+
+ Here is a reference to 'vhangup'. It says, 'POSIX.1 does
+ not specify how controlling terminal access is affected by
+ a user logging out (that is, by a controlling process
+ terminating).' vhangup() is recognized as one way to handle
+ the problem. I'm not clear what happens in Reno; I have
+ the impression that when the controlling process terminates,
+ references to the controlling terminal are converted to
+ references to a 'dead' vnode. I don't know whether vhangup()
+ is required.
+
+B.2.2.2, p206 line 921:
+
+ Orphaned process groups bear indirectly on this issue. A
+ session leader's process group is considered to be orphaned;
+ that is, it's immune to job control signals from the terminal.
+
+B.2.2.2, p233 line 2055:
+
+ 'Historically, the implementation-dependent process that
+ inherits children whose parents have terminated without
+ waiting on them is called "init" and has a process ID of 1.'
+
+ It goes on to note that it used to be the case that 'init'
+ was responsible for sending SIGHUP to the foreground process
+ group of a tty whose controlling process has exited, using
+ vhangup(). It is now the responsibility of the kernel to
+ do this when the controlling process calls _exit(). The
+ kernel is also responsible for sending SIGCONT to stopped
+ process groups that become orphaned. This is like old BSD
+ but entire process groups are signaled instead of individual
+ processes.
+
+ In general it appears that the kernel now automatically
+ takes care of orphans, relieving 'init' of any responsibility.
+ Specifics are listed on the _exit() page (p50).
+
+On setsid():
+-----------
+
+It appears that neither getty nor login call setsid(), so init must
+do this -- seems reasonable. B.4.3.2 p 248 implies that this is the
+way that 'init' should work; it says that setsid() should be called
+after forking.
+
+Process group leaders cannot call setsid() -- another reason to
+fork! Of course setsid() causes the current process to become a
+process group leader, so we can only call setsid() once. Note that
+the controlling terminal acquires the session leader's process
+group when opened.
+
+Controlling terminals:
+---------------------
+
+B.7.1.1.3 p276: 'POSIX.1 does not specify a mechanism by which to
+allocate a controlling terminal. This is normally done by a system
+utility (such as 'getty') and is considered ... outside the scope
+of POSIX.1.' It goes on to say that historically the first open()
+of a tty in a session sets the controlling terminal. P130 has the
+full details; nothing particularly surprising.
+
+The glossary p12 describes a 'controlling process' as the first
+process in a session that acquires a controlling terminal. Access
+to the terminal from the session is revoked if the controlling
+process exits (see p50, in the discussion of process termination).
+
+Design notes:
+------------
+
+your generic finite state machine
+we are fascist about which signals we elect to receive,
+ even signals purportedly generated by hardware
+handle fatal errors gracefully if possible (we reboot if we goof!!)
+ if we get a segmentation fault etc., print a message on the console
+ and spin for a while before rebooting
+ (this at least decreases the amount of paper consumed :-)
+apply hysteresis to rapidly exiting gettys
+check wait status of children we reap
+ don't wait for stopped children
+don't use SIGCHILD, it's too expensive
+ but it may close windows and avoid races, sigh
+look for EINTR in case we need to change state
+init is responsible for utmp and wtmp maintenance (ick)
+ maybe now we can consider replacements? maintain them in parallel
+ init only removes utmp and closes out wtmp entries...
+
+necessary states and state transitions (gleaned from the man page):
+ 1: single user shell (with password checking?); on exit, go to 2
+ 2: rc script: on exit 0, go to 3; on exit N (error), go to 1
+ 3: read ttys file: on completion, go to 4
+ 4: multi-user operation: on SIGTERM, go to 7; on SIGHUP, go to 5;
+ on SIGTSTP, go to 6
+ 5: clean up mode (re-read ttys file, killing off controlling processes
+ on lines that are now 'off', starting them on lines newly 'on')
+ on completion, go to 4
+ 6: boring mode (no new sessions); signals as in 4
+ 7: death: send SIGHUP to all controlling processes, reap for 30 seconds,
+ then go to 1 (warn if not all processes died, i.e. wait blocks)
+Given the -s flag, we start at state 1; otherwise state 2
diff --git a/sbin/init/init.8 b/sbin/init/init.8
new file mode 100644
index 0000000..a7488a9
--- /dev/null
+++ b/sbin/init/init.8
@@ -0,0 +1,360 @@
+.\" Copyright (c) 1980, 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" Donn Seeley at Berkeley Software Design, Inc.
+.\"
+.\" 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.
+.\" 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.
+.\"
+.\" @(#)init.8 8.3 (Berkeley) 4/18/94
+.\" $FreeBSD$
+.\"
+.Dd March 14, 2012
+.Dt INIT 8
+.Os
+.Sh NAME
+.Nm init
+.Nd process control initialization
+.Sh SYNOPSIS
+.Nm
+.Nm
+.Oo
+.Cm 0 | 1 | 6 |
+.Cm c | q
+.Oc
+.Sh DESCRIPTION
+The
+.Nm
+utility
+is the last stage of the boot process.
+It normally runs the automatic reboot sequence as described in
+.Xr rc 8 ,
+and if this succeeds, begins multi-user operation.
+If the reboot scripts fail,
+.Nm
+commences single-user operation by giving
+the super-user a shell on the console.
+The
+.Nm
+utility may be passed parameters
+from the boot program to
+prevent the system from going multi-user and to instead execute
+a single-user shell without starting the normal daemons.
+The system is then quiescent for maintenance work and may
+later be made to go to multi-user by exiting the
+single-user shell (with ^D).
+This
+causes
+.Nm
+to run the
+.Pa /etc/rc
+start up command file in fastboot mode (skipping disk checks).
+.Pp
+If the
+.Em console
+entry in the
+.Xr ttys 5
+file is marked
+.Dq insecure ,
+then
+.Nm
+will require that the super-user password be
+entered before the system will start a single-user shell.
+The password check is skipped if the
+.Em console
+is marked as
+.Dq secure .
+.Pp
+If the system security level (see
+.Xr security 7 )
+is initially nonzero, then
+.Nm
+leaves it unchanged.
+Otherwise,
+.Nm
+raises the level to 1 before going multi-user for the first time.
+Since the level cannot be reduced, it will be at least 1 for
+subsequent operation, even on return to single-user.
+If a level higher than 1 is desired while running multi-user,
+it can be set before going multi-user, e.g., by the startup script
+.Xr rc 8 ,
+using
+.Xr sysctl 8
+to set the
+.Va kern.securelevel
+variable to the required security level.
+.Pp
+If
+.Nm
+is run in a jail, the security level of the
+.Dq host system
+will not be affected.
+Part of the information set up in the kernel to support a jail
+is a per-jail security level.
+This allows running a higher security level inside of a jail
+than that of the host system.
+See
+.Xr jail 8
+for more information about jails.
+.Pp
+In multi-user operation,
+.Nm
+maintains
+processes for the terminal ports found in the file
+.Xr ttys 5 .
+The
+.Nm
+utility reads this file and executes the command found in the second field,
+unless the first field refers to a device in
+.Pa /dev
+which is not configured.
+The first field is supplied as the final argument to the command.
+This command is usually
+.Xr getty 8 ;
+.Nm getty
+opens and initializes the tty line
+and
+executes the
+.Xr login 1
+program.
+The
+.Nm login
+program, when a valid user logs in,
+executes a shell for that user.
+When this shell
+dies, either because the user logged out
+or an abnormal termination occurred (a signal),
+the cycle is restarted by
+executing a new
+.Nm getty
+for the line.
+.Pp
+The
+.Nm
+utility can also be used to keep arbitrary daemons running,
+automatically restarting them if they die.
+In this case, the first field in the
+.Xr ttys 5
+file must not reference the path to a configured device node
+and will be passed to the daemon
+as the final argument on its command line.
+This is similar to the facility offered in the
+.At V
+.Pa /etc/inittab .
+.Pp
+Line status (on, off, secure, getty, or window information)
+may be changed in the
+.Xr ttys 5
+file without a reboot by sending the signal
+.Dv SIGHUP
+to
+.Nm
+with the command
+.Dq Li "kill -HUP 1" .
+On receipt of this signal,
+.Nm
+re-reads the
+.Xr ttys 5
+file.
+When a line is turned off in
+.Xr ttys 5 ,
+.Nm
+will send a SIGHUP signal to the controlling process
+for the session associated with the line.
+For any lines that were previously turned off in the
+.Xr ttys 5
+file and are now on,
+.Nm
+executes the command specified in the second field.
+If the command or window field for a line is changed,
+the change takes effect at the end of the current
+login session (e.g., the next time
+.Nm
+starts a process on the line).
+If a line is commented out or deleted from
+.Xr ttys 5 ,
+.Nm
+will not do anything at all to that line.
+.Pp
+The
+.Nm
+utility will terminate multi-user operations and resume single-user mode
+if sent a terminate
+.Pq Dv TERM
+signal, for example,
+.Dq Li "kill \-TERM 1" .
+If there are processes outstanding that are deadlocked (because of
+hardware or software failure),
+.Nm
+will not wait for them all to die (which might take forever), but
+will time out after 30 seconds and print a warning message.
+.Pp
+The
+.Nm
+utility will cease creating new processes
+and allow the system to slowly die away, if it is sent a terminal stop
+.Pq Dv TSTP
+signal, i.e.\&
+.Dq Li "kill \-TSTP 1" .
+A later hangup will resume full
+multi-user operations, or a terminate will start a single-user shell.
+This hook is used by
+.Xr reboot 8
+and
+.Xr halt 8 .
+.Pp
+The
+.Nm
+utility will terminate all possible processes (again, it will not wait
+for deadlocked processes) and reboot the machine if sent the interrupt
+.Pq Dv INT
+signal, i.e.\&
+.Dq Li "kill \-INT 1".
+This is useful for shutting the machine down cleanly from inside the kernel
+or from X when the machine appears to be hung.
+.Pp
+The
+.Nm
+utility will do the same, except it will halt the machine if sent
+the user defined signal 1
+.Pq Dv USR1 ,
+or will halt and turn the power off (if hardware permits) if sent
+the user defined signal 2
+.Pq Dv USR2 .
+.Pp
+When shutting down the machine,
+.Nm
+will try to run the
+.Pa /etc/rc.shutdown
+script.
+This script can be used to cleanly terminate specific programs such
+as
+.Nm innd
+(the InterNetNews server).
+If this script does not terminate within 120 seconds,
+.Nm
+will terminate it.
+The timeout can be configured via the
+.Xr sysctl 8
+variable
+.Va kern.init_shutdown_timeout .
+.Pp
+The role of
+.Nm
+is so critical that if it dies, the system will reboot itself
+automatically.
+If, at bootstrap time, the
+.Nm
+process cannot be located, the system will panic with the message
+.Dq "panic: init died (signal %d, exit %d)" .
+.Pp
+If run as a user process as shown in the second synopsis line,
+.Nm
+will emulate
+.At V
+behavior, i.e., super-user can specify the desired
+.Em run-level
+on a command line, and
+.Nm
+will signal the original
+(PID 1)
+.Nm
+as follows:
+.Bl -column Run-level SIGTERM
+.It Sy "Run-level Signal Action"
+.It Cm 0 Ta Dv SIGUSR2 Ta "Halt and turn the power off"
+.It Cm 1 Ta Dv SIGTERM Ta "Go to single-user mode"
+.It Cm 6 Ta Dv SIGINT Ta "Reboot the machine"
+.It Cm c Ta Dv SIGTSTP Ta "Block further logins"
+.It Cm q Ta Dv SIGHUP Ta Rescan the
+.Xr ttys 5
+file
+.El
+.Sh FILES
+.Bl -tag -width /var/log/init.log -compact
+.It Pa /dev/console
+system console device
+.It Pa /dev/tty*
+terminal ports found in
+.Xr ttys 5
+.It Pa /etc/ttys
+the terminal initialization information file
+.It Pa /etc/rc
+system startup commands
+.It Pa /etc/rc.shutdown
+system shutdown commands
+.It Pa /var/log/init.log
+log of
+.Xr rc 8
+output if the system console device is not available
+.El
+.Sh DIAGNOSTICS
+.Bl -diag
+.It "getty repeating too quickly on port %s, sleeping."
+A process being started to service a line is exiting quickly
+each time it is started.
+This is often caused by a ringing or noisy terminal line.
+.Bf -emphasis
+Init will sleep for 30 seconds,
+then continue trying to start the process.
+.Ef
+.It "some processes would not die; ps axl advised."
+A process
+is hung and could not be killed when the system was shutting down.
+This condition is usually caused by a process
+that is stuck in a device driver because of
+a persistent device error condition.
+.El
+.Sh SEE ALSO
+.Xr kill 1 ,
+.Xr login 1 ,
+.Xr sh 1 ,
+.Xr ttys 5 ,
+.Xr security 7 ,
+.Xr getty 8 ,
+.Xr halt 8 ,
+.Xr jail 8 ,
+.Xr rc 8 ,
+.Xr reboot 8 ,
+.Xr shutdown 8 ,
+.Xr sysctl 8
+.Sh HISTORY
+An
+.Nm
+utility appeared in
+.At v6 .
+.Sh CAVEATS
+Systems without
+.Xr sysctl 8
+behave as though they have security level \-1.
+.Pp
+Setting the security level above 1 too early in the boot sequence can
+prevent
+.Xr fsck 8
+from repairing inconsistent file systems.
+The
+preferred location to set the security level is at the end of
+.Pa /etc/rc
+after all multi-user startup actions are complete.
diff --git a/sbin/init/init.c b/sbin/init/init.c
new file mode 100644
index 0000000..5ab3527
--- /dev/null
+++ b/sbin/init/init.c
@@ -0,0 +1,1725 @@
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Donn Seeley at Berkeley Software Design, Inc.
+ *
+ * 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.
+ * 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 lint
+static const char copyright[] =
+"@(#) Copyright (c) 1991, 1993\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)init.c 8.1 (Berkeley) 7/15/93";
+#endif
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/mount.h>
+#include <sys/sysctl.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+
+#include <db.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <kenv.h>
+#include <libutil.h>
+#include <paths.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <time.h>
+#include <ttyent.h>
+#include <unistd.h>
+#include <sys/reboot.h>
+#include <err.h>
+
+#include <stdarg.h>
+
+#ifdef SECURE
+#include <pwd.h>
+#endif
+
+#ifdef LOGIN_CAP
+#include <login_cap.h>
+#endif
+
+#include "pathnames.h"
+
+/*
+ * Sleep times; used to prevent thrashing.
+ */
+#define GETTY_SPACING 5 /* N secs minimum getty spacing */
+#define GETTY_SLEEP 30 /* sleep N secs after spacing problem */
+#define GETTY_NSPACE 3 /* max. spacing count to bring reaction */
+#define WINDOW_WAIT 3 /* wait N secs after starting window */
+#define STALL_TIMEOUT 30 /* wait N secs after warning */
+#define DEATH_WATCH 10 /* wait N secs for procs to die */
+#define DEATH_SCRIPT 120 /* wait for 2min for /etc/rc.shutdown */
+#define RESOURCE_RC "daemon"
+#define RESOURCE_WINDOW "default"
+#define RESOURCE_GETTY "default"
+
+static void handle(sig_t, ...);
+static void delset(sigset_t *, ...);
+
+static void stall(const char *, ...) __printflike(1, 2);
+static void warning(const char *, ...) __printflike(1, 2);
+static void emergency(const char *, ...) __printflike(1, 2);
+static void disaster(int);
+static void badsys(int);
+static int runshutdown(void);
+static char *strk(char *);
+
+/*
+ * We really need a recursive typedef...
+ * The following at least guarantees that the return type of (*state_t)()
+ * is sufficiently wide to hold a function pointer.
+ */
+typedef long (*state_func_t)(void);
+typedef state_func_t (*state_t)(void);
+
+static state_func_t single_user(void);
+static state_func_t runcom(void);
+static state_func_t read_ttys(void);
+static state_func_t multi_user(void);
+static state_func_t clean_ttys(void);
+static state_func_t catatonia(void);
+static state_func_t death(void);
+static state_func_t death_single(void);
+
+static state_func_t run_script(const char *);
+
+static enum { AUTOBOOT, FASTBOOT } runcom_mode = AUTOBOOT;
+#define FALSE 0
+#define TRUE 1
+
+static int Reboot = FALSE;
+static int howto = RB_AUTOBOOT;
+
+static int devfs;
+
+static void transition(state_t);
+static state_t requested_transition;
+static state_t current_state = death_single;
+
+static void open_console(void);
+static const char *get_shell(void);
+static void write_stderr(const char *message);
+
+typedef struct init_session {
+ pid_t se_process; /* controlling process */
+ time_t se_started; /* used to avoid thrashing */
+ int se_flags; /* status of session */
+#define SE_SHUTDOWN 0x1 /* session won't be restarted */
+#define SE_PRESENT 0x2 /* session is in /etc/ttys */
+ int se_nspace; /* spacing count */
+ char *se_device; /* filename of port */
+ char *se_getty; /* what to run on that port */
+ char *se_getty_argv_space; /* pre-parsed argument array space */
+ char **se_getty_argv; /* pre-parsed argument array */
+ char *se_window; /* window system (started only once) */
+ char *se_window_argv_space; /* pre-parsed argument array space */
+ char **se_window_argv; /* pre-parsed argument array */
+ char *se_type; /* default terminal type */
+ struct init_session *se_prev;
+ struct init_session *se_next;
+} session_t;
+
+static void free_session(session_t *);
+static session_t *new_session(session_t *, struct ttyent *);
+static session_t *sessions;
+
+static char **construct_argv(char *);
+static void start_window_system(session_t *);
+static void collect_child(pid_t);
+static pid_t start_getty(session_t *);
+static void transition_handler(int);
+static void alrm_handler(int);
+static void setsecuritylevel(int);
+static int getsecuritylevel(void);
+static int setupargv(session_t *, struct ttyent *);
+#ifdef LOGIN_CAP
+static void setprocresources(const char *);
+#endif
+static int clang;
+
+static int start_session_db(void);
+static void add_session(session_t *);
+static void del_session(session_t *);
+static session_t *find_session(pid_t);
+static DB *session_db;
+
+/*
+ * The mother of all processes.
+ */
+int
+main(int argc, char *argv[])
+{
+ state_t initial_transition = runcom;
+ char kenv_value[PATH_MAX];
+ int c;
+ struct sigaction sa;
+ sigset_t mask;
+
+ /* Dispose of random users. */
+ if (getuid() != 0)
+ errx(1, "%s", strerror(EPERM));
+
+ /* System V users like to reexec init. */
+ if (getpid() != 1) {
+#ifdef COMPAT_SYSV_INIT
+ /* So give them what they want */
+ if (argc > 1) {
+ if (strlen(argv[1]) == 1) {
+ char runlevel = *argv[1];
+ int sig;
+
+ switch (runlevel) {
+ case '0': /* halt + poweroff */
+ sig = SIGUSR2;
+ break;
+ case '1': /* single-user */
+ sig = SIGTERM;
+ break;
+ case '6': /* reboot */
+ sig = SIGINT;
+ break;
+ case 'c': /* block further logins */
+ sig = SIGTSTP;
+ break;
+ case 'q': /* rescan /etc/ttys */
+ sig = SIGHUP;
+ break;
+ default:
+ goto invalid;
+ }
+ kill(1, sig);
+ _exit(0);
+ } else
+invalid:
+ errx(1, "invalid run-level ``%s''", argv[1]);
+ } else
+#endif
+ errx(1, "already running");
+ }
+ /*
+ * Note that this does NOT open a file...
+ * Does 'init' deserve its own facility number?
+ */
+ openlog("init", LOG_CONS, LOG_AUTH);
+
+ /*
+ * Create an initial session.
+ */
+ if (setsid() < 0)
+ warning("initial setsid() failed: %m");
+
+ /*
+ * Establish an initial user so that programs running
+ * single user do not freak out and die (like passwd).
+ */
+ if (setlogin("root") < 0)
+ warning("setlogin() failed: %m");
+
+ /*
+ * This code assumes that we always get arguments through flags,
+ * never through bits set in some random machine register.
+ */
+ while ((c = getopt(argc, argv, "dsf")) != -1)
+ switch (c) {
+ case 'd':
+ devfs = 1;
+ break;
+ case 's':
+ initial_transition = single_user;
+ break;
+ case 'f':
+ runcom_mode = FASTBOOT;
+ break;
+ default:
+ warning("unrecognized flag '-%c'", c);
+ break;
+ }
+
+ if (optind != argc)
+ warning("ignoring excess arguments");
+
+ /*
+ * We catch or block signals rather than ignore them,
+ * so that they get reset on exec.
+ */
+ handle(badsys, SIGSYS, 0);
+ handle(disaster, SIGABRT, SIGFPE, SIGILL, SIGSEGV, SIGBUS, SIGXCPU,
+ SIGXFSZ, 0);
+ handle(transition_handler, SIGHUP, SIGINT, SIGTERM, SIGTSTP, SIGUSR1,
+ SIGUSR2, 0);
+ handle(alrm_handler, SIGALRM, 0);
+ sigfillset(&mask);
+ delset(&mask, SIGABRT, SIGFPE, SIGILL, SIGSEGV, SIGBUS, SIGSYS,
+ SIGXCPU, SIGXFSZ, SIGHUP, SIGINT, SIGTERM, SIGTSTP, SIGALRM,
+ SIGUSR1, SIGUSR2, 0);
+ sigprocmask(SIG_SETMASK, &mask, (sigset_t *) 0);
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ sa.sa_handler = SIG_IGN;
+ sigaction(SIGTTIN, &sa, (struct sigaction *)0);
+ sigaction(SIGTTOU, &sa, (struct sigaction *)0);
+
+ /*
+ * Paranoia.
+ */
+ close(0);
+ close(1);
+ close(2);
+
+ if (kenv(KENV_GET, "init_script", kenv_value, sizeof(kenv_value)) > 0) {
+ state_func_t next_transition;
+
+ if ((next_transition = run_script(kenv_value)) != 0)
+ initial_transition = (state_t) next_transition;
+ }
+
+ if (kenv(KENV_GET, "init_chroot", kenv_value, sizeof(kenv_value)) > 0) {
+ if (chdir(kenv_value) != 0 || chroot(".") != 0)
+ warning("Can't chroot to %s: %m", kenv_value);
+ }
+
+ /*
+ * Additional check if devfs needs to be mounted:
+ * If "/" and "/dev" have the same device number,
+ * then it hasn't been mounted yet.
+ */
+ if (!devfs) {
+ struct stat stst;
+ dev_t root_devno;
+
+ stat("/", &stst);
+ root_devno = stst.st_dev;
+ if (stat("/dev", &stst) != 0)
+ warning("Can't stat /dev: %m");
+ else if (stst.st_dev == root_devno)
+ devfs++;
+ }
+
+ if (devfs) {
+ struct iovec iov[4];
+ char *s;
+ int i;
+
+ char _fstype[] = "fstype";
+ char _devfs[] = "devfs";
+ char _fspath[] = "fspath";
+ char _path_dev[]= _PATH_DEV;
+
+ iov[0].iov_base = _fstype;
+ iov[0].iov_len = sizeof(_fstype);
+ iov[1].iov_base = _devfs;
+ iov[1].iov_len = sizeof(_devfs);
+ iov[2].iov_base = _fspath;
+ iov[2].iov_len = sizeof(_fspath);
+ /*
+ * Try to avoid the trailing slash in _PATH_DEV.
+ * Be *very* defensive.
+ */
+ s = strdup(_PATH_DEV);
+ if (s != NULL) {
+ i = strlen(s);
+ if (i > 0 && s[i - 1] == '/')
+ s[i - 1] = '\0';
+ iov[3].iov_base = s;
+ iov[3].iov_len = strlen(s) + 1;
+ } else {
+ iov[3].iov_base = _path_dev;
+ iov[3].iov_len = sizeof(_path_dev);
+ }
+ nmount(iov, 4, 0);
+ if (s != NULL)
+ free(s);
+ }
+
+ /*
+ * Start the state machine.
+ */
+ transition(initial_transition);
+
+ /*
+ * Should never reach here.
+ */
+ return 1;
+}
+
+/*
+ * Associate a function with a signal handler.
+ */
+static void
+handle(sig_t handler, ...)
+{
+ int sig;
+ struct sigaction sa;
+ sigset_t mask_everything;
+ va_list ap;
+ va_start(ap, handler);
+
+ sa.sa_handler = handler;
+ sigfillset(&mask_everything);
+
+ while ((sig = va_arg(ap, int)) != 0) {
+ sa.sa_mask = mask_everything;
+ /* XXX SA_RESTART? */
+ sa.sa_flags = sig == SIGCHLD ? SA_NOCLDSTOP : 0;
+ sigaction(sig, &sa, (struct sigaction *) 0);
+ }
+ va_end(ap);
+}
+
+/*
+ * Delete a set of signals from a mask.
+ */
+static void
+delset(sigset_t *maskp, ...)
+{
+ int sig;
+ va_list ap;
+ va_start(ap, maskp);
+
+ while ((sig = va_arg(ap, int)) != 0)
+ sigdelset(maskp, sig);
+ va_end(ap);
+}
+
+/*
+ * Log a message and sleep for a while (to give someone an opportunity
+ * to read it and to save log or hardcopy output if the problem is chronic).
+ * NB: should send a message to the session logger to avoid blocking.
+ */
+static void
+stall(const char *message, ...)
+{
+ va_list ap;
+ va_start(ap, message);
+
+ vsyslog(LOG_ALERT, message, ap);
+ va_end(ap);
+ sleep(STALL_TIMEOUT);
+}
+
+/*
+ * Like stall(), but doesn't sleep.
+ * If cpp had variadic macros, the two functions could be #defines for another.
+ * NB: should send a message to the session logger to avoid blocking.
+ */
+static void
+warning(const char *message, ...)
+{
+ va_list ap;
+ va_start(ap, message);
+
+ vsyslog(LOG_ALERT, message, ap);
+ va_end(ap);
+}
+
+/*
+ * Log an emergency message.
+ * NB: should send a message to the session logger to avoid blocking.
+ */
+static void
+emergency(const char *message, ...)
+{
+ va_list ap;
+ va_start(ap, message);
+
+ vsyslog(LOG_EMERG, message, ap);
+ va_end(ap);
+}
+
+/*
+ * Catch a SIGSYS signal.
+ *
+ * These may arise if a system does not support sysctl.
+ * We tolerate up to 25 of these, then throw in the towel.
+ */
+static void
+badsys(int sig)
+{
+ static int badcount = 0;
+
+ if (badcount++ < 25)
+ return;
+ disaster(sig);
+}
+
+/*
+ * Catch an unexpected signal.
+ */
+static void
+disaster(int sig)
+{
+
+ emergency("fatal signal: %s",
+ (unsigned)sig < NSIG ? sys_siglist[sig] : "unknown signal");
+
+ sleep(STALL_TIMEOUT);
+ _exit(sig); /* reboot */
+}
+
+/*
+ * Get the security level of the kernel.
+ */
+static int
+getsecuritylevel(void)
+{
+#ifdef KERN_SECURELVL
+ int name[2], curlevel;
+ size_t len;
+
+ name[0] = CTL_KERN;
+ name[1] = KERN_SECURELVL;
+ len = sizeof curlevel;
+ if (sysctl(name, 2, &curlevel, &len, NULL, 0) == -1) {
+ emergency("cannot get kernel security level: %s",
+ strerror(errno));
+ return (-1);
+ }
+ return (curlevel);
+#else
+ return (-1);
+#endif
+}
+
+/*
+ * Set the security level of the kernel.
+ */
+static void
+setsecuritylevel(int newlevel)
+{
+#ifdef KERN_SECURELVL
+ int name[2], curlevel;
+
+ curlevel = getsecuritylevel();
+ if (newlevel == curlevel)
+ return;
+ name[0] = CTL_KERN;
+ name[1] = KERN_SECURELVL;
+ if (sysctl(name, 2, NULL, NULL, &newlevel, sizeof newlevel) == -1) {
+ emergency(
+ "cannot change kernel security level from %d to %d: %s",
+ curlevel, newlevel, strerror(errno));
+ return;
+ }
+#ifdef SECURE
+ warning("kernel security level changed from %d to %d",
+ curlevel, newlevel);
+#endif
+#endif
+}
+
+/*
+ * Change states in the finite state machine.
+ * The initial state is passed as an argument.
+ */
+static void
+transition(state_t s)
+{
+
+ current_state = s;
+ for (;;)
+ current_state = (state_t) (*current_state)();
+}
+
+/*
+ * Start a session and allocate a controlling terminal.
+ * Only called by children of init after forking.
+ */
+static void
+open_console(void)
+{
+ int fd;
+
+ /*
+ * Try to open /dev/console. Open the device with O_NONBLOCK to
+ * prevent potential blocking on a carrier.
+ */
+ revoke(_PATH_CONSOLE);
+ if ((fd = open(_PATH_CONSOLE, O_RDWR | O_NONBLOCK)) != -1) {
+ (void)fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) & ~O_NONBLOCK);
+ if (login_tty(fd) == 0)
+ return;
+ close(fd);
+ }
+
+ /* No luck. Log output to file if possible. */
+ if ((fd = open(_PATH_DEVNULL, O_RDWR)) == -1) {
+ stall("cannot open null device.");
+ _exit(1);
+ }
+ if (fd != STDIN_FILENO) {
+ dup2(fd, STDIN_FILENO);
+ close(fd);
+ }
+ fd = open(_PATH_INITLOG, O_WRONLY | O_APPEND | O_CREAT, 0644);
+ if (fd == -1)
+ dup2(STDIN_FILENO, STDOUT_FILENO);
+ else if (fd != STDOUT_FILENO) {
+ dup2(fd, STDOUT_FILENO);
+ close(fd);
+ }
+ dup2(STDOUT_FILENO, STDERR_FILENO);
+}
+
+static const char *
+get_shell(void)
+{
+ static char kenv_value[PATH_MAX];
+
+ if (kenv(KENV_GET, "init_shell", kenv_value, sizeof(kenv_value)) > 0)
+ return kenv_value;
+ else
+ return _PATH_BSHELL;
+}
+
+static void
+write_stderr(const char *message)
+{
+
+ write(STDERR_FILENO, message, strlen(message));
+}
+
+/*
+ * Bring the system up single user.
+ */
+static state_func_t
+single_user(void)
+{
+ pid_t pid, wpid;
+ int status;
+ sigset_t mask;
+ const char *shell;
+ char *argv[2];
+#ifdef SECURE
+ struct ttyent *typ;
+ struct passwd *pp;
+ static const char banner[] =
+ "Enter root password, or ^D to go multi-user\n";
+ char *clear, *password;
+#endif
+#ifdef DEBUGSHELL
+ char altshell[128];
+#endif
+
+ if (Reboot) {
+ /* Instead of going single user, let's reboot the machine */
+ sync();
+ reboot(howto);
+ _exit(0);
+ }
+
+ shell = get_shell();
+
+ if ((pid = fork()) == 0) {
+ /*
+ * Start the single user session.
+ */
+ open_console();
+
+#ifdef SECURE
+ /*
+ * Check the root password.
+ * We don't care if the console is 'on' by default;
+ * it's the only tty that can be 'off' and 'secure'.
+ */
+ typ = getttynam("console");
+ pp = getpwnam("root");
+ if (typ && (typ->ty_status & TTY_SECURE) == 0 &&
+ pp && *pp->pw_passwd) {
+ write_stderr(banner);
+ for (;;) {
+ clear = getpass("Password:");
+ if (clear == 0 || *clear == '\0')
+ _exit(0);
+ password = crypt(clear, pp->pw_passwd);
+ bzero(clear, _PASSWORD_LEN);
+ if (password == NULL ||
+ strcmp(password, pp->pw_passwd) == 0)
+ break;
+ warning("single-user login failed\n");
+ }
+ }
+ endttyent();
+ endpwent();
+#endif /* SECURE */
+
+#ifdef DEBUGSHELL
+ {
+ char *cp = altshell;
+ int num;
+
+#define SHREQUEST "Enter full pathname of shell or RETURN for "
+ write_stderr(SHREQUEST);
+ write_stderr(shell);
+ write_stderr(": ");
+ while ((num = read(STDIN_FILENO, cp, 1)) != -1 &&
+ num != 0 && *cp != '\n' && cp < &altshell[127])
+ cp++;
+ *cp = '\0';
+ if (altshell[0] != '\0')
+ shell = altshell;
+ }
+#endif /* DEBUGSHELL */
+
+ /*
+ * Unblock signals.
+ * We catch all the interesting ones,
+ * and those are reset to SIG_DFL on exec.
+ */
+ sigemptyset(&mask);
+ sigprocmask(SIG_SETMASK, &mask, (sigset_t *) 0);
+
+ /*
+ * Fire off a shell.
+ * If the default one doesn't work, try the Bourne shell.
+ */
+
+ char name[] = "-sh";
+
+ argv[0] = name;
+ argv[1] = 0;
+ execv(shell, argv);
+ emergency("can't exec %s for single user: %m", shell);
+ execv(_PATH_BSHELL, argv);
+ emergency("can't exec %s for single user: %m", _PATH_BSHELL);
+ sleep(STALL_TIMEOUT);
+ _exit(1);
+ }
+
+ if (pid == -1) {
+ /*
+ * We are seriously hosed. Do our best.
+ */
+ emergency("can't fork single-user shell, trying again");
+ while (waitpid(-1, (int *) 0, WNOHANG) > 0)
+ continue;
+ return (state_func_t) single_user;
+ }
+
+ requested_transition = 0;
+ do {
+ if ((wpid = waitpid(-1, &status, WUNTRACED)) != -1)
+ collect_child(wpid);
+ if (wpid == -1) {
+ if (errno == EINTR)
+ continue;
+ warning("wait for single-user shell failed: %m; restarting");
+ return (state_func_t) single_user;
+ }
+ if (wpid == pid && WIFSTOPPED(status)) {
+ warning("init: shell stopped, restarting\n");
+ kill(pid, SIGCONT);
+ wpid = -1;
+ }
+ } while (wpid != pid && !requested_transition);
+
+ if (requested_transition)
+ return (state_func_t) requested_transition;
+
+ if (!WIFEXITED(status)) {
+ if (WTERMSIG(status) == SIGKILL) {
+ /*
+ * reboot(8) killed shell?
+ */
+ warning("single user shell terminated.");
+ sleep(STALL_TIMEOUT);
+ _exit(0);
+ } else {
+ warning("single user shell terminated, restarting");
+ return (state_func_t) single_user;
+ }
+ }
+
+ runcom_mode = FASTBOOT;
+ return (state_func_t) runcom;
+}
+
+/*
+ * Run the system startup script.
+ */
+static state_func_t
+runcom(void)
+{
+ state_func_t next_transition;
+
+ if ((next_transition = run_script(_PATH_RUNCOM)) != 0)
+ return next_transition;
+
+ runcom_mode = AUTOBOOT; /* the default */
+ return (state_func_t) read_ttys;
+}
+
+/*
+ * Run a shell script.
+ * Returns 0 on success, otherwise the next transition to enter:
+ * - single_user if fork/execv/waitpid failed, or if the script
+ * terminated with a signal or exit code != 0.
+ * - death_single if a SIGTERM was delivered to init(8).
+ */
+static state_func_t
+run_script(const char *script)
+{
+ pid_t pid, wpid;
+ int status;
+ char *argv[4];
+ const char *shell;
+ struct sigaction sa;
+
+ shell = get_shell();
+
+ if ((pid = fork()) == 0) {
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ sa.sa_handler = SIG_IGN;
+ sigaction(SIGTSTP, &sa, (struct sigaction *)0);
+ sigaction(SIGHUP, &sa, (struct sigaction *)0);
+
+ open_console();
+
+ char _sh[] = "sh";
+ char _autoboot[] = "autoboot";
+
+ argv[0] = _sh;
+ argv[1] = __DECONST(char *, script);
+ argv[2] = runcom_mode == AUTOBOOT ? _autoboot : 0;
+ argv[3] = 0;
+
+ sigprocmask(SIG_SETMASK, &sa.sa_mask, (sigset_t *) 0);
+
+#ifdef LOGIN_CAP
+ setprocresources(RESOURCE_RC);
+#endif
+ execv(shell, argv);
+ stall("can't exec %s for %s: %m", shell, script);
+ _exit(1); /* force single user mode */
+ }
+
+ if (pid == -1) {
+ emergency("can't fork for %s on %s: %m", shell, script);
+ while (waitpid(-1, (int *) 0, WNOHANG) > 0)
+ continue;
+ sleep(STALL_TIMEOUT);
+ return (state_func_t) single_user;
+ }
+
+ /*
+ * Copied from single_user(). This is a bit paranoid.
+ */
+ requested_transition = 0;
+ do {
+ if ((wpid = waitpid(-1, &status, WUNTRACED)) != -1)
+ collect_child(wpid);
+ if (wpid == -1) {
+ if (requested_transition == death_single)
+ return (state_func_t) death_single;
+ if (errno == EINTR)
+ continue;
+ warning("wait for %s on %s failed: %m; going to "
+ "single user mode", shell, script);
+ return (state_func_t) single_user;
+ }
+ if (wpid == pid && WIFSTOPPED(status)) {
+ warning("init: %s on %s stopped, restarting\n",
+ shell, script);
+ kill(pid, SIGCONT);
+ wpid = -1;
+ }
+ } while (wpid != pid);
+
+ if (WIFSIGNALED(status) && WTERMSIG(status) == SIGTERM &&
+ requested_transition == catatonia) {
+ /* /etc/rc executed /sbin/reboot; wait for the end quietly */
+ sigset_t s;
+
+ sigfillset(&s);
+ for (;;)
+ sigsuspend(&s);
+ }
+
+ if (!WIFEXITED(status)) {
+ warning("%s on %s terminated abnormally, going to single "
+ "user mode", shell, script);
+ return (state_func_t) single_user;
+ }
+
+ if (WEXITSTATUS(status))
+ return (state_func_t) single_user;
+
+ return (state_func_t) 0;
+}
+
+/*
+ * Open the session database.
+ *
+ * NB: We could pass in the size here; is it necessary?
+ */
+static int
+start_session_db(void)
+{
+ if (session_db && (*session_db->close)(session_db))
+ emergency("session database close: %s", strerror(errno));
+ if ((session_db = dbopen(NULL, O_RDWR, 0, DB_HASH, NULL)) == 0) {
+ emergency("session database open: %s", strerror(errno));
+ return (1);
+ }
+ return (0);
+
+}
+
+/*
+ * Add a new login session.
+ */
+static void
+add_session(session_t *sp)
+{
+ DBT key;
+ DBT data;
+
+ key.data = &sp->se_process;
+ key.size = sizeof sp->se_process;
+ data.data = &sp;
+ data.size = sizeof sp;
+
+ if ((*session_db->put)(session_db, &key, &data, 0))
+ emergency("insert %d: %s", sp->se_process, strerror(errno));
+}
+
+/*
+ * Delete an old login session.
+ */
+static void
+del_session(session_t *sp)
+{
+ DBT key;
+
+ key.data = &sp->se_process;
+ key.size = sizeof sp->se_process;
+
+ if ((*session_db->del)(session_db, &key, 0))
+ emergency("delete %d: %s", sp->se_process, strerror(errno));
+}
+
+/*
+ * Look up a login session by pid.
+ */
+static session_t *
+find_session(pid_t pid)
+{
+ DBT key;
+ DBT data;
+ session_t *ret;
+
+ key.data = &pid;
+ key.size = sizeof pid;
+ if ((*session_db->get)(session_db, &key, &data, 0) != 0)
+ return 0;
+ bcopy(data.data, (char *)&ret, sizeof(ret));
+ return ret;
+}
+
+/*
+ * Construct an argument vector from a command line.
+ */
+static char **
+construct_argv(char *command)
+{
+ int argc = 0;
+ char **argv = (char **) malloc(((strlen(command) + 1) / 2 + 1)
+ * sizeof (char *));
+
+ if ((argv[argc++] = strk(command)) == 0) {
+ free(argv);
+ return (NULL);
+ }
+ while ((argv[argc++] = strk((char *) 0)) != NULL)
+ continue;
+ return argv;
+}
+
+/*
+ * Deallocate a session descriptor.
+ */
+static void
+free_session(session_t *sp)
+{
+ free(sp->se_device);
+ if (sp->se_getty) {
+ free(sp->se_getty);
+ free(sp->se_getty_argv_space);
+ free(sp->se_getty_argv);
+ }
+ if (sp->se_window) {
+ free(sp->se_window);
+ free(sp->se_window_argv_space);
+ free(sp->se_window_argv);
+ }
+ if (sp->se_type)
+ free(sp->se_type);
+ free(sp);
+}
+
+/*
+ * Allocate a new session descriptor.
+ * Mark it SE_PRESENT.
+ */
+static session_t *
+new_session(session_t *sprev, struct ttyent *typ)
+{
+ session_t *sp;
+ int fd;
+
+ if ((typ->ty_status & TTY_ON) == 0 ||
+ typ->ty_name == 0 ||
+ typ->ty_getty == 0)
+ return 0;
+
+ sp = (session_t *) calloc(1, sizeof (session_t));
+
+ sp->se_flags |= SE_PRESENT;
+
+ sp->se_device = malloc(sizeof(_PATH_DEV) + strlen(typ->ty_name));
+ sprintf(sp->se_device, "%s%s", _PATH_DEV, typ->ty_name);
+
+ /*
+ * Attempt to open the device, if we get "device not configured"
+ * then don't add the device to the session list.
+ */
+ if ((fd = open(sp->se_device, O_RDONLY | O_NONBLOCK, 0)) < 0) {
+ if (errno == ENXIO) {
+ free_session(sp);
+ return (0);
+ }
+ } else
+ close(fd);
+
+ if (setupargv(sp, typ) == 0) {
+ free_session(sp);
+ return (0);
+ }
+
+ sp->se_next = 0;
+ if (sprev == 0) {
+ sessions = sp;
+ sp->se_prev = 0;
+ } else {
+ sprev->se_next = sp;
+ sp->se_prev = sprev;
+ }
+
+ return sp;
+}
+
+/*
+ * Calculate getty and if useful window argv vectors.
+ */
+static int
+setupargv(session_t *sp, struct ttyent *typ)
+{
+
+ if (sp->se_getty) {
+ free(sp->se_getty);
+ free(sp->se_getty_argv_space);
+ free(sp->se_getty_argv);
+ }
+ sp->se_getty = malloc(strlen(typ->ty_getty) + strlen(typ->ty_name) + 2);
+ sprintf(sp->se_getty, "%s %s", typ->ty_getty, typ->ty_name);
+ sp->se_getty_argv_space = strdup(sp->se_getty);
+ sp->se_getty_argv = construct_argv(sp->se_getty_argv_space);
+ if (sp->se_getty_argv == 0) {
+ warning("can't parse getty for port %s", sp->se_device);
+ free(sp->se_getty);
+ free(sp->se_getty_argv_space);
+ sp->se_getty = sp->se_getty_argv_space = 0;
+ return (0);
+ }
+ if (sp->se_window) {
+ free(sp->se_window);
+ free(sp->se_window_argv_space);
+ free(sp->se_window_argv);
+ }
+ sp->se_window = sp->se_window_argv_space = 0;
+ sp->se_window_argv = 0;
+ if (typ->ty_window) {
+ sp->se_window = strdup(typ->ty_window);
+ sp->se_window_argv_space = strdup(sp->se_window);
+ sp->se_window_argv = construct_argv(sp->se_window_argv_space);
+ if (sp->se_window_argv == 0) {
+ warning("can't parse window for port %s",
+ sp->se_device);
+ free(sp->se_window_argv_space);
+ free(sp->se_window);
+ sp->se_window = sp->se_window_argv_space = 0;
+ return (0);
+ }
+ }
+ if (sp->se_type)
+ free(sp->se_type);
+ sp->se_type = typ->ty_type ? strdup(typ->ty_type) : 0;
+ return (1);
+}
+
+/*
+ * Walk the list of ttys and create sessions for each active line.
+ */
+static state_func_t
+read_ttys(void)
+{
+ session_t *sp, *snext;
+ struct ttyent *typ;
+
+ /*
+ * Destroy any previous session state.
+ * There shouldn't be any, but just in case...
+ */
+ for (sp = sessions; sp; sp = snext) {
+ snext = sp->se_next;
+ free_session(sp);
+ }
+ sessions = 0;
+ if (start_session_db())
+ return (state_func_t) single_user;
+
+ /*
+ * Allocate a session entry for each active port.
+ * Note that sp starts at 0.
+ */
+ while ((typ = getttyent()) != NULL)
+ if ((snext = new_session(sp, typ)) != NULL)
+ sp = snext;
+
+ endttyent();
+
+ return (state_func_t) multi_user;
+}
+
+/*
+ * Start a window system running.
+ */
+static void
+start_window_system(session_t *sp)
+{
+ pid_t pid;
+ sigset_t mask;
+ char term[64], *env[2];
+ int status;
+
+ if ((pid = fork()) == -1) {
+ emergency("can't fork for window system on port %s: %m",
+ sp->se_device);
+ /* hope that getty fails and we can try again */
+ return;
+ }
+ if (pid) {
+ waitpid(-1, &status, 0);
+ return;
+ }
+
+ /* reparent window process to the init to not make a zombie on exit */
+ if ((pid = fork()) == -1) {
+ emergency("can't fork for window system on port %s: %m",
+ sp->se_device);
+ _exit(1);
+ }
+ if (pid)
+ _exit(0);
+
+ sigemptyset(&mask);
+ sigprocmask(SIG_SETMASK, &mask, (sigset_t *) 0);
+
+ if (setsid() < 0)
+ emergency("setsid failed (window) %m");
+
+#ifdef LOGIN_CAP
+ setprocresources(RESOURCE_WINDOW);
+#endif
+ if (sp->se_type) {
+ /* Don't use malloc after fork */
+ strcpy(term, "TERM=");
+ strncat(term, sp->se_type, sizeof(term) - 6);
+ env[0] = term;
+ env[1] = 0;
+ }
+ else
+ env[0] = 0;
+ execve(sp->se_window_argv[0], sp->se_window_argv, env);
+ stall("can't exec window system '%s' for port %s: %m",
+ sp->se_window_argv[0], sp->se_device);
+ _exit(1);
+}
+
+/*
+ * Start a login session running.
+ */
+static pid_t
+start_getty(session_t *sp)
+{
+ pid_t pid;
+ sigset_t mask;
+ time_t current_time = time((time_t *) 0);
+ int too_quick = 0;
+ char term[64], *env[2];
+
+ if (current_time >= sp->se_started &&
+ current_time - sp->se_started < GETTY_SPACING) {
+ if (++sp->se_nspace > GETTY_NSPACE) {
+ sp->se_nspace = 0;
+ too_quick = 1;
+ }
+ } else
+ sp->se_nspace = 0;
+
+ /*
+ * fork(), not vfork() -- we can't afford to block.
+ */
+ if ((pid = fork()) == -1) {
+ emergency("can't fork for getty on port %s: %m", sp->se_device);
+ return -1;
+ }
+
+ if (pid)
+ return pid;
+
+ if (too_quick) {
+ warning("getty repeating too quickly on port %s, sleeping %d secs",
+ sp->se_device, GETTY_SLEEP);
+ sleep((unsigned) GETTY_SLEEP);
+ }
+
+ if (sp->se_window) {
+ start_window_system(sp);
+ sleep(WINDOW_WAIT);
+ }
+
+ sigemptyset(&mask);
+ sigprocmask(SIG_SETMASK, &mask, (sigset_t *) 0);
+
+#ifdef LOGIN_CAP
+ setprocresources(RESOURCE_GETTY);
+#endif
+ if (sp->se_type) {
+ /* Don't use malloc after fork */
+ strcpy(term, "TERM=");
+ strncat(term, sp->se_type, sizeof(term) - 6);
+ env[0] = term;
+ env[1] = 0;
+ } else
+ env[0] = 0;
+ execve(sp->se_getty_argv[0], sp->se_getty_argv, env);
+ stall("can't exec getty '%s' for port %s: %m",
+ sp->se_getty_argv[0], sp->se_device);
+ _exit(1);
+}
+
+/*
+ * Collect exit status for a child.
+ * If an exiting login, start a new login running.
+ */
+static void
+collect_child(pid_t pid)
+{
+ session_t *sp, *sprev, *snext;
+
+ if (! sessions)
+ return;
+
+ if (! (sp = find_session(pid)))
+ return;
+
+ del_session(sp);
+ sp->se_process = 0;
+
+ if (sp->se_flags & SE_SHUTDOWN) {
+ if ((sprev = sp->se_prev) != NULL)
+ sprev->se_next = sp->se_next;
+ else
+ sessions = sp->se_next;
+ if ((snext = sp->se_next) != NULL)
+ snext->se_prev = sp->se_prev;
+ free_session(sp);
+ return;
+ }
+
+ if ((pid = start_getty(sp)) == -1) {
+ /* serious trouble */
+ requested_transition = clean_ttys;
+ return;
+ }
+
+ sp->se_process = pid;
+ sp->se_started = time((time_t *) 0);
+ add_session(sp);
+}
+
+/*
+ * Catch a signal and request a state transition.
+ */
+static void
+transition_handler(int sig)
+{
+
+ switch (sig) {
+ case SIGHUP:
+ if (current_state == read_ttys || current_state == multi_user ||
+ current_state == clean_ttys || current_state == catatonia)
+ requested_transition = clean_ttys;
+ break;
+ case SIGUSR2:
+ howto = RB_POWEROFF;
+ case SIGUSR1:
+ howto |= RB_HALT;
+ case SIGINT:
+ Reboot = TRUE;
+ case SIGTERM:
+ if (current_state == read_ttys || current_state == multi_user ||
+ current_state == clean_ttys || current_state == catatonia)
+ requested_transition = death;
+ else
+ requested_transition = death_single;
+ break;
+ case SIGTSTP:
+ if (current_state == runcom || current_state == read_ttys ||
+ current_state == clean_ttys ||
+ current_state == multi_user || current_state == catatonia)
+ requested_transition = catatonia;
+ break;
+ default:
+ requested_transition = 0;
+ break;
+ }
+}
+
+/*
+ * Take the system multiuser.
+ */
+static state_func_t
+multi_user(void)
+{
+ pid_t pid;
+ session_t *sp;
+
+ requested_transition = 0;
+
+ /*
+ * If the administrator has not set the security level to -1
+ * to indicate that the kernel should not run multiuser in secure
+ * mode, and the run script has not set a higher level of security
+ * than level 1, then put the kernel into secure mode.
+ */
+ if (getsecuritylevel() == 0)
+ setsecuritylevel(1);
+
+ for (sp = sessions; sp; sp = sp->se_next) {
+ if (sp->se_process)
+ continue;
+ if ((pid = start_getty(sp)) == -1) {
+ /* serious trouble */
+ requested_transition = clean_ttys;
+ break;
+ }
+ sp->se_process = pid;
+ sp->se_started = time((time_t *) 0);
+ add_session(sp);
+ }
+
+ while (!requested_transition)
+ if ((pid = waitpid(-1, (int *) 0, 0)) != -1)
+ collect_child(pid);
+
+ return (state_func_t) requested_transition;
+}
+
+/*
+ * This is an (n*2)+(n^2) algorithm. We hope it isn't run often...
+ */
+static state_func_t
+clean_ttys(void)
+{
+ session_t *sp, *sprev;
+ struct ttyent *typ;
+ int devlen;
+ char *old_getty, *old_window, *old_type;
+
+ /*
+ * mark all sessions for death, (!SE_PRESENT)
+ * as we find or create new ones they'll be marked as keepers,
+ * we'll later nuke all the ones not found in /etc/ttys
+ */
+ for (sp = sessions; sp != NULL; sp = sp->se_next)
+ sp->se_flags &= ~SE_PRESENT;
+
+ devlen = sizeof(_PATH_DEV) - 1;
+ while ((typ = getttyent()) != NULL) {
+ for (sprev = 0, sp = sessions; sp; sprev = sp, sp = sp->se_next)
+ if (strcmp(typ->ty_name, sp->se_device + devlen) == 0)
+ break;
+
+ if (sp) {
+ /* we want this one to live */
+ sp->se_flags |= SE_PRESENT;
+ if ((typ->ty_status & TTY_ON) == 0 ||
+ typ->ty_getty == 0) {
+ sp->se_flags |= SE_SHUTDOWN;
+ kill(sp->se_process, SIGHUP);
+ continue;
+ }
+ sp->se_flags &= ~SE_SHUTDOWN;
+ old_getty = sp->se_getty ? strdup(sp->se_getty) : 0;
+ old_window = sp->se_window ? strdup(sp->se_window) : 0;
+ old_type = sp->se_type ? strdup(sp->se_type) : 0;
+ if (setupargv(sp, typ) == 0) {
+ warning("can't parse getty for port %s",
+ sp->se_device);
+ sp->se_flags |= SE_SHUTDOWN;
+ kill(sp->se_process, SIGHUP);
+ }
+ else if ( !old_getty
+ || (!old_type && sp->se_type)
+ || (old_type && !sp->se_type)
+ || (!old_window && sp->se_window)
+ || (old_window && !sp->se_window)
+ || (strcmp(old_getty, sp->se_getty) != 0)
+ || (old_window && strcmp(old_window, sp->se_window) != 0)
+ || (old_type && strcmp(old_type, sp->se_type) != 0)
+ ) {
+ /* Don't set SE_SHUTDOWN here */
+ sp->se_nspace = 0;
+ sp->se_started = 0;
+ kill(sp->se_process, SIGHUP);
+ }
+ if (old_getty)
+ free(old_getty);
+ if (old_window)
+ free(old_window);
+ if (old_type)
+ free(old_type);
+ continue;
+ }
+
+ new_session(sprev, typ);
+ }
+
+ endttyent();
+
+ /*
+ * sweep through and kill all deleted sessions
+ * ones who's /etc/ttys line was deleted (SE_PRESENT unset)
+ */
+ for (sp = sessions; sp != NULL; sp = sp->se_next) {
+ if ((sp->se_flags & SE_PRESENT) == 0) {
+ sp->se_flags |= SE_SHUTDOWN;
+ kill(sp->se_process, SIGHUP);
+ }
+ }
+
+ return (state_func_t) multi_user;
+}
+
+/*
+ * Block further logins.
+ */
+static state_func_t
+catatonia(void)
+{
+ session_t *sp;
+
+ for (sp = sessions; sp; sp = sp->se_next)
+ sp->se_flags |= SE_SHUTDOWN;
+
+ return (state_func_t) multi_user;
+}
+
+/*
+ * Note SIGALRM.
+ */
+static void
+alrm_handler(int sig)
+{
+
+ (void)sig;
+ clang = 1;
+}
+
+/*
+ * Bring the system down to single user.
+ */
+static state_func_t
+death(void)
+{
+ session_t *sp;
+
+ /*
+ * Also revoke the TTY here. Because runshutdown() may reopen
+ * the TTY whose getty we're killing here, there is no guarantee
+ * runshutdown() will perform the initial open() call, causing
+ * the terminal attributes to be misconfigured.
+ */
+ for (sp = sessions; sp; sp = sp->se_next) {
+ sp->se_flags |= SE_SHUTDOWN;
+ kill(sp->se_process, SIGHUP);
+ revoke(sp->se_device);
+ }
+
+ /* Try to run the rc.shutdown script within a period of time */
+ runshutdown();
+
+ return (state_func_t) death_single;
+}
+
+/*
+ * Do what is necessary to reinitialize single user mode or reboot
+ * from an incomplete state.
+ */
+static state_func_t
+death_single(void)
+{
+ int i;
+ pid_t pid;
+ static const int death_sigs[2] = { SIGTERM, SIGKILL };
+
+ revoke(_PATH_CONSOLE);
+
+ for (i = 0; i < 2; ++i) {
+ if (kill(-1, death_sigs[i]) == -1 && errno == ESRCH)
+ return (state_func_t) single_user;
+
+ clang = 0;
+ alarm(DEATH_WATCH);
+ do
+ if ((pid = waitpid(-1, (int *)0, 0)) != -1)
+ collect_child(pid);
+ while (clang == 0 && errno != ECHILD);
+
+ if (errno == ECHILD)
+ return (state_func_t) single_user;
+ }
+
+ warning("some processes would not die; ps axl advised");
+
+ return (state_func_t) single_user;
+}
+
+/*
+ * Run the system shutdown script.
+ *
+ * Exit codes: XXX I should document more
+ * -2 shutdown script terminated abnormally
+ * -1 fatal error - can't run script
+ * 0 good.
+ * >0 some error (exit code)
+ */
+static int
+runshutdown(void)
+{
+ pid_t pid, wpid;
+ int status;
+ int shutdowntimeout;
+ size_t len;
+ char *argv[4];
+ const char *shell;
+ struct sigaction sa;
+ struct stat sb;
+
+ /*
+ * rc.shutdown is optional, so to prevent any unnecessary
+ * complaints from the shell we simply don't run it if the
+ * file does not exist. If the stat() here fails for other
+ * reasons, we'll let the shell complain.
+ */
+ if (stat(_PATH_RUNDOWN, &sb) == -1 && errno == ENOENT)
+ return 0;
+
+ shell = get_shell();
+
+ if ((pid = fork()) == 0) {
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ sa.sa_handler = SIG_IGN;
+ sigaction(SIGTSTP, &sa, (struct sigaction *)0);
+ sigaction(SIGHUP, &sa, (struct sigaction *)0);
+
+ open_console();
+
+ char _sh[] = "sh";
+ char _reboot[] = "reboot";
+ char _single[] = "single";
+ char _path_rundown[] = _PATH_RUNDOWN;
+
+ argv[0] = _sh;
+ argv[1] = _path_rundown;
+ argv[2] = Reboot ? _reboot : _single;
+ argv[3] = 0;
+
+ sigprocmask(SIG_SETMASK, &sa.sa_mask, (sigset_t *) 0);
+
+#ifdef LOGIN_CAP
+ setprocresources(RESOURCE_RC);
+#endif
+ execv(shell, argv);
+ warning("can't exec %s for %s: %m", shell, _PATH_RUNDOWN);
+ _exit(1); /* force single user mode */
+ }
+
+ if (pid == -1) {
+ emergency("can't fork for %s on %s: %m", shell, _PATH_RUNDOWN);
+ while (waitpid(-1, (int *) 0, WNOHANG) > 0)
+ continue;
+ sleep(STALL_TIMEOUT);
+ return -1;
+ }
+
+ len = sizeof(shutdowntimeout);
+ if (sysctlbyname("kern.init_shutdown_timeout", &shutdowntimeout, &len,
+ NULL, 0) == -1 || shutdowntimeout < 2)
+ shutdowntimeout = DEATH_SCRIPT;
+ alarm(shutdowntimeout);
+ clang = 0;
+ /*
+ * Copied from single_user(). This is a bit paranoid.
+ * Use the same ALRM handler.
+ */
+ do {
+ if ((wpid = waitpid(-1, &status, WUNTRACED)) != -1)
+ collect_child(wpid);
+ if (clang == 1) {
+ /* we were waiting for the sub-shell */
+ kill(wpid, SIGTERM);
+ warning("timeout expired for %s on %s: %m; going to "
+ "single user mode", shell, _PATH_RUNDOWN);
+ return -1;
+ }
+ if (wpid == -1) {
+ if (errno == EINTR)
+ continue;
+ warning("wait for %s on %s failed: %m; going to "
+ "single user mode", shell, _PATH_RUNDOWN);
+ return -1;
+ }
+ if (wpid == pid && WIFSTOPPED(status)) {
+ warning("init: %s on %s stopped, restarting\n",
+ shell, _PATH_RUNDOWN);
+ kill(pid, SIGCONT);
+ wpid = -1;
+ }
+ } while (wpid != pid && !clang);
+
+ /* Turn off the alarm */
+ alarm(0);
+
+ if (WIFSIGNALED(status) && WTERMSIG(status) == SIGTERM &&
+ requested_transition == catatonia) {
+ /*
+ * /etc/rc.shutdown executed /sbin/reboot;
+ * wait for the end quietly
+ */
+ sigset_t s;
+
+ sigfillset(&s);
+ for (;;)
+ sigsuspend(&s);
+ }
+
+ if (!WIFEXITED(status)) {
+ warning("%s on %s terminated abnormally, going to "
+ "single user mode", shell, _PATH_RUNDOWN);
+ return -2;
+ }
+
+ if ((status = WEXITSTATUS(status)) != 0)
+ warning("%s returned status %d", _PATH_RUNDOWN, status);
+
+ return status;
+}
+
+static char *
+strk(char *p)
+{
+ static char *t;
+ char *q;
+ int c;
+
+ if (p)
+ t = p;
+ if (!t)
+ return 0;
+
+ c = *t;
+ while (c == ' ' || c == '\t' )
+ c = *++t;
+ if (!c) {
+ t = 0;
+ return 0;
+ }
+ q = t;
+ if (c == '\'') {
+ c = *++t;
+ q = t;
+ while (c && c != '\'')
+ c = *++t;
+ if (!c) /* unterminated string */
+ q = t = 0;
+ else
+ *t++ = 0;
+ } else {
+ while (c && c != ' ' && c != '\t' )
+ c = *++t;
+ *t++ = 0;
+ if (!c)
+ t = 0;
+ }
+ return q;
+}
+
+#ifdef LOGIN_CAP
+static void
+setprocresources(const char *cname)
+{
+ login_cap_t *lc;
+ if ((lc = login_getclassbyname(cname, NULL)) != NULL) {
+ setusercontext(lc, (struct passwd*)NULL, 0,
+ LOGIN_SETPRIORITY | LOGIN_SETRESOURCES |
+ LOGIN_SETLOGINCLASS | LOGIN_SETCPUMASK);
+ login_close(lc);
+ }
+}
+#endif
diff --git a/sbin/init/pathnames.h b/sbin/init/pathnames.h
new file mode 100644
index 0000000..39eed4c
--- /dev/null
+++ b/sbin/init/pathnames.h
@@ -0,0 +1,41 @@
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Donn Seeley at Berkeley Software Design, Inc.
+ *
+ * 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.
+ * 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.
+ *
+ * @(#)pathnames.h 8.1 (Berkeley) 6/5/93
+ * $FreeBSD$
+ */
+
+#include <paths.h>
+
+#define _PATH_INITLOG "/var/log/init.log"
+#define _PATH_SLOGGER "/sbin/session_logger"
+#define _PATH_RUNCOM "/etc/rc"
+#define _PATH_RUNDOWN "/etc/rc.shutdown"
diff --git a/sbin/ipf/Makefile b/sbin/ipf/Makefile
new file mode 100644
index 0000000..df57c80
--- /dev/null
+++ b/sbin/ipf/Makefile
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+#.WAIT
+SUBDIR= libipf
+SUBDIR+= ipf ipfs ipfstat ipftest ipmon ipnat ippool ipresend
+#SUBDIR+= ipsend iptest rules
+
+.include <bsd.subdir.mk>
diff --git a/sbin/ipf/Makefile.inc b/sbin/ipf/Makefile.inc
new file mode 100644
index 0000000..79bdb8e
--- /dev/null
+++ b/sbin/ipf/Makefile.inc
@@ -0,0 +1,30 @@
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+WARNS?= 2
+NO_WFORMAT=
+NO_WARRAY_BOUNDS=
+
+CFLAGS+= -I${.CURDIR}/../../../contrib/ipfilter
+CFLAGS+= -I${.CURDIR}/../../../contrib/ipfilter/tools
+CFLAGS+= -I${.CURDIR}/../../../sys
+CFLAGS+= -I${.CURDIR}/../../../sys/contrib/ipfilter
+CFLAGS+= -DSTATETOP -D__UIO_EXPOSE
+
+.if ${MK_INET6_SUPPORT} != "no"
+CFLAGS+= -DUSE_INET6
+.else
+CFLAGS+= -DNOINET6
+.endif
+
+LIBADD+= ipf
+
+CLEANFILES+= y.tab.c y.tab.h
+
+.PATH: ${.CURDIR}/../../../contrib/ipfilter \
+ ${.CURDIR}/../../../contrib/ipfilter/lib \
+ ${.CURDIR}/../../../contrib/ipfilter/tools \
+ ${.CURDIR}/../../../contrib/ipfilter/man
+
+.include "../Makefile.inc"
diff --git a/sbin/ipf/ipf/Makefile b/sbin/ipf/ipf/Makefile
new file mode 100644
index 0000000..3ffd2b2
--- /dev/null
+++ b/sbin/ipf/ipf/Makefile
@@ -0,0 +1,41 @@
+# $FreeBSD$
+
+PROG= ipf
+SRCS= ${GENHDRS} ipf.c ipfcomp.c ipf_y.c ipf_l.c bpf_filter.c
+MAN= ipfilter.4 ipfilter.5 ipf.8 ipf.4 ipf.5 ipl.4
+MLINKS= ipf.5 ipf.conf.5 ipf.5 ipf6.conf.5
+CFLAGS+= -I. -DIPFILTER_BPF -DHAS_SYS_MD5_H
+
+GENHDRS= ipf_l.h ipf_y.h
+DPSRCS+= ${GENHDRS}
+
+CLEANFILES+= ${GENHDRS} ipf_y.c ipf_l.c
+
+ipf_y.c: ipf_y.y
+ ${YACC} -d ${.ALLSRC}
+ sed -e 's/yy/ipf_yy/g' \
+ -e 's/"ipf_y.y"/"..\/tools\/ipf_y.y"/' \
+ y.tab.c > ${.TARGET}
+ sed -e 's/yy/ipf_yy/g' \
+ y.tab.h > ${.TARGET:.c=.h}
+
+ipf_y.h: ipf_y.c
+
+ipf_l.c: lexer.c
+ sed -e 's/yy/ipf_yy/g' \
+ -e 's/y.tab.h/ipf_y.h/' \
+ -e 's/lexer.h/ipf_l.h/' \
+ ${.ALLSRC} > ${.TARGET}
+
+ipf_l.h: lexer.h
+ sed -e 's/yy/ipf_yy/g' \
+ ${.ALLSRC} > ${.TARGET}
+
+.if defined(RESCUE)
+LIBIPF_SRCS!= cd ${.CURDIR}/../libipf && ${MAKE} -V SRCS
+SRCS+= ${LIBIPF_SRCS}
+.else
+LIBADD+= pcap
+.endif
+
+.include <bsd.prog.mk>
diff --git a/sbin/ipf/ipfs/Makefile b/sbin/ipf/ipfs/Makefile
new file mode 100644
index 0000000..a64f805
--- /dev/null
+++ b/sbin/ipf/ipfs/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+PROG= ipfs
+MAN= ipfs.8
+
+.include <bsd.prog.mk>
diff --git a/sbin/ipf/ipfstat/Makefile b/sbin/ipf/ipfstat/Makefile
new file mode 100644
index 0000000..14823cf
--- /dev/null
+++ b/sbin/ipf/ipfstat/Makefile
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+NOGCCERROR= # defined
+
+PROG= ipfstat
+SRCS= ipfstat.c
+MAN= ipfstat.8
+LIBADD+= ncursesw
+
+.include <bsd.prog.mk>
diff --git a/sbin/ipf/ipftest/Makefile b/sbin/ipf/ipftest/Makefile
new file mode 100644
index 0000000..32b074c
--- /dev/null
+++ b/sbin/ipf/ipftest/Makefile
@@ -0,0 +1,97 @@
+# $FreeBSD$
+
+PROG= ipftest
+SRCS= ${GENHDRS} ipftest.c fil.c ip_frag.c ip_state.c ip_nat.c \
+ ip_nat6.c \
+ ip_proxy.c ip_auth.c ip_htable.c ip_lookup.c \
+ ip_pool.c ip_scan.c ip_sync.c ip_rules.c \
+ ip_fil.c ip_log.c ippool_y.c ippool_l.c ipf_y.c \
+ ipf_l.c ipnat_y.c ipnat_l.c md5.c radix_ipf.c ip_dstlist.c
+MAN= ipftest.1
+
+WARNS?= 0
+CFLAGS+= -DIPFILTER_LOG -DIPFILTER_COMPILED -DIPFILTER_LOOKUP \
+ -DIPFILTER_SYNC -DIPFILTER_CKSUM -DHAS_SYS_MD5_H -I.
+
+# XXX The original tarball does not define IPFILTER_SCAN when building this
+# XXX and other modules. It is believed the reason is it fails to build.
+# XXX It has been removed for now.
+# XXX CFLAGS+= -DIPFILTER_SCAN
+
+
+.PATH: ${.CURDIR}/../../../sys/contrib/ipfilter/netinet
+
+GENHDRS= ipnat_l.h ipnat_y.h ippool_l.h ippool_y.h ipf_l.h ipf_y.h
+DPSRCS+= ${GENHDRS}
+
+CLEANFILES+= ${GENHDRS}
+CLEANFILES+= ipf_y.c ipf_l.c
+CLEANFILES+= ipf.tab.c ipf.tab.h
+CLEANFILES+= ipnat_y.c ipnat_l.c
+CLEANFILES+= ipnat.tab.c ipnat.tab.h
+CLEANFILES+= ippool_y.c ippool_l.c
+CLEANFILES+= ippool.tab.c ippool.tab.h
+
+ipnat_y.c: ipnat_y.y
+ ${YACC} -b ipnat -d ${.ALLSRC}
+ sed -e 's/yy/ipnat_yy/g' \
+ -e 's/y.tab.c/ipnat_y.c/' \
+ -e s/\"ipnat_y.y\"/\"..\\/tools\\/ipnat_y.y\"/ \
+ ipnat.tab.c > ${.TARGET}
+ sed -e 's/yy/ipnat_yy/g' \
+ -e 's/y.tab.h/ipnat_y.h/' \
+ ipnat.tab.h > ${.TARGET:.c=.h}
+
+ipnat_y.h: ipnat_y.c
+
+ipnat_l.c: lexer.c
+ sed -e 's/yy/ipnat_yy/g' \
+ -e 's/y.tab.h/ipnat_y.h/' \
+ -e 's/lexer.h/ipnat_l.h/' \
+ ${.ALLSRC} > ${.TARGET}
+
+ipnat_l.h: lexer.h
+ sed -e 's/yy/ipnat_yy/g' \
+ ${.ALLSRC} > ${.TARGET}
+
+ippool_y.c: ippool_y.y
+ ${YACC} -b ippool -d ${.ALLSRC}
+ sed -e 's/yy/ippool_yy/g' \
+ -e 's/"ippool_y.y"/"..\/tools\/ippool_y.y"/' \
+ ippool.tab.c > ${.TARGET}
+ sed -e 's/yy/ippool_yy/g' \
+ ippool.tab.h > ${.TARGET:.c=.h}
+
+ippool_y.h: ippool_y.c
+
+ippool_l.c: lexer.c
+ sed -e 's/yy/ippool_yy/g' \
+ -e 's/y.tab.h/ippool_y.h/' \
+ -e 's/lexer.h/ippool_l.h/' \
+ ${.ALLSRC} > ${.TARGET}
+
+ippool_l.h: lexer.h
+ sed -e 's/yy/ippool_yy/g' \
+ ${.ALLSRC} > ${.TARGET}
+
+ipf_y.c: ipf_y.y
+ ${YACC} -b ipf -d ${.ALLSRC}
+ sed -e 's/yy/ipf_yy/g' \
+ -e 's/"ipf_y.y"/"..\/tools\/ipf_y.y"/' \
+ ipf.tab.c > ${.TARGET}
+ sed -e 's/yy/ipf_yy/g' \
+ ipf.tab.h > ${.TARGET:.c=.h}
+
+ipf_y.h: ipf_y.c
+
+ipf_l.c: lexer.c
+ sed -e 's/yy/ipf_yy/g' \
+ -e 's/y.tab.h/ipf_y.h/' \
+ -e 's/lexer.h/ipf_l.h/' \
+ ${.ALLSRC} > ${.TARGET}
+
+ipf_l.h: lexer.h
+ sed -e 's/yy/ipf_yy/g' \
+ ${.ALLSRC} > ${.TARGET}
+
+.include <bsd.prog.mk>
diff --git a/sbin/ipf/ipmon/Makefile b/sbin/ipf/ipmon/Makefile
new file mode 100644
index 0000000..3639f87
--- /dev/null
+++ b/sbin/ipf/ipmon/Makefile
@@ -0,0 +1,34 @@
+# $FreeBSD$
+
+PROG= ipmon
+SRCS= ${GENHDRS} ipmon.c ipmon_y.c ipmon_l.c
+MAN= ipmon.8
+
+CFLAGS+= -DLOGFAC=LOG_LOCAL0 -I.
+
+GENHDRS+= ipmon_l.h ipmon_y.h
+DPSRCS+= ${GENHDRS}
+
+CLEANFILES+= ${GENHDRS} ipmon_y.c ipmon_l.c
+
+ipmon_y.c: ipmon_y.y
+ ${YACC} -d ${.ALLSRC}
+ sed -e 's/yy/ipmon_yy/g' \
+ -e 's/"ipmon_y.y"/"..\/tools\/ipmon_y.y"/' \
+ y.tab.c > ${.TARGET}
+ sed -e 's/yy/ipmon_yy/g' \
+ y.tab.h > ${.TARGET:.c=.h}
+
+ipmon_y.h: ipmon_y.c
+
+ipmon_l.c: lexer.c
+ sed -e 's/yy/ipmon_yy/g' \
+ -e 's/y.tab.h/ipmon_y.h/' \
+ -e 's/lexer.h/ipmon_l.h/' \
+ ${.ALLSRC} > ${.TARGET}
+
+ipmon_l.h: lexer.h
+ sed -e 's/yy/ipmon_yy/g' \
+ ${.ALLSRC} > ${.TARGET}
+
+.include <bsd.prog.mk>
diff --git a/sbin/ipf/ipnat/Makefile b/sbin/ipf/ipnat/Makefile
new file mode 100644
index 0000000..1c017e1
--- /dev/null
+++ b/sbin/ipf/ipnat/Makefile
@@ -0,0 +1,36 @@
+# $FreeBSD$
+
+PROG= ipnat
+SRCS= ${GENHDRS} ipnat.c ipnat_y.c ipnat_l.c
+MAN= ipnat.8 ipnat.4 ipnat.5
+MLINKS= ipnat.5 ipnat.conf.5
+CFLAGS+= -I.
+
+GENHDRS= ipnat_l.h ipnat_y.h
+DPSRCS+= ${GENHDRS}
+
+CLEANFILES+= ${GENHDRS} ipnat_y.c ipnat_l.c
+
+ipnat_y.c: ipnat_y.y
+ ${YACC} -d ${.ALLSRC}
+ sed -e 's/yy/ipnat_yy/g' \
+ -e 's/y.tab.c/ipnat_y.c/' \
+ -e s/\"ipnat_y.y\"/\"..\\/tools\\/ipnat_y.y\"/ \
+ y.tab.c > ${.TARGET}
+ sed -e 's/yy/ipnat_yy/g' \
+ -e 's/y.tab.h/ipnat_y.h/' \
+ y.tab.h > ${.TARGET:.c=.h}
+
+ipnat_y.h: ipnat_y.c
+
+ipnat_l.c: lexer.c
+ sed -e 's/yy/ipnat_yy/g' \
+ -e 's/y.tab.h/ipnat_y.h/' \
+ -e 's/lexer.h/ipnat_l.h/' \
+ ${.ALLSRC} > ${.TARGET}
+
+ipnat_l.h: lexer.h
+ sed -e 's/yy/ipnat_yy/g' \
+ ${.ALLSRC} > ${.TARGET}
+
+.include <bsd.prog.mk>
diff --git a/sbin/ipf/ippool/Makefile b/sbin/ipf/ippool/Makefile
new file mode 100644
index 0000000..6e3f85d
--- /dev/null
+++ b/sbin/ipf/ippool/Makefile
@@ -0,0 +1,33 @@
+# $FreeBSD$
+
+PROG= ippool
+SRCS= ${GENHDRS} ippool_y.c ippool_l.c kmem.c ippool.c
+MAN= ippool.5 ippool.8
+CFLAGS+= -I.
+
+GENHDRS= ippool_l.h ippool_y.h
+DPSRCS+= ${GENHDRS}
+
+CLEANFILES+= ${GENHDRS} ippool_y.c ippool_l.c
+
+ippool_y.c: ippool_y.y
+ ${YACC} -d ${.ALLSRC}
+ sed -e 's/yy/ippool_yy/g' \
+ -e 's/"ippool_y.y"/"..\/tools\/ippool_y.y"/' \
+ y.tab.c > ${.TARGET}
+ sed -e 's/yy/ippool_yy/g' \
+ y.tab.h > ${.TARGET:.c=.h}
+
+ippool_y.h: ippool_y.c
+
+ippool_l.c: lexer.c
+ sed -e 's/yy/ippool_yy/g' \
+ -e 's/y.tab.h/ippool_y.h/' \
+ -e 's/lexer.h/ippool_l.h/' \
+ ${.ALLSRC} > ${.TARGET}
+
+ippool_l.h: lexer.h
+ sed -e 's/yy/ippool_yy/g' \
+ ${.ALLSRC} > ${.TARGET}
+
+.include <bsd.prog.mk>
diff --git a/sbin/ipf/ipresend/Makefile b/sbin/ipf/ipresend/Makefile
new file mode 100644
index 0000000..5e0ac15
--- /dev/null
+++ b/sbin/ipf/ipresend/Makefile
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+PROG= ipresend
+SRCS= ipresend.c ip.c resend.c sbpf.c sock.c 44arp.c
+MAN= ipresend.1
+
+.PATH: ${.CURDIR}/../../../contrib/ipfilter/ipsend
+
+.include <bsd.prog.mk>
diff --git a/sbin/ipf/ipsend/Makefile b/sbin/ipf/ipsend/Makefile
new file mode 100644
index 0000000..176cb10
--- /dev/null
+++ b/sbin/ipf/ipsend/Makefile
@@ -0,0 +1,38 @@
+# $FreeBSD$
+
+NOGCCERROR= # defined
+
+.include <bsd.own.mk>
+
+PROG= ipsend
+SRCS= ipsend.c ip.c ipsopt.c iplang_y.c iplang_l.l sbpf.c \
+ sock.c 44arp.c
+MAN= ipsend.1 ipsend.5
+LIBADD+= l
+
+CFLAGS+= -I${NETBSDSRCDIR}/dist/ipf/ipsend
+CFLAGS+= -I${NETBSDSRCDIR}/dist/ipf/iplang
+CFLAGS+= -I.
+
+CLEANFILES+= iplang_y.c iplang_y.h
+
+DPSRCS+= iplang_y.h
+
+.PATH: ${NETBSDSRCDIR}/dist/ipf/ipsend \
+ ${NETBSDSRCDIR}/dist/ipf/iplang
+
+iplang_y.c: iplang_y.y
+ ${YACC} -d ${.ALLSRC}
+ mv y.tab.c ${.TARGET}
+ mv y.tab.h ${.TARGET:.c=.h}
+
+iplang_y.h: iplang_y.c
+
+# XXX
+# We have a problem with make and linking ipsend
+# cc -o /home/source/src/usr.sbin/ipf/ipsend/../../../dist/ipf/ipsend .....
+# isn't correct.
+# Use .NOPATH as an workaround for that problem
+.NOPATH: ipsend
+
+.include <bsd.prog.mk>
diff --git a/sbin/ipf/iptest/Makefile b/sbin/ipf/iptest/Makefile
new file mode 100644
index 0000000..647471c
--- /dev/null
+++ b/sbin/ipf/iptest/Makefile
@@ -0,0 +1,11 @@
+# $FreeBSD$
+
+.include <bsd.own.mk>
+
+PROG= iptest
+SRCS= iptest.c iptests.c ip.c sbpf.c sock.c 44arp.c
+MAN= iptest.1
+
+.PATH: ${NETBSDSRCDIR}/dist/ipf/ipsend
+
+.include <bsd.prog.mk>
diff --git a/sbin/ipf/libipf/Makefile b/sbin/ipf/libipf/Makefile
new file mode 100644
index 0000000..077062f
--- /dev/null
+++ b/sbin/ipf/libipf/Makefile
@@ -0,0 +1,46 @@
+# $FreeBSD$
+
+LIB= ipf
+INTERNALLIB=
+
+SRCS= addicmp.c addipopt.c alist_free.c alist_new.c allocmbt.c \
+ assigndefined.c bcopywrap.c \
+ binprint.c buildopts.c checkrev.c connecttcp.c \
+ count4bits.c count6bits.c \
+ debug.c dupmbt.c \
+ facpri.c familyname.c \
+ fill6bits.c findword.c \
+ flags.c freembt.c ftov.c \
+ genmask.c \
+ gethost.c getifname.c geticmptype.c \
+ getnattype.c getport.c getportproto.c getproto.c getsumd.c \
+ hostname.c icmpcode.c icmptypename.c icmptypes.c \
+ initparse.c interror.c ionames.c \
+ ipf_dotuning.c ipf_perror.c ipft_hx.c ipft_pc.c \
+ ipft_tx.c ipoptsec.c kmem.c kmemcpywrap.c \
+ kvatoname.c load_dstlist.c load_dstlistnode.c load_file.c \
+ load_hash.c load_hashnode.c \
+ load_http.c load_pool.c load_poolnode.c load_url.c \
+ mb_hexdump.c msgdsize.c \
+ mutex_emul.c nametokva.c nat_setgroupmap.c ntomask.c \
+ optname.c optprint.c optprintv6.c optvalue.c parsefields.c \
+ parseipfexpr.c parsewhoisline.c poolio.c portname.c \
+ prependmbt.c \
+ print_toif.c printactiveaddr.c printactivenat.c printaddr.c \
+ printaps.c printbuf.c printdstl_live.c printdstlist.c \
+ printdstlistdata.c printdstlistnode.c printdstlistpolicy.c \
+ printfieldhdr.c \
+ printfr.c printfraginfo.c printhash.c printhash_live.c \
+ printhashdata.c printhashnode.c printhost.c printhostmap.c \
+ printhostmask.c printifname.c printip.c printipfexpr.c printiphdr.c printlog.c printlookup.c \
+ printmask.c printnat.c printnataddr.c printnatfield.c printnatside.c printpacket.c printpacket6.c \
+ printpool.c printpool_live.c printpooldata.c printpoolfield.c printpoolnode.c \
+ printportcmp.c printproto.c printsbuf.c printstate.c printstatefields.c \
+ printtcpflags.c \
+ printtqtable.c printtunable.c printunit.c remove_hash.c remove_hashnode.c \
+ remove_pool.c remove_poolnode.c resetlexer.c rwlock_emul.c \
+ save_execute.c save_file.c save_nothing.c save_syslog.c save_v1trap.c save_v2trap.c vtof.c \
+ tcp_flags.c tcpflags.c tcpoptnames.c v6ionames.c v6optvalue.c \
+ var.c verbose.c
+
+.include <bsd.lib.mk>
diff --git a/sbin/ipf/rules/Makefile b/sbin/ipf/rules/Makefile
new file mode 100644
index 0000000..a90907f
--- /dev/null
+++ b/sbin/ipf/rules/Makefile
@@ -0,0 +1,17 @@
+# $FreeBSD$
+
+.include <bsd.own.mk>
+
+MAN= mkfilters.1
+.if ${MKSHARE} != "no"
+FILESDIR= /usr/share/examples/ipf
+
+FILES= BASIC.NAT BASIC_1.FW BASIC_2.FW example.1 example.2 example.3 \
+ example.4 example.5 example.6 example.7 example.8 example.9 \
+ example.10 example.11 example.12 example.13 example.sr \
+ firewall ftp-proxy ftppxy mediaone nat-setup \
+ nat.eg server tcpstate mkfilters
+.endif
+
+.PATH: ${NETBSDSRCDIR}/dist/ipf/rules
+.include <bsd.prog.mk>
diff --git a/sbin/ipfw/Makefile b/sbin/ipfw/Makefile
new file mode 100644
index 0000000..efd99fc
--- /dev/null
+++ b/sbin/ipfw/Makefile
@@ -0,0 +1,17 @@
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+PROG= ipfw
+SRCS= ipfw2.c dummynet.c ipv6.c main.c nat.c tables.c
+WARNS?= 2
+
+.if ${MK_PF} != "no"
+SRCS+= altq.c
+CFLAGS+=-DPF
+.endif
+
+LIBADD= util
+MAN= ipfw.8
+
+.include <bsd.prog.mk>
diff --git a/sbin/ipfw/altq.c b/sbin/ipfw/altq.c
new file mode 100644
index 0000000..8398ab6
--- /dev/null
+++ b/sbin/ipfw/altq.c
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2002-2003 Luigi Rizzo
+ * Copyright (c) 1996 Alex Nash, Paul Traina, Poul-Henning Kamp
+ * Copyright (c) 1994 Ugen J.S.Antsilevich
+ *
+ * Idea and grammar partially left from:
+ * Copyright (c) 1993 Daniel Boulet
+ *
+ * Redistribution and use in source forms, with and without modification,
+ * are permitted provided that this entire comment appears intact.
+ *
+ * Redistribution in binary form may occur without any restrictions.
+ * Obviously, it would be nice if you gave credit where credit is due
+ * but requiring it would be too onerous.
+ *
+ * This software is provided ``AS IS'' without any warranties of any kind.
+ *
+ * NEW command line interface for IP firewall facility
+ *
+ * $FreeBSD$
+ *
+ * altq interface
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+
+#include "ipfw2.h"
+
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <net/if.h> /* IFNAMSIZ */
+#include <net/pfvar.h>
+#include <netinet/in.h> /* in_addr */
+#include <netinet/ip_fw.h>
+
+/*
+ * Map between current altq queue id numbers and names.
+ */
+static TAILQ_HEAD(, pf_altq) altq_entries =
+ TAILQ_HEAD_INITIALIZER(altq_entries);
+
+void
+altq_set_enabled(int enabled)
+{
+ int pffd;
+
+ pffd = open("/dev/pf", O_RDWR);
+ if (pffd == -1)
+ err(EX_UNAVAILABLE,
+ "altq support opening pf(4) control device");
+ if (enabled) {
+ if (ioctl(pffd, DIOCSTARTALTQ) != 0 && errno != EEXIST)
+ err(EX_UNAVAILABLE, "enabling altq");
+ } else {
+ if (ioctl(pffd, DIOCSTOPALTQ) != 0 && errno != ENOENT)
+ err(EX_UNAVAILABLE, "disabling altq");
+ }
+ close(pffd);
+}
+
+static void
+altq_fetch(void)
+{
+ struct pfioc_altq pfioc;
+ struct pf_altq *altq;
+ int pffd;
+ unsigned int mnr;
+ static int altq_fetched = 0;
+
+ if (altq_fetched)
+ return;
+ altq_fetched = 1;
+ pffd = open("/dev/pf", O_RDONLY);
+ if (pffd == -1) {
+ warn("altq support opening pf(4) control device");
+ return;
+ }
+ bzero(&pfioc, sizeof(pfioc));
+ if (ioctl(pffd, DIOCGETALTQS, &pfioc) != 0) {
+ warn("altq support getting queue list");
+ close(pffd);
+ return;
+ }
+ mnr = pfioc.nr;
+ for (pfioc.nr = 0; pfioc.nr < mnr; pfioc.nr++) {
+ if (ioctl(pffd, DIOCGETALTQ, &pfioc) != 0) {
+ if (errno == EBUSY)
+ break;
+ warn("altq support getting queue list");
+ close(pffd);
+ return;
+ }
+ if (pfioc.altq.qid == 0)
+ continue;
+ altq = safe_calloc(1, sizeof(*altq));
+ *altq = pfioc.altq;
+ TAILQ_INSERT_TAIL(&altq_entries, altq, entries);
+ }
+ close(pffd);
+}
+
+u_int32_t
+altq_name_to_qid(const char *name)
+{
+ struct pf_altq *altq;
+
+ altq_fetch();
+ TAILQ_FOREACH(altq, &altq_entries, entries)
+ if (strcmp(name, altq->qname) == 0)
+ break;
+ if (altq == NULL)
+ errx(EX_DATAERR, "altq has no queue named `%s'", name);
+ return altq->qid;
+}
+
+static const char *
+altq_qid_to_name(u_int32_t qid)
+{
+ struct pf_altq *altq;
+
+ altq_fetch();
+ TAILQ_FOREACH(altq, &altq_entries, entries)
+ if (qid == altq->qid)
+ break;
+ if (altq == NULL)
+ return NULL;
+ return altq->qname;
+}
+
+void
+print_altq_cmd(struct buf_pr *bp, ipfw_insn_altq *altqptr)
+{
+ if (altqptr) {
+ const char *qname;
+
+ qname = altq_qid_to_name(altqptr->qid);
+ if (qname == NULL)
+ bprintf(bp, " altq ?<%u>", altqptr->qid);
+ else
+ bprintf(bp, " altq %s", qname);
+ }
+}
diff --git a/sbin/ipfw/dummynet.c b/sbin/ipfw/dummynet.c
new file mode 100644
index 0000000..dc95a19
--- /dev/null
+++ b/sbin/ipfw/dummynet.c
@@ -0,0 +1,1410 @@
+/*
+ * Copyright (c) 2002-2003,2010 Luigi Rizzo
+ *
+ * Redistribution and use in source forms, with and without modification,
+ * are permitted provided that this entire comment appears intact.
+ *
+ * Redistribution in binary form may occur without any restrictions.
+ * Obviously, it would be nice if you gave credit where credit is due
+ * but requiring it would be too onerous.
+ *
+ * This software is provided ``AS IS'' without any warranties of any kind.
+ *
+ * $FreeBSD$
+ *
+ * dummynet support
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+/* XXX there are several sysctl leftover here */
+#include <sys/sysctl.h>
+
+#include "ipfw2.h"
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <libutil.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <netinet/ip_fw.h>
+#include <netinet/ip_dummynet.h>
+#include <arpa/inet.h> /* inet_ntoa */
+
+
+static struct _s_x dummynet_params[] = {
+ { "plr", TOK_PLR },
+ { "noerror", TOK_NOERROR },
+ { "buckets", TOK_BUCKETS },
+ { "dst-ip", TOK_DSTIP },
+ { "src-ip", TOK_SRCIP },
+ { "dst-port", TOK_DSTPORT },
+ { "src-port", TOK_SRCPORT },
+ { "proto", TOK_PROTO },
+ { "weight", TOK_WEIGHT },
+ { "lmax", TOK_LMAX },
+ { "maxlen", TOK_LMAX },
+ { "all", TOK_ALL },
+ { "mask", TOK_MASK }, /* alias for both */
+ { "sched_mask", TOK_SCHED_MASK },
+ { "flow_mask", TOK_FLOW_MASK },
+ { "droptail", TOK_DROPTAIL },
+ { "ecn", TOK_ECN },
+ { "red", TOK_RED },
+ { "gred", TOK_GRED },
+ { "bw", TOK_BW },
+ { "bandwidth", TOK_BW },
+ { "delay", TOK_DELAY },
+ { "link", TOK_LINK },
+ { "pipe", TOK_PIPE },
+ { "queue", TOK_QUEUE },
+ { "flowset", TOK_FLOWSET },
+ { "sched", TOK_SCHED },
+ { "pri", TOK_PRI },
+ { "priority", TOK_PRI },
+ { "type", TOK_TYPE },
+ { "flow-id", TOK_FLOWID},
+ { "dst-ipv6", TOK_DSTIP6},
+ { "dst-ip6", TOK_DSTIP6},
+ { "src-ipv6", TOK_SRCIP6},
+ { "src-ip6", TOK_SRCIP6},
+ { "profile", TOK_PROFILE},
+ { "burst", TOK_BURST},
+ { "dummynet-params", TOK_NULL },
+ { NULL, 0 } /* terminator */
+};
+
+#define O_NEXT(p, len) ((void *)((char *)p + len))
+
+static void
+oid_fill(struct dn_id *oid, int len, int type, uintptr_t id)
+{
+ oid->len = len;
+ oid->type = type;
+ oid->subtype = 0;
+ oid->id = id;
+}
+
+/* make room in the buffer and move the pointer forward */
+static void *
+o_next(struct dn_id **o, int len, int type)
+{
+ struct dn_id *ret = *o;
+ oid_fill(ret, len, type, 0);
+ *o = O_NEXT(*o, len);
+ return ret;
+}
+
+#if 0
+static int
+sort_q(void *arg, const void *pa, const void *pb)
+{
+ int rev = (co.do_sort < 0);
+ int field = rev ? -co.do_sort : co.do_sort;
+ long long res = 0;
+ const struct dn_flow_queue *a = pa;
+ const struct dn_flow_queue *b = pb;
+
+ switch (field) {
+ case 1: /* pkts */
+ res = a->len - b->len;
+ break;
+ case 2: /* bytes */
+ res = a->len_bytes - b->len_bytes;
+ break;
+
+ case 3: /* tot pkts */
+ res = a->tot_pkts - b->tot_pkts;
+ break;
+
+ case 4: /* tot bytes */
+ res = a->tot_bytes - b->tot_bytes;
+ break;
+ }
+ if (res < 0)
+ res = -1;
+ if (res > 0)
+ res = 1;
+ return (int)(rev ? res : -res);
+}
+#endif
+
+/* print a mask and header for the subsequent list of flows */
+static void
+print_mask(struct ipfw_flow_id *id)
+{
+ if (!IS_IP6_FLOW_ID(id)) {
+ printf(" "
+ "mask: %s 0x%02x 0x%08x/0x%04x -> 0x%08x/0x%04x\n",
+ id->extra ? "queue," : "",
+ id->proto,
+ id->src_ip, id->src_port,
+ id->dst_ip, id->dst_port);
+ } else {
+ char buf[255];
+ printf("\n mask: %sproto: 0x%02x, flow_id: 0x%08x, ",
+ id->extra ? "queue," : "",
+ id->proto, id->flow_id6);
+ inet_ntop(AF_INET6, &(id->src_ip6), buf, sizeof(buf));
+ printf("%s/0x%04x -> ", buf, id->src_port);
+ inet_ntop(AF_INET6, &(id->dst_ip6), buf, sizeof(buf));
+ printf("%s/0x%04x\n", buf, id->dst_port);
+ }
+}
+
+static void
+print_header(struct ipfw_flow_id *id)
+{
+ if (!IS_IP6_FLOW_ID(id))
+ printf("BKT Prot ___Source IP/port____ "
+ "____Dest. IP/port____ "
+ "Tot_pkt/bytes Pkt/Byte Drp\n");
+ else
+ printf("BKT ___Prot___ _flow-id_ "
+ "______________Source IPv6/port_______________ "
+ "_______________Dest. IPv6/port_______________ "
+ "Tot_pkt/bytes Pkt/Byte Drp\n");
+}
+
+static void
+list_flow(struct buf_pr *bp, struct dn_flow *ni)
+{
+ char buff[255];
+ struct protoent *pe = NULL;
+ struct in_addr ina;
+ struct ipfw_flow_id *id = &ni->fid;
+
+ pe = getprotobynumber(id->proto);
+ /* XXX: Should check for IPv4 flows */
+ bprintf(bp, "%3u%c", (ni->oid.id) & 0xff,
+ id->extra ? '*' : ' ');
+ if (!IS_IP6_FLOW_ID(id)) {
+ if (pe)
+ bprintf(bp, "%-4s ", pe->p_name);
+ else
+ bprintf(bp, "%4u ", id->proto);
+ ina.s_addr = htonl(id->src_ip);
+ bprintf(bp, "%15s/%-5d ",
+ inet_ntoa(ina), id->src_port);
+ ina.s_addr = htonl(id->dst_ip);
+ bprintf(bp, "%15s/%-5d ",
+ inet_ntoa(ina), id->dst_port);
+ } else {
+ /* Print IPv6 flows */
+ if (pe != NULL)
+ bprintf(bp, "%9s ", pe->p_name);
+ else
+ bprintf(bp, "%9u ", id->proto);
+ bprintf(bp, "%7d %39s/%-5d ", id->flow_id6,
+ inet_ntop(AF_INET6, &(id->src_ip6), buff, sizeof(buff)),
+ id->src_port);
+ bprintf(bp, " %39s/%-5d ",
+ inet_ntop(AF_INET6, &(id->dst_ip6), buff, sizeof(buff)),
+ id->dst_port);
+ }
+ pr_u64(bp, &ni->tot_pkts, 4);
+ pr_u64(bp, &ni->tot_bytes, 8);
+ bprintf(bp, "%2u %4u %3u",
+ ni->length, ni->len_bytes, ni->drops);
+}
+
+static void
+print_flowset_parms(struct dn_fs *fs, char *prefix)
+{
+ int l;
+ char qs[30];
+ char plr[30];
+ char red[90]; /* Display RED parameters */
+
+ l = fs->qsize;
+ if (fs->flags & DN_QSIZE_BYTES) {
+ if (l >= 8192)
+ sprintf(qs, "%d KB", l / 1024);
+ else
+ sprintf(qs, "%d B", l);
+ } else
+ sprintf(qs, "%3d sl.", l);
+ if (fs->plr)
+ sprintf(plr, "plr %f", 1.0 * fs->plr / (double)(0x7fffffff));
+ else
+ plr[0] = '\0';
+
+ if (fs->flags & DN_IS_RED) { /* RED parameters */
+ sprintf(red,
+ "\n\t %cRED w_q %f min_th %d max_th %d max_p %f",
+ (fs->flags & DN_IS_GENTLE_RED) ? 'G' : ' ',
+ 1.0 * fs->w_q / (double)(1 << SCALE_RED),
+ fs->min_th,
+ fs->max_th,
+ 1.0 * fs->max_p / (double)(1 << SCALE_RED));
+ if (fs->flags & DN_IS_ECN)
+ strncat(red, " (ecn)", 6);
+ } else
+ sprintf(red, "droptail");
+
+ if (prefix[0]) {
+ printf("%s %s%s %d queues (%d buckets) %s\n",
+ prefix, qs, plr, fs->oid.id, fs->buckets, red);
+ prefix[0] = '\0';
+ } else {
+ printf("q%05d %s%s %d flows (%d buckets) sched %d "
+ "weight %d lmax %d pri %d %s\n",
+ fs->fs_nr, qs, plr, fs->oid.id, fs->buckets,
+ fs->sched_nr, fs->par[0], fs->par[1], fs->par[2], red);
+ if (fs->flags & DN_HAVE_MASK)
+ print_mask(&fs->flow_mask);
+ }
+}
+
+static void
+print_extra_delay_parms(struct dn_profile *p)
+{
+ double loss;
+ if (p->samples_no <= 0)
+ return;
+
+ loss = p->loss_level;
+ loss /= p->samples_no;
+ printf("\t profile: name \"%s\" loss %f samples %d\n",
+ p->name, loss, p->samples_no);
+}
+
+static void
+flush_buf(char *buf)
+{
+ if (buf[0])
+ printf("%s\n", buf);
+ buf[0] = '\0';
+}
+
+/*
+ * generic list routine. We expect objects in a specific order, i.e.
+ * PIPES AND SCHEDULERS:
+ * link; scheduler; internal flowset if any; instances
+ * we can tell a pipe from the number.
+ *
+ * FLOWSETS:
+ * flowset; queues;
+ * link i (int queue); scheduler i; si(i) { flowsets() : queues }
+ */
+static void
+list_pipes(struct dn_id *oid, struct dn_id *end)
+{
+ char buf[160]; /* pending buffer */
+ int toPrint = 1; /* print header */
+ struct buf_pr bp;
+
+ buf[0] = '\0';
+ bp_alloc(&bp, 4096);
+ for (; oid != end; oid = O_NEXT(oid, oid->len)) {
+ if (oid->len < sizeof(*oid))
+ errx(1, "invalid oid len %d\n", oid->len);
+
+ switch (oid->type) {
+ default:
+ flush_buf(buf);
+ printf("unrecognized object %d size %d\n", oid->type, oid->len);
+ break;
+ case DN_TEXT: /* list of attached flowsets */
+ {
+ int i, l;
+ struct {
+ struct dn_id id;
+ uint32_t p[0];
+ } *d = (void *)oid;
+ l = (oid->len - sizeof(*oid))/sizeof(d->p[0]);
+ if (l == 0)
+ break;
+ printf(" Children flowsets: ");
+ for (i = 0; i < l; i++)
+ printf("%u ", d->p[i]);
+ printf("\n");
+ break;
+ }
+ case DN_CMD_GET:
+ if (co.verbose)
+ printf("answer for cmd %d, len %d\n", oid->type, oid->id);
+ break;
+ case DN_SCH: {
+ struct dn_sch *s = (struct dn_sch *)oid;
+ flush_buf(buf);
+ printf(" sched %d type %s flags 0x%x %d buckets %d active\n",
+ s->sched_nr,
+ s->name, s->flags, s->buckets, s->oid.id);
+ if (s->flags & DN_HAVE_MASK)
+ print_mask(&s->sched_mask);
+ }
+ break;
+
+ case DN_FLOW:
+ if (toPrint != 0) {
+ print_header(&((struct dn_flow *)oid)->fid);
+ toPrint = 0;
+ }
+ list_flow(&bp, (struct dn_flow *)oid);
+ printf("%s\n", bp.buf);
+ break;
+
+ case DN_LINK: {
+ struct dn_link *p = (struct dn_link *)oid;
+ double b = p->bandwidth;
+ char bwbuf[30];
+ char burst[5 + 7];
+
+ /* This starts a new object so flush buffer */
+ flush_buf(buf);
+ /* data rate */
+ if (b == 0)
+ sprintf(bwbuf, "unlimited ");
+ else if (b >= 1000000)
+ sprintf(bwbuf, "%7.3f Mbit/s", b/1000000);
+ else if (b >= 1000)
+ sprintf(bwbuf, "%7.3f Kbit/s", b/1000);
+ else
+ sprintf(bwbuf, "%7.3f bit/s ", b);
+
+ if (humanize_number(burst, sizeof(burst), p->burst,
+ "", HN_AUTOSCALE, 0) < 0 || co.verbose)
+ sprintf(burst, "%d", (int)p->burst);
+ sprintf(buf, "%05d: %s %4d ms burst %s",
+ p->link_nr % DN_MAX_ID, bwbuf, p->delay, burst);
+ }
+ break;
+
+ case DN_FS:
+ print_flowset_parms((struct dn_fs *)oid, buf);
+ break;
+ case DN_PROFILE:
+ flush_buf(buf);
+ print_extra_delay_parms((struct dn_profile *)oid);
+ }
+ flush_buf(buf); // XXX does it really go here ?
+ }
+
+ bp_free(&bp);
+}
+
+/*
+ * Delete pipe, queue or scheduler i
+ */
+int
+ipfw_delete_pipe(int do_pipe, int i)
+{
+ struct {
+ struct dn_id oid;
+ uintptr_t a[1]; /* add more if we want a list */
+ } cmd;
+ oid_fill((void *)&cmd, sizeof(cmd), DN_CMD_DELETE, DN_API_VERSION);
+ cmd.oid.subtype = (do_pipe == 1) ? DN_LINK :
+ ( (do_pipe == 2) ? DN_FS : DN_SCH);
+ cmd.a[0] = i;
+ i = do_cmd(IP_DUMMYNET3, &cmd, cmd.oid.len);
+ if (i) {
+ i = 1;
+ warn("rule %u: setsockopt(IP_DUMMYNET_DEL)", i);
+ }
+ return i;
+}
+
+/*
+ * Code to parse delay profiles.
+ *
+ * Some link types introduce extra delays in the transmission
+ * of a packet, e.g. because of MAC level framing, contention on
+ * the use of the channel, MAC level retransmissions and so on.
+ * From our point of view, the channel is effectively unavailable
+ * for this extra time, which is constant or variable depending
+ * on the link type. Additionally, packets may be dropped after this
+ * time (e.g. on a wireless link after too many retransmissions).
+ * We can model the additional delay with an empirical curve
+ * that represents its distribution.
+ *
+ * cumulative probability
+ * 1.0 ^
+ * |
+ * L +-- loss-level x
+ * | ******
+ * | *
+ * | *****
+ * | *
+ * | **
+ * | *
+ * +-------*------------------->
+ * delay
+ *
+ * The empirical curve may have both vertical and horizontal lines.
+ * Vertical lines represent constant delay for a range of
+ * probabilities; horizontal lines correspond to a discontinuty
+ * in the delay distribution: the link will use the largest delay
+ * for a given probability.
+ *
+ * To pass the curve to dummynet, we must store the parameters
+ * in a file as described below, and issue the command
+ *
+ * ipfw pipe <n> config ... bw XXX profile <filename> ...
+ *
+ * The file format is the following, with whitespace acting as
+ * a separator and '#' indicating the beginning a comment:
+ *
+ * samples N
+ * the number of samples used in the internal
+ * representation (2..1024; default 100);
+ *
+ * loss-level L
+ * The probability above which packets are lost.
+ * (0.0 <= L <= 1.0, default 1.0 i.e. no loss);
+ *
+ * name identifier
+ * Optional a name (listed by "ipfw pipe show")
+ * to identify the distribution;
+ *
+ * "delay prob" | "prob delay"
+ * One of these two lines is mandatory and defines
+ * the format of the following lines with data points.
+ *
+ * XXX YYY
+ * 2 or more lines representing points in the curve,
+ * with either delay or probability first, according
+ * to the chosen format.
+ * The unit for delay is milliseconds.
+ *
+ * Data points does not need to be ordered or equal to the number
+ * specified in the "samples" line. ipfw will sort and interpolate
+ * the curve as needed.
+ *
+ * Example of a profile file:
+
+ name bla_bla_bla
+ samples 100
+ loss-level 0.86
+ prob delay
+ 0 200 # minimum overhead is 200ms
+ 0.5 200
+ 0.5 300
+ 0.8 1000
+ 0.9 1300
+ 1 1300
+
+ * Internally, we will convert the curve to a fixed number of
+ * samples, and when it is time to transmit a packet we will
+ * model the extra delay as extra bits in the packet.
+ *
+ */
+
+#define ED_MAX_LINE_LEN 256+ED_MAX_NAME_LEN
+#define ED_TOK_SAMPLES "samples"
+#define ED_TOK_LOSS "loss-level"
+#define ED_TOK_NAME "name"
+#define ED_TOK_DELAY "delay"
+#define ED_TOK_PROB "prob"
+#define ED_TOK_BW "bw"
+#define ED_SEPARATORS " \t\n"
+#define ED_MIN_SAMPLES_NO 2
+
+/*
+ * returns 1 if s is a non-negative number, with at least one '.'
+ */
+static int
+is_valid_number(const char *s)
+{
+ int i, dots_found = 0;
+ int len = strlen(s);
+
+ for (i = 0; i<len; ++i)
+ if (!isdigit(s[i]) && (s[i] !='.' || ++dots_found > 1))
+ return 0;
+ return 1;
+}
+
+/*
+ * Take as input a string describing a bandwidth value
+ * and return the numeric bandwidth value.
+ * set clocking interface or bandwidth value
+ */
+static void
+read_bandwidth(char *arg, int *bandwidth, char *if_name, int namelen)
+{
+ if (*bandwidth != -1)
+ warnx("duplicate token, override bandwidth value!");
+
+ if (arg[0] >= 'a' && arg[0] <= 'z') {
+ if (!if_name) {
+ errx(1, "no if support");
+ }
+ if (namelen >= IFNAMSIZ)
+ warn("interface name truncated");
+ namelen--;
+ /* interface name */
+ strncpy(if_name, arg, namelen);
+ if_name[namelen] = '\0';
+ *bandwidth = 0;
+ } else { /* read bandwidth value */
+ int bw;
+ char *end = NULL;
+
+ bw = strtoul(arg, &end, 0);
+ if (*end == 'K' || *end == 'k') {
+ end++;
+ bw *= 1000;
+ } else if (*end == 'M' || *end == 'm') {
+ end++;
+ bw *= 1000000;
+ }
+ if ((*end == 'B' &&
+ _substrcmp2(end, "Bi", "Bit/s") != 0) ||
+ _substrcmp2(end, "by", "bytes") == 0)
+ bw *= 8;
+
+ if (bw < 0)
+ errx(EX_DATAERR, "bandwidth too large");
+
+ *bandwidth = bw;
+ if (if_name)
+ if_name[0] = '\0';
+ }
+}
+
+struct point {
+ double prob;
+ double delay;
+};
+
+static int
+compare_points(const void *vp1, const void *vp2)
+{
+ const struct point *p1 = vp1;
+ const struct point *p2 = vp2;
+ double res = 0;
+
+ res = p1->prob - p2->prob;
+ if (res == 0)
+ res = p1->delay - p2->delay;
+ if (res < 0)
+ return -1;
+ else if (res > 0)
+ return 1;
+ else
+ return 0;
+}
+
+#define ED_EFMT(s) EX_DATAERR,"error in %s at line %d: "#s,filename,lineno
+
+static void
+load_extra_delays(const char *filename, struct dn_profile *p,
+ struct dn_link *link)
+{
+ char line[ED_MAX_LINE_LEN];
+ FILE *f;
+ int lineno = 0;
+ int i;
+
+ int samples = -1;
+ double loss = -1.0;
+ char profile_name[ED_MAX_NAME_LEN];
+ int delay_first = -1;
+ int do_points = 0;
+ struct point points[ED_MAX_SAMPLES_NO];
+ int points_no = 0;
+
+ /* XXX link never NULL? */
+ p->link_nr = link->link_nr;
+
+ profile_name[0] = '\0';
+ f = fopen(filename, "r");
+ if (f == NULL)
+ err(EX_UNAVAILABLE, "fopen: %s", filename);
+
+ while (fgets(line, ED_MAX_LINE_LEN, f)) { /* read commands */
+ char *s, *cur = line, *name = NULL, *arg = NULL;
+
+ ++lineno;
+
+ /* parse the line */
+ while (cur) {
+ s = strsep(&cur, ED_SEPARATORS);
+ if (s == NULL || *s == '#')
+ break;
+ if (*s == '\0')
+ continue;
+ if (arg)
+ errx(ED_EFMT("too many arguments"));
+ if (name == NULL)
+ name = s;
+ else
+ arg = s;
+ }
+ if (name == NULL) /* empty line */
+ continue;
+ if (arg == NULL)
+ errx(ED_EFMT("missing arg for %s"), name);
+
+ if (!strcasecmp(name, ED_TOK_SAMPLES)) {
+ if (samples > 0)
+ errx(ED_EFMT("duplicate ``samples'' line"));
+ if (atoi(arg) <=0)
+ errx(ED_EFMT("invalid number of samples"));
+ samples = atoi(arg);
+ if (samples>ED_MAX_SAMPLES_NO)
+ errx(ED_EFMT("too many samples, maximum is %d"),
+ ED_MAX_SAMPLES_NO);
+ do_points = 0;
+ } else if (!strcasecmp(name, ED_TOK_BW)) {
+ char buf[IFNAMSIZ];
+ read_bandwidth(arg, &link->bandwidth, buf, sizeof(buf));
+ } else if (!strcasecmp(name, ED_TOK_LOSS)) {
+ if (loss != -1.0)
+ errx(ED_EFMT("duplicated token: %s"), name);
+ if (!is_valid_number(arg))
+ errx(ED_EFMT("invalid %s"), arg);
+ loss = atof(arg);
+ if (loss > 1)
+ errx(ED_EFMT("%s greater than 1.0"), name);
+ do_points = 0;
+ } else if (!strcasecmp(name, ED_TOK_NAME)) {
+ if (profile_name[0] != '\0')
+ errx(ED_EFMT("duplicated token: %s"), name);
+ strncpy(profile_name, arg, sizeof(profile_name) - 1);
+ profile_name[sizeof(profile_name)-1] = '\0';
+ do_points = 0;
+ } else if (!strcasecmp(name, ED_TOK_DELAY)) {
+ if (do_points)
+ errx(ED_EFMT("duplicated token: %s"), name);
+ delay_first = 1;
+ do_points = 1;
+ } else if (!strcasecmp(name, ED_TOK_PROB)) {
+ if (do_points)
+ errx(ED_EFMT("duplicated token: %s"), name);
+ delay_first = 0;
+ do_points = 1;
+ } else if (do_points) {
+ if (!is_valid_number(name) || !is_valid_number(arg))
+ errx(ED_EFMT("invalid point found"));
+ if (delay_first) {
+ points[points_no].delay = atof(name);
+ points[points_no].prob = atof(arg);
+ } else {
+ points[points_no].delay = atof(arg);
+ points[points_no].prob = atof(name);
+ }
+ if (points[points_no].prob > 1.0)
+ errx(ED_EFMT("probability greater than 1.0"));
+ ++points_no;
+ } else {
+ errx(ED_EFMT("unrecognised command '%s'"), name);
+ }
+ }
+
+ fclose (f);
+
+ if (samples == -1) {
+ warnx("'%s' not found, assuming 100", ED_TOK_SAMPLES);
+ samples = 100;
+ }
+
+ if (loss == -1.0) {
+ warnx("'%s' not found, assuming no loss", ED_TOK_LOSS);
+ loss = 1;
+ }
+
+ /* make sure that there are enough points. */
+ if (points_no < ED_MIN_SAMPLES_NO)
+ errx(ED_EFMT("too few samples, need at least %d"),
+ ED_MIN_SAMPLES_NO);
+
+ qsort(points, points_no, sizeof(struct point), compare_points);
+
+ /* interpolation */
+ for (i = 0; i<points_no-1; ++i) {
+ double y1 = points[i].prob * samples;
+ double x1 = points[i].delay;
+ double y2 = points[i+1].prob * samples;
+ double x2 = points[i+1].delay;
+
+ int ix = y1;
+ int stop = y2;
+
+ if (x1 == x2) {
+ for (; ix<stop; ++ix)
+ p->samples[ix] = x1;
+ } else {
+ double m = (y2-y1)/(x2-x1);
+ double c = y1 - m*x1;
+ for (; ix<stop ; ++ix)
+ p->samples[ix] = (ix - c)/m;
+ }
+ }
+ p->samples_no = samples;
+ p->loss_level = loss * samples;
+ strncpy(p->name, profile_name, sizeof(p->name));
+}
+
+/*
+ * configuration of pipes, schedulers, flowsets.
+ * When we configure a new scheduler, an empty pipe is created, so:
+ *
+ * do_pipe = 1 -> "pipe N config ..." only for backward compatibility
+ * sched N+Delta type fifo sched_mask ...
+ * pipe N+Delta <parameters>
+ * flowset N+Delta pipe N+Delta (no parameters)
+ * sched N type wf2q+ sched_mask ...
+ * pipe N <parameters>
+ *
+ * do_pipe = 2 -> flowset N config
+ * flowset N parameters
+ *
+ * do_pipe = 3 -> sched N config
+ * sched N parameters (default no pipe)
+ * optional Pipe N config ...
+ * pipe ==>
+ */
+void
+ipfw_config_pipe(int ac, char **av)
+{
+ int i;
+ u_int j;
+ char *end;
+ struct dn_id *buf, *base;
+ struct dn_sch *sch = NULL;
+ struct dn_link *p = NULL;
+ struct dn_fs *fs = NULL;
+ struct dn_profile *pf = NULL;
+ struct ipfw_flow_id *mask = NULL;
+ int lmax;
+ uint32_t _foo = 0, *flags = &_foo , *buckets = &_foo;
+
+ /*
+ * allocate space for 1 header,
+ * 1 scheduler, 1 link, 1 flowset, 1 profile
+ */
+ lmax = sizeof(struct dn_id); /* command header */
+ lmax += sizeof(struct dn_sch) + sizeof(struct dn_link) +
+ sizeof(struct dn_fs) + sizeof(struct dn_profile);
+
+ av++; ac--;
+ /* Pipe number */
+ if (ac && isdigit(**av)) {
+ i = atoi(*av); av++; ac--;
+ } else
+ i = -1;
+ if (i <= 0)
+ errx(EX_USAGE, "need a pipe/flowset/sched number");
+ base = buf = safe_calloc(1, lmax);
+ /* all commands start with a 'CONFIGURE' and a version */
+ o_next(&buf, sizeof(struct dn_id), DN_CMD_CONFIG);
+ base->id = DN_API_VERSION;
+
+ switch (co.do_pipe) {
+ case 1: /* "pipe N config ..." */
+ /* Allocate space for the WF2Q+ scheduler, its link
+ * and the FIFO flowset. Set the number, but leave
+ * the scheduler subtype and other parameters to 0
+ * so the kernel will use appropriate defaults.
+ * XXX todo: add a flag to record if a parameter
+ * is actually configured.
+ * If we do a 'pipe config' mask -> sched_mask.
+ * The FIFO scheduler and link are derived from the
+ * WF2Q+ one in the kernel.
+ */
+ sch = o_next(&buf, sizeof(*sch), DN_SCH);
+ p = o_next(&buf, sizeof(*p), DN_LINK);
+ fs = o_next(&buf, sizeof(*fs), DN_FS);
+
+ sch->sched_nr = i;
+ sch->oid.subtype = 0; /* defaults to WF2Q+ */
+ mask = &sch->sched_mask;
+ flags = &sch->flags;
+ buckets = &sch->buckets;
+ *flags |= DN_PIPE_CMD;
+
+ p->link_nr = i;
+
+ /* This flowset is only for the FIFO scheduler */
+ fs->fs_nr = i + 2*DN_MAX_ID;
+ fs->sched_nr = i + DN_MAX_ID;
+ break;
+
+ case 2: /* "queue N config ... " */
+ fs = o_next(&buf, sizeof(*fs), DN_FS);
+ fs->fs_nr = i;
+ mask = &fs->flow_mask;
+ flags = &fs->flags;
+ buckets = &fs->buckets;
+ break;
+
+ case 3: /* "sched N config ..." */
+ sch = o_next(&buf, sizeof(*sch), DN_SCH);
+ fs = o_next(&buf, sizeof(*fs), DN_FS);
+ sch->sched_nr = i;
+ mask = &sch->sched_mask;
+ flags = &sch->flags;
+ buckets = &sch->buckets;
+ /* fs is used only with !MULTIQUEUE schedulers */
+ fs->fs_nr = i + DN_MAX_ID;
+ fs->sched_nr = i;
+ break;
+ }
+ /* set to -1 those fields for which we want to reuse existing
+ * values from the kernel.
+ * Also, *_nr and subtype = 0 mean reuse the value from the kernel.
+ * XXX todo: support reuse of the mask.
+ */
+ if (p)
+ p->bandwidth = -1;
+ for (j = 0; j < sizeof(fs->par)/sizeof(fs->par[0]); j++)
+ fs->par[j] = -1;
+ while (ac > 0) {
+ double d;
+ int tok = match_token(dummynet_params, *av);
+ ac--; av++;
+
+ switch(tok) {
+ case TOK_NOERROR:
+ NEED(fs, "noerror is only for pipes");
+ fs->flags |= DN_NOERROR;
+ break;
+
+ case TOK_PLR:
+ NEED(fs, "plr is only for pipes");
+ NEED1("plr needs argument 0..1\n");
+ d = strtod(av[0], NULL);
+ if (d > 1)
+ d = 1;
+ else if (d < 0)
+ d = 0;
+ fs->plr = (int)(d*0x7fffffff);
+ ac--; av++;
+ break;
+
+ case TOK_QUEUE:
+ NEED(fs, "queue is only for pipes or flowsets");
+ NEED1("queue needs queue size\n");
+ end = NULL;
+ fs->qsize = strtoul(av[0], &end, 0);
+ if (*end == 'K' || *end == 'k') {
+ fs->flags |= DN_QSIZE_BYTES;
+ fs->qsize *= 1024;
+ } else if (*end == 'B' ||
+ _substrcmp2(end, "by", "bytes") == 0) {
+ fs->flags |= DN_QSIZE_BYTES;
+ }
+ ac--; av++;
+ break;
+
+ case TOK_BUCKETS:
+ NEED(fs, "buckets is only for pipes or flowsets");
+ NEED1("buckets needs argument\n");
+ *buckets = strtoul(av[0], NULL, 0);
+ ac--; av++;
+ break;
+
+ case TOK_FLOW_MASK:
+ case TOK_SCHED_MASK:
+ case TOK_MASK:
+ NEED(mask, "tok_mask");
+ NEED1("mask needs mask specifier\n");
+ /*
+ * per-flow queue, mask is dst_ip, dst_port,
+ * src_ip, src_port, proto measured in bits
+ */
+
+ bzero(mask, sizeof(*mask));
+ end = NULL;
+
+ while (ac >= 1) {
+ uint32_t *p32 = NULL;
+ uint16_t *p16 = NULL;
+ uint32_t *p20 = NULL;
+ struct in6_addr *pa6 = NULL;
+ uint32_t a;
+
+ tok = match_token(dummynet_params, *av);
+ ac--; av++;
+ switch(tok) {
+ case TOK_ALL:
+ /*
+ * special case, all bits significant
+ * except 'extra' (the queue number)
+ */
+ mask->dst_ip = ~0;
+ mask->src_ip = ~0;
+ mask->dst_port = ~0;
+ mask->src_port = ~0;
+ mask->proto = ~0;
+ n2mask(&mask->dst_ip6, 128);
+ n2mask(&mask->src_ip6, 128);
+ mask->flow_id6 = ~0;
+ *flags |= DN_HAVE_MASK;
+ goto end_mask;
+
+ case TOK_QUEUE:
+ mask->extra = ~0;
+ *flags |= DN_HAVE_MASK;
+ goto end_mask;
+
+ case TOK_DSTIP:
+ mask->addr_type = 4;
+ p32 = &mask->dst_ip;
+ break;
+
+ case TOK_SRCIP:
+ mask->addr_type = 4;
+ p32 = &mask->src_ip;
+ break;
+
+ case TOK_DSTIP6:
+ mask->addr_type = 6;
+ pa6 = &mask->dst_ip6;
+ break;
+
+ case TOK_SRCIP6:
+ mask->addr_type = 6;
+ pa6 = &mask->src_ip6;
+ break;
+
+ case TOK_FLOWID:
+ mask->addr_type = 6;
+ p20 = &mask->flow_id6;
+ break;
+
+ case TOK_DSTPORT:
+ p16 = &mask->dst_port;
+ break;
+
+ case TOK_SRCPORT:
+ p16 = &mask->src_port;
+ break;
+
+ case TOK_PROTO:
+ break;
+
+ default:
+ ac++; av--; /* backtrack */
+ goto end_mask;
+ }
+ if (ac < 1)
+ errx(EX_USAGE, "mask: value missing");
+ if (*av[0] == '/') {
+ a = strtoul(av[0]+1, &end, 0);
+ if (pa6 == NULL)
+ a = (a == 32) ? ~0 : (1 << a) - 1;
+ } else
+ a = strtoul(av[0], &end, 0);
+ if (p32 != NULL)
+ *p32 = a;
+ else if (p16 != NULL) {
+ if (a > 0xFFFF)
+ errx(EX_DATAERR,
+ "port mask must be 16 bit");
+ *p16 = (uint16_t)a;
+ } else if (p20 != NULL) {
+ if (a > 0xfffff)
+ errx(EX_DATAERR,
+ "flow_id mask must be 20 bit");
+ *p20 = (uint32_t)a;
+ } else if (pa6 != NULL) {
+ if (a > 128)
+ errx(EX_DATAERR,
+ "in6addr invalid mask len");
+ else
+ n2mask(pa6, a);
+ } else {
+ if (a > 0xFF)
+ errx(EX_DATAERR,
+ "proto mask must be 8 bit");
+ mask->proto = (uint8_t)a;
+ }
+ if (a != 0)
+ *flags |= DN_HAVE_MASK;
+ ac--; av++;
+ } /* end while, config masks */
+end_mask:
+ break;
+
+ case TOK_RED:
+ case TOK_GRED:
+ NEED1("red/gred needs w_q/min_th/max_th/max_p\n");
+ fs->flags |= DN_IS_RED;
+ if (tok == TOK_GRED)
+ fs->flags |= DN_IS_GENTLE_RED;
+ /*
+ * the format for parameters is w_q/min_th/max_th/max_p
+ */
+ if ((end = strsep(&av[0], "/"))) {
+ double w_q = strtod(end, NULL);
+ if (w_q > 1 || w_q <= 0)
+ errx(EX_DATAERR, "0 < w_q <= 1");
+ fs->w_q = (int) (w_q * (1 << SCALE_RED));
+ }
+ if ((end = strsep(&av[0], "/"))) {
+ fs->min_th = strtoul(end, &end, 0);
+ if (*end == 'K' || *end == 'k')
+ fs->min_th *= 1024;
+ }
+ if ((end = strsep(&av[0], "/"))) {
+ fs->max_th = strtoul(end, &end, 0);
+ if (*end == 'K' || *end == 'k')
+ fs->max_th *= 1024;
+ }
+ if ((end = strsep(&av[0], "/"))) {
+ double max_p = strtod(end, NULL);
+ if (max_p > 1 || max_p < 0)
+ errx(EX_DATAERR, "0 <= max_p <= 1");
+ fs->max_p = (int)(max_p * (1 << SCALE_RED));
+ }
+ ac--; av++;
+ break;
+
+ case TOK_ECN:
+ fs->flags |= DN_IS_ECN;
+ break;
+
+ case TOK_DROPTAIL:
+ NEED(fs, "droptail is only for flowsets");
+ fs->flags &= ~(DN_IS_RED|DN_IS_GENTLE_RED);
+ break;
+
+ case TOK_BW:
+ NEED(p, "bw is only for links");
+ NEED1("bw needs bandwidth or interface\n");
+ read_bandwidth(av[0], &p->bandwidth, NULL, 0);
+ ac--; av++;
+ break;
+
+ case TOK_DELAY:
+ NEED(p, "delay is only for links");
+ NEED1("delay needs argument 0..10000ms\n");
+ p->delay = strtoul(av[0], NULL, 0);
+ ac--; av++;
+ break;
+
+ case TOK_TYPE: {
+ int l;
+ NEED(sch, "type is only for schedulers");
+ NEED1("type needs a string");
+ l = strlen(av[0]);
+ if (l == 0 || l > 15)
+ errx(1, "type %s too long\n", av[0]);
+ strcpy(sch->name, av[0]);
+ sch->oid.subtype = 0; /* use string */
+ ac--; av++;
+ break;
+ }
+
+ case TOK_WEIGHT:
+ NEED(fs, "weight is only for flowsets");
+ NEED1("weight needs argument\n");
+ fs->par[0] = strtol(av[0], &end, 0);
+ ac--; av++;
+ break;
+
+ case TOK_LMAX:
+ NEED(fs, "lmax is only for flowsets");
+ NEED1("lmax needs argument\n");
+ fs->par[1] = strtol(av[0], &end, 0);
+ ac--; av++;
+ break;
+
+ case TOK_PRI:
+ NEED(fs, "priority is only for flowsets");
+ NEED1("priority needs argument\n");
+ fs->par[2] = strtol(av[0], &end, 0);
+ ac--; av++;
+ break;
+
+ case TOK_SCHED:
+ case TOK_PIPE:
+ NEED(fs, "pipe/sched");
+ NEED1("pipe/link/sched needs number\n");
+ fs->sched_nr = strtoul(av[0], &end, 0);
+ ac--; av++;
+ break;
+
+ case TOK_PROFILE:
+ NEED((!pf), "profile already set");
+ NEED(p, "profile");
+ {
+ NEED1("extra delay needs the file name\n");
+ pf = o_next(&buf, sizeof(*pf), DN_PROFILE);
+ load_extra_delays(av[0], pf, p); //XXX can't fail?
+ --ac; ++av;
+ }
+ break;
+
+ case TOK_BURST:
+ NEED(p, "burst");
+ NEED1("burst needs argument\n");
+ errno = 0;
+ if (expand_number(av[0], &p->burst) < 0)
+ if (errno != ERANGE)
+ errx(EX_DATAERR,
+ "burst: invalid argument");
+ if (errno || p->burst > (1ULL << 48) - 1)
+ errx(EX_DATAERR,
+ "burst: out of range (0..2^48-1)");
+ ac--; av++;
+ break;
+
+ default:
+ errx(EX_DATAERR, "unrecognised option ``%s''", av[-1]);
+ }
+ }
+
+ /* check validity of parameters */
+ if (p) {
+ if (p->delay > 10000)
+ errx(EX_DATAERR, "delay must be < 10000");
+ if (p->bandwidth == -1)
+ p->bandwidth = 0;
+ }
+ if (fs) {
+ /* XXX accept a 0 scheduler to keep the default */
+ if (fs->flags & DN_QSIZE_BYTES) {
+ size_t len;
+ long limit;
+
+ len = sizeof(limit);
+ if (sysctlbyname("net.inet.ip.dummynet.pipe_byte_limit",
+ &limit, &len, NULL, 0) == -1)
+ limit = 1024*1024;
+ if (fs->qsize > limit)
+ errx(EX_DATAERR, "queue size must be < %ldB", limit);
+ } else {
+ size_t len;
+ long limit;
+
+ len = sizeof(limit);
+ if (sysctlbyname("net.inet.ip.dummynet.pipe_slot_limit",
+ &limit, &len, NULL, 0) == -1)
+ limit = 100;
+ if (fs->qsize > limit)
+ errx(EX_DATAERR, "2 <= queue size <= %ld", limit);
+ }
+
+ if ((fs->flags & DN_IS_ECN) && !(fs->flags & DN_IS_RED))
+ errx(EX_USAGE, "enable red/gred for ECN");
+
+ if (fs->flags & DN_IS_RED) {
+ size_t len;
+ int lookup_depth, avg_pkt_size;
+
+ if (!(fs->flags & DN_IS_ECN) && (fs->min_th >= fs->max_th))
+ errx(EX_DATAERR, "min_th %d must be < than max_th %d",
+ fs->min_th, fs->max_th);
+ else if ((fs->flags & DN_IS_ECN) && (fs->min_th > fs->max_th))
+ errx(EX_DATAERR, "min_th %d must be =< than max_th %d",
+ fs->min_th, fs->max_th);
+
+ if (fs->max_th == 0)
+ errx(EX_DATAERR, "max_th must be > 0");
+
+ len = sizeof(int);
+ if (sysctlbyname("net.inet.ip.dummynet.red_lookup_depth",
+ &lookup_depth, &len, NULL, 0) == -1)
+ lookup_depth = 256;
+ if (lookup_depth == 0)
+ errx(EX_DATAERR, "net.inet.ip.dummynet.red_lookup_depth"
+ " must be greater than zero");
+
+ len = sizeof(int);
+ if (sysctlbyname("net.inet.ip.dummynet.red_avg_pkt_size",
+ &avg_pkt_size, &len, NULL, 0) == -1)
+ avg_pkt_size = 512;
+
+ if (avg_pkt_size == 0)
+ errx(EX_DATAERR,
+ "net.inet.ip.dummynet.red_avg_pkt_size must"
+ " be greater than zero");
+
+#if 0 /* the following computation is now done in the kernel */
+ /*
+ * Ticks needed for sending a medium-sized packet.
+ * Unfortunately, when we are configuring a WF2Q+ queue, we
+ * do not have bandwidth information, because that is stored
+ * in the parent pipe, and also we have multiple queues
+ * competing for it. So we set s=0, which is not very
+ * correct. But on the other hand, why do we want RED with
+ * WF2Q+ ?
+ */
+ if (p.bandwidth==0) /* this is a WF2Q+ queue */
+ s = 0;
+ else
+ s = (double)ck.hz * avg_pkt_size * 8 / p.bandwidth;
+ /*
+ * max idle time (in ticks) before avg queue size becomes 0.
+ * NOTA: (3/w_q) is approx the value x so that
+ * (1-w_q)^x < 10^-3.
+ */
+ w_q = ((double)fs->w_q) / (1 << SCALE_RED);
+ idle = s * 3. / w_q;
+ fs->lookup_step = (int)idle / lookup_depth;
+ if (!fs->lookup_step)
+ fs->lookup_step = 1;
+ weight = 1 - w_q;
+ for (t = fs->lookup_step; t > 1; --t)
+ weight *= 1 - w_q;
+ fs->lookup_weight = (int)(weight * (1 << SCALE_RED));
+#endif /* code moved in the kernel */
+ }
+ }
+
+ i = do_cmd(IP_DUMMYNET3, base, (char *)buf - (char *)base);
+
+ if (i)
+ err(1, "setsockopt(%s)", "IP_DUMMYNET_CONFIGURE");
+}
+
+void
+dummynet_flush(void)
+{
+ struct dn_id oid;
+ oid_fill(&oid, sizeof(oid), DN_CMD_FLUSH, DN_API_VERSION);
+ do_cmd(IP_DUMMYNET3, &oid, oid.len);
+}
+
+/* Parse input for 'ipfw [pipe|sched|queue] show [range list]'
+ * Returns the number of ranges, and possibly stores them
+ * in the array v of size len.
+ */
+static int
+parse_range(int ac, char *av[], uint32_t *v, int len)
+{
+ int n = 0;
+ char *endptr, *s;
+ uint32_t base[2];
+
+ if (v == NULL || len < 2) {
+ v = base;
+ len = 2;
+ }
+
+ for (s = *av; s != NULL; av++, ac--) {
+ v[0] = strtoul(s, &endptr, 10);
+ v[1] = (*endptr != '-') ? v[0] :
+ strtoul(endptr+1, &endptr, 10);
+ if (*endptr == '\0') { /* prepare for next round */
+ s = (ac > 0) ? *(av+1) : NULL;
+ } else {
+ if (*endptr != ',') {
+ warn("invalid number: %s", s);
+ s = ++endptr;
+ continue;
+ }
+ /* continue processing from here */
+ s = ++endptr;
+ ac++;
+ av--;
+ }
+ if (v[1] < v[0] ||
+ v[1] >= DN_MAX_ID-1 ||
+ v[1] >= DN_MAX_ID-1) {
+ continue; /* invalid entry */
+ }
+ n++;
+ /* translate if 'pipe list' */
+ if (co.do_pipe == 1) {
+ v[0] += DN_MAX_ID;
+ v[1] += DN_MAX_ID;
+ }
+ v = (n*2 < len) ? v + 2 : base;
+ }
+ return n;
+}
+
+/* main entry point for dummynet list functions. co.do_pipe indicates
+ * which function we want to support.
+ * av may contain filtering arguments, either individual entries
+ * or ranges, or lists (space or commas are valid separators).
+ * Format for a range can be n1-n2 or n3 n4 n5 ...
+ * In a range n1 must be <= n2, otherwise the range is ignored.
+ * A number 'n4' is translate in a range 'n4-n4'
+ * All number must be > 0 and < DN_MAX_ID-1
+ */
+void
+dummynet_list(int ac, char *av[], int show_counters)
+{
+ struct dn_id *oid, *x = NULL;
+ int ret, i;
+ int n; /* # of ranges */
+ u_int buflen, l;
+ u_int max_size; /* largest obj passed up */
+
+ (void)show_counters; // XXX unused, but we should use it.
+ ac--;
+ av++; /* skip 'list' | 'show' word */
+
+ n = parse_range(ac, av, NULL, 0); /* Count # of ranges. */
+
+ /* Allocate space to store ranges */
+ l = sizeof(*oid) + sizeof(uint32_t) * n * 2;
+ oid = safe_calloc(1, l);
+ oid_fill(oid, l, DN_CMD_GET, DN_API_VERSION);
+
+ if (n > 0) /* store ranges in idx */
+ parse_range(ac, av, (uint32_t *)(oid + 1), n*2);
+ /*
+ * Compute the size of the largest object returned. If the
+ * response leaves at least this much spare space in the
+ * buffer, then surely the response is complete; otherwise
+ * there might be a risk of truncation and we will need to
+ * retry with a larger buffer.
+ * XXX don't bother with smaller structs.
+ */
+ max_size = sizeof(struct dn_fs);
+ if (max_size < sizeof(struct dn_sch))
+ max_size = sizeof(struct dn_sch);
+ if (max_size < sizeof(struct dn_flow))
+ max_size = sizeof(struct dn_flow);
+
+ switch (co.do_pipe) {
+ case 1:
+ oid->subtype = DN_LINK; /* list pipe */
+ break;
+ case 2:
+ oid->subtype = DN_FS; /* list queue */
+ break;
+ case 3:
+ oid->subtype = DN_SCH; /* list sched */
+ break;
+ }
+
+ /*
+ * Ask the kernel an estimate of the required space (result
+ * in oid.id), unless we are requesting a subset of objects,
+ * in which case the kernel does not give an exact answer.
+ * In any case, space might grow in the meantime due to the
+ * creation of new queues, so we must be prepared to retry.
+ */
+ if (n > 0) {
+ buflen = 4*1024;
+ } else {
+ ret = do_cmd(-IP_DUMMYNET3, oid, (uintptr_t)&l);
+ if (ret != 0 || oid->id <= sizeof(*oid))
+ goto done;
+ buflen = oid->id + max_size;
+ oid->len = sizeof(*oid); /* restore */
+ }
+ /* Try a few times, until the buffer fits */
+ for (i = 0; i < 20; i++) {
+ l = buflen;
+ x = safe_realloc(x, l);
+ bcopy(oid, x, oid->len);
+ ret = do_cmd(-IP_DUMMYNET3, x, (uintptr_t)&l);
+ if (ret != 0 || x->id <= sizeof(*oid))
+ goto done; /* no response */
+ if (l + max_size <= buflen)
+ break; /* ok */
+ buflen *= 2; /* double for next attempt */
+ }
+ list_pipes(x, O_NEXT(x, l));
+done:
+ if (x)
+ free(x);
+ free(oid);
+}
diff --git a/sbin/ipfw/ipfw.8 b/sbin/ipfw/ipfw.8
new file mode 100644
index 0000000..63f04cf
--- /dev/null
+++ b/sbin/ipfw/ipfw.8
@@ -0,0 +1,3725 @@
+.\"
+.\" $FreeBSD$
+.\"
+.Dd March 13, 2015
+.Dt IPFW 8
+.Os
+.Sh NAME
+.Nm ipfw
+.Nd User interface for firewall, traffic shaper, packet scheduler,
+in-kernel NAT.
+.Sh SYNOPSIS
+.Ss FIREWALL CONFIGURATION
+.Nm
+.Op Fl cq
+.Cm add
+.Ar rule
+.Nm
+.Op Fl acdefnNStT
+.Op Cm set Ar N
+.Brq Cm list | show
+.Op Ar rule | first-last ...
+.Nm
+.Op Fl f | q
+.Op Cm set Ar N
+.Cm flush
+.Nm
+.Op Fl q
+.Op Cm set Ar N
+.Brq Cm delete | zero | resetlog
+.Op Ar number ...
+.Pp
+.Nm
+.Cm set Oo Cm disable Ar number ... Oc Op Cm enable Ar number ...
+.Nm
+.Cm set move
+.Op Cm rule
+.Ar number Cm to Ar number
+.Nm
+.Cm set swap Ar number number
+.Nm
+.Cm set show
+.Ss SYSCTL SHORTCUTS
+.Nm
+.Cm enable
+.Brq Cm firewall | altq | one_pass | debug | verbose | dyn_keepalive
+.Nm
+.Cm disable
+.Brq Cm firewall | altq | one_pass | debug | verbose | dyn_keepalive
+.Ss LOOKUP TABLES
+.Nm
+.Oo Cm set Ar N Oc Cm table Ar name Cm create Ar create-options
+.Nm
+.Oo Cm set Ar N Oc Cm table Ar name Cm destroy
+.Nm
+.Oo Cm set Ar N Oc Cm table Ar name Cm modify Ar modify-options
+.Nm
+.Oo Cm set Ar N Oc Cm table Ar name Cm swap Ar name
+.Nm
+.Oo Cm set Ar N Oc Cm table Ar name Cm add Ar table-key Op Ar value
+.Nm
+.Oo Cm set Ar N Oc Cm table Ar name Cm add Op Ar table-key Ar value ...
+.Nm
+.Oo Cm set Ar N Oc Cm table Ar name Cm atomic add Op Ar table-key Ar value ...
+.Nm
+.Oo Cm set Ar N Oc Cm table Ar name Cm delete Op Ar table-key ...
+.Nm
+.Oo Cm set Ar N Oc Cm table Ar name Cm lookup Ar addr
+.Nm
+.Oo Cm set Ar N Oc Cm table Ar name Cm lock
+.Nm
+.Oo Cm set Ar N Oc Cm table Ar name Cm unlock
+.Nm
+.Oo Cm set Ar N Oc Cm table
+.Brq Ar name | all
+.Cm list
+.Nm
+.Oo Cm set Ar N Oc Cm table
+.Brq Ar name | all
+.Cm info
+.Nm
+.Oo Cm set Ar N Oc Cm table
+.Brq Ar name | all
+.Cm detail
+.Nm
+.Oo Cm set Ar N Oc Cm table
+.Brq Ar name | all
+.Cm flush
+.Ss DUMMYNET CONFIGURATION (TRAFFIC SHAPER AND PACKET SCHEDULER)
+.Nm
+.Brq Cm pipe | queue | sched
+.Ar number
+.Cm config
+.Ar config-options
+.Nm
+.Op Fl s Op Ar field
+.Brq Cm pipe | queue | sched
+.Brq Cm delete | list | show
+.Op Ar number ...
+.Ss IN-KERNEL NAT
+.Nm
+.Op Fl q
+.Cm nat
+.Ar number
+.Cm config
+.Ar config-options
+.Pp
+.Nm
+.Op Fl cfnNqS
+.Oo
+.Fl p Ar preproc
+.Oo
+.Ar preproc-flags
+.Oc
+.Oc
+.Ar pathname
+.Ss INTERNAL DIAGNOSTICS
+.Nm
+.Cm internal iflist
+.Nm
+.Cm internal talist
+.Nm
+.Cm internal vlist
+.Sh DESCRIPTION
+The
+.Nm
+utility is the user interface for controlling the
+.Xr ipfw 4
+firewall, the
+.Xr dummynet 4
+traffic shaper/packet scheduler, and the
+in-kernel NAT services.
+.Pp
+A firewall configuration, or
+.Em ruleset ,
+is made of a list of
+.Em rules
+numbered from 1 to 65535.
+Packets are passed to the firewall
+from a number of different places in the protocol stack
+(depending on the source and destination of the packet,
+it is possible for the firewall to be
+invoked multiple times on the same packet).
+The packet passed to the firewall is compared
+against each of the rules in the
+.Em ruleset ,
+in rule-number order
+(multiple rules with the same number are permitted, in which case
+they are processed in order of insertion).
+When a match is found, the action corresponding to the
+matching rule is performed.
+.Pp
+Depending on the action and certain system settings, packets
+can be reinjected into the firewall at some rule after the
+matching one for further processing.
+.Pp
+A ruleset always includes a
+.Em default
+rule (numbered 65535) which cannot be modified or deleted,
+and matches all packets.
+The action associated with the
+.Em default
+rule can be either
+.Cm deny
+or
+.Cm allow
+depending on how the kernel is configured.
+.Pp
+If the ruleset includes one or more rules with the
+.Cm keep-state
+or
+.Cm limit
+option,
+the firewall will have a
+.Em stateful
+behaviour, i.e., upon a match it will create
+.Em dynamic rules ,
+i.e., rules that match packets with the same 5-tuple
+(protocol, source and destination addresses and ports)
+as the packet which caused their creation.
+Dynamic rules, which have a limited lifetime, are checked
+at the first occurrence of a
+.Cm check-state ,
+.Cm keep-state
+or
+.Cm limit
+rule, and are typically used to open the firewall on-demand to
+legitimate traffic only.
+See the
+.Sx STATEFUL FIREWALL
+and
+.Sx EXAMPLES
+Sections below for more information on the stateful behaviour of
+.Nm .
+.Pp
+All rules (including dynamic ones) have a few associated counters:
+a packet count, a byte count, a log count and a timestamp
+indicating the time of the last match.
+Counters can be displayed or reset with
+.Nm
+commands.
+.Pp
+Each rule belongs to one of 32 different
+.Em sets
+, and there are
+.Nm
+commands to atomically manipulate sets, such as enable,
+disable, swap sets, move all rules in a set to another
+one, delete all rules in a set.
+These can be useful to
+install temporary configurations, or to test them.
+See Section
+.Sx SETS OF RULES
+for more information on
+.Em sets .
+.Pp
+Rules can be added with the
+.Cm add
+command; deleted individually or in groups with the
+.Cm delete
+command, and globally (except those in set 31) with the
+.Cm flush
+command; displayed, optionally with the content of the
+counters, using the
+.Cm show
+and
+.Cm list
+commands.
+Finally, counters can be reset with the
+.Cm zero
+and
+.Cm resetlog
+commands.
+.Pp
+.Ss COMMAND OPTIONS
+The following general options are available when invoking
+.Nm :
+.Bl -tag -width indent
+.It Fl a
+Show counter values when listing rules.
+The
+.Cm show
+command implies this option.
+.It Fl b
+Only show the action and the comment, not the body of a rule.
+Implies
+.Fl c .
+.It Fl c
+When entering or showing rules, print them in compact form,
+i.e., omitting the "ip from any to any" string
+when this does not carry any additional information.
+.It Fl d
+When listing, show dynamic rules in addition to static ones.
+.It Fl e
+When listing and
+.Fl d
+is specified, also show expired dynamic rules.
+.It Fl f
+Do not ask for confirmation for commands that can cause problems
+if misused, i.e.,
+.Cm flush .
+If there is no tty associated with the process, this is implied.
+.It Fl i
+When listing a table (see the
+.Sx LOOKUP TABLES
+section below for more information on lookup tables), format values
+as IP addresses.
+By default, values are shown as integers.
+.It Fl n
+Only check syntax of the command strings, without actually passing
+them to the kernel.
+.It Fl N
+Try to resolve addresses and service names in output.
+.It Fl q
+Be quiet when executing the
+.Cm add ,
+.Cm nat ,
+.Cm zero ,
+.Cm resetlog
+or
+.Cm flush
+commands;
+(implies
+.Fl f ) .
+This is useful when updating rulesets by executing multiple
+.Nm
+commands in a script
+(e.g.,
+.Ql sh\ /etc/rc.firewall ) ,
+or by processing a file with many
+.Nm
+rules across a remote login session.
+It also stops a table add or delete
+from failing if the entry already exists or is not present.
+.Pp
+The reason why this option may be important is that
+for some of these actions,
+.Nm
+may print a message; if the action results in blocking the
+traffic to the remote client,
+the remote login session will be closed
+and the rest of the ruleset will not be processed.
+Access to the console would then be required to recover.
+.It Fl S
+When listing rules, show the
+.Em set
+each rule belongs to.
+If this flag is not specified, disabled rules will not be
+listed.
+.It Fl s Op Ar field
+When listing pipes, sort according to one of the four
+counters (total or current packets or bytes).
+.It Fl t
+When listing, show last match timestamp converted with ctime().
+.It Fl T
+When listing, show last match timestamp as seconds from the epoch.
+This form can be more convenient for postprocessing by scripts.
+.El
+.Ss LIST OF RULES AND PREPROCESSING
+To ease configuration, rules can be put into a file which is
+processed using
+.Nm
+as shown in the last synopsis line.
+An absolute
+.Ar pathname
+must be used.
+The file will be read line by line and applied as arguments to the
+.Nm
+utility.
+.Pp
+Optionally, a preprocessor can be specified using
+.Fl p Ar preproc
+where
+.Ar pathname
+is to be piped through.
+Useful preprocessors include
+.Xr cpp 1
+and
+.Xr m4 1 .
+If
+.Ar preproc
+does not start with a slash
+.Pq Ql /
+as its first character, the usual
+.Ev PATH
+name search is performed.
+Care should be taken with this in environments where not all
+file systems are mounted (yet) by the time
+.Nm
+is being run (e.g.\& when they are mounted over NFS).
+Once
+.Fl p
+has been specified, any additional arguments are passed on to the preprocessor
+for interpretation.
+This allows for flexible configuration files (like conditionalizing
+them on the local hostname) and the use of macros to centralize
+frequently required arguments like IP addresses.
+.Ss TRAFFIC SHAPER CONFIGURATION
+The
+.Nm
+.Cm pipe , queue
+and
+.Cm sched
+commands are used to configure the traffic shaper and packet scheduler.
+See the
+.Sx TRAFFIC SHAPER (DUMMYNET) CONFIGURATION
+Section below for details.
+.Pp
+If the world and the kernel get out of sync the
+.Nm
+ABI may break, preventing you from being able to add any rules.
+This can adversely affect the booting process.
+You can use
+.Nm
+.Cm disable
+.Cm firewall
+to temporarily disable the firewall to regain access to the network,
+allowing you to fix the problem.
+.Sh PACKET FLOW
+A packet is checked against the active ruleset in multiple places
+in the protocol stack, under control of several sysctl variables.
+These places and variables are shown below, and it is important to
+have this picture in mind in order to design a correct ruleset.
+.Bd -literal -offset indent
+ ^ to upper layers V
+ | |
+ +----------->-----------+
+ ^ V
+ [ip(6)_input] [ip(6)_output] net.inet(6).ip(6).fw.enable=1
+ | |
+ ^ V
+ [ether_demux] [ether_output_frame] net.link.ether.ipfw=1
+ | |
+ +-->--[bdg_forward]-->--+ net.link.bridge.ipfw=1
+ ^ V
+ | to devices |
+.Ed
+.Pp
+The number of
+times the same packet goes through the firewall can
+vary between 0 and 4 depending on packet source and
+destination, and system configuration.
+.Pp
+Note that as packets flow through the stack, headers can be
+stripped or added to it, and so they may or may not be available
+for inspection.
+E.g., incoming packets will include the MAC header when
+.Nm
+is invoked from
+.Cm ether_demux() ,
+but the same packets will have the MAC header stripped off when
+.Nm
+is invoked from
+.Cm ip_input()
+or
+.Cm ip6_input() .
+.Pp
+Also note that each packet is always checked against the complete ruleset,
+irrespective of the place where the check occurs, or the source of the packet.
+If a rule contains some match patterns or actions which are not valid
+for the place of invocation (e.g.\& trying to match a MAC header within
+.Cm ip_input
+or
+.Cm ip6_input ),
+the match pattern will not match, but a
+.Cm not
+operator in front of such patterns
+.Em will
+cause the pattern to
+.Em always
+match on those packets.
+It is thus the responsibility of
+the programmer, if necessary, to write a suitable ruleset to
+differentiate among the possible places.
+.Cm skipto
+rules can be useful here, as an example:
+.Bd -literal -offset indent
+# packets from ether_demux or bdg_forward
+ipfw add 10 skipto 1000 all from any to any layer2 in
+# packets from ip_input
+ipfw add 10 skipto 2000 all from any to any not layer2 in
+# packets from ip_output
+ipfw add 10 skipto 3000 all from any to any not layer2 out
+# packets from ether_output_frame
+ipfw add 10 skipto 4000 all from any to any layer2 out
+.Ed
+.Pp
+(yes, at the moment there is no way to differentiate between
+ether_demux and bdg_forward).
+.Sh SYNTAX
+In general, each keyword or argument must be provided as
+a separate command line argument, with no leading or trailing
+spaces.
+Keywords are case-sensitive, whereas arguments may
+or may not be case-sensitive depending on their nature
+(e.g.\& uid's are, hostnames are not).
+.Pp
+Some arguments (e.g., port or address lists) are comma-separated
+lists of values.
+In this case, spaces after commas ',' are allowed to make
+the line more readable.
+You can also put the entire
+command (including flags) into a single argument.
+E.g., the following forms are equivalent:
+.Bd -literal -offset indent
+ipfw -q add deny src-ip 10.0.0.0/24,127.0.0.1/8
+ipfw -q add deny src-ip 10.0.0.0/24, 127.0.0.1/8
+ipfw "-q add deny src-ip 10.0.0.0/24, 127.0.0.1/8"
+.Ed
+.Sh RULE FORMAT
+The format of firewall rules is the following:
+.Bd -ragged -offset indent
+.Bk -words
+.Op Ar rule_number
+.Op Cm set Ar set_number
+.Op Cm prob Ar match_probability
+.Ar action
+.Op Cm log Op Cm logamount Ar number
+.Op Cm altq Ar queue
+.Oo
+.Bro Cm tag | untag
+.Brc Ar number
+.Oc
+.Ar body
+.Ek
+.Ed
+.Pp
+where the body of the rule specifies which information is used
+for filtering packets, among the following:
+.Pp
+.Bl -tag -width "Source and dest. addresses and ports" -offset XXX -compact
+.It Layer-2 header fields
+When available
+.It IPv4 and IPv6 Protocol
+TCP, UDP, ICMP, etc.
+.It Source and dest. addresses and ports
+.It Direction
+See Section
+.Sx PACKET FLOW
+.It Transmit and receive interface
+By name or address
+.It Misc. IP header fields
+Version, type of service, datagram length, identification,
+fragment flag (non-zero IP offset),
+Time To Live
+.It IP options
+.It IPv6 Extension headers
+Fragmentation, Hop-by-Hop options,
+Routing Headers, Source routing rthdr0, Mobile IPv6 rthdr2, IPSec options.
+.It IPv6 Flow-ID
+.It Misc. TCP header fields
+TCP flags (SYN, FIN, ACK, RST, etc.),
+sequence number, acknowledgment number,
+window
+.It TCP options
+.It ICMP types
+for ICMP packets
+.It ICMP6 types
+for ICMP6 packets
+.It User/group ID
+When the packet can be associated with a local socket.
+.It Divert status
+Whether a packet came from a divert socket (e.g.,
+.Xr natd 8 ) .
+.It Fib annotation state
+Whether a packet has been tagged for using a specific FIB (routing table)
+in future forwarding decisions.
+.El
+.Pp
+Note that some of the above information, e.g.\& source MAC or IP addresses and
+TCP/UDP ports, can be easily spoofed, so filtering on those fields
+alone might not guarantee the desired results.
+.Bl -tag -width indent
+.It Ar rule_number
+Each rule is associated with a
+.Ar rule_number
+in the range 1..65535, with the latter reserved for the
+.Em default
+rule.
+Rules are checked sequentially by rule number.
+Multiple rules can have the same number, in which case they are
+checked (and listed) according to the order in which they have
+been added.
+If a rule is entered without specifying a number, the kernel will
+assign one in such a way that the rule becomes the last one
+before the
+.Em default
+rule.
+Automatic rule numbers are assigned by incrementing the last
+non-default rule number by the value of the sysctl variable
+.Ar net.inet.ip.fw.autoinc_step
+which defaults to 100.
+If this is not possible (e.g.\& because we would go beyond the
+maximum allowed rule number), the number of the last
+non-default value is used instead.
+.It Cm set Ar set_number
+Each rule is associated with a
+.Ar set_number
+in the range 0..31.
+Sets can be individually disabled and enabled, so this parameter
+is of fundamental importance for atomic ruleset manipulation.
+It can be also used to simplify deletion of groups of rules.
+If a rule is entered without specifying a set number,
+set 0 will be used.
+.br
+Set 31 is special in that it cannot be disabled,
+and rules in set 31 are not deleted by the
+.Nm ipfw flush
+command (but you can delete them with the
+.Nm ipfw delete set 31
+command).
+Set 31 is also used for the
+.Em default
+rule.
+.It Cm prob Ar match_probability
+A match is only declared with the specified probability
+(floating point number between 0 and 1).
+This can be useful for a number of applications such as
+random packet drop or
+(in conjunction with
+.Nm dummynet )
+to simulate the effect of multiple paths leading to out-of-order
+packet delivery.
+.Pp
+Note: this condition is checked before any other condition, including
+ones such as keep-state or check-state which might have side effects.
+.It Cm log Op Cm logamount Ar number
+Packets matching a rule with the
+.Cm log
+keyword will be made available for logging in two ways:
+if the sysctl variable
+.Va net.inet.ip.fw.verbose
+is set to 0 (default), one can use
+.Xr bpf 4
+attached to the
+.Li ipfw0
+pseudo interface.
+This pseudo interface can be created after a boot
+manually by using the following command:
+.Bd -literal -offset indent
+# ifconfig ipfw0 create
+.Ed
+.Pp
+Or, automatically at boot time by adding the following
+line to the
+.Xr rc.conf 5
+file:
+.Bd -literal -offset indent
+firewall_logif="YES"
+.Ed
+.Pp
+There is no overhead if no
+.Xr bpf 4
+is attached to the pseudo interface.
+.Pp
+If
+.Va net.inet.ip.fw.verbose
+is set to 1, packets will be logged to
+.Xr syslogd 8
+with a
+.Dv LOG_SECURITY
+facility up to a maximum of
+.Cm logamount
+packets.
+If no
+.Cm logamount
+is specified, the limit is taken from the sysctl variable
+.Va net.inet.ip.fw.verbose_limit .
+In both cases, a value of 0 means unlimited logging.
+.Pp
+Once the limit is reached, logging can be re-enabled by
+clearing the logging counter or the packet counter for that entry, see the
+.Cm resetlog
+command.
+.Pp
+Note: logging is done after all other packet matching conditions
+have been successfully verified, and before performing the final
+action (accept, deny, etc.) on the packet.
+.It Cm tag Ar number
+When a packet matches a rule with the
+.Cm tag
+keyword, the numeric tag for the given
+.Ar number
+in the range 1..65534 will be attached to the packet.
+The tag acts as an internal marker (it is not sent out over
+the wire) that can be used to identify these packets later on.
+This can be used, for example, to provide trust between interfaces
+and to start doing policy-based filtering.
+A packet can have multiple tags at the same time.
+Tags are "sticky", meaning once a tag is applied to a packet by a
+matching rule it exists until explicit removal.
+Tags are kept with the packet everywhere within the kernel, but are
+lost when packet leaves the kernel, for example, on transmitting
+packet out to the network or sending packet to a
+.Xr divert 4
+socket.
+.Pp
+To check for previously applied tags, use the
+.Cm tagged
+rule option.
+To delete previously applied tag, use the
+.Cm untag
+keyword.
+.Pp
+Note: since tags are kept with the packet everywhere in kernelspace,
+they can be set and unset anywhere in the kernel network subsystem
+(using the
+.Xr mbuf_tags 9
+facility), not only by means of the
+.Xr ipfw 4
+.Cm tag
+and
+.Cm untag
+keywords.
+For example, there can be a specialized
+.Xr netgraph 4
+node doing traffic analyzing and tagging for later inspecting
+in firewall.
+.It Cm untag Ar number
+When a packet matches a rule with the
+.Cm untag
+keyword, the tag with the number
+.Ar number
+is searched among the tags attached to this packet and,
+if found, removed from it.
+Other tags bound to packet, if present, are left untouched.
+.It Cm altq Ar queue
+When a packet matches a rule with the
+.Cm altq
+keyword, the ALTQ identifier for the given
+.Ar queue
+(see
+.Xr altq 4 )
+will be attached.
+Note that this ALTQ tag is only meaningful for packets going "out" of IPFW,
+and not being rejected or going to divert sockets.
+Note that if there is insufficient memory at the time the packet is
+processed, it will not be tagged, so it is wise to make your ALTQ
+"default" queue policy account for this.
+If multiple
+.Cm altq
+rules match a single packet, only the first one adds the ALTQ classification
+tag.
+In doing so, traffic may be shaped by using
+.Cm count Cm altq Ar queue
+rules for classification early in the ruleset, then later applying
+the filtering decision.
+For example,
+.Cm check-state
+and
+.Cm keep-state
+rules may come later and provide the actual filtering decisions in
+addition to the fallback ALTQ tag.
+.Pp
+You must run
+.Xr pfctl 8
+to set up the queues before IPFW will be able to look them up by name,
+and if the ALTQ disciplines are rearranged, the rules in containing the
+queue identifiers in the kernel will likely have gone stale and need
+to be reloaded.
+Stale queue identifiers will probably result in misclassification.
+.Pp
+All system ALTQ processing can be turned on or off via
+.Nm
+.Cm enable Ar altq
+and
+.Nm
+.Cm disable Ar altq .
+The usage of
+.Va net.inet.ip.fw.one_pass
+is irrelevant to ALTQ traffic shaping, as the actual rule action is followed
+always after adding an ALTQ tag.
+.El
+.Ss RULE ACTIONS
+A rule can be associated with one of the following actions, which
+will be executed when the packet matches the body of the rule.
+.Bl -tag -width indent
+.It Cm allow | accept | pass | permit
+Allow packets that match rule.
+The search terminates.
+.It Cm check-state
+Checks the packet against the dynamic ruleset.
+If a match is found, execute the action associated with
+the rule which generated this dynamic rule, otherwise
+move to the next rule.
+.br
+.Cm Check-state
+rules do not have a body.
+If no
+.Cm check-state
+rule is found, the dynamic ruleset is checked at the first
+.Cm keep-state
+or
+.Cm limit
+rule.
+.It Cm count
+Update counters for all packets that match rule.
+The search continues with the next rule.
+.It Cm deny | drop
+Discard packets that match this rule.
+The search terminates.
+.It Cm divert Ar port
+Divert packets that match this rule to the
+.Xr divert 4
+socket bound to port
+.Ar port .
+The search terminates.
+.It Cm fwd | forward Ar ipaddr | tablearg Ns Op , Ns Ar port
+Change the next-hop on matching packets to
+.Ar ipaddr ,
+which can be an IP address or a host name.
+For IPv4, the next hop can also be supplied by the last table
+looked up for the packet by using the
+.Cm tablearg
+keyword instead of an explicit address.
+The search terminates if this rule matches.
+.Pp
+If
+.Ar ipaddr
+is a local address, then matching packets will be forwarded to
+.Ar port
+(or the port number in the packet if one is not specified in the rule)
+on the local machine.
+.br
+If
+.Ar ipaddr
+is not a local address, then the port number
+(if specified) is ignored, and the packet will be
+forwarded to the remote address, using the route as found in
+the local routing table for that IP.
+.br
+A
+.Ar fwd
+rule will not match layer-2 packets (those received
+on ether_input, ether_output, or bridged).
+.br
+The
+.Cm fwd
+action does not change the contents of the packet at all.
+In particular, the destination address remains unmodified, so
+packets forwarded to another system will usually be rejected by that system
+unless there is a matching rule on that system to capture them.
+For packets forwarded locally,
+the local address of the socket will be
+set to the original destination address of the packet.
+This makes the
+.Xr netstat 1
+entry look rather weird but is intended for
+use with transparent proxy servers.
+.It Cm nat Ar nat_nr | tablearg
+Pass packet to a
+nat instance
+(for network address translation, address redirect, etc.):
+see the
+.Sx NETWORK ADDRESS TRANSLATION (NAT)
+Section for further information.
+.It Cm pipe Ar pipe_nr
+Pass packet to a
+.Nm dummynet
+.Dq pipe
+(for bandwidth limitation, delay, etc.).
+See the
+.Sx TRAFFIC SHAPER (DUMMYNET) CONFIGURATION
+Section for further information.
+The search terminates; however, on exit from the pipe and if
+the
+.Xr sysctl 8
+variable
+.Va net.inet.ip.fw.one_pass
+is not set, the packet is passed again to the firewall code
+starting from the next rule.
+.It Cm queue Ar queue_nr
+Pass packet to a
+.Nm dummynet
+.Dq queue
+(for bandwidth limitation using WF2Q+).
+.It Cm reject
+(Deprecated).
+Synonym for
+.Cm unreach host .
+.It Cm reset
+Discard packets that match this rule, and if the
+packet is a TCP packet, try to send a TCP reset (RST) notice.
+The search terminates.
+.It Cm reset6
+Discard packets that match this rule, and if the
+packet is a TCP packet, try to send a TCP reset (RST) notice.
+The search terminates.
+.It Cm skipto Ar number | tablearg
+Skip all subsequent rules numbered less than
+.Ar number .
+The search continues with the first rule numbered
+.Ar number
+or higher.
+It is possible to use the
+.Cm tablearg
+keyword with a skipto for a
+.Em computed
+skipto. Skipto may work either in O(log(N)) or in O(1) depending
+on amount of memory and/or sysctl variables.
+See the
+.Sx SYSCTL VARIABLES
+section for more details.
+.It Cm call Ar number | tablearg
+The current rule number is saved in the internal stack and
+ruleset processing continues with the first rule numbered
+.Ar number
+or higher.
+If later a rule with the
+.Cm return
+action is encountered, the processing returns to the first rule
+with number of this
+.Cm call
+rule plus one or higher
+(the same behaviour as with packets returning from
+.Xr divert 4
+socket after a
+.Cm divert
+action).
+This could be used to make somewhat like an assembly language
+.Dq subroutine
+calls to rules with common checks for different interfaces, etc.
+.Pp
+Rule with any number could be called, not just forward jumps as with
+.Cm skipto .
+So, to prevent endless loops in case of mistakes, both
+.Cm call
+and
+.Cm return
+actions don't do any jumps and simply go to the next rule if memory
+cannot be allocated or stack overflowed/underflowed.
+.Pp
+Internally stack for rule numbers is implemented using
+.Xr mbuf_tags 9
+facility and currently has size of 16 entries.
+As mbuf tags are lost when packet leaves the kernel,
+.Cm divert
+should not be used in subroutines to avoid endless loops
+and other undesired effects.
+.It Cm return
+Takes rule number saved to internal stack by the last
+.Cm call
+action and returns ruleset processing to the first rule
+with number greater than number of corresponding
+.Cm call
+rule.
+See description of the
+.Cm call
+action for more details.
+.Pp
+Note that
+.Cm return
+rules usually end a
+.Dq subroutine
+and thus are unconditional, but
+.Nm
+command-line utility currently requires every action except
+.Cm check-state
+to have body.
+While it is sometimes useful to return only on some packets,
+usually you want to print just
+.Dq return
+for readability.
+A workaround for this is to use new syntax and
+.Fl c
+switch:
+.Bd -literal -offset indent
+# Add a rule without actual body
+ipfw add 2999 return via any
+
+# List rules without "from any to any" part
+ipfw -c list
+.Ed
+.Pp
+This cosmetic annoyance may be fixed in future releases.
+.It Cm tee Ar port
+Send a copy of packets matching this rule to the
+.Xr divert 4
+socket bound to port
+.Ar port .
+The search continues with the next rule.
+.It Cm unreach Ar code
+Discard packets that match this rule, and try to send an ICMP
+unreachable notice with code
+.Ar code ,
+where
+.Ar code
+is a number from 0 to 255, or one of these aliases:
+.Cm net , host , protocol , port ,
+.Cm needfrag , srcfail , net-unknown , host-unknown ,
+.Cm isolated , net-prohib , host-prohib , tosnet ,
+.Cm toshost , filter-prohib , host-precedence
+or
+.Cm precedence-cutoff .
+The search terminates.
+.It Cm unreach6 Ar code
+Discard packets that match this rule, and try to send an ICMPv6
+unreachable notice with code
+.Ar code ,
+where
+.Ar code
+is a number from 0, 1, 3 or 4, or one of these aliases:
+.Cm no-route, admin-prohib, address
+or
+.Cm port .
+The search terminates.
+.It Cm netgraph Ar cookie
+Divert packet into netgraph with given
+.Ar cookie .
+The search terminates.
+If packet is later returned from netgraph it is either
+accepted or continues with the next rule, depending on
+.Va net.inet.ip.fw.one_pass
+sysctl variable.
+.It Cm ngtee Ar cookie
+A copy of packet is diverted into netgraph, original
+packet continues with the next rule.
+See
+.Xr ng_ipfw 4
+for more information on
+.Cm netgraph
+and
+.Cm ngtee
+actions.
+.It Cm setfib Ar fibnum | tablearg
+The packet is tagged so as to use the FIB (routing table)
+.Ar fibnum
+in any subsequent forwarding decisions.
+In the current implementation, this is limited to the values 0 through 15, see
+.Xr setfib 2 .
+Processing continues at the next rule.
+It is possible to use the
+.Cm tablearg
+keyword with setfib.
+If the tablearg value is not within the compiled range of fibs,
+the packet's fib is set to 0.
+.It Cm setdscp Ar DSCP | number | tablearg
+Set specified DiffServ codepoint for an IPv4/IPv6 packet.
+Processing continues at the next rule.
+Supported values are:
+.Pp
+.Cm CS0
+.Pq Dv 000000 ,
+.Cm CS1
+.Pq Dv 001000 ,
+.Cm CS2
+.Pq Dv 010000 ,
+.Cm CS3
+.Pq Dv 011000 ,
+.Cm CS4
+.Pq Dv 100000 ,
+.Cm CS5
+.Pq Dv 101000 ,
+.Cm CS6
+.Pq Dv 110000 ,
+.Cm CS7
+.Pq Dv 111000 ,
+.Cm AF11
+.Pq Dv 001010 ,
+.Cm AF12
+.Pq Dv 001100 ,
+.Cm AF13
+.Pq Dv 001110 ,
+.Cm AF21
+.Pq Dv 010010 ,
+.Cm AF22
+.Pq Dv 010100 ,
+.Cm AF23
+.Pq Dv 010110 ,
+.Cm AF31
+.Pq Dv 011010 ,
+.Cm AF32
+.Pq Dv 011100 ,
+.Cm AF33
+.Pq Dv 011110 ,
+.Cm AF41
+.Pq Dv 100010 ,
+.Cm AF42
+.Pq Dv 100100 ,
+.Cm AF43
+.Pq Dv 100110 ,
+.Cm EF
+.Pq Dv 101110 ,
+.Cm BE
+.Pq Dv 000000 .
+Additionally, DSCP value can be specified by number (0..64).
+It is also possible to use the
+.Cm tablearg
+keyword with setdscp.
+If the tablearg value is not within the 0..64 range, lower 6 bits of supplied
+value are used.
+.It Cm reass
+Queue and reassemble IP fragments.
+If the packet is not fragmented, counters are updated and
+processing continues with the next rule.
+If the packet is the last logical fragment, the packet is reassembled and, if
+.Va net.inet.ip.fw.one_pass
+is set to 0, processing continues with the next rule.
+Otherwise, the packet is allowed to pass and the search terminates.
+If the packet is a fragment in the middle of a logical group of fragments,
+it is consumed and
+processing stops immediately.
+.Pp
+Fragment handling can be tuned via
+.Va net.inet.ip.maxfragpackets
+and
+.Va net.inet.ip.maxfragsperpacket
+which limit, respectively, the maximum number of processable
+fragments (default: 800) and
+the maximum number of fragments per packet (default: 16).
+.Pp
+NOTA BENE: since fragments do not contain port numbers,
+they should be avoided with the
+.Nm reass
+rule.
+Alternatively, direction-based (like
+.Nm in
+/
+.Nm out
+) and source-based (like
+.Nm via
+) match patterns can be used to select fragments.
+.Pp
+Usually a simple rule like:
+.Bd -literal -offset indent
+# reassemble incoming fragments
+ipfw add reass all from any to any in
+.Ed
+.Pp
+is all you need at the beginning of your ruleset.
+.El
+.Ss RULE BODY
+The body of a rule contains zero or more patterns (such as
+specific source and destination addresses or ports,
+protocol options, incoming or outgoing interfaces, etc.)
+that the packet must match in order to be recognised.
+In general, the patterns are connected by (implicit)
+.Cm and
+operators -- i.e., all must match in order for the
+rule to match.
+Individual patterns can be prefixed by the
+.Cm not
+operator to reverse the result of the match, as in
+.Pp
+.Dl "ipfw add 100 allow ip from not 1.2.3.4 to any"
+.Pp
+Additionally, sets of alternative match patterns
+.Pq Em or-blocks
+can be constructed by putting the patterns in
+lists enclosed between parentheses ( ) or braces { }, and
+using the
+.Cm or
+operator as follows:
+.Pp
+.Dl "ipfw add 100 allow ip from { x or not y or z } to any"
+.Pp
+Only one level of parentheses is allowed.
+Beware that most shells have special meanings for parentheses
+or braces, so it is advisable to put a backslash \\ in front of them
+to prevent such interpretations.
+.Pp
+The body of a rule must in general include a source and destination
+address specifier.
+The keyword
+.Ar any
+can be used in various places to specify that the content of
+a required field is irrelevant.
+.Pp
+The rule body has the following format:
+.Bd -ragged -offset indent
+.Op Ar proto Cm from Ar src Cm to Ar dst
+.Op Ar options
+.Ed
+.Pp
+The first part (proto from src to dst) is for backward
+compatibility with earlier versions of
+.Fx .
+In modern
+.Fx
+any match pattern (including MAC headers, IP protocols,
+addresses and ports) can be specified in the
+.Ar options
+section.
+.Pp
+Rule fields have the following meaning:
+.Bl -tag -width indent
+.It Ar proto : protocol | Cm { Ar protocol Cm or ... }
+.It Ar protocol : Oo Cm not Oc Ar protocol-name | protocol-number
+An IP protocol specified by number or name
+(for a complete list see
+.Pa /etc/protocols ) ,
+or one of the following keywords:
+.Bl -tag -width indent
+.It Cm ip4 | ipv4
+Matches IPv4 packets.
+.It Cm ip6 | ipv6
+Matches IPv6 packets.
+.It Cm ip | all
+Matches any packet.
+.El
+.Pp
+The
+.Cm ipv6
+in
+.Cm proto
+option will be treated as inner protocol.
+And, the
+.Cm ipv4
+is not available in
+.Cm proto
+option.
+.Pp
+The
+.Cm { Ar protocol Cm or ... }
+format (an
+.Em or-block )
+is provided for convenience only but its use is deprecated.
+.It Ar src No and Ar dst : Bro Cm addr | Cm { Ar addr Cm or ... } Brc Op Oo Cm not Oc Ar ports
+An address (or a list, see below)
+optionally followed by
+.Ar ports
+specifiers.
+.Pp
+The second format
+.Em ( or-block
+with multiple addresses) is provided for convenience only and
+its use is discouraged.
+.It Ar addr : Oo Cm not Oc Bro
+.Cm any | me | me6 |
+.Cm table Ns Pq Ar name Ns Op , Ns Ar value
+.Ar | addr-list | addr-set
+.Brc
+.Bl -tag -width indent
+.It Cm any
+matches any IP address.
+.It Cm me
+matches any IP address configured on an interface in the system.
+.It Cm me6
+matches any IPv6 address configured on an interface in the system.
+The address list is evaluated at the time the packet is
+analysed.
+.It Cm table Ns Pq Ar name Ns Op , Ns Ar value
+Matches any IPv4 or IPv6 address for which an entry exists in the lookup table
+.Ar number .
+If an optional 32-bit unsigned
+.Ar value
+is also specified, an entry will match only if it has this value.
+See the
+.Sx LOOKUP TABLES
+section below for more information on lookup tables.
+.El
+.It Ar addr-list : ip-addr Ns Op Ns , Ns Ar addr-list
+.It Ar ip-addr :
+A host or subnet address specified in one of the following ways:
+.Bl -tag -width indent
+.It Ar numeric-ip | hostname
+Matches a single IPv4 address, specified as dotted-quad or a hostname.
+Hostnames are resolved at the time the rule is added to the firewall list.
+.It Ar addr Ns / Ns Ar masklen
+Matches all addresses with base
+.Ar addr
+(specified as an IP address, a network number, or a hostname)
+and mask width of
+.Cm masklen
+bits.
+As an example, 1.2.3.4/25 or 1.2.3.0/25 will match
+all IP numbers from 1.2.3.0 to 1.2.3.127 .
+.It Ar addr Ns : Ns Ar mask
+Matches all addresses with base
+.Ar addr
+(specified as an IP address, a network number, or a hostname)
+and the mask of
+.Ar mask ,
+specified as a dotted quad.
+As an example, 1.2.3.4:255.0.255.0 or 1.0.3.0:255.0.255.0 will match
+1.*.3.*.
+This form is advised only for non-contiguous
+masks.
+It is better to resort to the
+.Ar addr Ns / Ns Ar masklen
+format for contiguous masks, which is more compact and less
+error-prone.
+.El
+.It Ar addr-set : addr Ns Oo Ns / Ns Ar masklen Oc Ns Cm { Ns Ar list Ns Cm }
+.It Ar list : Bro Ar num | num-num Brc Ns Op Ns , Ns Ar list
+Matches all addresses with base address
+.Ar addr
+(specified as an IP address, a network number, or a hostname)
+and whose last byte is in the list between braces { } .
+Note that there must be no spaces between braces and
+numbers (spaces after commas are allowed).
+Elements of the list can be specified as single entries
+or ranges.
+The
+.Ar masklen
+field is used to limit the size of the set of addresses,
+and can have any value between 24 and 32.
+If not specified,
+it will be assumed as 24.
+.br
+This format is particularly useful to handle sparse address sets
+within a single rule.
+Because the matching occurs using a
+bitmask, it takes constant time and dramatically reduces
+the complexity of rulesets.
+.br
+As an example, an address specified as 1.2.3.4/24{128,35-55,89}
+or 1.2.3.0/24{128,35-55,89}
+will match the following IP addresses:
+.br
+1.2.3.128, 1.2.3.35 to 1.2.3.55, 1.2.3.89 .
+.It Ar addr6-list : ip6-addr Ns Op Ns , Ns Ar addr6-list
+.It Ar ip6-addr :
+A host or subnet specified one of the following ways:
+.Bl -tag -width indent
+.It Ar numeric-ip | hostname
+Matches a single IPv6 address as allowed by
+.Xr inet_pton 3
+or a hostname.
+Hostnames are resolved at the time the rule is added to the firewall
+list.
+.It Ar addr Ns / Ns Ar masklen
+Matches all IPv6 addresses with base
+.Ar addr
+(specified as allowed by
+.Xr inet_pton
+or a hostname)
+and mask width of
+.Cm masklen
+bits.
+.El
+.Pp
+No support for sets of IPv6 addresses is provided because IPv6 addresses
+are typically random past the initial prefix.
+.It Ar ports : Bro Ar port | port Ns \&- Ns Ar port Ns Brc Ns Op , Ns Ar ports
+For protocols which support port numbers (such as TCP and UDP), optional
+.Cm ports
+may be specified as one or more ports or port ranges, separated
+by commas but no spaces, and an optional
+.Cm not
+operator.
+The
+.Ql \&-
+notation specifies a range of ports (including boundaries).
+.Pp
+Service names (from
+.Pa /etc/services )
+may be used instead of numeric port values.
+The length of the port list is limited to 30 ports or ranges,
+though one can specify larger ranges by using an
+.Em or-block
+in the
+.Cm options
+section of the rule.
+.Pp
+A backslash
+.Pq Ql \e
+can be used to escape the dash
+.Pq Ql -
+character in a service name (from a shell, the backslash must be
+typed twice to avoid the shell itself interpreting it as an escape
+character).
+.Pp
+.Dl "ipfw add count tcp from any ftp\e\e-data-ftp to any"
+.Pp
+Fragmented packets which have a non-zero offset (i.e., not the first
+fragment) will never match a rule which has one or more port
+specifications.
+See the
+.Cm frag
+option for details on matching fragmented packets.
+.El
+.Ss RULE OPTIONS (MATCH PATTERNS)
+Additional match patterns can be used within
+rules.
+Zero or more of these so-called
+.Em options
+can be present in a rule, optionally prefixed by the
+.Cm not
+operand, and possibly grouped into
+.Em or-blocks .
+.Pp
+The following match patterns can be used (listed in alphabetical order):
+.Bl -tag -width indent
+.It Cm // this is a comment.
+Inserts the specified text as a comment in the rule.
+Everything following // is considered as a comment and stored in the rule.
+You can have comment-only rules, which are listed as having a
+.Cm count
+action followed by the comment.
+.It Cm bridged
+Alias for
+.Cm layer2 .
+.It Cm diverted
+Matches only packets generated by a divert socket.
+.It Cm diverted-loopback
+Matches only packets coming from a divert socket back into the IP stack
+input for delivery.
+.It Cm diverted-output
+Matches only packets going from a divert socket back outward to the IP
+stack output for delivery.
+.It Cm dst-ip Ar ip-address
+Matches IPv4 packets whose destination IP is one of the address(es)
+specified as argument.
+.It Bro Cm dst-ip6 | dst-ipv6 Brc Ar ip6-address
+Matches IPv6 packets whose destination IP is one of the address(es)
+specified as argument.
+.It Cm dst-port Ar ports
+Matches IP packets whose destination port is one of the port(s)
+specified as argument.
+.It Cm established
+Matches TCP packets that have the RST or ACK bits set.
+.It Cm ext6hdr Ar header
+Matches IPv6 packets containing the extended header given by
+.Ar header .
+Supported headers are:
+.Pp
+Fragment,
+.Pq Cm frag ,
+Hop-to-hop options
+.Pq Cm hopopt ,
+any type of Routing Header
+.Pq Cm route ,
+Source routing Routing Header Type 0
+.Pq Cm rthdr0 ,
+Mobile IPv6 Routing Header Type 2
+.Pq Cm rthdr2 ,
+Destination options
+.Pq Cm dstopt ,
+IPSec authentication headers
+.Pq Cm ah ,
+and IPsec encapsulated security payload headers
+.Pq Cm esp .
+.It Cm fib Ar fibnum
+Matches a packet that has been tagged to use
+the given FIB (routing table) number.
+.It Cm flow Ar table Ns Pq Ar name Ns Op , Ns Ar value
+Search for the flow entry in lookup table
+.Ar name .
+If not found, the match fails.
+Otherwise, the match succeeds and
+.Cm tablearg
+is set to the value extracted from the table.
+.Pp
+This option can be useful to quickly dispatch traffic based on
+certain packet fields.
+See the
+.Sx LOOKUP TABLES
+section below for more information on lookup tables.
+.It Cm flow-id Ar labels
+Matches IPv6 packets containing any of the flow labels given in
+.Ar labels .
+.Ar labels
+is a comma separated list of numeric flow labels.
+.It Cm frag
+Matches packets that are fragments and not the first
+fragment of an IP datagram.
+Note that these packets will not have
+the next protocol header (e.g.\& TCP, UDP) so options that look into
+these headers cannot match.
+.It Cm gid Ar group
+Matches all TCP or UDP packets sent by or received for a
+.Ar group .
+A
+.Ar group
+may be specified by name or number.
+.It Cm jail Ar prisonID
+Matches all TCP or UDP packets sent by or received for the
+jail whos prison ID is
+.Ar prisonID .
+.It Cm icmptypes Ar types
+Matches ICMP packets whose ICMP type is in the list
+.Ar types .
+The list may be specified as any combination of
+individual types (numeric) separated by commas.
+.Em Ranges are not allowed .
+The supported ICMP types are:
+.Pp
+echo reply
+.Pq Cm 0 ,
+destination unreachable
+.Pq Cm 3 ,
+source quench
+.Pq Cm 4 ,
+redirect
+.Pq Cm 5 ,
+echo request
+.Pq Cm 8 ,
+router advertisement
+.Pq Cm 9 ,
+router solicitation
+.Pq Cm 10 ,
+time-to-live exceeded
+.Pq Cm 11 ,
+IP header bad
+.Pq Cm 12 ,
+timestamp request
+.Pq Cm 13 ,
+timestamp reply
+.Pq Cm 14 ,
+information request
+.Pq Cm 15 ,
+information reply
+.Pq Cm 16 ,
+address mask request
+.Pq Cm 17
+and address mask reply
+.Pq Cm 18 .
+.It Cm icmp6types Ar types
+Matches ICMP6 packets whose ICMP6 type is in the list of
+.Ar types .
+The list may be specified as any combination of
+individual types (numeric) separated by commas.
+.Em Ranges are not allowed .
+.It Cm in | out
+Matches incoming or outgoing packets, respectively.
+.Cm in
+and
+.Cm out
+are mutually exclusive (in fact,
+.Cm out
+is implemented as
+.Cm not in Ns No ).
+.It Cm ipid Ar id-list
+Matches IPv4 packets whose
+.Cm ip_id
+field has value included in
+.Ar id-list ,
+which is either a single value or a list of values or ranges
+specified in the same way as
+.Ar ports .
+.It Cm iplen Ar len-list
+Matches IP packets whose total length, including header and data, is
+in the set
+.Ar len-list ,
+which is either a single value or a list of values or ranges
+specified in the same way as
+.Ar ports .
+.It Cm ipoptions Ar spec
+Matches packets whose IPv4 header contains the comma separated list of
+options specified in
+.Ar spec .
+The supported IP options are:
+.Pp
+.Cm ssrr
+(strict source route),
+.Cm lsrr
+(loose source route),
+.Cm rr
+(record packet route) and
+.Cm ts
+(timestamp).
+The absence of a particular option may be denoted
+with a
+.Ql \&! .
+.It Cm ipprecedence Ar precedence
+Matches IPv4 packets whose precedence field is equal to
+.Ar precedence .
+.It Cm ipsec
+Matches packets that have IPSEC history associated with them
+(i.e., the packet comes encapsulated in IPSEC, the kernel
+has IPSEC support and IPSEC_FILTERTUNNEL option, and can correctly
+decapsulate it).
+.Pp
+Note that specifying
+.Cm ipsec
+is different from specifying
+.Cm proto Ar ipsec
+as the latter will only look at the specific IP protocol field,
+irrespective of IPSEC kernel support and the validity of the IPSEC data.
+.Pp
+Further note that this flag is silently ignored in kernels without
+IPSEC support.
+It does not affect rule processing when given and the
+rules are handled as if with no
+.Cm ipsec
+flag.
+.It Cm iptos Ar spec
+Matches IPv4 packets whose
+.Cm tos
+field contains the comma separated list of
+service types specified in
+.Ar spec .
+The supported IP types of service are:
+.Pp
+.Cm lowdelay
+.Pq Dv IPTOS_LOWDELAY ,
+.Cm throughput
+.Pq Dv IPTOS_THROUGHPUT ,
+.Cm reliability
+.Pq Dv IPTOS_RELIABILITY ,
+.Cm mincost
+.Pq Dv IPTOS_MINCOST ,
+.Cm congestion
+.Pq Dv IPTOS_ECN_CE .
+The absence of a particular type may be denoted
+with a
+.Ql \&! .
+.It Cm dscp spec Ns Op , Ns Ar spec
+Matches IPv4/IPv6 packets whose
+.Cm DS
+field value is contained in
+.Ar spec
+mask.
+Multiple values can be specified via
+the comma separated list.
+Value can be one of keywords used in
+.Cm setdscp
+action or exact number.
+.It Cm ipttl Ar ttl-list
+Matches IPv4 packets whose time to live is included in
+.Ar ttl-list ,
+which is either a single value or a list of values or ranges
+specified in the same way as
+.Ar ports .
+.It Cm ipversion Ar ver
+Matches IP packets whose IP version field is
+.Ar ver .
+.It Cm keep-state
+Upon a match, the firewall will create a dynamic rule, whose
+default behaviour is to match bidirectional traffic between
+source and destination IP/port using the same protocol.
+The rule has a limited lifetime (controlled by a set of
+.Xr sysctl 8
+variables), and the lifetime is refreshed every time a matching
+packet is found.
+.It Cm layer2
+Matches only layer2 packets, i.e., those passed to
+.Nm
+from ether_demux() and ether_output_frame().
+.It Cm limit Bro Cm src-addr | src-port | dst-addr | dst-port Brc Ar N
+The firewall will only allow
+.Ar N
+connections with the same
+set of parameters as specified in the rule.
+One or more
+of source and destination addresses and ports can be
+specified.
+Currently,
+only IPv4 flows are supported.
+.It Cm lookup Bro Cm dst-ip | dst-port | src-ip | src-port | uid | jail Brc Ar name
+Search an entry in lookup table
+.Ar name
+that matches the field specified as argument.
+If not found, the match fails.
+Otherwise, the match succeeds and
+.Cm tablearg
+is set to the value extracted from the table.
+.Pp
+This option can be useful to quickly dispatch traffic based on
+certain packet fields.
+See the
+.Sx LOOKUP TABLES
+section below for more information on lookup tables.
+.It Cm { MAC | mac } Ar dst-mac src-mac
+Match packets with a given
+.Ar dst-mac
+and
+.Ar src-mac
+addresses, specified as the
+.Cm any
+keyword (matching any MAC address), or six groups of hex digits
+separated by colons,
+and optionally followed by a mask indicating the significant bits.
+The mask may be specified using either of the following methods:
+.Bl -enum -width indent
+.It
+A slash
+.Pq /
+followed by the number of significant bits.
+For example, an address with 33 significant bits could be specified as:
+.Pp
+.Dl "MAC 10:20:30:40:50:60/33 any"
+.It
+An ampersand
+.Pq &
+followed by a bitmask specified as six groups of hex digits separated
+by colons.
+For example, an address in which the last 16 bits are significant could
+be specified as:
+.Pp
+.Dl "MAC 10:20:30:40:50:60&00:00:00:00:ff:ff any"
+.Pp
+Note that the ampersand character has a special meaning in many shells
+and should generally be escaped.
+.El
+Note that the order of MAC addresses (destination first,
+source second) is
+the same as on the wire, but the opposite of the one used for
+IP addresses.
+.It Cm mac-type Ar mac-type
+Matches packets whose Ethernet Type field
+corresponds to one of those specified as argument.
+.Ar mac-type
+is specified in the same way as
+.Cm port numbers
+(i.e., one or more comma-separated single values or ranges).
+You can use symbolic names for known values such as
+.Em vlan , ipv4, ipv6 .
+Values can be entered as decimal or hexadecimal (if prefixed by 0x),
+and they are always printed as hexadecimal (unless the
+.Cm -N
+option is used, in which case symbolic resolution will be attempted).
+.It Cm proto Ar protocol
+Matches packets with the corresponding IP protocol.
+.It Cm recv | xmit | via Brq Ar ifX | Ar if Ns Cm * | Ar table Ns Po Ar name Ns Oo , Ns Ar value Oc Pc | Ar ipno | Ar any
+Matches packets received, transmitted or going through,
+respectively, the interface specified by exact name
+.Po Ar ifX Pc ,
+by device name
+.Po Ar if* Pc ,
+by IP address, or through some interface.
+Table
+.Ar name
+may be used to match interface by its kernel ifindex.
+See the
+.Sx LOOKUP TABLES
+section below for more information on lookup tables.
+.Pp
+The
+.Cm via
+keyword causes the interface to always be checked.
+If
+.Cm recv
+or
+.Cm xmit
+is used instead of
+.Cm via ,
+then only the receive or transmit interface (respectively)
+is checked.
+By specifying both, it is possible to match packets based on
+both receive and transmit interface, e.g.:
+.Pp
+.Dl "ipfw add deny ip from any to any out recv ed0 xmit ed1"
+.Pp
+The
+.Cm recv
+interface can be tested on either incoming or outgoing packets,
+while the
+.Cm xmit
+interface can only be tested on outgoing packets.
+So
+.Cm out
+is required (and
+.Cm in
+is invalid) whenever
+.Cm xmit
+is used.
+.Pp
+A packet might not have a receive or transmit interface: packets
+originating from the local host have no receive interface,
+while packets destined for the local host have no transmit
+interface.
+.It Cm setup
+Matches TCP packets that have the SYN bit set but no ACK bit.
+This is the short form of
+.Dq Li tcpflags\ syn,!ack .
+.It Cm sockarg
+Matches packets that are associated to a local socket and
+for which the SO_USER_COOKIE socket option has been set
+to a non-zero value.
+As a side effect, the value of the
+option is made available as
+.Cm tablearg
+value, which in turn can be used as
+.Cm skipto
+or
+.Cm pipe
+number.
+.It Cm src-ip Ar ip-address
+Matches IPv4 packets whose source IP is one of the address(es)
+specified as an argument.
+.It Cm src-ip6 Ar ip6-address
+Matches IPv6 packets whose source IP is one of the address(es)
+specified as an argument.
+.It Cm src-port Ar ports
+Matches IP packets whose source port is one of the port(s)
+specified as argument.
+.It Cm tagged Ar tag-list
+Matches packets whose tags are included in
+.Ar tag-list ,
+which is either a single value or a list of values or ranges
+specified in the same way as
+.Ar ports .
+Tags can be applied to the packet using
+.Cm tag
+rule action parameter (see it's description for details on tags).
+.It Cm tcpack Ar ack
+TCP packets only.
+Match if the TCP header acknowledgment number field is set to
+.Ar ack .
+.It Cm tcpdatalen Ar tcpdatalen-list
+Matches TCP packets whose length of TCP data is
+.Ar tcpdatalen-list ,
+which is either a single value or a list of values or ranges
+specified in the same way as
+.Ar ports .
+.It Cm tcpflags Ar spec
+TCP packets only.
+Match if the TCP header contains the comma separated list of
+flags specified in
+.Ar spec .
+The supported TCP flags are:
+.Pp
+.Cm fin ,
+.Cm syn ,
+.Cm rst ,
+.Cm psh ,
+.Cm ack
+and
+.Cm urg .
+The absence of a particular flag may be denoted
+with a
+.Ql \&! .
+A rule which contains a
+.Cm tcpflags
+specification can never match a fragmented packet which has
+a non-zero offset.
+See the
+.Cm frag
+option for details on matching fragmented packets.
+.It Cm tcpseq Ar seq
+TCP packets only.
+Match if the TCP header sequence number field is set to
+.Ar seq .
+.It Cm tcpwin Ar tcpwin-list
+Matches TCP packets whose header window field is set to
+.Ar tcpwin-list ,
+which is either a single value or a list of values or ranges
+specified in the same way as
+.Ar ports .
+.It Cm tcpoptions Ar spec
+TCP packets only.
+Match if the TCP header contains the comma separated list of
+options specified in
+.Ar spec .
+The supported TCP options are:
+.Pp
+.Cm mss
+(maximum segment size),
+.Cm window
+(tcp window advertisement),
+.Cm sack
+(selective ack),
+.Cm ts
+(rfc1323 timestamp) and
+.Cm cc
+(rfc1644 t/tcp connection count).
+The absence of a particular option may be denoted
+with a
+.Ql \&! .
+.It Cm uid Ar user
+Match all TCP or UDP packets sent by or received for a
+.Ar user .
+A
+.Ar user
+may be matched by name or identification number.
+.It Cm verrevpath
+For incoming packets,
+a routing table lookup is done on the packet's source address.
+If the interface on which the packet entered the system matches the
+outgoing interface for the route,
+the packet matches.
+If the interfaces do not match up,
+the packet does not match.
+All outgoing packets or packets with no incoming interface match.
+.Pp
+The name and functionality of the option is intentionally similar to
+the Cisco IOS command:
+.Pp
+.Dl ip verify unicast reverse-path
+.Pp
+This option can be used to make anti-spoofing rules to reject all
+packets with source addresses not from this interface.
+See also the option
+.Cm antispoof .
+.It Cm versrcreach
+For incoming packets,
+a routing table lookup is done on the packet's source address.
+If a route to the source address exists, but not the default route
+or a blackhole/reject route, the packet matches.
+Otherwise, the packet does not match.
+All outgoing packets match.
+.Pp
+The name and functionality of the option is intentionally similar to
+the Cisco IOS command:
+.Pp
+.Dl ip verify unicast source reachable-via any
+.Pp
+This option can be used to make anti-spoofing rules to reject all
+packets whose source address is unreachable.
+.It Cm antispoof
+For incoming packets, the packet's source address is checked if it
+belongs to a directly connected network.
+If the network is directly connected, then the interface the packet
+came on in is compared to the interface the network is connected to.
+When incoming interface and directly connected interface are not the
+same, the packet does not match.
+Otherwise, the packet does match.
+All outgoing packets match.
+.Pp
+This option can be used to make anti-spoofing rules to reject all
+packets that pretend to be from a directly connected network but do
+not come in through that interface.
+This option is similar to but more restricted than
+.Cm verrevpath
+because it engages only on packets with source addresses of directly
+connected networks instead of all source addresses.
+.El
+.Sh LOOKUP TABLES
+Lookup tables are useful to handle large sparse sets of
+addresses or other search keys (e.g., ports, jail IDs, interface names).
+In the rest of this section we will use the term ``key''.
+Table name needs to match the following spec:
+.Ar table-name .
+Tables with the same name can be created in different
+.Ar sets .
+However, rule links to the tables in
+.Ar set 0
+by default.
+This behavior can be controlled by
+.Va net.inet.ip.fw.tables_sets
+variable.
+See the
+.Sx SETS OF RULES
+section for more information.
+There may be up to 65535 different lookup tables.
+.Pp
+The following table types are supported:
+.Bl -tag -width indent
+.It Ar table-type : Ar addr | iface | number | flow
+.It Ar table-key : Ar addr Ns Oo / Ns Ar masklen Oc | iface-name | number | flow-spec
+.It Ar flow-spec : Ar flow-field Ns Op , Ns Ar flow-spec
+.It Ar flow-field : src-ip | proto | src-port | dst-ip | dst-port
+.It Cm addr
+matches IPv4 or IPv6 address.
+Each entry is represented by an
+.Ar addr Ns Op / Ns Ar masklen
+and will match all addresses with base
+.Ar addr
+(specified as an IPv4/IPv6 address, or a hostname) and mask width of
+.Ar masklen
+bits.
+If
+.Ar masklen
+is not specified, it defaults to 32 for IPv4 and 128 for IPv6.
+When looking up an IP address in a table, the most specific
+entry will match.
+.It Cm iface
+matches interface names.
+Each entry is represented by string treated as interface name.
+Wildcards are not supported.
+.It Cm number
+maches protocol ports, uids/gids or jail IDs.
+Each entry is represented by 32-bit unsigned integer.
+Ranges are not supported.
+.It Cm flow
+Matches packet fields specified by
+.Ar flow
+type suboptions with table entries.
+.El
+.Pp
+Tables require explicit creation via
+.Cm create
+before use.
+.Pp
+The following creation options are supported:
+.Bl -tag -width indent
+.It Ar create-options : Ar create-option | create-options
+.It Ar create-option : Cm type Ar table-type | Cm valtype Ar value-mask | Cm algo Ar algo-desc |
+.Cm limit Ar number | Cm locked
+.It Cm type
+Table key type.
+.It Cm valtype
+Table value mask.
+.It Cm algo
+Table algorithm to use (see below).
+.It Cm limit
+Maximum number of items that may be inserted into table.
+.It Cm locked
+Restrict any table modifications.
+.El
+.Pp
+Some of these options may be modified later via
+.Cm modify
+keyword.
+The following options can be changed:
+.Bl -tag -width indent
+.It Ar modify-options : Ar modify-option | modify-options
+.It Ar modify-option : Cm limit Ar number
+.It Cm limit
+Alter maximum number of items that may be inserted into table.
+.El
+.Pp
+Additionally, table can be locked or unlocked using
+.Cm lock
+or
+.Cm unlock
+commands.
+.Pp
+Tables of the same
+.Ar type
+can be swapped with each other using
+.Cm swap Ar name
+command.
+Swap may fail if tables limits are set and data exchange
+would result in limits hit.
+Operation is performed atomically.
+.Pp
+One or more entries can be added to a table at once using
+.Cm add
+command.
+Addition of all items are performed atomically.
+By default, error in addition of one entry does not influence
+addition of other entries. However, non-zero error code is returned
+in that case.
+Special
+.Cm atomic
+keyword may be specified before
+.Cm add
+to indicate all-or-none add request.
+.Pp
+One or more entries can be removed from a table at once using
+.Cm delete
+command.
+By default, error in removal of one entry does not influence
+removing of other entries. However, non-zero error code is returned
+in that case.
+.Pp
+It may be possible to check what entry will be found on particular
+.Ar table-key
+using
+.Cm lookup
+.Ar table-key
+command.
+This functionality is optional and may be unsupported in some algorithms.
+.Pp
+The following operations can be performed on
+.Ar one
+or
+.Cm all
+tables:
+.Bl -tag -width indent
+.It Cm list
+List all entries.
+.It Cm flush
+Removes all entries.
+.It Cm info
+Shows generic table information.
+.It Cm detail
+Shows generic table information and algo-specific data.
+.El
+.Pp
+The following lookup algorithms are supported:
+.Bl -tag -width indent
+.It Ar algo-desc : algo-name | "algo-name algo-data"
+.It Ar algo-name: Ar addr:radix | addr:hash | iface:array | number:array | flow:hash
+.It Cm addr:radix
+Separate Radix trees for IPv4 and IPv6, the same way as the routing table (see
+.Xr route 4 ) .
+Default choice for
+.Ar addr
+type.
+.It Cm addr:hash
+Separate auto-growing hashes for IPv4 and IPv6.
+Accepts entries with the same mask length specified initially via
+.Cm "addr:hash masks=/v4,/v6"
+algorithm creation options.
+Assume /32 and /128 masks by default.
+Search removes host bits (according to mask) from supplied address and checks
+resulting key in appropriate hash.
+Mostly optimized for /64 and byte-ranged IPv6 masks.
+.It Cm iface:array
+Array storing sorted indexes for entries which are presented in the system.
+Optimized for very fast lookup.
+.It Cm number:array
+Array storing sorted u32 numbers.
+.It Cm flow:hash
+Auto-growing hash storing flow entries.
+Search calculates hash on required packet fields and searches for matching
+entries in selected bucket.
+.El
+.Pp
+The
+.Cm tablearg
+feature provides the ability to use a value, looked up in the table, as
+the argument for a rule action, action parameter or rule option.
+This can significantly reduce number of rules in some configurations.
+If two tables are used in a rule, the result of the second (destination)
+is used.
+.Pp
+Each record may hold one or more values according to
+.Ar value-mask .
+This mask is set on table creation via
+.Cm valtype
+option.
+The following value types are supported:
+.Bl -tag -width indent
+.It Ar value-mask : Ar value-type Ns Op , Ns Ar value-mask
+.It Ar value-type : Ar skipto | pipe | fib | nat | dscp | tag | divert |
+.Ar netgraph | limit | ipv4
+.It Cm skipto
+rule number to jump to.
+.It Cm pipe
+Pipe number to use.
+.It Cm fib
+fib number to match/set.
+.It Cm nat
+nat number to jump to.
+.It Cm dscp
+dscp value to match/set.
+.It Cm tag
+tag number to match/set.
+.It Cm divert
+port number to divert traffic to.
+.It Cm netgraph
+hook number to move packet to.
+.It Cm limit
+maximum number of connections.
+.It Cm ipv4
+IPv4 nexthop to fwd packets to.
+.It Cm ipv6
+IPv6 nexthop to fwd packets to.
+.El
+.Pp
+The
+.Cm tablearg
+argument can be used with the following actions:
+.Cm nat, pipe , queue, divert, tee, netgraph, ngtee, fwd, skipto, setfib,
+action parameters:
+.Cm tag, untag,
+rule options:
+.Cm limit, tagged.
+.Pp
+When used with the
+.Cm skipto
+action, the user should be aware that the code will walk the ruleset
+up to a rule equal to, or past, the given number.
+.Pp
+See the
+.Sx EXAMPLES
+Section for example usage of tables and the tablearg keyword.
+.Sh SETS OF RULES
+Each rule or table belongs to one of 32 different
+.Em sets
+, numbered 0 to 31.
+Set 31 is reserved for the default rule.
+.Pp
+By default, rules or tables are put in set 0, unless you use the
+.Cm set N
+attribute when adding a new rule or table.
+Sets can be individually and atomically enabled or disabled,
+so this mechanism permits an easy way to store multiple configurations
+of the firewall and quickly (and atomically) switch between them.
+.Pp
+By default, tables from set 0 are referenced when adding rule with
+table opcodes regardless of rule set.
+This behavior can be changed by setting
+.Va net.inet.ip.fw.tables_set
+variable to 1.
+Rule's set will then be used for table references.
+.Pp
+The command to enable/disable sets is
+.Bd -ragged -offset indent
+.Nm
+.Cm set Oo Cm disable Ar number ... Oc Op Cm enable Ar number ...
+.Ed
+.Pp
+where multiple
+.Cm enable
+or
+.Cm disable
+sections can be specified.
+Command execution is atomic on all the sets specified in the command.
+By default, all sets are enabled.
+.Pp
+When you disable a set, its rules behave as if they do not exist
+in the firewall configuration, with only one exception:
+.Bd -ragged -offset indent
+dynamic rules created from a rule before it had been disabled
+will still be active until they expire.
+In order to delete
+dynamic rules you have to explicitly delete the parent rule
+which generated them.
+.Ed
+.Pp
+The set number of rules can be changed with the command
+.Bd -ragged -offset indent
+.Nm
+.Cm set move
+.Brq Cm rule Ar rule-number | old-set
+.Cm to Ar new-set
+.Ed
+.Pp
+Also, you can atomically swap two rulesets with the command
+.Bd -ragged -offset indent
+.Nm
+.Cm set swap Ar first-set second-set
+.Ed
+.Pp
+See the
+.Sx EXAMPLES
+Section on some possible uses of sets of rules.
+.Sh STATEFUL FIREWALL
+Stateful operation is a way for the firewall to dynamically
+create rules for specific flows when packets that
+match a given pattern are detected.
+Support for stateful
+operation comes through the
+.Cm check-state , keep-state
+and
+.Cm limit
+options of
+.Nm rules .
+.Pp
+Dynamic rules are created when a packet matches a
+.Cm keep-state
+or
+.Cm limit
+rule, causing the creation of a
+.Em dynamic
+rule which will match all and only packets with
+a given
+.Em protocol
+between a
+.Em src-ip/src-port dst-ip/dst-port
+pair of addresses
+.Em ( src
+and
+.Em dst
+are used here only to denote the initial match addresses, but they
+are completely equivalent afterwards).
+Dynamic rules will be checked at the first
+.Cm check-state, keep-state
+or
+.Cm limit
+occurrence, and the action performed upon a match will be the same
+as in the parent rule.
+.Pp
+Note that no additional attributes other than protocol and IP addresses
+and ports are checked on dynamic rules.
+.Pp
+The typical use of dynamic rules is to keep a closed firewall configuration,
+but let the first TCP SYN packet from the inside network install a
+dynamic rule for the flow so that packets belonging to that session
+will be allowed through the firewall:
+.Pp
+.Dl "ipfw add check-state"
+.Dl "ipfw add allow tcp from my-subnet to any setup keep-state"
+.Dl "ipfw add deny tcp from any to any"
+.Pp
+A similar approach can be used for UDP, where an UDP packet coming
+from the inside will install a dynamic rule to let the response through
+the firewall:
+.Pp
+.Dl "ipfw add check-state"
+.Dl "ipfw add allow udp from my-subnet to any keep-state"
+.Dl "ipfw add deny udp from any to any"
+.Pp
+Dynamic rules expire after some time, which depends on the status
+of the flow and the setting of some
+.Cm sysctl
+variables.
+See Section
+.Sx SYSCTL VARIABLES
+for more details.
+For TCP sessions, dynamic rules can be instructed to periodically
+send keepalive packets to refresh the state of the rule when it is
+about to expire.
+.Pp
+See Section
+.Sx EXAMPLES
+for more examples on how to use dynamic rules.
+.Sh TRAFFIC SHAPER (DUMMYNET) CONFIGURATION
+.Nm
+is also the user interface for the
+.Nm dummynet
+traffic shaper, packet scheduler and network emulator, a subsystem that
+can artificially queue, delay or drop packets
+emulating the behaviour of certain network links
+or queueing systems.
+.Pp
+.Nm dummynet
+operates by first using the firewall to select packets
+using any match pattern that can be used in
+.Nm
+rules.
+Matching packets are then passed to either of two
+different objects, which implement the traffic regulation:
+.Bl -hang -offset XXXX
+.It Em pipe
+A
+.Em pipe
+emulates a
+.Em link
+with given bandwidth and propagation delay,
+driven by a FIFO scheduler and a single queue with programmable
+queue size and packet loss rate.
+Packets are appended to the queue as they come out from
+.Nm ipfw ,
+and then transferred in FIFO order to the link at the desired rate.
+.It Em queue
+A
+.Em queue
+is an abstraction used to implement packet scheduling
+using one of several packet scheduling algorithms.
+Packets sent to a
+.Em queue
+are first grouped into flows according to a mask on the 5-tuple.
+Flows are then passed to the scheduler associated to the
+.Em queue ,
+and each flow uses scheduling parameters (weight and others)
+as configured in the
+.Em queue
+itself.
+A scheduler in turn is connected to an emulated link,
+and arbitrates the link's bandwidth among backlogged flows according to
+weights and to the features of the scheduling algorithm in use.
+.El
+.Pp
+In practice,
+.Em pipes
+can be used to set hard limits to the bandwidth that a flow can use, whereas
+.Em queues
+can be used to determine how different flows share the available bandwidth.
+.Pp
+A graphical representation of the binding of queues,
+flows, schedulers and links is below.
+.Bd -literal -offset indent
+ (flow_mask|sched_mask) sched_mask
+ +---------+ weight Wx +-------------+
+ | |->-[flow]-->--| |-+
+ -->--| QUEUE x | ... | | |
+ | |->-[flow]-->--| SCHEDuler N | |
+ +---------+ | | |
+ ... | +--[LINK N]-->--
+ +---------+ weight Wy | | +--[LINK N]-->--
+ | |->-[flow]-->--| | |
+ -->--| QUEUE y | ... | | |
+ | |->-[flow]-->--| | |
+ +---------+ +-------------+ |
+ +-------------+
+.Ed
+It is important to understand the role of the SCHED_MASK
+and FLOW_MASK, which are configured through the commands
+.Dl "ipfw sched N config mask SCHED_MASK ..."
+and
+.Dl "ipfw queue X config mask FLOW_MASK ..." .
+.Pp
+The SCHED_MASK is used to assign flows to one or more
+scheduler instances, one for each
+value of the packet's 5-tuple after applying SCHED_MASK.
+As an example, using ``src-ip 0xffffff00'' creates one instance
+for each /24 destination subnet.
+.Pp
+The FLOW_MASK, together with the SCHED_MASK, is used to split
+packets into flows.
+As an example, using
+``src-ip 0x000000ff''
+together with the previous SCHED_MASK makes a flow for
+each individual source address.
+In turn, flows for each /24
+subnet will be sent to the same scheduler instance.
+.Pp
+The above diagram holds even for the
+.Em pipe
+case, with the only restriction that a
+.Em pipe
+only supports a SCHED_MASK, and forces the use of a FIFO
+scheduler (these are for backward compatibility reasons;
+in fact, internally, a
+.Nm dummynet's
+pipe is implemented exactly as above).
+.Pp
+There are two modes of
+.Nm dummynet
+operation:
+.Dq normal
+and
+.Dq fast .
+The
+.Dq normal
+mode tries to emulate a real link: the
+.Nm dummynet
+scheduler ensures that the packet will not leave the pipe faster than it
+would on the real link with a given bandwidth.
+The
+.Dq fast
+mode allows certain packets to bypass the
+.Nm dummynet
+scheduler (if packet flow does not exceed pipe's bandwidth).
+This is the reason why the
+.Dq fast
+mode requires less CPU cycles per packet (on average) and packet latency
+can be significantly lower in comparison to a real link with the same
+bandwidth.
+The default mode is
+.Dq normal .
+The
+.Dq fast
+mode can be enabled by setting the
+.Va net.inet.ip.dummynet.io_fast
+.Xr sysctl 8
+variable to a non-zero value.
+.Pp
+.Ss PIPE, QUEUE AND SCHEDULER CONFIGURATION
+The
+.Em pipe ,
+.Em queue
+and
+.Em scheduler
+configuration commands are the following:
+.Bd -ragged -offset indent
+.Cm pipe Ar number Cm config Ar pipe-configuration
+.Pp
+.Cm queue Ar number Cm config Ar queue-configuration
+.Pp
+.Cm sched Ar number Cm config Ar sched-configuration
+.Ed
+.Pp
+The following parameters can be configured for a pipe:
+.Pp
+.Bl -tag -width indent -compact
+.It Cm bw Ar bandwidth | device
+Bandwidth, measured in
+.Sm off
+.Op Cm K | M
+.Brq Cm bit/s | Byte/s .
+.Sm on
+.Pp
+A value of 0 (default) means unlimited bandwidth.
+The unit must immediately follow the number, as in
+.Pp
+.Dl "ipfw pipe 1 config bw 300Kbit/s"
+.Pp
+If a device name is specified instead of a numeric value, as in
+.Pp
+.Dl "ipfw pipe 1 config bw tun0"
+.Pp
+then the transmit clock is supplied by the specified device.
+At the moment only the
+.Xr tun 4
+device supports this
+functionality, for use in conjunction with
+.Xr ppp 8 .
+.Pp
+.It Cm delay Ar ms-delay
+Propagation delay, measured in milliseconds.
+The value is rounded to the next multiple of the clock tick
+(typically 10ms, but it is a good practice to run kernels
+with
+.Dq "options HZ=1000"
+to reduce
+the granularity to 1ms or less).
+The default value is 0, meaning no delay.
+.Pp
+.It Cm burst Ar size
+If the data to be sent exceeds the pipe's bandwidth limit
+(and the pipe was previously idle), up to
+.Ar size
+bytes of data are allowed to bypass the
+.Nm dummynet
+scheduler, and will be sent as fast as the physical link allows.
+Any additional data will be transmitted at the rate specified
+by the
+.Nm pipe
+bandwidth.
+The burst size depends on how long the pipe has been idle;
+the effective burst size is calculated as follows:
+MAX(
+.Ar size
+,
+.Nm bw
+* pipe_idle_time).
+.Pp
+.It Cm profile Ar filename
+A file specifying the additional overhead incurred in the transmission
+of a packet on the link.
+.Pp
+Some link types introduce extra delays in the transmission
+of a packet, e.g., because of MAC level framing, contention on
+the use of the channel, MAC level retransmissions and so on.
+From our point of view, the channel is effectively unavailable
+for this extra time, which is constant or variable depending
+on the link type.
+Additionally, packets may be dropped after this
+time (e.g., on a wireless link after too many retransmissions).
+We can model the additional delay with an empirical curve
+that represents its distribution.
+.Bd -literal -offset indent
+ cumulative probability
+ 1.0 ^
+ |
+ L +-- loss-level x
+ | ******
+ | *
+ | *****
+ | *
+ | **
+ | *
+ +-------*------------------->
+ delay
+.Ed
+The empirical curve may have both vertical and horizontal lines.
+Vertical lines represent constant delay for a range of
+probabilities.
+Horizontal lines correspond to a discontinuity in the delay
+distribution: the pipe will use the largest delay for a
+given probability.
+.Pp
+The file format is the following, with whitespace acting as
+a separator and '#' indicating the beginning a comment:
+.Bl -tag -width indent
+.It Cm name Ar identifier
+optional name (listed by "ipfw pipe show")
+to identify the delay distribution;
+.It Cm bw Ar value
+the bandwidth used for the pipe.
+If not specified here, it must be present
+explicitly as a configuration parameter for the pipe;
+.It Cm loss-level Ar L
+the probability above which packets are lost.
+(0.0 <= L <= 1.0, default 1.0 i.e., no loss);
+.It Cm samples Ar N
+the number of samples used in the internal
+representation of the curve (2..1024; default 100);
+.It Cm "delay prob" | "prob delay"
+One of these two lines is mandatory and defines
+the format of the following lines with data points.
+.It Ar XXX Ar YYY
+2 or more lines representing points in the curve,
+with either delay or probability first, according
+to the chosen format.
+The unit for delay is milliseconds.
+Data points do not need to be sorted.
+Also, the number of actual lines can be different
+from the value of the "samples" parameter:
+.Nm
+utility will sort and interpolate
+the curve as needed.
+.El
+.Pp
+Example of a profile file:
+.Bd -literal -offset indent
+name bla_bla_bla
+samples 100
+loss-level 0.86
+prob delay
+0 200 # minimum overhead is 200ms
+0.5 200
+0.5 300
+0.8 1000
+0.9 1300
+1 1300
+#configuration file end
+.Ed
+.El
+.Pp
+The following parameters can be configured for a queue:
+.Pp
+.Bl -tag -width indent -compact
+.It Cm pipe Ar pipe_nr
+Connects a queue to the specified pipe.
+Multiple queues (with the same or different weights) can be connected to
+the same pipe, which specifies the aggregate rate for the set of queues.
+.Pp
+.It Cm weight Ar weight
+Specifies the weight to be used for flows matching this queue.
+The weight must be in the range 1..100, and defaults to 1.
+.El
+.Pp
+The following case-insensitive parameters can be configured for a
+scheduler:
+.Pp
+.Bl -tag -width indent -compact
+.It Cm type Ar {fifo | wf2q+ | rr | qfq}
+specifies the scheduling algorithm to use.
+.Bl -tag -width indent -compact
+.It Cm fifo
+is just a FIFO scheduler (which means that all packets
+are stored in the same queue as they arrive to the scheduler).
+FIFO has O(1) per-packet time complexity, with very low
+constants (estimate 60-80ns on a 2GHz desktop machine)
+but gives no service guarantees.
+.It Cm wf2q+
+implements the WF2Q+ algorithm, which is a Weighted Fair Queueing
+algorithm which permits flows to share bandwidth according to
+their weights.
+Note that weights are not priorities; even a flow
+with a minuscule weight will never starve.
+WF2Q+ has O(log N) per-packet processing cost, where N is the number
+of flows, and is the default algorithm used by previous versions
+dummynet's queues.
+.It Cm rr
+implements the Deficit Round Robin algorithm, which has O(1) processing
+costs (roughly, 100-150ns per packet)
+and permits bandwidth allocation according to weights, but
+with poor service guarantees.
+.It Cm qfq
+implements the QFQ algorithm, which is a very fast variant of
+WF2Q+, with similar service guarantees and O(1) processing
+costs (roughly, 200-250ns per packet).
+.El
+.El
+.Pp
+In addition to the type, all parameters allowed for a pipe can also
+be specified for a scheduler.
+.Pp
+Finally, the following parameters can be configured for both
+pipes and queues:
+.Pp
+.Bl -tag -width XXXX -compact
+.It Cm buckets Ar hash-table-size
+Specifies the size of the hash table used for storing the
+various queues.
+Default value is 64 controlled by the
+.Xr sysctl 8
+variable
+.Va net.inet.ip.dummynet.hash_size ,
+allowed range is 16 to 65536.
+.Pp
+.It Cm mask Ar mask-specifier
+Packets sent to a given pipe or queue by an
+.Nm
+rule can be further classified into multiple flows, each of which is then
+sent to a different
+.Em dynamic
+pipe or queue.
+A flow identifier is constructed by masking the IP addresses,
+ports and protocol types as specified with the
+.Cm mask
+options in the configuration of the pipe or queue.
+For each different flow identifier, a new pipe or queue is created
+with the same parameters as the original object, and matching packets
+are sent to it.
+.Pp
+Thus, when
+.Em dynamic pipes
+are used, each flow will get the same bandwidth as defined by the pipe,
+whereas when
+.Em dynamic queues
+are used, each flow will share the parent's pipe bandwidth evenly
+with other flows generated by the same queue (note that other queues
+with different weights might be connected to the same pipe).
+.br
+Available mask specifiers are a combination of one or more of the following:
+.Pp
+.Cm dst-ip Ar mask ,
+.Cm dst-ip6 Ar mask ,
+.Cm src-ip Ar mask ,
+.Cm src-ip6 Ar mask ,
+.Cm dst-port Ar mask ,
+.Cm src-port Ar mask ,
+.Cm flow-id Ar mask ,
+.Cm proto Ar mask
+or
+.Cm all ,
+.Pp
+where the latter means all bits in all fields are significant.
+.Pp
+.It Cm noerror
+When a packet is dropped by a
+.Nm dummynet
+queue or pipe, the error
+is normally reported to the caller routine in the kernel, in the
+same way as it happens when a device queue fills up.
+Setting this
+option reports the packet as successfully delivered, which can be
+needed for some experimental setups where you want to simulate
+loss or congestion at a remote router.
+.Pp
+.It Cm plr Ar packet-loss-rate
+Packet loss rate.
+Argument
+.Ar packet-loss-rate
+is a floating-point number between 0 and 1, with 0 meaning no
+loss, 1 meaning 100% loss.
+The loss rate is internally represented on 31 bits.
+.Pp
+.It Cm queue Brq Ar slots | size Ns Cm Kbytes
+Queue size, in
+.Ar slots
+or
+.Cm KBytes .
+Default value is 50 slots, which
+is the typical queue size for Ethernet devices.
+Note that for slow speed links you should keep the queue
+size short or your traffic might be affected by a significant
+queueing delay.
+E.g., 50 max-sized ethernet packets (1500 bytes) mean 600Kbit
+or 20s of queue on a 30Kbit/s pipe.
+Even worse effects can result if you get packets from an
+interface with a much larger MTU, e.g.\& the loopback interface
+with its 16KB packets.
+The
+.Xr sysctl 8
+variables
+.Em net.inet.ip.dummynet.pipe_byte_limit
+and
+.Em net.inet.ip.dummynet.pipe_slot_limit
+control the maximum lengths that can be specified.
+.Pp
+.It Cm red | gred Ar w_q Ns / Ns Ar min_th Ns / Ns Ar max_th Ns / Ns Ar max_p
+[ecn]
+Make use of the RED (Random Early Detection) queue management algorithm.
+.Ar w_q
+and
+.Ar max_p
+are floating
+point numbers between 0 and 1 (inclusive), while
+.Ar min_th
+and
+.Ar max_th
+are integer numbers specifying thresholds for queue management
+(thresholds are computed in bytes if the queue has been defined
+in bytes, in slots otherwise).
+The two parameters can also be of the same value if needed. The
+.Nm dummynet
+also supports the gentle RED variant (gred) and ECN (Explicit Congestion
+Notification) as optional. Three
+.Xr sysctl 8
+variables can be used to control the RED behaviour:
+.Bl -tag -width indent
+.It Va net.inet.ip.dummynet.red_lookup_depth
+specifies the accuracy in computing the average queue
+when the link is idle (defaults to 256, must be greater than zero)
+.It Va net.inet.ip.dummynet.red_avg_pkt_size
+specifies the expected average packet size (defaults to 512, must be
+greater than zero)
+.It Va net.inet.ip.dummynet.red_max_pkt_size
+specifies the expected maximum packet size, only used when queue
+thresholds are in bytes (defaults to 1500, must be greater than zero).
+.El
+.El
+.Pp
+When used with IPv6 data,
+.Nm dummynet
+currently has several limitations.
+Information necessary to route link-local packets to an
+interface is not available after processing by
+.Nm dummynet
+so those packets are dropped in the output path.
+Care should be taken to ensure that link-local packets are not passed to
+.Nm dummynet .
+.Sh CHECKLIST
+Here are some important points to consider when designing your
+rules:
+.Bl -bullet
+.It
+Remember that you filter both packets going
+.Cm in
+and
+.Cm out .
+Most connections need packets going in both directions.
+.It
+Remember to test very carefully.
+It is a good idea to be near the console when doing this.
+If you cannot be near the console,
+use an auto-recovery script such as the one in
+.Pa /usr/share/examples/ipfw/change_rules.sh .
+.It
+Do not forget the loopback interface.
+.El
+.Sh FINE POINTS
+.Bl -bullet
+.It
+There are circumstances where fragmented datagrams are unconditionally
+dropped.
+TCP packets are dropped if they do not contain at least 20 bytes of
+TCP header, UDP packets are dropped if they do not contain a full 8
+byte UDP header, and ICMP packets are dropped if they do not contain
+4 bytes of ICMP header, enough to specify the ICMP type, code, and
+checksum.
+These packets are simply logged as
+.Dq pullup failed
+since there may not be enough good data in the packet to produce a
+meaningful log entry.
+.It
+Another type of packet is unconditionally dropped, a TCP packet with a
+fragment offset of one.
+This is a valid packet, but it only has one use, to try
+to circumvent firewalls.
+When logging is enabled, these packets are
+reported as being dropped by rule -1.
+.It
+If you are logged in over a network, loading the
+.Xr kld 4
+version of
+.Nm
+is probably not as straightforward as you would think.
+The following command line is recommended:
+.Bd -literal -offset indent
+kldload ipfw && \e
+ipfw add 32000 allow ip from any to any
+.Ed
+.Pp
+Along the same lines, doing an
+.Bd -literal -offset indent
+ipfw flush
+.Ed
+.Pp
+in similar surroundings is also a bad idea.
+.It
+The
+.Nm
+filter list may not be modified if the system security level
+is set to 3 or higher
+(see
+.Xr init 8
+for information on system security levels).
+.El
+.Sh PACKET DIVERSION
+A
+.Xr divert 4
+socket bound to the specified port will receive all packets
+diverted to that port.
+If no socket is bound to the destination port, or if the divert module is
+not loaded, or if the kernel was not compiled with divert socket support,
+the packets are dropped.
+.Sh NETWORK ADDRESS TRANSLATION (NAT)
+.Nm
+support in-kernel NAT using the kernel version of
+.Xr libalias 3 .
+.Pp
+The nat configuration command is the following:
+.Bd -ragged -offset indent
+.Bk -words
+.Cm nat
+.Ar nat_number
+.Cm config
+.Ar nat-configuration
+.Ek
+.Ed
+.Pp
+The following parameters can be configured:
+.Bl -tag -width indent
+.It Cm ip Ar ip_address
+Define an ip address to use for aliasing.
+.It Cm if Ar nic
+Use ip address of NIC for aliasing, dynamically changing
+it if NIC's ip address changes.
+.It Cm log
+Enable logging on this nat instance.
+.It Cm deny_in
+Deny any incoming connection from outside world.
+.It Cm same_ports
+Try to leave the alias port numbers unchanged from
+the actual local port numbers.
+.It Cm unreg_only
+Traffic on the local network not originating from an
+unregistered address spaces will be ignored.
+.It Cm reset
+Reset table of the packet aliasing engine on address change.
+.It Cm reverse
+Reverse the way libalias handles aliasing.
+.It Cm proxy_only
+Obey transparent proxy rules only, packet aliasing is not performed.
+.It Cm skip_global
+Skip instance in case of global state lookup (see below).
+.El
+.Pp
+Some specials value can be supplied instead of
+.Va nat_number:
+.Bl -tag -width indent
+.It Cm global
+Looks up translation state in all configured nat instances.
+If an entry is found, packet is aliased according to that entry.
+If no entry was found in any of the instances, packet is passed unchanged,
+and no new entry will be created.
+See section
+.Sx MULTIPLE INSTANCES
+in
+.Xr natd 8
+for more information.
+.It Cm tablearg
+Uses argument supplied in lookup table.
+See
+.Sx LOOKUP TABLES
+section below for more information on lookup tables.
+.El
+.Pp
+To let the packet continue after being (de)aliased, set the sysctl variable
+.Va net.inet.ip.fw.one_pass
+to 0.
+For more information about aliasing modes, refer to
+.Xr libalias 3 .
+See Section
+.Sx EXAMPLES
+for some examples about nat usage.
+.Ss REDIRECT AND LSNAT SUPPORT IN IPFW
+Redirect and LSNAT support follow closely the syntax used in
+.Xr natd 8 .
+See Section
+.Sx EXAMPLES
+for some examples on how to do redirect and lsnat.
+.Ss SCTP NAT SUPPORT
+SCTP nat can be configured in a similar manner to TCP through the
+.Nm
+command line tool.
+The main difference is that
+.Nm sctp nat
+does not do port translation.
+Since the local and global side ports will be the same,
+there is no need to specify both.
+Ports are redirected as follows:
+.Bd -ragged -offset indent
+.Bk -words
+.Cm nat
+.Ar nat_number
+.Cm config if
+.Ar nic
+.Cm redirect_port sctp
+.Ar ip_address [,addr_list] {[port | port-port] [,ports]}
+.Ek
+.Ed
+.Pp
+Most
+.Nm sctp nat
+configuration can be done in real-time through the
+.Xr sysctl 8
+interface.
+All may be changed dynamically, though the hash_table size will only
+change for new
+.Nm nat
+instances.
+See
+.Sx SYSCTL VARIABLES
+for more info.
+.Sh LOADER TUNABLES
+Tunables can be set in
+.Xr loader 8
+prompt,
+.Xr loader.conf 5
+or
+.Xr kenv 1
+before ipfw module gets loaded.
+.Bl -tag -width indent
+.It Va net.inet.ip.fw.default_to_accept: No 0
+Defines ipfw last rule behavior.
+This value overrides
+.Cd "options IPFW_DEFAULT_TO_(ACCEPT|DENY)"
+from kernel configuration file.
+.It Va net.inet.ip.fw.tables_max: No 128
+Defines number of tables available in ipfw.
+Number cannot exceed 65534.
+.El
+.Sh SYSCTL VARIABLES
+A set of
+.Xr sysctl 8
+variables controls the behaviour of the firewall and
+associated modules
+.Pq Nm dummynet , bridge , sctp nat .
+These are shown below together with their default value
+(but always check with the
+.Xr sysctl 8
+command what value is actually in use) and meaning:
+.Bl -tag -width indent
+.It Va net.inet.ip.alias.sctp.accept_global_ootb_addip: No 0
+Defines how the
+.Nm nat
+responds to receipt of global OOTB ASCONF-AddIP:
+.Bl -tag -width indent
+.It Cm 0
+No response (unless a partially matching association exists -
+ports and vtags match but global address does not)
+.It Cm 1
+.Nm nat
+will accept and process all OOTB global AddIP messages.
+.El
+.Pp
+Option 1 should never be selected as this forms a security risk.
+An attacker can
+establish multiple fake associations by sending AddIP messages.
+.It Va net.inet.ip.alias.sctp.chunk_proc_limit: No 5
+Defines the maximum number of chunks in an SCTP packet that will be
+parsed for a
+packet that matches an existing association.
+This value is enforced to be greater or equal than
+.Cm net.inet.ip.alias.sctp.initialising_chunk_proc_limit .
+A high value is
+a DoS risk yet setting too low a value may result in
+important control chunks in
+the packet not being located and parsed.
+.It Va net.inet.ip.alias.sctp.error_on_ootb: No 1
+Defines when the
+.Nm nat
+responds to any Out-of-the-Blue (OOTB) packets with ErrorM packets.
+An OOTB packet is a packet that arrives with no existing association
+registered in the
+.Nm nat
+and is not an INIT or ASCONF-AddIP packet:
+.Bl -tag -width indent
+.It Cm 0
+ErrorM is never sent in response to OOTB packets.
+.It Cm 1
+ErrorM is only sent to OOTB packets received on the local side.
+.It Cm 2
+ErrorM is sent to the local side and on the global side ONLY if there is a
+partial match (ports and vtags match but the source global IP does not).
+This value is only useful if the
+.Nm nat
+is tracking global IP addresses.
+.It Cm 3
+ErrorM is sent in response to all OOTB packets on both
+the local and global side
+(DoS risk).
+.El
+.Pp
+At the moment the default is 0, since the ErrorM packet is not yet
+supported by most SCTP stacks.
+When it is supported, and if not tracking
+global addresses, we recommend setting this value to 1 to allow
+multi-homed local hosts to function with the
+.Nm nat .
+To track global addresses, we recommend setting this value to 2 to
+allow global hosts to be informed when they need to (re)send an
+ASCONF-AddIP.
+Value 3 should never be chosen (except for debugging) as the
+.Nm nat
+will respond to all OOTB global packets (a DoS risk).
+.It Va net.inet.ip.alias.sctp.hashtable_size: No 2003
+Size of hash tables used for
+.Nm nat
+lookups (100 < prime_number > 1000001).
+This value sets the
+.Nm hash table
+size for any future created
+.Nm nat
+instance and therefore must be set prior to creating a
+.Nm nat
+instance.
+The table sizes may be changed to suit specific needs.
+If there will be few
+concurrent associations, and memory is scarce, you may make these smaller.
+If there will be many thousands (or millions) of concurrent associations, you
+should make these larger.
+A prime number is best for the table size.
+The sysctl
+update function will adjust your input value to the next highest prime number.
+.It Va net.inet.ip.alias.sctp.holddown_time: No 0
+Hold association in table for this many seconds after receiving a
+SHUTDOWN-COMPLETE.
+This allows endpoints to correct shutdown gracefully if a
+shutdown_complete is lost and retransmissions are required.
+.It Va net.inet.ip.alias.sctp.init_timer: No 15
+Timeout value while waiting for (INIT-ACK|AddIP-ACK).
+This value cannot be 0.
+.It Va net.inet.ip.alias.sctp.initialising_chunk_proc_limit: No 2
+Defines the maximum number of chunks in an SCTP packet that will be parsed when
+no existing association exists that matches that packet.
+Ideally this packet
+will only be an INIT or ASCONF-AddIP packet.
+A higher value may become a DoS
+risk as malformed packets can consume processing resources.
+.It Va net.inet.ip.alias.sctp.param_proc_limit: No 25
+Defines the maximum number of parameters within a chunk that will be
+parsed in a
+packet.
+As for other similar sysctl variables, larger values pose a DoS risk.
+.It Va net.inet.ip.alias.sctp.log_level: No 0
+Level of detail in the system log messages (0 \- minimal, 1 \- event,
+2 \- info, 3 \- detail, 4 \- debug, 5 \- max debug).
+May be a good
+option in high loss environments.
+.It Va net.inet.ip.alias.sctp.shutdown_time: No 15
+Timeout value while waiting for SHUTDOWN-COMPLETE.
+This value cannot be 0.
+.It Va net.inet.ip.alias.sctp.track_global_addresses: No 0
+Enables/disables global IP address tracking within the
+.Nm nat
+and places an
+upper limit on the number of addresses tracked for each association:
+.Bl -tag -width indent
+.It Cm 0
+Global tracking is disabled
+.It Cm >1
+Enables tracking, the maximum number of addresses tracked for each
+association is limited to this value
+.El
+.Pp
+This variable is fully dynamic, the new value will be adopted for all newly
+arriving associations, existing associations are treated
+as they were previously.
+Global tracking will decrease the number of collisions within the
+.Nm nat
+at a cost
+of increased processing load, memory usage, complexity, and possible
+.Nm nat
+state
+problems in complex networks with multiple
+.Nm nats .
+We recommend not tracking
+global IP addresses, this will still result in a fully functional
+.Nm nat .
+.It Va net.inet.ip.alias.sctp.up_timer: No 300
+Timeout value to keep an association up with no traffic.
+This value cannot be 0.
+.It Va net.inet.ip.dummynet.expire : No 1
+Lazily delete dynamic pipes/queue once they have no pending traffic.
+You can disable this by setting the variable to 0, in which case
+the pipes/queues will only be deleted when the threshold is reached.
+.It Va net.inet.ip.dummynet.hash_size : No 64
+Default size of the hash table used for dynamic pipes/queues.
+This value is used when no
+.Cm buckets
+option is specified when configuring a pipe/queue.
+.It Va net.inet.ip.dummynet.io_fast : No 0
+If set to a non-zero value,
+the
+.Dq fast
+mode of
+.Nm dummynet
+operation (see above) is enabled.
+.It Va net.inet.ip.dummynet.io_pkt
+Number of packets passed to
+.Nm dummynet .
+.It Va net.inet.ip.dummynet.io_pkt_drop
+Number of packets dropped by
+.Nm dummynet .
+.It Va net.inet.ip.dummynet.io_pkt_fast
+Number of packets bypassed by the
+.Nm dummynet
+scheduler.
+.It Va net.inet.ip.dummynet.max_chain_len : No 16
+Target value for the maximum number of pipes/queues in a hash bucket.
+The product
+.Cm max_chain_len*hash_size
+is used to determine the threshold over which empty pipes/queues
+will be expired even when
+.Cm net.inet.ip.dummynet.expire=0 .
+.It Va net.inet.ip.dummynet.red_lookup_depth : No 256
+.It Va net.inet.ip.dummynet.red_avg_pkt_size : No 512
+.It Va net.inet.ip.dummynet.red_max_pkt_size : No 1500
+Parameters used in the computations of the drop probability
+for the RED algorithm.
+.It Va net.inet.ip.dummynet.pipe_byte_limit : No 1048576
+.It Va net.inet.ip.dummynet.pipe_slot_limit : No 100
+The maximum queue size that can be specified in bytes or packets.
+These limits prevent accidental exhaustion of resources such as mbufs.
+If you raise these limits,
+you should make sure the system is configured so that sufficient resources
+are available.
+.It Va net.inet.ip.fw.autoinc_step : No 100
+Delta between rule numbers when auto-generating them.
+The value must be in the range 1..1000.
+.It Va net.inet.ip.fw.curr_dyn_buckets : Va net.inet.ip.fw.dyn_buckets
+The current number of buckets in the hash table for dynamic rules
+(readonly).
+.It Va net.inet.ip.fw.debug : No 1
+Controls debugging messages produced by
+.Nm .
+.It Va net.inet.ip.fw.default_rule : No 65535
+The default rule number (read-only).
+By the design of
+.Nm , the default rule is the last one, so its number
+can also serve as the highest number allowed for a rule.
+.It Va net.inet.ip.fw.dyn_buckets : No 256
+The number of buckets in the hash table for dynamic rules.
+Must be a power of 2, up to 65536.
+It only takes effect when all dynamic rules have expired, so you
+are advised to use a
+.Cm flush
+command to make sure that the hash table is resized.
+.It Va net.inet.ip.fw.dyn_count : No 3
+Current number of dynamic rules
+(read-only).
+.It Va net.inet.ip.fw.dyn_keepalive : No 1
+Enables generation of keepalive packets for
+.Cm keep-state
+rules on TCP sessions.
+A keepalive is generated to both
+sides of the connection every 5 seconds for the last 20
+seconds of the lifetime of the rule.
+.It Va net.inet.ip.fw.dyn_max : No 8192
+Maximum number of dynamic rules.
+When you hit this limit, no more dynamic rules can be
+installed until old ones expire.
+.It Va net.inet.ip.fw.dyn_ack_lifetime : No 300
+.It Va net.inet.ip.fw.dyn_syn_lifetime : No 20
+.It Va net.inet.ip.fw.dyn_fin_lifetime : No 1
+.It Va net.inet.ip.fw.dyn_rst_lifetime : No 1
+.It Va net.inet.ip.fw.dyn_udp_lifetime : No 5
+.It Va net.inet.ip.fw.dyn_short_lifetime : No 30
+These variables control the lifetime, in seconds, of dynamic
+rules.
+Upon the initial SYN exchange the lifetime is kept short,
+then increased after both SYN have been seen, then decreased
+again during the final FIN exchange or when a RST is received.
+Both
+.Em dyn_fin_lifetime
+and
+.Em dyn_rst_lifetime
+must be strictly lower than 5 seconds, the period of
+repetition of keepalives.
+The firewall enforces that.
+.It Va net.inet.ip.fw.dyn_keep_states: No 0
+Keep dynamic states on rule/set deletion.
+States are relinked to default rule (65535).
+This can be handly for ruleset reload.
+Turned off by default.
+.It Va net.inet.ip.fw.enable : No 1
+Enables the firewall.
+Setting this variable to 0 lets you run your machine without
+firewall even if compiled in.
+.It Va net.inet6.ip6.fw.enable : No 1
+provides the same functionality as above for the IPv6 case.
+.It Va net.inet.ip.fw.one_pass : No 1
+When set, the packet exiting from the
+.Nm dummynet
+pipe or from
+.Xr ng_ipfw 4
+node is not passed though the firewall again.
+Otherwise, after an action, the packet is
+reinjected into the firewall at the next rule.
+.It Va net.inet.ip.fw.tables_max : No 128
+Maximum number of tables.
+.It Va net.inet.ip.fw.verbose : No 1
+Enables verbose messages.
+.It Va net.inet.ip.fw.verbose_limit : No 0
+Limits the number of messages produced by a verbose firewall.
+.It Va net.inet6.ip6.fw.deny_unknown_exthdrs : No 1
+If enabled packets with unknown IPv6 Extension Headers will be denied.
+.It Va net.link.ether.ipfw : No 0
+Controls whether layer-2 packets are passed to
+.Nm .
+Default is no.
+.It Va net.link.bridge.ipfw : No 0
+Controls whether bridged packets are passed to
+.Nm .
+Default is no.
+.El
+.Sh INTERNAL DIAGNOSTICS
+There are some commands that may be useful to understand current state
+of certain subsystems inside kernel module.
+These commands provide debugging output which may change without notice.
+.Pp
+Currently the following commands are available as
+.Cm internal
+sub-options:
+.Bl -tag -width indent
+.It Cm iflist
+Lists all interface which are currently tracked by
+.Nm
+with their in-kernel status.
+.It Cm talist
+List all table lookup algorithms currently available.
+.El
+.Sh EXAMPLES
+There are far too many possible uses of
+.Nm
+so this Section will only give a small set of examples.
+.Pp
+.Ss BASIC PACKET FILTERING
+This command adds an entry which denies all tcp packets from
+.Em cracker.evil.org
+to the telnet port of
+.Em wolf.tambov.su
+from being forwarded by the host:
+.Pp
+.Dl "ipfw add deny tcp from cracker.evil.org to wolf.tambov.su telnet"
+.Pp
+This one disallows any connection from the entire cracker's
+network to my host:
+.Pp
+.Dl "ipfw add deny ip from 123.45.67.0/24 to my.host.org"
+.Pp
+A first and efficient way to limit access (not using dynamic rules)
+is the use of the following rules:
+.Pp
+.Dl "ipfw add allow tcp from any to any established"
+.Dl "ipfw add allow tcp from net1 portlist1 to net2 portlist2 setup"
+.Dl "ipfw add allow tcp from net3 portlist3 to net3 portlist3 setup"
+.Dl "..."
+.Dl "ipfw add deny tcp from any to any"
+.Pp
+The first rule will be a quick match for normal TCP packets,
+but it will not match the initial SYN packet, which will be
+matched by the
+.Cm setup
+rules only for selected source/destination pairs.
+All other SYN packets will be rejected by the final
+.Cm deny
+rule.
+.Pp
+If you administer one or more subnets, you can take advantage
+of the address sets and or-blocks and write extremely
+compact rulesets which selectively enable services to blocks
+of clients, as below:
+.Pp
+.Dl "goodguys=\*q{ 10.1.2.0/24{20,35,66,18} or 10.2.3.0/28{6,3,11} }\*q"
+.Dl "badguys=\*q10.1.2.0/24{8,38,60}\*q"
+.Dl ""
+.Dl "ipfw add allow ip from ${goodguys} to any"
+.Dl "ipfw add deny ip from ${badguys} to any"
+.Dl "... normal policies ..."
+.Pp
+The
+.Cm verrevpath
+option could be used to do automated anti-spoofing by adding the
+following to the top of a ruleset:
+.Pp
+.Dl "ipfw add deny ip from any to any not verrevpath in"
+.Pp
+This rule drops all incoming packets that appear to be coming to the
+system on the wrong interface.
+For example, a packet with a source
+address belonging to a host on a protected internal network would be
+dropped if it tried to enter the system from an external interface.
+.Pp
+The
+.Cm antispoof
+option could be used to do similar but more restricted anti-spoofing
+by adding the following to the top of a ruleset:
+.Pp
+.Dl "ipfw add deny ip from any to any not antispoof in"
+.Pp
+This rule drops all incoming packets that appear to be coming from another
+directly connected system but on the wrong interface.
+For example, a packet with a source address of
+.Li 192.168.0.0/24 ,
+configured on
+.Li fxp0 ,
+but coming in on
+.Li fxp1
+would be dropped.
+.Pp
+The
+.Cm setdscp
+option could be used to (re)mark user traffic,
+by adding the following to the appropriate place in ruleset:
+.Pp
+.Dl "ipfw add setdscp be ip from any to any dscp af11,af21"
+.Ss DYNAMIC RULES
+In order to protect a site from flood attacks involving fake
+TCP packets, it is safer to use dynamic rules:
+.Pp
+.Dl "ipfw add check-state"
+.Dl "ipfw add deny tcp from any to any established"
+.Dl "ipfw add allow tcp from my-net to any setup keep-state"
+.Pp
+This will let the firewall install dynamic rules only for
+those connection which start with a regular SYN packet coming
+from the inside of our network.
+Dynamic rules are checked when encountering the first
+occurrence of a
+.Cm check-state ,
+.Cm keep-state
+or
+.Cm limit
+rule.
+A
+.Cm check-state
+rule should usually be placed near the beginning of the
+ruleset to minimize the amount of work scanning the ruleset.
+Your mileage may vary.
+.Pp
+To limit the number of connections a user can open
+you can use the following type of rules:
+.Pp
+.Dl "ipfw add allow tcp from my-net/24 to any setup limit src-addr 10"
+.Dl "ipfw add allow tcp from any to me setup limit src-addr 4"
+.Pp
+The former (assuming it runs on a gateway) will allow each host
+on a /24 network to open at most 10 TCP connections.
+The latter can be placed on a server to make sure that a single
+client does not use more than 4 simultaneous connections.
+.Pp
+.Em BEWARE :
+stateful rules can be subject to denial-of-service attacks
+by a SYN-flood which opens a huge number of dynamic rules.
+The effects of such attacks can be partially limited by
+acting on a set of
+.Xr sysctl 8
+variables which control the operation of the firewall.
+.Pp
+Here is a good usage of the
+.Cm list
+command to see accounting records and timestamp information:
+.Pp
+.Dl ipfw -at list
+.Pp
+or in short form without timestamps:
+.Pp
+.Dl ipfw -a list
+.Pp
+which is equivalent to:
+.Pp
+.Dl ipfw show
+.Pp
+Next rule diverts all incoming packets from 192.168.2.0/24
+to divert port 5000:
+.Pp
+.Dl ipfw divert 5000 ip from 192.168.2.0/24 to any in
+.Ss TRAFFIC SHAPING
+The following rules show some of the applications of
+.Nm
+and
+.Nm dummynet
+for simulations and the like.
+.Pp
+This rule drops random incoming packets with a probability
+of 5%:
+.Pp
+.Dl "ipfw add prob 0.05 deny ip from any to any in"
+.Pp
+A similar effect can be achieved making use of
+.Nm dummynet
+pipes:
+.Pp
+.Dl "ipfw add pipe 10 ip from any to any"
+.Dl "ipfw pipe 10 config plr 0.05"
+.Pp
+We can use pipes to artificially limit bandwidth, e.g.\& on a
+machine acting as a router, if we want to limit traffic from
+local clients on 192.168.2.0/24 we do:
+.Pp
+.Dl "ipfw add pipe 1 ip from 192.168.2.0/24 to any out"
+.Dl "ipfw pipe 1 config bw 300Kbit/s queue 50KBytes"
+.Pp
+note that we use the
+.Cm out
+modifier so that the rule is not used twice.
+Remember in fact that
+.Nm
+rules are checked both on incoming and outgoing packets.
+.Pp
+Should we want to simulate a bidirectional link with bandwidth
+limitations, the correct way is the following:
+.Pp
+.Dl "ipfw add pipe 1 ip from any to any out"
+.Dl "ipfw add pipe 2 ip from any to any in"
+.Dl "ipfw pipe 1 config bw 64Kbit/s queue 10Kbytes"
+.Dl "ipfw pipe 2 config bw 64Kbit/s queue 10Kbytes"
+.Pp
+The above can be very useful, e.g.\& if you want to see how
+your fancy Web page will look for a residential user who
+is connected only through a slow link.
+You should not use only one pipe for both directions, unless
+you want to simulate a half-duplex medium (e.g.\& AppleTalk,
+Ethernet, IRDA).
+It is not necessary that both pipes have the same configuration,
+so we can also simulate asymmetric links.
+.Pp
+Should we want to verify network performance with the RED queue
+management algorithm:
+.Pp
+.Dl "ipfw add pipe 1 ip from any to any"
+.Dl "ipfw pipe 1 config bw 500Kbit/s queue 100 red 0.002/30/80/0.1"
+.Pp
+Another typical application of the traffic shaper is to
+introduce some delay in the communication.
+This can significantly affect applications which do a lot of Remote
+Procedure Calls, and where the round-trip-time of the
+connection often becomes a limiting factor much more than
+bandwidth:
+.Pp
+.Dl "ipfw add pipe 1 ip from any to any out"
+.Dl "ipfw add pipe 2 ip from any to any in"
+.Dl "ipfw pipe 1 config delay 250ms bw 1Mbit/s"
+.Dl "ipfw pipe 2 config delay 250ms bw 1Mbit/s"
+.Pp
+Per-flow queueing can be useful for a variety of purposes.
+A very simple one is counting traffic:
+.Pp
+.Dl "ipfw add pipe 1 tcp from any to any"
+.Dl "ipfw add pipe 1 udp from any to any"
+.Dl "ipfw add pipe 1 ip from any to any"
+.Dl "ipfw pipe 1 config mask all"
+.Pp
+The above set of rules will create queues (and collect
+statistics) for all traffic.
+Because the pipes have no limitations, the only effect is
+collecting statistics.
+Note that we need 3 rules, not just the last one, because
+when
+.Nm
+tries to match IP packets it will not consider ports, so we
+would not see connections on separate ports as different
+ones.
+.Pp
+A more sophisticated example is limiting the outbound traffic
+on a net with per-host limits, rather than per-network limits:
+.Pp
+.Dl "ipfw add pipe 1 ip from 192.168.2.0/24 to any out"
+.Dl "ipfw add pipe 2 ip from any to 192.168.2.0/24 in"
+.Dl "ipfw pipe 1 config mask src-ip 0x000000ff bw 200Kbit/s queue 20Kbytes"
+.Dl "ipfw pipe 2 config mask dst-ip 0x000000ff bw 200Kbit/s queue 20Kbytes"
+.Ss LOOKUP TABLES
+In the following example, we need to create several traffic bandwidth
+classes and we need different hosts/networks to fall into different classes.
+We create one pipe for each class and configure them accordingly.
+Then we create a single table and fill it with IP subnets and addresses.
+For each subnet/host we set the argument equal to the number of the pipe
+that it should use.
+Then we classify traffic using a single rule:
+.Pp
+.Dl "ipfw pipe 1 config bw 1000Kbyte/s"
+.Dl "ipfw pipe 4 config bw 4000Kbyte/s"
+.Dl "..."
+.Dl "ipfw table T1 create type addr"
+.Dl "ipfw table T1 add 192.168.2.0/24 1"
+.Dl "ipfw table T1 add 192.168.0.0/27 4"
+.Dl "ipfw table T1 add 192.168.0.2 1"
+.Dl "..."
+.Dl "ipfw add pipe tablearg ip from 'table(T1)' to any"
+.Pp
+Using the
+.Cm fwd
+action, the table entries may include hostnames and IP addresses.
+.Pp
+.Dl "ipfw table T2 create type addr ftype ip"
+.Dl "ipfw table T2 add 192.168.2.0/24 10.23.2.1"
+.Dl "ipfw table T21 add 192.168.0.0/27 router1.dmz"
+.Dl "..."
+.Dl "ipfw add 100 fwd tablearg ip from any to table(1)"
+.Pp
+In the following example per-interface firewall is created:
+.Pp
+.Dl "ipfw table IN create type iface valtype skipto,fib"
+.Dl "ipfw table IN add vlan20 12000,12"
+.Dl "ipfw table IN add vlan30 13000,13"
+.Dl "ipfw table OUT create type iface valtype skipto"
+.Dl "ipfw table OUT add vlan20 22000"
+.Dl "ipfw table OUT add vlan30 23000"
+.Dl ".."
+.Dl "ipfw add 100 ipfw setfib tablearg ip from any to any recv 'table(IN)' in"
+.Dl "ipfw add 200 ipfw skipto tablearg ip from any to any recv 'table(IN)' in"
+.Dl "ipfw add 300 ipfw skipto tablearg ip from any to any xmit 'table(OUT)' out"
+.Pp
+The following example illustrate usage of flow tables:
+.Pp
+.Dl "ipfw table fl create type flow:flow:src-ip,proto,dst-ip,dst-port"
+.Dl "ipfw table fl add 2a02:6b8:77::88,tcp,2a02:6b8:77::99,80 11"
+.Dl "ipfw table fl add 10.0.0.1,udp,10.0.0.2,53 12"
+.Dl ".."
+.Dl "ipfw add 100 allow ip from any to any flow 'table(fl,11)' recv ix0"
+.Ss SETS OF RULES
+To add a set of rules atomically, e.g.\& set 18:
+.Pp
+.Dl "ipfw set disable 18"
+.Dl "ipfw add NN set 18 ... # repeat as needed"
+.Dl "ipfw set enable 18"
+.Pp
+To delete a set of rules atomically the command is simply:
+.Pp
+.Dl "ipfw delete set 18"
+.Pp
+To test a ruleset and disable it and regain control if something goes wrong:
+.Pp
+.Dl "ipfw set disable 18"
+.Dl "ipfw add NN set 18 ... # repeat as needed"
+.Dl "ipfw set enable 18; echo done; sleep 30 && ipfw set disable 18"
+.Pp
+Here if everything goes well, you press control-C before the "sleep"
+terminates, and your ruleset will be left active.
+Otherwise, e.g.\& if
+you cannot access your box, the ruleset will be disabled after
+the sleep terminates thus restoring the previous situation.
+.Pp
+To show rules of the specific set:
+.Pp
+.Dl "ipfw set 18 show"
+.Pp
+To show rules of the disabled set:
+.Pp
+.Dl "ipfw -S set 18 show"
+.Pp
+To clear a specific rule counters of the specific set:
+.Pp
+.Dl "ipfw set 18 zero NN"
+.Pp
+To delete a specific rule of the specific set:
+.Pp
+.Dl "ipfw set 18 delete NN"
+.Ss NAT, REDIRECT AND LSNAT
+First redirect all the traffic to nat instance 123:
+.Pp
+.Dl "ipfw add nat 123 all from any to any"
+.Pp
+Then to configure nat instance 123 to alias all the outgoing traffic with ip
+192.168.0.123, blocking all incoming connections, trying to keep
+same ports on both sides, clearing aliasing table on address change
+and keeping a log of traffic/link statistics:
+.Pp
+.Dl "ipfw nat 123 config ip 192.168.0.123 log deny_in reset same_ports"
+.Pp
+Or to change address of instance 123, aliasing table will be cleared (see
+reset option):
+.Pp
+.Dl "ipfw nat 123 config ip 10.0.0.1"
+.Pp
+To see configuration of nat instance 123:
+.Pp
+.Dl "ipfw nat 123 show config"
+.Pp
+To show logs of all the instances in range 111-999:
+.Pp
+.Dl "ipfw nat 111-999 show"
+.Pp
+To see configurations of all instances:
+.Pp
+.Dl "ipfw nat show config"
+.Pp
+Or a redirect rule with mixed modes could looks like:
+.Pp
+.Dl "ipfw nat 123 config redirect_addr 10.0.0.1 10.0.0.66"
+.Dl " redirect_port tcp 192.168.0.1:80 500"
+.Dl " redirect_proto udp 192.168.1.43 192.168.1.1"
+.Dl " redirect_addr 192.168.0.10,192.168.0.11"
+.Dl " 10.0.0.100 # LSNAT"
+.Dl " redirect_port tcp 192.168.0.1:80,192.168.0.10:22"
+.Dl " 500 # LSNAT"
+.Pp
+or it could be split in:
+.Pp
+.Dl "ipfw nat 1 config redirect_addr 10.0.0.1 10.0.0.66"
+.Dl "ipfw nat 2 config redirect_port tcp 192.168.0.1:80 500"
+.Dl "ipfw nat 3 config redirect_proto udp 192.168.1.43 192.168.1.1"
+.Dl "ipfw nat 4 config redirect_addr 192.168.0.10,192.168.0.11,192.168.0.12"
+.Dl " 10.0.0.100"
+.Dl "ipfw nat 5 config redirect_port tcp"
+.Dl " 192.168.0.1:80,192.168.0.10:22,192.168.0.20:25 500"
+.Sh SEE ALSO
+.Xr cpp 1 ,
+.Xr m4 1 ,
+.Xr altq 4 ,
+.Xr divert 4 ,
+.Xr dummynet 4 ,
+.Xr if_bridge 4 ,
+.Xr ip 4 ,
+.Xr ipfirewall 4 ,
+.Xr ng_ipfw 4 ,
+.Xr protocols 5 ,
+.Xr services 5 ,
+.Xr init 8 ,
+.Xr kldload 8 ,
+.Xr reboot 8 ,
+.Xr sysctl 8 ,
+.Xr syslogd 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 2.0 .
+.Nm dummynet
+was introduced in
+.Fx 2.2.8 .
+Stateful extensions were introduced in
+.Fx 4.0 .
+.Nm ipfw2
+was introduced in Summer 2002.
+.Sh AUTHORS
+.An Ugen J. S. Antsilevich ,
+.An Poul-Henning Kamp ,
+.An Alex Nash ,
+.An Archie Cobbs ,
+.An Luigi Rizzo .
+.Pp
+.An -nosplit
+API based upon code written by
+.An Daniel Boulet
+for BSDI.
+.Pp
+Dummynet has been introduced by Luigi Rizzo in 1997-1998.
+.Pp
+Some early work (1999-2000) on the
+.Nm dummynet
+traffic shaper supported by Akamba Corp.
+.Pp
+The ipfw core (ipfw2) has been completely redesigned and
+reimplemented by Luigi Rizzo in summer 2002.
+Further
+actions and
+options have been added by various developer over the years.
+.Pp
+.An -nosplit
+In-kernel NAT support written by
+.An Paolo Pisati Aq Mt piso@FreeBSD.org
+as part of a Summer of Code 2005 project.
+.Pp
+SCTP
+.Nm nat
+support has been developed by
+.An The Centre for Advanced Internet Architectures (CAIA) Aq http://www.caia.swin.edu.au .
+The primary developers and maintainers are David Hayes and Jason But.
+For further information visit:
+.Aq http://www.caia.swin.edu.au/urp/SONATA
+.Pp
+Delay profiles have been developed by Alessandro Cerri and
+Luigi Rizzo, supported by the
+European Commission within Projects Onelab and Onelab2.
+.Sh BUGS
+The syntax has grown over the years and sometimes it might be confusing.
+Unfortunately, backward compatibility prevents cleaning up mistakes
+made in the definition of the syntax.
+.Pp
+.Em !!! WARNING !!!
+.Pp
+Misconfiguring the firewall can put your computer in an unusable state,
+possibly shutting down network services and requiring console access to
+regain control of it.
+.Pp
+Incoming packet fragments diverted by
+.Cm divert
+are reassembled before delivery to the socket.
+The action used on those packet is the one from the
+rule which matches the first fragment of the packet.
+.Pp
+Packets diverted to userland, and then reinserted by a userland process
+may lose various packet attributes.
+The packet source interface name
+will be preserved if it is shorter than 8 bytes and the userland process
+saves and reuses the sockaddr_in
+(as does
+.Xr natd 8 ) ;
+otherwise, it may be lost.
+If a packet is reinserted in this manner, later rules may be incorrectly
+applied, making the order of
+.Cm divert
+rules in the rule sequence very important.
+.Pp
+Dummynet drops all packets with IPv6 link-local addresses.
+.Pp
+Rules using
+.Cm uid
+or
+.Cm gid
+may not behave as expected.
+In particular, incoming SYN packets may
+have no uid or gid associated with them since they do not yet belong
+to a TCP connection, and the uid/gid associated with a packet may not
+be as expected if the associated process calls
+.Xr setuid 2
+or similar system calls.
+.Pp
+Rule syntax is subject to the command line environment and some patterns
+may need to be escaped with the backslash character
+or quoted appropriately.
+.Pp
+Due to the architecture of
+.Xr libalias 3 ,
+ipfw nat is not compatible with the TCP segmentation offloading (TSO).
+Thus, to reliably nat your network traffic, please disable TSO
+on your NICs using
+.Xr ifconfig 8 .
+.Pp
+ICMP error messages are not implicitly matched by dynamic rules
+for the respective conversations.
+To avoid failures of network error detection and path MTU discovery,
+ICMP error messages may need to be allowed explicitly through static
+rules.
+.Pp
+Rules using
+.Cm call
+and
+.Cm return
+actions may lead to confusing behaviour if ruleset has mistakes,
+and/or interaction with other subsystems (netgraph, dummynet, etc.) is used.
+One possible case for this is packet leaving
+.Nm
+in subroutine on the input pass, while later on output encountering unpaired
+.Cm return
+first.
+As the call stack is kept intact after input pass, packet will suddenly
+return to the rule number used on input pass, not on output one.
+Order of processing should be checked carefully to avoid such mistakes.
diff --git a/sbin/ipfw/ipfw2.c b/sbin/ipfw/ipfw2.c
new file mode 100644
index 0000000..687d707
--- /dev/null
+++ b/sbin/ipfw/ipfw2.c
@@ -0,0 +1,5085 @@
+/*
+ * Copyright (c) 2002-2003 Luigi Rizzo
+ * Copyright (c) 1996 Alex Nash, Paul Traina, Poul-Henning Kamp
+ * Copyright (c) 1994 Ugen J.S.Antsilevich
+ *
+ * Idea and grammar partially left from:
+ * Copyright (c) 1993 Daniel Boulet
+ *
+ * Redistribution and use in source forms, with and without modification,
+ * are permitted provided that this entire comment appears intact.
+ *
+ * Redistribution in binary form may occur without any restrictions.
+ * Obviously, it would be nice if you gave credit where credit is due
+ * but requiring it would be too onerous.
+ *
+ * This software is provided ``AS IS'' without any warranties of any kind.
+ *
+ * NEW command line interface for IP firewall facility
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <sys/sysctl.h>
+
+#include "ipfw2.h"
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <grp.h>
+#include <netdb.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <time.h> /* ctime */
+#include <timeconv.h> /* _long_to_time */
+#include <unistd.h>
+#include <fcntl.h>
+#include <stddef.h> /* offsetof */
+
+#include <net/ethernet.h>
+#include <net/if.h> /* only IFNAMSIZ */
+#include <netinet/in.h>
+#include <netinet/in_systm.h> /* only n_short, n_long */
+#include <netinet/ip.h>
+#include <netinet/ip_icmp.h>
+#include <netinet/ip_fw.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+
+struct cmdline_opts co; /* global options */
+
+struct format_opts {
+ int bcwidth;
+ int pcwidth;
+ int show_counters;
+ int show_time; /* show timestamp */
+ uint32_t set_mask; /* enabled sets mask */
+ uint32_t flags; /* request flags */
+ uint32_t first; /* first rule to request */
+ uint32_t last; /* last rule to request */
+ uint32_t dcnt; /* number of dynamic states */
+ ipfw_obj_ctlv *tstate; /* table state data */
+};
+
+int resvd_set_number = RESVD_SET;
+
+int ipfw_socket = -1;
+
+#define CHECK_LENGTH(v, len) do { \
+ if ((v) < (len)) \
+ errx(EX_DATAERR, "Rule too long"); \
+ } while (0)
+/*
+ * Check if we have enough space in cmd buffer. Note that since
+ * first 8? u32 words are reserved by reserved header, full cmd
+ * buffer can't be used, so we need to protect from buffer overrun
+ * only. At the beginnig, cblen is less than actual buffer size by
+ * size of ipfw_insn_u32 instruction + 1 u32 work. This eliminates need
+ * for checking small instructions fitting in given range.
+ * We also (ab)use the fact that ipfw_insn is always the first field
+ * for any custom instruction.
+ */
+#define CHECK_CMDLEN CHECK_LENGTH(cblen, F_LEN((ipfw_insn *)cmd))
+
+#define GET_UINT_ARG(arg, min, max, tok, s_x) do { \
+ if (!av[0]) \
+ errx(EX_USAGE, "%s: missing argument", match_value(s_x, tok)); \
+ if (_substrcmp(*av, "tablearg") == 0) { \
+ arg = IP_FW_TARG; \
+ break; \
+ } \
+ \
+ { \
+ long _xval; \
+ char *end; \
+ \
+ _xval = strtol(*av, &end, 10); \
+ \
+ if (!isdigit(**av) || *end != '\0' || (_xval == 0 && errno == EINVAL)) \
+ errx(EX_DATAERR, "%s: invalid argument: %s", \
+ match_value(s_x, tok), *av); \
+ \
+ if (errno == ERANGE || _xval < min || _xval > max) \
+ errx(EX_DATAERR, "%s: argument is out of range (%u..%u): %s", \
+ match_value(s_x, tok), min, max, *av); \
+ \
+ if (_xval == IP_FW_TARG) \
+ errx(EX_DATAERR, "%s: illegal argument value: %s", \
+ match_value(s_x, tok), *av); \
+ arg = _xval; \
+ } \
+} while (0)
+
+static struct _s_x f_tcpflags[] = {
+ { "syn", TH_SYN },
+ { "fin", TH_FIN },
+ { "ack", TH_ACK },
+ { "psh", TH_PUSH },
+ { "rst", TH_RST },
+ { "urg", TH_URG },
+ { "tcp flag", 0 },
+ { NULL, 0 }
+};
+
+static struct _s_x f_tcpopts[] = {
+ { "mss", IP_FW_TCPOPT_MSS },
+ { "maxseg", IP_FW_TCPOPT_MSS },
+ { "window", IP_FW_TCPOPT_WINDOW },
+ { "sack", IP_FW_TCPOPT_SACK },
+ { "ts", IP_FW_TCPOPT_TS },
+ { "timestamp", IP_FW_TCPOPT_TS },
+ { "cc", IP_FW_TCPOPT_CC },
+ { "tcp option", 0 },
+ { NULL, 0 }
+};
+
+/*
+ * IP options span the range 0 to 255 so we need to remap them
+ * (though in fact only the low 5 bits are significant).
+ */
+static struct _s_x f_ipopts[] = {
+ { "ssrr", IP_FW_IPOPT_SSRR},
+ { "lsrr", IP_FW_IPOPT_LSRR},
+ { "rr", IP_FW_IPOPT_RR},
+ { "ts", IP_FW_IPOPT_TS},
+ { "ip option", 0 },
+ { NULL, 0 }
+};
+
+static struct _s_x f_iptos[] = {
+ { "lowdelay", IPTOS_LOWDELAY},
+ { "throughput", IPTOS_THROUGHPUT},
+ { "reliability", IPTOS_RELIABILITY},
+ { "mincost", IPTOS_MINCOST},
+ { "congestion", IPTOS_ECN_CE},
+ { "ecntransport", IPTOS_ECN_ECT0},
+ { "ip tos option", 0},
+ { NULL, 0 }
+};
+
+struct _s_x f_ipdscp[] = {
+ { "af11", IPTOS_DSCP_AF11 >> 2 }, /* 001010 */
+ { "af12", IPTOS_DSCP_AF12 >> 2 }, /* 001100 */
+ { "af13", IPTOS_DSCP_AF13 >> 2 }, /* 001110 */
+ { "af21", IPTOS_DSCP_AF21 >> 2 }, /* 010010 */
+ { "af22", IPTOS_DSCP_AF22 >> 2 }, /* 010100 */
+ { "af23", IPTOS_DSCP_AF23 >> 2 }, /* 010110 */
+ { "af31", IPTOS_DSCP_AF31 >> 2 }, /* 011010 */
+ { "af32", IPTOS_DSCP_AF32 >> 2 }, /* 011100 */
+ { "af33", IPTOS_DSCP_AF33 >> 2 }, /* 011110 */
+ { "af41", IPTOS_DSCP_AF41 >> 2 }, /* 100010 */
+ { "af42", IPTOS_DSCP_AF42 >> 2 }, /* 100100 */
+ { "af43", IPTOS_DSCP_AF43 >> 2 }, /* 100110 */
+ { "be", IPTOS_DSCP_CS0 >> 2 }, /* 000000 */
+ { "ef", IPTOS_DSCP_EF >> 2 }, /* 101110 */
+ { "cs0", IPTOS_DSCP_CS0 >> 2 }, /* 000000 */
+ { "cs1", IPTOS_DSCP_CS1 >> 2 }, /* 001000 */
+ { "cs2", IPTOS_DSCP_CS2 >> 2 }, /* 010000 */
+ { "cs3", IPTOS_DSCP_CS3 >> 2 }, /* 011000 */
+ { "cs4", IPTOS_DSCP_CS4 >> 2 }, /* 100000 */
+ { "cs5", IPTOS_DSCP_CS5 >> 2 }, /* 101000 */
+ { "cs6", IPTOS_DSCP_CS6 >> 2 }, /* 110000 */
+ { "cs7", IPTOS_DSCP_CS7 >> 2 }, /* 100000 */
+ { NULL, 0 }
+};
+
+static struct _s_x limit_masks[] = {
+ {"all", DYN_SRC_ADDR|DYN_SRC_PORT|DYN_DST_ADDR|DYN_DST_PORT},
+ {"src-addr", DYN_SRC_ADDR},
+ {"src-port", DYN_SRC_PORT},
+ {"dst-addr", DYN_DST_ADDR},
+ {"dst-port", DYN_DST_PORT},
+ {NULL, 0}
+};
+
+/*
+ * we use IPPROTO_ETHERTYPE as a fake protocol id to call the print routines
+ * This is only used in this code.
+ */
+#define IPPROTO_ETHERTYPE 0x1000
+static struct _s_x ether_types[] = {
+ /*
+ * Note, we cannot use "-:&/" in the names because they are field
+ * separators in the type specifications. Also, we use s = NULL as
+ * end-delimiter, because a type of 0 can be legal.
+ */
+ { "ip", 0x0800 },
+ { "ipv4", 0x0800 },
+ { "ipv6", 0x86dd },
+ { "arp", 0x0806 },
+ { "rarp", 0x8035 },
+ { "vlan", 0x8100 },
+ { "loop", 0x9000 },
+ { "trail", 0x1000 },
+ { "at", 0x809b },
+ { "atalk", 0x809b },
+ { "aarp", 0x80f3 },
+ { "pppoe_disc", 0x8863 },
+ { "pppoe_sess", 0x8864 },
+ { "ipx_8022", 0x00E0 },
+ { "ipx_8023", 0x0000 },
+ { "ipx_ii", 0x8137 },
+ { "ipx_snap", 0x8137 },
+ { "ipx", 0x8137 },
+ { "ns", 0x0600 },
+ { NULL, 0 }
+};
+
+
+static struct _s_x rule_actions[] = {
+ { "accept", TOK_ACCEPT },
+ { "pass", TOK_ACCEPT },
+ { "allow", TOK_ACCEPT },
+ { "permit", TOK_ACCEPT },
+ { "count", TOK_COUNT },
+ { "pipe", TOK_PIPE },
+ { "queue", TOK_QUEUE },
+ { "divert", TOK_DIVERT },
+ { "tee", TOK_TEE },
+ { "netgraph", TOK_NETGRAPH },
+ { "ngtee", TOK_NGTEE },
+ { "fwd", TOK_FORWARD },
+ { "forward", TOK_FORWARD },
+ { "skipto", TOK_SKIPTO },
+ { "deny", TOK_DENY },
+ { "drop", TOK_DENY },
+ { "reject", TOK_REJECT },
+ { "reset6", TOK_RESET6 },
+ { "reset", TOK_RESET },
+ { "unreach6", TOK_UNREACH6 },
+ { "unreach", TOK_UNREACH },
+ { "check-state", TOK_CHECKSTATE },
+ { "//", TOK_COMMENT },
+ { "nat", TOK_NAT },
+ { "reass", TOK_REASS },
+ { "setfib", TOK_SETFIB },
+ { "setdscp", TOK_SETDSCP },
+ { "call", TOK_CALL },
+ { "return", TOK_RETURN },
+ { NULL, 0 } /* terminator */
+};
+
+static struct _s_x rule_action_params[] = {
+ { "altq", TOK_ALTQ },
+ { "log", TOK_LOG },
+ { "tag", TOK_TAG },
+ { "untag", TOK_UNTAG },
+ { NULL, 0 } /* terminator */
+};
+
+/*
+ * The 'lookup' instruction accepts one of the following arguments.
+ * -1 is a terminator for the list.
+ * Arguments are passed as v[1] in O_DST_LOOKUP options.
+ */
+static int lookup_key[] = {
+ TOK_DSTIP, TOK_SRCIP, TOK_DSTPORT, TOK_SRCPORT,
+ TOK_UID, TOK_JAIL, TOK_DSCP, -1 };
+
+static struct _s_x rule_options[] = {
+ { "tagged", TOK_TAGGED },
+ { "uid", TOK_UID },
+ { "gid", TOK_GID },
+ { "jail", TOK_JAIL },
+ { "in", TOK_IN },
+ { "limit", TOK_LIMIT },
+ { "keep-state", TOK_KEEPSTATE },
+ { "bridged", TOK_LAYER2 },
+ { "layer2", TOK_LAYER2 },
+ { "out", TOK_OUT },
+ { "diverted", TOK_DIVERTED },
+ { "diverted-loopback", TOK_DIVERTEDLOOPBACK },
+ { "diverted-output", TOK_DIVERTEDOUTPUT },
+ { "xmit", TOK_XMIT },
+ { "recv", TOK_RECV },
+ { "via", TOK_VIA },
+ { "fragment", TOK_FRAG },
+ { "frag", TOK_FRAG },
+ { "fib", TOK_FIB },
+ { "ipoptions", TOK_IPOPTS },
+ { "ipopts", TOK_IPOPTS },
+ { "iplen", TOK_IPLEN },
+ { "ipid", TOK_IPID },
+ { "ipprecedence", TOK_IPPRECEDENCE },
+ { "dscp", TOK_DSCP },
+ { "iptos", TOK_IPTOS },
+ { "ipttl", TOK_IPTTL },
+ { "ipversion", TOK_IPVER },
+ { "ipver", TOK_IPVER },
+ { "estab", TOK_ESTAB },
+ { "established", TOK_ESTAB },
+ { "setup", TOK_SETUP },
+ { "sockarg", TOK_SOCKARG },
+ { "tcpdatalen", TOK_TCPDATALEN },
+ { "tcpflags", TOK_TCPFLAGS },
+ { "tcpflgs", TOK_TCPFLAGS },
+ { "tcpoptions", TOK_TCPOPTS },
+ { "tcpopts", TOK_TCPOPTS },
+ { "tcpseq", TOK_TCPSEQ },
+ { "tcpack", TOK_TCPACK },
+ { "tcpwin", TOK_TCPWIN },
+ { "icmptype", TOK_ICMPTYPES },
+ { "icmptypes", TOK_ICMPTYPES },
+ { "dst-ip", TOK_DSTIP },
+ { "src-ip", TOK_SRCIP },
+ { "dst-port", TOK_DSTPORT },
+ { "src-port", TOK_SRCPORT },
+ { "proto", TOK_PROTO },
+ { "MAC", TOK_MAC },
+ { "mac", TOK_MAC },
+ { "mac-type", TOK_MACTYPE },
+ { "verrevpath", TOK_VERREVPATH },
+ { "versrcreach", TOK_VERSRCREACH },
+ { "antispoof", TOK_ANTISPOOF },
+ { "ipsec", TOK_IPSEC },
+ { "icmp6type", TOK_ICMP6TYPES },
+ { "icmp6types", TOK_ICMP6TYPES },
+ { "ext6hdr", TOK_EXT6HDR},
+ { "flow-id", TOK_FLOWID},
+ { "ipv6", TOK_IPV6},
+ { "ip6", TOK_IPV6},
+ { "ipv4", TOK_IPV4},
+ { "ip4", TOK_IPV4},
+ { "dst-ipv6", TOK_DSTIP6},
+ { "dst-ip6", TOK_DSTIP6},
+ { "src-ipv6", TOK_SRCIP6},
+ { "src-ip6", TOK_SRCIP6},
+ { "lookup", TOK_LOOKUP},
+ { "flow", TOK_FLOW},
+ { "//", TOK_COMMENT },
+
+ { "not", TOK_NOT }, /* pseudo option */
+ { "!", /* escape ? */ TOK_NOT }, /* pseudo option */
+ { "or", TOK_OR }, /* pseudo option */
+ { "|", /* escape */ TOK_OR }, /* pseudo option */
+ { "{", TOK_STARTBRACE }, /* pseudo option */
+ { "(", TOK_STARTBRACE }, /* pseudo option */
+ { "}", TOK_ENDBRACE }, /* pseudo option */
+ { ")", TOK_ENDBRACE }, /* pseudo option */
+ { NULL, 0 } /* terminator */
+};
+
+void bprint_uint_arg(struct buf_pr *bp, const char *str, uint32_t arg);
+static int ipfw_get_config(struct cmdline_opts *co, struct format_opts *fo,
+ ipfw_cfg_lheader **pcfg, size_t *psize);
+static int ipfw_show_config(struct cmdline_opts *co, struct format_opts *fo,
+ ipfw_cfg_lheader *cfg, size_t sz, int ac, char **av);
+static void ipfw_list_tifaces(void);
+
+struct tidx;
+static uint16_t pack_object(struct tidx *tstate, char *name, int otype);
+static uint16_t pack_table(struct tidx *tstate, char *name);
+
+static char *table_search_ctlv(ipfw_obj_ctlv *ctlv, uint16_t idx);
+static void object_sort_ctlv(ipfw_obj_ctlv *ctlv);
+
+/*
+ * Simple string buffer API.
+ * Used to simplify buffer passing between function and for
+ * transparent overrun handling.
+ */
+
+/*
+ * Allocates new buffer of given size @sz.
+ *
+ * Returns 0 on success.
+ */
+int
+bp_alloc(struct buf_pr *b, size_t size)
+{
+ memset(b, 0, sizeof(struct buf_pr));
+
+ if ((b->buf = calloc(1, size)) == NULL)
+ return (ENOMEM);
+
+ b->ptr = b->buf;
+ b->size = size;
+ b->avail = b->size;
+
+ return (0);
+}
+
+void
+bp_free(struct buf_pr *b)
+{
+
+ free(b->buf);
+}
+
+/*
+ * Flushes buffer so new writer start from beginning.
+ */
+void
+bp_flush(struct buf_pr *b)
+{
+
+ b->ptr = b->buf;
+ b->avail = b->size;
+}
+
+/*
+ * Print message specified by @format and args.
+ * Automatically manage buffer space and transparently handle
+ * buffer overruns.
+ *
+ * Returns number of bytes that should have been printed.
+ */
+int
+bprintf(struct buf_pr *b, char *format, ...)
+{
+ va_list args;
+ int i;
+
+ va_start(args, format);
+
+ i = vsnprintf(b->ptr, b->avail, format, args);
+ va_end(args);
+
+ if (i > b->avail || i < 0) {
+ /* Overflow or print error */
+ b->avail = 0;
+ } else {
+ b->ptr += i;
+ b->avail -= i;
+ }
+
+ b->needed += i;
+
+ return (i);
+}
+
+/*
+ * Special values printer for tablearg-aware opcodes.
+ */
+void
+bprint_uint_arg(struct buf_pr *bp, const char *str, uint32_t arg)
+{
+
+ if (str != NULL)
+ bprintf(bp, "%s", str);
+ if (arg == IP_FW_TARG)
+ bprintf(bp, "tablearg");
+ else
+ bprintf(bp, "%u", arg);
+}
+
+/*
+ * Helper routine to print a possibly unaligned uint64_t on
+ * various platform. If width > 0, print the value with
+ * the desired width, followed by a space;
+ * otherwise, return the required width.
+ */
+int
+pr_u64(struct buf_pr *b, uint64_t *pd, int width)
+{
+#ifdef TCC
+#define U64_FMT "I64"
+#else
+#define U64_FMT "llu"
+#endif
+ uint64_t u;
+ unsigned long long d;
+
+ bcopy (pd, &u, sizeof(u));
+ d = u;
+ return (width > 0) ?
+ bprintf(b, "%*" U64_FMT " ", width, d) :
+ snprintf(NULL, 0, "%" U64_FMT, d) ;
+#undef U64_FMT
+}
+
+
+void *
+safe_calloc(size_t number, size_t size)
+{
+ void *ret = calloc(number, size);
+
+ if (ret == NULL)
+ err(EX_OSERR, "calloc");
+ return ret;
+}
+
+void *
+safe_realloc(void *ptr, size_t size)
+{
+ void *ret = realloc(ptr, size);
+
+ if (ret == NULL)
+ err(EX_OSERR, "realloc");
+ return ret;
+}
+
+/*
+ * Compare things like interface or table names.
+ */
+int
+stringnum_cmp(const char *a, const char *b)
+{
+ int la, lb;
+
+ la = strlen(a);
+ lb = strlen(b);
+
+ if (la > lb)
+ return (1);
+ else if (la < lb)
+ return (-01);
+
+ return (strcmp(a, b));
+}
+
+
+/*
+ * conditionally runs the command.
+ * Selected options or negative -> getsockopt
+ */
+int
+do_cmd(int optname, void *optval, uintptr_t optlen)
+{
+ int i;
+
+ if (co.test_only)
+ return 0;
+
+ if (ipfw_socket == -1)
+ ipfw_socket = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
+ if (ipfw_socket < 0)
+ err(EX_UNAVAILABLE, "socket");
+
+ if (optname == IP_FW_GET || optname == IP_DUMMYNET_GET ||
+ optname == IP_FW_ADD || optname == IP_FW3 ||
+ optname == IP_FW_NAT_GET_CONFIG ||
+ optname < 0 ||
+ optname == IP_FW_NAT_GET_LOG) {
+ if (optname < 0)
+ optname = -optname;
+ i = getsockopt(ipfw_socket, IPPROTO_IP, optname, optval,
+ (socklen_t *)optlen);
+ } else {
+ i = setsockopt(ipfw_socket, IPPROTO_IP, optname, optval, optlen);
+ }
+ return i;
+}
+
+/*
+ * do_set3 - pass ipfw control cmd to kernel
+ * @optname: option name
+ * @optval: pointer to option data
+ * @optlen: option length
+ *
+ * Assumes op3 header is already embedded.
+ * Calls setsockopt() with IP_FW3 as kernel-visible opcode.
+ * Returns 0 on success or errno otherwise.
+ */
+int
+do_set3(int optname, ip_fw3_opheader *op3, uintptr_t optlen)
+{
+
+ if (co.test_only)
+ return (0);
+
+ if (ipfw_socket == -1)
+ ipfw_socket = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
+ if (ipfw_socket < 0)
+ err(EX_UNAVAILABLE, "socket");
+
+ op3->opcode = optname;
+
+ return (setsockopt(ipfw_socket, IPPROTO_IP, IP_FW3, op3, optlen));
+}
+
+/*
+ * do_get3 - pass ipfw control cmd to kernel
+ * @optname: option name
+ * @optval: pointer to option data
+ * @optlen: pointer to option length
+ *
+ * Assumes op3 header is already embedded.
+ * Calls getsockopt() with IP_FW3 as kernel-visible opcode.
+ * Returns 0 on success or errno otherwise.
+ */
+int
+do_get3(int optname, ip_fw3_opheader *op3, size_t *optlen)
+{
+ int error;
+
+ if (co.test_only)
+ return (0);
+
+ if (ipfw_socket == -1)
+ ipfw_socket = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
+ if (ipfw_socket < 0)
+ err(EX_UNAVAILABLE, "socket");
+
+ op3->opcode = optname;
+
+ error = getsockopt(ipfw_socket, IPPROTO_IP, IP_FW3, op3,
+ (socklen_t *)optlen);
+
+ return (error);
+}
+
+/**
+ * match_token takes a table and a string, returns the value associated
+ * with the string (-1 in case of failure).
+ */
+int
+match_token(struct _s_x *table, char *string)
+{
+ struct _s_x *pt;
+ uint i = strlen(string);
+
+ for (pt = table ; i && pt->s != NULL ; pt++)
+ if (strlen(pt->s) == i && !bcmp(string, pt->s, i))
+ return pt->x;
+ return (-1);
+}
+
+/**
+ * match_token takes a table and a string, returns the value associated
+ * with the string for the best match.
+ *
+ * Returns:
+ * value from @table for matched records
+ * -1 for non-matched records
+ * -2 if more than one records match @string.
+ */
+int
+match_token_relaxed(struct _s_x *table, char *string)
+{
+ struct _s_x *pt, *m;
+ int i, c;
+
+ i = strlen(string);
+ c = 0;
+
+ for (pt = table ; i != 0 && pt->s != NULL ; pt++) {
+ if (strncmp(pt->s, string, i) != 0)
+ continue;
+ m = pt;
+ c++;
+ }
+
+ if (c == 1)
+ return (m->x);
+
+ return (c > 0 ? -2: -1);
+}
+
+/**
+ * match_value takes a table and a value, returns the string associated
+ * with the value (NULL in case of failure).
+ */
+char const *
+match_value(struct _s_x *p, int value)
+{
+ for (; p->s != NULL; p++)
+ if (p->x == value)
+ return p->s;
+ return NULL;
+}
+
+size_t
+concat_tokens(char *buf, size_t bufsize, struct _s_x *table, char *delimiter)
+{
+ struct _s_x *pt;
+ int l;
+ size_t sz;
+
+ for (sz = 0, pt = table ; pt->s != NULL; pt++) {
+ l = snprintf(buf + sz, bufsize - sz, "%s%s",
+ (sz == 0) ? "" : delimiter, pt->s);
+ sz += l;
+ bufsize += l;
+ if (sz > bufsize)
+ return (bufsize);
+ }
+
+ return (sz);
+}
+
+/*
+ * helper function to process a set of flags and set bits in the
+ * appropriate masks.
+ */
+int
+fill_flags(struct _s_x *flags, char *p, char **e, uint32_t *set,
+ uint32_t *clear)
+{
+ char *q; /* points to the separator */
+ int val;
+ uint32_t *which; /* mask we are working on */
+
+ while (p && *p) {
+ if (*p == '!') {
+ p++;
+ which = clear;
+ } else
+ which = set;
+ q = strchr(p, ',');
+ if (q)
+ *q++ = '\0';
+ val = match_token(flags, p);
+ if (val <= 0) {
+ if (e != NULL)
+ *e = p;
+ return (-1);
+ }
+ *which |= (uint32_t)val;
+ p = q;
+ }
+ return (0);
+}
+
+void
+print_flags_buffer(char *buf, size_t sz, struct _s_x *list, uint32_t set)
+{
+ char const *comma = "";
+ int i, l;
+
+ for (i = 0; list[i].x != 0; i++) {
+ if ((set & list[i].x) == 0)
+ continue;
+
+ set &= ~list[i].x;
+ l = snprintf(buf, sz, "%s%s", comma, list[i].s);
+ if (l >= sz)
+ return;
+ comma = ",";
+ buf += l;
+ sz -=l;
+ }
+}
+
+/*
+ * _substrcmp takes two strings and returns 1 if they do not match,
+ * and 0 if they match exactly or the first string is a sub-string
+ * of the second. A warning is printed to stderr in the case that the
+ * first string is a sub-string of the second.
+ *
+ * This function will be removed in the future through the usual
+ * deprecation process.
+ */
+int
+_substrcmp(const char *str1, const char* str2)
+{
+
+ if (strncmp(str1, str2, strlen(str1)) != 0)
+ return 1;
+
+ if (strlen(str1) != strlen(str2))
+ warnx("DEPRECATED: '%s' matched '%s' as a sub-string",
+ str1, str2);
+ return 0;
+}
+
+/*
+ * _substrcmp2 takes three strings and returns 1 if the first two do not match,
+ * and 0 if they match exactly or the second string is a sub-string
+ * of the first. A warning is printed to stderr in the case that the
+ * first string does not match the third.
+ *
+ * This function exists to warn about the bizarre construction
+ * strncmp(str, "by", 2) which is used to allow people to use a shortcut
+ * for "bytes". The problem is that in addition to accepting "by",
+ * "byt", "byte", and "bytes", it also excepts "by_rabid_dogs" and any
+ * other string beginning with "by".
+ *
+ * This function will be removed in the future through the usual
+ * deprecation process.
+ */
+int
+_substrcmp2(const char *str1, const char* str2, const char* str3)
+{
+
+ if (strncmp(str1, str2, strlen(str2)) != 0)
+ return 1;
+
+ if (strcmp(str1, str3) != 0)
+ warnx("DEPRECATED: '%s' matched '%s'",
+ str1, str3);
+ return 0;
+}
+
+/*
+ * prints one port, symbolic or numeric
+ */
+static void
+print_port(struct buf_pr *bp, int proto, uint16_t port)
+{
+
+ if (proto == IPPROTO_ETHERTYPE) {
+ char const *s;
+
+ if (co.do_resolv && (s = match_value(ether_types, port)) )
+ bprintf(bp, "%s", s);
+ else
+ bprintf(bp, "0x%04x", port);
+ } else {
+ struct servent *se = NULL;
+ if (co.do_resolv) {
+ struct protoent *pe = getprotobynumber(proto);
+
+ se = getservbyport(htons(port), pe ? pe->p_name : NULL);
+ }
+ if (se)
+ bprintf(bp, "%s", se->s_name);
+ else
+ bprintf(bp, "%d", port);
+ }
+}
+
+static struct _s_x _port_name[] = {
+ {"dst-port", O_IP_DSTPORT},
+ {"src-port", O_IP_SRCPORT},
+ {"ipid", O_IPID},
+ {"iplen", O_IPLEN},
+ {"ipttl", O_IPTTL},
+ {"mac-type", O_MAC_TYPE},
+ {"tcpdatalen", O_TCPDATALEN},
+ {"tcpwin", O_TCPWIN},
+ {"tagged", O_TAGGED},
+ {NULL, 0}
+};
+
+/*
+ * Print the values in a list 16-bit items of the types above.
+ * XXX todo: add support for mask.
+ */
+static void
+print_newports(struct buf_pr *bp, ipfw_insn_u16 *cmd, int proto, int opcode)
+{
+ uint16_t *p = cmd->ports;
+ int i;
+ char const *sep;
+
+ if (opcode != 0) {
+ sep = match_value(_port_name, opcode);
+ if (sep == NULL)
+ sep = "???";
+ bprintf(bp, " %s", sep);
+ }
+ sep = " ";
+ for (i = F_LEN((ipfw_insn *)cmd) - 1; i > 0; i--, p += 2) {
+ bprintf(bp, "%s", sep);
+ print_port(bp, proto, p[0]);
+ if (p[0] != p[1]) {
+ bprintf(bp, "-");
+ print_port(bp, proto, p[1]);
+ }
+ sep = ",";
+ }
+}
+
+/*
+ * Like strtol, but also translates service names into port numbers
+ * for some protocols.
+ * In particular:
+ * proto == -1 disables the protocol check;
+ * proto == IPPROTO_ETHERTYPE looks up an internal table
+ * proto == <some value in /etc/protocols> matches the values there.
+ * Returns *end == s in case the parameter is not found.
+ */
+static int
+strtoport(char *s, char **end, int base, int proto)
+{
+ char *p, *buf;
+ char *s1;
+ int i;
+
+ *end = s; /* default - not found */
+ if (*s == '\0')
+ return 0; /* not found */
+
+ if (isdigit(*s))
+ return strtol(s, end, base);
+
+ /*
+ * find separator. '\\' escapes the next char.
+ */
+ for (s1 = s; *s1 && (isalnum(*s1) || *s1 == '\\') ; s1++)
+ if (*s1 == '\\' && s1[1] != '\0')
+ s1++;
+
+ buf = safe_calloc(s1 - s + 1, 1);
+
+ /*
+ * copy into a buffer skipping backslashes
+ */
+ for (p = s, i = 0; p != s1 ; p++)
+ if (*p != '\\')
+ buf[i++] = *p;
+ buf[i++] = '\0';
+
+ if (proto == IPPROTO_ETHERTYPE) {
+ i = match_token(ether_types, buf);
+ free(buf);
+ if (i != -1) { /* found */
+ *end = s1;
+ return i;
+ }
+ } else {
+ struct protoent *pe = NULL;
+ struct servent *se;
+
+ if (proto != 0)
+ pe = getprotobynumber(proto);
+ setservent(1);
+ se = getservbyname(buf, pe ? pe->p_name : NULL);
+ free(buf);
+ if (se != NULL) {
+ *end = s1;
+ return ntohs(se->s_port);
+ }
+ }
+ return 0; /* not found */
+}
+
+/*
+ * Fill the body of the command with the list of port ranges.
+ */
+static int
+fill_newports(ipfw_insn_u16 *cmd, char *av, int proto, int cblen)
+{
+ uint16_t a, b, *p = cmd->ports;
+ int i = 0;
+ char *s = av;
+
+ while (*s) {
+ a = strtoport(av, &s, 0, proto);
+ if (s == av) /* empty or invalid argument */
+ return (0);
+
+ CHECK_LENGTH(cblen, i + 2);
+
+ switch (*s) {
+ case '-': /* a range */
+ av = s + 1;
+ b = strtoport(av, &s, 0, proto);
+ /* Reject expressions like '1-abc' or '1-2-3'. */
+ if (s == av || (*s != ',' && *s != '\0'))
+ return (0);
+ p[0] = a;
+ p[1] = b;
+ break;
+ case ',': /* comma separated list */
+ case '\0':
+ p[0] = p[1] = a;
+ break;
+ default:
+ warnx("port list: invalid separator <%c> in <%s>",
+ *s, av);
+ return (0);
+ }
+
+ i++;
+ p += 2;
+ av = s + 1;
+ }
+ if (i > 0) {
+ if (i + 1 > F_LEN_MASK)
+ errx(EX_DATAERR, "too many ports/ranges\n");
+ cmd->o.len |= i + 1; /* leave F_NOT and F_OR untouched */
+ }
+ return (i);
+}
+
+/*
+ * Fill the body of the command with the list of DiffServ codepoints.
+ */
+static void
+fill_dscp(ipfw_insn *cmd, char *av, int cblen)
+{
+ uint32_t *low, *high;
+ char *s = av, *a;
+ int code;
+
+ cmd->opcode = O_DSCP;
+ cmd->len |= F_INSN_SIZE(ipfw_insn_u32) + 1;
+
+ CHECK_CMDLEN;
+
+ low = (uint32_t *)(cmd + 1);
+ high = low + 1;
+
+ *low = 0;
+ *high = 0;
+
+ while (s != NULL) {
+ a = strchr(s, ',');
+
+ if (a != NULL)
+ *a++ = '\0';
+
+ if (isalpha(*s)) {
+ if ((code = match_token(f_ipdscp, s)) == -1)
+ errx(EX_DATAERR, "Unknown DSCP code");
+ } else {
+ code = strtoul(s, NULL, 10);
+ if (code < 0 || code > 63)
+ errx(EX_DATAERR, "Invalid DSCP value");
+ }
+
+ if (code > 32)
+ *high |= 1 << (code - 32);
+ else
+ *low |= 1 << code;
+
+ s = a;
+ }
+}
+
+static struct _s_x icmpcodes[] = {
+ { "net", ICMP_UNREACH_NET },
+ { "host", ICMP_UNREACH_HOST },
+ { "protocol", ICMP_UNREACH_PROTOCOL },
+ { "port", ICMP_UNREACH_PORT },
+ { "needfrag", ICMP_UNREACH_NEEDFRAG },
+ { "srcfail", ICMP_UNREACH_SRCFAIL },
+ { "net-unknown", ICMP_UNREACH_NET_UNKNOWN },
+ { "host-unknown", ICMP_UNREACH_HOST_UNKNOWN },
+ { "isolated", ICMP_UNREACH_ISOLATED },
+ { "net-prohib", ICMP_UNREACH_NET_PROHIB },
+ { "host-prohib", ICMP_UNREACH_HOST_PROHIB },
+ { "tosnet", ICMP_UNREACH_TOSNET },
+ { "toshost", ICMP_UNREACH_TOSHOST },
+ { "filter-prohib", ICMP_UNREACH_FILTER_PROHIB },
+ { "host-precedence", ICMP_UNREACH_HOST_PRECEDENCE },
+ { "precedence-cutoff", ICMP_UNREACH_PRECEDENCE_CUTOFF },
+ { NULL, 0 }
+};
+
+static void
+fill_reject_code(u_short *codep, char *str)
+{
+ int val;
+ char *s;
+
+ val = strtoul(str, &s, 0);
+ if (s == str || *s != '\0' || val >= 0x100)
+ val = match_token(icmpcodes, str);
+ if (val < 0)
+ errx(EX_DATAERR, "unknown ICMP unreachable code ``%s''", str);
+ *codep = val;
+ return;
+}
+
+static void
+print_reject_code(struct buf_pr *bp, uint16_t code)
+{
+ char const *s;
+
+ if ((s = match_value(icmpcodes, code)) != NULL)
+ bprintf(bp, "unreach %s", s);
+ else
+ bprintf(bp, "unreach %u", code);
+}
+
+/*
+ * Returns the number of bits set (from left) in a contiguous bitmask,
+ * or -1 if the mask is not contiguous.
+ * XXX this needs a proper fix.
+ * This effectively works on masks in big-endian (network) format.
+ * when compiled on little endian architectures.
+ *
+ * First bit is bit 7 of the first byte -- note, for MAC addresses,
+ * the first bit on the wire is bit 0 of the first byte.
+ * len is the max length in bits.
+ */
+int
+contigmask(uint8_t *p, int len)
+{
+ int i, n;
+
+ for (i=0; i<len ; i++)
+ if ( (p[i/8] & (1 << (7 - (i%8)))) == 0) /* first bit unset */
+ break;
+ for (n=i+1; n < len; n++)
+ if ( (p[n/8] & (1 << (7 - (n%8)))) != 0)
+ return -1; /* mask not contiguous */
+ return i;
+}
+
+/*
+ * print flags set/clear in the two bitmasks passed as parameters.
+ * There is a specialized check for f_tcpflags.
+ */
+static void
+print_flags(struct buf_pr *bp, char const *name, ipfw_insn *cmd,
+ struct _s_x *list)
+{
+ char const *comma = "";
+ int i;
+ uint8_t set = cmd->arg1 & 0xff;
+ uint8_t clear = (cmd->arg1 >> 8) & 0xff;
+
+ if (list == f_tcpflags && set == TH_SYN && clear == TH_ACK) {
+ bprintf(bp, " setup");
+ return;
+ }
+
+ bprintf(bp, " %s ", name);
+ for (i=0; list[i].x != 0; i++) {
+ if (set & list[i].x) {
+ set &= ~list[i].x;
+ bprintf(bp, "%s%s", comma, list[i].s);
+ comma = ",";
+ }
+ if (clear & list[i].x) {
+ clear &= ~list[i].x;
+ bprintf(bp, "%s!%s", comma, list[i].s);
+ comma = ",";
+ }
+ }
+}
+
+
+/*
+ * Print the ip address contained in a command.
+ */
+static void
+print_ip(struct buf_pr *bp, struct format_opts *fo, ipfw_insn_ip *cmd,
+ char const *s)
+{
+ struct hostent *he = NULL;
+ struct in_addr *ia;
+ uint32_t len = F_LEN((ipfw_insn *)cmd);
+ uint32_t *a = ((ipfw_insn_u32 *)cmd)->d;
+ char *t;
+
+ if (cmd->o.opcode == O_IP_DST_LOOKUP && len > F_INSN_SIZE(ipfw_insn_u32)) {
+ uint32_t d = a[1];
+ const char *arg = "<invalid>";
+
+ if (d < sizeof(lookup_key)/sizeof(lookup_key[0]))
+ arg = match_value(rule_options, lookup_key[d]);
+ t = table_search_ctlv(fo->tstate, ((ipfw_insn *)cmd)->arg1);
+ bprintf(bp, "%s lookup %s %s", cmd->o.len & F_NOT ? " not": "",
+ arg, t);
+ return;
+ }
+ bprintf(bp, "%s%s ", cmd->o.len & F_NOT ? " not": "", s);
+
+ if (cmd->o.opcode == O_IP_SRC_ME || cmd->o.opcode == O_IP_DST_ME) {
+ bprintf(bp, "me");
+ return;
+ }
+ if (cmd->o.opcode == O_IP_SRC_LOOKUP ||
+ cmd->o.opcode == O_IP_DST_LOOKUP) {
+ t = table_search_ctlv(fo->tstate, ((ipfw_insn *)cmd)->arg1);
+ bprintf(bp, "table(%s", t);
+ if (len == F_INSN_SIZE(ipfw_insn_u32))
+ bprintf(bp, ",%u", *a);
+ bprintf(bp, ")");
+ return;
+ }
+ if (cmd->o.opcode == O_IP_SRC_SET || cmd->o.opcode == O_IP_DST_SET) {
+ uint32_t x, *map = (uint32_t *)&(cmd->mask);
+ int i, j;
+ char comma = '{';
+
+ x = cmd->o.arg1 - 1;
+ x = htonl( ~x );
+ cmd->addr.s_addr = htonl(cmd->addr.s_addr);
+ bprintf(bp, "%s/%d", inet_ntoa(cmd->addr),
+ contigmask((uint8_t *)&x, 32));
+ x = cmd->addr.s_addr = htonl(cmd->addr.s_addr);
+ x &= 0xff; /* base */
+ /*
+ * Print bits and ranges.
+ * Locate first bit set (i), then locate first bit unset (j).
+ * If we have 3+ consecutive bits set, then print them as a
+ * range, otherwise only print the initial bit and rescan.
+ */
+ for (i=0; i < cmd->o.arg1; i++)
+ if (map[i/32] & (1<<(i & 31))) {
+ for (j=i+1; j < cmd->o.arg1; j++)
+ if (!(map[ j/32] & (1<<(j & 31))))
+ break;
+ bprintf(bp, "%c%d", comma, i+x);
+ if (j>i+2) { /* range has at least 3 elements */
+ bprintf(bp, "-%d", j-1+x);
+ i = j-1;
+ }
+ comma = ',';
+ }
+ bprintf(bp, "}");
+ return;
+ }
+ /*
+ * len == 2 indicates a single IP, whereas lists of 1 or more
+ * addr/mask pairs have len = (2n+1). We convert len to n so we
+ * use that to count the number of entries.
+ */
+ for (len = len / 2; len > 0; len--, a += 2) {
+ int mb = /* mask length */
+ (cmd->o.opcode == O_IP_SRC || cmd->o.opcode == O_IP_DST) ?
+ 32 : contigmask((uint8_t *)&(a[1]), 32);
+ if (mb == 32 && co.do_resolv)
+ he = gethostbyaddr((char *)&(a[0]), sizeof(u_long), AF_INET);
+ if (he != NULL) /* resolved to name */
+ bprintf(bp, "%s", he->h_name);
+ else if (mb == 0) /* any */
+ bprintf(bp, "any");
+ else { /* numeric IP followed by some kind of mask */
+ ia = (struct in_addr *)&a[0];
+ bprintf(bp, "%s", inet_ntoa(*ia));
+ if (mb < 0) {
+ ia = (struct in_addr *)&a[1];
+ bprintf(bp, ":%s", inet_ntoa(*ia));
+ } else if (mb < 32)
+ bprintf(bp, "/%d", mb);
+ }
+ if (len > 1)
+ bprintf(bp, ",");
+ }
+}
+
+/*
+ * prints a MAC address/mask pair
+ */
+static void
+print_mac(struct buf_pr *bp, uint8_t *addr, uint8_t *mask)
+{
+ int l = contigmask(mask, 48);
+
+ if (l == 0)
+ bprintf(bp, " any");
+ else {
+ bprintf(bp, " %02x:%02x:%02x:%02x:%02x:%02x",
+ addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]);
+ if (l == -1)
+ bprintf(bp, "&%02x:%02x:%02x:%02x:%02x:%02x",
+ mask[0], mask[1], mask[2],
+ mask[3], mask[4], mask[5]);
+ else if (l < 48)
+ bprintf(bp, "/%d", l);
+ }
+}
+
+static void
+fill_icmptypes(ipfw_insn_u32 *cmd, char *av)
+{
+ uint8_t type;
+
+ cmd->d[0] = 0;
+ while (*av) {
+ if (*av == ',')
+ av++;
+
+ type = strtoul(av, &av, 0);
+
+ if (*av != ',' && *av != '\0')
+ errx(EX_DATAERR, "invalid ICMP type");
+
+ if (type > 31)
+ errx(EX_DATAERR, "ICMP type out of range");
+
+ cmd->d[0] |= 1 << type;
+ }
+ cmd->o.opcode = O_ICMPTYPE;
+ cmd->o.len |= F_INSN_SIZE(ipfw_insn_u32);
+}
+
+static void
+print_icmptypes(struct buf_pr *bp, ipfw_insn_u32 *cmd)
+{
+ int i;
+ char sep= ' ';
+
+ bprintf(bp, " icmptypes");
+ for (i = 0; i < 32; i++) {
+ if ( (cmd->d[0] & (1 << (i))) == 0)
+ continue;
+ bprintf(bp, "%c%d", sep, i);
+ sep = ',';
+ }
+}
+
+static void
+print_dscp(struct buf_pr *bp, ipfw_insn_u32 *cmd)
+{
+ int i, c;
+ uint32_t *v;
+ char sep= ' ';
+ const char *code;
+
+ bprintf(bp, " dscp");
+ i = 0;
+ c = 0;
+ v = cmd->d;
+ while (i < 64) {
+ if (*v & (1 << i)) {
+ if ((code = match_value(f_ipdscp, i)) != NULL)
+ bprintf(bp, "%c%s", sep, code);
+ else
+ bprintf(bp, "%c%d", sep, i);
+ sep = ',';
+ }
+
+ if ((++i % 32) == 0)
+ v++;
+ }
+}
+
+/*
+ * show_ipfw() prints the body of an ipfw rule.
+ * Because the standard rule has at least proto src_ip dst_ip, we use
+ * a helper function to produce these entries if not provided explicitly.
+ * The first argument is the list of fields we have, the second is
+ * the list of fields we want to be printed.
+ *
+ * Special cases if we have provided a MAC header:
+ * + if the rule does not contain IP addresses/ports, do not print them;
+ * + if the rule does not contain an IP proto, print "all" instead of "ip";
+ *
+ * Once we have 'have_options', IP header fields are printed as options.
+ */
+#define HAVE_PROTO 0x0001
+#define HAVE_SRCIP 0x0002
+#define HAVE_DSTIP 0x0004
+#define HAVE_PROTO4 0x0008
+#define HAVE_PROTO6 0x0010
+#define HAVE_IP 0x0100
+#define HAVE_OPTIONS 0x8000
+
+static void
+show_prerequisites(struct buf_pr *bp, int *flags, int want, int cmd)
+{
+ (void)cmd; /* UNUSED */
+ if (co.comment_only)
+ return;
+ if ( (*flags & HAVE_IP) == HAVE_IP)
+ *flags |= HAVE_OPTIONS;
+
+ if ( !(*flags & HAVE_OPTIONS)) {
+ if ( !(*flags & HAVE_PROTO) && (want & HAVE_PROTO)) {
+ if ( (*flags & HAVE_PROTO4))
+ bprintf(bp, " ip4");
+ else if ( (*flags & HAVE_PROTO6))
+ bprintf(bp, " ip6");
+ else
+ bprintf(bp, " ip");
+ }
+ if ( !(*flags & HAVE_SRCIP) && (want & HAVE_SRCIP))
+ bprintf(bp, " from any");
+ if ( !(*flags & HAVE_DSTIP) && (want & HAVE_DSTIP))
+ bprintf(bp, " to any");
+ }
+ *flags |= want;
+}
+
+static void
+show_static_rule(struct cmdline_opts *co, struct format_opts *fo,
+ struct buf_pr *bp, struct ip_fw_rule *rule, struct ip_fw_bcounter *cntr)
+{
+ static int twidth = 0;
+ int l;
+ ipfw_insn *cmd, *tagptr = NULL;
+ const char *comment = NULL; /* ptr to comment if we have one */
+ int proto = 0; /* default */
+ int flags = 0; /* prerequisites */
+ ipfw_insn_log *logptr = NULL; /* set if we find an O_LOG */
+ ipfw_insn_altq *altqptr = NULL; /* set if we find an O_ALTQ */
+ int or_block = 0; /* we are in an or block */
+ uint32_t uval;
+
+ if ((fo->set_mask & (1 << rule->set)) == 0) {
+ /* disabled mask */
+ if (!co->show_sets)
+ return;
+ else
+ bprintf(bp, "# DISABLED ");
+ }
+ bprintf(bp, "%05u ", rule->rulenum);
+
+ /* Print counters if enabled */
+ if (fo->pcwidth > 0 || fo->bcwidth > 0) {
+ pr_u64(bp, &cntr->pcnt, fo->pcwidth);
+ pr_u64(bp, &cntr->bcnt, fo->bcwidth);
+ }
+
+ if (co->do_time == 2)
+ bprintf(bp, "%10u ", cntr->timestamp);
+ else if (co->do_time == 1) {
+ char timestr[30];
+ time_t t = (time_t)0;
+
+ if (twidth == 0) {
+ strcpy(timestr, ctime(&t));
+ *strchr(timestr, '\n') = '\0';
+ twidth = strlen(timestr);
+ }
+ if (cntr->timestamp > 0) {
+ t = _long_to_time(cntr->timestamp);
+
+ strcpy(timestr, ctime(&t));
+ *strchr(timestr, '\n') = '\0';
+ bprintf(bp, "%s ", timestr);
+ } else {
+ bprintf(bp, "%*s", twidth, " ");
+ }
+ }
+
+ if (co->show_sets)
+ bprintf(bp, "set %d ", rule->set);
+
+ /*
+ * print the optional "match probability"
+ */
+ if (rule->cmd_len > 0) {
+ cmd = rule->cmd ;
+ if (cmd->opcode == O_PROB) {
+ ipfw_insn_u32 *p = (ipfw_insn_u32 *)cmd;
+ double d = 1.0 * p->d[0];
+
+ d = (d / 0x7fffffff);
+ bprintf(bp, "prob %f ", d);
+ }
+ }
+
+ /*
+ * first print actions
+ */
+ for (l = rule->cmd_len - rule->act_ofs, cmd = ACTION_PTR(rule);
+ l > 0 ; l -= F_LEN(cmd), cmd += F_LEN(cmd)) {
+ switch(cmd->opcode) {
+ case O_CHECK_STATE:
+ bprintf(bp, "check-state");
+ /* avoid printing anything else */
+ flags = HAVE_PROTO | HAVE_SRCIP |
+ HAVE_DSTIP | HAVE_IP;
+ break;
+
+ case O_ACCEPT:
+ bprintf(bp, "allow");
+ break;
+
+ case O_COUNT:
+ bprintf(bp, "count");
+ break;
+
+ case O_DENY:
+ bprintf(bp, "deny");
+ break;
+
+ case O_REJECT:
+ if (cmd->arg1 == ICMP_REJECT_RST)
+ bprintf(bp, "reset");
+ else if (cmd->arg1 == ICMP_UNREACH_HOST)
+ bprintf(bp, "reject");
+ else
+ print_reject_code(bp, cmd->arg1);
+ break;
+
+ case O_UNREACH6:
+ if (cmd->arg1 == ICMP6_UNREACH_RST)
+ bprintf(bp, "reset6");
+ else
+ print_unreach6_code(cmd->arg1);
+ break;
+
+ case O_SKIPTO:
+ bprint_uint_arg(bp, "skipto ", cmd->arg1);
+ break;
+
+ case O_PIPE:
+ bprint_uint_arg(bp, "pipe ", cmd->arg1);
+ break;
+
+ case O_QUEUE:
+ bprint_uint_arg(bp, "queue ", cmd->arg1);
+ break;
+
+ case O_DIVERT:
+ bprint_uint_arg(bp, "divert ", cmd->arg1);
+ break;
+
+ case O_TEE:
+ bprint_uint_arg(bp, "tee ", cmd->arg1);
+ break;
+
+ case O_NETGRAPH:
+ bprint_uint_arg(bp, "netgraph ", cmd->arg1);
+ break;
+
+ case O_NGTEE:
+ bprint_uint_arg(bp, "ngtee ", cmd->arg1);
+ break;
+
+ case O_FORWARD_IP:
+ {
+ ipfw_insn_sa *s = (ipfw_insn_sa *)cmd;
+
+ if (s->sa.sin_addr.s_addr == INADDR_ANY) {
+ bprintf(bp, "fwd tablearg");
+ } else {
+ bprintf(bp, "fwd %s",inet_ntoa(s->sa.sin_addr));
+ }
+ if (s->sa.sin_port)
+ bprintf(bp, ",%d", s->sa.sin_port);
+ }
+ break;
+
+ case O_FORWARD_IP6:
+ {
+ char buf[INET6_ADDRSTRLEN + IF_NAMESIZE + 2];
+ ipfw_insn_sa6 *s = (ipfw_insn_sa6 *)cmd;
+
+ bprintf(bp, "fwd ");
+ if (getnameinfo((const struct sockaddr *)&s->sa,
+ sizeof(struct sockaddr_in6), buf, sizeof(buf),
+ NULL, 0, NI_NUMERICHOST) == 0)
+ bprintf(bp, "%s", buf);
+ if (s->sa.sin6_port)
+ bprintf(bp, ",%d", s->sa.sin6_port);
+ }
+ break;
+
+ case O_LOG: /* O_LOG is printed last */
+ logptr = (ipfw_insn_log *)cmd;
+ break;
+
+ case O_ALTQ: /* O_ALTQ is printed after O_LOG */
+ altqptr = (ipfw_insn_altq *)cmd;
+ break;
+
+ case O_TAG:
+ tagptr = cmd;
+ break;
+
+ case O_NAT:
+ if (cmd->arg1 != 0)
+ bprint_uint_arg(bp, "nat ", cmd->arg1);
+ else
+ bprintf(bp, "nat global");
+ break;
+
+ case O_SETFIB:
+ bprint_uint_arg(bp, "setfib ", cmd->arg1 & 0x7FFF);
+ break;
+
+ case O_SETDSCP:
+ {
+ const char *code;
+
+ if (cmd->arg1 == IP_FW_TARG) {
+ bprint_uint_arg(bp, "setdscp ", cmd->arg1);
+ break;
+ }
+ uval = cmd->arg1 & 0x3F;
+ if ((code = match_value(f_ipdscp, uval)) != NULL)
+ bprintf(bp, "setdscp %s", code);
+ else
+ bprint_uint_arg(bp, "setdscp ", uval);
+ }
+ break;
+
+ case O_REASS:
+ bprintf(bp, "reass");
+ break;
+
+ case O_CALLRETURN:
+ if (cmd->len & F_NOT)
+ bprintf(bp, "return");
+ else
+ bprint_uint_arg(bp, "call ", cmd->arg1);
+ break;
+
+ default:
+ bprintf(bp, "** unrecognized action %d len %d ",
+ cmd->opcode, cmd->len);
+ }
+ }
+ if (logptr) {
+ if (logptr->max_log > 0)
+ bprintf(bp, " log logamount %d", logptr->max_log);
+ else
+ bprintf(bp, " log");
+ }
+#ifndef NO_ALTQ
+ if (altqptr) {
+ print_altq_cmd(bp, altqptr);
+ }
+#endif
+ if (tagptr) {
+ if (tagptr->len & F_NOT)
+ bprint_uint_arg(bp, " untag ", tagptr->arg1);
+ else
+ bprint_uint_arg(bp, " tag ", tagptr->arg1);
+ }
+
+ /*
+ * then print the body.
+ */
+ for (l = rule->act_ofs, cmd = rule->cmd;
+ l > 0 ; l -= F_LEN(cmd) , cmd += F_LEN(cmd)) {
+ if ((cmd->len & F_OR) || (cmd->len & F_NOT))
+ continue;
+ if (cmd->opcode == O_IP4) {
+ flags |= HAVE_PROTO4;
+ break;
+ } else if (cmd->opcode == O_IP6) {
+ flags |= HAVE_PROTO6;
+ break;
+ }
+ }
+ if (rule->flags & IPFW_RULE_NOOPT) { /* empty rules before options */
+ if (!co->do_compact) {
+ show_prerequisites(bp, &flags, HAVE_PROTO, 0);
+ bprintf(bp, " from any to any");
+ }
+ flags |= HAVE_IP | HAVE_OPTIONS | HAVE_PROTO |
+ HAVE_SRCIP | HAVE_DSTIP;
+ }
+
+ if (co->comment_only)
+ comment = "...";
+
+ for (l = rule->act_ofs, cmd = rule->cmd;
+ l > 0 ; l -= F_LEN(cmd) , cmd += F_LEN(cmd)) {
+ /* useful alias */
+ ipfw_insn_u32 *cmd32 = (ipfw_insn_u32 *)cmd;
+
+ if (co->comment_only) {
+ if (cmd->opcode != O_NOP)
+ continue;
+ bprintf(bp, " // %s\n", (char *)(cmd + 1));
+ return;
+ }
+
+ show_prerequisites(bp, &flags, 0, cmd->opcode);
+
+ switch(cmd->opcode) {
+ case O_PROB:
+ break; /* done already */
+
+ case O_PROBE_STATE:
+ break; /* no need to print anything here */
+
+ case O_IP_SRC:
+ case O_IP_SRC_LOOKUP:
+ case O_IP_SRC_MASK:
+ case O_IP_SRC_ME:
+ case O_IP_SRC_SET:
+ show_prerequisites(bp, &flags, HAVE_PROTO, 0);
+ if (!(flags & HAVE_SRCIP))
+ bprintf(bp, " from");
+ if ((cmd->len & F_OR) && !or_block)
+ bprintf(bp, " {");
+ print_ip(bp, fo, (ipfw_insn_ip *)cmd,
+ (flags & HAVE_OPTIONS) ? " src-ip" : "");
+ flags |= HAVE_SRCIP;
+ break;
+
+ case O_IP_DST:
+ case O_IP_DST_LOOKUP:
+ case O_IP_DST_MASK:
+ case O_IP_DST_ME:
+ case O_IP_DST_SET:
+ show_prerequisites(bp, &flags, HAVE_PROTO|HAVE_SRCIP, 0);
+ if (!(flags & HAVE_DSTIP))
+ bprintf(bp, " to");
+ if ((cmd->len & F_OR) && !or_block)
+ bprintf(bp, " {");
+ print_ip(bp, fo, (ipfw_insn_ip *)cmd,
+ (flags & HAVE_OPTIONS) ? " dst-ip" : "");
+ flags |= HAVE_DSTIP;
+ break;
+
+ case O_IP6_SRC:
+ case O_IP6_SRC_MASK:
+ case O_IP6_SRC_ME:
+ show_prerequisites(bp, &flags, HAVE_PROTO, 0);
+ if (!(flags & HAVE_SRCIP))
+ bprintf(bp, " from");
+ if ((cmd->len & F_OR) && !or_block)
+ bprintf(bp, " {");
+ print_ip6(bp, (ipfw_insn_ip6 *)cmd,
+ (flags & HAVE_OPTIONS) ? " src-ip6" : "");
+ flags |= HAVE_SRCIP | HAVE_PROTO;
+ break;
+
+ case O_IP6_DST:
+ case O_IP6_DST_MASK:
+ case O_IP6_DST_ME:
+ show_prerequisites(bp, &flags, HAVE_PROTO|HAVE_SRCIP, 0);
+ if (!(flags & HAVE_DSTIP))
+ bprintf(bp, " to");
+ if ((cmd->len & F_OR) && !or_block)
+ bprintf(bp, " {");
+ print_ip6(bp, (ipfw_insn_ip6 *)cmd,
+ (flags & HAVE_OPTIONS) ? " dst-ip6" : "");
+ flags |= HAVE_DSTIP;
+ break;
+
+ case O_FLOW6ID:
+ print_flow6id(bp, (ipfw_insn_u32 *) cmd );
+ flags |= HAVE_OPTIONS;
+ break;
+
+ case O_IP_DSTPORT:
+ show_prerequisites(bp, &flags,
+ HAVE_PROTO | HAVE_SRCIP |
+ HAVE_DSTIP | HAVE_IP, 0);
+ case O_IP_SRCPORT:
+ if (flags & HAVE_DSTIP)
+ flags |= HAVE_IP;
+ show_prerequisites(bp, &flags,
+ HAVE_PROTO | HAVE_SRCIP, 0);
+ if ((cmd->len & F_OR) && !or_block)
+ bprintf(bp, " {");
+ if (cmd->len & F_NOT)
+ bprintf(bp, " not");
+ print_newports(bp, (ipfw_insn_u16 *)cmd, proto,
+ (flags & HAVE_OPTIONS) ? cmd->opcode : 0);
+ break;
+
+ case O_PROTO: {
+ struct protoent *pe = NULL;
+
+ if ((cmd->len & F_OR) && !or_block)
+ bprintf(bp, " {");
+ if (cmd->len & F_NOT)
+ bprintf(bp, " not");
+ proto = cmd->arg1;
+ pe = getprotobynumber(cmd->arg1);
+ if ((flags & (HAVE_PROTO4 | HAVE_PROTO6)) &&
+ !(flags & HAVE_PROTO))
+ show_prerequisites(bp, &flags,
+ HAVE_PROTO | HAVE_IP | HAVE_SRCIP |
+ HAVE_DSTIP | HAVE_OPTIONS, 0);
+ if (flags & HAVE_OPTIONS)
+ bprintf(bp, " proto");
+ if (pe)
+ bprintf(bp, " %s", pe->p_name);
+ else
+ bprintf(bp, " %u", cmd->arg1);
+ }
+ flags |= HAVE_PROTO;
+ break;
+
+ default: /*options ... */
+ if (!(cmd->len & (F_OR|F_NOT)))
+ if (((cmd->opcode == O_IP6) &&
+ (flags & HAVE_PROTO6)) ||
+ ((cmd->opcode == O_IP4) &&
+ (flags & HAVE_PROTO4)))
+ break;
+ show_prerequisites(bp, &flags, HAVE_PROTO | HAVE_SRCIP |
+ HAVE_DSTIP | HAVE_IP | HAVE_OPTIONS, 0);
+ if ((cmd->len & F_OR) && !or_block)
+ bprintf(bp, " {");
+ if (cmd->len & F_NOT && cmd->opcode != O_IN)
+ bprintf(bp, " not");
+ switch(cmd->opcode) {
+ case O_MACADDR2: {
+ ipfw_insn_mac *m = (ipfw_insn_mac *)cmd;
+
+ bprintf(bp, " MAC");
+ print_mac(bp, m->addr, m->mask);
+ print_mac(bp, m->addr + 6, m->mask + 6);
+ }
+ break;
+
+ case O_MAC_TYPE:
+ print_newports(bp, (ipfw_insn_u16 *)cmd,
+ IPPROTO_ETHERTYPE, cmd->opcode);
+ break;
+
+
+ case O_FRAG:
+ bprintf(bp, " frag");
+ break;
+
+ case O_FIB:
+ bprintf(bp, " fib %u", cmd->arg1 );
+ break;
+ case O_SOCKARG:
+ bprintf(bp, " sockarg");
+ break;
+
+ case O_IN:
+ bprintf(bp, cmd->len & F_NOT ? " out" : " in");
+ break;
+
+ case O_DIVERTED:
+ switch (cmd->arg1) {
+ case 3:
+ bprintf(bp, " diverted");
+ break;
+ case 1:
+ bprintf(bp, " diverted-loopback");
+ break;
+ case 2:
+ bprintf(bp, " diverted-output");
+ break;
+ default:
+ bprintf(bp, " diverted-?<%u>", cmd->arg1);
+ break;
+ }
+ break;
+
+ case O_LAYER2:
+ bprintf(bp, " layer2");
+ break;
+ case O_XMIT:
+ case O_RECV:
+ case O_VIA:
+ {
+ char const *s, *t;
+ ipfw_insn_if *cmdif = (ipfw_insn_if *)cmd;
+
+ if (cmd->opcode == O_XMIT)
+ s = "xmit";
+ else if (cmd->opcode == O_RECV)
+ s = "recv";
+ else /* if (cmd->opcode == O_VIA) */
+ s = "via";
+ if (cmdif->name[0] == '\0')
+ bprintf(bp, " %s %s", s,
+ inet_ntoa(cmdif->p.ip));
+ else if (cmdif->name[0] == '\1') {
+ /* interface table */
+ t = table_search_ctlv(fo->tstate,
+ cmdif->p.kidx);
+ bprintf(bp, " %s table(%s)", s, t);
+ } else
+ bprintf(bp, " %s %s", s, cmdif->name);
+
+ break;
+ }
+ case O_IP_FLOW_LOOKUP:
+ {
+ char *t;
+
+ t = table_search_ctlv(fo->tstate, cmd->arg1);
+ bprintf(bp, " flow table(%s", t);
+ if (F_LEN(cmd) == F_INSN_SIZE(ipfw_insn_u32))
+ bprintf(bp, ",%u",
+ ((ipfw_insn_u32 *)cmd)->d[0]);
+ bprintf(bp, ")");
+ break;
+ }
+ case O_IPID:
+ if (F_LEN(cmd) == 1)
+ bprintf(bp, " ipid %u", cmd->arg1 );
+ else
+ print_newports(bp, (ipfw_insn_u16 *)cmd, 0,
+ O_IPID);
+ break;
+
+ case O_IPTTL:
+ if (F_LEN(cmd) == 1)
+ bprintf(bp, " ipttl %u", cmd->arg1 );
+ else
+ print_newports(bp, (ipfw_insn_u16 *)cmd, 0,
+ O_IPTTL);
+ break;
+
+ case O_IPVER:
+ bprintf(bp, " ipver %u", cmd->arg1 );
+ break;
+
+ case O_IPPRECEDENCE:
+ bprintf(bp, " ipprecedence %u", cmd->arg1 >> 5);
+ break;
+
+ case O_DSCP:
+ print_dscp(bp, (ipfw_insn_u32 *)cmd);
+ break;
+
+ case O_IPLEN:
+ if (F_LEN(cmd) == 1)
+ bprintf(bp, " iplen %u", cmd->arg1 );
+ else
+ print_newports(bp, (ipfw_insn_u16 *)cmd, 0,
+ O_IPLEN);
+ break;
+
+ case O_IPOPT:
+ print_flags(bp, "ipoptions", cmd, f_ipopts);
+ break;
+
+ case O_IPTOS:
+ print_flags(bp, "iptos", cmd, f_iptos);
+ break;
+
+ case O_ICMPTYPE:
+ print_icmptypes(bp, (ipfw_insn_u32 *)cmd);
+ break;
+
+ case O_ESTAB:
+ bprintf(bp, " established");
+ break;
+
+ case O_TCPDATALEN:
+ if (F_LEN(cmd) == 1)
+ bprintf(bp, " tcpdatalen %u", cmd->arg1 );
+ else
+ print_newports(bp, (ipfw_insn_u16 *)cmd, 0,
+ O_TCPDATALEN);
+ break;
+
+ case O_TCPFLAGS:
+ print_flags(bp, "tcpflags", cmd, f_tcpflags);
+ break;
+
+ case O_TCPOPTS:
+ print_flags(bp, "tcpoptions", cmd, f_tcpopts);
+ break;
+
+ case O_TCPWIN:
+ if (F_LEN(cmd) == 1)
+ bprintf(bp, " tcpwin %u", cmd->arg1);
+ else
+ print_newports(bp, (ipfw_insn_u16 *)cmd, 0,
+ O_TCPWIN);
+ break;
+
+ case O_TCPACK:
+ bprintf(bp, " tcpack %d", ntohl(cmd32->d[0]));
+ break;
+
+ case O_TCPSEQ:
+ bprintf(bp, " tcpseq %d", ntohl(cmd32->d[0]));
+ break;
+
+ case O_UID:
+ {
+ struct passwd *pwd = getpwuid(cmd32->d[0]);
+
+ if (pwd)
+ bprintf(bp, " uid %s", pwd->pw_name);
+ else
+ bprintf(bp, " uid %u", cmd32->d[0]);
+ }
+ break;
+
+ case O_GID:
+ {
+ struct group *grp = getgrgid(cmd32->d[0]);
+
+ if (grp)
+ bprintf(bp, " gid %s", grp->gr_name);
+ else
+ bprintf(bp, " gid %u", cmd32->d[0]);
+ }
+ break;
+
+ case O_JAIL:
+ bprintf(bp, " jail %d", cmd32->d[0]);
+ break;
+
+ case O_VERREVPATH:
+ bprintf(bp, " verrevpath");
+ break;
+
+ case O_VERSRCREACH:
+ bprintf(bp, " versrcreach");
+ break;
+
+ case O_ANTISPOOF:
+ bprintf(bp, " antispoof");
+ break;
+
+ case O_IPSEC:
+ bprintf(bp, " ipsec");
+ break;
+
+ case O_NOP:
+ comment = (char *)(cmd + 1);
+ break;
+
+ case O_KEEP_STATE:
+ bprintf(bp, " keep-state");
+ break;
+
+ case O_LIMIT: {
+ struct _s_x *p = limit_masks;
+ ipfw_insn_limit *c = (ipfw_insn_limit *)cmd;
+ uint8_t x = c->limit_mask;
+ char const *comma = " ";
+
+ bprintf(bp, " limit");
+ for (; p->x != 0 ; p++)
+ if ((x & p->x) == p->x) {
+ x &= ~p->x;
+ bprintf(bp, "%s%s", comma,p->s);
+ comma = ",";
+ }
+ bprint_uint_arg(bp, " ", c->conn_limit);
+ break;
+ }
+
+ case O_IP6:
+ bprintf(bp, " ip6");
+ break;
+
+ case O_IP4:
+ bprintf(bp, " ip4");
+ break;
+
+ case O_ICMP6TYPE:
+ print_icmp6types(bp, (ipfw_insn_u32 *)cmd);
+ break;
+
+ case O_EXT_HDR:
+ print_ext6hdr(bp, (ipfw_insn *)cmd);
+ break;
+
+ case O_TAGGED:
+ if (F_LEN(cmd) == 1)
+ bprint_uint_arg(bp, " tagged ",
+ cmd->arg1);
+ else
+ print_newports(bp, (ipfw_insn_u16 *)cmd,
+ 0, O_TAGGED);
+ break;
+
+ default:
+ bprintf(bp, " [opcode %d len %d]",
+ cmd->opcode, cmd->len);
+ }
+ }
+ if (cmd->len & F_OR) {
+ bprintf(bp, " or");
+ or_block = 1;
+ } else if (or_block) {
+ bprintf(bp, " }");
+ or_block = 0;
+ }
+ }
+ show_prerequisites(bp, &flags, HAVE_PROTO | HAVE_SRCIP | HAVE_DSTIP
+ | HAVE_IP, 0);
+ if (comment)
+ bprintf(bp, " // %s", comment);
+ bprintf(bp, "\n");
+}
+
+static void
+show_dyn_state(struct cmdline_opts *co, struct format_opts *fo,
+ struct buf_pr *bp, ipfw_dyn_rule *d)
+{
+ struct protoent *pe;
+ struct in_addr a;
+ uint16_t rulenum;
+ char buf[INET6_ADDRSTRLEN];
+
+ if (!co->do_expired) {
+ if (!d->expire && !(d->dyn_type == O_LIMIT_PARENT))
+ return;
+ }
+ bcopy(&d->rule, &rulenum, sizeof(rulenum));
+ bprintf(bp, "%05d", rulenum);
+ if (fo->pcwidth > 0 || fo->bcwidth > 0) {
+ bprintf(bp, " ");
+ pr_u64(bp, &d->pcnt, fo->pcwidth);
+ pr_u64(bp, &d->bcnt, fo->bcwidth);
+ bprintf(bp, "(%ds)", d->expire);
+ }
+ switch (d->dyn_type) {
+ case O_LIMIT_PARENT:
+ bprintf(bp, " PARENT %d", d->count);
+ break;
+ case O_LIMIT:
+ bprintf(bp, " LIMIT");
+ break;
+ case O_KEEP_STATE: /* bidir, no mask */
+ bprintf(bp, " STATE");
+ break;
+ }
+
+ if ((pe = getprotobynumber(d->id.proto)) != NULL)
+ bprintf(bp, " %s", pe->p_name);
+ else
+ bprintf(bp, " proto %u", d->id.proto);
+
+ if (d->id.addr_type == 4) {
+ a.s_addr = htonl(d->id.src_ip);
+ bprintf(bp, " %s %d", inet_ntoa(a), d->id.src_port);
+
+ a.s_addr = htonl(d->id.dst_ip);
+ bprintf(bp, " <-> %s %d", inet_ntoa(a), d->id.dst_port);
+ } else if (d->id.addr_type == 6) {
+ bprintf(bp, " %s %d", inet_ntop(AF_INET6, &d->id.src_ip6, buf,
+ sizeof(buf)), d->id.src_port);
+ bprintf(bp, " <-> %s %d", inet_ntop(AF_INET6, &d->id.dst_ip6,
+ buf, sizeof(buf)), d->id.dst_port);
+ } else
+ bprintf(bp, " UNKNOWN <-> UNKNOWN\n");
+}
+
+static int
+do_range_cmd(int cmd, ipfw_range_tlv *rt)
+{
+ ipfw_range_header rh;
+ size_t sz;
+
+ memset(&rh, 0, sizeof(rh));
+ memcpy(&rh.range, rt, sizeof(*rt));
+ rh.range.head.length = sizeof(*rt);
+ rh.range.head.type = IPFW_TLV_RANGE;
+ sz = sizeof(rh);
+
+ if (do_get3(cmd, &rh.opheader, &sz) != 0)
+ return (-1);
+ /* Save number of matched objects */
+ rt->new_set = rh.range.new_set;
+ return (0);
+}
+
+/*
+ * This one handles all set-related commands
+ * ipfw set { show | enable | disable }
+ * ipfw set swap X Y
+ * ipfw set move X to Y
+ * ipfw set move rule X to Y
+ */
+void
+ipfw_sets_handler(char *av[])
+{
+ uint32_t masks[2];
+ int i;
+ uint8_t cmd, rulenum;
+ ipfw_range_tlv rt;
+ char *msg;
+ size_t size;
+
+ av++;
+ memset(&rt, 0, sizeof(rt));
+
+ if (av[0] == NULL)
+ errx(EX_USAGE, "set needs command");
+ if (_substrcmp(*av, "show") == 0) {
+ struct format_opts fo;
+ ipfw_cfg_lheader *cfg;
+
+ memset(&fo, 0, sizeof(fo));
+ if (ipfw_get_config(&co, &fo, &cfg, &size) != 0)
+ err(EX_OSERR, "requesting config failed");
+
+ for (i = 0, msg = "disable"; i < RESVD_SET; i++)
+ if ((cfg->set_mask & (1<<i)) == 0) {
+ printf("%s %d", msg, i);
+ msg = "";
+ }
+ msg = (cfg->set_mask != (uint32_t)-1) ? " enable" : "enable";
+ for (i = 0; i < RESVD_SET; i++)
+ if ((cfg->set_mask & (1<<i)) != 0) {
+ printf("%s %d", msg, i);
+ msg = "";
+ }
+ printf("\n");
+ free(cfg);
+ } else if (_substrcmp(*av, "swap") == 0) {
+ av++;
+ if ( av[0] == NULL || av[1] == NULL )
+ errx(EX_USAGE, "set swap needs 2 set numbers\n");
+ rt.set = atoi(av[0]);
+ rt.new_set = atoi(av[1]);
+ if (!isdigit(*(av[0])) || rt.set > RESVD_SET)
+ errx(EX_DATAERR, "invalid set number %s\n", av[0]);
+ if (!isdigit(*(av[1])) || rt.new_set > RESVD_SET)
+ errx(EX_DATAERR, "invalid set number %s\n", av[1]);
+ i = do_range_cmd(IP_FW_SET_SWAP, &rt);
+ } else if (_substrcmp(*av, "move") == 0) {
+ av++;
+ if (av[0] && _substrcmp(*av, "rule") == 0) {
+ rt.flags = IPFW_RCFLAG_RANGE; /* move rules to new set */
+ cmd = IP_FW_XMOVE;
+ av++;
+ } else
+ cmd = IP_FW_SET_MOVE; /* Move set to new one */
+ if (av[0] == NULL || av[1] == NULL || av[2] == NULL ||
+ av[3] != NULL || _substrcmp(av[1], "to") != 0)
+ errx(EX_USAGE, "syntax: set move [rule] X to Y\n");
+ rulenum = atoi(av[0]);
+ rt.new_set = atoi(av[2]);
+ if (cmd == IP_FW_XMOVE) {
+ rt.start_rule = rulenum;
+ rt.end_rule = rulenum;
+ } else
+ rt.set = rulenum;
+ rt.new_set = atoi(av[2]);
+ if (!isdigit(*(av[0])) || (cmd == 3 && rt.set > RESVD_SET) ||
+ (cmd == 2 && rt.start_rule == IPFW_DEFAULT_RULE) )
+ errx(EX_DATAERR, "invalid source number %s\n", av[0]);
+ if (!isdigit(*(av[2])) || rt.new_set > RESVD_SET)
+ errx(EX_DATAERR, "invalid dest. set %s\n", av[1]);
+ i = do_range_cmd(cmd, &rt);
+ } else if (_substrcmp(*av, "disable") == 0 ||
+ _substrcmp(*av, "enable") == 0 ) {
+ int which = _substrcmp(*av, "enable") == 0 ? 1 : 0;
+
+ av++;
+ masks[0] = masks[1] = 0;
+
+ while (av[0]) {
+ if (isdigit(**av)) {
+ i = atoi(*av);
+ if (i < 0 || i > RESVD_SET)
+ errx(EX_DATAERR,
+ "invalid set number %d\n", i);
+ masks[which] |= (1<<i);
+ } else if (_substrcmp(*av, "disable") == 0)
+ which = 0;
+ else if (_substrcmp(*av, "enable") == 0)
+ which = 1;
+ else
+ errx(EX_DATAERR,
+ "invalid set command %s\n", *av);
+ av++;
+ }
+ if ( (masks[0] & masks[1]) != 0 )
+ errx(EX_DATAERR,
+ "cannot enable and disable the same set\n");
+
+ rt.set = masks[0];
+ rt.new_set = masks[1];
+ i = do_range_cmd(IP_FW_SET_ENABLE, &rt);
+ if (i)
+ warn("set enable/disable: setsockopt(IP_FW_SET_ENABLE)");
+ } else
+ errx(EX_USAGE, "invalid set command %s\n", *av);
+}
+
+void
+ipfw_sysctl_handler(char *av[], int which)
+{
+ av++;
+
+ if (av[0] == NULL) {
+ warnx("missing keyword to enable/disable\n");
+ } else if (_substrcmp(*av, "firewall") == 0) {
+ sysctlbyname("net.inet.ip.fw.enable", NULL, 0,
+ &which, sizeof(which));
+ sysctlbyname("net.inet6.ip6.fw.enable", NULL, 0,
+ &which, sizeof(which));
+ } else if (_substrcmp(*av, "one_pass") == 0) {
+ sysctlbyname("net.inet.ip.fw.one_pass", NULL, 0,
+ &which, sizeof(which));
+ } else if (_substrcmp(*av, "debug") == 0) {
+ sysctlbyname("net.inet.ip.fw.debug", NULL, 0,
+ &which, sizeof(which));
+ } else if (_substrcmp(*av, "verbose") == 0) {
+ sysctlbyname("net.inet.ip.fw.verbose", NULL, 0,
+ &which, sizeof(which));
+ } else if (_substrcmp(*av, "dyn_keepalive") == 0) {
+ sysctlbyname("net.inet.ip.fw.dyn_keepalive", NULL, 0,
+ &which, sizeof(which));
+#ifndef NO_ALTQ
+ } else if (_substrcmp(*av, "altq") == 0) {
+ altq_set_enabled(which);
+#endif
+ } else {
+ warnx("unrecognize enable/disable keyword: %s\n", *av);
+ }
+}
+
+typedef void state_cb(struct cmdline_opts *co, struct format_opts *fo,
+ void *arg, void *state);
+
+static void
+prepare_format_dyn(struct cmdline_opts *co, struct format_opts *fo,
+ void *arg, void *_state)
+{
+ ipfw_dyn_rule *d;
+ int width;
+ uint8_t set;
+
+ d = (ipfw_dyn_rule *)_state;
+ /* Count _ALL_ states */
+ fo->dcnt++;
+
+ if (fo->show_counters == 0)
+ return;
+
+ if (co->use_set) {
+ /* skip states from another set */
+ bcopy((char *)&d->rule + sizeof(uint16_t), &set,
+ sizeof(uint8_t));
+ if (set != co->use_set - 1)
+ return;
+ }
+
+ width = pr_u64(NULL, &d->pcnt, 0);
+ if (width > fo->pcwidth)
+ fo->pcwidth = width;
+
+ width = pr_u64(NULL, &d->bcnt, 0);
+ if (width > fo->bcwidth)
+ fo->bcwidth = width;
+}
+
+static int
+foreach_state(struct cmdline_opts *co, struct format_opts *fo,
+ caddr_t base, size_t sz, state_cb dyn_bc, void *dyn_arg)
+{
+ int ttype;
+ state_cb *fptr;
+ void *farg;
+ ipfw_obj_tlv *tlv;
+ ipfw_obj_ctlv *ctlv;
+
+ fptr = NULL;
+ ttype = 0;
+
+ while (sz > 0) {
+ ctlv = (ipfw_obj_ctlv *)base;
+ switch (ctlv->head.type) {
+ case IPFW_TLV_DYNSTATE_LIST:
+ base += sizeof(*ctlv);
+ sz -= sizeof(*ctlv);
+ ttype = IPFW_TLV_DYN_ENT;
+ fptr = dyn_bc;
+ farg = dyn_arg;
+ break;
+ default:
+ return (sz);
+ }
+
+ while (sz > 0) {
+ tlv = (ipfw_obj_tlv *)base;
+ if (tlv->type != ttype)
+ break;
+
+ fptr(co, fo, farg, tlv + 1);
+ sz -= tlv->length;
+ base += tlv->length;
+ }
+ }
+
+ return (sz);
+}
+
+static void
+prepare_format_opts(struct cmdline_opts *co, struct format_opts *fo,
+ ipfw_obj_tlv *rtlv, int rcnt, caddr_t dynbase, size_t dynsz)
+{
+ int bcwidth, pcwidth, width;
+ int n;
+ struct ip_fw_bcounter *cntr;
+ struct ip_fw_rule *r;
+
+ bcwidth = 0;
+ pcwidth = 0;
+ if (fo->show_counters != 0) {
+ for (n = 0; n < rcnt; n++,
+ rtlv = (ipfw_obj_tlv *)((caddr_t)rtlv + rtlv->length)) {
+ cntr = (struct ip_fw_bcounter *)(rtlv + 1);
+ r = (struct ip_fw_rule *)((caddr_t)cntr + cntr->size);
+ /* skip rules from another set */
+ if (co->use_set && r->set != co->use_set - 1)
+ continue;
+
+ /* packet counter */
+ width = pr_u64(NULL, &cntr->pcnt, 0);
+ if (width > pcwidth)
+ pcwidth = width;
+
+ /* byte counter */
+ width = pr_u64(NULL, &cntr->bcnt, 0);
+ if (width > bcwidth)
+ bcwidth = width;
+ }
+ }
+ fo->bcwidth = bcwidth;
+ fo->pcwidth = pcwidth;
+
+ fo->dcnt = 0;
+ if (co->do_dynamic && dynsz > 0)
+ foreach_state(co, fo, dynbase, dynsz, prepare_format_dyn, NULL);
+}
+
+static int
+list_static_range(struct cmdline_opts *co, struct format_opts *fo,
+ struct buf_pr *bp, ipfw_obj_tlv *rtlv, int rcnt)
+{
+ int n, seen;
+ struct ip_fw_rule *r;
+ struct ip_fw_bcounter *cntr;
+ int c = 0;
+
+ for (n = seen = 0; n < rcnt; n++,
+ rtlv = (ipfw_obj_tlv *)((caddr_t)rtlv + rtlv->length)) {
+
+ if ((fo->show_counters | fo->show_time) != 0) {
+ cntr = (struct ip_fw_bcounter *)(rtlv + 1);
+ r = (struct ip_fw_rule *)((caddr_t)cntr + cntr->size);
+ } else {
+ cntr = NULL;
+ r = (struct ip_fw_rule *)(rtlv + 1);
+ }
+ if (r->rulenum > fo->last)
+ break;
+ if (co->use_set && r->set != co->use_set - 1)
+ continue;
+ if (r->rulenum >= fo->first && r->rulenum <= fo->last) {
+ show_static_rule(co, fo, bp, r, cntr);
+ printf("%s", bp->buf);
+ c += rtlv->length;
+ bp_flush(bp);
+ seen++;
+ }
+ }
+
+ return (seen);
+}
+
+static void
+list_dyn_state(struct cmdline_opts *co, struct format_opts *fo,
+ void *_arg, void *_state)
+{
+ uint16_t rulenum;
+ uint8_t set;
+ ipfw_dyn_rule *d;
+ struct buf_pr *bp;
+
+ d = (ipfw_dyn_rule *)_state;
+ bp = (struct buf_pr *)_arg;
+
+ bcopy(&d->rule, &rulenum, sizeof(rulenum));
+ if (rulenum > fo->last)
+ return;
+ if (co->use_set) {
+ bcopy((char *)&d->rule + sizeof(uint16_t),
+ &set, sizeof(uint8_t));
+ if (set != co->use_set - 1)
+ return;
+ }
+ if (rulenum >= fo->first) {
+ show_dyn_state(co, fo, bp, d);
+ printf("%s\n", bp->buf);
+ bp_flush(bp);
+ }
+}
+
+static int
+list_dyn_range(struct cmdline_opts *co, struct format_opts *fo,
+ struct buf_pr *bp, caddr_t base, size_t sz)
+{
+
+ sz = foreach_state(co, fo, base, sz, list_dyn_state, bp);
+ return (sz);
+}
+
+void
+ipfw_list(int ac, char *av[], int show_counters)
+{
+ ipfw_cfg_lheader *cfg;
+ struct format_opts sfo;
+ size_t sz;
+ int error;
+ int lac;
+ char **lav;
+ uint32_t rnum;
+ char *endptr;
+
+ if (co.test_only) {
+ fprintf(stderr, "Testing only, list disabled\n");
+ return;
+ }
+ if (co.do_pipe) {
+ dummynet_list(ac, av, show_counters);
+ return;
+ }
+
+ ac--;
+ av++;
+ memset(&sfo, 0, sizeof(sfo));
+
+ /* Determine rule range to request */
+ if (ac > 0) {
+ for (lac = ac, lav = av; lac != 0; lac--) {
+ rnum = strtoul(*lav++, &endptr, 10);
+ if (sfo.first == 0 || rnum < sfo.first)
+ sfo.first = rnum;
+
+ if (*endptr == '-')
+ rnum = strtoul(endptr + 1, &endptr, 10);
+ if (sfo.last == 0 || rnum > sfo.last)
+ sfo.last = rnum;
+ }
+ }
+
+ /* get configuraion from kernel */
+ cfg = NULL;
+ sfo.show_counters = show_counters;
+ sfo.show_time = co.do_time;
+ sfo.flags = IPFW_CFG_GET_STATIC;
+ if (co.do_dynamic != 0)
+ sfo.flags |= IPFW_CFG_GET_STATES;
+ if ((sfo.show_counters | sfo.show_time) != 0)
+ sfo.flags |= IPFW_CFG_GET_COUNTERS;
+ if (ipfw_get_config(&co, &sfo, &cfg, &sz) != 0)
+ err(EX_OSERR, "retrieving config failed");
+
+ error = ipfw_show_config(&co, &sfo, cfg, sz, ac, av);
+
+ free(cfg);
+
+ if (error != EX_OK)
+ exit(error);
+}
+
+static int
+ipfw_show_config(struct cmdline_opts *co, struct format_opts *fo,
+ ipfw_cfg_lheader *cfg, size_t sz, int ac, char *av[])
+{
+ caddr_t dynbase;
+ size_t dynsz;
+ int rcnt;
+ int exitval = EX_OK;
+ int lac;
+ char **lav;
+ char *endptr;
+ size_t readsz;
+ struct buf_pr bp;
+ ipfw_obj_ctlv *ctlv, *tstate;
+ ipfw_obj_tlv *rbase;
+
+ /*
+ * Handle tablenames TLV first, if any
+ */
+ tstate = NULL;
+ rbase = NULL;
+ dynbase = NULL;
+ dynsz = 0;
+ readsz = sizeof(*cfg);
+ rcnt = 0;
+
+ fo->set_mask = cfg->set_mask;
+
+ ctlv = (ipfw_obj_ctlv *)(cfg + 1);
+
+ if (cfg->flags & IPFW_CFG_GET_STATIC) {
+ /* We've requested static rules */
+ if (ctlv->head.type == IPFW_TLV_TBLNAME_LIST) {
+ object_sort_ctlv(ctlv);
+ fo->tstate = ctlv;
+ readsz += ctlv->head.length;
+ ctlv = (ipfw_obj_ctlv *)((caddr_t)ctlv +
+ ctlv->head.length);
+ }
+
+ if (ctlv->head.type == IPFW_TLV_RULE_LIST) {
+ rbase = (ipfw_obj_tlv *)(ctlv + 1);
+ rcnt = ctlv->count;
+ readsz += ctlv->head.length;
+ ctlv = (ipfw_obj_ctlv *)((caddr_t)ctlv +
+ ctlv->head.length);
+ }
+ }
+
+ if ((cfg->flags & IPFW_CFG_GET_STATES) && (readsz != sz)) {
+ /* We may have some dynamic states */
+ dynsz = sz - readsz;
+ /* Skip empty header */
+ if (dynsz != sizeof(ipfw_obj_ctlv))
+ dynbase = (caddr_t)ctlv;
+ else
+ dynsz = 0;
+ }
+
+ prepare_format_opts(co, fo, rbase, rcnt, dynbase, dynsz);
+ bp_alloc(&bp, 4096);
+
+ /* if no rule numbers were specified, list all rules */
+ if (ac == 0) {
+ fo->first = 0;
+ fo->last = IPFW_DEFAULT_RULE;
+ list_static_range(co, fo, &bp, rbase, rcnt);
+
+ if (co->do_dynamic && dynsz > 0) {
+ printf("## Dynamic rules (%d %zu):\n", fo->dcnt, dynsz);
+ list_dyn_range(co, fo, &bp, dynbase, dynsz);
+ }
+
+ bp_free(&bp);
+ return (EX_OK);
+ }
+
+ /* display specific rules requested on command line */
+ for (lac = ac, lav = av; lac != 0; lac--) {
+ /* convert command line rule # */
+ fo->last = fo->first = strtoul(*lav++, &endptr, 10);
+ if (*endptr == '-')
+ fo->last = strtoul(endptr + 1, &endptr, 10);
+ if (*endptr) {
+ exitval = EX_USAGE;
+ warnx("invalid rule number: %s", *(lav - 1));
+ continue;
+ }
+
+ if (list_static_range(co, fo, &bp, rbase, rcnt) == 0) {
+ /* give precedence to other error(s) */
+ if (exitval == EX_OK)
+ exitval = EX_UNAVAILABLE;
+ if (fo->first == fo->last)
+ warnx("rule %u does not exist", fo->first);
+ else
+ warnx("no rules in range %u-%u",
+ fo->first, fo->last);
+ }
+ }
+
+ if (co->do_dynamic && dynsz > 0) {
+ printf("## Dynamic rules:\n");
+ for (lac = ac, lav = av; lac != 0; lac--) {
+ fo->last = fo->first = strtoul(*lav++, &endptr, 10);
+ if (*endptr == '-')
+ fo->last = strtoul(endptr+1, &endptr, 10);
+ if (*endptr)
+ /* already warned */
+ continue;
+ list_dyn_range(co, fo, &bp, dynbase, dynsz);
+ }
+ }
+
+ bp_free(&bp);
+ return (exitval);
+}
+
+
+/*
+ * Retrieves current ipfw configuration of given type
+ * and stores its pointer to @pcfg.
+ *
+ * Caller is responsible for freeing @pcfg.
+ *
+ * Returns 0 on success.
+ */
+
+static int
+ipfw_get_config(struct cmdline_opts *co, struct format_opts *fo,
+ ipfw_cfg_lheader **pcfg, size_t *psize)
+{
+ ipfw_cfg_lheader *cfg;
+ size_t sz;
+ int i;
+
+
+ if (co->test_only != 0) {
+ fprintf(stderr, "Testing only, list disabled\n");
+ return (0);
+ }
+
+ /* Start with some data size */
+ sz = 4096;
+ cfg = NULL;
+
+ for (i = 0; i < 16; i++) {
+ if (cfg != NULL)
+ free(cfg);
+ if ((cfg = calloc(1, sz)) == NULL)
+ return (ENOMEM);
+
+ cfg->flags = fo->flags;
+ cfg->start_rule = fo->first;
+ cfg->end_rule = fo->last;
+
+ if (do_get3(IP_FW_XGET, &cfg->opheader, &sz) != 0) {
+ if (errno != ENOMEM) {
+ free(cfg);
+ return (errno);
+ }
+
+ /* Buffer size is not enough. Try to increase */
+ sz = sz * 2;
+ if (sz < cfg->size)
+ sz = cfg->size;
+ continue;
+ }
+
+ *pcfg = cfg;
+ *psize = sz;
+ return (0);
+ }
+
+ free(cfg);
+ return (ENOMEM);
+}
+
+static int
+lookup_host (char *host, struct in_addr *ipaddr)
+{
+ struct hostent *he;
+
+ if (!inet_aton(host, ipaddr)) {
+ if ((he = gethostbyname(host)) == NULL)
+ return(-1);
+ *ipaddr = *(struct in_addr *)he->h_addr_list[0];
+ }
+ return(0);
+}
+
+struct tidx {
+ ipfw_obj_ntlv *idx;
+ uint32_t count;
+ uint32_t size;
+ uint16_t counter;
+ uint8_t set;
+};
+
+static uint16_t
+pack_object(struct tidx *tstate, char *name, int otype)
+{
+ int i;
+ ipfw_obj_ntlv *ntlv;
+
+ for (i = 0; i < tstate->count; i++) {
+ if (strcmp(tstate->idx[i].name, name) != 0)
+ continue;
+ if (tstate->idx[i].set != tstate->set)
+ continue;
+ if (tstate->idx[i].head.type != otype)
+ continue;
+
+ return (tstate->idx[i].idx);
+ }
+
+ if (tstate->count + 1 > tstate->size) {
+ tstate->size += 4;
+ tstate->idx = realloc(tstate->idx, tstate->size *
+ sizeof(ipfw_obj_ntlv));
+ if (tstate->idx == NULL)
+ return (0);
+ }
+
+ ntlv = &tstate->idx[i];
+ memset(ntlv, 0, sizeof(ipfw_obj_ntlv));
+ strlcpy(ntlv->name, name, sizeof(ntlv->name));
+ ntlv->head.type = otype;
+ ntlv->head.length = sizeof(ipfw_obj_ntlv);
+ ntlv->set = tstate->set;
+ ntlv->idx = ++tstate->counter;
+ tstate->count++;
+
+ return (ntlv->idx);
+}
+
+static uint16_t
+pack_table(struct tidx *tstate, char *name)
+{
+
+ if (table_check_name(name) != 0)
+ return (0);
+
+ return (pack_object(tstate, name, IPFW_TLV_TBL_NAME));
+}
+
+static void
+fill_table(ipfw_insn *cmd, char *av, uint8_t opcode, struct tidx *tstate)
+{
+ uint32_t *d = ((ipfw_insn_u32 *)cmd)->d;
+ uint16_t uidx;
+ char *p;
+
+ if ((p = strchr(av + 6, ')')) == NULL)
+ errx(EX_DATAERR, "forgotten parenthesis: '%s'", av);
+ *p = '\0';
+ p = strchr(av + 6, ',');
+ if (p)
+ *p++ = '\0';
+
+ if ((uidx = pack_table(tstate, av + 6)) == 0)
+ errx(EX_DATAERR, "Invalid table name: %s", av + 6);
+
+ cmd->opcode = opcode;
+ cmd->arg1 = uidx;
+ if (p) {
+ cmd->len |= F_INSN_SIZE(ipfw_insn_u32);
+ d[0] = strtoul(p, NULL, 0);
+ } else
+ cmd->len |= F_INSN_SIZE(ipfw_insn);
+}
+
+
+/*
+ * fills the addr and mask fields in the instruction as appropriate from av.
+ * Update length as appropriate.
+ * The following formats are allowed:
+ * me returns O_IP_*_ME
+ * 1.2.3.4 single IP address
+ * 1.2.3.4:5.6.7.8 address:mask
+ * 1.2.3.4/24 address/mask
+ * 1.2.3.4/26{1,6,5,4,23} set of addresses in a subnet
+ * We can have multiple comma-separated address/mask entries.
+ */
+static void
+fill_ip(ipfw_insn_ip *cmd, char *av, int cblen, struct tidx *tstate)
+{
+ int len = 0;
+ uint32_t *d = ((ipfw_insn_u32 *)cmd)->d;
+
+ cmd->o.len &= ~F_LEN_MASK; /* zero len */
+
+ if (_substrcmp(av, "any") == 0)
+ return;
+
+ if (_substrcmp(av, "me") == 0) {
+ cmd->o.len |= F_INSN_SIZE(ipfw_insn);
+ return;
+ }
+
+ if (strncmp(av, "table(", 6) == 0) {
+ fill_table(&cmd->o, av, O_IP_DST_LOOKUP, tstate);
+ return;
+ }
+
+ while (av) {
+ /*
+ * After the address we can have '/' or ':' indicating a mask,
+ * ',' indicating another address follows, '{' indicating a
+ * set of addresses of unspecified size.
+ */
+ char *t = NULL, *p = strpbrk(av, "/:,{");
+ int masklen;
+ char md, nd = '\0';
+
+ CHECK_LENGTH(cblen, F_INSN_SIZE(ipfw_insn) + 2 + len);
+
+ if (p) {
+ md = *p;
+ *p++ = '\0';
+ if ((t = strpbrk(p, ",{")) != NULL) {
+ nd = *t;
+ *t = '\0';
+ }
+ } else
+ md = '\0';
+
+ if (lookup_host(av, (struct in_addr *)&d[0]) != 0)
+ errx(EX_NOHOST, "hostname ``%s'' unknown", av);
+ switch (md) {
+ case ':':
+ if (!inet_aton(p, (struct in_addr *)&d[1]))
+ errx(EX_DATAERR, "bad netmask ``%s''", p);
+ break;
+ case '/':
+ masklen = atoi(p);
+ if (masklen == 0)
+ d[1] = htonl(0); /* mask */
+ else if (masklen > 32)
+ errx(EX_DATAERR, "bad width ``%s''", p);
+ else
+ d[1] = htonl(~0 << (32 - masklen));
+ break;
+ case '{': /* no mask, assume /24 and put back the '{' */
+ d[1] = htonl(~0 << (32 - 24));
+ *(--p) = md;
+ break;
+
+ case ',': /* single address plus continuation */
+ *(--p) = md;
+ /* FALLTHROUGH */
+ case 0: /* initialization value */
+ default:
+ d[1] = htonl(~0); /* force /32 */
+ break;
+ }
+ d[0] &= d[1]; /* mask base address with mask */
+ if (t)
+ *t = nd;
+ /* find next separator */
+ if (p)
+ p = strpbrk(p, ",{");
+ if (p && *p == '{') {
+ /*
+ * We have a set of addresses. They are stored as follows:
+ * arg1 is the set size (powers of 2, 2..256)
+ * addr is the base address IN HOST FORMAT
+ * mask.. is an array of arg1 bits (rounded up to
+ * the next multiple of 32) with bits set
+ * for each host in the map.
+ */
+ uint32_t *map = (uint32_t *)&cmd->mask;
+ int low, high;
+ int i = contigmask((uint8_t *)&(d[1]), 32);
+
+ if (len > 0)
+ errx(EX_DATAERR, "address set cannot be in a list");
+ if (i < 24 || i > 31)
+ errx(EX_DATAERR, "invalid set with mask %d\n", i);
+ cmd->o.arg1 = 1<<(32-i); /* map length */
+ d[0] = ntohl(d[0]); /* base addr in host format */
+ cmd->o.opcode = O_IP_DST_SET; /* default */
+ cmd->o.len |= F_INSN_SIZE(ipfw_insn_u32) + (cmd->o.arg1+31)/32;
+ for (i = 0; i < (cmd->o.arg1+31)/32 ; i++)
+ map[i] = 0; /* clear map */
+
+ av = p + 1;
+ low = d[0] & 0xff;
+ high = low + cmd->o.arg1 - 1;
+ /*
+ * Here, i stores the previous value when we specify a range
+ * of addresses within a mask, e.g. 45-63. i = -1 means we
+ * have no previous value.
+ */
+ i = -1; /* previous value in a range */
+ while (isdigit(*av)) {
+ char *s;
+ int a = strtol(av, &s, 0);
+
+ if (s == av) { /* no parameter */
+ if (*av != '}')
+ errx(EX_DATAERR, "set not closed\n");
+ if (i != -1)
+ errx(EX_DATAERR, "incomplete range %d-", i);
+ break;
+ }
+ if (a < low || a > high)
+ errx(EX_DATAERR, "addr %d out of range [%d-%d]\n",
+ a, low, high);
+ a -= low;
+ if (i == -1) /* no previous in range */
+ i = a;
+ else { /* check that range is valid */
+ if (i > a)
+ errx(EX_DATAERR, "invalid range %d-%d",
+ i+low, a+low);
+ if (*s == '-')
+ errx(EX_DATAERR, "double '-' in range");
+ }
+ for (; i <= a; i++)
+ map[i/32] |= 1<<(i & 31);
+ i = -1;
+ if (*s == '-')
+ i = a;
+ else if (*s == '}')
+ break;
+ av = s+1;
+ }
+ return;
+ }
+ av = p;
+ if (av) /* then *av must be a ',' */
+ av++;
+
+ /* Check this entry */
+ if (d[1] == 0) { /* "any", specified as x.x.x.x/0 */
+ /*
+ * 'any' turns the entire list into a NOP.
+ * 'not any' never matches, so it is removed from the
+ * list unless it is the only item, in which case we
+ * report an error.
+ */
+ if (cmd->o.len & F_NOT) { /* "not any" never matches */
+ if (av == NULL && len == 0) /* only this entry */
+ errx(EX_DATAERR, "not any never matches");
+ }
+ /* else do nothing and skip this entry */
+ return;
+ }
+ /* A single IP can be stored in an optimized format */
+ if (d[1] == (uint32_t)~0 && av == NULL && len == 0) {
+ cmd->o.len |= F_INSN_SIZE(ipfw_insn_u32);
+ return;
+ }
+ len += 2; /* two words... */
+ d += 2;
+ } /* end while */
+ if (len + 1 > F_LEN_MASK)
+ errx(EX_DATAERR, "address list too long");
+ cmd->o.len |= len+1;
+}
+
+
+/* n2mask sets n bits of the mask */
+void
+n2mask(struct in6_addr *mask, int n)
+{
+ static int minimask[9] =
+ { 0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff };
+ u_char *p;
+
+ memset(mask, 0, sizeof(struct in6_addr));
+ p = (u_char *) mask;
+ for (; n > 0; p++, n -= 8) {
+ if (n >= 8)
+ *p = 0xff;
+ else
+ *p = minimask[n];
+ }
+ return;
+}
+
+static void
+fill_flags_cmd(ipfw_insn *cmd, enum ipfw_opcodes opcode,
+ struct _s_x *flags, char *p)
+{
+ char *e;
+ uint32_t set = 0, clear = 0;
+
+ if (fill_flags(flags, p, &e, &set, &clear) != 0)
+ errx(EX_DATAERR, "invalid flag %s", e);
+
+ cmd->opcode = opcode;
+ cmd->len = (cmd->len & (F_NOT | F_OR)) | 1;
+ cmd->arg1 = (set & 0xff) | ( (clear & 0xff) << 8);
+}
+
+
+void
+ipfw_delete(char *av[])
+{
+ int i;
+ int exitval = EX_OK;
+ int do_set = 0;
+ ipfw_range_tlv rt;
+
+ av++;
+ NEED1("missing rule specification");
+ memset(&rt, 0, sizeof(rt));
+ if ( *av && _substrcmp(*av, "set") == 0) {
+ /* Do not allow using the following syntax:
+ * ipfw set N delete set M
+ */
+ if (co.use_set)
+ errx(EX_DATAERR, "invalid syntax");
+ do_set = 1; /* delete set */
+ av++;
+ }
+
+ /* Rule number */
+ while (*av && isdigit(**av)) {
+ i = atoi(*av); av++;
+ if (co.do_nat) {
+ exitval = do_cmd(IP_FW_NAT_DEL, &i, sizeof i);
+ if (exitval) {
+ exitval = EX_UNAVAILABLE;
+ warn("rule %u not available", i);
+ }
+ } else if (co.do_pipe) {
+ exitval = ipfw_delete_pipe(co.do_pipe, i);
+ } else {
+ if (do_set != 0) {
+ rt.set = i & 31;
+ rt.flags = IPFW_RCFLAG_SET;
+ } else {
+ rt.start_rule = i & 0xffff;
+ rt.end_rule = i & 0xffff;
+ if (rt.start_rule == 0 && rt.end_rule == 0)
+ rt.flags |= IPFW_RCFLAG_ALL;
+ else
+ rt.flags |= IPFW_RCFLAG_RANGE;
+ if (co.use_set != 0) {
+ rt.set = co.use_set - 1;
+ rt.flags |= IPFW_RCFLAG_SET;
+ }
+ }
+ i = do_range_cmd(IP_FW_XDEL, &rt);
+ if (i != 0) {
+ exitval = EX_UNAVAILABLE;
+ warn("rule %u: setsockopt(IP_FW_XDEL)",
+ rt.start_rule);
+ } else if (rt.new_set == 0) {
+ exitval = EX_UNAVAILABLE;
+ if (rt.start_rule != rt.end_rule)
+ warnx("no rules rules in %u-%u range",
+ rt.start_rule, rt.end_rule);
+ else
+ warnx("rule %u not found",
+ rt.start_rule);
+ }
+ }
+ }
+ if (exitval != EX_OK)
+ exit(exitval);
+}
+
+
+/*
+ * fill the interface structure. We do not check the name as we can
+ * create interfaces dynamically, so checking them at insert time
+ * makes relatively little sense.
+ * Interface names containing '*', '?', or '[' are assumed to be shell
+ * patterns which match interfaces.
+ */
+static void
+fill_iface(ipfw_insn_if *cmd, char *arg, int cblen, struct tidx *tstate)
+{
+ char *p;
+ uint16_t uidx;
+
+ cmd->name[0] = '\0';
+ cmd->o.len |= F_INSN_SIZE(ipfw_insn_if);
+
+ CHECK_CMDLEN;
+
+ /* Parse the interface or address */
+ if (strcmp(arg, "any") == 0)
+ cmd->o.len = 0; /* effectively ignore this command */
+ else if (strncmp(arg, "table(", 6) == 0) {
+ if ((p = strchr(arg + 6, ')')) == NULL)
+ errx(EX_DATAERR, "forgotten parenthesis: '%s'", arg);
+ *p = '\0';
+ p = strchr(arg + 6, ',');
+ if (p)
+ *p++ = '\0';
+ if ((uidx = pack_table(tstate, arg + 6)) == 0)
+ errx(EX_DATAERR, "Invalid table name: %s", arg + 6);
+
+ cmd->name[0] = '\1'; /* Special value indicating table */
+ cmd->p.kidx = uidx;
+ } else if (!isdigit(*arg)) {
+ strlcpy(cmd->name, arg, sizeof(cmd->name));
+ cmd->p.glob = strpbrk(arg, "*?[") != NULL ? 1 : 0;
+ } else if (!inet_aton(arg, &cmd->p.ip))
+ errx(EX_DATAERR, "bad ip address ``%s''", arg);
+}
+
+static void
+get_mac_addr_mask(const char *p, uint8_t *addr, uint8_t *mask)
+{
+ int i;
+ size_t l;
+ char *ap, *ptr, *optr;
+ struct ether_addr *mac;
+ const char *macset = "0123456789abcdefABCDEF:";
+
+ if (strcmp(p, "any") == 0) {
+ for (i = 0; i < ETHER_ADDR_LEN; i++)
+ addr[i] = mask[i] = 0;
+ return;
+ }
+
+ optr = ptr = strdup(p);
+ if ((ap = strsep(&ptr, "&/")) != NULL && *ap != 0) {
+ l = strlen(ap);
+ if (strspn(ap, macset) != l || (mac = ether_aton(ap)) == NULL)
+ errx(EX_DATAERR, "Incorrect MAC address");
+ bcopy(mac, addr, ETHER_ADDR_LEN);
+ } else
+ errx(EX_DATAERR, "Incorrect MAC address");
+
+ if (ptr != NULL) { /* we have mask? */
+ if (p[ptr - optr - 1] == '/') { /* mask len */
+ long ml = strtol(ptr, &ap, 10);
+ if (*ap != 0 || ml > ETHER_ADDR_LEN * 8 || ml < 0)
+ errx(EX_DATAERR, "Incorrect mask length");
+ for (i = 0; ml > 0 && i < ETHER_ADDR_LEN; ml -= 8, i++)
+ mask[i] = (ml >= 8) ? 0xff: (~0) << (8 - ml);
+ } else { /* mask */
+ l = strlen(ptr);
+ if (strspn(ptr, macset) != l ||
+ (mac = ether_aton(ptr)) == NULL)
+ errx(EX_DATAERR, "Incorrect mask");
+ bcopy(mac, mask, ETHER_ADDR_LEN);
+ }
+ } else { /* default mask: ff:ff:ff:ff:ff:ff */
+ for (i = 0; i < ETHER_ADDR_LEN; i++)
+ mask[i] = 0xff;
+ }
+ for (i = 0; i < ETHER_ADDR_LEN; i++)
+ addr[i] &= mask[i];
+
+ free(optr);
+}
+
+/*
+ * helper function, updates the pointer to cmd with the length
+ * of the current command, and also cleans up the first word of
+ * the new command in case it has been clobbered before.
+ */
+static ipfw_insn *
+next_cmd(ipfw_insn *cmd, int *len)
+{
+ *len -= F_LEN(cmd);
+ CHECK_LENGTH(*len, 0);
+ cmd += F_LEN(cmd);
+ bzero(cmd, sizeof(*cmd));
+ return cmd;
+}
+
+/*
+ * Takes arguments and copies them into a comment
+ */
+static void
+fill_comment(ipfw_insn *cmd, char **av, int cblen)
+{
+ int i, l;
+ char *p = (char *)(cmd + 1);
+
+ cmd->opcode = O_NOP;
+ cmd->len = (cmd->len & (F_NOT | F_OR));
+
+ /* Compute length of comment string. */
+ for (i = 0, l = 0; av[i] != NULL; i++)
+ l += strlen(av[i]) + 1;
+ if (l == 0)
+ return;
+ if (l > 84)
+ errx(EX_DATAERR,
+ "comment too long (max 80 chars)");
+ l = 1 + (l+3)/4;
+ cmd->len = (cmd->len & (F_NOT | F_OR)) | l;
+ CHECK_CMDLEN;
+
+ for (i = 0; av[i] != NULL; i++) {
+ strcpy(p, av[i]);
+ p += strlen(av[i]);
+ *p++ = ' ';
+ }
+ *(--p) = '\0';
+}
+
+/*
+ * A function to fill simple commands of size 1.
+ * Existing flags are preserved.
+ */
+static void
+fill_cmd(ipfw_insn *cmd, enum ipfw_opcodes opcode, int flags, uint16_t arg)
+{
+ cmd->opcode = opcode;
+ cmd->len = ((cmd->len | flags) & (F_NOT | F_OR)) | 1;
+ cmd->arg1 = arg;
+}
+
+/*
+ * Fetch and add the MAC address and type, with masks. This generates one or
+ * two microinstructions, and returns the pointer to the last one.
+ */
+static ipfw_insn *
+add_mac(ipfw_insn *cmd, char *av[], int cblen)
+{
+ ipfw_insn_mac *mac;
+
+ if ( ( av[0] == NULL ) || ( av[1] == NULL ) )
+ errx(EX_DATAERR, "MAC dst src");
+
+ cmd->opcode = O_MACADDR2;
+ cmd->len = (cmd->len & (F_NOT | F_OR)) | F_INSN_SIZE(ipfw_insn_mac);
+ CHECK_CMDLEN;
+
+ mac = (ipfw_insn_mac *)cmd;
+ get_mac_addr_mask(av[0], mac->addr, mac->mask); /* dst */
+ get_mac_addr_mask(av[1], &(mac->addr[ETHER_ADDR_LEN]),
+ &(mac->mask[ETHER_ADDR_LEN])); /* src */
+ return cmd;
+}
+
+static ipfw_insn *
+add_mactype(ipfw_insn *cmd, char *av, int cblen)
+{
+ if (!av)
+ errx(EX_DATAERR, "missing MAC type");
+ if (strcmp(av, "any") != 0) { /* we have a non-null type */
+ fill_newports((ipfw_insn_u16 *)cmd, av, IPPROTO_ETHERTYPE,
+ cblen);
+ cmd->opcode = O_MAC_TYPE;
+ return cmd;
+ } else
+ return NULL;
+}
+
+static ipfw_insn *
+add_proto0(ipfw_insn *cmd, char *av, u_char *protop)
+{
+ struct protoent *pe;
+ char *ep;
+ int proto;
+
+ proto = strtol(av, &ep, 10);
+ if (*ep != '\0' || proto <= 0) {
+ if ((pe = getprotobyname(av)) == NULL)
+ return NULL;
+ proto = pe->p_proto;
+ }
+
+ fill_cmd(cmd, O_PROTO, 0, proto);
+ *protop = proto;
+ return cmd;
+}
+
+static ipfw_insn *
+add_proto(ipfw_insn *cmd, char *av, u_char *protop)
+{
+ u_char proto = IPPROTO_IP;
+
+ if (_substrcmp(av, "all") == 0 || strcmp(av, "ip") == 0)
+ ; /* do not set O_IP4 nor O_IP6 */
+ else if (strcmp(av, "ip4") == 0)
+ /* explicit "just IPv4" rule */
+ fill_cmd(cmd, O_IP4, 0, 0);
+ else if (strcmp(av, "ip6") == 0) {
+ /* explicit "just IPv6" rule */
+ proto = IPPROTO_IPV6;
+ fill_cmd(cmd, O_IP6, 0, 0);
+ } else
+ return add_proto0(cmd, av, protop);
+
+ *protop = proto;
+ return cmd;
+}
+
+static ipfw_insn *
+add_proto_compat(ipfw_insn *cmd, char *av, u_char *protop)
+{
+ u_char proto = IPPROTO_IP;
+
+ if (_substrcmp(av, "all") == 0 || strcmp(av, "ip") == 0)
+ ; /* do not set O_IP4 nor O_IP6 */
+ else if (strcmp(av, "ipv4") == 0 || strcmp(av, "ip4") == 0)
+ /* explicit "just IPv4" rule */
+ fill_cmd(cmd, O_IP4, 0, 0);
+ else if (strcmp(av, "ipv6") == 0 || strcmp(av, "ip6") == 0) {
+ /* explicit "just IPv6" rule */
+ proto = IPPROTO_IPV6;
+ fill_cmd(cmd, O_IP6, 0, 0);
+ } else
+ return add_proto0(cmd, av, protop);
+
+ *protop = proto;
+ return cmd;
+}
+
+static ipfw_insn *
+add_srcip(ipfw_insn *cmd, char *av, int cblen, struct tidx *tstate)
+{
+ fill_ip((ipfw_insn_ip *)cmd, av, cblen, tstate);
+ if (cmd->opcode == O_IP_DST_SET) /* set */
+ cmd->opcode = O_IP_SRC_SET;
+ else if (cmd->opcode == O_IP_DST_LOOKUP) /* table */
+ cmd->opcode = O_IP_SRC_LOOKUP;
+ else if (F_LEN(cmd) == F_INSN_SIZE(ipfw_insn)) /* me */
+ cmd->opcode = O_IP_SRC_ME;
+ else if (F_LEN(cmd) == F_INSN_SIZE(ipfw_insn_u32)) /* one IP */
+ cmd->opcode = O_IP_SRC;
+ else /* addr/mask */
+ cmd->opcode = O_IP_SRC_MASK;
+ return cmd;
+}
+
+static ipfw_insn *
+add_dstip(ipfw_insn *cmd, char *av, int cblen, struct tidx *tstate)
+{
+ fill_ip((ipfw_insn_ip *)cmd, av, cblen, tstate);
+ if (cmd->opcode == O_IP_DST_SET) /* set */
+ ;
+ else if (cmd->opcode == O_IP_DST_LOOKUP) /* table */
+ ;
+ else if (F_LEN(cmd) == F_INSN_SIZE(ipfw_insn)) /* me */
+ cmd->opcode = O_IP_DST_ME;
+ else if (F_LEN(cmd) == F_INSN_SIZE(ipfw_insn_u32)) /* one IP */
+ cmd->opcode = O_IP_DST;
+ else /* addr/mask */
+ cmd->opcode = O_IP_DST_MASK;
+ return cmd;
+}
+
+static struct _s_x f_reserved_keywords[] = {
+ { "altq", TOK_OR },
+ { "//", TOK_OR },
+ { "diverted", TOK_OR },
+ { "dst-port", TOK_OR },
+ { "src-port", TOK_OR },
+ { "established", TOK_OR },
+ { "keep-state", TOK_OR },
+ { "frag", TOK_OR },
+ { "icmptypes", TOK_OR },
+ { "in", TOK_OR },
+ { "out", TOK_OR },
+ { "ip6", TOK_OR },
+ { "any", TOK_OR },
+ { "to", TOK_OR },
+ { "via", TOK_OR },
+ { "{", TOK_OR },
+ { NULL, 0 } /* terminator */
+};
+
+static ipfw_insn *
+add_ports(ipfw_insn *cmd, char *av, u_char proto, int opcode, int cblen)
+{
+
+ if (match_token(f_reserved_keywords, av) != -1)
+ return (NULL);
+
+ if (fill_newports((ipfw_insn_u16 *)cmd, av, proto, cblen)) {
+ /* XXX todo: check that we have a protocol with ports */
+ cmd->opcode = opcode;
+ return cmd;
+ }
+ return NULL;
+}
+
+static ipfw_insn *
+add_src(ipfw_insn *cmd, char *av, u_char proto, int cblen, struct tidx *tstate)
+{
+ struct in6_addr a;
+ char *host, *ch, buf[INET6_ADDRSTRLEN];
+ ipfw_insn *ret = NULL;
+ int len;
+
+ /* Copy first address in set if needed */
+ if ((ch = strpbrk(av, "/,")) != NULL) {
+ len = ch - av;
+ strlcpy(buf, av, sizeof(buf));
+ if (len < sizeof(buf))
+ buf[len] = '\0';
+ host = buf;
+ } else
+ host = av;
+
+ if (proto == IPPROTO_IPV6 || strcmp(av, "me6") == 0 ||
+ inet_pton(AF_INET6, host, &a) == 1)
+ ret = add_srcip6(cmd, av, cblen);
+ /* XXX: should check for IPv4, not !IPv6 */
+ if (ret == NULL && (proto == IPPROTO_IP || strcmp(av, "me") == 0 ||
+ inet_pton(AF_INET6, host, &a) != 1))
+ ret = add_srcip(cmd, av, cblen, tstate);
+ if (ret == NULL && strcmp(av, "any") != 0)
+ ret = cmd;
+
+ return ret;
+}
+
+static ipfw_insn *
+add_dst(ipfw_insn *cmd, char *av, u_char proto, int cblen, struct tidx *tstate)
+{
+ struct in6_addr a;
+ char *host, *ch, buf[INET6_ADDRSTRLEN];
+ ipfw_insn *ret = NULL;
+ int len;
+
+ /* Copy first address in set if needed */
+ if ((ch = strpbrk(av, "/,")) != NULL) {
+ len = ch - av;
+ strlcpy(buf, av, sizeof(buf));
+ if (len < sizeof(buf))
+ buf[len] = '\0';
+ host = buf;
+ } else
+ host = av;
+
+ if (proto == IPPROTO_IPV6 || strcmp(av, "me6") == 0 ||
+ inet_pton(AF_INET6, host, &a) == 1)
+ ret = add_dstip6(cmd, av, cblen);
+ /* XXX: should check for IPv4, not !IPv6 */
+ if (ret == NULL && (proto == IPPROTO_IP || strcmp(av, "me") == 0 ||
+ inet_pton(AF_INET6, host, &a) != 1))
+ ret = add_dstip(cmd, av, cblen, tstate);
+ if (ret == NULL && strcmp(av, "any") != 0)
+ ret = cmd;
+
+ return ret;
+}
+
+/*
+ * Parse arguments and assemble the microinstructions which make up a rule.
+ * Rules are added into the 'rulebuf' and then copied in the correct order
+ * into the actual rule.
+ *
+ * The syntax for a rule starts with the action, followed by
+ * optional action parameters, and the various match patterns.
+ * In the assembled microcode, the first opcode must be an O_PROBE_STATE
+ * (generated if the rule includes a keep-state option), then the
+ * various match patterns, log/altq actions, and the actual action.
+ *
+ */
+void
+compile_rule(char *av[], uint32_t *rbuf, int *rbufsize, struct tidx *tstate)
+{
+ /*
+ * rules are added into the 'rulebuf' and then copied in
+ * the correct order into the actual rule.
+ * Some things that need to go out of order (prob, action etc.)
+ * go into actbuf[].
+ */
+ static uint32_t actbuf[255], cmdbuf[255];
+ int rblen, ablen, cblen;
+
+ ipfw_insn *src, *dst, *cmd, *action, *prev=NULL;
+ ipfw_insn *first_cmd; /* first match pattern */
+
+ struct ip_fw_rule *rule;
+
+ /*
+ * various flags used to record that we entered some fields.
+ */
+ ipfw_insn *have_state = NULL; /* check-state or keep-state */
+ ipfw_insn *have_log = NULL, *have_altq = NULL, *have_tag = NULL;
+ size_t len;
+
+ int i;
+
+ int open_par = 0; /* open parenthesis ( */
+
+ /* proto is here because it is used to fetch ports */
+ u_char proto = IPPROTO_IP; /* default protocol */
+
+ double match_prob = 1; /* match probability, default is always match */
+
+ bzero(actbuf, sizeof(actbuf)); /* actions go here */
+ bzero(cmdbuf, sizeof(cmdbuf));
+ bzero(rbuf, *rbufsize);
+
+ rule = (struct ip_fw_rule *)rbuf;
+ cmd = (ipfw_insn *)cmdbuf;
+ action = (ipfw_insn *)actbuf;
+
+ rblen = *rbufsize / sizeof(uint32_t);
+ rblen -= sizeof(struct ip_fw_rule) / sizeof(uint32_t);
+ ablen = sizeof(actbuf) / sizeof(actbuf[0]);
+ cblen = sizeof(cmdbuf) / sizeof(cmdbuf[0]);
+ cblen -= F_INSN_SIZE(ipfw_insn_u32) + 1;
+
+#define CHECK_RBUFLEN(len) { CHECK_LENGTH(rblen, len); rblen -= len; }
+#define CHECK_ACTLEN CHECK_LENGTH(ablen, action->len)
+
+ av++;
+
+ /* [rule N] -- Rule number optional */
+ if (av[0] && isdigit(**av)) {
+ rule->rulenum = atoi(*av);
+ av++;
+ }
+
+ /* [set N] -- set number (0..RESVD_SET), optional */
+ if (av[0] && av[1] && _substrcmp(*av, "set") == 0) {
+ int set = strtoul(av[1], NULL, 10);
+ if (set < 0 || set > RESVD_SET)
+ errx(EX_DATAERR, "illegal set %s", av[1]);
+ rule->set = set;
+ tstate->set = set;
+ av += 2;
+ }
+
+ /* [prob D] -- match probability, optional */
+ if (av[0] && av[1] && _substrcmp(*av, "prob") == 0) {
+ match_prob = strtod(av[1], NULL);
+
+ if (match_prob <= 0 || match_prob > 1)
+ errx(EX_DATAERR, "illegal match prob. %s", av[1]);
+ av += 2;
+ }
+
+ /* action -- mandatory */
+ NEED1("missing action");
+ i = match_token(rule_actions, *av);
+ av++;
+ action->len = 1; /* default */
+ CHECK_ACTLEN;
+ switch(i) {
+ case TOK_CHECKSTATE:
+ have_state = action;
+ action->opcode = O_CHECK_STATE;
+ break;
+
+ case TOK_ACCEPT:
+ action->opcode = O_ACCEPT;
+ break;
+
+ case TOK_DENY:
+ action->opcode = O_DENY;
+ action->arg1 = 0;
+ break;
+
+ case TOK_REJECT:
+ action->opcode = O_REJECT;
+ action->arg1 = ICMP_UNREACH_HOST;
+ break;
+
+ case TOK_RESET:
+ action->opcode = O_REJECT;
+ action->arg1 = ICMP_REJECT_RST;
+ break;
+
+ case TOK_RESET6:
+ action->opcode = O_UNREACH6;
+ action->arg1 = ICMP6_UNREACH_RST;
+ break;
+
+ case TOK_UNREACH:
+ action->opcode = O_REJECT;
+ NEED1("missing reject code");
+ fill_reject_code(&action->arg1, *av);
+ av++;
+ break;
+
+ case TOK_UNREACH6:
+ action->opcode = O_UNREACH6;
+ NEED1("missing unreach code");
+ fill_unreach6_code(&action->arg1, *av);
+ av++;
+ break;
+
+ case TOK_COUNT:
+ action->opcode = O_COUNT;
+ break;
+
+ case TOK_NAT:
+ action->opcode = O_NAT;
+ action->len = F_INSN_SIZE(ipfw_insn_nat);
+ CHECK_ACTLEN;
+ if (_substrcmp(*av, "global") == 0) {
+ action->arg1 = 0;
+ av++;
+ break;
+ } else
+ goto chkarg;
+ case TOK_QUEUE:
+ action->opcode = O_QUEUE;
+ goto chkarg;
+ case TOK_PIPE:
+ action->opcode = O_PIPE;
+ goto chkarg;
+ case TOK_SKIPTO:
+ action->opcode = O_SKIPTO;
+ goto chkarg;
+ case TOK_NETGRAPH:
+ action->opcode = O_NETGRAPH;
+ goto chkarg;
+ case TOK_NGTEE:
+ action->opcode = O_NGTEE;
+ goto chkarg;
+ case TOK_DIVERT:
+ action->opcode = O_DIVERT;
+ goto chkarg;
+ case TOK_TEE:
+ action->opcode = O_TEE;
+ goto chkarg;
+ case TOK_CALL:
+ action->opcode = O_CALLRETURN;
+chkarg:
+ if (!av[0])
+ errx(EX_USAGE, "missing argument for %s", *(av - 1));
+ if (isdigit(**av)) {
+ action->arg1 = strtoul(*av, NULL, 10);
+ if (action->arg1 <= 0 || action->arg1 >= IP_FW_TABLEARG)
+ errx(EX_DATAERR, "illegal argument for %s",
+ *(av - 1));
+ } else if (_substrcmp(*av, "tablearg") == 0) {
+ action->arg1 = IP_FW_TARG;
+ } else if (i == TOK_DIVERT || i == TOK_TEE) {
+ struct servent *s;
+ setservent(1);
+ s = getservbyname(av[0], "divert");
+ if (s != NULL)
+ action->arg1 = ntohs(s->s_port);
+ else
+ errx(EX_DATAERR, "illegal divert/tee port");
+ } else
+ errx(EX_DATAERR, "illegal argument for %s", *(av - 1));
+ av++;
+ break;
+
+ case TOK_FORWARD: {
+ /*
+ * Locate the address-port separator (':' or ',').
+ * Could be one of the following:
+ * hostname:port
+ * IPv4 a.b.c.d,port
+ * IPv4 a.b.c.d:port
+ * IPv6 w:x:y::z,port
+ * The ':' can only be used with hostname and IPv4 address.
+ * XXX-BZ Should we also support [w:x:y::z]:port?
+ */
+ struct sockaddr_storage result;
+ struct addrinfo *res;
+ char *s, *end;
+ int family;
+ u_short port_number;
+
+ NEED1("missing forward address[:port]");
+
+ /*
+ * locate the address-port separator (':' or ',')
+ */
+ s = strchr(*av, ',');
+ if (s == NULL) {
+ /* Distinguish between IPv4:port and IPv6 cases. */
+ s = strchr(*av, ':');
+ if (s && strchr(s+1, ':'))
+ s = NULL; /* no port */
+ }
+
+ port_number = 0;
+ if (s != NULL) {
+ /* Terminate host portion and set s to start of port. */
+ *(s++) = '\0';
+ i = strtoport(s, &end, 0 /* base */, 0 /* proto */);
+ if (s == end)
+ errx(EX_DATAERR,
+ "illegal forwarding port ``%s''", s);
+ port_number = (u_short)i;
+ }
+
+ if (_substrcmp(*av, "tablearg") == 0) {
+ family = PF_INET;
+ ((struct sockaddr_in*)&result)->sin_addr.s_addr =
+ INADDR_ANY;
+ } else {
+ /*
+ * Resolve the host name or address to a family and a
+ * network representation of the address.
+ */
+ if (getaddrinfo(*av, NULL, NULL, &res))
+ errx(EX_DATAERR, NULL);
+ /* Just use the first host in the answer. */
+ family = res->ai_family;
+ memcpy(&result, res->ai_addr, res->ai_addrlen);
+ freeaddrinfo(res);
+ }
+
+ if (family == PF_INET) {
+ ipfw_insn_sa *p = (ipfw_insn_sa *)action;
+
+ action->opcode = O_FORWARD_IP;
+ action->len = F_INSN_SIZE(ipfw_insn_sa);
+ CHECK_ACTLEN;
+
+ /*
+ * In the kernel we assume AF_INET and use only
+ * sin_port and sin_addr. Remember to set sin_len as
+ * the routing code seems to use it too.
+ */
+ p->sa.sin_len = sizeof(struct sockaddr_in);
+ p->sa.sin_family = AF_INET;
+ p->sa.sin_port = port_number;
+ p->sa.sin_addr.s_addr =
+ ((struct sockaddr_in *)&result)->sin_addr.s_addr;
+ } else if (family == PF_INET6) {
+ ipfw_insn_sa6 *p = (ipfw_insn_sa6 *)action;
+
+ action->opcode = O_FORWARD_IP6;
+ action->len = F_INSN_SIZE(ipfw_insn_sa6);
+ CHECK_ACTLEN;
+
+ p->sa.sin6_len = sizeof(struct sockaddr_in6);
+ p->sa.sin6_family = AF_INET6;
+ p->sa.sin6_port = port_number;
+ p->sa.sin6_flowinfo = 0;
+ p->sa.sin6_scope_id =
+ ((struct sockaddr_in6 *)&result)->sin6_scope_id;
+ bcopy(&((struct sockaddr_in6*)&result)->sin6_addr,
+ &p->sa.sin6_addr, sizeof(p->sa.sin6_addr));
+ } else {
+ errx(EX_DATAERR, "Invalid address family in forward action");
+ }
+ av++;
+ break;
+ }
+ case TOK_COMMENT:
+ /* pretend it is a 'count' rule followed by the comment */
+ action->opcode = O_COUNT;
+ av--; /* go back... */
+ break;
+
+ case TOK_SETFIB:
+ {
+ int numfibs;
+ size_t intsize = sizeof(int);
+
+ action->opcode = O_SETFIB;
+ NEED1("missing fib number");
+ if (_substrcmp(*av, "tablearg") == 0) {
+ action->arg1 = IP_FW_TARG;
+ } else {
+ action->arg1 = strtoul(*av, NULL, 10);
+ if (sysctlbyname("net.fibs", &numfibs, &intsize,
+ NULL, 0) == -1)
+ errx(EX_DATAERR, "fibs not suported.\n");
+ if (action->arg1 >= numfibs) /* Temporary */
+ errx(EX_DATAERR, "fib too large.\n");
+ /* Add high-order bit to fib to make room for tablearg*/
+ action->arg1 |= 0x8000;
+ }
+ av++;
+ break;
+ }
+
+ case TOK_SETDSCP:
+ {
+ int code;
+
+ action->opcode = O_SETDSCP;
+ NEED1("missing DSCP code");
+ if (_substrcmp(*av, "tablearg") == 0) {
+ action->arg1 = IP_FW_TARG;
+ } else if (isalpha(*av[0])) {
+ if ((code = match_token(f_ipdscp, *av)) == -1)
+ errx(EX_DATAERR, "Unknown DSCP code");
+ action->arg1 = code;
+ } else
+ action->arg1 = strtoul(*av, NULL, 10);
+ /* Add high-order bit to DSCP to make room for tablearg */
+ if (action->arg1 != IP_FW_TARG)
+ action->arg1 |= 0x8000;
+ av++;
+ break;
+ }
+
+ case TOK_REASS:
+ action->opcode = O_REASS;
+ break;
+
+ case TOK_RETURN:
+ fill_cmd(action, O_CALLRETURN, F_NOT, 0);
+ break;
+
+ default:
+ errx(EX_DATAERR, "invalid action %s\n", av[-1]);
+ }
+ action = next_cmd(action, &ablen);
+
+ /*
+ * [altq queuename] -- altq tag, optional
+ * [log [logamount N]] -- log, optional
+ *
+ * If they exist, it go first in the cmdbuf, but then it is
+ * skipped in the copy section to the end of the buffer.
+ */
+ while (av[0] != NULL && (i = match_token(rule_action_params, *av)) != -1) {
+ av++;
+ switch (i) {
+ case TOK_LOG:
+ {
+ ipfw_insn_log *c = (ipfw_insn_log *)cmd;
+ int l;
+
+ if (have_log)
+ errx(EX_DATAERR,
+ "log cannot be specified more than once");
+ have_log = (ipfw_insn *)c;
+ cmd->len = F_INSN_SIZE(ipfw_insn_log);
+ CHECK_CMDLEN;
+ cmd->opcode = O_LOG;
+ if (av[0] && _substrcmp(*av, "logamount") == 0) {
+ av++;
+ NEED1("logamount requires argument");
+ l = atoi(*av);
+ if (l < 0)
+ errx(EX_DATAERR,
+ "logamount must be positive");
+ c->max_log = l;
+ av++;
+ } else {
+ len = sizeof(c->max_log);
+ if (sysctlbyname("net.inet.ip.fw.verbose_limit",
+ &c->max_log, &len, NULL, 0) == -1) {
+ if (co.test_only) {
+ c->max_log = 0;
+ break;
+ }
+ errx(1, "sysctlbyname(\"%s\")",
+ "net.inet.ip.fw.verbose_limit");
+ }
+ }
+ }
+ break;
+
+#ifndef NO_ALTQ
+ case TOK_ALTQ:
+ {
+ ipfw_insn_altq *a = (ipfw_insn_altq *)cmd;
+
+ NEED1("missing altq queue name");
+ if (have_altq)
+ errx(EX_DATAERR,
+ "altq cannot be specified more than once");
+ have_altq = (ipfw_insn *)a;
+ cmd->len = F_INSN_SIZE(ipfw_insn_altq);
+ CHECK_CMDLEN;
+ cmd->opcode = O_ALTQ;
+ a->qid = altq_name_to_qid(*av);
+ av++;
+ }
+ break;
+#endif
+
+ case TOK_TAG:
+ case TOK_UNTAG: {
+ uint16_t tag;
+
+ if (have_tag)
+ errx(EX_USAGE, "tag and untag cannot be "
+ "specified more than once");
+ GET_UINT_ARG(tag, IPFW_ARG_MIN, IPFW_ARG_MAX, i,
+ rule_action_params);
+ have_tag = cmd;
+ fill_cmd(cmd, O_TAG, (i == TOK_TAG) ? 0: F_NOT, tag);
+ av++;
+ break;
+ }
+
+ default:
+ abort();
+ }
+ cmd = next_cmd(cmd, &cblen);
+ }
+
+ if (have_state) /* must be a check-state, we are done */
+ goto done;
+
+#define OR_START(target) \
+ if (av[0] && (*av[0] == '(' || *av[0] == '{')) { \
+ if (open_par) \
+ errx(EX_USAGE, "nested \"(\" not allowed\n"); \
+ prev = NULL; \
+ open_par = 1; \
+ if ( (av[0])[1] == '\0') { \
+ av++; \
+ } else \
+ (*av)++; \
+ } \
+ target: \
+
+
+#define CLOSE_PAR \
+ if (open_par) { \
+ if (av[0] && ( \
+ strcmp(*av, ")") == 0 || \
+ strcmp(*av, "}") == 0)) { \
+ prev = NULL; \
+ open_par = 0; \
+ av++; \
+ } else \
+ errx(EX_USAGE, "missing \")\"\n"); \
+ }
+
+#define NOT_BLOCK \
+ if (av[0] && _substrcmp(*av, "not") == 0) { \
+ if (cmd->len & F_NOT) \
+ errx(EX_USAGE, "double \"not\" not allowed\n"); \
+ cmd->len |= F_NOT; \
+ av++; \
+ }
+
+#define OR_BLOCK(target) \
+ if (av[0] && _substrcmp(*av, "or") == 0) { \
+ if (prev == NULL || open_par == 0) \
+ errx(EX_DATAERR, "invalid OR block"); \
+ prev->len |= F_OR; \
+ av++; \
+ goto target; \
+ } \
+ CLOSE_PAR;
+
+ first_cmd = cmd;
+
+#if 0
+ /*
+ * MAC addresses, optional.
+ * If we have this, we skip the part "proto from src to dst"
+ * and jump straight to the option parsing.
+ */
+ NOT_BLOCK;
+ NEED1("missing protocol");
+ if (_substrcmp(*av, "MAC") == 0 ||
+ _substrcmp(*av, "mac") == 0) {
+ av++; /* the "MAC" keyword */
+ add_mac(cmd, av); /* exits in case of errors */
+ cmd = next_cmd(cmd);
+ av += 2; /* dst-mac and src-mac */
+ NOT_BLOCK;
+ NEED1("missing mac type");
+ if (add_mactype(cmd, av[0]))
+ cmd = next_cmd(cmd);
+ av++; /* any or mac-type */
+ goto read_options;
+ }
+#endif
+
+ /*
+ * protocol, mandatory
+ */
+ OR_START(get_proto);
+ NOT_BLOCK;
+ NEED1("missing protocol");
+ if (add_proto_compat(cmd, *av, &proto)) {
+ av++;
+ if (F_LEN(cmd) != 0) {
+ prev = cmd;
+ cmd = next_cmd(cmd, &cblen);
+ }
+ } else if (first_cmd != cmd) {
+ errx(EX_DATAERR, "invalid protocol ``%s''", *av);
+ } else
+ goto read_options;
+ OR_BLOCK(get_proto);
+
+ /*
+ * "from", mandatory
+ */
+ if ((av[0] == NULL) || _substrcmp(*av, "from") != 0)
+ errx(EX_USAGE, "missing ``from''");
+ av++;
+
+ /*
+ * source IP, mandatory
+ */
+ OR_START(source_ip);
+ NOT_BLOCK; /* optional "not" */
+ NEED1("missing source address");
+ if (add_src(cmd, *av, proto, cblen, tstate)) {
+ av++;
+ if (F_LEN(cmd) != 0) { /* ! any */
+ prev = cmd;
+ cmd = next_cmd(cmd, &cblen);
+ }
+ } else
+ errx(EX_USAGE, "bad source address %s", *av);
+ OR_BLOCK(source_ip);
+
+ /*
+ * source ports, optional
+ */
+ NOT_BLOCK; /* optional "not" */
+ if ( av[0] != NULL ) {
+ if (_substrcmp(*av, "any") == 0 ||
+ add_ports(cmd, *av, proto, O_IP_SRCPORT, cblen)) {
+ av++;
+ if (F_LEN(cmd) != 0)
+ cmd = next_cmd(cmd, &cblen);
+ }
+ }
+
+ /*
+ * "to", mandatory
+ */
+ if ( (av[0] == NULL) || _substrcmp(*av, "to") != 0 )
+ errx(EX_USAGE, "missing ``to''");
+ av++;
+
+ /*
+ * destination, mandatory
+ */
+ OR_START(dest_ip);
+ NOT_BLOCK; /* optional "not" */
+ NEED1("missing dst address");
+ if (add_dst(cmd, *av, proto, cblen, tstate)) {
+ av++;
+ if (F_LEN(cmd) != 0) { /* ! any */
+ prev = cmd;
+ cmd = next_cmd(cmd, &cblen);
+ }
+ } else
+ errx( EX_USAGE, "bad destination address %s", *av);
+ OR_BLOCK(dest_ip);
+
+ /*
+ * dest. ports, optional
+ */
+ NOT_BLOCK; /* optional "not" */
+ if (av[0]) {
+ if (_substrcmp(*av, "any") == 0 ||
+ add_ports(cmd, *av, proto, O_IP_DSTPORT, cblen)) {
+ av++;
+ if (F_LEN(cmd) != 0)
+ cmd = next_cmd(cmd, &cblen);
+ }
+ }
+
+read_options:
+ if (av[0] && first_cmd == cmd) {
+ /*
+ * nothing specified so far, store in the rule to ease
+ * printout later.
+ */
+ rule->flags |= IPFW_RULE_NOOPT;
+ }
+ prev = NULL;
+ while ( av[0] != NULL ) {
+ char *s;
+ ipfw_insn_u32 *cmd32; /* alias for cmd */
+
+ s = *av;
+ cmd32 = (ipfw_insn_u32 *)cmd;
+
+ if (*s == '!') { /* alternate syntax for NOT */
+ if (cmd->len & F_NOT)
+ errx(EX_USAGE, "double \"not\" not allowed\n");
+ cmd->len = F_NOT;
+ s++;
+ }
+ i = match_token(rule_options, s);
+ av++;
+ switch(i) {
+ case TOK_NOT:
+ if (cmd->len & F_NOT)
+ errx(EX_USAGE, "double \"not\" not allowed\n");
+ cmd->len = F_NOT;
+ break;
+
+ case TOK_OR:
+ if (open_par == 0 || prev == NULL)
+ errx(EX_USAGE, "invalid \"or\" block\n");
+ prev->len |= F_OR;
+ break;
+
+ case TOK_STARTBRACE:
+ if (open_par)
+ errx(EX_USAGE, "+nested \"(\" not allowed\n");
+ open_par = 1;
+ break;
+
+ case TOK_ENDBRACE:
+ if (!open_par)
+ errx(EX_USAGE, "+missing \")\"\n");
+ open_par = 0;
+ prev = NULL;
+ break;
+
+ case TOK_IN:
+ fill_cmd(cmd, O_IN, 0, 0);
+ break;
+
+ case TOK_OUT:
+ cmd->len ^= F_NOT; /* toggle F_NOT */
+ fill_cmd(cmd, O_IN, 0, 0);
+ break;
+
+ case TOK_DIVERTED:
+ fill_cmd(cmd, O_DIVERTED, 0, 3);
+ break;
+
+ case TOK_DIVERTEDLOOPBACK:
+ fill_cmd(cmd, O_DIVERTED, 0, 1);
+ break;
+
+ case TOK_DIVERTEDOUTPUT:
+ fill_cmd(cmd, O_DIVERTED, 0, 2);
+ break;
+
+ case TOK_FRAG:
+ fill_cmd(cmd, O_FRAG, 0, 0);
+ break;
+
+ case TOK_LAYER2:
+ fill_cmd(cmd, O_LAYER2, 0, 0);
+ break;
+
+ case TOK_XMIT:
+ case TOK_RECV:
+ case TOK_VIA:
+ NEED1("recv, xmit, via require interface name"
+ " or address");
+ fill_iface((ipfw_insn_if *)cmd, av[0], cblen, tstate);
+ av++;
+ if (F_LEN(cmd) == 0) /* not a valid address */
+ break;
+ if (i == TOK_XMIT)
+ cmd->opcode = O_XMIT;
+ else if (i == TOK_RECV)
+ cmd->opcode = O_RECV;
+ else if (i == TOK_VIA)
+ cmd->opcode = O_VIA;
+ break;
+
+ case TOK_ICMPTYPES:
+ NEED1("icmptypes requires list of types");
+ fill_icmptypes((ipfw_insn_u32 *)cmd, *av);
+ av++;
+ break;
+
+ case TOK_ICMP6TYPES:
+ NEED1("icmptypes requires list of types");
+ fill_icmp6types((ipfw_insn_icmp6 *)cmd, *av, cblen);
+ av++;
+ break;
+
+ case TOK_IPTTL:
+ NEED1("ipttl requires TTL");
+ if (strpbrk(*av, "-,")) {
+ if (!add_ports(cmd, *av, 0, O_IPTTL, cblen))
+ errx(EX_DATAERR, "invalid ipttl %s", *av);
+ } else
+ fill_cmd(cmd, O_IPTTL, 0, strtoul(*av, NULL, 0));
+ av++;
+ break;
+
+ case TOK_IPID:
+ NEED1("ipid requires id");
+ if (strpbrk(*av, "-,")) {
+ if (!add_ports(cmd, *av, 0, O_IPID, cblen))
+ errx(EX_DATAERR, "invalid ipid %s", *av);
+ } else
+ fill_cmd(cmd, O_IPID, 0, strtoul(*av, NULL, 0));
+ av++;
+ break;
+
+ case TOK_IPLEN:
+ NEED1("iplen requires length");
+ if (strpbrk(*av, "-,")) {
+ if (!add_ports(cmd, *av, 0, O_IPLEN, cblen))
+ errx(EX_DATAERR, "invalid ip len %s", *av);
+ } else
+ fill_cmd(cmd, O_IPLEN, 0, strtoul(*av, NULL, 0));
+ av++;
+ break;
+
+ case TOK_IPVER:
+ NEED1("ipver requires version");
+ fill_cmd(cmd, O_IPVER, 0, strtoul(*av, NULL, 0));
+ av++;
+ break;
+
+ case TOK_IPPRECEDENCE:
+ NEED1("ipprecedence requires value");
+ fill_cmd(cmd, O_IPPRECEDENCE, 0,
+ (strtoul(*av, NULL, 0) & 7) << 5);
+ av++;
+ break;
+
+ case TOK_DSCP:
+ NEED1("missing DSCP code");
+ fill_dscp(cmd, *av, cblen);
+ av++;
+ break;
+
+ case TOK_IPOPTS:
+ NEED1("missing argument for ipoptions");
+ fill_flags_cmd(cmd, O_IPOPT, f_ipopts, *av);
+ av++;
+ break;
+
+ case TOK_IPTOS:
+ NEED1("missing argument for iptos");
+ fill_flags_cmd(cmd, O_IPTOS, f_iptos, *av);
+ av++;
+ break;
+
+ case TOK_UID:
+ NEED1("uid requires argument");
+ {
+ char *end;
+ uid_t uid;
+ struct passwd *pwd;
+
+ cmd->opcode = O_UID;
+ uid = strtoul(*av, &end, 0);
+ pwd = (*end == '\0') ? getpwuid(uid) : getpwnam(*av);
+ if (pwd == NULL)
+ errx(EX_DATAERR, "uid \"%s\" nonexistent", *av);
+ cmd32->d[0] = pwd->pw_uid;
+ cmd->len |= F_INSN_SIZE(ipfw_insn_u32);
+ av++;
+ }
+ break;
+
+ case TOK_GID:
+ NEED1("gid requires argument");
+ {
+ char *end;
+ gid_t gid;
+ struct group *grp;
+
+ cmd->opcode = O_GID;
+ gid = strtoul(*av, &end, 0);
+ grp = (*end == '\0') ? getgrgid(gid) : getgrnam(*av);
+ if (grp == NULL)
+ errx(EX_DATAERR, "gid \"%s\" nonexistent", *av);
+ cmd32->d[0] = grp->gr_gid;
+ cmd->len |= F_INSN_SIZE(ipfw_insn_u32);
+ av++;
+ }
+ break;
+
+ case TOK_JAIL:
+ NEED1("jail requires argument");
+ {
+ char *end;
+ int jid;
+
+ cmd->opcode = O_JAIL;
+ jid = (int)strtol(*av, &end, 0);
+ if (jid < 0 || *end != '\0')
+ errx(EX_DATAERR, "jail requires prison ID");
+ cmd32->d[0] = (uint32_t)jid;
+ cmd->len |= F_INSN_SIZE(ipfw_insn_u32);
+ av++;
+ }
+ break;
+
+ case TOK_ESTAB:
+ fill_cmd(cmd, O_ESTAB, 0, 0);
+ break;
+
+ case TOK_SETUP:
+ fill_cmd(cmd, O_TCPFLAGS, 0,
+ (TH_SYN) | ( (TH_ACK) & 0xff) <<8 );
+ break;
+
+ case TOK_TCPDATALEN:
+ NEED1("tcpdatalen requires length");
+ if (strpbrk(*av, "-,")) {
+ if (!add_ports(cmd, *av, 0, O_TCPDATALEN, cblen))
+ errx(EX_DATAERR, "invalid tcpdata len %s", *av);
+ } else
+ fill_cmd(cmd, O_TCPDATALEN, 0,
+ strtoul(*av, NULL, 0));
+ av++;
+ break;
+
+ case TOK_TCPOPTS:
+ NEED1("missing argument for tcpoptions");
+ fill_flags_cmd(cmd, O_TCPOPTS, f_tcpopts, *av);
+ av++;
+ break;
+
+ case TOK_TCPSEQ:
+ case TOK_TCPACK:
+ NEED1("tcpseq/tcpack requires argument");
+ cmd->len = F_INSN_SIZE(ipfw_insn_u32);
+ cmd->opcode = (i == TOK_TCPSEQ) ? O_TCPSEQ : O_TCPACK;
+ cmd32->d[0] = htonl(strtoul(*av, NULL, 0));
+ av++;
+ break;
+
+ case TOK_TCPWIN:
+ NEED1("tcpwin requires length");
+ if (strpbrk(*av, "-,")) {
+ if (!add_ports(cmd, *av, 0, O_TCPWIN, cblen))
+ errx(EX_DATAERR, "invalid tcpwin len %s", *av);
+ } else
+ fill_cmd(cmd, O_TCPWIN, 0,
+ strtoul(*av, NULL, 0));
+ av++;
+ break;
+
+ case TOK_TCPFLAGS:
+ NEED1("missing argument for tcpflags");
+ cmd->opcode = O_TCPFLAGS;
+ fill_flags_cmd(cmd, O_TCPFLAGS, f_tcpflags, *av);
+ av++;
+ break;
+
+ case TOK_KEEPSTATE:
+ if (open_par)
+ errx(EX_USAGE, "keep-state cannot be part "
+ "of an or block");
+ if (have_state)
+ errx(EX_USAGE, "only one of keep-state "
+ "and limit is allowed");
+ have_state = cmd;
+ fill_cmd(cmd, O_KEEP_STATE, 0, 0);
+ break;
+
+ case TOK_LIMIT: {
+ ipfw_insn_limit *c = (ipfw_insn_limit *)cmd;
+ int val;
+
+ if (open_par)
+ errx(EX_USAGE,
+ "limit cannot be part of an or block");
+ if (have_state)
+ errx(EX_USAGE, "only one of keep-state and "
+ "limit is allowed");
+ have_state = cmd;
+
+ cmd->len = F_INSN_SIZE(ipfw_insn_limit);
+ CHECK_CMDLEN;
+ cmd->opcode = O_LIMIT;
+ c->limit_mask = c->conn_limit = 0;
+
+ while ( av[0] != NULL ) {
+ if ((val = match_token(limit_masks, *av)) <= 0)
+ break;
+ c->limit_mask |= val;
+ av++;
+ }
+
+ if (c->limit_mask == 0)
+ errx(EX_USAGE, "limit: missing limit mask");
+
+ GET_UINT_ARG(c->conn_limit, IPFW_ARG_MIN, IPFW_ARG_MAX,
+ TOK_LIMIT, rule_options);
+
+ av++;
+ break;
+ }
+
+ case TOK_PROTO:
+ NEED1("missing protocol");
+ if (add_proto(cmd, *av, &proto)) {
+ av++;
+ } else
+ errx(EX_DATAERR, "invalid protocol ``%s''",
+ *av);
+ break;
+
+ case TOK_SRCIP:
+ NEED1("missing source IP");
+ if (add_srcip(cmd, *av, cblen, tstate)) {
+ av++;
+ }
+ break;
+
+ case TOK_DSTIP:
+ NEED1("missing destination IP");
+ if (add_dstip(cmd, *av, cblen, tstate)) {
+ av++;
+ }
+ break;
+
+ case TOK_SRCIP6:
+ NEED1("missing source IP6");
+ if (add_srcip6(cmd, *av, cblen)) {
+ av++;
+ }
+ break;
+
+ case TOK_DSTIP6:
+ NEED1("missing destination IP6");
+ if (add_dstip6(cmd, *av, cblen)) {
+ av++;
+ }
+ break;
+
+ case TOK_SRCPORT:
+ NEED1("missing source port");
+ if (_substrcmp(*av, "any") == 0 ||
+ add_ports(cmd, *av, proto, O_IP_SRCPORT, cblen)) {
+ av++;
+ } else
+ errx(EX_DATAERR, "invalid source port %s", *av);
+ break;
+
+ case TOK_DSTPORT:
+ NEED1("missing destination port");
+ if (_substrcmp(*av, "any") == 0 ||
+ add_ports(cmd, *av, proto, O_IP_DSTPORT, cblen)) {
+ av++;
+ } else
+ errx(EX_DATAERR, "invalid destination port %s",
+ *av);
+ break;
+
+ case TOK_MAC:
+ if (add_mac(cmd, av, cblen))
+ av += 2;
+ break;
+
+ case TOK_MACTYPE:
+ NEED1("missing mac type");
+ if (!add_mactype(cmd, *av, cblen))
+ errx(EX_DATAERR, "invalid mac type %s", *av);
+ av++;
+ break;
+
+ case TOK_VERREVPATH:
+ fill_cmd(cmd, O_VERREVPATH, 0, 0);
+ break;
+
+ case TOK_VERSRCREACH:
+ fill_cmd(cmd, O_VERSRCREACH, 0, 0);
+ break;
+
+ case TOK_ANTISPOOF:
+ fill_cmd(cmd, O_ANTISPOOF, 0, 0);
+ break;
+
+ case TOK_IPSEC:
+ fill_cmd(cmd, O_IPSEC, 0, 0);
+ break;
+
+ case TOK_IPV6:
+ fill_cmd(cmd, O_IP6, 0, 0);
+ break;
+
+ case TOK_IPV4:
+ fill_cmd(cmd, O_IP4, 0, 0);
+ break;
+
+ case TOK_EXT6HDR:
+ fill_ext6hdr( cmd, *av );
+ av++;
+ break;
+
+ case TOK_FLOWID:
+ if (proto != IPPROTO_IPV6 )
+ errx( EX_USAGE, "flow-id filter is active "
+ "only for ipv6 protocol\n");
+ fill_flow6( (ipfw_insn_u32 *) cmd, *av, cblen);
+ av++;
+ break;
+
+ case TOK_COMMENT:
+ fill_comment(cmd, av, cblen);
+ av[0]=NULL;
+ break;
+
+ case TOK_TAGGED:
+ if (av[0] && strpbrk(*av, "-,")) {
+ if (!add_ports(cmd, *av, 0, O_TAGGED, cblen))
+ errx(EX_DATAERR, "tagged: invalid tag"
+ " list: %s", *av);
+ }
+ else {
+ uint16_t tag;
+
+ GET_UINT_ARG(tag, IPFW_ARG_MIN, IPFW_ARG_MAX,
+ TOK_TAGGED, rule_options);
+ fill_cmd(cmd, O_TAGGED, 0, tag);
+ }
+ av++;
+ break;
+
+ case TOK_FIB:
+ NEED1("fib requires fib number");
+ fill_cmd(cmd, O_FIB, 0, strtoul(*av, NULL, 0));
+ av++;
+ break;
+ case TOK_SOCKARG:
+ fill_cmd(cmd, O_SOCKARG, 0, 0);
+ break;
+
+ case TOK_LOOKUP: {
+ ipfw_insn_u32 *c = (ipfw_insn_u32 *)cmd;
+ int j;
+
+ if (!av[0] || !av[1])
+ errx(EX_USAGE, "format: lookup argument tablenum");
+ cmd->opcode = O_IP_DST_LOOKUP;
+ cmd->len |= F_INSN_SIZE(ipfw_insn) + 2;
+ i = match_token(rule_options, *av);
+ for (j = 0; lookup_key[j] >= 0 ; j++) {
+ if (i == lookup_key[j])
+ break;
+ }
+ if (lookup_key[j] <= 0)
+ errx(EX_USAGE, "format: cannot lookup on %s", *av);
+ __PAST_END(c->d, 1) = j; // i converted to option
+ av++;
+
+ if ((j = pack_table(tstate, *av)) == 0)
+ errx(EX_DATAERR, "Invalid table name: %s", *av);
+
+ cmd->arg1 = j;
+ av++;
+ }
+ break;
+ case TOK_FLOW:
+ NEED1("missing table name");
+ if (strncmp(*av, "table(", 6) != 0)
+ errx(EX_DATAERR,
+ "enclose table name into \"table()\"");
+ fill_table(cmd, *av, O_IP_FLOW_LOOKUP, tstate);
+ av++;
+ break;
+
+ default:
+ errx(EX_USAGE, "unrecognised option [%d] %s\n", i, s);
+ }
+ if (F_LEN(cmd) > 0) { /* prepare to advance */
+ prev = cmd;
+ cmd = next_cmd(cmd, &cblen);
+ }
+ }
+
+done:
+ /*
+ * Now copy stuff into the rule.
+ * If we have a keep-state option, the first instruction
+ * must be a PROBE_STATE (which is generated here).
+ * If we have a LOG option, it was stored as the first command,
+ * and now must be moved to the top of the action part.
+ */
+ dst = (ipfw_insn *)rule->cmd;
+
+ /*
+ * First thing to write into the command stream is the match probability.
+ */
+ if (match_prob != 1) { /* 1 means always match */
+ dst->opcode = O_PROB;
+ dst->len = 2;
+ *((int32_t *)(dst+1)) = (int32_t)(match_prob * 0x7fffffff);
+ dst += dst->len;
+ }
+
+ /*
+ * generate O_PROBE_STATE if necessary
+ */
+ if (have_state && have_state->opcode != O_CHECK_STATE) {
+ fill_cmd(dst, O_PROBE_STATE, 0, 0);
+ dst = next_cmd(dst, &rblen);
+ }
+
+ /* copy all commands but O_LOG, O_KEEP_STATE, O_LIMIT, O_ALTQ, O_TAG */
+ for (src = (ipfw_insn *)cmdbuf; src != cmd; src += i) {
+ i = F_LEN(src);
+ CHECK_RBUFLEN(i);
+
+ switch (src->opcode) {
+ case O_LOG:
+ case O_KEEP_STATE:
+ case O_LIMIT:
+ case O_ALTQ:
+ case O_TAG:
+ break;
+ default:
+ bcopy(src, dst, i * sizeof(uint32_t));
+ dst += i;
+ }
+ }
+
+ /*
+ * put back the have_state command as last opcode
+ */
+ if (have_state && have_state->opcode != O_CHECK_STATE) {
+ i = F_LEN(have_state);
+ CHECK_RBUFLEN(i);
+ bcopy(have_state, dst, i * sizeof(uint32_t));
+ dst += i;
+ }
+ /*
+ * start action section
+ */
+ rule->act_ofs = dst - rule->cmd;
+
+ /* put back O_LOG, O_ALTQ, O_TAG if necessary */
+ if (have_log) {
+ i = F_LEN(have_log);
+ CHECK_RBUFLEN(i);
+ bcopy(have_log, dst, i * sizeof(uint32_t));
+ dst += i;
+ }
+ if (have_altq) {
+ i = F_LEN(have_altq);
+ CHECK_RBUFLEN(i);
+ bcopy(have_altq, dst, i * sizeof(uint32_t));
+ dst += i;
+ }
+ if (have_tag) {
+ i = F_LEN(have_tag);
+ CHECK_RBUFLEN(i);
+ bcopy(have_tag, dst, i * sizeof(uint32_t));
+ dst += i;
+ }
+
+ /*
+ * copy all other actions
+ */
+ for (src = (ipfw_insn *)actbuf; src != action; src += i) {
+ i = F_LEN(src);
+ CHECK_RBUFLEN(i);
+ bcopy(src, dst, i * sizeof(uint32_t));
+ dst += i;
+ }
+
+ rule->cmd_len = (uint32_t *)dst - (uint32_t *)(rule->cmd);
+ *rbufsize = (char *)dst - (char *)rule;
+}
+
+static int
+compare_ntlv(const void *_a, const void *_b)
+{
+ ipfw_obj_ntlv *a, *b;
+
+ a = (ipfw_obj_ntlv *)_a;
+ b = (ipfw_obj_ntlv *)_b;
+
+ if (a->set < b->set)
+ return (-1);
+ else if (a->set > b->set)
+ return (1);
+
+ if (a->idx < b->idx)
+ return (-1);
+ else if (a->idx > b->idx)
+ return (1);
+
+ if (a->head.type < b->head.type)
+ return (-1);
+ else if (a->head.type > b->head.type)
+ return (1);
+
+ return (0);
+}
+
+/*
+ * Provide kernel with sorted list of referenced objects
+ */
+static void
+object_sort_ctlv(ipfw_obj_ctlv *ctlv)
+{
+
+ qsort(ctlv + 1, ctlv->count, ctlv->objsize, compare_ntlv);
+}
+
+struct object_kt {
+ uint16_t uidx;
+ uint16_t type;
+};
+static int
+compare_object_kntlv(const void *k, const void *v)
+{
+ ipfw_obj_ntlv *ntlv;
+ struct object_kt key;
+
+ key = *((struct object_kt *)k);
+ ntlv = (ipfw_obj_ntlv *)v;
+
+ if (key.uidx < ntlv->idx)
+ return (-1);
+ else if (key.uidx > ntlv->idx)
+ return (1);
+
+ if (key.type < ntlv->head.type)
+ return (-1);
+ else if (key.type > ntlv->head.type)
+ return (1);
+
+ return (0);
+}
+
+/*
+ * Finds object name in @ctlv by @idx and @type.
+ * Uses the following facts:
+ * 1) All TLVs are the same size
+ * 2) Kernel implementation provides already sorted list.
+ *
+ * Returns table name or NULL.
+ */
+static char *
+object_search_ctlv(ipfw_obj_ctlv *ctlv, uint16_t idx, uint16_t type)
+{
+ ipfw_obj_ntlv *ntlv;
+ struct object_kt key;
+
+ key.uidx = idx;
+ key.type = type;
+
+ ntlv = bsearch(&key, (ctlv + 1), ctlv->count, ctlv->objsize,
+ compare_object_kntlv);
+
+ if (ntlv != 0)
+ return (ntlv->name);
+
+ return (NULL);
+}
+
+static char *
+table_search_ctlv(ipfw_obj_ctlv *ctlv, uint16_t idx)
+{
+
+ return (object_search_ctlv(ctlv, idx, IPFW_TLV_TBL_NAME));
+}
+
+/*
+ * Adds one or more rules to ipfw chain.
+ * Data layout:
+ * Request:
+ * [
+ * ip_fw3_opheader
+ * [ ipfw_obj_ctlv(IPFW_TLV_TBL_LIST) ipfw_obj_ntlv x N ] (optional *1)
+ * [ ipfw_obj_ctlv(IPFW_TLV_RULE_LIST) [ ip_fw_rule ip_fw_insn ] x N ] (*2) (*3)
+ * ]
+ * Reply:
+ * [
+ * ip_fw3_opheader
+ * [ ipfw_obj_ctlv(IPFW_TLV_TBL_LIST) ipfw_obj_ntlv x N ] (optional)
+ * [ ipfw_obj_ctlv(IPFW_TLV_RULE_LIST) [ ip_fw_rule ip_fw_insn ] x N ]
+ * ]
+ *
+ * Rules in reply are modified to store their actual ruleset number.
+ *
+ * (*1) TLVs inside IPFW_TLV_TBL_LIST needs to be sorted ascending
+ * accoring to their idx field and there has to be no duplicates.
+ * (*2) Numbered rules inside IPFW_TLV_RULE_LIST needs to be sorted ascending.
+ * (*3) Each ip_fw structure needs to be aligned to u64 boundary.
+ */
+void
+ipfw_add(char *av[])
+{
+ uint32_t rulebuf[1024];
+ int rbufsize, default_off, tlen, rlen;
+ size_t sz;
+ struct tidx ts;
+ struct ip_fw_rule *rule;
+ caddr_t tbuf;
+ ip_fw3_opheader *op3;
+ ipfw_obj_ctlv *ctlv, *tstate;
+
+ rbufsize = sizeof(rulebuf);
+ memset(rulebuf, 0, rbufsize);
+ memset(&ts, 0, sizeof(ts));
+
+ /* Optimize case with no tables */
+ default_off = sizeof(ipfw_obj_ctlv) + sizeof(ip_fw3_opheader);
+ op3 = (ip_fw3_opheader *)rulebuf;
+ ctlv = (ipfw_obj_ctlv *)(op3 + 1);
+ rule = (struct ip_fw_rule *)(ctlv + 1);
+ rbufsize -= default_off;
+
+ compile_rule(av, (uint32_t *)rule, &rbufsize, &ts);
+ /* Align rule size to u64 boundary */
+ rlen = roundup2(rbufsize, sizeof(uint64_t));
+
+ tbuf = NULL;
+ sz = 0;
+ tstate = NULL;
+ if (ts.count != 0) {
+ /* Some tables. We have to alloc more data */
+ tlen = ts.count * sizeof(ipfw_obj_ntlv);
+ sz = default_off + sizeof(ipfw_obj_ctlv) + tlen + rlen;
+
+ if ((tbuf = calloc(1, sz)) == NULL)
+ err(EX_UNAVAILABLE, "malloc() failed for IP_FW_ADD");
+ op3 = (ip_fw3_opheader *)tbuf;
+ /* Tables first */
+ ctlv = (ipfw_obj_ctlv *)(op3 + 1);
+ ctlv->head.type = IPFW_TLV_TBLNAME_LIST;
+ ctlv->head.length = sizeof(ipfw_obj_ctlv) + tlen;
+ ctlv->count = ts.count;
+ ctlv->objsize = sizeof(ipfw_obj_ntlv);
+ memcpy(ctlv + 1, ts.idx, tlen);
+ object_sort_ctlv(ctlv);
+ tstate = ctlv;
+ /* Rule next */
+ ctlv = (ipfw_obj_ctlv *)((caddr_t)ctlv + ctlv->head.length);
+ ctlv->head.type = IPFW_TLV_RULE_LIST;
+ ctlv->head.length = sizeof(ipfw_obj_ctlv) + rlen;
+ ctlv->count = 1;
+ memcpy(ctlv + 1, rule, rbufsize);
+ } else {
+ /* Simply add header */
+ sz = rlen + default_off;
+ memset(ctlv, 0, sizeof(*ctlv));
+ ctlv->head.type = IPFW_TLV_RULE_LIST;
+ ctlv->head.length = sizeof(ipfw_obj_ctlv) + rlen;
+ ctlv->count = 1;
+ }
+
+ if (do_get3(IP_FW_XADD, op3, &sz) != 0)
+ err(EX_UNAVAILABLE, "getsockopt(%s)", "IP_FW_XADD");
+
+ if (!co.do_quiet) {
+ struct format_opts sfo;
+ struct buf_pr bp;
+ memset(&sfo, 0, sizeof(sfo));
+ sfo.tstate = tstate;
+ sfo.set_mask = (uint32_t)(-1);
+ bp_alloc(&bp, 4096);
+ show_static_rule(&co, &sfo, &bp, rule, NULL);
+ printf("%s", bp.buf);
+ bp_free(&bp);
+ }
+
+ if (tbuf != NULL)
+ free(tbuf);
+
+ if (ts.idx != NULL)
+ free(ts.idx);
+}
+
+/*
+ * clear the counters or the log counters.
+ * optname has the following values:
+ * 0 (zero both counters and logging)
+ * 1 (zero logging only)
+ */
+void
+ipfw_zero(int ac, char *av[], int optname)
+{
+ ipfw_range_tlv rt;
+ uint32_t arg;
+ int failed = EX_OK;
+ char const *errstr;
+ char const *name = optname ? "RESETLOG" : "ZERO";
+
+ optname = optname ? IP_FW_XRESETLOG : IP_FW_XZERO;
+ memset(&rt, 0, sizeof(rt));
+
+ av++; ac--;
+
+ if (ac == 0) {
+ /* clear all entries */
+ rt.flags = IPFW_RCFLAG_ALL;
+ if (do_range_cmd(optname, &rt) < 0)
+ err(EX_UNAVAILABLE, "setsockopt(IP_FW_X%s)", name);
+ if (!co.do_quiet)
+ printf("%s.\n", optname == IP_FW_XZERO ?
+ "Accounting cleared":"Logging counts reset");
+
+ return;
+ }
+
+ while (ac) {
+ /* Rule number */
+ if (isdigit(**av)) {
+ arg = strtonum(*av, 0, 0xffff, &errstr);
+ if (errstr)
+ errx(EX_DATAERR,
+ "invalid rule number %s\n", *av);
+ rt.start_rule = arg;
+ rt.end_rule = arg;
+ rt.flags |= IPFW_RCFLAG_RANGE;
+ if (co.use_set != 0) {
+ rt.set = co.use_set - 1;
+ rt.flags |= IPFW_RCFLAG_SET;
+ }
+ if (do_range_cmd(optname, &rt) != 0) {
+ warn("rule %u: setsockopt(IP_FW_X%s)",
+ arg, name);
+ failed = EX_UNAVAILABLE;
+ } else if (rt.new_set == 0) {
+ printf("Entry %d not found\n", arg);
+ failed = EX_UNAVAILABLE;
+ } else if (!co.do_quiet)
+ printf("Entry %d %s.\n", arg,
+ optname == IP_FW_XZERO ?
+ "cleared" : "logging count reset");
+ } else {
+ errx(EX_USAGE, "invalid rule number ``%s''", *av);
+ }
+ av++; ac--;
+ }
+ if (failed != EX_OK)
+ exit(failed);
+}
+
+void
+ipfw_flush(int force)
+{
+ ipfw_range_tlv rt;
+
+ if (!force && !co.do_quiet) { /* need to ask user */
+ int c;
+
+ printf("Are you sure? [yn] ");
+ fflush(stdout);
+ do {
+ c = toupper(getc(stdin));
+ while (c != '\n' && getc(stdin) != '\n')
+ if (feof(stdin))
+ return; /* and do not flush */
+ } while (c != 'Y' && c != 'N');
+ printf("\n");
+ if (c == 'N') /* user said no */
+ return;
+ }
+ if (co.do_pipe) {
+ dummynet_flush();
+ return;
+ }
+ /* `ipfw set N flush` - is the same that `ipfw delete set N` */
+ memset(&rt, 0, sizeof(rt));
+ if (co.use_set != 0) {
+ rt.set = co.use_set - 1;
+ rt.flags = IPFW_RCFLAG_SET;
+ } else
+ rt.flags = IPFW_RCFLAG_ALL;
+ if (do_range_cmd(IP_FW_XDEL, &rt) != 0)
+ err(EX_UNAVAILABLE, "setsockopt(IP_FW_XDEL)");
+ if (!co.do_quiet)
+ printf("Flushed all %s.\n", co.do_pipe ? "pipes" : "rules");
+}
+
+static struct _s_x intcmds[] = {
+ { "talist", TOK_TALIST },
+ { "iflist", TOK_IFLIST },
+ { "vlist", TOK_VLIST },
+ { NULL, 0 }
+};
+
+void
+ipfw_internal_handler(int ac, char *av[])
+{
+ int tcmd;
+
+ ac--; av++;
+ NEED1("internal cmd required");
+
+ if ((tcmd = match_token(intcmds, *av)) == -1)
+ errx(EX_USAGE, "invalid internal sub-cmd: %s", *av);
+
+ switch (tcmd) {
+ case TOK_IFLIST:
+ ipfw_list_tifaces();
+ break;
+ case TOK_TALIST:
+ ipfw_list_ta(ac, av);
+ break;
+ case TOK_VLIST:
+ ipfw_list_values(ac, av);
+ break;
+ }
+}
+
+static int
+ipfw_get_tracked_ifaces(ipfw_obj_lheader **polh)
+{
+ ipfw_obj_lheader req, *olh;
+ size_t sz;
+
+ memset(&req, 0, sizeof(req));
+ sz = sizeof(req);
+
+ if (do_get3(IP_FW_XIFLIST, &req.opheader, &sz) != 0) {
+ if (errno != ENOMEM)
+ return (errno);
+ }
+
+ sz = req.size;
+ if ((olh = calloc(1, sz)) == NULL)
+ return (ENOMEM);
+
+ olh->size = sz;
+ if (do_get3(IP_FW_XIFLIST, &olh->opheader, &sz) != 0) {
+ free(olh);
+ return (errno);
+ }
+
+ *polh = olh;
+ return (0);
+}
+
+static int
+ifinfo_cmp(const void *a, const void *b)
+{
+ ipfw_iface_info *ia, *ib;
+
+ ia = (ipfw_iface_info *)a;
+ ib = (ipfw_iface_info *)b;
+
+ return (stringnum_cmp(ia->ifname, ib->ifname));
+}
+
+/*
+ * Retrieves table list from kernel,
+ * optionally sorts it and calls requested function for each table.
+ * Returns 0 on success.
+ */
+static void
+ipfw_list_tifaces()
+{
+ ipfw_obj_lheader *olh;
+ ipfw_iface_info *info;
+ int i, error;
+
+ if ((error = ipfw_get_tracked_ifaces(&olh)) != 0)
+ err(EX_OSERR, "Unable to request ipfw tracked interface list");
+
+
+ qsort(olh + 1, olh->count, olh->objsize, ifinfo_cmp);
+
+ info = (ipfw_iface_info *)(olh + 1);
+ for (i = 0; i < olh->count; i++) {
+ if (info->flags & IPFW_IFFLAG_RESOLVED)
+ printf("%s ifindex: %d refcount: %u changes: %u\n",
+ info->ifname, info->ifindex, info->refcnt,
+ info->gencnt);
+ else
+ printf("%s ifindex: unresolved refcount: %u changes: %u\n",
+ info->ifname, info->refcnt, info->gencnt);
+ info = (ipfw_iface_info *)((caddr_t)info + olh->objsize);
+ }
+
+ free(olh);
+}
+
+
+
+
diff --git a/sbin/ipfw/ipfw2.h b/sbin/ipfw/ipfw2.h
new file mode 100644
index 0000000..5a08321
--- /dev/null
+++ b/sbin/ipfw/ipfw2.h
@@ -0,0 +1,350 @@
+/*
+ * Copyright (c) 2002-2003 Luigi Rizzo
+ * Copyright (c) 1996 Alex Nash, Paul Traina, Poul-Henning Kamp
+ * Copyright (c) 1994 Ugen J.S.Antsilevich
+ *
+ * Idea and grammar partially left from:
+ * Copyright (c) 1993 Daniel Boulet
+ *
+ * Redistribution and use in source forms, with and without modification,
+ * are permitted provided that this entire comment appears intact.
+ *
+ * Redistribution in binary form may occur without any restrictions.
+ * Obviously, it would be nice if you gave credit where credit is due
+ * but requiring it would be too onerous.
+ *
+ * This software is provided ``AS IS'' without any warranties of any kind.
+ *
+ * NEW command line interface for IP firewall facility
+ *
+ * $FreeBSD$
+ */
+
+/*
+ * Options that can be set on the command line.
+ * When reading commands from a file, a subset of the options can also
+ * be applied globally by specifying them before the file name.
+ * After that, each line can contain its own option that changes
+ * the global value.
+ * XXX The context is not restored after each line.
+ */
+
+struct cmdline_opts {
+ /* boolean options: */
+ int do_value_as_ip; /* show table value as IP */
+ int do_resolv; /* try to resolve all ip to names */
+ int do_time; /* Show time stamps */
+ int do_quiet; /* Be quiet in add and flush */
+ int do_pipe; /* this cmd refers to a pipe/queue/sched */
+ int do_nat; /* this cmd refers to a nat config */
+ int do_dynamic; /* display dynamic rules */
+ int do_expired; /* display expired dynamic rules */
+ int do_compact; /* show rules in compact mode */
+ int do_force; /* do not ask for confirmation */
+ int show_sets; /* display the set each rule belongs to */
+ int test_only; /* only check syntax */
+ int comment_only; /* only print action and comment */
+ int verbose; /* be verbose on some commands */
+
+ /* The options below can have multiple values. */
+
+ int do_sort; /* field to sort results (0 = no) */
+ /* valid fields are 1 and above */
+
+ int use_set; /* work with specified set number */
+ /* 0 means all sets, otherwise apply to set use_set - 1 */
+
+};
+
+extern struct cmdline_opts co;
+
+/*
+ * _s_x is a structure that stores a string <-> token pairs, used in
+ * various places in the parser. Entries are stored in arrays,
+ * with an entry with s=NULL as terminator.
+ * The search routines are match_token() and match_value().
+ * Often, an element with x=0 contains an error string.
+ *
+ */
+struct _s_x {
+ char const *s;
+ int x;
+};
+
+extern struct _s_x f_ipdscp[];
+
+enum tokens {
+ TOK_NULL=0,
+
+ TOK_OR,
+ TOK_NOT,
+ TOK_STARTBRACE,
+ TOK_ENDBRACE,
+
+ TOK_ACCEPT,
+ TOK_COUNT,
+ TOK_PIPE,
+ TOK_LINK,
+ TOK_QUEUE,
+ TOK_FLOWSET,
+ TOK_SCHED,
+ TOK_DIVERT,
+ TOK_TEE,
+ TOK_NETGRAPH,
+ TOK_NGTEE,
+ TOK_FORWARD,
+ TOK_SKIPTO,
+ TOK_DENY,
+ TOK_REJECT,
+ TOK_RESET,
+ TOK_UNREACH,
+ TOK_CHECKSTATE,
+ TOK_NAT,
+ TOK_REASS,
+ TOK_CALL,
+ TOK_RETURN,
+
+ TOK_ALTQ,
+ TOK_LOG,
+ TOK_TAG,
+ TOK_UNTAG,
+
+ TOK_TAGGED,
+ TOK_UID,
+ TOK_GID,
+ TOK_JAIL,
+ TOK_IN,
+ TOK_LIMIT,
+ TOK_KEEPSTATE,
+ TOK_LAYER2,
+ TOK_OUT,
+ TOK_DIVERTED,
+ TOK_DIVERTEDLOOPBACK,
+ TOK_DIVERTEDOUTPUT,
+ TOK_XMIT,
+ TOK_RECV,
+ TOK_VIA,
+ TOK_FRAG,
+ TOK_IPOPTS,
+ TOK_IPLEN,
+ TOK_IPID,
+ TOK_IPPRECEDENCE,
+ TOK_DSCP,
+ TOK_IPTOS,
+ TOK_IPTTL,
+ TOK_IPVER,
+ TOK_ESTAB,
+ TOK_SETUP,
+ TOK_TCPDATALEN,
+ TOK_TCPFLAGS,
+ TOK_TCPOPTS,
+ TOK_TCPSEQ,
+ TOK_TCPACK,
+ TOK_TCPWIN,
+ TOK_ICMPTYPES,
+ TOK_MAC,
+ TOK_MACTYPE,
+ TOK_VERREVPATH,
+ TOK_VERSRCREACH,
+ TOK_ANTISPOOF,
+ TOK_IPSEC,
+ TOK_COMMENT,
+
+ TOK_PLR,
+ TOK_NOERROR,
+ TOK_BUCKETS,
+ TOK_DSTIP,
+ TOK_SRCIP,
+ TOK_DSTPORT,
+ TOK_SRCPORT,
+ TOK_ALL,
+ TOK_MASK,
+ TOK_FLOW_MASK,
+ TOK_SCHED_MASK,
+ TOK_BW,
+ TOK_DELAY,
+ TOK_PROFILE,
+ TOK_BURST,
+ TOK_RED,
+ TOK_GRED,
+ TOK_ECN,
+ TOK_DROPTAIL,
+ TOK_PROTO,
+ /* dummynet tokens */
+ TOK_WEIGHT,
+ TOK_LMAX,
+ TOK_PRI,
+ TOK_TYPE,
+ TOK_SLOTSIZE,
+
+ TOK_IP,
+ TOK_IF,
+ TOK_ALOG,
+ TOK_DENY_INC,
+ TOK_SAME_PORTS,
+ TOK_UNREG_ONLY,
+ TOK_SKIP_GLOBAL,
+ TOK_RESET_ADDR,
+ TOK_ALIAS_REV,
+ TOK_PROXY_ONLY,
+ TOK_REDIR_ADDR,
+ TOK_REDIR_PORT,
+ TOK_REDIR_PROTO,
+
+ TOK_IPV6,
+ TOK_FLOWID,
+ TOK_ICMP6TYPES,
+ TOK_EXT6HDR,
+ TOK_DSTIP6,
+ TOK_SRCIP6,
+
+ TOK_IPV4,
+ TOK_UNREACH6,
+ TOK_RESET6,
+
+ TOK_FIB,
+ TOK_SETFIB,
+ TOK_LOOKUP,
+ TOK_SOCKARG,
+ TOK_SETDSCP,
+ TOK_FLOW,
+ TOK_IFLIST,
+ /* Table tokens */
+ TOK_CREATE,
+ TOK_DESTROY,
+ TOK_LIST,
+ TOK_INFO,
+ TOK_DETAIL,
+ TOK_MODIFY,
+ TOK_FLUSH,
+ TOK_SWAP,
+ TOK_ADD,
+ TOK_DEL,
+ TOK_VALTYPE,
+ TOK_ALGO,
+ TOK_TALIST,
+ TOK_ATOMIC,
+ TOK_LOCK,
+ TOK_UNLOCK,
+ TOK_VLIST,
+};
+
+/*
+ * the following macro returns an error message if we run out of
+ * arguments.
+ */
+#define NEED(_p, msg) {if (!_p) errx(EX_USAGE, msg);}
+#define NEED1(msg) {if (!(*av)) errx(EX_USAGE, msg);}
+
+struct buf_pr {
+ char *buf; /* allocated buffer */
+ char *ptr; /* current pointer */
+ size_t size; /* total buffer size */
+ size_t avail; /* available storage */
+ size_t needed; /* length needed */
+};
+
+int pr_u64(struct buf_pr *bp, uint64_t *pd, int width);
+int bp_alloc(struct buf_pr *b, size_t size);
+void bp_free(struct buf_pr *b);
+int bprintf(struct buf_pr *b, char *format, ...);
+
+
+/* memory allocation support */
+void *safe_calloc(size_t number, size_t size);
+void *safe_realloc(void *ptr, size_t size);
+
+/* string comparison functions used for historical compatibility */
+int _substrcmp(const char *str1, const char* str2);
+int _substrcmp2(const char *str1, const char* str2, const char* str3);
+int stringnum_cmp(const char *a, const char *b);
+
+/* utility functions */
+int match_token(struct _s_x *table, char *string);
+int match_token_relaxed(struct _s_x *table, char *string);
+char const *match_value(struct _s_x *p, int value);
+size_t concat_tokens(char *buf, size_t bufsize, struct _s_x *table,
+ char *delimiter);
+int fill_flags(struct _s_x *flags, char *p, char **e, uint32_t *set,
+ uint32_t *clear);
+void print_flags_buffer(char *buf, size_t sz, struct _s_x *list, uint32_t set);
+
+struct _ip_fw3_opheader;
+int do_cmd(int optname, void *optval, uintptr_t optlen);
+int do_set3(int optname, struct _ip_fw3_opheader *op3, uintptr_t optlen);
+int do_get3(int optname, struct _ip_fw3_opheader *op3, size_t *optlen);
+
+struct in6_addr;
+void n2mask(struct in6_addr *mask, int n);
+int contigmask(uint8_t *p, int len);
+
+/*
+ * Forward declarations to avoid include way too many headers.
+ * C does not allow duplicated typedefs, so we use the base struct
+ * that the typedef points to.
+ * Should the typedefs use a different type, the compiler will
+ * still detect the change when compiling the body of the
+ * functions involved, so we do not lose error checking.
+ */
+struct _ipfw_insn;
+struct _ipfw_insn_altq;
+struct _ipfw_insn_u32;
+struct _ipfw_insn_ip6;
+struct _ipfw_insn_icmp6;
+
+/*
+ * The reserved set numer. This is a constant in ip_fw.h
+ * but we store it in a variable so other files do not depend
+ * in that header just for one constant.
+ */
+extern int resvd_set_number;
+
+/* first-level command handlers */
+void ipfw_add(char *av[]);
+void ipfw_show_nat(int ac, char **av);
+void ipfw_config_pipe(int ac, char **av);
+void ipfw_config_nat(int ac, char **av);
+void ipfw_sets_handler(char *av[]);
+void ipfw_table_handler(int ac, char *av[]);
+void ipfw_sysctl_handler(char *av[], int which);
+void ipfw_delete(char *av[]);
+void ipfw_flush(int force);
+void ipfw_zero(int ac, char *av[], int optname);
+void ipfw_list(int ac, char *av[], int show_counters);
+void ipfw_internal_handler(int ac, char *av[]);
+
+#ifdef PF
+/* altq.c */
+void altq_set_enabled(int enabled);
+u_int32_t altq_name_to_qid(const char *name);
+void print_altq_cmd(struct buf_pr *bp, struct _ipfw_insn_altq *altqptr);
+#else
+#define NO_ALTQ
+#endif
+
+/* dummynet.c */
+void dummynet_list(int ac, char *av[], int show_counters);
+void dummynet_flush(void);
+int ipfw_delete_pipe(int pipe_or_queue, int n);
+
+/* ipv6.c */
+void print_unreach6_code(uint16_t code);
+void print_ip6(struct buf_pr *bp, struct _ipfw_insn_ip6 *cmd, char const *s);
+void print_flow6id(struct buf_pr *bp, struct _ipfw_insn_u32 *cmd);
+void print_icmp6types(struct buf_pr *bp, struct _ipfw_insn_u32 *cmd);
+void print_ext6hdr(struct buf_pr *bp, struct _ipfw_insn *cmd );
+
+struct _ipfw_insn *add_srcip6(struct _ipfw_insn *cmd, char *av, int cblen);
+struct _ipfw_insn *add_dstip6(struct _ipfw_insn *cmd, char *av, int cblen);
+
+void fill_flow6(struct _ipfw_insn_u32 *cmd, char *av, int cblen);
+void fill_unreach6_code(u_short *codep, char *str);
+void fill_icmp6types(struct _ipfw_insn_icmp6 *cmd, char *av, int cblen);
+int fill_ext6hdr(struct _ipfw_insn *cmd, char *av);
+
+/* tables.c */
+struct _ipfw_obj_ctlv;
+int table_check_name(char *tablename);
+void ipfw_list_ta(int ac, char *av[]);
+void ipfw_list_values(int ac, char *av[]);
+
diff --git a/sbin/ipfw/ipv6.c b/sbin/ipfw/ipv6.c
new file mode 100644
index 0000000..36ee675
--- /dev/null
+++ b/sbin/ipfw/ipv6.c
@@ -0,0 +1,536 @@
+/*
+ * Copyright (c) 2002-2003 Luigi Rizzo
+ * Copyright (c) 1996 Alex Nash, Paul Traina, Poul-Henning Kamp
+ * Copyright (c) 1994 Ugen J.S.Antsilevich
+ *
+ * Idea and grammar partially left from:
+ * Copyright (c) 1993 Daniel Boulet
+ *
+ * Redistribution and use in source forms, with and without modification,
+ * are permitted provided that this entire comment appears intact.
+ *
+ * Redistribution in binary form may occur without any restrictions.
+ * Obviously, it would be nice if you gave credit where credit is due
+ * but requiring it would be too onerous.
+ *
+ * This software is provided ``AS IS'' without any warranties of any kind.
+ *
+ * NEW command line interface for IP firewall facility
+ *
+ * $FreeBSD$
+ *
+ * ipv6 support
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include "ipfw2.h"
+
+#include <err.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <netinet/icmp6.h>
+#include <netinet/ip_fw.h>
+#include <arpa/inet.h>
+
+#define CHECK_LENGTH(v, len) do { \
+ if ((v) < (len)) \
+ errx(EX_DATAERR, "Rule too long"); \
+ } while (0)
+
+static struct _s_x icmp6codes[] = {
+ { "no-route", ICMP6_DST_UNREACH_NOROUTE },
+ { "admin-prohib", ICMP6_DST_UNREACH_ADMIN },
+ { "address", ICMP6_DST_UNREACH_ADDR },
+ { "port", ICMP6_DST_UNREACH_NOPORT },
+ { NULL, 0 }
+};
+
+void
+fill_unreach6_code(u_short *codep, char *str)
+{
+ int val;
+ char *s;
+
+ val = strtoul(str, &s, 0);
+ if (s == str || *s != '\0' || val >= 0x100)
+ val = match_token(icmp6codes, str);
+ if (val < 0)
+ errx(EX_DATAERR, "unknown ICMPv6 unreachable code ``%s''", str);
+ *codep = val;
+ return;
+}
+
+void
+print_unreach6_code(uint16_t code)
+{
+ char const *s = match_value(icmp6codes, code);
+
+ if (s != NULL)
+ printf("unreach6 %s", s);
+ else
+ printf("unreach6 %u", code);
+}
+
+/*
+ * Print the ip address contained in a command.
+ */
+void
+print_ip6(struct buf_pr *bp, ipfw_insn_ip6 *cmd, char const *s)
+{
+ struct hostent *he = NULL;
+ int len = F_LEN((ipfw_insn *) cmd) - 1;
+ struct in6_addr *a = &(cmd->addr6);
+ char trad[255];
+
+ bprintf(bp, "%s%s ", cmd->o.len & F_NOT ? " not": "", s);
+
+ if (cmd->o.opcode == O_IP6_SRC_ME || cmd->o.opcode == O_IP6_DST_ME) {
+ bprintf(bp, "me6");
+ return;
+ }
+ if (cmd->o.opcode == O_IP6) {
+ bprintf(bp, " ip6");
+ return;
+ }
+
+ /*
+ * len == 4 indicates a single IP, whereas lists of 1 or more
+ * addr/mask pairs have len = (2n+1). We convert len to n so we
+ * use that to count the number of entries.
+ */
+
+ for (len = len / 4; len > 0; len -= 2, a += 2) {
+ int mb = /* mask length */
+ (cmd->o.opcode == O_IP6_SRC || cmd->o.opcode == O_IP6_DST) ?
+ 128 : contigmask((uint8_t *)&(a[1]), 128);
+
+ if (mb == 128 && co.do_resolv)
+ he = gethostbyaddr((char *)a, sizeof(*a), AF_INET6);
+ if (he != NULL) /* resolved to name */
+ bprintf(bp, "%s", he->h_name);
+ else if (mb == 0) /* any */
+ bprintf(bp, "any");
+ else { /* numeric IP followed by some kind of mask */
+ if (inet_ntop(AF_INET6, a, trad, sizeof( trad ) ) == NULL)
+ bprintf(bp, "Error ntop in print_ip6\n");
+ bprintf(bp, "%s", trad );
+ if (mb < 0) /* XXX not really legal... */
+ bprintf(bp, ":%s",
+ inet_ntop(AF_INET6, &a[1], trad, sizeof(trad)));
+ else if (mb < 128)
+ bprintf(bp, "/%d", mb);
+ }
+ if (len > 2)
+ bprintf(bp, ",");
+ }
+}
+
+void
+fill_icmp6types(ipfw_insn_icmp6 *cmd, char *av, int cblen)
+{
+ uint8_t type;
+
+ CHECK_LENGTH(cblen, F_INSN_SIZE(ipfw_insn_icmp6));
+
+ bzero(cmd, sizeof(*cmd));
+ while (*av) {
+ if (*av == ',')
+ av++;
+ type = strtoul(av, &av, 0);
+ if (*av != ',' && *av != '\0')
+ errx(EX_DATAERR, "invalid ICMP6 type");
+ /*
+ * XXX: shouldn't this be 0xFF? I can't see any reason why
+ * we shouldn't be able to filter all possiable values
+ * regardless of the ability of the rest of the kernel to do
+ * anything useful with them.
+ */
+ if (type > ICMP6_MAXTYPE)
+ errx(EX_DATAERR, "ICMP6 type out of range");
+ cmd->d[type / 32] |= ( 1 << (type % 32));
+ }
+ cmd->o.opcode = O_ICMP6TYPE;
+ cmd->o.len |= F_INSN_SIZE(ipfw_insn_icmp6);
+}
+
+
+void
+print_icmp6types(struct buf_pr *bp, ipfw_insn_u32 *cmd)
+{
+ int i, j;
+ char sep= ' ';
+
+ bprintf(bp, " ip6 icmp6types");
+ for (i = 0; i < 7; i++)
+ for (j=0; j < 32; ++j) {
+ if ( (cmd->d[i] & (1 << (j))) == 0)
+ continue;
+ bprintf(bp, "%c%d", sep, (i*32 + j));
+ sep = ',';
+ }
+}
+
+void
+print_flow6id(struct buf_pr *bp, ipfw_insn_u32 *cmd)
+{
+ uint16_t i, limit = cmd->o.arg1;
+ char sep = ',';
+
+ bprintf(bp, " flow-id ");
+ for( i=0; i < limit; ++i) {
+ if (i == limit - 1)
+ sep = ' ';
+ bprintf(bp, "%d%c", cmd->d[i], sep);
+ }
+}
+
+/* structure and define for the extension header in ipv6 */
+static struct _s_x ext6hdrcodes[] = {
+ { "frag", EXT_FRAGMENT },
+ { "hopopt", EXT_HOPOPTS },
+ { "route", EXT_ROUTING },
+ { "dstopt", EXT_DSTOPTS },
+ { "ah", EXT_AH },
+ { "esp", EXT_ESP },
+ { "rthdr0", EXT_RTHDR0 },
+ { "rthdr2", EXT_RTHDR2 },
+ { NULL, 0 }
+};
+
+/* fills command for the extension header filtering */
+int
+fill_ext6hdr( ipfw_insn *cmd, char *av)
+{
+ int tok;
+ char *s = av;
+
+ cmd->arg1 = 0;
+
+ while(s) {
+ av = strsep( &s, ",") ;
+ tok = match_token(ext6hdrcodes, av);
+ switch (tok) {
+ case EXT_FRAGMENT:
+ cmd->arg1 |= EXT_FRAGMENT;
+ break;
+
+ case EXT_HOPOPTS:
+ cmd->arg1 |= EXT_HOPOPTS;
+ break;
+
+ case EXT_ROUTING:
+ cmd->arg1 |= EXT_ROUTING;
+ break;
+
+ case EXT_DSTOPTS:
+ cmd->arg1 |= EXT_DSTOPTS;
+ break;
+
+ case EXT_AH:
+ cmd->arg1 |= EXT_AH;
+ break;
+
+ case EXT_ESP:
+ cmd->arg1 |= EXT_ESP;
+ break;
+
+ case EXT_RTHDR0:
+ cmd->arg1 |= EXT_RTHDR0;
+ break;
+
+ case EXT_RTHDR2:
+ cmd->arg1 |= EXT_RTHDR2;
+ break;
+
+ default:
+ errx( EX_DATAERR, "invalid option for ipv6 exten header" );
+ break;
+ }
+ }
+ if (cmd->arg1 == 0 )
+ return 0;
+ cmd->opcode = O_EXT_HDR;
+ cmd->len |= F_INSN_SIZE( ipfw_insn );
+ return 1;
+}
+
+void
+print_ext6hdr(struct buf_pr *bp, ipfw_insn *cmd )
+{
+ char sep = ' ';
+
+ bprintf(bp, " extension header:");
+ if (cmd->arg1 & EXT_FRAGMENT ) {
+ bprintf(bp, "%cfragmentation", sep);
+ sep = ',';
+ }
+ if (cmd->arg1 & EXT_HOPOPTS ) {
+ bprintf(bp, "%chop options", sep);
+ sep = ',';
+ }
+ if (cmd->arg1 & EXT_ROUTING ) {
+ bprintf(bp, "%crouting options", sep);
+ sep = ',';
+ }
+ if (cmd->arg1 & EXT_RTHDR0 ) {
+ bprintf(bp, "%crthdr0", sep);
+ sep = ',';
+ }
+ if (cmd->arg1 & EXT_RTHDR2 ) {
+ bprintf(bp, "%crthdr2", sep);
+ sep = ',';
+ }
+ if (cmd->arg1 & EXT_DSTOPTS ) {
+ bprintf(bp, "%cdestination options", sep);
+ sep = ',';
+ }
+ if (cmd->arg1 & EXT_AH ) {
+ bprintf(bp, "%cauthentication header", sep);
+ sep = ',';
+ }
+ if (cmd->arg1 & EXT_ESP ) {
+ bprintf(bp, "%cencapsulated security payload", sep);
+ }
+}
+
+/* Try to find ipv6 address by hostname */
+static int
+lookup_host6 (char *host, struct in6_addr *ip6addr)
+{
+ struct hostent *he;
+
+ if (!inet_pton(AF_INET6, host, ip6addr)) {
+ if ((he = gethostbyname2(host, AF_INET6)) == NULL)
+ return(-1);
+ memcpy(ip6addr, he->h_addr_list[0], sizeof( struct in6_addr));
+ }
+ return(0);
+}
+
+
+/*
+ * fill the addr and mask fields in the instruction as appropriate from av.
+ * Update length as appropriate.
+ * The following formats are allowed:
+ * any matches any IP6. Actually returns an empty instruction.
+ * me returns O_IP6_*_ME
+ *
+ * 03f1::234:123:0342 single IP6 addres
+ * 03f1::234:123:0342/24 address/mask
+ * 03f1::234:123:0342/24,03f1::234:123:0343/ List of address
+ *
+ * Set of address (as in ipv6) not supported because ipv6 address
+ * are typically random past the initial prefix.
+ * Return 1 on success, 0 on failure.
+ */
+static int
+fill_ip6(ipfw_insn_ip6 *cmd, char *av, int cblen)
+{
+ int len = 0;
+ struct in6_addr *d = &(cmd->addr6);
+ /*
+ * Needed for multiple address.
+ * Note d[1] points to struct in6_add r mask6 of cmd
+ */
+
+ cmd->o.len &= ~F_LEN_MASK; /* zero len */
+
+ if (strcmp(av, "any") == 0)
+ return (1);
+
+
+ if (strcmp(av, "me") == 0) { /* Set the data for "me" opt*/
+ cmd->o.len |= F_INSN_SIZE(ipfw_insn);
+ return (1);
+ }
+
+ if (strcmp(av, "me6") == 0) { /* Set the data for "me" opt*/
+ cmd->o.len |= F_INSN_SIZE(ipfw_insn);
+ return (1);
+ }
+
+ if (strncmp(av, "table(", 6) == 0) {
+ char *p = strchr(av + 6, ',');
+ uint32_t *dm = ((ipfw_insn_u32 *)cmd)->d;
+
+ if (p)
+ *p++ = '\0';
+ cmd->o.opcode = O_IP_DST_LOOKUP;
+ cmd->o.arg1 = strtoul(av + 6, NULL, 0);
+ if (p) {
+ cmd->o.len |= F_INSN_SIZE(ipfw_insn_u32);
+ dm[0] = strtoul(p, NULL, 0);
+ } else
+ cmd->o.len |= F_INSN_SIZE(ipfw_insn);
+ return (1);
+ }
+
+ av = strdup(av);
+ while (av) {
+ /*
+ * After the address we can have '/' indicating a mask,
+ * or ',' indicating another address follows.
+ */
+
+ char *p;
+ int masklen;
+ char md = '\0';
+
+ CHECK_LENGTH(cblen, 1 + len + 2 * F_INSN_SIZE(struct in6_addr));
+
+ if ((p = strpbrk(av, "/,")) ) {
+ md = *p; /* save the separator */
+ *p = '\0'; /* terminate address string */
+ p++; /* and skip past it */
+ }
+ /* now p points to NULL, mask or next entry */
+
+ /* lookup stores address in *d as a side effect */
+ if (lookup_host6(av, d) != 0) {
+ /* XXX: failed. Free memory and go */
+ errx(EX_DATAERR, "bad address \"%s\"", av);
+ }
+ /* next, look at the mask, if any */
+ masklen = (md == '/') ? atoi(p) : 128;
+ if (masklen > 128 || masklen < 0)
+ errx(EX_DATAERR, "bad width \"%s\''", p);
+ else
+ n2mask(&d[1], masklen);
+
+ APPLY_MASK(d, &d[1]) /* mask base address with mask */
+
+ /* find next separator */
+
+ if (md == '/') { /* find separator past the mask */
+ p = strpbrk(p, ",");
+ if (p != NULL)
+ p++;
+ }
+ av = p;
+
+ /* Check this entry */
+ if (masklen == 0) {
+ /*
+ * 'any' turns the entire list into a NOP.
+ * 'not any' never matches, so it is removed from the
+ * list unless it is the only item, in which case we
+ * report an error.
+ */
+ if (cmd->o.len & F_NOT && av == NULL && len == 0)
+ errx(EX_DATAERR, "not any never matches");
+ continue;
+ }
+
+ /*
+ * A single IP can be stored alone
+ */
+ if (masklen == 128 && av == NULL && len == 0) {
+ len = F_INSN_SIZE(struct in6_addr);
+ break;
+ }
+
+ /* Update length and pointer to arguments */
+ len += F_INSN_SIZE(struct in6_addr)*2;
+ d += 2;
+ } /* end while */
+
+ /*
+ * Total length of the command, remember that 1 is the size of
+ * the base command.
+ */
+ if (len + 1 > F_LEN_MASK)
+ errx(EX_DATAERR, "address list too long");
+ cmd->o.len |= len+1;
+ free(av);
+ return (1);
+}
+
+/*
+ * fills command for ipv6 flow-id filtering
+ * note that the 20 bit flow number is stored in a array of u_int32_t
+ * it's supported lists of flow-id, so in the o.arg1 we store how many
+ * additional flow-id we want to filter, the basic is 1
+ */
+void
+fill_flow6( ipfw_insn_u32 *cmd, char *av, int cblen)
+{
+ u_int32_t type; /* Current flow number */
+ u_int16_t nflow = 0; /* Current flow index */
+ char *s = av;
+ cmd->d[0] = 0; /* Initializing the base number*/
+
+ while (s) {
+ CHECK_LENGTH(cblen, F_INSN_SIZE(ipfw_insn_u32) + nflow + 1);
+
+ av = strsep( &s, ",") ;
+ type = strtoul(av, &av, 0);
+ if (*av != ',' && *av != '\0')
+ errx(EX_DATAERR, "invalid ipv6 flow number %s", av);
+ if (type > 0xfffff)
+ errx(EX_DATAERR, "flow number out of range %s", av);
+ cmd->d[nflow] |= type;
+ nflow++;
+ }
+ if( nflow > 0 ) {
+ cmd->o.opcode = O_FLOW6ID;
+ cmd->o.len |= F_INSN_SIZE(ipfw_insn_u32) + nflow;
+ cmd->o.arg1 = nflow;
+ }
+ else {
+ errx(EX_DATAERR, "invalid ipv6 flow number %s", av);
+ }
+}
+
+ipfw_insn *
+add_srcip6(ipfw_insn *cmd, char *av, int cblen)
+{
+
+ fill_ip6((ipfw_insn_ip6 *)cmd, av, cblen);
+ if (cmd->opcode == O_IP_DST_SET) /* set */
+ cmd->opcode = O_IP_SRC_SET;
+ else if (cmd->opcode == O_IP_DST_LOOKUP) /* table */
+ cmd->opcode = O_IP_SRC_LOOKUP;
+ else if (F_LEN(cmd) == 0) { /* any */
+ } else if (F_LEN(cmd) == F_INSN_SIZE(ipfw_insn)) { /* "me" */
+ cmd->opcode = O_IP6_SRC_ME;
+ } else if (F_LEN(cmd) ==
+ (F_INSN_SIZE(struct in6_addr) + F_INSN_SIZE(ipfw_insn))) {
+ /* single IP, no mask*/
+ cmd->opcode = O_IP6_SRC;
+ } else { /* addr/mask opt */
+ cmd->opcode = O_IP6_SRC_MASK;
+ }
+ return cmd;
+}
+
+ipfw_insn *
+add_dstip6(ipfw_insn *cmd, char *av, int cblen)
+{
+
+ fill_ip6((ipfw_insn_ip6 *)cmd, av, cblen);
+ if (cmd->opcode == O_IP_DST_SET) /* set */
+ ;
+ else if (cmd->opcode == O_IP_DST_LOOKUP) /* table */
+ ;
+ else if (F_LEN(cmd) == 0) { /* any */
+ } else if (F_LEN(cmd) == F_INSN_SIZE(ipfw_insn)) { /* "me" */
+ cmd->opcode = O_IP6_DST_ME;
+ } else if (F_LEN(cmd) ==
+ (F_INSN_SIZE(struct in6_addr) + F_INSN_SIZE(ipfw_insn))) {
+ /* single IP, no mask*/
+ cmd->opcode = O_IP6_DST;
+ } else { /* addr/mask opt */
+ cmd->opcode = O_IP6_DST_MASK;
+ }
+ return cmd;
+}
diff --git a/sbin/ipfw/main.c b/sbin/ipfw/main.c
new file mode 100644
index 0000000..f25578f
--- /dev/null
+++ b/sbin/ipfw/main.c
@@ -0,0 +1,628 @@
+/*
+ * Copyright (c) 2002-2003,2010 Luigi Rizzo
+ * Copyright (c) 1996 Alex Nash, Paul Traina, Poul-Henning Kamp
+ * Copyright (c) 1994 Ugen J.S.Antsilevich
+ *
+ * Idea and grammar partially left from:
+ * Copyright (c) 1993 Daniel Boulet
+ *
+ * Redistribution and use in source forms, with and without modification,
+ * are permitted provided that this entire comment appears intact.
+ *
+ * Redistribution in binary form may occur without any restrictions.
+ * Obviously, it would be nice if you gave credit where credit is due
+ * but requiring it would be too onerous.
+ *
+ * This software is provided ``AS IS'' without any warranties of any kind.
+ *
+ * Command line interface for IP firewall facility
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/wait.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include "ipfw2.h"
+
+static void
+help(void)
+{
+ fprintf(stderr,
+"ipfw syntax summary (but please do read the ipfw(8) manpage):\n\n"
+"\tipfw [-abcdefhnNqStTv] <command>\n\n"
+"where <command> is one of the following:\n\n"
+"add [num] [set N] [prob x] RULE-BODY\n"
+"{pipe|queue} N config PIPE-BODY\n"
+"[pipe|queue] {zero|delete|show} [N{,N}]\n"
+"nat N config {ip IPADDR|if IFNAME|log|deny_in|same_ports|unreg_only|reset|\n"
+" reverse|proxy_only|redirect_addr linkspec|\n"
+" redirect_port linkspec|redirect_proto linkspec}\n"
+"set [disable N... enable N...] | move [rule] X to Y | swap X Y | show\n"
+"set N {show|list|zero|resetlog|delete} [N{,N}] | flush\n"
+"table N {add ip[/bits] [value] | delete ip[/bits] | flush | list}\n"
+"table all {flush | list}\n"
+"\n"
+"RULE-BODY: check-state [PARAMS] | ACTION [PARAMS] ADDR [OPTION_LIST]\n"
+"ACTION: check-state | allow | count | deny | unreach{,6} CODE |\n"
+" skipto N | {divert|tee} PORT | forward ADDR |\n"
+" pipe N | queue N | nat N | setfib FIB | reass\n"
+"PARAMS: [log [logamount LOGLIMIT]] [altq QUEUE_NAME]\n"
+"ADDR: [ MAC dst src ether_type ] \n"
+" [ ip from IPADDR [ PORT ] to IPADDR [ PORTLIST ] ]\n"
+" [ ipv6|ip6 from IP6ADDR [ PORT ] to IP6ADDR [ PORTLIST ] ]\n"
+"IPADDR: [not] { any | me | ip/bits{x,y,z} | table(t[,v]) | IPLIST }\n"
+"IP6ADDR: [not] { any | me | me6 | ip6/bits | IP6LIST }\n"
+"IP6LIST: { ip6 | ip6/bits }[,IP6LIST]\n"
+"IPLIST: { ip | ip/bits | ip:mask }[,IPLIST]\n"
+"OPTION_LIST: OPTION [OPTION_LIST]\n"
+"OPTION: bridged | diverted | diverted-loopback | diverted-output |\n"
+" {dst-ip|src-ip} IPADDR | {dst-ip6|src-ip6|dst-ipv6|src-ipv6} IP6ADDR |\n"
+" {dst-port|src-port} LIST |\n"
+" estab | frag | {gid|uid} N | icmptypes LIST | in | out | ipid LIST |\n"
+" iplen LIST | ipoptions SPEC | ipprecedence | ipsec | iptos SPEC |\n"
+" ipttl LIST | ipversion VER | keep-state | layer2 | limit ... |\n"
+" icmp6types LIST | ext6hdr LIST | flow-id N[,N] | fib FIB |\n"
+" mac ... | mac-type LIST | proto LIST | {recv|xmit|via} {IF|IPADDR} |\n"
+" setup | {tcpack|tcpseq|tcpwin} NN | tcpflags SPEC | tcpoptions SPEC |\n"
+" tcpdatalen LIST | verrevpath | versrcreach | antispoof\n"
+);
+
+ exit(0);
+}
+
+/*
+ * Called with the arguments, including program name because getopt
+ * wants it to be present.
+ * Returns 0 if successful, 1 if empty command, errx() in case of errors.
+ * First thing we do is process parameters creating an argv[] array
+ * which includes the program name and a NULL entry at the end.
+ * If we are called with a single string, we split it on whitespace.
+ * Also, arguments with a trailing ',' are joined to the next one.
+ * The pointers (av[]) and data are in a single chunk of memory.
+ * av[0] points to the original program name, all other entries
+ * point into the allocated chunk.
+ */
+static int
+ipfw_main(int oldac, char **oldav)
+{
+ int ch, ac;
+ const char *errstr;
+ char **av, **save_av;
+ int do_acct = 0; /* Show packet/byte count */
+ int try_next = 0; /* set if pipe cmd not found */
+ int av_size; /* compute the av size */
+ char *av_p; /* used to build the av list */
+
+#define WHITESP " \t\f\v\n\r"
+ if (oldac < 2)
+ return 1; /* need at least one argument */
+
+ if (oldac == 2) {
+ /*
+ * If we are called with one argument, try to split it into
+ * words for subsequent parsing. Spaces after a ',' are
+ * removed by copying the string in-place.
+ */
+ char *arg = oldav[1]; /* The string is the first arg. */
+ int l = strlen(arg);
+ int copy = 0; /* 1 if we need to copy, 0 otherwise */
+ int i, j;
+
+ for (i = j = 0; i < l; i++) {
+ if (arg[i] == '#') /* comment marker */
+ break;
+ if (copy) {
+ arg[j++] = arg[i];
+ copy = !strchr("," WHITESP, arg[i]);
+ } else {
+ copy = !strchr(WHITESP, arg[i]);
+ if (copy)
+ arg[j++] = arg[i];
+ }
+ }
+ if (!copy && j > 0) /* last char was a 'blank', remove it */
+ j--;
+ l = j; /* the new argument length */
+ arg[j++] = '\0';
+ if (l == 0) /* empty string! */
+ return 1;
+
+ /*
+ * First, count number of arguments. Because of the previous
+ * processing, this is just the number of blanks plus 1.
+ */
+ for (i = 0, ac = 1; i < l; i++)
+ if (strchr(WHITESP, arg[i]) != NULL)
+ ac++;
+
+ /*
+ * Allocate the argument list structure as a single block
+ * of memory, containing pointers and the argument
+ * strings. We include one entry for the program name
+ * because getopt expects it, and a NULL at the end
+ * to simplify further parsing.
+ */
+ ac++; /* add 1 for the program name */
+ av_size = (ac+1) * sizeof(char *) + l + 1;
+ av = safe_calloc(av_size, 1);
+
+ /*
+ * Init the argument pointer to the end of the array
+ * and copy arguments from arg[] to av[]. For each one,
+ * j is the initial character, i is the one past the end.
+ */
+ av_p = (char *)&av[ac+1];
+ for (ac = 1, i = j = 0; i < l; i++) {
+ if (strchr(WHITESP, arg[i]) != NULL || i == l-1) {
+ if (i == l-1)
+ i++;
+ bcopy(arg+j, av_p, i-j);
+ av[ac] = av_p;
+ av_p += i-j; /* the length of the string */
+ *av_p++ = '\0';
+ ac++;
+ j = i + 1;
+ }
+ }
+ } else {
+ /*
+ * If an argument ends with ',' join with the next one.
+ */
+ int first, i, l=0;
+
+ /*
+ * Allocate the argument list structure as a single block
+ * of memory, containing both pointers and the argument
+ * strings. We include some space for the program name
+ * because getopt expects it.
+ * We add an extra pointer to the end of the array,
+ * to make simpler further parsing.
+ */
+ for (i=0; i<oldac; i++)
+ l += strlen(oldav[i]);
+
+ av_size = (oldac+1) * sizeof(char *) + l + oldac;
+ av = safe_calloc(av_size, 1);
+
+ /*
+ * Init the argument pointer to the end of the array
+ * and copy arguments from arg[] to av[]
+ */
+ av_p = (char *)&av[oldac+1];
+ for (first = i = ac = 1, l = 0; i < oldac; i++) {
+ char *arg = oldav[i];
+ int k = strlen(arg);
+
+ l += k;
+ if (arg[k-1] != ',' || i == oldac-1) {
+ /* Time to copy. */
+ av[ac] = av_p;
+ for (l=0; first <= i; first++) {
+ strcat(av_p, oldav[first]);
+ av_p += strlen(oldav[first]);
+ }
+ *av_p++ = '\0';
+ ac++;
+ l = 0;
+ first = i+1;
+ }
+ }
+ }
+
+ /*
+ * set the progname pointer to the original string
+ * and terminate the array with null
+ */
+ av[0] = oldav[0];
+ av[ac] = NULL;
+
+ /* Set the force flag for non-interactive processes */
+ if (!co.do_force)
+ co.do_force = !isatty(STDIN_FILENO);
+
+#ifdef EMULATE_SYSCTL /* sysctl emulation */
+ if ( ac >= 2 && !strcmp(av[1], "sysctl")) {
+ char *s;
+ int i;
+
+ if (ac != 3) {
+ printf( "sysctl emulation usage:\n"
+ " ipfw sysctl name[=value]\n"
+ " ipfw sysctl -a\n");
+ return 0;
+ }
+ s = strchr(av[2], '=');
+ if (s == NULL) {
+ s = !strcmp(av[2], "-a") ? NULL : av[2];
+ sysctlbyname(s, NULL, NULL, NULL, 0);
+ } else { /* ipfw sysctl x.y.z=value */
+ /* assume an INT value, will extend later */
+ if (s[1] == '\0') {
+ printf("ipfw sysctl: missing value\n\n");
+ return 0;
+ }
+ *s = '\0';
+ i = strtol(s+1, NULL, 0);
+ sysctlbyname(av[2], NULL, NULL, &i, sizeof(int));
+ }
+ return 0;
+ }
+#endif
+
+ /* Save arguments for final freeing of memory. */
+ save_av = av;
+
+ optind = optreset = 1; /* restart getopt() */
+ while ((ch = getopt(ac, av, "abcdefhinNp:qs:STtv")) != -1)
+ switch (ch) {
+ case 'a':
+ do_acct = 1;
+ break;
+
+ case 'b':
+ co.comment_only = 1;
+ co.do_compact = 1;
+ break;
+
+ case 'c':
+ co.do_compact = 1;
+ break;
+
+ case 'd':
+ co.do_dynamic = 1;
+ break;
+
+ case 'e':
+ co.do_expired = 1;
+ break;
+
+ case 'f':
+ co.do_force = 1;
+ break;
+
+ case 'h': /* help */
+ free(save_av);
+ help();
+ break; /* NOTREACHED */
+
+ case 'i':
+ co.do_value_as_ip = 1;
+ break;
+
+ case 'n':
+ co.test_only = 1;
+ break;
+
+ case 'N':
+ co.do_resolv = 1;
+ break;
+
+ case 'p':
+ errx(EX_USAGE, "An absolute pathname must be used "
+ "with -p option.");
+ /* NOTREACHED */
+
+ case 'q':
+ co.do_quiet = 1;
+ break;
+
+ case 's': /* sort */
+ co.do_sort = atoi(optarg);
+ break;
+
+ case 'S':
+ co.show_sets = 1;
+ break;
+
+ case 't':
+ co.do_time = 1;
+ break;
+
+ case 'T':
+ co.do_time = 2; /* numeric timestamp */
+ break;
+
+ case 'v': /* verbose */
+ co.verbose = 1;
+ break;
+
+ default:
+ free(save_av);
+ return 1;
+ }
+
+ ac -= optind;
+ av += optind;
+ NEED1("bad arguments, for usage summary ``ipfw''");
+
+ /*
+ * An undocumented behaviour of ipfw1 was to allow rule numbers first,
+ * e.g. "100 add allow ..." instead of "add 100 allow ...".
+ * In case, swap first and second argument to get the normal form.
+ */
+ if (ac > 1 && isdigit(*av[0])) {
+ char *p = av[0];
+
+ av[0] = av[1];
+ av[1] = p;
+ }
+
+ /*
+ * Optional: pipe, queue or nat.
+ */
+ co.do_nat = 0;
+ co.do_pipe = 0;
+ co.use_set = 0;
+ if (!strncmp(*av, "nat", strlen(*av)))
+ co.do_nat = 1;
+ else if (!strncmp(*av, "pipe", strlen(*av)))
+ co.do_pipe = 1;
+ else if (_substrcmp(*av, "queue") == 0)
+ co.do_pipe = 2;
+ else if (_substrcmp(*av, "flowset") == 0)
+ co.do_pipe = 2;
+ else if (_substrcmp(*av, "sched") == 0)
+ co.do_pipe = 3;
+ else if (!strncmp(*av, "set", strlen(*av))) {
+ if (ac > 1 && isdigit(av[1][0])) {
+ co.use_set = strtonum(av[1], 0, resvd_set_number,
+ &errstr);
+ if (errstr)
+ errx(EX_DATAERR,
+ "invalid set number %s\n", av[1]);
+ ac -= 2; av += 2; co.use_set++;
+ }
+ }
+
+ if (co.do_pipe || co.do_nat) {
+ ac--;
+ av++;
+ }
+ NEED1("missing command");
+
+ /*
+ * For pipes, queues and nats we normally say 'nat|pipe NN config'
+ * but the code is easier to parse as 'nat|pipe config NN'
+ * so we swap the two arguments.
+ */
+ if ((co.do_pipe || co.do_nat) && ac > 1 && isdigit(*av[0])) {
+ char *p = av[0];
+
+ av[0] = av[1];
+ av[1] = p;
+ }
+
+ if (co.use_set == 0) {
+ if (_substrcmp(*av, "add") == 0)
+ ipfw_add(av);
+ else if (co.do_nat && _substrcmp(*av, "show") == 0)
+ ipfw_show_nat(ac, av);
+ else if (co.do_pipe && _substrcmp(*av, "config") == 0)
+ ipfw_config_pipe(ac, av);
+ else if (co.do_nat && _substrcmp(*av, "config") == 0)
+ ipfw_config_nat(ac, av);
+ else if (_substrcmp(*av, "set") == 0)
+ ipfw_sets_handler(av);
+ else if (_substrcmp(*av, "table") == 0)
+ ipfw_table_handler(ac, av);
+ else if (_substrcmp(*av, "enable") == 0)
+ ipfw_sysctl_handler(av, 1);
+ else if (_substrcmp(*av, "disable") == 0)
+ ipfw_sysctl_handler(av, 0);
+ else
+ try_next = 1;
+ }
+
+ if (co.use_set || try_next) {
+ if (_substrcmp(*av, "delete") == 0)
+ ipfw_delete(av);
+ else if (_substrcmp(*av, "flush") == 0)
+ ipfw_flush(co.do_force);
+ else if (_substrcmp(*av, "zero") == 0)
+ ipfw_zero(ac, av, 0 /* IP_FW_ZERO */);
+ else if (_substrcmp(*av, "resetlog") == 0)
+ ipfw_zero(ac, av, 1 /* IP_FW_RESETLOG */);
+ else if (_substrcmp(*av, "print") == 0 ||
+ _substrcmp(*av, "list") == 0)
+ ipfw_list(ac, av, do_acct);
+ else if (_substrcmp(*av, "show") == 0)
+ ipfw_list(ac, av, 1 /* show counters */);
+ else if (_substrcmp(*av, "table") == 0)
+ ipfw_table_handler(ac, av);
+ else if (_substrcmp(*av, "internal") == 0)
+ ipfw_internal_handler(ac, av);
+ else
+ errx(EX_USAGE, "bad command `%s'", *av);
+ }
+
+ /* Free memory allocated in the argument parsing. */
+ free(save_av);
+ return 0;
+}
+
+
+static void
+ipfw_readfile(int ac, char *av[])
+{
+#define MAX_ARGS 32
+ char buf[4096];
+ char *progname = av[0]; /* original program name */
+ const char *cmd = NULL; /* preprocessor name, if any */
+ const char *filename = av[ac-1]; /* file to read */
+ int c, lineno=0;
+ FILE *f = NULL;
+ pid_t preproc = 0;
+
+ while ((c = getopt(ac, av, "cfNnp:qS")) != -1) {
+ switch(c) {
+ case 'c':
+ co.do_compact = 1;
+ break;
+
+ case 'f':
+ co.do_force = 1;
+ break;
+
+ case 'N':
+ co.do_resolv = 1;
+ break;
+
+ case 'n':
+ co.test_only = 1;
+ break;
+
+ case 'p':
+ /*
+ * ipfw -p cmd [args] filename
+ *
+ * We are done with getopt(). All arguments
+ * except the filename go to the preprocessor,
+ * so we need to do the following:
+ * - check that a filename is actually present;
+ * - advance av by optind-1 to skip arguments
+ * already processed;
+ * - decrease ac by optind, to remove the args
+ * already processed and the final filename;
+ * - set the last entry in av[] to NULL so
+ * popen() can detect the end of the array;
+ * - set optind=ac to let getopt() terminate.
+ */
+ if (optind == ac)
+ errx(EX_USAGE, "no filename argument");
+ cmd = optarg;
+ av[ac-1] = NULL;
+ av += optind - 1;
+ ac -= optind;
+ optind = ac;
+ break;
+
+ case 'q':
+ co.do_quiet = 1;
+ break;
+
+ case 'S':
+ co.show_sets = 1;
+ break;
+
+ default:
+ errx(EX_USAGE, "bad arguments, for usage"
+ " summary ``ipfw''");
+ }
+
+ }
+
+ if (cmd == NULL && ac != optind + 1)
+ errx(EX_USAGE, "extraneous filename arguments %s", av[ac-1]);
+
+ if ((f = fopen(filename, "r")) == NULL)
+ err(EX_UNAVAILABLE, "fopen: %s", filename);
+
+ if (cmd != NULL) { /* pipe through preprocessor */
+ int pipedes[2];
+
+ if (pipe(pipedes) == -1)
+ err(EX_OSERR, "cannot create pipe");
+
+ preproc = fork();
+ if (preproc == -1)
+ err(EX_OSERR, "cannot fork");
+
+ if (preproc == 0) {
+ /*
+ * Child, will run the preprocessor with the
+ * file on stdin and the pipe on stdout.
+ */
+ if (dup2(fileno(f), 0) == -1
+ || dup2(pipedes[1], 1) == -1)
+ err(EX_OSERR, "dup2()");
+ fclose(f);
+ close(pipedes[1]);
+ close(pipedes[0]);
+ execvp(cmd, av);
+ err(EX_OSERR, "execvp(%s) failed", cmd);
+ } else { /* parent, will reopen f as the pipe */
+ fclose(f);
+ close(pipedes[1]);
+ if ((f = fdopen(pipedes[0], "r")) == NULL) {
+ int savederrno = errno;
+
+ (void)kill(preproc, SIGTERM);
+ errno = savederrno;
+ err(EX_OSERR, "fdopen()");
+ }
+ }
+ }
+
+ while (fgets(buf, sizeof(buf), f)) { /* read commands */
+ char linename[20];
+ char *args[2];
+
+ lineno++;
+ snprintf(linename, sizeof(linename), "Line %d", lineno);
+ setprogname(linename); /* XXX */
+ args[0] = progname;
+ args[1] = buf;
+ ipfw_main(2, args);
+ }
+ fclose(f);
+ if (cmd != NULL) {
+ int status;
+
+ if (waitpid(preproc, &status, 0) == -1)
+ errx(EX_OSERR, "waitpid()");
+ if (WIFEXITED(status) && WEXITSTATUS(status) != EX_OK)
+ errx(EX_UNAVAILABLE,
+ "preprocessor exited with status %d",
+ WEXITSTATUS(status));
+ else if (WIFSIGNALED(status))
+ errx(EX_UNAVAILABLE,
+ "preprocessor exited with signal %d",
+ WTERMSIG(status));
+ }
+}
+
+int
+main(int ac, char *av[])
+{
+#if defined(_WIN32) && defined(TCC)
+ {
+ WSADATA wsaData;
+ int ret=0;
+ unsigned short wVersionRequested = MAKEWORD(2, 2);
+ ret = WSAStartup(wVersionRequested, &wsaData);
+ if (ret != 0) {
+ /* Tell the user that we could not find a usable */
+ /* Winsock DLL. */
+ printf("WSAStartup failed with error: %d\n", ret);
+ return 1;
+ }
+ }
+#endif
+ /*
+ * If the last argument is an absolute pathname, interpret it
+ * as a file to be preprocessed.
+ */
+
+ if (ac > 1 && av[ac - 1][0] == '/') {
+ if (access(av[ac - 1], R_OK) == 0)
+ ipfw_readfile(ac, av);
+ else
+ err(EX_USAGE, "pathname: %s", av[ac - 1]);
+ } else {
+ if (ipfw_main(ac, av)) {
+ errx(EX_USAGE,
+ "usage: ipfw [options]\n"
+ "do \"ipfw -h\" or \"man ipfw\" for details");
+ }
+ }
+ return EX_OK;
+}
diff --git a/sbin/ipfw/nat.c b/sbin/ipfw/nat.c
new file mode 100644
index 0000000..184b172
--- /dev/null
+++ b/sbin/ipfw/nat.c
@@ -0,0 +1,1114 @@
+/*
+ * Copyright (c) 2002-2003 Luigi Rizzo
+ * Copyright (c) 1996 Alex Nash, Paul Traina, Poul-Henning Kamp
+ * Copyright (c) 1994 Ugen J.S.Antsilevich
+ *
+ * Idea and grammar partially left from:
+ * Copyright (c) 1993 Daniel Boulet
+ *
+ * Redistribution and use in source forms, with and without modification,
+ * are permitted provided that this entire comment appears intact.
+ *
+ * Redistribution in binary form may occur without any restrictions.
+ * Obviously, it would be nice if you gave credit where credit is due
+ * but requiring it would be too onerous.
+ *
+ * This software is provided ``AS IS'' without any warranties of any kind.
+ *
+ * NEW command line interface for IP firewall facility
+ *
+ * $FreeBSD$
+ *
+ * In-kernel nat support
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+
+#include "ipfw2.h"
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <net/route.h> /* def. of struct route */
+#include <netinet/in.h>
+#include <netinet/ip_fw.h>
+#include <arpa/inet.h>
+#include <alias.h>
+
+typedef int (nat_cb_t)(struct nat44_cfg_nat *cfg, void *arg);
+static void nat_show_cfg(struct nat44_cfg_nat *n, void *arg);
+static void nat_show_log(struct nat44_cfg_nat *n, void *arg);
+static int nat_show_data(struct nat44_cfg_nat *cfg, void *arg);
+static int natname_cmp(const void *a, const void *b);
+static int nat_foreach(nat_cb_t *f, void *arg, int sort);
+static int nat_get_cmd(char *name, uint16_t cmd, ipfw_obj_header **ooh);
+
+static struct _s_x nat_params[] = {
+ { "ip", TOK_IP },
+ { "if", TOK_IF },
+ { "log", TOK_ALOG },
+ { "deny_in", TOK_DENY_INC },
+ { "same_ports", TOK_SAME_PORTS },
+ { "unreg_only", TOK_UNREG_ONLY },
+ { "skip_global", TOK_SKIP_GLOBAL },
+ { "reset", TOK_RESET_ADDR },
+ { "reverse", TOK_ALIAS_REV },
+ { "proxy_only", TOK_PROXY_ONLY },
+ { "redirect_addr", TOK_REDIR_ADDR },
+ { "redirect_port", TOK_REDIR_PORT },
+ { "redirect_proto", TOK_REDIR_PROTO },
+ { NULL, 0 } /* terminator */
+};
+
+
+/*
+ * Search for interface with name "ifn", and fill n accordingly:
+ *
+ * n->ip ip address of interface "ifn"
+ * n->if_name copy of interface name "ifn"
+ */
+static void
+set_addr_dynamic(const char *ifn, struct nat44_cfg_nat *n)
+{
+ size_t needed;
+ int mib[6];
+ char *buf, *lim, *next;
+ struct if_msghdr *ifm;
+ struct ifa_msghdr *ifam;
+ struct sockaddr_dl *sdl;
+ struct sockaddr_in *sin;
+ int ifIndex, ifMTU;
+
+ mib[0] = CTL_NET;
+ mib[1] = PF_ROUTE;
+ mib[2] = 0;
+ mib[3] = AF_INET;
+ mib[4] = NET_RT_IFLIST;
+ mib[5] = 0;
+/*
+ * Get interface data.
+ */
+ if (sysctl(mib, 6, NULL, &needed, NULL, 0) == -1)
+ err(1, "iflist-sysctl-estimate");
+ buf = safe_calloc(1, needed);
+ if (sysctl(mib, 6, buf, &needed, NULL, 0) == -1)
+ err(1, "iflist-sysctl-get");
+ lim = buf + needed;
+/*
+ * Loop through interfaces until one with
+ * given name is found. This is done to
+ * find correct interface index for routing
+ * message processing.
+ */
+ ifIndex = 0;
+ next = buf;
+ while (next < lim) {
+ ifm = (struct if_msghdr *)next;
+ next += ifm->ifm_msglen;
+ if (ifm->ifm_version != RTM_VERSION) {
+ if (co.verbose)
+ warnx("routing message version %d "
+ "not understood", ifm->ifm_version);
+ continue;
+ }
+ if (ifm->ifm_type == RTM_IFINFO) {
+ sdl = (struct sockaddr_dl *)(ifm + 1);
+ if (strlen(ifn) == sdl->sdl_nlen &&
+ strncmp(ifn, sdl->sdl_data, sdl->sdl_nlen) == 0) {
+ ifIndex = ifm->ifm_index;
+ ifMTU = ifm->ifm_data.ifi_mtu;
+ break;
+ }
+ }
+ }
+ if (!ifIndex)
+ errx(1, "unknown interface name %s", ifn);
+/*
+ * Get interface address.
+ */
+ sin = NULL;
+ while (next < lim) {
+ ifam = (struct ifa_msghdr *)next;
+ next += ifam->ifam_msglen;
+ if (ifam->ifam_version != RTM_VERSION) {
+ if (co.verbose)
+ warnx("routing message version %d "
+ "not understood", ifam->ifam_version);
+ continue;
+ }
+ if (ifam->ifam_type != RTM_NEWADDR)
+ break;
+ if (ifam->ifam_addrs & RTA_IFA) {
+ int i;
+ char *cp = (char *)(ifam + 1);
+
+ for (i = 1; i < RTA_IFA; i <<= 1) {
+ if (ifam->ifam_addrs & i)
+ cp += SA_SIZE((struct sockaddr *)cp);
+ }
+ if (((struct sockaddr *)cp)->sa_family == AF_INET) {
+ sin = (struct sockaddr_in *)cp;
+ break;
+ }
+ }
+ }
+ if (sin == NULL)
+ n->ip.s_addr = htonl(INADDR_ANY);
+ else
+ n->ip = sin->sin_addr;
+ strncpy(n->if_name, ifn, IF_NAMESIZE);
+
+ free(buf);
+}
+
+/*
+ * XXX - The following functions, macros and definitions come from natd.c:
+ * it would be better to move them outside natd.c, in a file
+ * (redirect_support.[ch]?) shared by ipfw and natd, but for now i can live
+ * with it.
+ */
+
+/*
+ * Definition of a port range, and macros to deal with values.
+ * FORMAT: HI 16-bits == first port in range, 0 == all ports.
+ * LO 16-bits == number of ports in range
+ * NOTES: - Port values are not stored in network byte order.
+ */
+
+#define port_range u_long
+
+#define GETLOPORT(x) ((x) >> 0x10)
+#define GETNUMPORTS(x) ((x) & 0x0000ffff)
+#define GETHIPORT(x) (GETLOPORT((x)) + GETNUMPORTS((x)))
+
+/* Set y to be the low-port value in port_range variable x. */
+#define SETLOPORT(x,y) ((x) = ((x) & 0x0000ffff) | ((y) << 0x10))
+
+/* Set y to be the number of ports in port_range variable x. */
+#define SETNUMPORTS(x,y) ((x) = ((x) & 0xffff0000) | (y))
+
+static void
+StrToAddr (const char* str, struct in_addr* addr)
+{
+ struct hostent* hp;
+
+ if (inet_aton (str, addr))
+ return;
+
+ hp = gethostbyname (str);
+ if (!hp)
+ errx (1, "unknown host %s", str);
+
+ memcpy (addr, hp->h_addr, sizeof (struct in_addr));
+}
+
+static int
+StrToPortRange (const char* str, const char* proto, port_range *portRange)
+{
+ char* sep;
+ struct servent* sp;
+ char* end;
+ u_short loPort;
+ u_short hiPort;
+
+ /* First see if this is a service, return corresponding port if so. */
+ sp = getservbyname (str,proto);
+ if (sp) {
+ SETLOPORT(*portRange, ntohs(sp->s_port));
+ SETNUMPORTS(*portRange, 1);
+ return 0;
+ }
+
+ /* Not a service, see if it's a single port or port range. */
+ sep = strchr (str, '-');
+ if (sep == NULL) {
+ SETLOPORT(*portRange, strtol(str, &end, 10));
+ if (end != str) {
+ /* Single port. */
+ SETNUMPORTS(*portRange, 1);
+ return 0;
+ }
+
+ /* Error in port range field. */
+ errx (EX_DATAERR, "%s/%s: unknown service", str, proto);
+ }
+
+ /* Port range, get the values and sanity check. */
+ sscanf (str, "%hu-%hu", &loPort, &hiPort);
+ SETLOPORT(*portRange, loPort);
+ SETNUMPORTS(*portRange, 0); /* Error by default */
+ if (loPort <= hiPort)
+ SETNUMPORTS(*portRange, hiPort - loPort + 1);
+
+ if (GETNUMPORTS(*portRange) == 0)
+ errx (EX_DATAERR, "invalid port range %s", str);
+
+ return 0;
+}
+
+static int
+StrToProto (const char* str)
+{
+ if (!strcmp (str, "tcp"))
+ return IPPROTO_TCP;
+
+ if (!strcmp (str, "udp"))
+ return IPPROTO_UDP;
+
+ if (!strcmp (str, "sctp"))
+ return IPPROTO_SCTP;
+ errx (EX_DATAERR, "unknown protocol %s. Expected sctp, tcp or udp", str);
+}
+
+static int
+StrToAddrAndPortRange (const char* str, struct in_addr* addr, char* proto,
+ port_range *portRange)
+{
+ char* ptr;
+
+ ptr = strchr (str, ':');
+ if (!ptr)
+ errx (EX_DATAERR, "%s is missing port number", str);
+
+ *ptr = '\0';
+ ++ptr;
+
+ StrToAddr (str, addr);
+ return StrToPortRange (ptr, proto, portRange);
+}
+
+/* End of stuff taken from natd.c. */
+
+/*
+ * The next 3 functions add support for the addr, port and proto redirect and
+ * their logic is loosely based on SetupAddressRedirect(), SetupPortRedirect()
+ * and SetupProtoRedirect() from natd.c.
+ *
+ * Every setup_* function fills at least one redirect entry
+ * (struct nat44_cfg_redir) and zero or more server pool entry
+ * (struct nat44_cfg_spool) in buf.
+ *
+ * The format of data in buf is:
+ *
+ * nat44_cfg_nat nat44_cfg_redir nat44_cfg_spool ...... nat44_cfg_spool
+ *
+ * ------------------------------------- ------------
+ * | | .....X ..... | | | | .....
+ * ------------------------------------- ...... ------------
+ * ^
+ * spool_cnt n=0 ...... n=(X-1)
+ *
+ * len points to the amount of available space in buf
+ * space counts the memory consumed by every function
+ *
+ * XXX - Every function get all the argv params so it
+ * has to check, in optional parameters, that the next
+ * args is a valid option for the redir entry and not
+ * another token. Only redir_port and redir_proto are
+ * affected by this.
+ */
+
+static int
+estimate_redir_addr(int *ac, char ***av)
+{
+ size_t space = sizeof(struct nat44_cfg_redir);
+ char *sep = **av;
+ u_int c = 0;
+
+ (void)ac; /* UNUSED */
+ while ((sep = strchr(sep, ',')) != NULL) {
+ c++;
+ sep++;
+ }
+
+ if (c > 0)
+ c++;
+
+ space += c * sizeof(struct nat44_cfg_spool);
+
+ return (space);
+}
+
+static int
+setup_redir_addr(char *buf, int *ac, char ***av)
+{
+ struct nat44_cfg_redir *r;
+ char *sep;
+ size_t space;
+
+ r = (struct nat44_cfg_redir *)buf;
+ r->mode = REDIR_ADDR;
+ /* Skip nat44_cfg_redir at beginning of buf. */
+ buf = &buf[sizeof(struct nat44_cfg_redir)];
+ space = sizeof(struct nat44_cfg_redir);
+
+ /* Extract local address. */
+ if (strchr(**av, ',') != NULL) {
+ struct nat44_cfg_spool *spool;
+
+ /* Setup LSNAT server pool. */
+ r->laddr.s_addr = INADDR_NONE;
+ sep = strtok(**av, ",");
+ while (sep != NULL) {
+ spool = (struct nat44_cfg_spool *)buf;
+ space += sizeof(struct nat44_cfg_spool);
+ StrToAddr(sep, &spool->addr);
+ spool->port = ~0;
+ r->spool_cnt++;
+ /* Point to the next possible nat44_cfg_spool. */
+ buf = &buf[sizeof(struct nat44_cfg_spool)];
+ sep = strtok(NULL, ",");
+ }
+ } else
+ StrToAddr(**av, &r->laddr);
+ (*av)++; (*ac)--;
+
+ /* Extract public address. */
+ StrToAddr(**av, &r->paddr);
+ (*av)++; (*ac)--;
+
+ return (space);
+}
+
+static int
+estimate_redir_port(int *ac, char ***av)
+{
+ size_t space = sizeof(struct nat44_cfg_redir);
+ char *sep = **av;
+ u_int c = 0;
+
+ (void)ac; /* UNUSED */
+ while ((sep = strchr(sep, ',')) != NULL) {
+ c++;
+ sep++;
+ }
+
+ if (c > 0)
+ c++;
+
+ space += c * sizeof(struct nat44_cfg_spool);
+
+ return (space);
+}
+
+static int
+setup_redir_port(char *buf, int *ac, char ***av)
+{
+ struct nat44_cfg_redir *r;
+ char *sep, *protoName, *lsnat = NULL;
+ size_t space;
+ u_short numLocalPorts;
+ port_range portRange;
+
+ numLocalPorts = 0;
+
+ r = (struct nat44_cfg_redir *)buf;
+ r->mode = REDIR_PORT;
+ /* Skip nat44_cfg_redir at beginning of buf. */
+ buf = &buf[sizeof(struct nat44_cfg_redir)];
+ space = sizeof(struct nat44_cfg_redir);
+
+ /*
+ * Extract protocol.
+ */
+ r->proto = StrToProto(**av);
+ protoName = **av;
+ (*av)++; (*ac)--;
+
+ /*
+ * Extract local address.
+ */
+ if (strchr(**av, ',') != NULL) {
+ r->laddr.s_addr = INADDR_NONE;
+ r->lport = ~0;
+ numLocalPorts = 1;
+ lsnat = **av;
+ } else {
+ /*
+ * The sctp nat does not allow the port numbers to be mapped to
+ * new port numbers. Therefore, no ports are to be specified
+ * in the target port field.
+ */
+ if (r->proto == IPPROTO_SCTP) {
+ if (strchr(**av, ':'))
+ errx(EX_DATAERR, "redirect_port:"
+ "port numbers do not change in sctp, so do "
+ "not specify them as part of the target");
+ else
+ StrToAddr(**av, &r->laddr);
+ } else {
+ if (StrToAddrAndPortRange(**av, &r->laddr, protoName,
+ &portRange) != 0)
+ errx(EX_DATAERR, "redirect_port: "
+ "invalid local port range");
+
+ r->lport = GETLOPORT(portRange);
+ numLocalPorts = GETNUMPORTS(portRange);
+ }
+ }
+ (*av)++; (*ac)--;
+
+ /*
+ * Extract public port and optionally address.
+ */
+ if (strchr(**av, ':') != NULL) {
+ if (StrToAddrAndPortRange(**av, &r->paddr, protoName,
+ &portRange) != 0)
+ errx(EX_DATAERR, "redirect_port: "
+ "invalid public port range");
+ } else {
+ r->paddr.s_addr = INADDR_ANY;
+ if (StrToPortRange(**av, protoName, &portRange) != 0)
+ errx(EX_DATAERR, "redirect_port: "
+ "invalid public port range");
+ }
+
+ r->pport = GETLOPORT(portRange);
+ if (r->proto == IPPROTO_SCTP) { /* so the logic below still works */
+ numLocalPorts = GETNUMPORTS(portRange);
+ r->lport = r->pport;
+ }
+ r->pport_cnt = GETNUMPORTS(portRange);
+ (*av)++; (*ac)--;
+
+ /*
+ * Extract remote address and optionally port.
+ */
+ /*
+ * NB: isdigit(**av) => we've to check that next parameter is really an
+ * option for this redirect entry, else stop here processing arg[cv].
+ */
+ if (*ac != 0 && isdigit(***av)) {
+ if (strchr(**av, ':') != NULL) {
+ if (StrToAddrAndPortRange(**av, &r->raddr, protoName,
+ &portRange) != 0)
+ errx(EX_DATAERR, "redirect_port: "
+ "invalid remote port range");
+ } else {
+ SETLOPORT(portRange, 0);
+ SETNUMPORTS(portRange, 1);
+ StrToAddr(**av, &r->raddr);
+ }
+ (*av)++; (*ac)--;
+ } else {
+ SETLOPORT(portRange, 0);
+ SETNUMPORTS(portRange, 1);
+ r->raddr.s_addr = INADDR_ANY;
+ }
+ r->rport = GETLOPORT(portRange);
+ r->rport_cnt = GETNUMPORTS(portRange);
+
+ /*
+ * Make sure port ranges match up, then add the redirect ports.
+ */
+ if (numLocalPorts != r->pport_cnt)
+ errx(EX_DATAERR, "redirect_port: "
+ "port ranges must be equal in size");
+
+ /* Remote port range is allowed to be '0' which means all ports. */
+ if (r->rport_cnt != numLocalPorts &&
+ (r->rport_cnt != 1 || r->rport != 0))
+ errx(EX_DATAERR, "redirect_port: remote port must"
+ "be 0 or equal to local port range in size");
+
+ /* Setup LSNAT server pool. */
+ if (lsnat != NULL) {
+ struct nat44_cfg_spool *spool;
+
+ sep = strtok(lsnat, ",");
+ while (sep != NULL) {
+ spool = (struct nat44_cfg_spool *)buf;
+ space += sizeof(struct nat44_cfg_spool);
+ /*
+ * The sctp nat does not allow the port numbers to
+ * be mapped to new port numbers. Therefore, no ports
+ * are to be specified in the target port field.
+ */
+ if (r->proto == IPPROTO_SCTP) {
+ if (strchr (sep, ':')) {
+ errx(EX_DATAERR, "redirect_port:"
+ "port numbers do not change in "
+ "sctp, so do not specify them as "
+ "part of the target");
+ } else {
+ StrToAddr(sep, &spool->addr);
+ spool->port = r->pport;
+ }
+ } else {
+ if (StrToAddrAndPortRange(sep, &spool->addr,
+ protoName, &portRange) != 0)
+ errx(EX_DATAERR, "redirect_port:"
+ "invalid local port range");
+ if (GETNUMPORTS(portRange) != 1)
+ errx(EX_DATAERR, "redirect_port: "
+ "local port must be single in "
+ "this context");
+ spool->port = GETLOPORT(portRange);
+ }
+ r->spool_cnt++;
+ /* Point to the next possible nat44_cfg_spool. */
+ buf = &buf[sizeof(struct nat44_cfg_spool)];
+ sep = strtok(NULL, ",");
+ }
+ }
+
+ return (space);
+}
+
+static int
+setup_redir_proto(char *buf, int *ac, char ***av)
+{
+ struct nat44_cfg_redir *r;
+ struct protoent *protoent;
+ size_t space;
+
+ r = (struct nat44_cfg_redir *)buf;
+ r->mode = REDIR_PROTO;
+ /* Skip nat44_cfg_redir at beginning of buf. */
+ buf = &buf[sizeof(struct nat44_cfg_redir)];
+ space = sizeof(struct nat44_cfg_redir);
+
+ /*
+ * Extract protocol.
+ */
+ protoent = getprotobyname(**av);
+ if (protoent == NULL)
+ errx(EX_DATAERR, "redirect_proto: unknown protocol %s", **av);
+ else
+ r->proto = protoent->p_proto;
+
+ (*av)++; (*ac)--;
+
+ /*
+ * Extract local address.
+ */
+ StrToAddr(**av, &r->laddr);
+
+ (*av)++; (*ac)--;
+
+ /*
+ * Extract optional public address.
+ */
+ if (*ac == 0) {
+ r->paddr.s_addr = INADDR_ANY;
+ r->raddr.s_addr = INADDR_ANY;
+ } else {
+ /* see above in setup_redir_port() */
+ if (isdigit(***av)) {
+ StrToAddr(**av, &r->paddr);
+ (*av)++; (*ac)--;
+
+ /*
+ * Extract optional remote address.
+ */
+ /* see above in setup_redir_port() */
+ if (*ac != 0 && isdigit(***av)) {
+ StrToAddr(**av, &r->raddr);
+ (*av)++; (*ac)--;
+ }
+ }
+ }
+
+ return (space);
+}
+
+static void
+nat_show_log(struct nat44_cfg_nat *n, void *arg)
+{
+ char *buf;
+
+ buf = (char *)(n + 1);
+ if (buf[0] != '\0')
+ printf("nat %s: %s\n", n->name, buf);
+}
+
+static void
+nat_show_cfg(struct nat44_cfg_nat *n, void *arg)
+{
+ int i, cnt, flag, off;
+ struct nat44_cfg_redir *t;
+ struct nat44_cfg_spool *s;
+ caddr_t buf;
+ struct protoent *p;
+
+ buf = (caddr_t)n;
+ flag = 1;
+ off = sizeof(*n);
+ printf("ipfw nat %s config", n->name);
+ if (strlen(n->if_name) != 0)
+ printf(" if %s", n->if_name);
+ else if (n->ip.s_addr != 0)
+ printf(" ip %s", inet_ntoa(n->ip));
+ while (n->mode != 0) {
+ if (n->mode & PKT_ALIAS_LOG) {
+ printf(" log");
+ n->mode &= ~PKT_ALIAS_LOG;
+ } else if (n->mode & PKT_ALIAS_DENY_INCOMING) {
+ printf(" deny_in");
+ n->mode &= ~PKT_ALIAS_DENY_INCOMING;
+ } else if (n->mode & PKT_ALIAS_SAME_PORTS) {
+ printf(" same_ports");
+ n->mode &= ~PKT_ALIAS_SAME_PORTS;
+ } else if (n->mode & PKT_ALIAS_SKIP_GLOBAL) {
+ printf(" skip_global");
+ n->mode &= ~PKT_ALIAS_SKIP_GLOBAL;
+ } else if (n->mode & PKT_ALIAS_UNREGISTERED_ONLY) {
+ printf(" unreg_only");
+ n->mode &= ~PKT_ALIAS_UNREGISTERED_ONLY;
+ } else if (n->mode & PKT_ALIAS_RESET_ON_ADDR_CHANGE) {
+ printf(" reset");
+ n->mode &= ~PKT_ALIAS_RESET_ON_ADDR_CHANGE;
+ } else if (n->mode & PKT_ALIAS_REVERSE) {
+ printf(" reverse");
+ n->mode &= ~PKT_ALIAS_REVERSE;
+ } else if (n->mode & PKT_ALIAS_PROXY_ONLY) {
+ printf(" proxy_only");
+ n->mode &= ~PKT_ALIAS_PROXY_ONLY;
+ }
+ }
+ /* Print all the redirect's data configuration. */
+ for (cnt = 0; cnt < n->redir_cnt; cnt++) {
+ t = (struct nat44_cfg_redir *)&buf[off];
+ off += sizeof(struct nat44_cfg_redir);
+ switch (t->mode) {
+ case REDIR_ADDR:
+ printf(" redirect_addr");
+ if (t->spool_cnt == 0)
+ printf(" %s", inet_ntoa(t->laddr));
+ else
+ for (i = 0; i < t->spool_cnt; i++) {
+ s = (struct nat44_cfg_spool *)&buf[off];
+ if (i)
+ printf(",");
+ else
+ printf(" ");
+ printf("%s", inet_ntoa(s->addr));
+ off += sizeof(struct nat44_cfg_spool);
+ }
+ printf(" %s", inet_ntoa(t->paddr));
+ break;
+ case REDIR_PORT:
+ p = getprotobynumber(t->proto);
+ printf(" redirect_port %s ", p->p_name);
+ if (!t->spool_cnt) {
+ printf("%s:%u", inet_ntoa(t->laddr), t->lport);
+ if (t->pport_cnt > 1)
+ printf("-%u", t->lport +
+ t->pport_cnt - 1);
+ } else
+ for (i=0; i < t->spool_cnt; i++) {
+ s = (struct nat44_cfg_spool *)&buf[off];
+ if (i)
+ printf(",");
+ printf("%s:%u", inet_ntoa(s->addr),
+ s->port);
+ off += sizeof(struct nat44_cfg_spool);
+ }
+
+ printf(" ");
+ if (t->paddr.s_addr)
+ printf("%s:", inet_ntoa(t->paddr));
+ printf("%u", t->pport);
+ if (!t->spool_cnt && t->pport_cnt > 1)
+ printf("-%u", t->pport + t->pport_cnt - 1);
+
+ if (t->raddr.s_addr) {
+ printf(" %s", inet_ntoa(t->raddr));
+ if (t->rport) {
+ printf(":%u", t->rport);
+ if (!t->spool_cnt && t->rport_cnt > 1)
+ printf("-%u", t->rport +
+ t->rport_cnt - 1);
+ }
+ }
+ break;
+ case REDIR_PROTO:
+ p = getprotobynumber(t->proto);
+ printf(" redirect_proto %s %s", p->p_name,
+ inet_ntoa(t->laddr));
+ if (t->paddr.s_addr != 0) {
+ printf(" %s", inet_ntoa(t->paddr));
+ if (t->raddr.s_addr)
+ printf(" %s", inet_ntoa(t->raddr));
+ }
+ break;
+ default:
+ errx(EX_DATAERR, "unknown redir mode");
+ break;
+ }
+ }
+ printf("\n");
+}
+
+void
+ipfw_config_nat(int ac, char **av)
+{
+ ipfw_obj_header *oh;
+ struct nat44_cfg_nat *n; /* Nat instance configuration. */
+ int i, off, tok, ac1;
+ char *id, *buf, **av1, *end;
+ size_t len;
+
+ av++;
+ ac--;
+ /* Nat id. */
+ if (ac == 0)
+ errx(EX_DATAERR, "missing nat id");
+ id = *av;
+ i = (int)strtol(id, &end, 0);
+ if (i <= 0 || *end != '\0')
+ errx(EX_DATAERR, "illegal nat id: %s", id);
+ av++;
+ ac--;
+ if (ac == 0)
+ errx(EX_DATAERR, "missing option");
+
+ len = sizeof(*oh) + sizeof(*n);
+ ac1 = ac;
+ av1 = av;
+ while (ac1 > 0) {
+ tok = match_token(nat_params, *av1);
+ ac1--;
+ av1++;
+ switch (tok) {
+ case TOK_IP:
+ case TOK_IF:
+ ac1--;
+ av1++;
+ break;
+ case TOK_ALOG:
+ case TOK_DENY_INC:
+ case TOK_SAME_PORTS:
+ case TOK_SKIP_GLOBAL:
+ case TOK_UNREG_ONLY:
+ case TOK_RESET_ADDR:
+ case TOK_ALIAS_REV:
+ case TOK_PROXY_ONLY:
+ break;
+ case TOK_REDIR_ADDR:
+ if (ac1 < 2)
+ errx(EX_DATAERR, "redirect_addr: "
+ "not enough arguments");
+ len += estimate_redir_addr(&ac1, &av1);
+ av1 += 2;
+ ac1 -= 2;
+ break;
+ case TOK_REDIR_PORT:
+ if (ac1 < 3)
+ errx(EX_DATAERR, "redirect_port: "
+ "not enough arguments");
+ av1++;
+ ac1--;
+ len += estimate_redir_port(&ac1, &av1);
+ av1 += 2;
+ ac1 -= 2;
+ /* Skip optional remoteIP/port */
+ if (ac1 != 0 && isdigit(**av1)) {
+ av1++;
+ ac1--;
+ }
+ break;
+ case TOK_REDIR_PROTO:
+ if (ac1 < 2)
+ errx(EX_DATAERR, "redirect_proto: "
+ "not enough arguments");
+ len += sizeof(struct nat44_cfg_redir);
+ av1 += 2;
+ ac1 -= 2;
+ /* Skip optional remoteIP/port */
+ if (ac1 != 0 && isdigit(**av1)) {
+ av1++;
+ ac1--;
+ }
+ if (ac1 != 0 && isdigit(**av1)) {
+ av1++;
+ ac1--;
+ }
+ break;
+ default:
+ errx(EX_DATAERR, "unrecognised option ``%s''", av1[-1]);
+ }
+ }
+
+ if ((buf = malloc(len)) == NULL)
+ errx(EX_OSERR, "malloc failed");
+
+ /* Offset in buf: save space for header at the beginning. */
+ off = sizeof(*oh) + sizeof(*n);
+ memset(buf, 0, len);
+ oh = (ipfw_obj_header *)buf;
+ n = (struct nat44_cfg_nat *)(oh + 1);
+ oh->ntlv.head.length = sizeof(oh->ntlv);
+ snprintf(oh->ntlv.name, sizeof(oh->ntlv.name), "%d", i);
+ snprintf(n->name, sizeof(n->name), "%d", i);
+
+ while (ac > 0) {
+ tok = match_token(nat_params, *av);
+ ac--;
+ av++;
+ switch (tok) {
+ case TOK_IP:
+ if (ac == 0)
+ errx(EX_DATAERR, "missing option");
+ if (!inet_aton(av[0], &(n->ip)))
+ errx(EX_DATAERR, "bad ip address ``%s''",
+ av[0]);
+ ac--;
+ av++;
+ break;
+ case TOK_IF:
+ if (ac == 0)
+ errx(EX_DATAERR, "missing option");
+ set_addr_dynamic(av[0], n);
+ ac--;
+ av++;
+ break;
+ case TOK_ALOG:
+ n->mode |= PKT_ALIAS_LOG;
+ break;
+ case TOK_DENY_INC:
+ n->mode |= PKT_ALIAS_DENY_INCOMING;
+ break;
+ case TOK_SAME_PORTS:
+ n->mode |= PKT_ALIAS_SAME_PORTS;
+ break;
+ case TOK_UNREG_ONLY:
+ n->mode |= PKT_ALIAS_UNREGISTERED_ONLY;
+ break;
+ case TOK_SKIP_GLOBAL:
+ n->mode |= PKT_ALIAS_SKIP_GLOBAL;
+ break;
+ case TOK_RESET_ADDR:
+ n->mode |= PKT_ALIAS_RESET_ON_ADDR_CHANGE;
+ break;
+ case TOK_ALIAS_REV:
+ n->mode |= PKT_ALIAS_REVERSE;
+ break;
+ case TOK_PROXY_ONLY:
+ n->mode |= PKT_ALIAS_PROXY_ONLY;
+ break;
+ /*
+ * All the setup_redir_* functions work directly in
+ * the final buffer, see above for details.
+ */
+ case TOK_REDIR_ADDR:
+ case TOK_REDIR_PORT:
+ case TOK_REDIR_PROTO:
+ switch (tok) {
+ case TOK_REDIR_ADDR:
+ i = setup_redir_addr(&buf[off], &ac, &av);
+ break;
+ case TOK_REDIR_PORT:
+ i = setup_redir_port(&buf[off], &ac, &av);
+ break;
+ case TOK_REDIR_PROTO:
+ i = setup_redir_proto(&buf[off], &ac, &av);
+ break;
+ }
+ n->redir_cnt++;
+ off += i;
+ break;
+ }
+ }
+
+ i = do_set3(IP_FW_NAT44_XCONFIG, &oh->opheader, len);
+ if (i != 0)
+ err(1, "setsockopt(%s)", "IP_FW_NAT44_XCONFIG");
+
+ if (!co.do_quiet) {
+ /* After every modification, we show the resultant rule. */
+ int _ac = 3;
+ const char *_av[] = {"show", "config", id};
+ ipfw_show_nat(_ac, (char **)(void *)_av);
+ }
+}
+
+struct nat_list_arg {
+ uint16_t cmd;
+ int is_all;
+};
+
+static int
+nat_show_data(struct nat44_cfg_nat *cfg, void *arg)
+{
+ struct nat_list_arg *nla;
+ ipfw_obj_header *oh;
+
+ nla = (struct nat_list_arg *)arg;
+
+ switch (nla->cmd) {
+ case IP_FW_NAT44_XGETCONFIG:
+ if (nat_get_cmd(cfg->name, nla->cmd, &oh) != 0) {
+ warnx("Error getting nat instance %s info", cfg->name);
+ break;
+ }
+ nat_show_cfg((struct nat44_cfg_nat *)(oh + 1), NULL);
+ free(oh);
+ break;
+ case IP_FW_NAT44_XGETLOG:
+ if (nat_get_cmd(cfg->name, nla->cmd, &oh) == 0) {
+ nat_show_log((struct nat44_cfg_nat *)(oh + 1), NULL);
+ free(oh);
+ break;
+ }
+ /* Handle error */
+ if (nla->is_all != 0 && errno == ENOENT)
+ break;
+ warn("Error getting nat instance %s info", cfg->name);
+ break;
+ }
+
+ return (0);
+}
+
+/*
+ * Compare nat names.
+ * Honor number comparison.
+ */
+static int
+natname_cmp(const void *a, const void *b)
+{
+ struct nat44_cfg_nat *ia, *ib;
+
+ ia = (struct nat44_cfg_nat *)a;
+ ib = (struct nat44_cfg_nat *)b;
+
+ return (stringnum_cmp(ia->name, ib->name));
+}
+
+/*
+ * Retrieves nat list from kernel,
+ * optionally sorts it and calls requested function for each table.
+ * Returns 0 on success.
+ */
+static int
+nat_foreach(nat_cb_t *f, void *arg, int sort)
+{
+ ipfw_obj_lheader *olh;
+ struct nat44_cfg_nat *cfg;
+ size_t sz;
+ int i, error;
+
+ /* Start with reasonable default */
+ sz = sizeof(*olh) + 16 * sizeof(struct nat44_cfg_nat);
+
+ for (;;) {
+ if ((olh = calloc(1, sz)) == NULL)
+ return (ENOMEM);
+
+ olh->size = sz;
+ if (do_get3(IP_FW_NAT44_LIST_NAT, &olh->opheader, &sz) != 0) {
+ sz = olh->size;
+ free(olh);
+ if (errno == ENOMEM)
+ continue;
+ return (errno);
+ }
+
+ if (sort != 0)
+ qsort(olh + 1, olh->count, olh->objsize, natname_cmp);
+
+ cfg = (struct nat44_cfg_nat*)(olh + 1);
+ for (i = 0; i < olh->count; i++) {
+ error = f(cfg, arg); /* Ignore errors for now */
+ cfg = (struct nat44_cfg_nat *)((caddr_t)cfg +
+ olh->objsize);
+ }
+
+ free(olh);
+ break;
+ }
+
+ return (0);
+}
+
+static int
+nat_get_cmd(char *name, uint16_t cmd, ipfw_obj_header **ooh)
+{
+ ipfw_obj_header *oh;
+ struct nat44_cfg_nat *cfg;
+ size_t sz;
+
+ /* Start with reasonable default */
+ sz = sizeof(*oh) + sizeof(*cfg) + 128;
+
+ for (;;) {
+ if ((oh = calloc(1, sz)) == NULL)
+ return (ENOMEM);
+ cfg = (struct nat44_cfg_nat *)(oh + 1);
+ oh->ntlv.head.length = sizeof(oh->ntlv);
+ strlcpy(oh->ntlv.name, name, sizeof(oh->ntlv.name));
+ strlcpy(cfg->name, name, sizeof(cfg->name));
+
+ if (do_get3(cmd, &oh->opheader, &sz) != 0) {
+ sz = cfg->size;
+ free(oh);
+ if (errno == ENOMEM)
+ continue;
+ return (errno);
+ }
+
+ *ooh = oh;
+ break;
+ }
+
+ return (0);
+}
+
+void
+ipfw_show_nat(int ac, char **av)
+{
+ ipfw_obj_header *oh;
+ char *name;
+ int cmd;
+ struct nat_list_arg nla;
+
+ ac--;
+ av++;
+
+ if (co.test_only)
+ return;
+
+ /* Parse parameters. */
+ cmd = 0; /* XXX: Change to IP_FW_NAT44_XGETLOG @ MFC */
+ name = NULL;
+ for ( ; ac != 0; ac--, av++) {
+ if (!strncmp(av[0], "config", strlen(av[0]))) {
+ cmd = IP_FW_NAT44_XGETCONFIG;
+ continue;
+ }
+ if (strcmp(av[0], "log") == 0) {
+ cmd = IP_FW_NAT44_XGETLOG;
+ continue;
+ }
+ if (name != NULL)
+ err(EX_USAGE,"only one instance name may be specified");
+ name = av[0];
+ }
+
+ if (cmd == 0)
+ errx(EX_USAGE, "Please specify action. Available: config,log");
+
+ if (name == NULL) {
+ memset(&nla, 0, sizeof(nla));
+ nla.cmd = cmd;
+ nla.is_all = 1;
+ nat_foreach(nat_show_data, &nla, 1);
+ } else {
+ if (nat_get_cmd(name, cmd, &oh) != 0)
+ err(EX_OSERR, "Error getting nat %s instance info", name);
+ nat_show_cfg((struct nat44_cfg_nat *)(oh + 1), NULL);
+ free(oh);
+ }
+}
+
diff --git a/sbin/ipfw/tables.c b/sbin/ipfw/tables.c
new file mode 100644
index 0000000..08f4731
--- /dev/null
+++ b/sbin/ipfw/tables.c
@@ -0,0 +1,1966 @@
+/*
+ * Copyright (c) 2014 Yandex LLC
+ * Copyright (c) 2014 Alexander V. Chernikov
+ *
+ * Redistribution and use in source forms, with and without modification,
+ * are permitted provided that this entire comment appears intact.
+ *
+ * Redistribution in binary form may occur without any restrictions.
+ * Obviously, it would be nice if you gave credit where credit is due
+ * but requiring it would be too onerous.
+ *
+ * This software is provided ``AS IS'' without any warranties of any kind.
+ *
+ * in-kernel ipfw tables support.
+ *
+ * $FreeBSD$
+ */
+
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <netinet/ip_fw.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include "ipfw2.h"
+
+static void table_modify_record(ipfw_obj_header *oh, int ac, char *av[],
+ int add, int quiet, int update, int atomic);
+static int table_flush(ipfw_obj_header *oh);
+static int table_destroy(ipfw_obj_header *oh);
+static int table_do_create(ipfw_obj_header *oh, ipfw_xtable_info *i);
+static int table_do_modify(ipfw_obj_header *oh, ipfw_xtable_info *i);
+static int table_do_swap(ipfw_obj_header *oh, char *second);
+static void table_create(ipfw_obj_header *oh, int ac, char *av[]);
+static void table_modify(ipfw_obj_header *oh, int ac, char *av[]);
+static void table_lookup(ipfw_obj_header *oh, int ac, char *av[]);
+static void table_lock(ipfw_obj_header *oh, int lock);
+static int table_swap(ipfw_obj_header *oh, char *second);
+static int table_get_info(ipfw_obj_header *oh, ipfw_xtable_info *i);
+static int table_show_info(ipfw_xtable_info *i, void *arg);
+static void table_fill_ntlv(ipfw_obj_ntlv *ntlv, char *name, uint32_t set,
+ uint16_t uidx);
+
+static int table_flush_one(ipfw_xtable_info *i, void *arg);
+static int table_show_one(ipfw_xtable_info *i, void *arg);
+static int table_do_get_list(ipfw_xtable_info *i, ipfw_obj_header **poh);
+static void table_show_list(ipfw_obj_header *oh, int need_header);
+static void table_show_entry(ipfw_xtable_info *i, ipfw_obj_tentry *tent);
+
+static void tentry_fill_key(ipfw_obj_header *oh, ipfw_obj_tentry *tent,
+ char *key, int add, uint8_t *ptype, uint32_t *pvmask, ipfw_xtable_info *xi);
+static void tentry_fill_value(ipfw_obj_header *oh, ipfw_obj_tentry *tent,
+ char *arg, uint8_t type, uint32_t vmask);
+static void table_show_value(char *buf, size_t bufsize, ipfw_table_value *v,
+ uint32_t vmask, int print_ip);
+
+typedef int (table_cb_t)(ipfw_xtable_info *i, void *arg);
+static int tables_foreach(table_cb_t *f, void *arg, int sort);
+
+#ifndef s6_addr32
+#define s6_addr32 __u6_addr.__u6_addr32
+#endif
+
+static struct _s_x tabletypes[] = {
+ { "addr", IPFW_TABLE_ADDR },
+ { "iface", IPFW_TABLE_INTERFACE },
+ { "number", IPFW_TABLE_NUMBER },
+ { "flow", IPFW_TABLE_FLOW },
+ { NULL, 0 }
+};
+
+static struct _s_x tablevaltypes[] = {
+ { "skipto", IPFW_VTYPE_SKIPTO },
+ { "pipe", IPFW_VTYPE_PIPE },
+ { "fib", IPFW_VTYPE_FIB },
+ { "nat", IPFW_VTYPE_NAT },
+ { "dscp", IPFW_VTYPE_DSCP },
+ { "tag", IPFW_VTYPE_TAG },
+ { "divert", IPFW_VTYPE_DIVERT },
+ { "netgraph", IPFW_VTYPE_NETGRAPH },
+ { "limit", IPFW_VTYPE_LIMIT },
+ { "ipv4", IPFW_VTYPE_NH4 },
+ { "ipv6", IPFW_VTYPE_NH6 },
+ { NULL, 0 }
+};
+
+static struct _s_x tablecmds[] = {
+ { "add", TOK_ADD },
+ { "delete", TOK_DEL },
+ { "create", TOK_CREATE },
+ { "destroy", TOK_DESTROY },
+ { "flush", TOK_FLUSH },
+ { "modify", TOK_MODIFY },
+ { "swap", TOK_SWAP },
+ { "info", TOK_INFO },
+ { "detail", TOK_DETAIL },
+ { "list", TOK_LIST },
+ { "lookup", TOK_LOOKUP },
+ { "atomic", TOK_ATOMIC },
+ { "lock", TOK_LOCK },
+ { "unlock", TOK_UNLOCK },
+ { NULL, 0 }
+};
+
+static int
+lookup_host (char *host, struct in_addr *ipaddr)
+{
+ struct hostent *he;
+
+ if (!inet_aton(host, ipaddr)) {
+ if ((he = gethostbyname(host)) == NULL)
+ return(-1);
+ *ipaddr = *(struct in_addr *)he->h_addr_list[0];
+ }
+ return(0);
+}
+
+static int
+get_token(struct _s_x *table, char *string, char *errbase)
+{
+ int tcmd;
+
+ if ((tcmd = match_token_relaxed(table, string)) < 0)
+ errx(EX_USAGE, "%s %s %s",
+ (tcmd == 0) ? "invalid" : "ambiguous", errbase, string);
+
+ return (tcmd);
+}
+
+/*
+ * This one handles all table-related commands
+ * ipfw table NAME create ...
+ * ipfw table NAME modify ...
+ * ipfw table NAME destroy
+ * ipfw table NAME swap NAME
+ * ipfw table NAME lock
+ * ipfw table NAME unlock
+ * ipfw table NAME add addr[/masklen] [value]
+ * ipfw table NAME add [addr[/masklen] value] [addr[/masklen] value] ..
+ * ipfw table NAME delete addr[/masklen] [addr[/masklen]] ..
+ * ipfw table NAME lookup addr
+ * ipfw table {NAME | all} flush
+ * ipfw table {NAME | all} list
+ * ipfw table {NAME | all} info
+ * ipfw table {NAME | all} detail
+ */
+void
+ipfw_table_handler(int ac, char *av[])
+{
+ int do_add, is_all;
+ int atomic, error, tcmd;
+ ipfw_xtable_info i;
+ ipfw_obj_header oh;
+ char *tablename;
+ uint32_t set;
+ void *arg;
+
+ memset(&oh, 0, sizeof(oh));
+ is_all = 0;
+ if (co.use_set != 0)
+ set = co.use_set - 1;
+ else
+ set = 0;
+
+ ac--; av++;
+ NEED1("table needs name");
+ tablename = *av;
+
+ if (table_check_name(tablename) == 0) {
+ table_fill_ntlv(&oh.ntlv, *av, set, 1);
+ oh.idx = 1;
+ } else {
+ if (strcmp(tablename, "all") == 0)
+ is_all = 1;
+ else
+ errx(EX_USAGE, "table name %s is invalid", tablename);
+ }
+ ac--; av++;
+ NEED1("table needs command");
+
+ tcmd = get_token(tablecmds, *av, "table command");
+ /* Check if atomic operation was requested */
+ atomic = 0;
+ if (tcmd == TOK_ATOMIC) {
+ ac--; av++;
+ NEED1("atomic needs command");
+ tcmd = get_token(tablecmds, *av, "table command");
+ switch (tcmd) {
+ case TOK_ADD:
+ break;
+ default:
+ errx(EX_USAGE, "atomic is not compatible with %s", *av);
+ }
+ atomic = 1;
+ }
+
+ switch (tcmd) {
+ case TOK_LIST:
+ case TOK_INFO:
+ case TOK_DETAIL:
+ case TOK_FLUSH:
+ break;
+ default:
+ if (is_all != 0)
+ errx(EX_USAGE, "table name required");
+ }
+
+ switch (tcmd) {
+ case TOK_ADD:
+ case TOK_DEL:
+ do_add = **av == 'a';
+ ac--; av++;
+ table_modify_record(&oh, ac, av, do_add, co.do_quiet,
+ co.do_quiet, atomic);
+ break;
+ case TOK_CREATE:
+ ac--; av++;
+ table_create(&oh, ac, av);
+ break;
+ case TOK_MODIFY:
+ ac--; av++;
+ table_modify(&oh, ac, av);
+ break;
+ case TOK_DESTROY:
+ if (table_destroy(&oh) != 0)
+ err(EX_OSERR, "failed to destroy table %s", tablename);
+ break;
+ case TOK_FLUSH:
+ if (is_all == 0) {
+ if ((error = table_flush(&oh)) != 0)
+ err(EX_OSERR, "failed to flush table %s info",
+ tablename);
+ } else {
+ error = tables_foreach(table_flush_one, &oh, 1);
+ if (error != 0)
+ err(EX_OSERR, "failed to flush tables list");
+ }
+ break;
+ case TOK_SWAP:
+ ac--; av++;
+ NEED1("second table name required");
+ table_swap(&oh, *av);
+ break;
+ case TOK_LOCK:
+ case TOK_UNLOCK:
+ table_lock(&oh, (tcmd == TOK_LOCK));
+ break;
+ case TOK_DETAIL:
+ case TOK_INFO:
+ arg = (tcmd == TOK_DETAIL) ? (void *)1 : NULL;
+ if (is_all == 0) {
+ if ((error = table_get_info(&oh, &i)) != 0)
+ err(EX_OSERR, "failed to request table info");
+ table_show_info(&i, arg);
+ } else {
+ error = tables_foreach(table_show_info, arg, 1);
+ if (error != 0)
+ err(EX_OSERR, "failed to request tables list");
+ }
+ break;
+ case TOK_LIST:
+ if (is_all == 0) {
+ ipfw_xtable_info i;
+ if ((error = table_get_info(&oh, &i)) != 0)
+ err(EX_OSERR, "failed to request table info");
+ table_show_one(&i, NULL);
+ } else {
+ error = tables_foreach(table_show_one, NULL, 1);
+ if (error != 0)
+ err(EX_OSERR, "failed to request tables list");
+ }
+ break;
+ case TOK_LOOKUP:
+ ac--; av++;
+ table_lookup(&oh, ac, av);
+ break;
+ }
+}
+
+static void
+table_fill_ntlv(ipfw_obj_ntlv *ntlv, char *name, uint32_t set, uint16_t uidx)
+{
+
+ ntlv->head.type = IPFW_TLV_TBL_NAME;
+ ntlv->head.length = sizeof(ipfw_obj_ntlv);
+ ntlv->idx = uidx;
+ ntlv->set = set;
+ strlcpy(ntlv->name, name, sizeof(ntlv->name));
+}
+
+static void
+table_fill_objheader(ipfw_obj_header *oh, ipfw_xtable_info *i)
+{
+
+ oh->idx = 1;
+ table_fill_ntlv(&oh->ntlv, i->tablename, i->set, 1);
+}
+
+static struct _s_x tablenewcmds[] = {
+ { "type", TOK_TYPE },
+ { "valtype", TOK_VALTYPE },
+ { "algo", TOK_ALGO },
+ { "limit", TOK_LIMIT },
+ { "locked", TOK_LOCK },
+ { NULL, 0 }
+};
+
+static struct _s_x flowtypecmds[] = {
+ { "src-ip", IPFW_TFFLAG_SRCIP },
+ { "proto", IPFW_TFFLAG_PROTO },
+ { "src-port", IPFW_TFFLAG_SRCPORT },
+ { "dst-ip", IPFW_TFFLAG_DSTIP },
+ { "dst-port", IPFW_TFFLAG_DSTPORT },
+ { NULL, 0 }
+};
+
+int
+table_parse_type(uint8_t ttype, char *p, uint8_t *tflags)
+{
+ uint32_t fset, fclear;
+ char *e;
+
+ /* Parse type options */
+ switch(ttype) {
+ case IPFW_TABLE_FLOW:
+ fset = fclear = 0;
+ if (fill_flags(flowtypecmds, p, &e, &fset, &fclear) != 0)
+ errx(EX_USAGE,
+ "unable to parse flow option %s", e);
+ *tflags = fset;
+ break;
+ default:
+ return (EX_USAGE);
+ }
+
+ return (0);
+}
+
+void
+table_print_type(char *tbuf, size_t size, uint8_t type, uint8_t tflags)
+{
+ const char *tname;
+ int l;
+
+ if ((tname = match_value(tabletypes, type)) == NULL)
+ tname = "unknown";
+
+ l = snprintf(tbuf, size, "%s", tname);
+ tbuf += l;
+ size -= l;
+
+ switch(type) {
+ case IPFW_TABLE_FLOW:
+ if (tflags != 0) {
+ *tbuf++ = ':';
+ l--;
+ print_flags_buffer(tbuf, size, flowtypecmds, tflags);
+ }
+ break;
+ }
+}
+
+/*
+ * Creates new table
+ *
+ * ipfw table NAME create [ type { addr | iface | number | flow } ]
+ * [ algo algoname ]
+ */
+static void
+table_create(ipfw_obj_header *oh, int ac, char *av[])
+{
+ ipfw_xtable_info xi;
+ int error, tcmd, val;
+ uint32_t fset, fclear;
+ size_t sz;
+ char *e, *p;
+ char tbuf[128];
+
+ sz = sizeof(tbuf);
+ memset(&xi, 0, sizeof(xi));
+
+ while (ac > 0) {
+ tcmd = get_token(tablenewcmds, *av, "option");
+ ac--; av++;
+
+ switch (tcmd) {
+ case TOK_LIMIT:
+ NEED1("limit value required");
+ xi.limit = strtol(*av, NULL, 10);
+ ac--; av++;
+ break;
+ case TOK_TYPE:
+ NEED1("table type required");
+ /* Type may have suboptions after ':' */
+ if ((p = strchr(*av, ':')) != NULL)
+ *p++ = '\0';
+ val = match_token(tabletypes, *av);
+ if (val == -1) {
+ concat_tokens(tbuf, sizeof(tbuf), tabletypes,
+ ", ");
+ errx(EX_USAGE,
+ "Unknown tabletype: %s. Supported: %s",
+ *av, tbuf);
+ }
+ xi.type = val;
+ if (p != NULL) {
+ error = table_parse_type(val, p, &xi.tflags);
+ if (error != 0)
+ errx(EX_USAGE,
+ "Unsupported suboptions: %s", p);
+ }
+ ac--; av++;
+ break;
+ case TOK_VALTYPE:
+ NEED1("table value type required");
+ fset = fclear = 0;
+ val = fill_flags(tablevaltypes, *av, &e, &fset, &fclear);
+ if (val != -1) {
+ xi.vmask = fset;
+ ac--; av++;
+ break;
+ }
+ concat_tokens(tbuf, sizeof(tbuf), tablevaltypes, ", ");
+ errx(EX_USAGE, "Unknown value type: %s. Supported: %s",
+ e, tbuf);
+ break;
+ case TOK_ALGO:
+ NEED1("table algorithm name required");
+ if (strlen(*av) > sizeof(xi.algoname))
+ errx(EX_USAGE, "algorithm name too long");
+ strlcpy(xi.algoname, *av, sizeof(xi.algoname));
+ ac--; av++;
+ break;
+ case TOK_LOCK:
+ xi.flags |= IPFW_TGFLAGS_LOCKED;
+ break;
+ }
+ }
+
+ /* Set some defaults to preserve compability */
+ if (xi.algoname[0] == '\0' && xi.type == 0)
+ xi.type = IPFW_TABLE_ADDR;
+ if (xi.vmask == 0)
+ xi.vmask = IPFW_VTYPE_LEGACY;
+
+ if ((error = table_do_create(oh, &xi)) != 0)
+ err(EX_OSERR, "Table creation failed");
+}
+
+/*
+ * Creates new table
+ *
+ * Request: [ ipfw_obj_header ipfw_xtable_info ]
+ *
+ * Returns 0 on success.
+ */
+static int
+table_do_create(ipfw_obj_header *oh, ipfw_xtable_info *i)
+{
+ char tbuf[sizeof(ipfw_obj_header) + sizeof(ipfw_xtable_info)];
+ int error;
+
+ memcpy(tbuf, oh, sizeof(*oh));
+ memcpy(tbuf + sizeof(*oh), i, sizeof(*i));
+ oh = (ipfw_obj_header *)tbuf;
+
+ error = do_set3(IP_FW_TABLE_XCREATE, &oh->opheader, sizeof(tbuf));
+
+ return (error);
+}
+
+/*
+ * Modifies existing table
+ *
+ * ipfw table NAME modify [ limit number ]
+ */
+static void
+table_modify(ipfw_obj_header *oh, int ac, char *av[])
+{
+ ipfw_xtable_info xi;
+ int tcmd;
+ size_t sz;
+ char tbuf[128];
+
+ sz = sizeof(tbuf);
+ memset(&xi, 0, sizeof(xi));
+
+ while (ac > 0) {
+ tcmd = get_token(tablenewcmds, *av, "option");
+ ac--; av++;
+
+ switch (tcmd) {
+ case TOK_LIMIT:
+ NEED1("limit value required");
+ xi.limit = strtol(*av, NULL, 10);
+ xi.mflags |= IPFW_TMFLAGS_LIMIT;
+ ac--; av++;
+ break;
+ default:
+ errx(EX_USAGE, "cmd is not supported for modificatiob");
+ }
+ }
+
+ if (table_do_modify(oh, &xi) != 0)
+ err(EX_OSERR, "Table modification failed");
+}
+
+/*
+ * Modifies existing table.
+ *
+ * Request: [ ipfw_obj_header ipfw_xtable_info ]
+ *
+ * Returns 0 on success.
+ */
+static int
+table_do_modify(ipfw_obj_header *oh, ipfw_xtable_info *i)
+{
+ char tbuf[sizeof(ipfw_obj_header) + sizeof(ipfw_xtable_info)];
+ int error;
+
+ memcpy(tbuf, oh, sizeof(*oh));
+ memcpy(tbuf + sizeof(*oh), i, sizeof(*i));
+ oh = (ipfw_obj_header *)tbuf;
+
+ error = do_set3(IP_FW_TABLE_XMODIFY, &oh->opheader, sizeof(tbuf));
+
+ return (error);
+}
+
+/*
+ * Locks or unlocks given table
+ */
+static void
+table_lock(ipfw_obj_header *oh, int lock)
+{
+ ipfw_xtable_info xi;
+
+ memset(&xi, 0, sizeof(xi));
+
+ xi.mflags |= IPFW_TMFLAGS_LOCK;
+ xi.flags |= (lock != 0) ? IPFW_TGFLAGS_LOCKED : 0;
+
+ if (table_do_modify(oh, &xi) != 0)
+ err(EX_OSERR, "Table %s failed", lock != 0 ? "lock" : "unlock");
+}
+
+/*
+ * Destroys given table specified by @oh->ntlv.
+ * Returns 0 on success.
+ */
+static int
+table_destroy(ipfw_obj_header *oh)
+{
+
+ if (do_set3(IP_FW_TABLE_XDESTROY, &oh->opheader, sizeof(*oh)) != 0)
+ return (-1);
+
+ return (0);
+}
+
+/*
+ * Flushes given table specified by @oh->ntlv.
+ * Returns 0 on success.
+ */
+static int
+table_flush(ipfw_obj_header *oh)
+{
+
+ if (do_set3(IP_FW_TABLE_XFLUSH, &oh->opheader, sizeof(*oh)) != 0)
+ return (-1);
+
+ return (0);
+}
+
+static int
+table_do_swap(ipfw_obj_header *oh, char *second)
+{
+ char tbuf[sizeof(ipfw_obj_header) + sizeof(ipfw_obj_ntlv)];
+ int error;
+
+ memset(tbuf, 0, sizeof(tbuf));
+ memcpy(tbuf, oh, sizeof(*oh));
+ oh = (ipfw_obj_header *)tbuf;
+ table_fill_ntlv((ipfw_obj_ntlv *)(oh + 1), second, oh->ntlv.set, 1);
+
+ error = do_set3(IP_FW_TABLE_XSWAP, &oh->opheader, sizeof(tbuf));
+
+ return (error);
+}
+
+/*
+ * Swaps given table with @second one.
+ */
+static int
+table_swap(ipfw_obj_header *oh, char *second)
+{
+ int error;
+
+ if (table_check_name(second) != 0)
+ errx(EX_USAGE, "table name %s is invalid", second);
+
+ error = table_do_swap(oh, second);
+
+ switch (error) {
+ case EINVAL:
+ errx(EX_USAGE, "Unable to swap table: check types");
+ case EFBIG:
+ errx(EX_USAGE, "Unable to swap table: check limits");
+ }
+
+ return (0);
+}
+
+
+/*
+ * Retrieves table in given table specified by @oh->ntlv.
+ * it inside @i.
+ * Returns 0 on success.
+ */
+static int
+table_get_info(ipfw_obj_header *oh, ipfw_xtable_info *i)
+{
+ char tbuf[sizeof(ipfw_obj_header) + sizeof(ipfw_xtable_info)];
+ size_t sz;
+
+ sz = sizeof(tbuf);
+ memset(tbuf, 0, sizeof(tbuf));
+ memcpy(tbuf, oh, sizeof(*oh));
+ oh = (ipfw_obj_header *)tbuf;
+
+ if (do_get3(IP_FW_TABLE_XINFO, &oh->opheader, &sz) != 0)
+ return (errno);
+
+ if (sz < sizeof(tbuf))
+ return (EINVAL);
+
+ *i = *(ipfw_xtable_info *)(oh + 1);
+
+ return (0);
+}
+
+static struct _s_x tablealgoclass[] = {
+ { "hash", IPFW_TACLASS_HASH },
+ { "array", IPFW_TACLASS_ARRAY },
+ { "radix", IPFW_TACLASS_RADIX },
+ { NULL, 0 }
+};
+
+struct ta_cldata {
+ uint8_t taclass;
+ uint8_t spare4;
+ uint16_t itemsize;
+ uint16_t itemsize6;
+ uint32_t size;
+ uint32_t count;
+};
+
+/*
+ * Print global/per-AF table @i algorithm info.
+ */
+static void
+table_show_tainfo(ipfw_xtable_info *i, struct ta_cldata *d,
+ const char *af, const char *taclass)
+{
+
+ switch (d->taclass) {
+ case IPFW_TACLASS_HASH:
+ case IPFW_TACLASS_ARRAY:
+ printf(" %salgorithm %s info\n", af, taclass);
+ if (d->itemsize == d->itemsize6)
+ printf(" size: %u items: %u itemsize: %u\n",
+ d->size, d->count, d->itemsize);
+ else
+ printf(" size: %u items: %u "
+ "itemsize4: %u itemsize6: %u\n",
+ d->size, d->count,
+ d->itemsize, d->itemsize6);
+ break;
+ case IPFW_TACLASS_RADIX:
+ printf(" %salgorithm %s info\n", af, taclass);
+ if (d->itemsize == d->itemsize6)
+ printf(" items: %u itemsize: %u\n",
+ d->count, d->itemsize);
+ else
+ printf(" items: %u "
+ "itemsize4: %u itemsize6: %u\n",
+ d->count, d->itemsize, d->itemsize6);
+ break;
+ default:
+ printf(" algo class: %s\n", taclass);
+ }
+}
+
+static void
+table_print_valheader(char *buf, size_t bufsize, uint32_t vmask)
+{
+
+ if (vmask == IPFW_VTYPE_LEGACY) {
+ snprintf(buf, bufsize, "legacy");
+ return;
+ }
+
+ print_flags_buffer(buf, bufsize, tablevaltypes, vmask);
+}
+
+/*
+ * Prints table info struct @i in human-readable form.
+ */
+static int
+table_show_info(ipfw_xtable_info *i, void *arg)
+{
+ const char *vtype;
+ ipfw_ta_tinfo *tainfo;
+ int afdata, afitem;
+ struct ta_cldata d;
+ char ttype[64], tvtype[64];
+
+ table_print_type(ttype, sizeof(ttype), i->type, i->tflags);
+ table_print_valheader(tvtype, sizeof(tvtype), i->vmask);
+
+ printf("--- table(%s), set(%u) ---\n", i->tablename, i->set);
+ if ((i->flags & IPFW_TGFLAGS_LOCKED) != 0)
+ printf(" kindex: %d, type: %s, locked\n", i->kidx, ttype);
+ else
+ printf(" kindex: %d, type: %s\n", i->kidx, ttype);
+ printf(" references: %u, valtype: %s\n", i->refcnt, tvtype);
+ printf(" algorithm: %s\n", i->algoname);
+ printf(" items: %u, size: %u\n", i->count, i->size);
+ if (i->limit > 0)
+ printf(" limit: %u\n", i->limit);
+
+ /* Print algo-specific info if requested & set */
+ if (arg == NULL)
+ return (0);
+
+ if ((i->ta_info.flags & IPFW_TATFLAGS_DATA) == 0)
+ return (0);
+ tainfo = &i->ta_info;
+
+ afdata = 0;
+ afitem = 0;
+ if (tainfo->flags & IPFW_TATFLAGS_AFDATA)
+ afdata = 1;
+ if (tainfo->flags & IPFW_TATFLAGS_AFITEM)
+ afitem = 1;
+
+ memset(&d, 0, sizeof(d));
+ d.taclass = tainfo->taclass4;
+ d.size = tainfo->size4;
+ d.count = tainfo->count4;
+ d.itemsize = tainfo->itemsize4;
+ if (afdata == 0 && afitem != 0)
+ d.itemsize6 = tainfo->itemsize6;
+ else
+ d.itemsize6 = d.itemsize;
+ if ((vtype = match_value(tablealgoclass, d.taclass)) == NULL)
+ vtype = "unknown";
+
+ if (afdata == 0) {
+ table_show_tainfo(i, &d, "", vtype);
+ } else {
+ table_show_tainfo(i, &d, "IPv4 ", vtype);
+ memset(&d, 0, sizeof(d));
+ d.taclass = tainfo->taclass6;
+ if ((vtype = match_value(tablealgoclass, d.taclass)) == NULL)
+ vtype = "unknown";
+ d.size = tainfo->size6;
+ d.count = tainfo->count6;
+ d.itemsize = tainfo->itemsize6;
+ d.itemsize6 = d.itemsize;
+ table_show_tainfo(i, &d, "IPv6 ", vtype);
+ }
+
+ return (0);
+}
+
+
+/*
+ * Function wrappers which can be used either
+ * as is or as foreach function parameter.
+ */
+
+static int
+table_show_one(ipfw_xtable_info *i, void *arg)
+{
+ ipfw_obj_header *oh;
+ int error;
+
+ if ((error = table_do_get_list(i, &oh)) != 0) {
+ err(EX_OSERR, "Error requesting table %s list", i->tablename);
+ return (error);
+ }
+
+ table_show_list(oh, 1);
+
+ free(oh);
+ return (0);
+}
+
+static int
+table_flush_one(ipfw_xtable_info *i, void *arg)
+{
+ ipfw_obj_header *oh;
+
+ oh = (ipfw_obj_header *)arg;
+
+ table_fill_ntlv(&oh->ntlv, i->tablename, i->set, 1);
+
+ return (table_flush(oh));
+}
+
+static int
+table_do_modify_record(int cmd, ipfw_obj_header *oh,
+ ipfw_obj_tentry *tent, int count, int atomic)
+{
+ ipfw_obj_ctlv *ctlv;
+ ipfw_obj_tentry *tent_base;
+ caddr_t pbuf;
+ char xbuf[sizeof(*oh) + sizeof(ipfw_obj_ctlv) + sizeof(*tent)];
+ int error, i;
+ size_t sz;
+
+ sz = sizeof(*ctlv) + sizeof(*tent) * count;
+ if (count == 1) {
+ memset(xbuf, 0, sizeof(xbuf));
+ pbuf = xbuf;
+ } else {
+ if ((pbuf = calloc(1, sizeof(*oh) + sz)) == NULL)
+ return (ENOMEM);
+ }
+
+ memcpy(pbuf, oh, sizeof(*oh));
+ oh = (ipfw_obj_header *)pbuf;
+ oh->opheader.version = 1;
+
+ ctlv = (ipfw_obj_ctlv *)(oh + 1);
+ ctlv->count = count;
+ ctlv->head.length = sz;
+ if (atomic != 0)
+ ctlv->flags |= IPFW_CTF_ATOMIC;
+
+ tent_base = tent;
+ memcpy(ctlv + 1, tent, sizeof(*tent) * count);
+ tent = (ipfw_obj_tentry *)(ctlv + 1);
+ for (i = 0; i < count; i++, tent++) {
+ tent->head.length = sizeof(ipfw_obj_tentry);
+ tent->idx = oh->idx;
+ }
+
+ sz += sizeof(*oh);
+ error = do_get3(cmd, &oh->opheader, &sz);
+ tent = (ipfw_obj_tentry *)(ctlv + 1);
+ /* Copy result back to provided buffer */
+ memcpy(tent_base, ctlv + 1, sizeof(*tent) * count);
+
+ if (pbuf != xbuf)
+ free(pbuf);
+
+ return (error);
+}
+
+static void
+table_modify_record(ipfw_obj_header *oh, int ac, char *av[], int add,
+ int quiet, int update, int atomic)
+{
+ ipfw_obj_tentry *ptent, tent, *tent_buf;
+ ipfw_xtable_info xi;
+ uint8_t type;
+ uint32_t vmask;
+ int cmd, count, error, i, ignored;
+ char *texterr, *etxt, *px;
+
+ if (ac == 0)
+ errx(EX_USAGE, "address required");
+
+ if (add != 0) {
+ cmd = IP_FW_TABLE_XADD;
+ texterr = "Adding record failed";
+ } else {
+ cmd = IP_FW_TABLE_XDEL;
+ texterr = "Deleting record failed";
+ }
+
+ /*
+ * Calculate number of entries:
+ * Assume [key val] x N for add
+ * and
+ * key x N for delete
+ */
+ count = (add != 0) ? ac / 2 + 1 : ac;
+
+ if (count <= 1) {
+ /* Adding single entry with/without value */
+ memset(&tent, 0, sizeof(tent));
+ tent_buf = &tent;
+ } else {
+
+ if ((tent_buf = calloc(count, sizeof(tent))) == NULL)
+ errx(EX_OSERR,
+ "Unable to allocate memory for all entries");
+ }
+ ptent = tent_buf;
+
+ memset(&xi, 0, sizeof(xi));
+ count = 0;
+ while (ac > 0) {
+ tentry_fill_key(oh, ptent, *av, add, &type, &vmask, &xi);
+
+ /*
+ * compability layer: auto-create table if not exists
+ */
+ if (xi.tablename[0] == '\0') {
+ xi.type = type;
+ xi.vmask = vmask;
+ strlcpy(xi.tablename, oh->ntlv.name,
+ sizeof(xi.tablename));
+ fprintf(stderr, "DEPRECATED: inserting data info "
+ "non-existent table %s. (auto-created)\n",
+ xi.tablename);
+ table_do_create(oh, &xi);
+ }
+
+ oh->ntlv.type = type;
+ ac--; av++;
+
+ if (add != 0 && ac > 0) {
+ tentry_fill_value(oh, ptent, *av, type, vmask);
+ ac--; av++;
+ }
+
+ if (update != 0)
+ ptent->head.flags |= IPFW_TF_UPDATE;
+
+ count++;
+ ptent++;
+ }
+
+ error = table_do_modify_record(cmd, oh, tent_buf, count, atomic);
+
+ quiet = 0;
+
+ /*
+ * Compatibility stuff: do not yell on duplicate keys or
+ * failed deletions.
+ */
+ if (error == 0 || (error == EEXIST && add != 0) ||
+ (error == ENOENT && add == 0)) {
+ if (quiet != 0) {
+ if (tent_buf != &tent)
+ free(tent_buf);
+ return;
+ }
+ }
+
+ /* Report results back */
+ ptent = tent_buf;
+ for (i = 0; i < count; ptent++, i++) {
+ ignored = 0;
+ switch (ptent->result) {
+ case IPFW_TR_ADDED:
+ px = "added";
+ break;
+ case IPFW_TR_DELETED:
+ px = "deleted";
+ break;
+ case IPFW_TR_UPDATED:
+ px = "updated";
+ break;
+ case IPFW_TR_LIMIT:
+ px = "limit";
+ ignored = 1;
+ break;
+ case IPFW_TR_ERROR:
+ px = "error";
+ ignored = 1;
+ break;
+ case IPFW_TR_NOTFOUND:
+ px = "notfound";
+ ignored = 1;
+ break;
+ case IPFW_TR_EXISTS:
+ px = "exists";
+ ignored = 1;
+ break;
+ case IPFW_TR_IGNORED:
+ px = "ignored";
+ ignored = 1;
+ break;
+ default:
+ px = "unknown";
+ ignored = 1;
+ }
+
+ if (error != 0 && atomic != 0 && ignored == 0)
+ printf("%s(reverted): ", px);
+ else
+ printf("%s: ", px);
+
+ table_show_entry(&xi, ptent);
+ }
+
+ if (tent_buf != &tent)
+ free(tent_buf);
+
+ if (error == 0)
+ return;
+ /* Get real OS error */
+ error = errno;
+
+ /* Try to provide more human-readable error */
+ switch (error) {
+ case EEXIST:
+ etxt = "record already exists";
+ break;
+ case EFBIG:
+ etxt = "limit hit";
+ break;
+ case ESRCH:
+ etxt = "table not found";
+ break;
+ case ENOENT:
+ etxt = "record not found";
+ break;
+ case EACCES:
+ etxt = "table is locked";
+ break;
+ default:
+ etxt = strerror(error);
+ }
+
+ errx(EX_OSERR, "%s: %s", texterr, etxt);
+}
+
+static int
+table_do_lookup(ipfw_obj_header *oh, char *key, ipfw_xtable_info *xi,
+ ipfw_obj_tentry *xtent)
+{
+ char xbuf[sizeof(ipfw_obj_header) + sizeof(ipfw_obj_tentry)];
+ ipfw_obj_tentry *tent;
+ uint8_t type;
+ uint32_t vmask;
+ size_t sz;
+
+ memcpy(xbuf, oh, sizeof(*oh));
+ oh = (ipfw_obj_header *)xbuf;
+ tent = (ipfw_obj_tentry *)(oh + 1);
+
+ memset(tent, 0, sizeof(*tent));
+ tent->head.length = sizeof(*tent);
+ tent->idx = 1;
+
+ tentry_fill_key(oh, tent, key, 0, &type, &vmask, xi);
+ oh->ntlv.type = type;
+
+ sz = sizeof(xbuf);
+ if (do_get3(IP_FW_TABLE_XFIND, &oh->opheader, &sz) != 0)
+ return (errno);
+
+ if (sz < sizeof(xbuf))
+ return (EINVAL);
+
+ *xtent = *tent;
+
+ return (0);
+}
+
+static void
+table_lookup(ipfw_obj_header *oh, int ac, char *av[])
+{
+ ipfw_obj_tentry xtent;
+ ipfw_xtable_info xi;
+ char key[64];
+ int error;
+
+ if (ac == 0)
+ errx(EX_USAGE, "address required");
+
+ strlcpy(key, *av, sizeof(key));
+
+ memset(&xi, 0, sizeof(xi));
+ error = table_do_lookup(oh, key, &xi, &xtent);
+
+ switch (error) {
+ case 0:
+ break;
+ case ESRCH:
+ errx(EX_UNAVAILABLE, "Table %s not found", oh->ntlv.name);
+ case ENOENT:
+ errx(EX_UNAVAILABLE, "Entry %s not found", *av);
+ case ENOTSUP:
+ errx(EX_UNAVAILABLE, "Table %s algo does not support "
+ "\"lookup\" method", oh->ntlv.name);
+ default:
+ err(EX_OSERR, "getsockopt(IP_FW_TABLE_XFIND)");
+ }
+
+ table_show_entry(&xi, &xtent);
+}
+
+static void
+tentry_fill_key_type(char *arg, ipfw_obj_tentry *tentry, uint8_t type,
+ uint8_t tflags)
+{
+ char *p, *pp;
+ int mask, af;
+ struct in6_addr *paddr, tmp;
+ struct tflow_entry *tfe;
+ uint32_t key, *pkey;
+ uint16_t port;
+ struct protoent *pent;
+ struct servent *sent;
+ int masklen;
+
+ masklen = 0;
+ af = 0;
+ paddr = (struct in6_addr *)&tentry->k;
+
+ switch (type) {
+ case IPFW_TABLE_ADDR:
+ /* Remove / if exists */
+ if ((p = strchr(arg, '/')) != NULL) {
+ *p = '\0';
+ mask = atoi(p + 1);
+ }
+
+ if (inet_pton(AF_INET, arg, paddr) == 1) {
+ if (p != NULL && mask > 32)
+ errx(EX_DATAERR, "bad IPv4 mask width: %s",
+ p + 1);
+
+ masklen = p ? mask : 32;
+ af = AF_INET;
+ } else if (inet_pton(AF_INET6, arg, paddr) == 1) {
+ if (IN6_IS_ADDR_V4COMPAT(paddr))
+ errx(EX_DATAERR,
+ "Use IPv4 instead of v4-compatible");
+ if (p != NULL && mask > 128)
+ errx(EX_DATAERR, "bad IPv6 mask width: %s",
+ p + 1);
+
+ masklen = p ? mask : 128;
+ af = AF_INET6;
+ } else {
+ /* Assume FQDN */
+ if (lookup_host(arg, (struct in_addr *)paddr) != 0)
+ errx(EX_NOHOST, "hostname ``%s'' unknown", arg);
+
+ masklen = 32;
+ type = IPFW_TABLE_ADDR;
+ af = AF_INET;
+ }
+ break;
+ case IPFW_TABLE_INTERFACE:
+ /* Assume interface name. Copy significant data only */
+ mask = MIN(strlen(arg), IF_NAMESIZE - 1);
+ memcpy(paddr, arg, mask);
+ /* Set mask to exact match */
+ masklen = 8 * IF_NAMESIZE;
+ break;
+ case IPFW_TABLE_NUMBER:
+ /* Port or any other key */
+ key = strtol(arg, &p, 10);
+ if (*p != '\0')
+ errx(EX_DATAERR, "Invalid number: %s", arg);
+
+ pkey = (uint32_t *)paddr;
+ *pkey = key;
+ masklen = 32;
+ break;
+ case IPFW_TABLE_FLOW:
+ /* Assume [src-ip][,proto][,src-port][,dst-ip][,dst-port] */
+ tfe = &tentry->k.flow;
+ af = 0;
+
+ /* Handle <ipv4|ipv6> */
+ if ((tflags & IPFW_TFFLAG_SRCIP) != 0) {
+ if ((p = strchr(arg, ',')) != NULL)
+ *p++ = '\0';
+ /* Determine family using temporary storage */
+ if (inet_pton(AF_INET, arg, &tmp) == 1) {
+ if (af != 0 && af != AF_INET)
+ errx(EX_DATAERR,
+ "Inconsistent address family\n");
+ af = AF_INET;
+ memcpy(&tfe->a.a4.sip, &tmp, 4);
+ } else if (inet_pton(AF_INET6, arg, &tmp) == 1) {
+ if (af != 0 && af != AF_INET6)
+ errx(EX_DATAERR,
+ "Inconsistent address family\n");
+ af = AF_INET6;
+ memcpy(&tfe->a.a6.sip6, &tmp, 16);
+ }
+
+ arg = p;
+ }
+
+ /* Handle <proto-num|proto-name> */
+ if ((tflags & IPFW_TFFLAG_PROTO) != 0) {
+ if (arg == NULL)
+ errx(EX_DATAERR, "invalid key: proto missing");
+ if ((p = strchr(arg, ',')) != NULL)
+ *p++ = '\0';
+
+ key = strtol(arg, &pp, 10);
+ if (*pp != '\0') {
+ if ((pent = getprotobyname(arg)) == NULL)
+ errx(EX_DATAERR, "Unknown proto: %s",
+ arg);
+ else
+ key = pent->p_proto;
+ }
+
+ if (key > 255)
+ errx(EX_DATAERR, "Bad protocol number: %u",key);
+
+ tfe->proto = key;
+
+ arg = p;
+ }
+
+ /* Handle <port-num|service-name> */
+ if ((tflags & IPFW_TFFLAG_SRCPORT) != 0) {
+ if (arg == NULL)
+ errx(EX_DATAERR, "invalid key: src port missing");
+ if ((p = strchr(arg, ',')) != NULL)
+ *p++ = '\0';
+
+ if ((port = htons(strtol(arg, NULL, 10))) == 0) {
+ if ((sent = getservbyname(arg, NULL)) == NULL)
+ errx(EX_DATAERR, "Unknown service: %s",
+ arg);
+ else
+ key = sent->s_port;
+ }
+
+ tfe->sport = port;
+
+ arg = p;
+ }
+
+ /* Handle <ipv4|ipv6>*/
+ if ((tflags & IPFW_TFFLAG_DSTIP) != 0) {
+ if (arg == NULL)
+ errx(EX_DATAERR, "invalid key: dst ip missing");
+ if ((p = strchr(arg, ',')) != NULL)
+ *p++ = '\0';
+ /* Determine family using temporary storage */
+ if (inet_pton(AF_INET, arg, &tmp) == 1) {
+ if (af != 0 && af != AF_INET)
+ errx(EX_DATAERR,
+ "Inconsistent address family");
+ af = AF_INET;
+ memcpy(&tfe->a.a4.dip, &tmp, 4);
+ } else if (inet_pton(AF_INET6, arg, &tmp) == 1) {
+ if (af != 0 && af != AF_INET6)
+ errx(EX_DATAERR,
+ "Inconsistent address family");
+ af = AF_INET6;
+ memcpy(&tfe->a.a6.dip6, &tmp, 16);
+ }
+
+ arg = p;
+ }
+
+ /* Handle <port-num|service-name> */
+ if ((tflags & IPFW_TFFLAG_DSTPORT) != 0) {
+ if (arg == NULL)
+ errx(EX_DATAERR, "invalid key: dst port missing");
+ if ((p = strchr(arg, ',')) != NULL)
+ *p++ = '\0';
+
+ if ((port = htons(strtol(arg, NULL, 10))) == 0) {
+ if ((sent = getservbyname(arg, NULL)) == NULL)
+ errx(EX_DATAERR, "Unknown service: %s",
+ arg);
+ else
+ key = sent->s_port;
+ }
+
+ tfe->dport = port;
+
+ arg = p;
+ }
+
+ tfe->af = af;
+
+ break;
+
+ default:
+ errx(EX_DATAERR, "Unsupported table type: %d", type);
+ }
+
+ tentry->subtype = af;
+ tentry->masklen = masklen;
+}
+
+static void
+tentry_fill_key(ipfw_obj_header *oh, ipfw_obj_tentry *tent, char *key,
+ int add, uint8_t *ptype, uint32_t *pvmask, ipfw_xtable_info *xi)
+{
+ uint8_t type, tflags;
+ uint32_t vmask;
+ int error;
+ char *del;
+
+ type = 0;
+ tflags = 0;
+ vmask = 0;
+
+ if (xi->tablename[0] == '\0')
+ error = table_get_info(oh, xi);
+ else
+ error = 0;
+
+ if (error == 0) {
+ /* Table found. */
+ type = xi->type;
+ tflags = xi->tflags;
+ vmask = xi->vmask;
+ } else {
+ if (error != ESRCH)
+ errx(EX_OSERR, "Error requesting table %s info",
+ oh->ntlv.name);
+ if (add == 0)
+ errx(EX_DATAERR, "Table %s does not exist",
+ oh->ntlv.name);
+ /*
+ * Table does not exist.
+ * Compability layer: try to interpret data as ADDR
+ * before failing.
+ */
+ if ((del = strchr(key, '/')) != NULL)
+ *del = '\0';
+ if (inet_pton(AF_INET, key, &tent->k.addr6) == 1 ||
+ inet_pton(AF_INET6, key, &tent->k.addr6) == 1) {
+ /* OK Prepare and send */
+ type = IPFW_TABLE_ADDR;
+ vmask = IPFW_VTYPE_LEGACY;
+ } else {
+ /* Inknown key */
+ errx(EX_USAGE, "Table %s does not exist, cannot guess "
+ "key '%s' type", oh->ntlv.name, key);
+ }
+ if (del != NULL)
+ *del = '/';
+ }
+
+ tentry_fill_key_type(key, tent, type, tflags);
+
+ *ptype = type;
+ *pvmask = vmask;
+}
+
+static void
+set_legacy_value(uint32_t val, ipfw_table_value *v)
+{
+ v->tag = val;
+ v->pipe = val;
+ v->divert = val;
+ v->skipto = val;
+ v->netgraph = val;
+ v->fib = val;
+ v->nat = val;
+ v->nh4 = val;
+ v->dscp = (uint8_t)val;
+ v->limit = val;
+}
+
+static void
+tentry_fill_value(ipfw_obj_header *oh, ipfw_obj_tentry *tent, char *arg,
+ uint8_t type, uint32_t vmask)
+{
+ struct addrinfo hints, *res;
+ uint32_t a4, flag, val, vm;
+ ipfw_table_value *v;
+ uint32_t i;
+ int dval;
+ char *comma, *e, *etype, *n, *p;
+
+ v = &tent->v.value;
+ vm = vmask;
+
+ /* Compat layer: keep old behavior for legacy value types */
+ if (vmask == IPFW_VTYPE_LEGACY) {
+ /* Try to interpret as number first */
+ val = strtoul(arg, &p, 0);
+ if (*p == '\0') {
+ set_legacy_value(val, v);
+ return;
+ }
+ if (inet_pton(AF_INET, arg, &val) == 1) {
+ set_legacy_value(ntohl(val), v);
+ return;
+ }
+ /* Try hostname */
+ if (lookup_host(arg, (struct in_addr *)&val) == 0) {
+ set_legacy_value(val, v);
+ return;
+ }
+ errx(EX_OSERR, "Unable to parse value %s", arg);
+ }
+
+ /*
+ * Shorthands: handle single value if vmask consists
+ * of numbers only. e.g.:
+ * vmask = "fib,skipto" -> treat input "1" as "1,1"
+ */
+
+ n = arg;
+ etype = NULL;
+ for (i = 1; i < (1 << 31); i *= 2) {
+ if ((flag = (vmask & i)) == 0)
+ continue;
+ vmask &= ~flag;
+
+ if ((comma = strchr(n, ',')) != NULL)
+ *comma = '\0';
+
+ switch (flag) {
+ case IPFW_VTYPE_TAG:
+ v->tag = strtol(n, &e, 10);
+ if (*e != '\0')
+ etype = "tag";
+ break;
+ case IPFW_VTYPE_PIPE:
+ v->pipe = strtol(n, &e, 10);
+ if (*e != '\0')
+ etype = "pipe";
+ break;
+ case IPFW_VTYPE_DIVERT:
+ v->divert = strtol(n, &e, 10);
+ if (*e != '\0')
+ etype = "divert";
+ break;
+ case IPFW_VTYPE_SKIPTO:
+ v->skipto = strtol(n, &e, 10);
+ if (*e != '\0')
+ etype = "skipto";
+ break;
+ case IPFW_VTYPE_NETGRAPH:
+ v->netgraph = strtol(n, &e, 10);
+ if (*e != '\0')
+ etype = "netgraph";
+ break;
+ case IPFW_VTYPE_FIB:
+ v->fib = strtol(n, &e, 10);
+ if (*e != '\0')
+ etype = "fib";
+ break;
+ case IPFW_VTYPE_NAT:
+ v->nat = strtol(n, &e, 10);
+ if (*e != '\0')
+ etype = "nat";
+ break;
+ case IPFW_VTYPE_LIMIT:
+ v->limit = strtol(n, &e, 10);
+ if (*e != '\0')
+ etype = "limit";
+ break;
+ case IPFW_VTYPE_NH4:
+ if (strchr(n, '.') != NULL &&
+ inet_pton(AF_INET, n, &a4) == 1) {
+ v->nh4 = ntohl(a4);
+ break;
+ }
+ if (lookup_host(n, (struct in_addr *)&v->nh4) == 0)
+ break;
+ etype = "ipv4";
+ break;
+ case IPFW_VTYPE_DSCP:
+ if (isalpha(*n)) {
+ if ((dval = match_token(f_ipdscp, n)) != -1) {
+ v->dscp = dval;
+ break;
+ } else
+ etype = "DSCP code";
+ } else {
+ v->dscp = strtol(n, &e, 10);
+ if (v->dscp > 63 || *e != '\0')
+ etype = "DSCP value";
+ }
+ break;
+ case IPFW_VTYPE_NH6:
+ if (strchr(n, ':') != NULL) {
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_INET6;
+ hints.ai_flags = AI_NUMERICHOST;
+ if (getaddrinfo(n, NULL, &hints, &res) == 0) {
+ v->nh6 = ((struct sockaddr_in6 *)
+ res->ai_addr)->sin6_addr;
+ v->zoneid = ((struct sockaddr_in6 *)
+ res->ai_addr)->sin6_scope_id;
+ freeaddrinfo(res);
+ break;
+ }
+ }
+ etype = "ipv6";
+ break;
+ }
+
+ if (etype != NULL)
+ errx(EX_USAGE, "Unable to parse %s as %s", n, etype);
+
+ if (comma != NULL)
+ *comma++ = ',';
+
+ if ((n = comma) != NULL)
+ continue;
+
+ /* End of input. */
+ if (vmask != 0)
+ errx(EX_USAGE, "Not enough fields inside value");
+ }
+}
+
+/*
+ * Compare table names.
+ * Honor number comparison.
+ */
+static int
+tablename_cmp(const void *a, const void *b)
+{
+ ipfw_xtable_info *ia, *ib;
+
+ ia = (ipfw_xtable_info *)a;
+ ib = (ipfw_xtable_info *)b;
+
+ return (stringnum_cmp(ia->tablename, ib->tablename));
+}
+
+/*
+ * Retrieves table list from kernel,
+ * optionally sorts it and calls requested function for each table.
+ * Returns 0 on success.
+ */
+static int
+tables_foreach(table_cb_t *f, void *arg, int sort)
+{
+ ipfw_obj_lheader *olh;
+ ipfw_xtable_info *info;
+ size_t sz;
+ int i, error;
+
+ /* Start with reasonable default */
+ sz = sizeof(*olh) + 16 * sizeof(ipfw_xtable_info);
+
+ for (;;) {
+ if ((olh = calloc(1, sz)) == NULL)
+ return (ENOMEM);
+
+ olh->size = sz;
+ if (do_get3(IP_FW_TABLES_XLIST, &olh->opheader, &sz) != 0) {
+ sz = olh->size;
+ free(olh);
+ if (errno != ENOMEM)
+ return (errno);
+ continue;
+ }
+
+ if (sort != 0)
+ qsort(olh + 1, olh->count, olh->objsize, tablename_cmp);
+
+ info = (ipfw_xtable_info *)(olh + 1);
+ for (i = 0; i < olh->count; i++) {
+ error = f(info, arg); /* Ignore errors for now */
+ info = (ipfw_xtable_info *)((caddr_t)info + olh->objsize);
+ }
+
+ free(olh);
+ break;
+ }
+
+ return (0);
+}
+
+
+/*
+ * Retrieves all entries for given table @i in
+ * eXtended format. Allocate buffer large enough
+ * to store result. Called needs to free it later.
+ *
+ * Returns 0 on success.
+ */
+static int
+table_do_get_list(ipfw_xtable_info *i, ipfw_obj_header **poh)
+{
+ ipfw_obj_header *oh;
+ size_t sz;
+ int c;
+
+ sz = 0;
+ oh = NULL;
+ for (c = 0; c < 8; c++) {
+ if (sz < i->size)
+ sz = i->size + 44;
+ if (oh != NULL)
+ free(oh);
+ if ((oh = calloc(1, sz)) == NULL)
+ continue;
+ table_fill_objheader(oh, i);
+ oh->opheader.version = 1; /* Current version */
+ if (do_get3(IP_FW_TABLE_XLIST, &oh->opheader, &sz) == 0) {
+ *poh = oh;
+ return (0);
+ }
+
+ if (errno != ENOMEM)
+ break;
+ }
+ free(oh);
+
+ return (errno);
+}
+
+/*
+ * Shows all entries from @oh in human-readable format
+ */
+static void
+table_show_list(ipfw_obj_header *oh, int need_header)
+{
+ ipfw_obj_tentry *tent;
+ uint32_t count;
+ ipfw_xtable_info *i;
+
+ i = (ipfw_xtable_info *)(oh + 1);
+ tent = (ipfw_obj_tentry *)(i + 1);
+
+ if (need_header)
+ printf("--- table(%s), set(%u) ---\n", i->tablename, i->set);
+
+ count = i->count;
+ while (count > 0) {
+ table_show_entry(i, tent);
+ tent = (ipfw_obj_tentry *)((caddr_t)tent + tent->head.length);
+ count--;
+ }
+}
+
+static void
+table_show_value(char *buf, size_t bufsize, ipfw_table_value *v,
+ uint32_t vmask, int print_ip)
+{
+ char abuf[INET6_ADDRSTRLEN + IF_NAMESIZE + 2];
+ struct sockaddr_in6 sa6;
+ uint32_t flag, i, l;
+ size_t sz;
+ struct in_addr a4;
+
+ sz = bufsize;
+
+ /*
+ * Some shorthands for printing values:
+ * legacy assumes all values are equal, so keep the first one.
+ */
+ if (vmask == IPFW_VTYPE_LEGACY) {
+ if (print_ip != 0) {
+ flag = htonl(v->tag);
+ inet_ntop(AF_INET, &flag, buf, sz);
+ } else
+ snprintf(buf, sz, "%u", v->tag);
+ return;
+ }
+
+ for (i = 1; i < (1 << 31); i *= 2) {
+ if ((flag = (vmask & i)) == 0)
+ continue;
+ l = 0;
+
+ switch (flag) {
+ case IPFW_VTYPE_TAG:
+ l = snprintf(buf, sz, "%u,", v->tag);
+ break;
+ case IPFW_VTYPE_PIPE:
+ l = snprintf(buf, sz, "%u,", v->pipe);
+ break;
+ case IPFW_VTYPE_DIVERT:
+ l = snprintf(buf, sz, "%d,", v->divert);
+ break;
+ case IPFW_VTYPE_SKIPTO:
+ l = snprintf(buf, sz, "%d,", v->skipto);
+ break;
+ case IPFW_VTYPE_NETGRAPH:
+ l = snprintf(buf, sz, "%u,", v->netgraph);
+ break;
+ case IPFW_VTYPE_FIB:
+ l = snprintf(buf, sz, "%u,", v->fib);
+ break;
+ case IPFW_VTYPE_NAT:
+ l = snprintf(buf, sz, "%u,", v->nat);
+ break;
+ case IPFW_VTYPE_LIMIT:
+ l = snprintf(buf, sz, "%u,", v->limit);
+ break;
+ case IPFW_VTYPE_NH4:
+ a4.s_addr = htonl(v->nh4);
+ inet_ntop(AF_INET, &a4, abuf, sizeof(abuf));
+ l = snprintf(buf, sz, "%s,", abuf);
+ break;
+ case IPFW_VTYPE_DSCP:
+ l = snprintf(buf, sz, "%d,", v->dscp);
+ break;
+ case IPFW_VTYPE_NH6:
+ sa6.sin6_family = AF_INET6;
+ sa6.sin6_len = sizeof(sa6);
+ sa6.sin6_addr = v->nh6;
+ sa6.sin6_port = 0;
+ sa6.sin6_scope_id = v->zoneid;
+ if (getnameinfo((const struct sockaddr *)&sa6,
+ sa6.sin6_len, abuf, sizeof(abuf), NULL, 0,
+ NI_NUMERICHOST) == 0)
+ l = snprintf(buf, sz, "%s,", abuf);
+ break;
+ }
+
+ buf += l;
+ sz -= l;
+ }
+
+ if (sz != bufsize)
+ *(buf - 1) = '\0';
+}
+
+static void
+table_show_entry(ipfw_xtable_info *i, ipfw_obj_tentry *tent)
+{
+ char *comma, tbuf[128], pval[128];
+ void *paddr;
+ struct tflow_entry *tfe;
+
+ table_show_value(pval, sizeof(pval), &tent->v.value, i->vmask,
+ co.do_value_as_ip);
+
+ switch (i->type) {
+ case IPFW_TABLE_ADDR:
+ /* IPv4 or IPv6 prefixes */
+ inet_ntop(tent->subtype, &tent->k, tbuf, sizeof(tbuf));
+ printf("%s/%u %s\n", tbuf, tent->masklen, pval);
+ break;
+ case IPFW_TABLE_INTERFACE:
+ /* Interface names */
+ printf("%s %s\n", tent->k.iface, pval);
+ break;
+ case IPFW_TABLE_NUMBER:
+ /* numbers */
+ printf("%u %s\n", tent->k.key, pval);
+ break;
+ case IPFW_TABLE_FLOW:
+ /* flows */
+ tfe = &tent->k.flow;
+ comma = "";
+
+ if ((i->tflags & IPFW_TFFLAG_SRCIP) != 0) {
+ if (tfe->af == AF_INET)
+ paddr = &tfe->a.a4.sip;
+ else
+ paddr = &tfe->a.a6.sip6;
+
+ inet_ntop(tfe->af, paddr, tbuf, sizeof(tbuf));
+ printf("%s%s", comma, tbuf);
+ comma = ",";
+ }
+
+ if ((i->tflags & IPFW_TFFLAG_PROTO) != 0) {
+ printf("%s%d", comma, tfe->proto);
+ comma = ",";
+ }
+
+ if ((i->tflags & IPFW_TFFLAG_SRCPORT) != 0) {
+ printf("%s%d", comma, ntohs(tfe->sport));
+ comma = ",";
+ }
+ if ((i->tflags & IPFW_TFFLAG_DSTIP) != 0) {
+ if (tfe->af == AF_INET)
+ paddr = &tfe->a.a4.dip;
+ else
+ paddr = &tfe->a.a6.dip6;
+
+ inet_ntop(tfe->af, paddr, tbuf, sizeof(tbuf));
+ printf("%s%s", comma, tbuf);
+ comma = ",";
+ }
+
+ if ((i->tflags & IPFW_TFFLAG_DSTPORT) != 0) {
+ printf("%s%d", comma, ntohs(tfe->dport));
+ comma = ",";
+ }
+
+ printf(" %s\n", pval);
+ }
+}
+
+static int
+table_do_get_stdlist(uint16_t opcode, ipfw_obj_lheader **polh)
+{
+ ipfw_obj_lheader req, *olh;
+ size_t sz;
+
+ memset(&req, 0, sizeof(req));
+ sz = sizeof(req);
+
+ if (do_get3(opcode, &req.opheader, &sz) != 0)
+ if (errno != ENOMEM)
+ return (errno);
+
+ sz = req.size;
+ if ((olh = calloc(1, sz)) == NULL)
+ return (ENOMEM);
+
+ olh->size = sz;
+ if (do_get3(opcode, &olh->opheader, &sz) != 0) {
+ free(olh);
+ return (errno);
+ }
+
+ *polh = olh;
+ return (0);
+}
+
+static int
+table_do_get_algolist(ipfw_obj_lheader **polh)
+{
+
+ return (table_do_get_stdlist(IP_FW_TABLES_ALIST, polh));
+}
+
+static int
+table_do_get_vlist(ipfw_obj_lheader **polh)
+{
+
+ return (table_do_get_stdlist(IP_FW_TABLE_VLIST, polh));
+}
+
+void
+ipfw_list_ta(int ac, char *av[])
+{
+ ipfw_obj_lheader *olh;
+ ipfw_ta_info *info;
+ int error, i;
+ const char *atype;
+
+ error = table_do_get_algolist(&olh);
+ if (error != 0)
+ err(EX_OSERR, "Unable to request algorithm list");
+
+ info = (ipfw_ta_info *)(olh + 1);
+ for (i = 0; i < olh->count; i++) {
+ if ((atype = match_value(tabletypes, info->type)) == NULL)
+ atype = "unknown";
+ printf("--- %s ---\n", info->algoname);
+ printf(" type: %s\n refcount: %u\n", atype, info->refcnt);
+
+ info = (ipfw_ta_info *)((caddr_t)info + olh->objsize);
+ }
+
+ free(olh);
+}
+
+
+/* Copy of current kernel table_value structure */
+struct _table_value {
+ uint32_t tag; /* O_TAG/O_TAGGED */
+ uint32_t pipe; /* O_PIPE/O_QUEUE */
+ uint16_t divert; /* O_DIVERT/O_TEE */
+ uint16_t skipto; /* skipto, CALLRET */
+ uint32_t netgraph; /* O_NETGRAPH/O_NGTEE */
+ uint32_t fib; /* O_SETFIB */
+ uint32_t nat; /* O_NAT */
+ uint32_t nh4;
+ uint8_t dscp;
+ uint8_t spare0;
+ uint16_t spare1;
+ /* -- 32 bytes -- */
+ struct in6_addr nh6;
+ uint32_t limit; /* O_LIMIT */
+ uint32_t zoneid;
+ uint64_t refcnt; /* Number of references */
+};
+
+int
+compare_values(const void *_a, const void *_b)
+{
+ struct _table_value *a, *b;
+
+ a = (struct _table_value *)_a;
+ b = (struct _table_value *)_b;
+
+ if (a->spare1 < b->spare1)
+ return (-1);
+ else if (a->spare1 > b->spare1)
+ return (1);
+
+ return (0);
+}
+
+void
+ipfw_list_values(int ac, char *av[])
+{
+ ipfw_obj_lheader *olh;
+ struct _table_value *v;
+ int error, i;
+ uint32_t vmask;
+ char buf[128];
+
+ error = table_do_get_vlist(&olh);
+ if (error != 0)
+ err(EX_OSERR, "Unable to request value list");
+
+ vmask = 0x7FFFFFFF; /* Similar to IPFW_VTYPE_LEGACY */
+
+ table_print_valheader(buf, sizeof(buf), vmask);
+ printf("HEADER: %s\n", buf);
+ v = (struct _table_value *)(olh + 1);
+ qsort(v, olh->count, olh->objsize, compare_values);
+ for (i = 0; i < olh->count; i++) {
+ table_show_value(buf, sizeof(buf), (ipfw_table_value *)v,
+ vmask, 0);
+ printf("[%u] refs=%lu %s\n", v->spare1, (u_long)v->refcnt, buf);
+ v = (struct _table_value *)((caddr_t)v + olh->objsize);
+ }
+
+ free(olh);
+}
+
+int
+table_check_name(char *tablename)
+{
+ int c, i, l;
+
+ /*
+ * Check if tablename is null-terminated and contains
+ * valid symbols only. Valid mask is:
+ * [a-zA-Z0-9\-_\.]{1,63}
+ */
+ l = strlen(tablename);
+ if (l == 0 || l >= 64)
+ return (EINVAL);
+ for (i = 0; i < l; i++) {
+ c = tablename[i];
+ if (isalpha(c) || isdigit(c) || c == '_' ||
+ c == '-' || c == '.')
+ continue;
+ return (EINVAL);
+ }
+
+ /* Restrict some 'special' names */
+ if (strcmp(tablename, "all") == 0)
+ return (EINVAL);
+
+ return (0);
+}
+
diff --git a/sbin/iscontrol/Makefile b/sbin/iscontrol/Makefile
new file mode 100644
index 0000000..9c4d9f4
--- /dev/null
+++ b/sbin/iscontrol/Makefile
@@ -0,0 +1,13 @@
+# $FreeBSD$
+
+SRCS= iscontrol.c pdu.c fsm.c config.c login.c auth_subr.c misc.c
+PROG= iscontrol
+LIBADD= cam md
+S= ${.CURDIR}/../../sys
+
+WARNS?= 3
+CFLAGS+= -I$S
+
+MAN= iscontrol.8
+
+.include <bsd.prog.mk>
diff --git a/sbin/iscontrol/auth_subr.c b/sbin/iscontrol/auth_subr.c
new file mode 100644
index 0000000..5f82929
--- /dev/null
+++ b/sbin/iscontrol/auth_subr.c
@@ -0,0 +1,204 @@
+/*-
+ * Copyright (c) 2005-2010 Daniel Braniss <danny@cs.huji.ac.il>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+/*
+ | $Id: auth_subr.c,v 2.2 2007/06/01 08:09:37 danny Exp $
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <fcntl.h>
+
+#include <md5.h>
+#include <sha.h>
+
+#include <dev/iscsi_initiator/iscsi.h>
+#include "iscontrol.h"
+
+static int
+chapMD5(char id, char *cp, char *chapSecret, unsigned char *digest)
+{
+ MD5_CTX ctx;
+ char *tmp;
+ int len;
+
+ debug_called(3);
+
+ MD5Init(&ctx);
+
+ MD5Update(&ctx, &id, 1);
+
+ if((len = str2bin(chapSecret, &tmp)) == 0) {
+ // print error
+ return -1;
+ }
+ MD5Update(&ctx, tmp, len);
+ free(tmp);
+
+ if((len = str2bin(cp, &tmp)) == 0) {
+ // print error
+ return -1;
+ }
+ MD5Update(&ctx, tmp, len);
+ free(tmp);
+
+ MD5Final(digest, &ctx);
+
+
+ return 0;
+}
+
+static int
+chapSHA1(char id, char *cp, char *chapSecret, unsigned char *digest)
+{
+ SHA1_CTX ctx;
+ char *tmp;
+ int len;
+
+ debug_called(3);
+
+ SHA1_Init(&ctx);
+
+ SHA1_Update(&ctx, &id, 1);
+
+ if((len = str2bin(chapSecret, &tmp)) == 0) {
+ // print error
+ return -1;
+ }
+ SHA1_Update(&ctx, tmp, len);
+ free(tmp);
+
+ if((len = str2bin(cp, &tmp)) == 0) {
+ // print error
+ return -1;
+ }
+ SHA1_Update(&ctx, tmp, len);
+ free(tmp);
+
+ SHA1_Final(digest, &ctx);
+
+ return 0;
+
+}
+/*
+ | the input text format can be anything that the rfc3270 defines
+ | (see section 5.1 and str2bin)
+ | digest length for md5 is 128bits, and for sha1 is 160bits.
+ | digest is an ASCII string which represents the bits in
+ | hexadecimal or base64 according to the challenge(cp) format
+ */
+char *
+chapDigest(char *ap, char id, char *cp, char *chapSecret)
+{
+ int len;
+ unsigned char digest[20];
+ char encoding[3];
+
+ debug_called(3);
+
+ len = 0;
+ if(strcmp(ap, "5") == 0 && chapMD5(id, cp, chapSecret, digest) == 0)
+ len = 16;
+ else
+ if(strcmp(ap, "7") == 0 && chapSHA1(id, cp, chapSecret, digest) == 0)
+ len = 20;
+
+ if(len) {
+ sprintf(encoding, "%.2s", cp);
+ return bin2str(encoding, digest, len);
+ }
+
+ return NULL;
+}
+
+char *
+genChapChallenge(char *encoding, uint len)
+{
+ int fd;
+ unsigned char tmp[1024];
+
+ if(len > sizeof(tmp))
+ return NULL;
+
+ if((fd = open("/dev/random", O_RDONLY)) != -1) {
+ read(fd, tmp, len);
+ close(fd);
+ return bin2str(encoding, tmp, len);
+ }
+ perror("/dev/random");
+ // make up something ...
+ return NULL;
+}
+
+#ifdef TEST_AUTH
+static void
+puke(char *str, unsigned char *dg, int len)
+{
+ printf("%3d] %s\n 0x", len, str);
+ while(len-- > 0)
+ printf("%02x", *dg++);
+ printf("\n");
+}
+
+main(int cc, char **vv)
+{
+ char *p, *ap, *ip, *cp, *chapSecret, *digest;
+ int len;
+
+#if 0
+ ap = "5";
+ chapSecret = "0xa5aff013dd839b1edd31ee73a1df0b1b";
+// chapSecret = "abcdefghijklmnop";
+ len = str2bin(chapSecret, &cp);
+ puke(chapSecret, cp, len);
+
+ ip = "238";
+ cp = "0xbd456029";
+
+
+ if((digest = chapDigest(ap, ip, cp, chapSecret)) != NULL) {
+ len = str2bin(digest, &cp);
+ puke(digest, cp, len);
+ }
+#else
+ printf("%d] %s\n", 24, genChallenge("0X", 24));
+#endif
+}
+#endif
diff --git a/sbin/iscontrol/config.c b/sbin/iscontrol/config.c
new file mode 100644
index 0000000..e8fd309
--- /dev/null
+++ b/sbin/iscontrol/config.c
@@ -0,0 +1,380 @@
+ /*-
+ * Copyright (c) 2005-2009 Daniel Braniss <danny@cs.huji.ac.il>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+/*
+ | $Id: config.c,v 2.1 2006/11/12 08:06:51 danny Exp danny $
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <time.h>
+#include <ctype.h>
+#include <camlib.h>
+
+#include <dev/iscsi_initiator/iscsi.h>
+#include "iscontrol.h"
+
+/*
+ | ints
+ */
+#define OPT_port 1
+#define OPT_tags 2
+
+#define OPT_maxConnections 3
+#define OPT_maxRecvDataSegmentLength 4
+#define OPT_maxXmitDataSegmentLength 5
+#define OPT_maxBurstLength 6
+#define OPT_firstBurstLength 7
+#define OPT_defaultTime2Wait 8
+#define OPT_defaultTime2Retain 9
+#define OPT_maxOutstandingR2T 10
+#define OPT_errorRecoveryLevel 11
+#define OPT_targetPortalGroupTag 12
+#define OPT_headerDigest 13
+#define OPT_dataDigest 14
+/*
+ | Booleans
+ */
+#define OPT_initialR2T 16
+#define OPT_immediateData 17
+#define OPT_dataPDUInOrder 18
+#define OPT_dataSequenceInOrder 19
+/*
+ | strings
+ */
+#define OPT_sessionType 15
+
+#define OPT_targetAddress 21
+#define OPT_targetAlias 22
+#define OPT_targetName 23
+#define OPT_initiatorName 24
+#define OPT_initiatorAlias 25
+#define OPT_authMethod 26
+
+#define OPT_chapSecret 27
+#define OPT_chapIName 28
+#define OPT_chapDigest 29
+#define OPT_tgtChapName 30
+#define OPT_tgtChapSecret 31
+#define OPT_tgtChallengeLen 32
+/*
+ | private
+ */
+#define OPT_maxluns 33
+#define OPT_iqn 34
+#define OPT_sockbufsize 35
+
+/*
+ | sentinel
+ */
+#define OPT_end 0
+
+#define _OFF(v) ((int)&((isc_opt_t *)NULL)->v)
+#define _E(u, s, v) {.usage=u, .scope=s, .name=#v, .tokenID=OPT_##v}
+
+textkey_t keyMap[] = {
+ _E(U_PR, S_PR, port),
+ _E(U_PR, S_PR, tags),
+ _E(U_PR, S_PR, maxluns),
+ _E(U_PR, S_PR, sockbufsize),
+
+ _E(U_PR, S_PR, iqn),
+ _E(U_PR, S_PR, chapSecret),
+ _E(U_PR, S_PR, chapIName),
+ _E(U_PR, S_PR, chapDigest),
+ _E(U_PR, S_PR, tgtChapName),
+ _E(U_PR, S_PR, tgtChapSecret),
+ _E(U_PR, S_PR, tgtChallengeLen),
+
+ _E(U_IO, S_CO, headerDigest),
+ _E(U_IO, S_CO, dataDigest),
+
+ _E(U_IO, S_CO, authMethod),
+
+ _E(U_LO, S_SW, maxConnections),
+ _E(U_IO, S_SW, targetName),
+
+ _E(U_IO, S_SW, initiatorName),
+ _E(U_ALL,S_SW, targetAlias),
+ _E(U_ALL,S_SW, initiatorAlias),
+ _E(U_ALL,S_SW, targetAddress),
+
+ _E(U_ALL,S_SW, targetPortalGroupTag),
+
+ _E(U_LO, S_SW, initialR2T),
+ _E(U_LO, S_SW, immediateData),
+
+ _E(U_ALL,S_CO, maxRecvDataSegmentLength),
+ _E(U_ALL,S_CO, maxXmitDataSegmentLength),
+
+ _E(U_LO, S_SW, maxBurstLength),
+ _E(U_LO, S_SW, firstBurstLength),
+ _E(U_LO, S_SW, defaultTime2Wait),
+ _E(U_LO, S_SW, defaultTime2Retain),
+
+ _E(U_LO, S_SW, maxOutstandingR2T),
+ _E(U_LO, S_SW, dataPDUInOrder),
+ _E(U_LO, S_SW, dataSequenceInOrder),
+
+ _E(U_LO, S_SW, errorRecoveryLevel),
+
+ _E(U_LO, S_SW, sessionType),
+
+ _E(0, 0, end)
+};
+
+#define _OPT_INT(w) strtol((char *)w, NULL, 0)
+#define _OPT_STR(w) (char *)(w)
+
+static __inline int
+_OPT_BOOL(char *w)
+{
+ if(isalpha((unsigned char)*w))
+ return strcasecmp(w, "TRUE") == 0;
+ else
+ return _OPT_INT(w);
+}
+
+#define _CASE(k, v) case OPT_##k: op->k = v; break
+static void
+setOption(isc_opt_t *op, int which, void *rval)
+{
+ switch(which) {
+ _CASE(port, _OPT_INT(rval));
+ _CASE(tags, _OPT_INT(rval));
+ _CASE(maxluns, _OPT_INT(rval));
+ _CASE(iqn, _OPT_STR(rval));
+ _CASE(sockbufsize, _OPT_INT(rval));
+
+ _CASE(maxConnections, _OPT_INT(rval));
+ _CASE(maxRecvDataSegmentLength, _OPT_INT(rval));
+ _CASE(maxXmitDataSegmentLength, _OPT_INT(rval));
+ _CASE(maxBurstLength, _OPT_INT(rval));
+ _CASE(firstBurstLength, _OPT_INT(rval));
+ _CASE(defaultTime2Wait, _OPT_INT(rval));
+ _CASE(defaultTime2Retain, _OPT_INT(rval));
+ _CASE(maxOutstandingR2T, _OPT_INT(rval));
+ _CASE(errorRecoveryLevel, _OPT_INT(rval));
+ _CASE(targetPortalGroupTag, _OPT_INT(rval));
+ _CASE(headerDigest, _OPT_STR(rval));
+ _CASE(dataDigest, _OPT_STR(rval));
+
+ _CASE(targetAddress, _OPT_STR(rval));
+ _CASE(targetAlias, _OPT_STR(rval));
+ _CASE(targetName, _OPT_STR(rval));
+ _CASE(initiatorName, _OPT_STR(rval));
+ _CASE(initiatorAlias, _OPT_STR(rval));
+ _CASE(authMethod, _OPT_STR(rval));
+ _CASE(chapSecret, _OPT_STR(rval));
+ _CASE(chapIName, _OPT_STR(rval));
+ _CASE(chapDigest, _OPT_STR(rval));
+
+ _CASE(tgtChapName, _OPT_STR(rval));
+ _CASE(tgtChapSecret, _OPT_STR(rval));
+
+ _CASE(initialR2T, _OPT_BOOL(rval));
+ _CASE(immediateData, _OPT_BOOL(rval));
+ _CASE(dataPDUInOrder, _OPT_BOOL(rval));
+ _CASE(dataSequenceInOrder, _OPT_BOOL(rval));
+ }
+}
+
+static char *
+getline(FILE *fd)
+{
+ static char *sp, line[BUFSIZ];
+ char *lp, *p;
+
+ do {
+ if(sp == NULL)
+ sp = fgets(line, sizeof line, fd);
+
+ if((lp = sp) == NULL)
+ break;
+ if((p = strchr(lp, '\n')) != NULL)
+ *p = 0;
+ if((p = strchr(lp, '#')) != NULL)
+ *p = 0;
+ if((p = strchr(lp, ';')) != NULL) {
+ *p++ = 0;
+ sp = p;
+ } else
+ sp = NULL;
+ if(*lp)
+ return lp;
+ } while (feof(fd) == 0);
+ return NULL;
+}
+
+static int
+getConfig(FILE *fd, char *key, char **Ar, int *nargs)
+{
+ char *lp, *p, **ar;
+ int state, len, n;
+
+ ar = Ar;
+ if(key)
+ len = strlen(key);
+ else
+ len = 0;
+ state = 0;
+ while((lp = getline(fd)) != NULL) {
+ for(; isspace((unsigned char)*lp); lp++)
+ ;
+ switch(state) {
+ case 0:
+ if((p = strchr(lp, '{')) != NULL) {
+ while((--p > lp) && *p && isspace((unsigned char)*p));
+ n = p - lp;
+ if(len && strncmp(lp, key, MAX(n, len)) == 0)
+ state = 2;
+ else
+ state = 1;
+ continue;
+ }
+ break;
+
+ case 1:
+ if(*lp == '}')
+ state = 0;
+ continue;
+
+ case 2:
+ if(*lp == '}')
+ goto done;
+
+ break;
+ }
+
+
+ for(p = &lp[strlen(lp)-1]; isspace((unsigned char)*p); p--)
+ *p = 0;
+ if((*nargs)-- > 0)
+ *ar++ = strdup(lp);
+ }
+
+ done:
+ if(*nargs > 0)
+ *ar = 0;
+ *nargs = ar - Ar;
+ return ar - Ar;
+}
+
+static textkey_t *
+keyLookup(char *key)
+{
+ textkey_t *tk;
+
+ for(tk = keyMap; tk->name && strcmp(tk->name, "end"); tk++) {
+ if(strcasecmp(key, tk->name) == 0)
+ return tk;
+ }
+ return NULL;
+}
+
+static void
+puke(isc_opt_t *op)
+{
+ printf("%24s = %d\n", "port", op->port);
+ printf("%24s = %d\n", "tags", op->tags);
+ printf("%24s = %d\n", "maxluns", op->maxluns);
+ printf("%24s = %s\n", "iqn", op->iqn);
+
+ printf("%24s = %d\n", "maxConnections", op->maxConnections);
+ printf("%24s = %d\n", "maxRecvDataSegmentLength", op->maxRecvDataSegmentLength);
+ printf("%24s = %d\n", "maxXmitDataSegmentLength", op->maxRecvDataSegmentLength);
+ printf("%24s = %d\n", "maxBurstLength", op->maxBurstLength);
+ printf("%24s = %d\n", "firstBurstLength", op->firstBurstLength);
+ printf("%24s = %d\n", "defaultTime2Wait", op->defaultTime2Wait);
+ printf("%24s = %d\n", "defaultTime2Retain", op->defaultTime2Retain);
+ printf("%24s = %d\n", "maxOutstandingR2T", op->maxOutstandingR2T);
+ printf("%24s = %d\n", "errorRecoveryLevel", op->errorRecoveryLevel);
+ printf("%24s = %d\n", "targetPortalGroupTag", op->targetPortalGroupTag);
+
+ printf("%24s = %s\n", "headerDigest", op->headerDigest);
+ printf("%24s = %s\n", "dataDigest", op->dataDigest);
+
+ printf("%24s = %d\n", "initialR2T", op->initialR2T);
+ printf("%24s = %d\n", "immediateData", op->immediateData);
+ printf("%24s = %d\n", "dataPDUInOrder", op->dataPDUInOrder);
+ printf("%24s = %d\n", "dataSequenceInOrder", op->dataSequenceInOrder);
+
+ printf("%24s = %s\n", "sessionType", op->sessionType);
+ printf("%24s = %s\n", "targetAddress", op->targetAddress);
+ printf("%24s = %s\n", "targetAlias", op->targetAlias);
+ printf("%24s = %s\n", "targetName", op->targetName);
+ printf("%24s = %s\n", "initiatorName", op->initiatorName);
+ printf("%24s = %s\n", "initiatorAlias", op->initiatorAlias);
+ printf("%24s = %s\n", "authMethod", op->authMethod);
+ printf("%24s = %s\n", "chapSecret", op->chapSecret);
+ printf("%24s = %s\n", "chapIName", op->chapIName);
+ printf("%24s = %s\n", "tgtChapName", op->tgtChapName);
+ printf("%24s = %s\n", "tgtChapSecret", op->tgtChapSecret);
+ printf("%24s = %d\n", "tgttgtChallengeLen", op->tgtChallengeLen);
+}
+
+void
+parseArgs(int nargs, char **args, isc_opt_t *op)
+{
+ char **ar;
+ char *p, *v;
+ textkey_t *tk;
+
+ for(ar = args; nargs > 0; nargs--, ar++) {
+ p = strchr(*ar, '=');
+ if(p == NULL)
+ continue;
+ *p = 0;
+ v = p + 1;
+ while(isspace((unsigned char)*--p))
+ *p = 0;
+ while(isspace((unsigned char)*v))
+ v++;
+ if((tk = keyLookup(*ar)) == NULL)
+ continue;
+ setOption(op, tk->tokenID, v);
+ }
+}
+
+void
+parseConfig(FILE *fd, char *key, isc_opt_t *op)
+{
+ char *Ar[256];
+ int cc;
+
+ cc = 256;
+ if(getConfig(fd, key, Ar, &cc))
+ parseArgs(cc, Ar, op);
+ if(vflag)
+ puke(op);
+}
diff --git a/sbin/iscontrol/fsm.c b/sbin/iscontrol/fsm.c
new file mode 100644
index 0000000..eebde7c
--- /dev/null
+++ b/sbin/iscontrol/fsm.c
@@ -0,0 +1,757 @@
+/*-
+ * Copyright (c) 2005-2010 Daniel Braniss <danny@cs.huji.ac.il>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+/*
+ | $Id: fsm.c,v 2.8 2007/05/19 16:34:21 danny Exp danny $
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <sys/ioctl.h>
+#include <netdb.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <time.h>
+#include <syslog.h>
+#include <stdarg.h>
+#include <camlib.h>
+
+#include <dev/iscsi_initiator/iscsi.h>
+#include "iscontrol.h"
+
+typedef enum {
+ T1 = 1,
+ T2, /*T3,*/ T4, T5, /*T6,*/ T7, T8, T9,
+ T10, T11, T12, T13, T14, T15, T16, T18
+} trans_t;
+
+/*
+ | now supports IPV6
+ | thanks to:
+ | Hajimu UMEMOTO @ Internet Mutual Aid Society Yokohama, Japan
+ | ume@mahoroba.org ume@{,jp.}FreeBSD.org
+ | http://www.imasy.org/~ume/
+ */
+static trans_t
+tcpConnect(isess_t *sess)
+{
+ isc_opt_t *op = sess->op;
+ int val, sv_errno, soc;
+ struct addrinfo *res, *res0, hints;
+ char pbuf[10];
+
+ debug_called(3);
+ if(sess->flags & (SESS_RECONNECT|SESS_REDIRECT)) {
+ syslog(LOG_INFO, "%s", (sess->flags & SESS_RECONNECT)
+ ? "Reconnect": "Redirected");
+
+ debug(1, "%s", (sess->flags & SESS_RECONNECT) ? "Reconnect": "Redirected");
+ shutdown(sess->soc, SHUT_RDWR);
+ //close(sess->soc);
+ sess->soc = -1;
+
+ sess->flags &= ~SESS_CONNECTED;
+ if(sess->flags & SESS_REDIRECT) {
+ sess->redirect_cnt++;
+ sess->flags |= SESS_RECONNECT;
+ } else
+ sleep(2); // XXX: actually should be ?
+#ifdef notyet
+ {
+ time_t sec;
+ // make sure we are not in a loop
+ // XXX: this code has to be tested
+ sec = time(0) - sess->reconnect_time;
+ if(sec > (5*60)) {
+ // if we've been connected for more that 5 minutes
+ // then just reconnect
+ sess->reconnect_time = sec;
+ sess->reconnect_cnt1 = 0;
+ }
+ else {
+ //
+ sess->reconnect_cnt1++;
+ if((sec / sess->reconnect_cnt1) < 2) {
+ // if less that 2 seconds from the last reconnect
+ // we are most probably looping
+ syslog(LOG_CRIT, "too many reconnects %d", sess->reconnect_cnt1);
+ return 0;
+ }
+ }
+ }
+#endif
+ sess->reconnect_cnt++;
+ }
+
+ snprintf(pbuf, sizeof(pbuf), "%d", op->port);
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ debug(1, "targetAddress=%s port=%d", op->targetAddress, op->port);
+ if((val = getaddrinfo(op->targetAddress, pbuf, &hints, &res0)) != 0) {
+ fprintf(stderr, "getaddrinfo(%s): %s\n", op->targetAddress, gai_strerror(val));
+ return 0;
+ }
+ sess->flags &= ~SESS_CONNECTED;
+ sv_errno = 0;
+ soc = -1;
+ for(res = res0; res; res = res->ai_next) {
+ soc = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+ if (soc == -1)
+ continue;
+
+ // from Patrick.Guelat@imp.ch:
+ // iscontrol can be called without waiting for the socket entry to time out
+ val = 1;
+ if(setsockopt(soc, SOL_SOCKET, SO_REUSEADDR, &val, (socklen_t)sizeof(val)) < 0) {
+ fprintf(stderr, "Cannot set socket SO_REUSEADDR %d: %s\n\n",
+ errno, strerror(errno));
+ }
+
+ if(connect(soc, res->ai_addr, res->ai_addrlen) == 0)
+ break;
+ sv_errno = errno;
+ close(soc);
+ soc = -1;
+ }
+ freeaddrinfo(res0);
+ if(soc != -1) {
+ sess->soc = soc;
+
+#if 0
+ struct timeval timeout;
+
+ val = 1;
+ if(setsockopt(sess->soc, IPPROTO_TCP, TCP_KEEPALIVE, &val, sizeof(val)) < 0)
+ fprintf(stderr, "Cannot set socket KEEPALIVE option err=%d %s\n",
+ errno, strerror(errno));
+
+ if(setsockopt(sess->soc, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val)) < 0)
+ fprintf(stderr, "Cannot set socket NO delay option err=%d %s\n",
+ errno, strerror(errno));
+
+ timeout.tv_sec = 10;
+ timeout.tv_usec = 0;
+ if((setsockopt(sess->soc, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)) < 0)
+ || (setsockopt(sess->soc, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0)) {
+ fprintf(stderr, "Cannot set socket timeout to %ld err=%d %s\n",
+ timeout.tv_sec, errno, strerror(errno));
+ }
+#endif
+#ifdef CURIOUS
+ {
+ int len = sizeof(val);
+ if(getsockopt(sess->soc, SOL_SOCKET, SO_SNDBUF, &val, &len) == 0)
+ fprintf(stderr, "was: SO_SNDBUF=%dK\n", val/1024);
+ }
+#endif
+ if(sess->op->sockbufsize) {
+ val = sess->op->sockbufsize * 1024;
+ if((setsockopt(sess->soc, SOL_SOCKET, SO_SNDBUF, &val, sizeof(val)) < 0)
+ || (setsockopt(sess->soc, SOL_SOCKET, SO_RCVBUF, &val, sizeof(val)) < 0)) {
+ fprintf(stderr, "Cannot set socket sndbuf & rcvbuf to %d err=%d %s\n",
+ val, errno, strerror(errno));
+ return 0;
+ }
+ }
+ sess->flags |= SESS_CONNECTED;
+ return T1;
+ }
+
+ fprintf(stderr, "errno=%d\n", sv_errno);
+ perror("connect");
+ switch(sv_errno) {
+ case ECONNREFUSED:
+ case EHOSTUNREACH:
+ case ENETUNREACH:
+ case ETIMEDOUT:
+ if((sess->flags & SESS_REDIRECT) == 0) {
+ if(strcmp(op->targetAddress, sess->target.address) != 0) {
+ syslog(LOG_INFO, "reconnecting to original target address");
+ free(op->targetAddress);
+ op->targetAddress = sess->target.address;
+ op->port = sess->target.port;
+ op->targetPortalGroupTag = sess->target.pgt;
+ return T1;
+ }
+ }
+ sleep(5); // for now ...
+ return T1;
+ default:
+ return 0; // terminal error
+ }
+}
+
+int
+setOptions(isess_t *sess, int flag)
+{
+ isc_opt_t oop;
+ char *sep;
+
+ debug_called(3);
+
+ bzero(&oop, sizeof(isc_opt_t));
+
+ if((flag & SESS_FULLFEATURE) == 0) {
+ oop.initiatorName = sess->op->initiatorName;
+ oop.targetAddress = sess->op->targetAddress;
+ if(sess->op->targetName != 0)
+ oop.targetName = sess->op->targetName;
+
+ oop.maxRecvDataSegmentLength = sess->op->maxRecvDataSegmentLength;
+ oop.maxXmitDataSegmentLength = sess->op->maxXmitDataSegmentLength; // XXX:
+ oop.maxBurstLength = sess->op->maxBurstLength;
+ oop.maxluns = sess->op->maxluns;
+ }
+ else {
+ /*
+ | turn on digestion only after login
+ */
+ if(sess->op->headerDigest != NULL) {
+ sep = strchr(sess->op->headerDigest, ',');
+ if(sep == NULL)
+ oop.headerDigest = sess->op->headerDigest;
+ debug(1, "oop.headerDigest=%s", oop.headerDigest);
+ }
+ if(sess->op->dataDigest != NULL) {
+ sep = strchr(sess->op->dataDigest, ',');
+ if(sep == NULL)
+ oop.dataDigest = sess->op->dataDigest;
+ debug(1, "oop.dataDigest=%s", oop.dataDigest);
+ }
+ }
+
+ if(ioctl(sess->fd, ISCSISETOPT, &oop)) {
+ perror("ISCSISETOPT");
+ return -1;
+ }
+ return 0;
+}
+
+static trans_t
+startSession(isess_t *sess)
+{
+
+ int n, fd, nfd;
+ char *dev;
+
+ debug_called(3);
+
+ if((sess->flags & SESS_CONNECTED) == 0) {
+ return T2;
+ }
+ if(sess->fd == -1) {
+ fd = open(iscsidev, O_RDWR);
+ if(fd < 0) {
+ perror(iscsidev);
+ return 0;
+ }
+ {
+ // XXX: this has to go
+ size_t n;
+ n = sizeof(sess->isid);
+ if(sysctlbyname("net.iscsi_initiator.isid", (void *)sess->isid, (size_t *)&n, 0, 0) != 0)
+ perror("sysctlbyname");
+ }
+ if(ioctl(fd, ISCSISETSES, &n)) {
+ perror("ISCSISETSES");
+ return 0;
+ }
+ asprintf(&dev, "%s%d", iscsidev, n);
+ nfd = open(dev, O_RDWR);
+ if(nfd < 0) {
+ perror(dev);
+ free(dev);
+ return 0;
+ }
+ free(dev);
+ close(fd);
+ sess->fd = nfd;
+
+ if(setOptions(sess, 0) != 0)
+ return -1;
+ }
+
+ if(ioctl(sess->fd, ISCSISETSOC, &sess->soc)) {
+ perror("ISCSISETSOC");
+ return 0;
+ }
+
+ return T4;
+}
+
+isess_t *currsess;
+
+static void
+trap(int sig)
+{
+ syslog(LOG_NOTICE, "trapped signal %d", sig);
+ fprintf(stderr, "trapped signal %d\n", sig);
+
+ switch(sig) {
+ case SIGHUP:
+ currsess->flags |= SESS_DISCONNECT;
+ break;
+
+ case SIGUSR1:
+ currsess->flags |= SESS_RECONNECT;
+ break;
+
+ case SIGINT:
+ case SIGTERM:
+ default:
+ return; // ignore
+ }
+}
+
+static int
+doCAM(isess_t *sess)
+{
+ char pathstr[1024];
+ union ccb *ccb;
+ int i, n;
+
+ if(ioctl(sess->fd, ISCSIGETCAM, &sess->cam) != 0) {
+ syslog(LOG_WARNING, "ISCSIGETCAM failed: %d", errno);
+ return 0;
+ }
+ debug(1, "nluns=%d", sess->cam.target_nluns);
+ /*
+ | for now will do this for each lun ...
+ */
+ for(n = i = 0; i < sess->cam.target_nluns; i++) {
+ debug(2, "CAM path_id=%d target_id=%d",
+ sess->cam.path_id, sess->cam.target_id);
+
+ sess->camdev = cam_open_btl(sess->cam.path_id, sess->cam.target_id,
+ i, O_RDWR, NULL);
+ if(sess->camdev == NULL) {
+ //syslog(LOG_WARNING, "%s", cam_errbuf);
+ debug(3, "%s", cam_errbuf);
+ continue;
+ }
+
+ cam_path_string(sess->camdev, pathstr, sizeof(pathstr));
+ debug(2, "pathstr=%s", pathstr);
+
+ ccb = cam_getccb(sess->camdev);
+ bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_relsim) - sizeof(struct ccb_hdr));
+ ccb->ccb_h.func_code = XPT_REL_SIMQ;
+ ccb->crs.release_flags = RELSIM_ADJUST_OPENINGS;
+ ccb->crs.openings = sess->op->tags;
+ if(cam_send_ccb(sess->camdev, ccb) < 0)
+ debug(2, "%s", cam_errbuf);
+ else
+ if((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
+ syslog(LOG_WARNING, "XPT_REL_SIMQ CCB failed");
+ // cam_error_print(sess->camdev, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr);
+ }
+ else {
+ n++;
+ syslog(LOG_INFO, "%s tagged openings now %d\n", pathstr, ccb->crs.openings);
+ }
+ cam_freeccb(ccb);
+ cam_close_device(sess->camdev);
+ }
+ return n;
+}
+
+static trans_t
+supervise(isess_t *sess)
+{
+ int sig, val;
+
+ debug_called(3);
+
+ if(strcmp(sess->op->sessionType, "Discovery") == 0) {
+ sess->flags |= SESS_DISCONNECT;
+ return T9;
+ }
+
+ if(vflag)
+ printf("ready to go scsi\n");
+
+ if(setOptions(sess, SESS_FULLFEATURE) != 0)
+ return 0; // failure
+
+ if((sess->flags & SESS_FULLFEATURE) == 0) {
+ if(daemon(0, 1) != 0) {
+ perror("daemon");
+ exit(1);
+ }
+ if(sess->op->pidfile != NULL) {
+ FILE *pidf;
+
+ pidf = fopen(sess->op->pidfile, "w");
+ if(pidf != NULL) {
+ fprintf(pidf, "%d\n", getpid());
+ fclose(pidf);
+ }
+ }
+ openlog("iscontrol", LOG_CONS|LOG_PERROR|LOG_PID|LOG_NDELAY, LOG_KERN);
+ syslog(LOG_INFO, "running");
+
+ currsess = sess;
+ if(ioctl(sess->fd, ISCSISTART)) {
+ perror("ISCSISTART");
+ return -1;
+ }
+ if(doCAM(sess) == 0) {
+ syslog(LOG_WARNING, "no device found");
+ ioctl(sess->fd, ISCSISTOP);
+ return T15;
+ }
+
+ }
+ else {
+ if(ioctl(sess->fd, ISCSIRESTART)) {
+ perror("ISCSIRESTART");
+ return -1;
+ }
+ }
+
+ signal(SIGINT, trap);
+ signal(SIGHUP, trap);
+ signal(SIGTERM, trap);
+
+ sig = SIGUSR1;
+ signal(sig, trap);
+ if(ioctl(sess->fd, ISCSISIGNAL, &sig)) {
+ perror("ISCSISIGNAL");
+ return -1;
+ }
+ sess->flags |= SESS_FULLFEATURE;
+
+ sess->flags &= ~(SESS_REDIRECT | SESS_RECONNECT);
+ if(vflag)
+ printf("iscontrol: supervise starting main loop\n");
+ /*
+ | the main loop - actually do nothing
+ | all the work is done inside the kernel
+ */
+ while((sess->flags & (SESS_REDIRECT|SESS_RECONNECT|SESS_DISCONNECT)) == 0) {
+ // do something?
+ // like sending a nop_out?
+ sleep(60);
+ }
+ printf("iscontrol: supervise going down\n");
+ syslog(LOG_INFO, "sess flags=%x", sess->flags);
+
+ sig = 0;
+ if(ioctl(sess->fd, ISCSISIGNAL, &sig)) {
+ perror("ISCSISIGNAL");
+ }
+
+ if(sess->flags & SESS_DISCONNECT) {
+ sess->flags &= ~SESS_FULLFEATURE;
+ return T9;
+ }
+ else {
+ val = 0;
+ if(ioctl(sess->fd, ISCSISTOP, &val)) {
+ perror("ISCSISTOP");
+ }
+ sess->flags |= SESS_INITIALLOGIN1;
+ }
+ return T8;
+}
+
+static int
+handledDiscoveryResp(isess_t *sess, pdu_t *pp)
+{
+ u_char *ptr;
+ int len, n;
+
+ debug_called(3);
+
+ len = pp->ds_len;
+ ptr = pp->ds_addr;
+ while(len > 0) {
+ if(*ptr != 0)
+ printf("%s\n", ptr);
+ n = strlen((char *)ptr) + 1;
+ len -= n;
+ ptr += n;
+ }
+ return 0;
+}
+
+static int
+doDiscovery(isess_t *sess)
+{
+ pdu_t spp;
+ text_req_t *tp = (text_req_t *)&spp.ipdu.bhs;
+
+ debug_called(3);
+
+ bzero(&spp, sizeof(pdu_t));
+ tp->cmd = ISCSI_TEXT_CMD /*| 0x40 */; // because of a bug in openiscsi-target
+ tp->F = 1;
+ tp->ttt = 0xffffffff;
+ addText(&spp, "SendTargets=All");
+ return sendPDU(sess, &spp, handledDiscoveryResp);
+}
+
+static trans_t
+doLogin(isess_t *sess)
+{
+ isc_opt_t *op = sess->op;
+ int status, count;
+
+ debug_called(3);
+
+ if(op->chapSecret == NULL && op->tgtChapSecret == NULL)
+ /*
+ | don't need any security negotiation
+ | or in other words: we don't have any secrets to exchange
+ */
+ sess->csg = LON_PHASE;
+ else
+ sess->csg = SN_PHASE;
+
+ if(sess->tsih) {
+ sess->tsih = 0; // XXX: no 'reconnect' yet
+ sess->flags &= ~SESS_NEGODONE; // XXX: KLUDGE
+ }
+ count = 10; // should be more than enough
+ do {
+ debug(3, "count=%d csg=%d", count, sess->csg);
+ status = loginPhase(sess);
+ if(count-- == 0)
+ // just in case we get into a loop
+ status = -1;
+ } while(status == 0 && (sess->csg != FF_PHASE));
+
+ sess->flags &= ~SESS_INITIALLOGIN;
+ debug(3, "status=%d", status);
+
+ switch(status) {
+ case 0: // all is ok ...
+ sess->flags |= SESS_LOGGEDIN;
+ if(strcmp(sess->op->sessionType, "Discovery") == 0)
+ doDiscovery(sess);
+ return T5;
+
+ case 1: // redirect - temporary/permanent
+ /*
+ | start from scratch?
+ */
+ sess->flags &= ~SESS_NEGODONE;
+ sess->flags |= (SESS_REDIRECT | SESS_INITIALLOGIN1);
+ syslog(LOG_DEBUG, "target sent REDIRECT");
+ return T7;
+
+ case 2: // initiator terminal error
+ return 0;
+ case 3: // target terminal error -- could retry ...
+ sleep(5);
+ return T7; // lets try
+ default:
+ return 0;
+ }
+}
+
+static int
+handleLogoutResp(isess_t *sess, pdu_t *pp)
+{
+ if(sess->flags & SESS_DISCONNECT) {
+ int val = 0;
+ if(ioctl(sess->fd, ISCSISTOP, &val)) {
+ perror("ISCSISTOP");
+ }
+ return 0;
+ }
+ return T13;
+}
+
+static trans_t
+startLogout(isess_t *sess)
+{
+ pdu_t spp;
+ logout_req_t *p = (logout_req_t *)&spp.ipdu.bhs;
+
+ bzero(&spp, sizeof(pdu_t));
+ p->cmd = ISCSI_LOGOUT_CMD| 0x40;
+ p->reason = BIT(7) | 0;
+ p->CID = htons(1);
+
+ return sendPDU(sess, &spp, handleLogoutResp);
+}
+
+static trans_t
+inLogout(isess_t *sess)
+{
+ if(sess->flags & SESS_RECONNECT)
+ return T18;
+ return 0;
+}
+
+typedef enum {
+ S1, S2, /*S3,*/ S4, S5, S6, S7, S8
+} state_t;
+
+/**
+ S1: FREE
+ S2: XPT_WAIT
+ S4: IN_LOGIN
+ S5: LOGGED_IN
+ S6: IN_LOGOUT
+ S7: LOGOUT_REQUESTED
+ S8: CLEANUP_WAIT
+
+ -------<-------------+
+ +--------->/ S1 \<----+ |
+ T13| +->\ /<-+ \ |
+ | / ---+--- \ \ |
+ | / | T2 \ | |
+ | T8 | |T1 | | |
+ | | | / |T7 |
+ | | | / | |
+ | | | / | |
+ | | V / / |
+ | | ------- / / |
+ | | / S2 \ / |
+ | | \ / / |
+ | | ---+--- / |
+ | | |T4 / |
+ | | V / | T18
+ | | ------- / |
+ | | / S4 \ |
+ | | \ / |
+ | | ---+--- | T15
+ | | |T5 +--------+---------+
+ | | | /T16+-----+------+ |
+ | | | / -+-----+--+ | |
+ | | | / / S7 \ |T12| |
+ | | | / +->\ /<-+ V V
+ | | | / / -+----- -------
+ | | | / /T11 |T10 / S8 \
+ | | V / / V +----+ \ /
+ | | ---+-+- ----+-- | -------
+ | | / S5 \T9 / S6 \<+ ^
+ | +-----\ /--->\ / T14 |
+ | ------- --+----+------+T17
+ +---------------------------+
+*/
+
+int
+fsm(isc_opt_t *op)
+{
+ state_t state;
+ isess_t *sess;
+
+ if((sess = calloc(1, sizeof(isess_t))) == NULL) {
+ // boy, is this a bad start ...
+ fprintf(stderr, "no memory!\n");
+ return -1;
+ }
+
+ state = S1;
+ sess->op = op;
+ sess->fd = -1;
+ sess->soc = -1;
+ sess->target.address = strdup(op->targetAddress);
+ sess->target.port = op->port;
+ sess->target.pgt = op->targetPortalGroupTag;
+
+ sess->flags = SESS_INITIALLOGIN | SESS_INITIALLOGIN1;
+
+ do {
+ switch(state) {
+
+ case S1:
+ switch(tcpConnect(sess)) {
+ case T1: state = S2; break;
+ default: state = S8; break;
+ }
+ break;
+
+ case S2:
+ switch(startSession(sess)) {
+ case T2: state = S1; break;
+ case T4: state = S4; break;
+ default: state = S8; break;
+ }
+ break;
+
+ case S4:
+ switch(doLogin(sess)) {
+ case T7: state = S1; break;
+ case T5: state = S5; break;
+ default: state = S8; break;
+ }
+ break;
+
+ case S5:
+ switch(supervise(sess)) {
+ case T8: state = S1; break;
+ case T9: state = S6; break;
+ case T11: state = S7; break;
+ case T15: state = S8; break;
+ default: state = S8; break;
+ }
+ break;
+
+ case S6:
+ switch(startLogout(sess)) {
+ case T13: state = S1; break;
+ case T14: state = S6; break;
+ case T16: state = S8; break;
+ default: state = S8; break;
+ }
+ break;
+
+ case S7:
+ switch(inLogout(sess)) {
+ case T18: state = S1; break;
+ case T10: state = S6; break;
+ case T12: state = S7; break;
+ case T16: state = S8; break;
+ default: state = S8; break;
+ }
+ break;
+
+ case S8:
+ // maybe do some clean up?
+ syslog(LOG_INFO, "terminated");
+ return 0;
+ }
+ } while(1);
+}
diff --git a/sbin/iscontrol/iscontrol.8 b/sbin/iscontrol/iscontrol.8
new file mode 100644
index 0000000..36e0c47
--- /dev/null
+++ b/sbin/iscontrol/iscontrol.8
@@ -0,0 +1,137 @@
+.\" Copyright (c) 2007-2010 Daniel Braniss <danny@cs.huji.ac.il>
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd October 9, 2014
+.Dt ISCONTROL 8
+.Os
+.Sh NAME
+.Nm iscontrol
+.Nd login/negotiator/control for an iSCSI initiator session
+.Sh SYNOPSIS
+.Nm
+.Op Fl dv
+.Oo
+.Fl c Ar file
+.Op Fl n Ar nickname
+.Oc
+.Op Fl p Ar pidfile
+.Op Fl t Ar target
+.Op Ar variable Ns = Ns Ar value
+.Sh DESCRIPTION
+.Bf -symbolic
+This command, along with its kernel counterpart
+.Xr iscsi_initiator 4 ,
+is obsolete.
+Users are advised to use
+.Xr iscsictl 8
+instead.
+.Ef
+.Pp
+Internet SCSI (iSCSI) is a network protocol standard, that allows the
+use of the SCSI protocol over TCP/IP networks,
+the
+.Nm
+program is the userland side of an iSCSI session, see
+.Xr iscsi_initiator 4 .
+It has 2 modes of operation, if -d (discovery session) is specified,
+it will print out the
+.Em target names
+returned by the target and exit.
+In the second mode, it will, after a successful login/negotiation, run
+in daemon mode, monitoring the connection, and will try to reconnect
+in case of a network/target failure.
+It will terminate/logout the session
+when a SIGHUP signal is received.
+The flags are as follows:
+.Bl -tag -width variable=value
+.It Fl c Ar file
+a file containing configuration
+.Em key-options ,
+see
+.Xr iscsi.conf 5 .
+.It Fl d
+do a
+.Em discovery session
+and exit.
+.It Fl n Ar nickname
+if
+.Sy -c file
+is specified, then search for the block named
+.Em nickname
+in that file, see
+.Xr iscsi.conf 5 .
+.It Fl p Ar pidfile
+will write the process ID of the session to the specified
+.Em pidfile
+.It Fl t Ar target
+the target's IP address or name.
+.It Fl v
+verbose mode.
+.It Ar variable Ns = Ns Ar value
+see
+.Xr iscsi.conf 5
+for the complete list of variables/options and their
+possible values.
+.El
+.Sh EXAMPLES
+.Dl iscontrol -dt myiscsitarget
+.Pp
+will start a
+.Em discovery session
+with the target and
+print to stdout the list of available targetnames/targetadresses.
+Note: this listing does not necessarily mean availability, since
+depending on the target configuration, a discovery session might
+not need login/access permission, but a
+.Em full session
+certainly does.
+.sp
+.Dl iscontrol -c /etc/iscsi.conf -n myiscsi
+.Pp
+will read options from
+.Pa /etc/iscsi.conf ,
+use the targetaddress
+found in the block nicknamed myiscsi, login and negotiate
+whatever options are specified, and start an iscsi-session.
+.Sh SEE ALSO
+.Xr da 4 ,
+.Xr iscsi_initiator 4 ,
+.Xr sa 4 ,
+.Xr iscsi.conf 5 ,
+.Xr camcontrol 8 ,
+.Xr iscsictl 8
+.Sh STANDARDS
+RFC 3720
+.\"Sh HISTORY
+.Sh BUGS
+.Nm
+should probably load the iscsi_initiator module if needed.
+.br
+Not all functions/specifications have been implemented yet, noticeably
+missing are the Task Management Functions.
+The error recovery, though not
+.Em fully compliant
+does a brave effort to recover from network disconnects.
diff --git a/sbin/iscontrol/iscontrol.c b/sbin/iscontrol/iscontrol.c
new file mode 100644
index 0000000..2e8f2ea
--- /dev/null
+++ b/sbin/iscontrol/iscontrol.c
@@ -0,0 +1,259 @@
+/*-
+ * Copyright (c) 2005-2010 Daniel Braniss <danny@cs.huji.ac.il>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+/*
+ | $Id: iscontrol.c,v 2.2 2006/12/01 09:11:56 danny Exp danny $
+ */
+/*
+ | the user level initiator (client)
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <sys/ioctl.h>
+#include <netdb.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <camlib.h>
+
+#include <dev/iscsi_initiator/iscsi.h>
+#include "iscontrol.h"
+
+static char version[] = "2.3.1"; // keep in sync with iscsi_initiator
+
+#define USAGE "[-v] [-d] [-c config] [-n name] [-t target] [-p pidfile]"
+#define OPTIONS "vdc:t:n:p:"
+
+token_t AuthMethods[] = {
+ {"None", NONE},
+ {"KRB5", KRB5},
+ {"SPKM1", SPKM1},
+ {"SPKM2", SPKM2},
+ {"SRP", SRP},
+ {"CHAP", CHAP},
+ {0, 0}
+};
+
+token_t DigestMethods[] = {
+ {"None", 0},
+ {"CRC32", 1},
+ {"CRC32C", 1},
+ {0, 0}
+};
+
+u_char isid[6 + 6];
+/*
+ | Default values
+ */
+isc_opt_t opvals = {
+ .port = 3260,
+ .sockbufsize = 128,
+ .iqn = "iqn.2005-01.il.ac.huji.cs:",
+
+ .sessionType = "Normal",
+ .targetAddress = 0,
+ .targetName = 0,
+ .initiatorName = 0,
+ .authMethod = "None",
+ .headerDigest = "None,CRC32C",
+ .dataDigest = "None,CRC32C",
+ .maxConnections = 1,
+ .maxRecvDataSegmentLength = 64 * 1024,
+ .maxXmitDataSegmentLength = 8 * 1024, // 64 * 1024,
+ .maxBurstLength = 128 * 1024,
+ .firstBurstLength = 64 * 1024, // must be less than maxBurstLength
+ .defaultTime2Wait = 0,
+ .defaultTime2Retain = 0,
+ .maxOutstandingR2T = 1,
+ .errorRecoveryLevel = 0,
+
+ .dataPDUInOrder = TRUE,
+ .dataSequenceInOrder = TRUE,
+
+ .initialR2T = TRUE,
+ .immediateData = TRUE,
+};
+
+static void
+usage(const char *pname)
+{
+ fprintf(stderr, "usage: %s " USAGE "\n", pname);
+ exit(1);
+}
+
+int
+lookup(token_t *tbl, char *m)
+{
+ token_t *tp;
+
+ for(tp = tbl; tp->name != NULL; tp++)
+ if(strcasecmp(tp->name, m) == 0)
+ return tp->val;
+ return 0;
+}
+
+int
+main(int cc, char **vv)
+{
+ int ch, disco;
+ char *pname, *pidfile, *p, *q, *ta, *kw, *v;
+ isc_opt_t *op;
+ FILE *fd;
+ size_t n;
+
+ op = &opvals;
+ iscsidev = "/dev/"ISCSIDEV;
+ fd = NULL;
+ pname = vv[0];
+ if ((pname = basename(pname)) == NULL)
+ err(1, "basename");
+
+ kw = ta = 0;
+ disco = 0;
+ pidfile = NULL;
+ /*
+ | check for driver & controller version match
+ */
+ n = 0;
+#define VERSION_OID_S "net.iscsi_initiator.driver_version"
+ if (sysctlbyname(VERSION_OID_S, 0, &n, 0, 0) != 0) {
+ if (errno == ENOENT)
+ errx(1, "sysctlbyname(\"" VERSION_OID_S "\") "
+ "failed; is the iscsi driver loaded?");
+ err(1, "sysctlbyname(\"" VERSION_OID_S "\")");
+ }
+ v = malloc(n+1);
+ if (v == NULL)
+ err(1, "malloc");
+ if (sysctlbyname(VERSION_OID_S, v, &n, 0, 0) != 0)
+ err(1, "sysctlbyname");
+
+ if (strncmp(version, v, 3) != 0)
+ errx(1, "versions mismatch");
+
+ while((ch = getopt(cc, vv, OPTIONS)) != -1) {
+ switch(ch) {
+ case 'v':
+ vflag++;
+ break;
+ case 'c':
+ fd = fopen(optarg, "r");
+ if (fd == NULL)
+ err(1, "fopen(\"%s\")", optarg);
+ break;
+ case 'd':
+ disco = 1;
+ break;
+ case 't':
+ ta = optarg;
+ break;
+ case 'n':
+ kw = optarg;
+ break;
+ case 'p':
+ pidfile = optarg;
+ break;
+ default:
+ usage(pname);
+ }
+ }
+ if(fd == NULL)
+ fd = fopen("/etc/iscsi.conf", "r");
+
+ if(fd != NULL) {
+ parseConfig(fd, kw, op);
+ fclose(fd);
+ }
+ cc -= optind;
+ vv += optind;
+ if(cc > 0) {
+ if(vflag)
+ printf("adding '%s'\n", *vv);
+ parseArgs(cc, vv, op);
+ }
+ if(ta)
+ op->targetAddress = ta;
+
+ if(op->targetAddress == NULL) {
+ warnx("no target specified!");
+ usage(pname);
+ }
+ q = op->targetAddress;
+ if(*q == '[' && (q = strchr(q, ']')) != NULL) {
+ *q++ = '\0';
+ op->targetAddress++;
+ } else
+ q = op->targetAddress;
+ if((p = strchr(q, ':')) != NULL) {
+ *p++ = 0;
+ op->port = atoi(p);
+ p = strchr(p, ',');
+ }
+ if(p || ((p = strchr(q, ',')) != NULL)) {
+ *p++ = 0;
+ op->targetPortalGroupTag = atoi(p);
+ }
+ if(op->initiatorName == 0) {
+ char hostname[MAXHOSTNAMELEN];
+
+ if(op->iqn) {
+ if(gethostname(hostname, sizeof(hostname)) == 0)
+ asprintf(&op->initiatorName, "%s:%s", op->iqn, hostname);
+ else
+ asprintf(&op->initiatorName, "%s:%d", op->iqn, (int)time(0) & 0xff); // XXX:
+ }
+ else {
+ if(gethostname(hostname, sizeof(hostname)) == 0)
+ asprintf(&op->initiatorName, "%s", hostname);
+ else
+ asprintf(&op->initiatorName, "%d", (int)time(0) & 0xff); // XXX:
+ }
+ }
+ if(disco) {
+ op->sessionType = "Discovery";
+ op->targetName = 0;
+ }
+ op->pidfile = pidfile;
+ fsm(op);
+
+ exit(0);
+}
diff --git a/sbin/iscontrol/iscontrol.h b/sbin/iscontrol/iscontrol.h
new file mode 100644
index 0000000..6aa5612
--- /dev/null
+++ b/sbin/iscontrol/iscontrol.h
@@ -0,0 +1,165 @@
+/*-
+ * Copyright (c) 2005-2010 Daniel Braniss <danny@cs.huji.ac.il>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+/*
+ | $Id: iscontrol.h,v 2.3 2007/04/27 08:36:49 danny Exp danny $
+ */
+#ifdef DEBUG
+int vflag;
+
+# define debug(level, fmt, args...) do {if (level <= vflag) printf("%s: " fmt "\n", __func__ , ##args);} while(0)
+# define debug_called(level) do {if (level <= vflag) printf("%s: called\n", __func__);} while(0)
+#else
+# define debug(level, fmt, args...)
+# define debug_called(level)
+#endif // DEBUG
+#define xdebug(fmt, args...) printf("%s: " fmt "\n", __func__ , ##args)
+
+#define BIT(n) (1 <<(n))
+
+#define MAXREDIRECTS 2
+
+typedef int auth_t(void *sess);
+
+typedef struct {
+ char *address;
+ int port;
+ int pgt;
+} target_t;
+
+typedef struct isess {
+ int flags;
+#define SESS_CONNECTED BIT(0)
+#define SESS_DISCONNECT BIT(1)
+#define SESS_LOGGEDIN BIT(2)
+#define SESS_RECONNECT BIT(3)
+#define SESS_REDIRECT BIT(4)
+
+#define SESS_NEGODONE BIT(10) // XXX: kludge
+
+#define SESS_FULLFEATURE BIT(29)
+#define SESS_INITIALLOGIN1 BIT(30)
+#define SESS_INITIALLOGIN BIT(31)
+
+
+ isc_opt_t *op; // operational values
+ target_t target; // the Original target address
+ int fd; // the session fd
+ int soc; // the socket
+ iscsi_cam_t cam;
+ struct cam_device *camdev;
+
+ time_t open_time;
+ int redirect_cnt;
+ time_t redirect_time;
+ int reconnect_cnt;
+ int reconnect_cnt1;
+ time_t reconnect_time;
+ char isid[6+1];
+ int csg; // current stage
+ int nsg; // next stage
+ // Phases/Stages
+#define SN_PHASE 0 // Security Negotiation
+#define LON_PHASE 1 // Login Operational Negotiation
+#define FF_PHASE 3 // FuLL-Feature
+ uint tsih;
+ sn_t sn;
+} isess_t;
+
+typedef struct token {
+ char *name;
+ int val;
+} token_t;
+
+typedef enum {
+ NONE = 0,
+ KRB5,
+ SPKM1,
+ SPKM2,
+ SRP,
+ CHAP
+} authm_t;
+
+extern token_t AuthMethods[];
+extern token_t DigestMethods[];
+
+typedef enum {
+ SET,
+ GET
+} oper_t;
+
+typedef enum {
+ U_PR, // private
+ U_IO, // Initialize Only -- during login
+ U_LO, // Leading Only -- when TSIH is zero
+ U_FFPO, // Full Feature Phase Only
+ U_ALL // in any phase
+} usage_t;
+
+typedef enum {
+ S_PR,
+ S_CO, // Connect only
+ S_SW // Session Wide
+} scope_t;
+
+typedef void keyfun_t(isess_t *, oper_t);
+
+typedef struct {
+ usage_t usage;
+ scope_t scope;
+ char *name;
+ int tokenID;
+} textkey_t;
+
+typedef int handler_t(isess_t *sess, pdu_t *pp);
+
+int authenticateLogin(isess_t *sess);
+int fsm(isc_opt_t *op);
+int sendPDU(isess_t *sess, pdu_t *pp, handler_t *hdlr);
+int addText(pdu_t *pp, char *fmt, ...);
+void freePDU(pdu_t *pp);
+int xmitpdu(isess_t *sess, pdu_t *pp);
+int recvpdu(isess_t *sess, pdu_t *pp);
+
+int lookup(token_t *tbl, char *m);
+
+int vflag;
+char *iscsidev;
+
+void parseArgs(int nargs, char **args, isc_opt_t *op);
+void parseConfig(FILE *fd, char *key, isc_opt_t *op);
+
+char *chapDigest(char *ap, char id, char *cp, char *chapSecret);
+char *genChapChallenge(char *encoding, uint len);
+
+int str2bin(char *str, char **rsp);
+char *bin2str(char *fmt, unsigned char *md, int blen);
+
+int negotiateOPV(isess_t *sess);
+int setOptions(isess_t *sess, int flag);
+
+int loginPhase(isess_t *sess);
diff --git a/sbin/iscontrol/login.c b/sbin/iscontrol/login.c
new file mode 100644
index 0000000..92bbc64b
--- /dev/null
+++ b/sbin/iscontrol/login.c
@@ -0,0 +1,440 @@
+/*-
+ * Copyright (c) 2005-2010 Daniel Braniss <danny@cs.huji.ac.il>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+/*
+ | $Id: login.c,v 1.4 2007/04/27 07:40:40 danny Exp danny $
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <sys/ioctl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <dev/iscsi_initiator/iscsi.h>
+#include "iscontrol.h"
+
+static char *status_class1[] = {
+ "Initiator error",
+ "Authentication failure",
+ "Authorization failure",
+ "Not found",
+ "Target removed",
+ "Unsupported version",
+ "Too many connections",
+ "Missing parameter",
+ "Can't include in session",
+ "Session type not suported",
+ "Session does not exist",
+ "Invalid during login",
+};
+#define CLASS1_ERRS ((sizeof status_class1) / sizeof(char *))
+
+static char *status_class3[] = {
+ "Target error",
+ "Service unavailable",
+ "Out of resources"
+};
+#define CLASS3_ERRS ((sizeof status_class3) / sizeof(char *))
+
+static char *
+selectFrom(char *str, token_t *list)
+{
+ char *sep, *sp;
+ token_t *lp;
+ int n;
+
+ sp = str;
+ do {
+ sep = strchr(sp, ',');
+ if(sep != NULL)
+ n = sep - sp;
+ else
+ n = strlen(sp);
+
+ for(lp = list; lp->name != NULL; lp++) {
+ if(strncasecmp(lp->name, sp, n) == 0)
+ return strdup(lp->name);
+ }
+ sp = sep + 1;
+ } while(sep != NULL);
+
+ return NULL;
+}
+
+static char *
+getkeyval(char *key, pdu_t *pp)
+{
+ char *ptr;
+ int klen, len, n;
+
+ debug_called(3);
+
+ len = pp->ds_len;
+ ptr = (char *)pp->ds_addr;
+ klen = strlen(key);
+ while(len > klen) {
+ if(strncmp(key, ptr, klen) == 0)
+ return ptr+klen;
+ n = strlen(ptr) + 1;
+ len -= n;
+ ptr += n;
+ }
+ return 0;
+}
+
+static int
+handleTgtResp(isess_t *sess, pdu_t *pp)
+{
+ isc_opt_t *op = sess->op;
+ char *np, *rp, *d1, *d2;
+ int res, l1, l2;
+
+ res = -1;
+ if(((np = getkeyval("CHAP_N=", pp)) == NULL) ||
+ ((rp = getkeyval("CHAP_R=", pp)) == NULL))
+ goto out;
+ if(strcmp(np, op->tgtChapName? op->tgtChapName: op->initiatorName) != 0) {
+ fprintf(stderr, "%s does not match\n", np);
+ goto out;
+ }
+ l1 = str2bin(op->tgtChapDigest, &d1);
+ l2 = str2bin(rp, &d2);
+
+ debug(3, "l1=%d '%s' l2=%d '%s'", l1, op->tgtChapDigest, l2, rp);
+ if(l1 == l2 && memcmp(d1, d2, l1) == 0)
+ res = 0;
+ if(l1)
+ free(d1);
+ if(l2)
+ free(d2);
+ out:
+ free(op->tgtChapDigest);
+ op->tgtChapDigest = NULL;
+
+ debug(3, "res=%d", res);
+
+ return res;
+}
+
+static void
+processParams(isess_t *sess, pdu_t *pp)
+{
+ isc_opt_t *op = sess->op;
+ int len, klen, n;
+ char *eq, *ptr;
+
+ debug_called(3);
+
+ len = pp->ds_len;
+ ptr = (char *)pp->ds_addr;
+ while(len > 0) {
+ if(vflag > 1)
+ printf("got: len=%d %s\n", len, ptr);
+ klen = 0;
+ if((eq = strchr(ptr, '=')) != NULL)
+ klen = eq - ptr;
+ if(klen > 0) {
+ if(strncmp(ptr, "TargetAddress", klen) == 0) {
+ char *p, *q, *ta = NULL;
+
+ // TargetAddress=domainname[:port][,portal-group-tag]
+ // XXX: if(op->targetAddress) free(op->targetAddress);
+ q = op->targetAddress = strdup(eq+1);
+ if(*q == '[') {
+ // bracketed IPv6
+ if((q = strchr(q, ']')) != NULL) {
+ *q++ = '\0';
+ ta = op->targetAddress;
+ op->targetAddress = strdup(ta+1);
+ } else
+ q = op->targetAddress;
+ }
+ if((p = strchr(q, ',')) != NULL) {
+ *p++ = 0;
+ op->targetPortalGroupTag = atoi(p);
+ }
+ if((p = strchr(q, ':')) != NULL) {
+ *p++ = 0;
+ op->port = atoi(p);
+ }
+ if(ta)
+ free(ta);
+ } else if(strncmp(ptr, "MaxRecvDataSegmentLength", klen) == 0) {
+ // danny's RFC
+ op->maxXmitDataSegmentLength = strtol(eq+1, (char **)NULL, 0);
+ } else if(strncmp(ptr, "TargetPortalGroupTag", klen) == 0) {
+ op->targetPortalGroupTag = strtol(eq+1, (char **)NULL, 0);
+ } else if(strncmp(ptr, "HeaderDigest", klen) == 0) {
+ op->headerDigest = selectFrom(eq+1, DigestMethods);
+ } else if(strncmp(ptr, "DataDigest", klen) == 0) {
+ op->dataDigest = selectFrom(eq+1, DigestMethods);
+ } else if(strncmp(ptr, "MaxOutstandingR2T", klen) == 0)
+ op->maxOutstandingR2T = strtol(eq+1, (char **)NULL, 0);
+#if 0
+ else
+ for(kp = keyMap; kp->name; kp++) {
+ if(strncmp(ptr, kp->name, kp->len) == 0 && ptr[kp->len] == '=')
+ mp->func(sess, ptr+kp->len+1, GET);
+ }
+#endif
+ }
+ n = strlen(ptr) + 1;
+ len -= n;
+ ptr += n;
+ }
+
+}
+
+static int
+handleLoginResp(isess_t *sess, pdu_t *pp)
+{
+ login_rsp_t *lp = (login_rsp_t *)pp;
+ uint st_class, status = ntohs(lp->status);
+
+ debug_called(3);
+ debug(4, "Tbit=%d csg=%d nsg=%d status=%x", lp->T, lp->CSG, lp->NSG, status);
+
+ st_class = status >> 8;
+ if(status) {
+ uint st_detail = status & 0xff;
+
+ switch(st_class) {
+ case 1: // Redirect
+ switch(st_detail) {
+ // the ITN (iSCSI target Name) requests a:
+ case 1: // temporary address change
+ case 2: // permanent address change
+ status = 0;
+ }
+ break;
+
+ case 2: // Initiator Error
+ if(st_detail < CLASS1_ERRS)
+ printf("0x%04x: %s\n", status, status_class1[st_detail]);
+ break;
+
+ case 3:
+ if(st_detail < CLASS3_ERRS)
+ printf("0x%04x: %s\n", status, status_class3[st_detail]);
+ break;
+ }
+ }
+
+ if(status == 0) {
+ processParams(sess, pp);
+ setOptions(sess, 0); // XXX: just in case ...
+
+ if(lp->T) {
+ isc_opt_t *op = sess->op;
+
+ if(sess->csg == SN_PHASE && (op->tgtChapDigest != NULL))
+ if(handleTgtResp(sess, pp) != 0)
+ return 1; // XXX: Authentication failure ...
+ sess->csg = lp->NSG;
+ if(sess->csg == FF_PHASE) {
+ // XXX: will need this when implementing reconnect.
+ sess->tsih = lp->tsih;
+ debug(2, "TSIH=%x", sess->tsih);
+ }
+ }
+ }
+
+ return st_class;
+}
+
+static int
+handleChap(isess_t *sess, pdu_t *pp)
+{
+ pdu_t spp;
+ login_req_t *lp;
+ isc_opt_t *op = sess->op;
+ char *ap, *ip, *cp, *digest; // MD5 is 128bits, SHA1 160bits
+
+ debug_called(3);
+
+ bzero(&spp, sizeof(pdu_t));
+ lp = (login_req_t *)&spp.ipdu.bhs;
+ lp->cmd = ISCSI_LOGIN_CMD | 0x40; // login request + Inmediate
+ memcpy(lp->isid, sess->isid, 6);
+ lp->tsih = sess->tsih; // MUST be zero the first time!
+ lp->CID = htons(1);
+ lp->CSG = SN_PHASE; // Security Negotiation
+ lp->NSG = LON_PHASE;
+ lp->T = 1;
+
+ if(((ap = getkeyval("CHAP_A=", pp)) == NULL) ||
+ ((ip = getkeyval("CHAP_I=", pp)) == NULL) ||
+ ((cp = getkeyval("CHAP_C=", pp)) == NULL))
+ return -1;
+
+ if((digest = chapDigest(ap, (char)strtol(ip, (char **)NULL, 0), cp, op->chapSecret)) == NULL)
+ return -1;
+
+ addText(&spp, "CHAP_N=%s", op->chapIName? op->chapIName: op->initiatorName);
+ addText(&spp, "CHAP_R=%s", digest);
+ free(digest);
+
+ if(op->tgtChapSecret != NULL) {
+ op->tgtChapID = (random() >> 24) % 255; // should be random enough ...
+ addText(&spp, "CHAP_I=%d", op->tgtChapID);
+ cp = genChapChallenge(cp, op->tgtChallengeLen? op->tgtChallengeLen: 8);
+ addText(&spp, "CHAP_C=%s", cp);
+ op->tgtChapDigest = chapDigest(ap, op->tgtChapID, cp, op->tgtChapSecret);
+ }
+
+ return sendPDU(sess, &spp, handleLoginResp);
+}
+
+static int
+authenticate(isess_t *sess)
+{
+ pdu_t spp;
+ login_req_t *lp;
+ isc_opt_t *op = sess->op;
+
+ bzero(&spp, sizeof(pdu_t));
+ lp = (login_req_t *)&spp.ipdu.bhs;
+ lp->cmd = ISCSI_LOGIN_CMD | 0x40; // login request + Inmediate
+ memcpy(lp->isid, sess->isid, 6);
+ lp->tsih = sess->tsih; // MUST be zero the first time!
+ lp->CID = htons(1);
+ lp->CSG = SN_PHASE; // Security Negotiation
+ lp->NSG = SN_PHASE;
+ lp->T = 0;
+
+ switch((authm_t)lookup(AuthMethods, op->authMethod)) {
+ case NONE:
+ return 0;
+
+ case KRB5:
+ case SPKM1:
+ case SPKM2:
+ case SRP:
+ return 2;
+
+ case CHAP:
+ if(op->chapDigest == 0)
+ addText(&spp, "CHAP_A=5");
+ else
+ if(strcmp(op->chapDigest, "MD5") == 0)
+ addText(&spp, "CHAP_A=5");
+ else
+ if(strcmp(op->chapDigest, "SHA1") == 0)
+ addText(&spp, "CHAP_A=7");
+ else
+ addText(&spp, "CHAP_A=5,7");
+ return sendPDU(sess, &spp, handleChap);
+ }
+ return 1;
+}
+
+int
+loginPhase(isess_t *sess)
+{
+ pdu_t spp, *sp = &spp;
+ isc_opt_t *op = sess->op;
+ login_req_t *lp;
+ int status = 1;
+
+ debug_called(3);
+
+ bzero(sp, sizeof(pdu_t));
+ lp = (login_req_t *)&spp.ipdu.bhs;
+ lp->cmd = ISCSI_LOGIN_CMD | 0x40; // login request + Inmediate
+ memcpy(lp->isid, sess->isid, 6);
+ lp->tsih = sess->tsih; // MUST be zero the first time!
+ lp->CID = htons(1); // sess->cid?
+
+ if((lp->CSG = sess->csg) == LON_PHASE)
+ lp->NSG = FF_PHASE; // lets try and go full feature ...
+ else
+ lp->NSG = LON_PHASE;
+ lp->T = 1; // transit to next login stage
+
+ if(sess->flags & SESS_INITIALLOGIN1) {
+ sess->flags &= ~SESS_INITIALLOGIN1;
+
+ addText(sp, "SessionType=%s", op->sessionType);
+ addText(sp, "InitiatorName=%s", op->initiatorName);
+ if(strcmp(op->sessionType, "Discovery") != 0) {
+ addText(sp, "TargetName=%s", op->targetName);
+ }
+ }
+ switch(sess->csg) {
+ case SN_PHASE: // Security Negotiation
+ addText(sp, "AuthMethod=%s", op->authMethod);
+ break;
+
+ case LON_PHASE: // Login Operational Negotiation
+ if((sess->flags & SESS_NEGODONE) == 0) {
+ sess->flags |= SESS_NEGODONE;
+ addText(sp, "MaxBurstLength=%d", op->maxBurstLength);
+ addText(sp, "HeaderDigest=%s", op->headerDigest);
+ addText(sp, "DataDigest=%s", op->dataDigest);
+ addText(sp, "MaxRecvDataSegmentLength=%d", op->maxRecvDataSegmentLength);
+ addText(sp, "ErrorRecoveryLevel=%d", op->errorRecoveryLevel);
+ addText(sp, "DefaultTime2Wait=%d", op->defaultTime2Wait);
+ addText(sp, "DefaultTime2Retain=%d", op->defaultTime2Retain);
+ addText(sp, "DataPDUInOrder=%s", op->dataPDUInOrder? "Yes": "No");
+ addText(sp, "DataSequenceInOrder=%s", op->dataSequenceInOrder? "Yes": "No");
+ addText(sp, "MaxOutstandingR2T=%d", op->maxOutstandingR2T);
+
+ if(strcmp(op->sessionType, "Discovery") != 0) {
+ addText(sp, "MaxConnections=%d", op->maxConnections);
+ addText(sp, "FirstBurstLength=%d", op->firstBurstLength);
+ addText(sp, "InitialR2T=%s", op->initialR2T? "Yes": "No");
+ addText(sp, "ImmediateData=%s", op->immediateData? "Yes": "No");
+ }
+ }
+
+ break;
+ }
+
+ status = sendPDU(sess, &spp, handleLoginResp);
+
+ switch(status) {
+ case 0: // all is ok ...
+ if(sess->csg == SN_PHASE)
+ /*
+ | if we are still here, then we need
+ | to exchange some secrets ...
+ */
+ status = authenticate(sess);
+ }
+
+ return status;
+}
diff --git a/sbin/iscontrol/misc.c b/sbin/iscontrol/misc.c
new file mode 100644
index 0000000..b6fe6df
--- /dev/null
+++ b/sbin/iscontrol/misc.c
@@ -0,0 +1,226 @@
+/*-
+ * Copyright (c) 2005-2010 Daniel Braniss <danny@cs.huji.ac.il>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+/*
+ | $Id: misc.c,v 2.1 2006/11/12 08:06:51 danny Exp $
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <dev/iscsi_initiator/iscsi.h>
+#include "iscontrol.h"
+
+static inline char
+c2b(unsigned char c)
+{
+ switch(c) {
+ case '0' ... '9':
+ return c - '0';
+ case 'a' ... 'f':
+ return c - 'a' + 10;
+ case 'A' ... 'F':
+ return c - 'A' + 10;
+ }
+ return 0;
+}
+
+static char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789+/";
+
+static __inline unsigned char
+c64tobin(unsigned char c64)
+{
+ int i;
+ for(i = 0; i < 64; i++)
+ if(base64[i] == c64)
+ break;
+ return i;
+}
+/*
+ | according to rfc3720, the binary string
+ | cannot be larger than 1024 - but i can't find it :-) XXX
+ | not enforced yet.
+ */
+int
+str2bin(char *str, char **rsp)
+{
+ char *src, *dst, *tmp;
+ int i, len = 0;
+
+ src = str;
+ tmp = NULL;
+ if(strncasecmp("0x", src, 2) == 0) {
+ src += 2;
+ len = strlen(src);
+
+ if((tmp = malloc((len+1)/2)) == NULL) {
+ // XXX: print some error?
+ return 0;
+ }
+ dst = tmp;
+ if(len & 1)
+ *dst++ = c2b(*src++);
+ while(*src) {
+ *dst = c2b(*src++) << 4;
+ *dst++ |= c2b(*src++);
+ }
+ len = dst - tmp;
+ } else
+ if(strncasecmp("0b", src , 2) == 0) {
+ // base64
+ unsigned char b6;
+
+ src += 2;
+ len = strlen(src) / 4 * 3;
+ if((tmp = malloc(len)) == NULL) {
+ // XXX: print some error?
+ return 0;
+ }
+ dst = tmp;
+ i = 0;
+ while(*src && ((b6 = c64tobin(*src++)) != 64)) {
+ switch(i % 4) {
+ case 0:
+ *dst = b6 << 2;
+ break;
+ case 1:
+ *dst++ |= b6 >> 4;
+ *dst = b6 << 4;
+ break;
+ case 2:
+ *dst++ |= b6 >> 2;
+ *dst = b6 << 6;
+ break;
+ case 3:
+ *dst++ |= b6;
+ break;
+ }
+ i++;
+ }
+ len = dst - tmp;
+ }
+ else {
+ /*
+ | assume it to be an ascii string, so just copy it
+ */
+ len = strlen(str);
+ if((tmp = malloc(len)) == NULL)
+ return 0;
+ dst = tmp;
+ src = str;
+ while(*src)
+ *dst++ = *src++;
+ }
+
+ *rsp = tmp;
+ return len;
+}
+
+char *
+bin2str(char *encoding, unsigned char *md, int blen)
+{
+ int len;
+ char *dst, *ds;
+ unsigned char *cp;
+
+ if(strncasecmp(encoding, "0x", 2) == 0) {
+ char ofmt[5];
+
+ len = blen * 2;
+ dst = malloc(len + 3);
+ strcpy(dst, encoding);
+ ds = dst + 2;
+ cp = md;
+ sprintf(ofmt, "%%02%c", encoding[1]);
+ while(blen-- > 0) {
+ sprintf(ds, ofmt, *cp++);
+ ds += 2;
+ }
+ *ds = 0;
+ return dst;
+ }
+ if(strncasecmp(encoding, "0b", 2) == 0) {
+ int i, b6;
+
+ len = (blen + 2) * 4 / 3;
+ dst = malloc(len + 3);
+ strcpy(dst, encoding);
+ ds = dst + 2;
+ cp = md;
+ b6 = 0; // to keep compiler happy.
+ for(i = 0; i < blen; i++) {
+ switch(i % 3) {
+ case 0:
+ *ds++ = base64[*cp >> 2];
+ b6 = (*cp & 0x3) << 4;
+ break;
+ case 1:
+ b6 += (*cp >> 4);
+ *ds++ = base64[b6];
+ b6 = (*cp & 0xf) << 2;
+ break;
+ case 2:
+ b6 += (*cp >> 6);
+ *ds++ = base64[b6];
+ *ds++ = base64[*cp & 0x3f];
+ }
+ cp++;
+ }
+ switch(blen % 3) {
+ case 0:
+ break;
+ case 1:
+ *ds++ = base64[b6];
+ *ds++ = '=';
+ *ds++ = '=';
+ break;
+ case 2:
+ *ds++ = base64[b6];
+ *ds++ = '=';
+ break;
+ }
+
+ *ds = 0;
+ return dst;
+ }
+
+ return NULL;
+}
diff --git a/sbin/iscontrol/pdu.c b/sbin/iscontrol/pdu.c
new file mode 100644
index 0000000..7ce90ea
--- /dev/null
+++ b/sbin/iscontrol/pdu.c
@@ -0,0 +1,176 @@
+/*-
+ * Copyright (c) 2005-2010 Daniel Braniss <danny@cs.huji.ac.il>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+/*
+ | $Id: pdu.c,v 2.2 2006/12/01 09:11:56 danny Exp danny $
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/uio.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <camlib.h>
+
+#include <dev/iscsi_initiator/iscsi.h>
+#include "iscontrol.h"
+
+static void pukeText(char *it, pdu_t *pp);
+
+int
+xmitpdu(isess_t *sess, pdu_t *pp)
+{
+ if(ioctl(sess->fd, ISCSISEND, pp)) {
+ perror("xmitpdu");
+ return -1;
+ }
+ if(vflag)
+ pukeText("I-", pp);
+
+ return 0;
+}
+
+int
+recvpdu(isess_t *sess, pdu_t *pp)
+{
+ if(ioctl(sess->fd, ISCSIRECV, pp)) {
+ perror("recvpdu");
+ return -1;
+ }
+ // XXX: return error if truncated via
+ // the FUDGE factor.
+ if(vflag)
+ pukeText("T-", pp);
+
+ return 0;
+}
+
+int
+sendPDU(isess_t *sess, pdu_t *pp, handler_t *hdlr)
+{
+ if(xmitpdu(sess, pp))
+ return 0;
+ if(hdlr) {
+ int res;
+
+ pp->ahs_size = 8 * 1024;
+ if((pp->ahs_addr = malloc(pp->ahs_size)) == NULL) {
+ fprintf(stderr, "out of mem!");
+ return -1;
+ }
+ pp->ds_size = 0;
+ if((res = recvpdu(sess, pp)) != 0) {
+ fprintf(stderr, "recvpdu failed\n");
+ return res;
+ }
+ res = hdlr(sess, pp);
+ freePDU(pp);
+ return res;
+ }
+ return 1;
+}
+
+
+#define FUDGE (512 * 8)
+/*
+ | We use the same memory for the response
+ | so make enough room ...
+ | XXX: must find a better way.
+ */
+int
+addText(pdu_t *pp, char *fmt, ...)
+{
+ u_int len;
+ char *str;
+ va_list ap;
+
+ va_start(ap, fmt);
+ len = vasprintf(&str, fmt, ap) + 1;
+ if((pp->ds_len + len) > 0xffffff) {
+ printf("ds overflow\n");
+ free(str);
+ return 0;
+ }
+
+ if((pp->ds_len + len) > pp->ds_size) {
+ u_char *np;
+
+ np = realloc(pp->ds_addr, pp->ds_size + len + FUDGE);
+ if(np == NULL) {
+ free(str);
+ //XXX: out of memory!
+ return -1;
+ }
+ pp->ds_addr = np;
+ pp->ds_size += len + FUDGE;
+ }
+ memcpy(pp->ds_addr + pp->ds_len, str, len);
+ pp->ds_len += len;
+ free(str);
+ return len;
+}
+
+void
+freePDU(pdu_t *pp)
+{
+ if(pp->ahs_size)
+ free(pp->ahs_addr);
+ if(pp->ds_size)
+ free(pp->ds_addr);
+ bzero(&pp->ipdu, sizeof(union ipdu_u));
+ pp->ahs_addr = NULL;
+ pp->ds_addr = NULL;
+ pp->ahs_size = 0;
+ pp->ds_size = pp->ds_len = 0;
+}
+
+static void
+pukeText(char *it, pdu_t *pp)
+{
+ char *ptr;
+ int cmd;
+ size_t len, n;
+
+ len = pp->ds_len;
+ ptr = (char *)pp->ds_addr;
+ cmd = pp->ipdu.bhs.opcode;
+
+ printf("%s: cmd=0x%x len=%d\n", it, cmd, (int)len);
+ while(len > 0) {
+ printf("\t%s\n", ptr);
+ n = strlen(ptr) + 1;
+ len -= n;
+ ptr += n;
+ }
+}
diff --git a/sbin/kldconfig/Makefile b/sbin/kldconfig/Makefile
new file mode 100644
index 0000000..a478c4b
--- /dev/null
+++ b/sbin/kldconfig/Makefile
@@ -0,0 +1,32 @@
+#
+# Copyright (c) 2001 Peter Pentchev
+# 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.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# $FreeBSD$
+#
+
+PROG= kldconfig
+MAN= kldconfig.8
+
+.include <bsd.prog.mk>
diff --git a/sbin/kldconfig/kldconfig.8 b/sbin/kldconfig/kldconfig.8
new file mode 100644
index 0000000..3cc288f
--- /dev/null
+++ b/sbin/kldconfig/kldconfig.8
@@ -0,0 +1,108 @@
+.\"
+.\" Copyright (c) 2001 Peter Pentchev
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd June 15, 2001
+.Dt KLDCONFIG 8
+.Os
+.Sh NAME
+.Nm kldconfig
+.Nd display or modify the kernel module search path
+.Sh SYNOPSIS
+.Nm
+.Op Fl dfimnUv
+.Op Fl S Ar sysctlname
+.Op Ar path ...
+.Nm
+.Fl r
+.Sh DESCRIPTION
+The
+.Nm
+utility
+displays or modifies the search path used by the kernel when loading modules
+using the
+.Xr kldload 8
+utility or the
+.Xr kldload 2
+syscall.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl d
+Remove the specified paths from the module search path.
+.It Fl f
+Do not display a diagnostic message if a path specified for adding is
+already present in the search path, or if a path specified for removing
+is not present in the search path.
+This may be useful in startup/shutdown scripts for adding a path to
+a file system which is still not mounted, or in shutdown scripts for
+unconditionally removing a path that may have been added during startup.
+.It Fl i
+Add the specified paths to the beginning of the search path, not to the end.
+This option can only be used when adding paths.
+.It Fl m
+Instead of replacing the module search path with the set of paths
+specified,
+.Dq merge
+in the new entries.
+.It Fl n
+Do not actually change the module search path.
+.It Fl r
+Display the current search path.
+This option cannot be used if any paths are also specified.
+.It Fl S Ar sysctlname
+Specify the sysctl name to use instead of the default
+.Va kern.module_path .
+.It Fl U
+.Dq Unique-ify
+the current search path - if any of the directories is repeated one
+or more times, only the first occurrence remains.
+This option implies
+.Fl m .
+.It Fl v
+Verbose output: display the new module search path.
+If the path has been changed, and the
+.Fl v
+flag is specified more than once, the old path is displayed as well.
+.El
+.Sh FILES
+.Bl -tag -width indent
+.It Pa /boot/kernel , /boot/modules , /modules
+The default module search path used by the kernel.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr kldload 2 ,
+.Xr kldload 8 ,
+.Xr sysctl 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 4.4 .
+.Sh AUTHORS
+.An Peter Pentchev Aq Mt roam@FreeBSD.org
diff --git a/sbin/kldconfig/kldconfig.c b/sbin/kldconfig/kldconfig.c
new file mode 100644
index 0000000..30dc876
--- /dev/null
+++ b/sbin/kldconfig/kldconfig.c
@@ -0,0 +1,416 @@
+/*
+ * Copyright (c) 2001 Peter Pentchev
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/sysctl.h>
+
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/* the default sysctl name */
+#define PATHCTL "kern.module_path"
+
+/* queue structure for the module path broken down into components */
+TAILQ_HEAD(pathhead, pathentry);
+struct pathentry {
+ char *path;
+ TAILQ_ENTRY(pathentry) next;
+};
+
+/* the Management Information Base entries for the search path sysctl */
+static int mib[5];
+static size_t miblen;
+/* the sysctl name, defaults to PATHCTL */
+static char *pathctl;
+/* the sysctl value - the current module search path */
+static char *modpath;
+/* flag whether user actions require changing the sysctl value */
+static int changed;
+
+/* Top-level path management functions */
+static void addpath(struct pathhead *, char *, int, int);
+static void rempath(struct pathhead *, char *, int, int);
+static void showpath(struct pathhead *);
+
+/* Low-level path management functions */
+static char *qstring(struct pathhead *);
+
+/* sysctl-related functions */
+static void getmib(void);
+static void getpath(void);
+static void parsepath(struct pathhead *, char *, int);
+static void setpath(struct pathhead *);
+
+static void usage(void);
+
+/* Get the MIB entry for our sysctl */
+static void
+getmib(void)
+{
+
+ /* have we already fetched it? */
+ if (miblen != 0)
+ return;
+
+ miblen = sizeof(mib) / sizeof(mib[0]);
+ if (sysctlnametomib(pathctl, mib, &miblen) != 0)
+ err(1, "sysctlnametomib(%s)", pathctl);
+}
+
+/* Get the current module search path */
+static void
+getpath(void)
+{
+ char *path;
+ size_t sz;
+
+ if (modpath != NULL) {
+ free(modpath);
+ modpath = NULL;
+ }
+
+ if (miblen == 0)
+ getmib();
+ if (sysctl(mib, miblen, NULL, &sz, NULL, 0) == -1)
+ err(1, "getting path: sysctl(%s) - size only", pathctl);
+ if ((path = malloc(sz + 1)) == NULL) {
+ errno = ENOMEM;
+ err(1, "allocating %lu bytes for the path",
+ (unsigned long)sz+1);
+ }
+ if (sysctl(mib, miblen, path, &sz, NULL, 0) == -1)
+ err(1, "getting path: sysctl(%s)", pathctl);
+ modpath = path;
+}
+
+/* Set the module search path after changing it */
+static void
+setpath(struct pathhead *pathq)
+{
+ char *newpath;
+
+ if (miblen == 0)
+ getmib();
+ if ((newpath = qstring(pathq)) == NULL) {
+ errno = ENOMEM;
+ err(1, "building path string");
+ }
+ if (sysctl(mib, miblen, NULL, NULL, newpath, strlen(newpath)+1) == -1)
+ err(1, "setting path: sysctl(%s)", pathctl);
+
+ if (modpath != NULL)
+ free(modpath);
+ modpath = newpath;
+}
+
+/* Add/insert a new component to the module search path */
+static void
+addpath(struct pathhead *pathq, char *path, int force, int insert)
+{
+ struct pathentry *pe, *pskip;
+ char pathbuf[MAXPATHLEN+1];
+ size_t len;
+ static unsigned added = 0;
+ unsigned i;
+
+ /*
+ * If the path exists, use it; otherwise, take the user-specified
+ * path at face value - may be a removed directory.
+ */
+ if (realpath(path, pathbuf) == NULL)
+ strlcpy(pathbuf, path, sizeof(pathbuf));
+
+ len = strlen(pathbuf);
+ /* remove a terminating slash if present */
+ if ((len > 0) && (pathbuf[len-1] == '/'))
+ pathbuf[--len] = '\0';
+
+ /* is it already in there? */
+ TAILQ_FOREACH(pe, pathq, next)
+ if (!strcmp(pe->path, pathbuf))
+ break;
+ if (pe != NULL) {
+ if (force)
+ return;
+ errx(1, "already in the module search path: %s", pathbuf);
+ }
+
+ /* OK, allocate and add it. */
+ if (((pe = malloc(sizeof(*pe))) == NULL) ||
+ ((pe->path = strdup(pathbuf)) == NULL)) {
+ errno = ENOMEM;
+ err(1, "allocating path component");
+ }
+ if (!insert) {
+ TAILQ_INSERT_TAIL(pathq, pe, next);
+ } else {
+ for (i = 0, pskip = TAILQ_FIRST(pathq); i < added; i++)
+ pskip = TAILQ_NEXT(pskip, next);
+ if (pskip != NULL)
+ TAILQ_INSERT_BEFORE(pskip, pe, next);
+ else
+ TAILQ_INSERT_TAIL(pathq, pe, next);
+ added++;
+ }
+ changed = 1;
+}
+
+/* Remove a path component from the module search path */
+static void
+rempath(struct pathhead *pathq, char *path, int force, int insert __unused)
+{
+ char pathbuf[MAXPATHLEN+1];
+ struct pathentry *pe;
+ size_t len;
+
+ /* same logic as in addpath() */
+ if (realpath(path, pathbuf) == NULL)
+ strlcpy(pathbuf, path, sizeof(pathbuf));
+
+ len = strlen(pathbuf);
+ /* remove a terminating slash if present */
+ if ((len > 0) && (pathbuf[len-1] == '/'))
+ pathbuf[--len] = '\0';
+
+ /* Is it in there? */
+ TAILQ_FOREACH(pe, pathq, next)
+ if (!strcmp(pe->path, pathbuf))
+ break;
+ if (pe == NULL) {
+ if (force)
+ return;
+ errx(1, "not in module search path: %s", pathbuf);
+ }
+
+ /* OK, remove it now.. */
+ TAILQ_REMOVE(pathq, pe, next);
+ changed = 1;
+}
+
+/* Display the retrieved module search path */
+static void
+showpath(struct pathhead *pathq)
+{
+ char *s;
+
+ if ((s = qstring(pathq)) == NULL) {
+ errno = ENOMEM;
+ err(1, "building path string");
+ }
+ printf("%s\n", s);
+ free(s);
+}
+
+/* Break a string down into path components, store them into a queue */
+static void
+parsepath(struct pathhead *pathq, char *path, int uniq)
+{
+ char *p;
+ struct pathentry *pe;
+
+ while ((p = strsep(&path, ";")) != NULL)
+ if (!uniq) {
+ if (((pe = malloc(sizeof(*pe))) == NULL) ||
+ ((pe->path = strdup(p)) == NULL)) {
+ errno = ENOMEM;
+ err(1, "allocating path element");
+ }
+ TAILQ_INSERT_TAIL(pathq, pe, next);
+ } else {
+ addpath(pathq, p, 1, 0);
+ }
+}
+
+/* Recreate a path string from a components queue */
+static char *
+qstring(struct pathhead *pathq)
+{
+ char *s, *p;
+ struct pathentry *pe;
+
+ s = strdup("");
+ TAILQ_FOREACH(pe, pathq, next) {
+ asprintf(&p, "%s%s%s",
+ s, pe->path, (TAILQ_NEXT(pe, next) != NULL? ";": ""));
+ free(s);
+ if (p == NULL)
+ return (NULL);
+ s = p;
+ }
+
+ return (s);
+}
+
+/* Usage message */
+static void
+usage(void)
+{
+
+ fprintf(stderr, "%s\n%s\n",
+ "usage:\tkldconfig [-dfimnUv] [-S sysctlname] [path ...]",
+ "\tkldconfig -r");
+ exit(1);
+}
+
+/* Main function */
+int
+main(int argc, char *argv[])
+{
+ /* getopt() iterator */
+ int c;
+ /* iterator over argv[] path components */
+ int i;
+ /* Command-line flags: */
+ /* "-f" - no diagnostic messages */
+ int fflag;
+ /* "-i" - insert before the first element */
+ int iflag;
+ /* "-m" - merge into the existing path, do not replace it */
+ int mflag;
+ /* "-n" - do not actually set the new module path */
+ int nflag;
+ /* "-r" - print out the current search path */
+ int rflag;
+ /* "-U" - remove duplicate values from the path */
+ int uniqflag;
+ /* "-v" - verbose operation (currently a no-op) */
+ int vflag;
+ /* The higher-level function to call - add/remove */
+ void (*act)(struct pathhead *, char *, int, int);
+ /* The original path */
+ char *origpath;
+ /* The module search path broken down into components */
+ struct pathhead pathq;
+
+ fflag = iflag = mflag = nflag = rflag = uniqflag = vflag = 0;
+ act = addpath;
+ origpath = NULL;
+ if ((pathctl = strdup(PATHCTL)) == NULL) {
+ /* this is just too paranoid ;) */
+ errno = ENOMEM;
+ err(1, "initializing sysctl name %s", PATHCTL);
+ }
+
+ /* If no arguments and no options are specified, force '-m' */
+ if (argc == 1)
+ mflag = 1;
+
+ while ((c = getopt(argc, argv, "dfimnrS:Uv")) != -1)
+ switch (c) {
+ case 'd':
+ if (iflag || mflag)
+ usage();
+ act = rempath;
+ break;
+ case 'f':
+ fflag = 1;
+ break;
+ case 'i':
+ if (act != addpath)
+ usage();
+ iflag = 1;
+ break;
+ case 'm':
+ if (act != addpath)
+ usage();
+ mflag = 1;
+ break;
+ case 'n':
+ nflag = 1;
+ break;
+ case 'r':
+ rflag = 1;
+ break;
+ case 'S':
+ free(pathctl);
+ if ((pathctl = strdup(optarg)) == NULL) {
+ errno = ENOMEM;
+ err(1, "sysctl name %s", optarg);
+ }
+ break;
+ case 'U':
+ uniqflag = 1;
+ break;
+ case 'v':
+ vflag++;
+ break;
+ default:
+ usage();
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ /* The '-r' flag cannot be used when paths are also specified */
+ if (rflag && (argc > 0))
+ usage();
+
+ TAILQ_INIT(&pathq);
+
+ /* Retrieve and store the path from the sysctl value */
+ getpath();
+ if ((origpath = strdup(modpath)) == NULL) {
+ errno = ENOMEM;
+ err(1, "saving the original search path");
+ }
+
+ /*
+ * Break down the path into the components queue if:
+ * - we are NOT adding paths, OR
+ * - the 'merge' flag is specified, OR
+ * - the 'print only' flag is specified, OR
+ * - the 'unique' flag is specified.
+ */
+ if ((act != addpath) || mflag || rflag || uniqflag)
+ parsepath(&pathq, modpath, uniqflag);
+ else if (modpath[0] != '\0')
+ changed = 1;
+
+ /* Process the path arguments */
+ for (i = 0; i < argc; i++)
+ act(&pathq, argv[i], fflag, iflag);
+
+ if (changed && !nflag)
+ setpath(&pathq);
+
+ if (rflag || (changed && vflag)) {
+ if (changed && (vflag > 1))
+ printf("%s -> ", origpath);
+ showpath(&pathq);
+ }
+
+ return (0);
+}
diff --git a/sbin/kldload/Makefile b/sbin/kldload/Makefile
new file mode 100644
index 0000000..05ad5ba
--- /dev/null
+++ b/sbin/kldload/Makefile
@@ -0,0 +1,32 @@
+#
+# Copyright (c) 1997 Doug Rabson
+# 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.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# $FreeBSD$
+#
+
+PROG= kldload
+MAN= kldload.8
+
+.include <bsd.prog.mk>
diff --git a/sbin/kldload/kldload.8 b/sbin/kldload/kldload.8
new file mode 100644
index 0000000..b84b863
--- /dev/null
+++ b/sbin/kldload/kldload.8
@@ -0,0 +1,129 @@
+.\"
+.\" Copyright (c) 1997 Doug Rabson
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd March 18, 2012
+.Dt KLDLOAD 8
+.Os
+.Sh NAME
+.Nm kldload
+.Nd load a file into the kernel
+.Sh SYNOPSIS
+.Nm
+.Op Fl nqv
+.Ar
+.Sh DESCRIPTION
+The
+.Nm
+utility loads
+.Ar file Ns Pa .ko
+into the kernel using the kernel linker.
+Note that if multiple modules are specified then an attempt will
+be made to load them all, even if some fail.
+The
+.Pa .ko
+extension name is not mandatory when loading a given module
+using
+.Nm .
+It does not hurt to specify it though.
+.Pp
+If a bare filename is requested it will only be loaded if it is found within
+the module path as defined by the sysctl
+.Va kern.module_path .
+To load a module from the current directory it must be specified as a full or
+relative path.
+The
+.Nm
+utility will warn if a module is requested as a bare filename and is present
+in the current directory.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl n
+Do not try to load module if already loaded.
+.It Fl v
+Be more verbose.
+.It Fl q
+Silence any extraneous warnings.
+.El
+.Sh NOTES
+The kernel security level settings may prevent a module from being
+loaded or unloaded by giving
+.Em "Operation not permitted" .
+.Sh FILES
+.Bl -tag -width /boot/kernel -compact
+.It Pa /boot/kernel
+directory containing loadable modules.
+Modules must have an extension of
+.Pa .ko .
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh EXAMPLES
+To load by module name:
+.Bd -literal -offset indent
+\*[Gt] kldload foo
+.Ed
+.Pp
+To load by file name within the module path:
+.Bd -literal -offset indent
+\*[Gt] kldload foo.ko
+.Ed
+.Pp
+To load by relative path:
+.Bd -literal -offset indent
+\*[Gt] kldload ./foo.ko
+.Ed
+.Pp
+To load by full path:
+.Bd -literal -offset indent
+\*[Gt] kldload /boot/kernel/foo.ko
+.Ed
+.Sh AUTOMATICALLY LOADING MODULES
+Some modules (pf, ipfw, ipf, etc.) may be automatically loaded at boot
+time when the corresponding
+.Xr rc.conf 5
+statement is used.
+Modules may also be auto-loaded through their addition to
+.Xr loader.conf 5 .
+.Sh SEE ALSO
+.Xr kldload 2 ,
+.Xr loader.conf 5 ,
+.Xr rc.conf 5 ,
+.Xr security 7 ,
+.Xr kldconfig 8 ,
+.Xr kldstat 8 ,
+.Xr kldunload 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 3.0 ,
+replacing the
+.Nm lkm
+interface.
+.Sh AUTHORS
+.An Doug Rabson Aq Mt dfr@FreeBSD.org
diff --git a/sbin/kldload/kldload.c b/sbin/kldload/kldload.c
new file mode 100644
index 0000000..3891f33
--- /dev/null
+++ b/sbin/kldload/kldload.c
@@ -0,0 +1,214 @@
+/*-
+ * Copyright (c) 1997 Doug Rabson
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/linker.h>
+#include <sys/sysctl.h>
+#include <sys/stat.h>
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+#define PATHCTL "kern.module_path"
+
+static int path_check(const char *, int);
+static void usage(void);
+
+/*
+ * Check to see if the requested module is specified as a filename with no
+ * path. If so and if a file by the same name exists in the module path,
+ * warn the user that the module in the path will be used in preference.
+ */
+static int
+path_check(const char *kldname, int quiet)
+{
+ int mib[5], found;
+ size_t miblen, pathlen;
+ char kldpath[MAXPATHLEN];
+ char *path, *tmppath, *element;
+ struct stat sb;
+ dev_t dev;
+ ino_t ino;
+
+ if (strchr(kldname, '/') != NULL) {
+ return (0);
+ }
+ if (strstr(kldname, ".ko") == NULL) {
+ return (0);
+ }
+ if (stat(kldname, &sb) != 0) {
+ return (0);
+ }
+
+ found = 0;
+ dev = sb.st_dev;
+ ino = sb.st_ino;
+
+ miblen = sizeof(mib) / sizeof(mib[0]);
+ if (sysctlnametomib(PATHCTL, mib, &miblen) != 0) {
+ err(1, "sysctlnametomib(%s)", PATHCTL);
+ }
+ if (sysctl(mib, miblen, NULL, &pathlen, NULL, 0) == -1) {
+ err(1, "getting path: sysctl(%s) - size only", PATHCTL);
+ }
+ path = malloc(pathlen + 1);
+ if (path == NULL) {
+ err(1, "allocating %lu bytes for the path",
+ (unsigned long)pathlen + 1);
+ }
+ if (sysctl(mib, miblen, path, &pathlen, NULL, 0) == -1) {
+ err(1, "getting path: sysctl(%s)", PATHCTL);
+ }
+ tmppath = path;
+
+ while ((element = strsep(&tmppath, ";")) != NULL) {
+ strlcpy(kldpath, element, MAXPATHLEN);
+ if (kldpath[strlen(kldpath) - 1] != '/') {
+ strlcat(kldpath, "/", MAXPATHLEN);
+ }
+ strlcat(kldpath, kldname, MAXPATHLEN);
+
+ if (stat(kldpath, &sb) == -1) {
+ continue;
+ }
+
+ found = 1;
+
+ if (sb.st_dev != dev || sb.st_ino != ino) {
+ if (!quiet) {
+ warnx("%s will be loaded from %s, not the "
+ "current directory", kldname, element);
+ }
+ break;
+ } else if (sb.st_dev == dev && sb.st_ino == ino) {
+ break;
+ }
+ }
+
+ free(path);
+
+ if (!found) {
+ if (!quiet) {
+ warnx("%s is not in the module path", kldname);
+ }
+ return (-1);
+ }
+
+ return (0);
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: kldload [-nqv] file ...\n");
+ exit(1);
+}
+
+int
+main(int argc, char** argv)
+{
+ int c;
+ int errors;
+ int fileid;
+ int verbose;
+ int quiet;
+ int check_loaded;
+
+ errors = 0;
+ verbose = 0;
+ quiet = 0;
+ check_loaded = 0;
+
+ while ((c = getopt(argc, argv, "nqv")) != -1) {
+ switch (c) {
+ case 'q':
+ quiet = 1;
+ verbose = 0;
+ break;
+ case 'v':
+ verbose = 1;
+ quiet = 0;
+ break;
+ case 'n':
+ check_loaded = 1;
+ break;
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc == 0)
+ usage();
+
+ while (argc-- != 0) {
+ if (path_check(argv[0], quiet) == 0) {
+ fileid = kldload(argv[0]);
+ if (fileid < 0) {
+ if (check_loaded != 0 && errno == EEXIST) {
+ if (verbose)
+ printf("%s is already "
+ "loaded\n", argv[0]);
+ } else {
+ switch (errno) {
+ case EEXIST:
+ warnx("can't load %s: module "
+ "already loaded or "
+ "in kernel", argv[0]);
+ break;
+ case ENOEXEC:
+ warnx("an error occurred while "
+ "loading the module. "
+ "Please check dmesg(8) for "
+ "more details.");
+ break;
+ default:
+ warn("can't load %s", argv[0]);
+ break;
+ }
+ errors++;
+ }
+ } else {
+ if (verbose)
+ printf("Loaded %s, id=%d\n", argv[0],
+ fileid);
+ }
+ } else {
+ errors++;
+ }
+ argv++;
+ }
+
+ return (errors ? 1 : 0);
+}
diff --git a/sbin/kldstat/Makefile b/sbin/kldstat/Makefile
new file mode 100644
index 0000000..e4145d7
--- /dev/null
+++ b/sbin/kldstat/Makefile
@@ -0,0 +1,32 @@
+#
+# Copyright (c) 1997 Doug Rabson
+# 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.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# $FreeBSD$
+#
+
+PROG= kldstat
+MAN= kldstat.8
+
+.include <bsd.prog.mk>
diff --git a/sbin/kldstat/kldstat.8 b/sbin/kldstat/kldstat.8
new file mode 100644
index 0000000..b892ef6
--- /dev/null
+++ b/sbin/kldstat/kldstat.8
@@ -0,0 +1,77 @@
+.\"
+.\" Copyright (c) 1997 Doug Rabson
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd January 22, 2014
+.Dt KLDSTAT 8
+.Os
+.Sh NAME
+.Nm kldstat
+.Nd display status of dynamic kernel linker
+.Sh SYNOPSIS
+.Nm
+.Op Fl q
+.Op Fl v
+.Op Fl i Ar id
+.Op Fl n Ar filename
+.Nm
+.Op Fl q
+.Op Fl m Ar modname
+.Sh DESCRIPTION
+The
+.Nm
+utility displays the status of any files dynamically linked into the
+kernel.
+.Pp
+The following options are available:
+.Bl -tag -width indentXX
+.It Fl v
+Be more verbose.
+.It Fl i Ar id
+Display the status of only the file with this ID.
+.It Fl n Ar filename
+Display the status of only the file with this filename.
+.It Fl q
+Only check if module is loaded or compiled into the kernel.
+.It Fl m Ar modname
+Display the status of only the module with this modname.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr kldstat 2 ,
+.Xr kldload 8 ,
+.Xr kldunload 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 3.0 ,
+replacing the
+.Nm lkm
+interface.
+.Sh AUTHORS
+.An Doug Rabson Aq Mt dfr@FreeBSD.org
diff --git a/sbin/kldstat/kldstat.c b/sbin/kldstat/kldstat.c
new file mode 100644
index 0000000..8785c00
--- /dev/null
+++ b/sbin/kldstat/kldstat.c
@@ -0,0 +1,166 @@
+/*-
+ * Copyright (c) 1997 Doug Rabson
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/module.h>
+#include <sys/linker.h>
+
+#define POINTER_WIDTH ((int)(sizeof(void *) * 2 + 2))
+
+static void
+printmod(int modid)
+{
+ struct module_stat stat;
+
+ stat.version = sizeof(struct module_stat);
+ if (modstat(modid, &stat) < 0)
+ warn("can't stat module id %d", modid);
+ else
+ printf("\t\t%2d %s\n", stat.id, stat.name);
+}
+
+static void
+printfile(int fileid, int verbose)
+{
+ struct kld_file_stat stat;
+ int modid;
+
+ stat.version = sizeof(struct kld_file_stat);
+ if (kldstat(fileid, &stat) < 0)
+ err(1, "can't stat file id %d", fileid);
+ else
+ printf("%2d %4d %p %-8zx %s",
+ stat.id, stat.refs, stat.address, stat.size,
+ stat.name);
+
+ if (verbose) {
+ printf(" (%s)\n", stat.pathname);
+ printf("\tContains modules:\n");
+ printf("\t\tId Name\n");
+ for (modid = kldfirstmod(fileid); modid > 0;
+ modid = modfnext(modid))
+ printmod(modid);
+ } else
+ printf("\n");
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: kldstat [-q] [-v] [-i id] [-n filename]\n");
+ fprintf(stderr, " kldstat [-q] [-m modname]\n");
+ exit(1);
+}
+
+int
+main(int argc, char** argv)
+{
+ int c;
+ int verbose = 0;
+ int fileid = 0;
+ int quiet = 0;
+ char* filename = NULL;
+ char* modname = NULL;
+ char* p;
+
+ while ((c = getopt(argc, argv, "i:m:n:qv")) != -1)
+ switch (c) {
+ case 'i':
+ fileid = (int)strtoul(optarg, &p, 10);
+ if (*p != '\0')
+ usage();
+ break;
+ case 'm':
+ modname = optarg;
+ break;
+ case 'n':
+ filename = optarg;
+ break;
+ case 'q':
+ quiet = 1;
+ break;
+ case 'v':
+ verbose = 1;
+ break;
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 0)
+ usage();
+
+ if (modname != NULL) {
+ int modid;
+ struct module_stat stat;
+
+ if ((modid = modfind(modname)) < 0) {
+ if (!quiet)
+ warn("can't find module %s", modname);
+ return 1;
+ } else if (quiet) {
+ return 0;
+ }
+
+ stat.version = sizeof(struct module_stat);
+ if (modstat(modid, &stat) < 0)
+ warn("can't stat module id %d", modid);
+ else {
+ printf("Id Refs Name\n");
+ printf("%3d %4d %s\n", stat.id, stat.refs, stat.name);
+ }
+
+ return 0;
+ }
+
+ if (filename != NULL) {
+ if ((fileid = kldfind(filename)) < 0) {
+ if (!quiet)
+ warn("can't find file %s", filename);
+ return 1;
+ } else if (quiet) {
+ return 0;
+ }
+ }
+
+ printf("Id Refs Address%*c Size Name\n", POINTER_WIDTH - 7, ' ');
+ if (fileid != 0)
+ printfile(fileid, verbose);
+ else
+ for (fileid = kldnext(0); fileid > 0; fileid = kldnext(fileid))
+ printfile(fileid, verbose);
+
+ return 0;
+}
diff --git a/sbin/kldunload/Makefile b/sbin/kldunload/Makefile
new file mode 100644
index 0000000..1a25a02
--- /dev/null
+++ b/sbin/kldunload/Makefile
@@ -0,0 +1,32 @@
+#
+# Copyright (c) 1997 Doug Rabson
+# 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.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# $FreeBSD$
+#
+
+PROG= kldunload
+MAN= kldunload.8
+
+.include <bsd.prog.mk>
diff --git a/sbin/kldunload/kldunload.8 b/sbin/kldunload/kldunload.8
new file mode 100644
index 0000000..5e371e6
--- /dev/null
+++ b/sbin/kldunload/kldunload.8
@@ -0,0 +1,81 @@
+.\"
+.\" Copyright (c) 1997 Doug Rabson
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd February 27, 2006
+.Dt KLDUNLOAD 8
+.Os
+.Sh NAME
+.Nm kldunload
+.Nd unload a file from the kernel
+.Sh SYNOPSIS
+.Nm
+.Op Fl fv
+.Fl i Ar id ...
+.Nm
+.Op Fl fv
+.Op Fl n
+.Ar name ...
+.Sh DESCRIPTION
+The
+.Nm
+utility unloads a file which was previously loaded with
+.Xr kldload 8 .
+.Pp
+The following options are available:
+.Bl -tag -width ".Fl n Ar name"
+.It Fl f
+Force the unload.
+This ignores error returns to
+.Dv MOD_QUIESCE
+from the module and implies
+that the module should be unloaded even if it is currently in use.
+The users are left to cope as best they can.
+.It Fl v
+Be more verbose.
+.It Fl i Ar id
+Unload the file with this ID.
+.It Fl n Ar name
+Unload the file with this name.
+.It Ar name
+Unload the file with this name.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr kldunload 2 ,
+.Xr kldload 8 ,
+.Xr kldstat 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 3.0 ,
+replacing the
+.Nm lkm
+interface.
+.Sh AUTHORS
+.An Doug Rabson Aq Mt dfr@FreeBSD.org
diff --git a/sbin/kldunload/kldunload.c b/sbin/kldunload/kldunload.c
new file mode 100644
index 0000000..8a3ea61
--- /dev/null
+++ b/sbin/kldunload/kldunload.c
@@ -0,0 +1,117 @@
+/*-
+ * Copyright (c) 1997 Doug Rabson
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/linker.h>
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: kldunload [-fv] -i id ...\n");
+ fprintf(stderr, " kldunload [-fv] [-n] name ...\n");
+ exit(EX_USAGE);
+}
+
+#define OPT_NULL 0x00
+#define OPT_ID 0x01
+#define OPT_VERBOSE 0x02
+#define OPT_FORCE 0x04
+
+int
+main(int argc, char** argv)
+{
+ struct kld_file_stat stat;
+ int c, fileid, force, opt;
+ char *filename;
+
+ filename = NULL;
+ opt = OPT_NULL;
+
+ while ((c = getopt(argc, argv, "finv")) != -1) {
+ switch (c) {
+ case 'f':
+ opt |= OPT_FORCE;
+ break;
+ case 'i':
+ opt |= OPT_ID;
+ break;
+ case 'n':
+ /*
+ * XXX: For backward compatibility. Currently does
+ * nothing
+ */
+ break;
+ case 'v':
+ opt |= OPT_VERBOSE;
+ break;
+ default:
+ usage();
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc == 0)
+ usage();
+
+ while ((filename = *argv++) != NULL) {
+ if (opt & OPT_ID) {
+ fileid = atoi(filename);
+ if (fileid < 0)
+ errx(EXIT_FAILURE, "Invalid ID %s", optarg);
+ } else {
+ if ((fileid = kldfind(filename)) < 0)
+ errx(EXIT_FAILURE, "can't find file %s",
+ filename);
+ }
+ if (opt & OPT_VERBOSE) {
+ stat.version = sizeof(stat);
+ if (kldstat(fileid, &stat) < 0)
+ err(EXIT_FAILURE, "can't stat file");
+ (void) printf("Unloading %s, id=%d\n", stat.name,
+ fileid);
+ }
+ if (opt & OPT_FORCE)
+ force = LINKER_UNLOAD_FORCE;
+ else
+ force = LINKER_UNLOAD_NORMAL;
+
+ if (kldunloadf(fileid, force) < 0)
+ err(EXIT_FAILURE, "can't unload file");
+ }
+
+ return (EXIT_SUCCESS);
+}
diff --git a/sbin/ldconfig/Makefile b/sbin/ldconfig/Makefile
new file mode 100644
index 0000000..15c3808
--- /dev/null
+++ b/sbin/ldconfig/Makefile
@@ -0,0 +1,11 @@
+# $FreeBSD$
+
+PROG= ldconfig
+SRCS= elfhints.c ldconfig.c shlib.c support.c
+LDDIR?= ${.CURDIR}/../../libexec/rtld-aout
+CFLAGS+=-I${LDDIR} -DFREEBSD_AOUT
+MAN= ldconfig.8
+
+.PATH: ${LDDIR}
+
+.include <bsd.prog.mk>
diff --git a/sbin/ldconfig/elfhints.c b/sbin/ldconfig/elfhints.c
new file mode 100644
index 0000000..9bdf56e
--- /dev/null
+++ b/sbin/ldconfig/elfhints.c
@@ -0,0 +1,302 @@
+/*-
+ * Copyright (c) 1998 John D. Polstra
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/param.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <dirent.h>
+#include <elf-hints.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "ldconfig.h"
+
+#define MAXDIRS 1024 /* Maximum directories in path */
+#define MAXFILESIZE (16*1024) /* Maximum hints file size */
+
+static void add_dir(const char *, const char *, int);
+static void read_dirs_from_file(const char *, const char *);
+static void read_elf_hints(const char *, int);
+static void write_elf_hints(const char *);
+
+static const char *dirs[MAXDIRS];
+static int ndirs;
+int insecure;
+
+static void
+add_dir(const char *hintsfile, const char *name, int trusted)
+{
+ struct stat stbuf;
+ int i;
+
+ /* Do some security checks */
+ if (!trusted && !insecure) {
+ if (stat(name, &stbuf) == -1) {
+ warn("%s", name);
+ return;
+ }
+ if (stbuf.st_uid != 0) {
+ warnx("%s: ignoring directory not owned by root", name);
+ return;
+ }
+ if ((stbuf.st_mode & S_IWOTH) != 0) {
+ warnx("%s: ignoring world-writable directory", name);
+ return;
+ }
+ if ((stbuf.st_mode & S_IWGRP) != 0) {
+ warnx("%s: ignoring group-writable directory", name);
+ return;
+ }
+ }
+
+ for (i = 0; i < ndirs; i++)
+ if (strcmp(dirs[i], name) == 0)
+ return;
+ if (ndirs >= MAXDIRS)
+ errx(1, "\"%s\": Too many directories in path", hintsfile);
+ dirs[ndirs++] = name;
+}
+
+void
+list_elf_hints(const char *hintsfile)
+{
+ int i;
+ int nlibs;
+
+ read_elf_hints(hintsfile, 1);
+ printf("%s:\n", hintsfile);
+ printf("\tsearch directories:");
+ for (i = 0; i < ndirs; i++)
+ printf("%c%s", i == 0 ? ' ' : ':', dirs[i]);
+ printf("\n");
+
+ nlibs = 0;
+ for (i = 0; i < ndirs; i++) {
+ DIR *dirp;
+ struct dirent *dp;
+
+ if ((dirp = opendir(dirs[i])) == NULL)
+ continue;
+ while ((dp = readdir(dirp)) != NULL) {
+ int len;
+ int namelen;
+ const char *name;
+ const char *vers;
+
+ /* Name can't be shorter than "libx.so.0" */
+ if ((len = strlen(dp->d_name)) < 9 ||
+ strncmp(dp->d_name, "lib", 3) != 0)
+ continue;
+ name = dp->d_name + 3;
+ vers = dp->d_name + len;
+ while (vers > dp->d_name && isdigit(*(vers-1)))
+ vers--;
+ if (vers == dp->d_name + len)
+ continue;
+ if (vers < dp->d_name + 4 ||
+ strncmp(vers - 4, ".so.", 4) != 0)
+ continue;
+
+ /* We have a valid shared library name. */
+ namelen = (vers - 4) - name;
+ printf("\t%d:-l%.*s.%s => %s/%s\n", nlibs,
+ namelen, name, vers, dirs[i], dp->d_name);
+ nlibs++;
+ }
+ closedir(dirp);
+ }
+}
+
+static void
+read_dirs_from_file(const char *hintsfile, const char *listfile)
+{
+ FILE *fp;
+ char buf[MAXPATHLEN];
+ int linenum;
+
+ if ((fp = fopen(listfile, "r")) == NULL)
+ err(1, "%s", listfile);
+
+ linenum = 0;
+ while (fgets(buf, sizeof buf, fp) != NULL) {
+ char *cp, *sp;
+
+ linenum++;
+ cp = buf;
+ /* Skip leading white space. */
+ while (isspace(*cp))
+ cp++;
+ if (*cp == '#' || *cp == '\0')
+ continue;
+ sp = cp;
+ /* Advance over the directory name. */
+ while (!isspace(*cp) && *cp != '\0')
+ cp++;
+ /* Terminate the string and skip trailing white space. */
+ if (*cp != '\0') {
+ *cp++ = '\0';
+ while (isspace(*cp))
+ cp++;
+ }
+ /* Now we had better be at the end of the line. */
+ if (*cp != '\0')
+ warnx("%s:%d: trailing characters ignored",
+ listfile, linenum);
+
+ if ((sp = strdup(sp)) == NULL)
+ errx(1, "Out of memory");
+ add_dir(hintsfile, sp, 0);
+ }
+
+ fclose(fp);
+}
+
+static void
+read_elf_hints(const char *hintsfile, int must_exist)
+{
+ int fd;
+ struct stat s;
+ void *mapbase;
+ struct elfhints_hdr *hdr;
+ char *strtab;
+ char *dirlist;
+ char *p;
+
+ if ((fd = open(hintsfile, O_RDONLY)) == -1) {
+ if (errno == ENOENT && !must_exist)
+ return;
+ err(1, "Cannot open \"%s\"", hintsfile);
+ }
+ if (fstat(fd, &s) == -1)
+ err(1, "Cannot stat \"%s\"", hintsfile);
+ if (s.st_size > MAXFILESIZE)
+ errx(1, "\"%s\" is unreasonably large", hintsfile);
+ /*
+ * We use a read-write, private mapping so that we can null-terminate
+ * some strings in it without affecting the underlying file.
+ */
+ mapbase = mmap(NULL, s.st_size, PROT_READ|PROT_WRITE,
+ MAP_PRIVATE, fd, 0);
+ if (mapbase == MAP_FAILED)
+ err(1, "Cannot mmap \"%s\"", hintsfile);
+ close(fd);
+
+ hdr = (struct elfhints_hdr *)mapbase;
+ if (hdr->magic != ELFHINTS_MAGIC)
+ errx(1, "\"%s\": invalid file format", hintsfile);
+ if (hdr->version != 1)
+ errx(1, "\"%s\": unrecognized file version (%d)", hintsfile,
+ hdr->version);
+
+ strtab = (char *)mapbase + hdr->strtab;
+ dirlist = strtab + hdr->dirlist;
+
+ if (*dirlist != '\0')
+ while ((p = strsep(&dirlist, ":")) != NULL)
+ add_dir(hintsfile, p, 1);
+}
+
+void
+update_elf_hints(const char *hintsfile, int argc, char **argv, int merge)
+{
+ int i;
+
+ if (merge)
+ read_elf_hints(hintsfile, 0);
+ for (i = 0; i < argc; i++) {
+ struct stat s;
+
+ if (stat(argv[i], &s) == -1)
+ warn("warning: %s", argv[i]);
+ else if (S_ISREG(s.st_mode))
+ read_dirs_from_file(hintsfile, argv[i]);
+ else
+ add_dir(hintsfile, argv[i], 0);
+ }
+ write_elf_hints(hintsfile);
+}
+
+static void
+write_elf_hints(const char *hintsfile)
+{
+ struct elfhints_hdr hdr;
+ char *tempname;
+ int fd;
+ FILE *fp;
+ int i;
+
+ if (asprintf(&tempname, "%s.XXXXXX", hintsfile) == -1)
+ errx(1, "Out of memory");
+ if ((fd = mkstemp(tempname)) == -1)
+ err(1, "mkstemp(%s)", tempname);
+ if (fchmod(fd, 0444) == -1)
+ err(1, "fchmod(%s)", tempname);
+ if ((fp = fdopen(fd, "wb")) == NULL)
+ err(1, "fdopen(%s)", tempname);
+
+ hdr.magic = ELFHINTS_MAGIC;
+ hdr.version = 1;
+ hdr.strtab = sizeof hdr;
+ hdr.strsize = 0;
+ hdr.dirlist = 0;
+ memset(hdr.spare, 0, sizeof hdr.spare);
+
+ /* Count up the size of the string table. */
+ if (ndirs > 0) {
+ hdr.strsize += strlen(dirs[0]);
+ for (i = 1; i < ndirs; i++)
+ hdr.strsize += 1 + strlen(dirs[i]);
+ }
+ hdr.dirlistlen = hdr.strsize;
+ hdr.strsize++; /* For the null terminator */
+
+ /* Write the header. */
+ if (fwrite(&hdr, 1, sizeof hdr, fp) != sizeof hdr)
+ err(1, "%s: write error", tempname);
+ /* Write the strings. */
+ if (ndirs > 0) {
+ if (fputs(dirs[0], fp) == EOF)
+ err(1, "%s: write error", tempname);
+ for (i = 1; i < ndirs; i++)
+ if (fprintf(fp, ":%s", dirs[i]) < 0)
+ err(1, "%s: write error", tempname);
+ }
+ if (putc('\0', fp) == EOF || fclose(fp) == EOF)
+ err(1, "%s: write error", tempname);
+
+ if (rename(tempname, hintsfile) == -1)
+ err(1, "rename %s to %s", tempname, hintsfile);
+ free(tempname);
+}
diff --git a/sbin/ldconfig/ldconfig.8 b/sbin/ldconfig/ldconfig.8
new file mode 100644
index 0000000..f2fc324
--- /dev/null
+++ b/sbin/ldconfig/ldconfig.8
@@ -0,0 +1,198 @@
+.\"
+.\" Copyright (c) 1993 Paul Kranenburg
+.\" 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 Paul Kranenburg.
+.\" 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd March 19, 2013
+.Dt LDCONFIG 8
+.Os
+.Sh NAME
+.Nm ldconfig
+.Nd configure the shared library cache
+.Sh SYNOPSIS
+.Nm
+.Op Fl 32
+.Op Fl aout | Fl elf
+.Op Fl Rimrsv
+.Op Fl f Ar hints_file
+.Op Ar directory | Ar
+.Sh DESCRIPTION
+The
+.Nm
+utility is used to prepare a set of
+.Dq hints
+for use by the dynamic linker
+to facilitate quick lookup of shared libraries available in multiple
+directories.
+It scans a set of built-in system directories and any
+.Ar directories
+specified on the command line (in the given order) looking for
+shared libraries and stores the results in a system file to forestall
+the overhead that would otherwise result from the directory search
+operations the dynamic linker would have to perform to load the
+required shared libraries.
+.Pp
+Files named on the command line are expected to contain directories
+to scan for shared libraries.
+Each directory's pathname must start on a new
+line.
+Blank lines and lines starting with the comment character
+.Ql \&#
+are ignored.
+Filenames must conform to the
+.Pa lib*.so.[0-9]
+pattern in order to be added to the hints file.
+.Pp
+For security reasons, directories which are world or group-writable or which
+are not owned by root produce warning messages and are skipped, unless
+the
+.Fl i
+option is present.
+.Pp
+The shared libraries which are found will be automatically available for loading
+if needed by the program being prepared for execution.
+This obviates the need
+for storing search paths within the executable.
+.Pp
+The
+.Ev LD_LIBRARY_PATH
+environment variable can be used to override the use of
+directories (or the order thereof) from the cache or to specify additional
+directories where shared libraries might be found.
+.Ev LD_LIBRARY_PATH
+is a
+.Sq \&:
+separated list of directory paths which are searched by
+the dynamic linker
+when it needs to load a shared library.
+It can be viewed as the run-time
+equivalent of the
+.Fl L
+switch of
+.Xr ld 1 .
+.Pp
+The
+.Nm
+utility is typically run as part of the boot sequence.
+.Pp
+The following options are recognized by
+.Nm :
+.Bl -tag -width indent
+.It Fl 32
+Generate the hints for 32-bit ABI shared libraries
+on 64-bit systems that support running 32-bit binaries.
+.It Fl aout
+Generate the hints for a.out format shared libraries.
+.It Fl elf
+Generate the hints for ELF format shared libraries.
+.It Fl R
+Rescan the previously configured directories.
+This opens the previous hints
+file and fetches the directory list from the header.
+Any additional pathnames
+on the command line are also processed.
+This is the default action when no parameters are given.
+.It Fl f Ar hints_file
+Read and/or update the specified hints file, instead of the standard file.
+This option is provided primarily for testing.
+.It Fl i
+Run in insecure mode.
+The security checks will not be performed.
+.It Fl m
+Instead of replacing the contents of the hints file
+with those found in the directories specified,
+.Dq merge
+in new entries.
+Directories recorded in the hints file by previous runs of
+.Nm
+are also rescanned for new shared libraries.
+.It Fl r
+List the current contents of the hints file
+on the standard output.
+The hints file is not modified.
+The list of
+directories stored in the hints file is included.
+.It Fl s
+Do not scan the built-in system directory
+.Pq Dq /usr/lib
+for shared libraries.
+.It Fl v
+Switch on verbose mode.
+.El
+.Sh SECURITY
+Special care must be taken when loading shared libraries into the address
+space of
+.Ev set-user-Id
+programs.
+Whenever such a program is run by any user except the owner of the program,
+the dynamic linker
+will only load shared libraries from the hints
+file.
+In particular, the
+.Ev LD_LIBRARY_PATH
+is not used to search for libraries.
+Thus, the role of ldconfig is dual.
+In
+addition to building a set of hints for quick lookup, it also serves to
+specify the trusted collection of directories from which shared objects can
+be safely loaded.
+.Sh FILES
+.Bl -tag -width /var/run/ld-elf.so.hintsxxx -compact
+.It Pa /var/run/ld.so.hints
+Standard hints file for the a.out dynamic linker.
+.It Pa /var/run/ld-elf.so.hints
+Standard hints file for the ELF dynamic linker.
+.It Pa /etc/ld.so.conf
+Conventional configuration file containing directory names for
+invocations with
+.Fl aout .
+.It Pa /etc/ld-elf.so.conf
+Conventional configuration file containing directory names for
+invocations with
+.Fl elf .
+.It Pa /var/run/ld-elf32.so.hints
+.It Pa /var/run/ld32.so.hints
+Conventional configuration files containing directory names for
+invocations with
+.Fl 32 .
+.El
+.Sh SEE ALSO
+.Xr ld 1 ,
+.Xr link 5
+.Sh HISTORY
+A
+.Nm
+utility first appeared in SunOS 4.0, it appeared in its current form
+in
+.Fx 1.1 .
+.Sh BUGS
+Some security checks (for example, verifying root ownership of
+added directories) are not performed when
+.Fl aout
+is specified.
diff --git a/sbin/ldconfig/ldconfig.c b/sbin/ldconfig/ldconfig.c
new file mode 100644
index 0000000..31d8083
--- /dev/null
+++ b/sbin/ldconfig/ldconfig.c
@@ -0,0 +1,640 @@
+/*
+ * Copyright (c) 1993,1995 Paul Kranenburg
+ * 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 Paul Kranenburg.
+ * 4. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef lint
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <a.out.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <elf-hints.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/link_aout.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "ldconfig.h"
+#include "shlib.h"
+#include "support.h"
+
+#if DEBUG
+/* test */
+#undef _PATH_LD_HINTS
+#define _PATH_LD_HINTS "./ld.so.hints"
+#undef _PATH_ELF_HINTS
+#define _PATH_ELF_HINTS "./ld-elf.so.hints"
+#endif
+
+#define _PATH_LD32_HINTS "/var/run/ld32.so.hints"
+#define _PATH_ELF32_HINTS "/var/run/ld-elf32.so.hints"
+
+#undef major
+#undef minor
+
+static int verbose;
+static int nostd;
+static int justread;
+static int merge;
+static int rescan;
+static const char *hints_file;
+
+struct shlib_list {
+ /* Internal list of shared libraries found */
+ char *name;
+ char *path;
+ int dewey[MAXDEWEY];
+ int ndewey;
+#define major dewey[0]
+#define minor dewey[1]
+ struct shlib_list *next;
+};
+
+static struct shlib_list *shlib_head = NULL, **shlib_tail = &shlib_head;
+static char *dir_list;
+
+static int buildhints(void);
+static int dodir(char *, int);
+int dofile(char *, int);
+static void enter(char *, char *, char *, int *, int);
+static void listhints(void);
+static int readhints(void);
+static void usage(void);
+
+/*
+ * Note on aout/a.out support.
+ * To properly support shared libraries for compat2x, which are a.out, we need
+ * to support a.out here. As of 2013, bug reports are still coming in for this
+ * feature (on amd64 no less), so we know it is still in use.
+ */
+
+int
+main(int argc, char **argv)
+{
+ int i, c;
+ int rval = 0;
+ int is_aout = 0;
+ int is_32 = 0;
+
+ while (argc > 1) {
+ if (strcmp(argv[1], "-aout") == 0) {
+ is_aout = 1;
+ argc--;
+ argv++;
+ } else if (strcmp(argv[1], "-elf") == 0) {
+ is_aout = 0;
+ argc--;
+ argv++;
+ } else if (strcmp(argv[1], "-32") == 0) {
+ is_32 = 1;
+ argc--;
+ argv++;
+ } else {
+ break;
+ }
+ }
+
+ if (is_32)
+ hints_file = is_aout ? _PATH_LD32_HINTS : _PATH_ELF32_HINTS;
+ else
+ hints_file = is_aout ? _PATH_LD_HINTS : _PATH_ELF_HINTS;
+ if (argc == 1)
+ rescan = 1;
+ else while((c = getopt(argc, argv, "Rf:imrsv")) != -1) {
+ switch (c) {
+ case 'R':
+ rescan = 1;
+ break;
+ case 'f':
+ hints_file = optarg;
+ break;
+ case 'i':
+ insecure = 1;
+ break;
+ case 'm':
+ merge = 1;
+ break;
+ case 'r':
+ justread = 1;
+ break;
+ case 's':
+ nostd = 1;
+ break;
+ case 'v':
+ verbose = 1;
+ break;
+ default:
+ usage();
+ break;
+ }
+ }
+
+ if (!is_aout) {
+ if (justread)
+ list_elf_hints(hints_file);
+ else
+ update_elf_hints(hints_file, argc - optind,
+ argv + optind, merge || rescan);
+ return 0;
+ }
+
+ /* Here begins the aout libs processing */
+ dir_list = strdup("");
+
+ if (justread || merge || rescan) {
+ if ((rval = readhints()) != 0)
+ return rval;
+ }
+
+ if (!nostd && !merge && !rescan)
+ std_search_path();
+
+ /* Add any directories/files from the command line */
+ if (!justread) {
+ for (i = optind; i < argc; i++) {
+ struct stat stbuf;
+
+ if (stat(argv[i], &stbuf) == -1) {
+ warn("%s", argv[i]);
+ rval = -1;
+ } else if (strcmp(argv[i], "/usr/lib") == 0) {
+ warnx("WARNING! '%s' can not be used", argv[i]);
+ rval = -1;
+ } else {
+ /*
+ * See if this is a directory-containing
+ * file instead of a directory
+ */
+ if (S_ISREG(stbuf.st_mode))
+ rval |= dofile(argv[i], 0);
+ else
+ add_search_path(argv[i]);
+ }
+ }
+ }
+
+ for (i = 0; i < n_search_dirs; i++) {
+ char *cp = concat(dir_list, *dir_list?":":"", search_dirs[i]);
+ free(dir_list);
+ dir_list = cp;
+ }
+
+ if (justread) {
+ listhints();
+ return 0;
+ }
+
+ for (i = 0; i < n_search_dirs; i++)
+ rval |= dodir(search_dirs[i], 1);
+
+ rval |= buildhints();
+
+ return rval;
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr,
+ "usage: ldconfig [-32] [-aout | -elf] [-Rimrsv] [-f hints_file] [directory | file ...]\n");
+ exit(1);
+}
+
+int
+dofile(char *fname, int silent)
+{
+ FILE *hfp;
+ char buf[MAXPATHLEN];
+ int rval = 0;
+ char *cp, *sp;
+
+ if ((hfp = fopen(fname, "r")) == NULL) {
+ warn("%s", fname);
+ return -1;
+ }
+
+ while (fgets(buf, sizeof(buf), hfp)) {
+ cp = buf;
+ while (isspace(*cp))
+ cp++;
+ if (*cp == '#' || *cp == '\0')
+ continue;
+ sp = cp;
+ while (!isspace(*cp) && *cp != '\0')
+ cp++;
+
+ if (*cp != '\n') {
+ *cp = '\0';
+ warnx("%s: trailing characters ignored", sp);
+ }
+
+ *cp = '\0';
+
+ rval |= dodir(sp, silent);
+ }
+
+ (void)fclose(hfp);
+ return rval;
+}
+
+int
+dodir(char *dir, int silent)
+{
+ DIR *dd;
+ struct dirent *dp;
+ char name[MAXPATHLEN];
+ int dewey[MAXDEWEY], ndewey;
+
+ if ((dd = opendir(dir)) == NULL) {
+ if (silent && errno == ENOENT) /* Ignore the error */
+ return 0;
+ warn("%s", dir);
+ return -1;
+ }
+
+ while ((dp = readdir(dd)) != NULL) {
+ int n;
+ char *cp;
+
+ /* Check for `lib' prefix */
+ if (dp->d_name[0] != 'l' ||
+ dp->d_name[1] != 'i' ||
+ dp->d_name[2] != 'b')
+ continue;
+
+ /* Copy the entry minus prefix */
+ (void)strcpy(name, dp->d_name + 3);
+ n = strlen(name);
+ if (n < 4)
+ continue;
+
+ /* Find ".so." in name */
+ for (cp = name + n - 4; cp > name; --cp) {
+ if (cp[0] == '.' &&
+ cp[1] == 's' &&
+ cp[2] == 'o' &&
+ cp[3] == '.')
+ break;
+ }
+ if (cp <= name)
+ continue;
+
+ *cp = '\0';
+ if (!isdigit(*(cp+4)))
+ continue;
+
+ bzero((caddr_t)dewey, sizeof(dewey));
+ ndewey = getdewey(dewey, cp + 4);
+ if (ndewey < 2)
+ continue;
+ enter(dir, dp->d_name, name, dewey, ndewey);
+ }
+
+ closedir(dd);
+ return 0;
+}
+
+static void
+enter(char *dir, char *file, char *name, int dewey[], int ndewey)
+{
+ struct shlib_list *shp;
+
+ for (shp = shlib_head; shp; shp = shp->next) {
+ if (strcmp(name, shp->name) != 0 || major != shp->major)
+ continue;
+
+ /* Name matches existing entry */
+ if (cmpndewey(dewey, ndewey, shp->dewey, shp->ndewey) > 0) {
+
+ /* Update this entry with higher versioned lib */
+ if (verbose)
+ printf("Updating lib%s.%d.%d to %s/%s\n",
+ shp->name, shp->major, shp->minor,
+ dir, file);
+
+ free(shp->name);
+ shp->name = strdup(name);
+ free(shp->path);
+ shp->path = concat(dir, "/", file);
+ bcopy(dewey, shp->dewey, sizeof(shp->dewey));
+ shp->ndewey = ndewey;
+ }
+ break;
+ }
+
+ if (shp)
+ /* Name exists: older version or just updated */
+ return;
+
+ /* Allocate new list element */
+ if (verbose)
+ printf("Adding %s/%s\n", dir, file);
+
+ shp = (struct shlib_list *)xmalloc(sizeof *shp);
+ shp->name = strdup(name);
+ shp->path = concat(dir, "/", file);
+ bcopy(dewey, shp->dewey, sizeof(shp->dewey));
+ shp->ndewey = ndewey;
+ shp->next = NULL;
+
+ *shlib_tail = shp;
+ shlib_tail = &shp->next;
+}
+
+
+static int
+hinthash(char *cp, int vmajor)
+{
+ int k = 0;
+
+ while (*cp)
+ k = (((k << 1) + (k >> 14)) ^ (*cp++)) & 0x3fff;
+
+ k = (((k << 1) + (k >> 14)) ^ (vmajor*257)) & 0x3fff;
+
+ return k;
+}
+
+int
+buildhints(void)
+{
+ struct hints_header hdr;
+ struct hints_bucket *blist;
+ struct shlib_list *shp;
+ char *strtab;
+ int i, n, str_index = 0;
+ int strtab_sz = 0; /* Total length of strings */
+ int nhints = 0; /* Total number of hints */
+ int fd;
+ char *tmpfilename;
+
+ for (shp = shlib_head; shp; shp = shp->next) {
+ strtab_sz += 1 + strlen(shp->name);
+ strtab_sz += 1 + strlen(shp->path);
+ nhints++;
+ }
+
+ /* Fill hints file header */
+ hdr.hh_magic = HH_MAGIC;
+ hdr.hh_version = LD_HINTS_VERSION_2;
+ hdr.hh_nbucket = 1 * nhints;
+ n = hdr.hh_nbucket * sizeof(struct hints_bucket);
+ hdr.hh_hashtab = sizeof(struct hints_header);
+ hdr.hh_strtab = hdr.hh_hashtab + n;
+ hdr.hh_dirlist = strtab_sz;
+ strtab_sz += 1 + strlen(dir_list);
+ hdr.hh_strtab_sz = strtab_sz;
+ hdr.hh_ehints = hdr.hh_strtab + hdr.hh_strtab_sz;
+
+ if (verbose)
+ printf("Totals: entries %d, buckets %ld, string size %d\n",
+ nhints, (long)hdr.hh_nbucket, strtab_sz);
+
+ /* Allocate buckets and string table */
+ blist = (struct hints_bucket *)xmalloc(n);
+ bzero((char *)blist, n);
+ for (i = 0; i < hdr.hh_nbucket; i++)
+ /* Empty all buckets */
+ blist[i].hi_next = -1;
+
+ strtab = (char *)xmalloc(strtab_sz);
+
+ /* Enter all */
+ for (shp = shlib_head; shp; shp = shp->next) {
+ struct hints_bucket *bp;
+
+ bp = blist +
+ (hinthash(shp->name, shp->major) % hdr.hh_nbucket);
+
+ if (bp->hi_pathx) {
+ int j;
+
+ for (j = 0; j < hdr.hh_nbucket; j++) {
+ if (blist[j].hi_pathx == 0)
+ break;
+ }
+ if (j == hdr.hh_nbucket) {
+ warnx("bummer!");
+ return -1;
+ }
+ while (bp->hi_next != -1)
+ bp = &blist[bp->hi_next];
+ bp->hi_next = j;
+ bp = blist + j;
+ }
+
+ /* Insert strings in string table */
+ bp->hi_namex = str_index;
+ strcpy(strtab + str_index, shp->name);
+ str_index += 1 + strlen(shp->name);
+
+ bp->hi_pathx = str_index;
+ strcpy(strtab + str_index, shp->path);
+ str_index += 1 + strlen(shp->path);
+
+ /* Copy versions */
+ bcopy(shp->dewey, bp->hi_dewey, sizeof(bp->hi_dewey));
+ bp->hi_ndewey = shp->ndewey;
+ }
+
+ /* Copy search directories */
+ strcpy(strtab + str_index, dir_list);
+ str_index += 1 + strlen(dir_list);
+
+ /* Sanity check */
+ if (str_index != strtab_sz) {
+ errx(1, "str_index(%d) != strtab_sz(%d)", str_index, strtab_sz);
+ }
+
+ tmpfilename = concat(hints_file, ".XXXXXXXXXX", "");
+ umask(0); /* Create with exact permissions */
+ if ((fd = mkstemp(tmpfilename)) == -1) {
+ warn("%s", tmpfilename);
+ return -1;
+ }
+ fchmod(fd, 0444);
+
+ if (write(fd, &hdr, sizeof(struct hints_header)) !=
+ sizeof(struct hints_header)) {
+ warn("%s", hints_file);
+ return -1;
+ }
+ if (write(fd, blist, hdr.hh_nbucket * sizeof(*blist)) !=
+ (ssize_t)(hdr.hh_nbucket * sizeof(*blist))) {
+ warn("%s", hints_file);
+ return -1;
+ }
+ if (write(fd, strtab, strtab_sz) != strtab_sz) {
+ warn("%s", hints_file);
+ return -1;
+ }
+ if (close(fd) != 0) {
+ warn("%s", hints_file);
+ return -1;
+ }
+
+ /* Install it */
+ if (unlink(hints_file) != 0 && errno != ENOENT) {
+ warn("%s", hints_file);
+ return -1;
+ }
+
+ if (rename(tmpfilename, hints_file) != 0) {
+ warn("%s", hints_file);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+readhints(void)
+{
+ int fd;
+ void *addr;
+ long fsize;
+ long msize;
+ struct hints_header *hdr;
+ struct hints_bucket *blist;
+ char *strtab;
+ struct shlib_list *shp;
+ int i;
+
+ if ((fd = open(hints_file, O_RDONLY, 0)) == -1) {
+ warn("%s", hints_file);
+ return -1;
+ }
+
+ msize = PAGE_SIZE;
+ addr = mmap(0, msize, PROT_READ, MAP_COPY, fd, 0);
+
+ if (addr == MAP_FAILED) {
+ warn("%s", hints_file);
+ return -1;
+ }
+
+ hdr = (struct hints_header *)addr;
+ if (HH_BADMAG(*hdr)) {
+ warnx("%s: bad magic: %lo", hints_file,
+ (unsigned long)hdr->hh_magic);
+ return -1;
+ }
+
+ if (hdr->hh_version != LD_HINTS_VERSION_1 &&
+ hdr->hh_version != LD_HINTS_VERSION_2) {
+ warnx("unsupported version: %ld", (long)hdr->hh_version);
+ return -1;
+ }
+
+ if (hdr->hh_ehints > msize) {
+ fsize = hdr->hh_ehints;
+ munmap(addr, msize);
+ addr = mmap(0, fsize, PROT_READ, MAP_COPY, fd, 0);
+ if (addr == MAP_FAILED) {
+ warn("%s", hints_file);
+ return -1;
+ }
+ hdr = (struct hints_header *)addr;
+ }
+ close(fd);
+
+ strtab = (char *)addr + hdr->hh_strtab;
+
+ if (hdr->hh_version >= LD_HINTS_VERSION_2)
+ add_search_path(strtab + hdr->hh_dirlist);
+ else if (rescan)
+ errx(1, "%s too old and does not contain the search path",
+ hints_file);
+
+ if (rescan)
+ return 0;
+
+ blist = malloc(sizeof(*blist) * hdr->hh_nbucket);
+ if (blist == NULL)
+ err(1, "readhints");
+ memcpy(blist, (char *)addr + hdr->hh_hashtab,
+ sizeof(*blist) * hdr->hh_nbucket);
+
+
+ for (i = 0; i < hdr->hh_nbucket; i++) {
+ struct hints_bucket *bp = &blist[i];
+
+ /* Sanity check */
+ if (bp->hi_namex >= hdr->hh_strtab_sz) {
+ warnx("bad name index: %#x", bp->hi_namex);
+ free(blist);
+ return -1;
+ }
+ if (bp->hi_pathx >= hdr->hh_strtab_sz) {
+ warnx("bad path index: %#x", bp->hi_pathx);
+ free(blist);
+ return -1;
+ }
+
+ /* Allocate new list element */
+ shp = (struct shlib_list *)xmalloc(sizeof *shp);
+ shp->name = strdup(strtab + bp->hi_namex);
+ shp->path = strdup(strtab + bp->hi_pathx);
+ bcopy(bp->hi_dewey, shp->dewey, sizeof(shp->dewey));
+ shp->ndewey = bp->hi_ndewey;
+ shp->next = NULL;
+
+ *shlib_tail = shp;
+ shlib_tail = &shp->next;
+ }
+
+ free(blist);
+ return 0;
+}
+
+static void
+listhints(void)
+{
+ struct shlib_list *shp;
+ int i;
+
+ printf("%s:\n", hints_file);
+ printf("\tsearch directories: %s\n", dir_list);
+
+ for (i = 0, shp = shlib_head; shp; i++, shp = shp->next)
+ printf("\t%d:-l%s.%d.%d => %s\n",
+ i, shp->name, shp->major, shp->minor, shp->path);
+
+ return;
+}
diff --git a/sbin/ldconfig/ldconfig.h b/sbin/ldconfig/ldconfig.h
new file mode 100644
index 0000000..859bcbd
--- /dev/null
+++ b/sbin/ldconfig/ldconfig.h
@@ -0,0 +1,41 @@
+/*-
+ * Copyright (c) 1998 John D. Polstra
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef LDCONFIG_H
+#define LDCONFIG_H 1
+
+#include <sys/cdefs.h>
+
+extern int insecure; /* -i flag, needed here for elfhints.c */
+
+__BEGIN_DECLS
+void list_elf_hints(const char *);
+void update_elf_hints(const char *, int, char **, int);
+__END_DECLS
+
+#endif
diff --git a/sbin/md5/Makefile b/sbin/md5/Makefile
new file mode 100644
index 0000000..b6afe4e
--- /dev/null
+++ b/sbin/md5/Makefile
@@ -0,0 +1,18 @@
+# @(#)Makefile 8.1 (Berkeley) 6/9/93
+# $FreeBSD$
+
+PROG= md5
+
+LINKS= ${BINDIR}/md5 ${BINDIR}/rmd160 \
+ ${BINDIR}/md5 ${BINDIR}/sha1 \
+ ${BINDIR}/md5 ${BINDIR}/sha256 \
+ ${BINDIR}/md5 ${BINDIR}/sha512
+
+MLINKS= md5.1 rmd160.1 \
+ md5.1 sha1.1 \
+ md5.1 sha256.1 \
+ md5.1 sha512.1
+
+LIBADD= md
+
+.include <bsd.prog.mk>
diff --git a/sbin/md5/md5.1 b/sbin/md5/md5.1
new file mode 100644
index 0000000..e191cd1
--- /dev/null
+++ b/sbin/md5/md5.1
@@ -0,0 +1,157 @@
+.\" $FreeBSD$
+.Dd May 17, 2014
+.Dt MD5 1
+.Os
+.Sh NAME
+.Nm md5 , sha1 , sha256 , sha512, rmd160
+.Nd calculate a message-digest fingerprint (checksum) for a file
+.Sh SYNOPSIS
+.Nm md5
+.Op Fl pqrtx
+.Op Fl c Ar string
+.Op Fl s Ar string
+.Op Ar
+.Nm sha1
+.Op Fl pqrtx
+.Op Fl c Ar string
+.Op Fl s Ar string
+.Op Ar
+.Nm sha256
+.Op Fl pqrtx
+.Op Fl c Ar string
+.Op Fl s Ar string
+.Op Ar
+.Nm sha512
+.Op Fl pqrtx
+.Op Fl c Ar string
+.Op Fl s Ar string
+.Op Ar
+.Nm rmd160
+.Op Fl pqrtx
+.Op Fl c Ar string
+.Op Fl s Ar string
+.Op Ar
+.Sh DESCRIPTION
+The
+.Nm md5 , sha1 , sha256 , sha512
+and
+.Nm rmd160
+utilities take as input a message of arbitrary length and produce as
+output a
+.Dq fingerprint
+or
+.Dq message digest
+of the input.
+It is conjectured that it is computationally infeasible to
+produce two messages having the same message digest, or to produce any
+message having a given prespecified target message digest.
+The
+.Tn MD5 , SHA-1 , SHA-256 , SHA-512
+and
+.Tn RIPEMD-160
+algorithms are intended for digital signature applications, where a
+large file must be
+.Dq compressed
+in a secure manner before being encrypted with a private
+(secret)
+key under a public-key cryptosystem such as
+.Tn RSA .
+.Pp
+.Tn MD5
+has been completely broken as far as finding collisions is
+concerned, and should not be relied upon to produce unique outputs.
+This also means that
+.Tn MD5
+should not be used as part of a cryptographic signature scheme.
+At the current time (2014-05-17) there is no publicly known method to
+.Dq reverse
+MD5, i.e., to find an input given a hash value.
+.Pp
+.Tn SHA-1
+currently (2014-05-17) has no known collisions, but an attack has been
+found which is faster than a brute-force search, placing the security of
+.Tn SHA-1
+in doubt.
+.Pp
+It is recommended that all new applications use
+.Tn SHA-256
+instead of one of the other hash functions.
+.Pp
+The following options may be used in any combination and must
+precede any files named on the command line.
+The hexadecimal checksum of each file listed on the command line is printed
+after the options are processed.
+.Bl -tag -width indent
+.It Fl c Ar string
+Compare the digest of the file against this string.
+.Pq Note that this option is not yet useful if multiple files are specified.
+.It Fl s Ar string
+Print a checksum of the given
+.Ar string .
+.It Fl p
+Echo stdin to stdout and append the checksum to stdout.
+.It Fl q
+Quiet mode \(em only the checksum is printed out.
+Overrides the
+.Fl r
+option.
+.It Fl r
+Reverses the format of the output.
+This helps with visual diffs.
+Does nothing
+when combined with the
+.Fl ptx
+options.
+.It Fl t
+Run a built-in time trial.
+.It Fl x
+Run a built-in test script.
+.El
+.Sh EXIT STATUS
+The
+.Nm md5 , sha1 , sha256 , sha512
+and
+.Nm rmd160
+utilities exit 0 on success,
+1 if at least one of the input files could not be read,
+and 2 if at least one file does not have the same hash as the
+.Fl c
+option.
+.Sh SEE ALSO
+.Xr cksum 1 ,
+.Xr md5 3 ,
+.Xr ripemd 3 ,
+.Xr sha 3 ,
+.Xr sha256 3 ,
+.Xr sha512 3
+.Rs
+.%A R. Rivest
+.%T The MD5 Message-Digest Algorithm
+.%O RFC1321
+.Re
+.Rs
+.%A J. Burrows
+.%T The Secure Hash Standard
+.%O FIPS PUB 180-2
+.Re
+.Rs
+.%A D. Eastlake and P. Jones
+.%T US Secure Hash Algorithm 1
+.%O RFC 3174
+.Re
+.Pp
+RIPEMD-160 is part of the ISO draft standard
+.Qq ISO/IEC DIS 10118-3
+on dedicated hash functions.
+.Pp
+Secure Hash Standard (SHS):
+.Pa http://csrc.nist.gov/cryptval/shs.html .
+.Pp
+The RIPEMD-160 page:
+.Pa http://www.esat.kuleuven.ac.be/~bosselae/ripemd160.html .
+.Sh ACKNOWLEDGMENTS
+This program is placed in the public domain for free general use by
+RSA Data Security.
+.Pp
+Support for SHA-1 and RIPEMD-160 has been added by
+.An Oliver Eikemeier Aq Mt eik@FreeBSD.org .
diff --git a/sbin/md5/md5.c b/sbin/md5/md5.c
new file mode 100644
index 0000000..f4c56ac
--- /dev/null
+++ b/sbin/md5/md5.c
@@ -0,0 +1,395 @@
+/*
+ * Derived from:
+ *
+ * MDDRIVER.C - test driver for MD2, MD4 and MD5
+ */
+
+/*
+ * Copyright (C) 1990-2, RSA Data Security, Inc. Created 1990. All
+ * rights reserved.
+ *
+ * RSA Data Security, Inc. makes no representations concerning either
+ * the merchantability of this software or the suitability of this
+ * software for any particular purpose. It is provided "as is"
+ * without express or implied warranty of any kind.
+ *
+ * These notices must be retained in any copies of any part of this
+ * documentation and/or software.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <err.h>
+#include <md5.h>
+#include <ripemd.h>
+#include <sha.h>
+#include <sha256.h>
+#include <sha512.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+/*
+ * Length of test block, number of test blocks.
+ */
+#define TEST_BLOCK_LEN 10000
+#define TEST_BLOCK_COUNT 100000
+#define MDTESTCOUNT 8
+
+static int qflag;
+static int rflag;
+static int sflag;
+static char* checkAgainst;
+static int checksFailed;
+
+typedef void (DIGEST_Init)(void *);
+typedef void (DIGEST_Update)(void *, const unsigned char *, size_t);
+typedef char *(DIGEST_End)(void *, char *);
+
+extern const char *MD5TestOutput[MDTESTCOUNT];
+extern const char *SHA1_TestOutput[MDTESTCOUNT];
+extern const char *SHA256_TestOutput[MDTESTCOUNT];
+extern const char *SHA512_TestOutput[MDTESTCOUNT];
+extern const char *RIPEMD160_TestOutput[MDTESTCOUNT];
+
+typedef struct Algorithm_t {
+ const char *progname;
+ const char *name;
+ const char *(*TestOutput)[MDTESTCOUNT];
+ DIGEST_Init *Init;
+ DIGEST_Update *Update;
+ DIGEST_End *End;
+ char *(*Data)(const void *, unsigned int, char *);
+ char *(*File)(const char *, char *);
+} Algorithm_t;
+
+static void MD5_Update(MD5_CTX *, const unsigned char *, size_t);
+static void MDString(const Algorithm_t *, const char *);
+static void MDTimeTrial(const Algorithm_t *);
+static void MDTestSuite(const Algorithm_t *);
+static void MDFilter(const Algorithm_t *, int);
+static void usage(const Algorithm_t *);
+
+typedef union {
+ MD5_CTX md5;
+ SHA1_CTX sha1;
+ SHA256_CTX sha256;
+ SHA512_CTX sha512;
+ RIPEMD160_CTX ripemd160;
+} DIGEST_CTX;
+
+/* max(MD5_DIGEST_LENGTH, SHA_DIGEST_LENGTH,
+ SHA256_DIGEST_LENGTH, SHA512_DIGEST_LENGTH,
+ RIPEMD160_DIGEST_LENGTH)*2+1 */
+#define HEX_DIGEST_LENGTH 129
+
+/* algorithm function table */
+
+static const struct Algorithm_t Algorithm[] = {
+ { "md5", "MD5", &MD5TestOutput, (DIGEST_Init*)&MD5Init,
+ (DIGEST_Update*)&MD5_Update, (DIGEST_End*)&MD5End,
+ &MD5Data, &MD5File },
+ { "sha1", "SHA1", &SHA1_TestOutput, (DIGEST_Init*)&SHA1_Init,
+ (DIGEST_Update*)&SHA1_Update, (DIGEST_End*)&SHA1_End,
+ &SHA1_Data, &SHA1_File },
+ { "sha256", "SHA256", &SHA256_TestOutput, (DIGEST_Init*)&SHA256_Init,
+ (DIGEST_Update*)&SHA256_Update, (DIGEST_End*)&SHA256_End,
+ &SHA256_Data, &SHA256_File },
+ { "sha512", "SHA512", &SHA512_TestOutput, (DIGEST_Init*)&SHA512_Init,
+ (DIGEST_Update*)&SHA512_Update, (DIGEST_End*)&SHA512_End,
+ &SHA512_Data, &SHA512_File },
+ { "rmd160", "RMD160", &RIPEMD160_TestOutput,
+ (DIGEST_Init*)&RIPEMD160_Init, (DIGEST_Update*)&RIPEMD160_Update,
+ (DIGEST_End*)&RIPEMD160_End, &RIPEMD160_Data, &RIPEMD160_File }
+};
+
+static void
+MD5_Update(MD5_CTX *c, const unsigned char *data, size_t len)
+{
+ MD5Update(c, data, len);
+}
+
+/* Main driver.
+
+Arguments (may be any combination):
+ -sstring - digests string
+ -t - runs time trial
+ -x - runs test script
+ filename - digests file
+ (none) - digests standard input
+ */
+int
+main(int argc, char *argv[])
+{
+ int ch;
+ char *p;
+ char buf[HEX_DIGEST_LENGTH];
+ int failed;
+ unsigned digest;
+ const char* progname;
+
+ if ((progname = strrchr(argv[0], '/')) == NULL)
+ progname = argv[0];
+ else
+ progname++;
+
+ for (digest = 0; digest < sizeof(Algorithm)/sizeof(*Algorithm); digest++)
+ if (strcasecmp(Algorithm[digest].progname, progname) == 0)
+ break;
+
+ if (digest == sizeof(Algorithm)/sizeof(*Algorithm))
+ digest = 0;
+
+ failed = 0;
+ checkAgainst = NULL;
+ checksFailed = 0;
+ while ((ch = getopt(argc, argv, "c:pqrs:tx")) != -1)
+ switch (ch) {
+ case 'c':
+ checkAgainst = optarg;
+ break;
+ case 'p':
+ MDFilter(&Algorithm[digest], 1);
+ break;
+ case 'q':
+ qflag = 1;
+ break;
+ case 'r':
+ rflag = 1;
+ break;
+ case 's':
+ sflag = 1;
+ MDString(&Algorithm[digest], optarg);
+ break;
+ case 't':
+ MDTimeTrial(&Algorithm[digest]);
+ break;
+ case 'x':
+ MDTestSuite(&Algorithm[digest]);
+ break;
+ default:
+ usage(&Algorithm[digest]);
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (*argv) {
+ do {
+ p = Algorithm[digest].File(*argv, buf);
+ if (!p) {
+ warn("%s", *argv);
+ failed++;
+ } else {
+ if (qflag)
+ printf("%s", p);
+ else if (rflag)
+ printf("%s %s", p, *argv);
+ else
+ printf("%s (%s) = %s",
+ Algorithm[digest].name, *argv, p);
+ if (checkAgainst && strcmp(checkAgainst,p))
+ {
+ checksFailed++;
+ if (!qflag)
+ printf(" [ Failed ]");
+ }
+ printf("\n");
+ }
+ } while (*++argv);
+ } else if (!sflag && (optind == 1 || qflag || rflag))
+ MDFilter(&Algorithm[digest], 0);
+
+ if (failed != 0)
+ return (1);
+ if (checksFailed != 0)
+ return (2);
+
+ return (0);
+}
+/*
+ * Digests a string and prints the result.
+ */
+static void
+MDString(const Algorithm_t *alg, const char *string)
+{
+ size_t len = strlen(string);
+ char buf[HEX_DIGEST_LENGTH];
+
+ alg->Data(string,len,buf);
+ if (qflag)
+ printf("%s", buf);
+ else if (rflag)
+ printf("%s \"%s\"", buf, string);
+ else
+ printf("%s (\"%s\") = %s", alg->name, string, buf);
+ if (checkAgainst && strcmp(buf,checkAgainst))
+ {
+ checksFailed++;
+ if (!qflag)
+ printf(" [ failed ]");
+ }
+ printf("\n");
+}
+/*
+ * Measures the time to digest TEST_BLOCK_COUNT TEST_BLOCK_LEN-byte blocks.
+ */
+static void
+MDTimeTrial(const Algorithm_t *alg)
+{
+ DIGEST_CTX context;
+ struct rusage before, after;
+ struct timeval total;
+ float seconds;
+ unsigned char block[TEST_BLOCK_LEN];
+ unsigned int i;
+ char *p, buf[HEX_DIGEST_LENGTH];
+
+ printf("%s time trial. Digesting %d %d-byte blocks ...",
+ alg->name, TEST_BLOCK_COUNT, TEST_BLOCK_LEN);
+ fflush(stdout);
+
+ /* Initialize block */
+ for (i = 0; i < TEST_BLOCK_LEN; i++)
+ block[i] = (unsigned char) (i & 0xff);
+
+ /* Start timer */
+ getrusage(RUSAGE_SELF, &before);
+
+ /* Digest blocks */
+ alg->Init(&context);
+ for (i = 0; i < TEST_BLOCK_COUNT; i++)
+ alg->Update(&context, block, TEST_BLOCK_LEN);
+ p = alg->End(&context, buf);
+
+ /* Stop timer */
+ getrusage(RUSAGE_SELF, &after);
+ timersub(&after.ru_utime, &before.ru_utime, &total);
+ seconds = total.tv_sec + (float) total.tv_usec / 1000000;
+
+ printf(" done\n");
+ printf("Digest = %s", p);
+ printf("\nTime = %f seconds\n", seconds);
+ printf("Speed = %f bytes/second\n",
+ (float) TEST_BLOCK_LEN * (float) TEST_BLOCK_COUNT / seconds);
+}
+/*
+ * Digests a reference suite of strings and prints the results.
+ */
+
+static const char *MDTestInput[MDTESTCOUNT] = {
+ "",
+ "a",
+ "abc",
+ "message digest",
+ "abcdefghijklmnopqrstuvwxyz",
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
+ "12345678901234567890123456789012345678901234567890123456789012345678901234567890",
+ "MD5 has not yet (2001-09-03) been broken, but sufficient attacks have been made \
+that its security is in some doubt"
+};
+
+const char *MD5TestOutput[MDTESTCOUNT] = {
+ "d41d8cd98f00b204e9800998ecf8427e",
+ "0cc175b9c0f1b6a831c399e269772661",
+ "900150983cd24fb0d6963f7d28e17f72",
+ "f96b697d7cb7938d525a2f31aaf161d0",
+ "c3fcd3d76192e4007dfb496cca67e13b",
+ "d174ab98d277d9f5a5611c2c9f419d9f",
+ "57edf4a22be3c955ac49da2e2107b67a",
+ "b50663f41d44d92171cb9976bc118538"
+};
+
+const char *SHA1_TestOutput[MDTESTCOUNT] = {
+ "da39a3ee5e6b4b0d3255bfef95601890afd80709",
+ "86f7e437faa5a7fce15d1ddcb9eaeaea377667b8",
+ "a9993e364706816aba3e25717850c26c9cd0d89d",
+ "c12252ceda8be8994d5fa0290a47231c1d16aae3",
+ "32d10c7b8cf96570ca04ce37f2a19d84240d3a89",
+ "761c457bf73b14d27e9e9265c46f4b4dda11f940",
+ "50abf5706a150990a08b2c5ea40fa0e585554732",
+ "18eca4333979c4181199b7b4fab8786d16cf2846"
+};
+
+const char *SHA256_TestOutput[MDTESTCOUNT] = {
+ "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb",
+ "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
+ "f7846f55cf23e14eebeab5b4e1550cad5b509e3348fbc4efa3a1413d393cb650",
+ "71c480df93d6ae2f1efad1447c66c9525e316218cf51fc8d9ed832f2daf18b73",
+ "db4bfcbd4da0cd85a60c3c37d3fbd8805c77f15fc6b1fdfe614ee0a7c8fdb4c0",
+ "f371bc4a311f2b009eef952dd83ca80e2b60026c8e935592d0f9c308453c813e",
+ "e6eae09f10ad4122a0e2a4075761d185a272ebd9f5aa489e998ff2f09cbfdd9f"
+};
+
+const char *SHA512_TestOutput[MDTESTCOUNT] = {
+ "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e",
+ "1f40fc92da241694750979ee6cf582f2d5d7d28e18335de05abc54d0560e0f5302860c652bf08d560252aa5e74210546f369fbbbce8c12cfc7957b2652fe9a75",
+ "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f",
+ "107dbf389d9e9f71a3a95f6c055b9251bc5268c2be16d6c13492ea45b0199f3309e16455ab1e96118e8a905d5597b72038ddb372a89826046de66687bb420e7c",
+ "4dbff86cc2ca1bae1e16468a05cb9881c97f1753bce3619034898faa1aabe429955a1bf8ec483d7421fe3c1646613a59ed5441fb0f321389f77f48a879c7b1f1",
+ "1e07be23c26a86ea37ea810c8ec7809352515a970e9253c26f536cfc7a9996c45c8370583e0a78fa4a90041d71a4ceab7423f19c71b9d5a3e01249f0bebd5894",
+ "72ec1ef1124a45b047e8b7c75a932195135bb61de24ec0d1914042246e0aec3a2354e093d76f3048b456764346900cb130d2a4fd5dd16abb5e30bcb850dee843",
+ "e8a835195e039708b13d9131e025f4441dbdc521ce625f245a436dcd762f54bf5cb298d96235e6c6a304e087ec8189b9512cbdf6427737ea82793460c367b9c3"
+};
+
+const char *RIPEMD160_TestOutput[MDTESTCOUNT] = {
+ "9c1185a5c5e9fc54612808977ee8f548b2258d31",
+ "0bdc9d2d256b3ee9daae347be6f4dc835a467ffe",
+ "8eb208f7e05d987a9b044a8e98c6b087f15a0bfc",
+ "5d0689ef49d2fae572b881b123a85ffa21595f36",
+ "f71c27109c692c1b56bbdceb5b9d2865b3708dbc",
+ "b0e20b6e3116640286ed3a87a5713079b21f5189",
+ "9b752e45573d4b39f4dbd3323cab82bf63326bfb",
+ "5feb69c6bf7c29d95715ad55f57d8ac5b2b7dd32"
+};
+
+static void
+MDTestSuite(const Algorithm_t *alg)
+{
+ int i;
+ char buffer[HEX_DIGEST_LENGTH];
+
+ printf("%s test suite:\n", alg->name);
+ for (i = 0; i < MDTESTCOUNT; i++) {
+ (*alg->Data)(MDTestInput[i], strlen(MDTestInput[i]), buffer);
+ printf("%s (\"%s\") = %s", alg->name, MDTestInput[i], buffer);
+ if (strcmp(buffer, (*alg->TestOutput)[i]) == 0)
+ printf(" - verified correct\n");
+ else
+ printf(" - INCORRECT RESULT!\n");
+ }
+}
+
+/*
+ * Digests the standard input and prints the result.
+ */
+static void
+MDFilter(const Algorithm_t *alg, int tee)
+{
+ DIGEST_CTX context;
+ unsigned int len;
+ unsigned char buffer[BUFSIZ];
+ char buf[HEX_DIGEST_LENGTH];
+
+ alg->Init(&context);
+ while ((len = fread(buffer, 1, BUFSIZ, stdin))) {
+ if (tee && len != fwrite(buffer, 1, len, stdout))
+ err(1, "stdout");
+ alg->Update(&context, buffer, len);
+ }
+ printf("%s\n", alg->End(&context, buf));
+}
+
+static void
+usage(const Algorithm_t *alg)
+{
+
+ fprintf(stderr, "usage: %s [-pqrtx] [-c string] [-s string] [files ...]\n", alg->progname);
+ exit(1);
+}
diff --git a/sbin/mdconfig/Makefile b/sbin/mdconfig/Makefile
new file mode 100644
index 0000000..6be9129
--- /dev/null
+++ b/sbin/mdconfig/Makefile
@@ -0,0 +1,14 @@
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+PROG= mdconfig
+MAN= mdconfig.8
+
+LIBADD= util geom
+
+.if ${MK_TESTS} != "no"
+SUBDIR+= tests
+.endif
+
+.include <bsd.prog.mk>
diff --git a/sbin/mdconfig/mdconfig.8 b/sbin/mdconfig/mdconfig.8
new file mode 100644
index 0000000..04d3391
--- /dev/null
+++ b/sbin/mdconfig/mdconfig.8
@@ -0,0 +1,322 @@
+.\" Copyright (c) 1993 University of Utah.
+.\" Copyright (c) 1980, 1989, 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\" Copyright (c) 2000
+.\" Poul-Henning Kamp All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Systems Programming Group of the University of Utah Computer
+.\" Science Department.
+.\"
+.\" 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.
+.\"
+.\" @(#)vnconfig.8 8.1 (Berkeley) 6/5/93
+.\" from: src/usr.sbin/vnconfig/vnconfig.8,v 1.19 2000/12/27 15:30:29
+.\"
+.\" $FreeBSD$
+.\"
+.Dd November 30, 2013
+.Dt MDCONFIG 8
+.Os
+.Sh NAME
+.Nm mdconfig
+.Nd configure and enable memory disks
+.Sh SYNOPSIS
+.Nm
+.Fl a
+.Fl t Ar type
+.Op Fl n
+.Oo Fl o Oo Cm no Oc Ns Ar option Oc ...
+.Op Fl f Ar file
+.Op Fl s Ar size
+.Op Fl S Ar sectorsize
+.Op Fl u Ar unit
+.Op Fl x Ar sectors/track
+.Op Fl y Ar heads/cylinder
+.Nm
+.Fl d
+.Fl u Ar unit
+.Op Fl o Oo Cm no Oc Ns Ar force
+.Nm
+.Fl r
+.Fl u Ar unit
+.Fl s Ar size
+.Op Fl o Oo Cm no Oc Ns Ar force
+.Nm
+.Fl l
+.Op Fl n
+.Op Fl v
+.Op Fl f Ar file
+.Op Fl u Ar unit
+.Nm
+.Ar file
+.Sh DESCRIPTION
+The
+.Nm
+utility configures and enables
+.Xr md 4
+devices.
+.Pp
+Options indicate an action to be performed:
+.Bl -tag -width indent
+.It Fl a
+Attach a memory disk.
+This will configure and attach a memory disk with the
+parameters specified and attach it to the system.
+If the
+.Fl u Ar unit
+option is not provided, the newly created device name will be printed on stdout.
+.It Fl d
+Detach a memory disk from the system and release all resources.
+.It Fl r
+Resize a memory disk.
+.It Fl t Ar type
+Select the type of the memory disk.
+.Bl -tag -width "malloc"
+.It Cm malloc
+Storage for this type of memory disk is allocated with
+.Xr malloc 9 .
+This limits the size to the malloc bucket limit in the kernel.
+If the
+.Fl o Cm reserve
+option is not set, creating and filling a large
+malloc-backed memory disk is a very easy way to
+panic a system.
+.It Cm vnode
+A file specified with
+.Fl f Ar file
+becomes the backing store for this memory disk.
+.It Cm swap
+Storage for this type of memory disk is allocated from buffer
+memory.
+Pages get pushed out to swap when the system is under memory
+pressure, otherwise they stay in the operating memory.
+Using
+.Cm swap
+backing is generally preferred instead of using
+.Cm malloc
+backing.
+.It Cm null
+Bitsink; all writes do nothing, all reads return zeroes.
+.El
+.It Fl f Ar file
+Filename to use for the vnode type memory disk.
+The
+.Fl a
+and
+.Fl t Ar vnode
+options are implied if not specified.
+.It Fl l
+List configured devices.
+If given with
+.Fl u ,
+display details about that particular device.
+If given with
+.Fl f Ar file ,
+display
+.Xr md 4
+device names of which
+.Ar file
+is used as the backing store.
+If both of
+.Fl u
+and
+.Fl f
+options are specified,
+display devices which match the two conditions.
+If the
+.Fl v
+option is specified, show all details.
+.It Fl n
+When printing
+.Xr md 4
+device names, print only the unit number without the
+.Xr md 4
+prefix.
+.It Fl s Ar size
+Size of the memory disk.
+.Ar Size
+is the number of 512 byte sectors unless suffixed with a
+.Cm b , k , m , g ,
+or
+.Cm t
+which
+denotes byte, kilobyte, megabyte, gigabyte and terabyte respectively.
+The
+.Fl a
+and
+.Fl t Ar swap
+options are implied if not specified.
+.It Fl S Ar sectorsize
+Sectorsize to use for the memory disk, in bytes.
+.It Fl x Ar sectors/track
+See the description of the
+.Fl y
+option below.
+.It Fl y Ar heads/cylinder
+For
+.Cm malloc
+or
+.Cm vnode
+backed devices, the
+.Fl x
+and
+.Fl y
+options can be used to specify a synthetic geometry.
+This is useful for constructing bootable images for later download to
+other devices.
+.It Fl o Oo Cm no Oc Ns Ar option
+Set or reset options.
+.Bl -tag -width indent
+.It Oo Cm no Oc Ns Cm async
+For
+.Cm vnode
+backed devices: avoid
+.Dv IO_SYNC
+for increased performance but
+at the risk of deadlocking the entire kernel.
+.It Oo Cm no Oc Ns Cm reserve
+Allocate and reserve all needed storage from the start, rather than as needed.
+.It Oo Cm no Oc Ns Cm cluster
+Enable clustering on this disk.
+.It Oo Cm no Oc Ns Cm compress
+Enable/disable compression features to reduce memory usage.
+.It Oo Cm no Oc Ns Cm force
+Disable/enable extra sanity checks to prevent the user from doing something
+that might adversely affect the system.
+.It Oo Cm no Oc Ns Cm readonly
+Enable/disable readonly mode.
+.El
+.It Fl u Ar unit
+Request a specific unit number or device name for the
+.Xr md 4
+device instead of automatic allocation.
+If a device name is specified, it must be start with
+.Dq md
+followed by the unit number.
+.El
+.Pp
+The last form,
+.Nm
+.Ar file ,
+is provided for convenience as an abbreviation of
+.Nm
+.Fl a
+.Fl t Ar vnode
+.Fl f Ar file .
+.Sh EXAMPLES
+Create a 4 megabyte
+.Xr malloc 9
+backed memory disk.
+The name of the allocated unit will be printed on stdout, such as
+.Dq Li md3 :
+.Pp
+.Dl mdconfig -a -t malloc -s 4m
+.Pp
+Create a disk named
+.Pa /dev/md4
+with
+.Pa /tmp/boot.flp
+as backing storage:
+.Pp
+.Dl mdconfig -a -t vnode -f /tmp/boot.flp -u md4
+.Pp
+Detach and free all resources used by
+.Pa /dev/md4 :
+.Pp
+.Dl mdconfig -d -u md4
+.Pp
+Create a 128MByte swap backed disk, initialize an
+.Xr ffs 7
+file system on it, and mount it on
+.Pa /tmp :
+.Bd -literal -offset indent
+mdconfig -a -t swap -s 128M -u md10
+newfs -U /dev/md10
+mount /dev/md10 /tmp
+chmod 1777 /tmp
+.Ed
+.Pp
+Create a 5MB file-backed disk
+.Po Fl a
+and
+.Fl t Ar vnode
+are implied
+.Pc :
+.Bd -literal -offset indent
+dd if=/dev/zero of=somebackingfile bs=1k count=5k
+mdconfig -f somebackingfile -u md0
+bsdlabel -w md0 auto
+newfs md0c
+mount /dev/md0c /mnt
+.Ed
+.Pp
+Create an
+.Xr md 4
+device out of an ISO 9660 CD image file
+.Po Fl a
+and
+.Fl t Ar vnode
+are implied
+.Pc , using the first available
+.Xr md 4
+device, and then mount the new memory disk:
+.Bd -literal -offset indent
+mount -t cd9660 /dev/`mdconfig -f cdimage.iso` /mnt
+.Pp
+.Ed
+Create a file-backed device from a hard disk image that begins
+with 512K of raw header information.
+.Xr gnop 8
+is used to skip over the header information, positioning
+.Pa md1.nop
+to the start of the filesystem in the image.
+.Bd -literal -offset indent
+mdconfig -f diskimage.img -u md1
+gnop create -o 512K md1
+mount /dev/md1.nop /mnt
+.Ed
+.Sh SEE ALSO
+.Xr md 4 ,
+.Xr ffs 7 ,
+.Xr bsdlabel 8 ,
+.Xr fdisk 8 ,
+.Xr mdmfs 8 ,
+.Xr malloc 9
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 5.0
+as a cleaner replacement for the
+.Xr vn 4
+and
+.Xr vnconfig 8
+combo.
+.Sh AUTHORS
+The
+.Nm
+utility was written by
+.An Poul-Henning Kamp Aq Mt phk@FreeBSD.org .
diff --git a/sbin/mdconfig/mdconfig.c b/sbin/mdconfig/mdconfig.c
new file mode 100644
index 0000000..d741c77
--- /dev/null
+++ b/sbin/mdconfig/mdconfig.c
@@ -0,0 +1,568 @@
+/*-
+ * Copyright (c) 2000-2004 Poul-Henning Kamp <phk@FreeBSD.org>
+ * Copyright (c) 2012 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * Portions of this software were developed by Edward Tomasz Napierala
+ * under sponsorship from the FreeBSD Foundation.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/param.h>
+#include <sys/devicestat.h>
+#include <sys/ioctl.h>
+#include <sys/linker.h>
+#include <sys/mdioctl.h>
+#include <sys/module.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+
+#include <assert.h>
+#include <devstat.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <libgeom.h>
+#include <libutil.h>
+#include <paths.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+static struct md_ioctl mdio;
+static enum {UNSET, ATTACH, DETACH, RESIZE, LIST} action = UNSET;
+static int nflag;
+
+static void usage(void);
+static void md_set_file(const char *);
+static int md_find(const char *, const char *);
+static int md_query(const char *, const int, const char *);
+static int md_list(const char *, int, const char *);
+static char *geom_config_get(struct gconf *g, const char *name);
+static void md_prthumanval(char *length);
+
+#define OPT_VERBOSE 0x01
+#define OPT_UNIT 0x02
+#define OPT_DONE 0x04
+#define OPT_LIST 0x10
+
+#define CLASS_NAME_MD "MD"
+
+static void
+usage(void)
+{
+
+ fprintf(stderr,
+"usage: mdconfig -a -t type [-n] [-o [no]option] ... [-f file]\n"
+" [-s size] [-S sectorsize] [-u unit]\n"
+" [-x sectors/track] [-y heads/cylinder]\n"
+" mdconfig -d -u unit [-o [no]force]\n"
+" mdconfig -r -u unit -s size [-o [no]force]\n"
+" mdconfig -l [-v] [-n] [-f file] [-u unit]\n"
+" mdconfig file\n");
+ fprintf(stderr, "\t\ttype = {malloc, vnode, swap}\n");
+ fprintf(stderr, "\t\toption = {cluster, compress, reserve}\n");
+ fprintf(stderr, "\t\tsize = %%d (512 byte blocks), %%db (B),\n");
+ fprintf(stderr, "\t\t %%dk (kB), %%dm (MB), %%dg (GB) or\n");
+ fprintf(stderr, "\t\t %%dt (TB)\n");
+ exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+ int ch, fd, i, vflag;
+ char *p;
+ char *fflag = NULL, *sflag = NULL, *tflag = NULL, *uflag = NULL;
+
+ bzero(&mdio, sizeof(mdio));
+ mdio.md_file = malloc(PATH_MAX);
+ if (mdio.md_file == NULL)
+ err(1, "could not allocate memory");
+ vflag = 0;
+ bzero(mdio.md_file, PATH_MAX);
+
+ if (argc == 1)
+ usage();
+
+ while ((ch = getopt(argc, argv, "ab:df:lno:rs:S:t:u:vx:y:")) != -1) {
+ switch (ch) {
+ case 'a':
+ if (action != UNSET && action != ATTACH)
+ errx(1, "-a is mutually exclusive "
+ "with -d, -r, and -l");
+ action = ATTACH;
+ break;
+ case 'd':
+ if (action != UNSET && action != DETACH)
+ errx(1, "-d is mutually exclusive "
+ "with -a, -r, and -l");
+ action = DETACH;
+ mdio.md_options |= MD_AUTOUNIT;
+ break;
+ case 'r':
+ if (action != UNSET && action != RESIZE)
+ errx(1, "-r is mutually exclusive "
+ "with -a, -d, and -l");
+ action = RESIZE;
+ mdio.md_options |= MD_AUTOUNIT;
+ break;
+ case 'l':
+ if (action != UNSET && action != LIST)
+ errx(1, "-l is mutually exclusive "
+ "with -a, -r, and -d");
+ action = LIST;
+ mdio.md_options |= MD_AUTOUNIT;
+ break;
+ case 'n':
+ nflag = 1;
+ break;
+ case 't':
+ if (tflag != NULL)
+ errx(1, "-t can be passed only once");
+ tflag = optarg;
+ if (!strcmp(optarg, "malloc")) {
+ mdio.md_type = MD_MALLOC;
+ mdio.md_options |= MD_AUTOUNIT | MD_COMPRESS;
+ } else if (!strcmp(optarg, "vnode")) {
+ mdio.md_type = MD_VNODE;
+ mdio.md_options |= MD_CLUSTER | MD_AUTOUNIT | MD_COMPRESS;
+ } else if (!strcmp(optarg, "swap")) {
+ mdio.md_type = MD_SWAP;
+ mdio.md_options |= MD_CLUSTER | MD_AUTOUNIT | MD_COMPRESS;
+ } else if (!strcmp(optarg, "null")) {
+ mdio.md_type = MD_NULL;
+ mdio.md_options |= MD_CLUSTER | MD_AUTOUNIT | MD_COMPRESS;
+ } else
+ errx(1, "unknown type: %s", optarg);
+ break;
+ case 'f':
+ if (fflag != NULL)
+ errx(1, "-f can be passed only once");
+ fflag = realpath(optarg, NULL);
+ if (fflag == NULL)
+ err(1, "realpath");
+ break;
+ case 'o':
+ if (!strcmp(optarg, "async"))
+ mdio.md_options |= MD_ASYNC;
+ else if (!strcmp(optarg, "noasync"))
+ mdio.md_options &= ~MD_ASYNC;
+ else if (!strcmp(optarg, "cluster"))
+ mdio.md_options |= MD_CLUSTER;
+ else if (!strcmp(optarg, "nocluster"))
+ mdio.md_options &= ~MD_CLUSTER;
+ else if (!strcmp(optarg, "compress"))
+ mdio.md_options |= MD_COMPRESS;
+ else if (!strcmp(optarg, "nocompress"))
+ mdio.md_options &= ~MD_COMPRESS;
+ else if (!strcmp(optarg, "force"))
+ mdio.md_options |= MD_FORCE;
+ else if (!strcmp(optarg, "noforce"))
+ mdio.md_options &= ~MD_FORCE;
+ else if (!strcmp(optarg, "readonly"))
+ mdio.md_options |= MD_READONLY;
+ else if (!strcmp(optarg, "noreadonly"))
+ mdio.md_options &= ~MD_READONLY;
+ else if (!strcmp(optarg, "reserve"))
+ mdio.md_options |= MD_RESERVE;
+ else if (!strcmp(optarg, "noreserve"))
+ mdio.md_options &= ~MD_RESERVE;
+ else
+ errx(1, "unknown option: %s", optarg);
+ break;
+ case 'S':
+ mdio.md_sectorsize = strtoul(optarg, &p, 0);
+ break;
+ case 's':
+ if (sflag != NULL)
+ errx(1, "-s can be passed only once");
+ sflag = optarg;
+ mdio.md_mediasize = (off_t)strtoumax(optarg, &p, 0);
+ if (p == NULL || *p == '\0')
+ mdio.md_mediasize *= DEV_BSIZE;
+ else if (*p == 'b' || *p == 'B')
+ ; /* do nothing */
+ else if (*p == 'k' || *p == 'K')
+ mdio.md_mediasize <<= 10;
+ else if (*p == 'm' || *p == 'M')
+ mdio.md_mediasize <<= 20;
+ else if (*p == 'g' || *p == 'G')
+ mdio.md_mediasize <<= 30;
+ else if (*p == 't' || *p == 'T') {
+ mdio.md_mediasize <<= 30;
+ mdio.md_mediasize <<= 10;
+ } else
+ errx(1, "unknown suffix on -s argument");
+ break;
+ case 'u':
+ if (!strncmp(optarg, _PATH_DEV, sizeof(_PATH_DEV) - 1))
+ optarg += sizeof(_PATH_DEV) - 1;
+ if (!strncmp(optarg, MD_NAME, sizeof(MD_NAME) - 1))
+ optarg += sizeof(MD_NAME) - 1;
+ uflag = optarg;
+ break;
+ case 'v':
+ vflag = OPT_VERBOSE;
+ break;
+ case 'x':
+ mdio.md_fwsectors = strtoul(optarg, &p, 0);
+ break;
+ case 'y':
+ mdio.md_fwheads = strtoul(optarg, &p, 0);
+ break;
+ default:
+ usage();
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (action == UNSET)
+ action = ATTACH;
+
+ if (action == ATTACH) {
+ if (tflag == NULL) {
+ /*
+ * Try to infer the type based on other arguments.
+ */
+ if (fflag != NULL || argc > 0) {
+ /* Imply ``-t vnode'' */
+ mdio.md_type = MD_VNODE;
+ mdio.md_options |= MD_CLUSTER | MD_AUTOUNIT |
+ MD_COMPRESS;
+ } else if (sflag != NULL) {
+ /* Imply ``-t swap'' */
+ mdio.md_type = MD_SWAP;
+ mdio.md_options |= MD_CLUSTER | MD_AUTOUNIT |
+ MD_COMPRESS;
+ } else
+ errx(1, "unable to determine type");
+ }
+
+ if ((fflag != NULL || argc > 0) && mdio.md_type != MD_VNODE)
+ errx(1, "only -t vnode can be used with file name");
+
+ if (mdio.md_type == MD_VNODE) {
+ if (fflag != NULL) {
+ if (argc != 0)
+ usage();
+ md_set_file(fflag);
+ } else {
+ if (argc != 1)
+ usage();
+ md_set_file(*argv);
+ }
+
+ if ((mdio.md_options & MD_READONLY) == 0 &&
+ access(mdio.md_file, W_OK) < 0 &&
+ (errno == EACCES || errno == EPERM ||
+ errno == EROFS)) {
+ warnx("WARNING: opening backing store: %s "
+ "readonly", mdio.md_file);
+ mdio.md_options |= MD_READONLY;
+ }
+ }
+
+ if ((mdio.md_type == MD_MALLOC || mdio.md_type == MD_SWAP ||
+ mdio.md_type == MD_NULL) && sflag == NULL)
+ errx(1, "must specify -s for -t malloc, -t swap, "
+ "or -t null");
+ if (mdio.md_type == MD_VNODE && mdio.md_file[0] == '\0')
+ errx(1, "must specify -f for -t vnode");
+ } else {
+ if (mdio.md_sectorsize != 0)
+ errx(1, "-S can only be used with -a");
+ if (action != RESIZE && sflag != NULL)
+ errx(1, "-s can only be used with -a and -r");
+ if (mdio.md_fwsectors != 0)
+ errx(1, "-x can only be used with -a");
+ if (mdio.md_fwheads != 0)
+ errx(1, "-y can only be used with -a");
+ if (fflag != NULL && action != LIST)
+ errx(1, "-f can only be used with -a and -l");
+ if (tflag != NULL)
+ errx(1, "-t can only be used with -a");
+ if (argc > 0)
+ errx(1, "file can only be used with -a");
+ if ((action != DETACH && action != RESIZE) &&
+ (mdio.md_options & ~MD_AUTOUNIT) != 0)
+ errx(1, "-o can only be used with -a, -d, and -r");
+ if (action == DETACH &&
+ (mdio.md_options & ~(MD_FORCE | MD_AUTOUNIT)) != 0)
+ errx(1, "only -o [no]force can be used with -d");
+ if (action == RESIZE &&
+ (mdio.md_options & ~(MD_FORCE | MD_RESERVE | MD_AUTOUNIT)) != 0)
+ errx(1, "only -o [no]force and -o [no]reserve can be used with -r");
+ }
+
+ if (action == RESIZE && sflag == NULL)
+ errx(1, "must specify -s for -r");
+
+ if (action != LIST && vflag == OPT_VERBOSE)
+ errx(1, "-v can only be used with -l");
+
+ if (uflag != NULL) {
+ mdio.md_unit = strtoul(uflag, &p, 0);
+ if (mdio.md_unit == (unsigned)ULONG_MAX || *p != '\0')
+ errx(1, "bad unit: %s", uflag);
+ mdio.md_options &= ~MD_AUTOUNIT;
+ }
+
+ mdio.md_version = MDIOVERSION;
+
+ if (!kld_isloaded("g_md") && kld_load("geom_md") == -1)
+ err(1, "failed to load geom_md module");
+
+ fd = open(_PATH_DEV MDCTL_NAME, O_RDWR, 0);
+ if (fd < 0)
+ err(1, "open(%s%s)", _PATH_DEV, MDCTL_NAME);
+
+ if (action == ATTACH) {
+ i = ioctl(fd, MDIOCATTACH, &mdio);
+ if (i < 0)
+ err(1, "ioctl(%s%s)", _PATH_DEV, MDCTL_NAME);
+ if (mdio.md_options & MD_AUTOUNIT)
+ printf("%s%d\n", nflag ? "" : MD_NAME, mdio.md_unit);
+ } else if (action == DETACH) {
+ if (mdio.md_options & MD_AUTOUNIT)
+ errx(1, "-d requires -u");
+ i = ioctl(fd, MDIOCDETACH, &mdio);
+ if (i < 0)
+ err(1, "ioctl(%s%s)", _PATH_DEV, MDCTL_NAME);
+ } else if (action == RESIZE) {
+ if (mdio.md_options & MD_AUTOUNIT)
+ errx(1, "-r requires -u");
+ i = ioctl(fd, MDIOCRESIZE, &mdio);
+ if (i < 0)
+ err(1, "ioctl(%s%s)", _PATH_DEV, MDCTL_NAME);
+ } else if (action == LIST) {
+ if (mdio.md_options & MD_AUTOUNIT) {
+ /*
+ * Listing all devices. This is why we pass NULL
+ * together with OPT_LIST.
+ */
+ return (md_list(NULL, OPT_LIST | vflag, fflag));
+ } else
+ return (md_query(uflag, vflag, fflag));
+ } else
+ usage();
+ close(fd);
+ return (0);
+}
+
+static void
+md_set_file(const char *fn)
+{
+ struct stat sb;
+ int fd;
+
+ if (realpath(fn, mdio.md_file) == NULL)
+ err(1, "could not find full path for %s", fn);
+ fd = open(mdio.md_file, O_RDONLY);
+ if (fd < 0)
+ err(1, "could not open %s", fn);
+ if (fstat(fd, &sb) == -1)
+ err(1, "could not stat %s", fn);
+ if (!S_ISREG(sb.st_mode))
+ errx(1, "%s is not a regular file", fn);
+ if (mdio.md_mediasize == 0)
+ mdio.md_mediasize = sb.st_size;
+ close(fd);
+}
+
+/*
+ * Lists md(4) disks. Is used also as a query routine, since it handles XML
+ * interface. 'units' can be NULL for listing memory disks. It might be
+ * coma-separated string containing md(4) disk names. 'opt' distinguished
+ * between list and query mode.
+ */
+static int
+md_list(const char *units, int opt, const char *fflag)
+{
+ struct gmesh gm;
+ struct gprovider *pp;
+ struct gconf *gc;
+ struct gident *gid;
+ struct devstat *gsp;
+ struct ggeom *gg;
+ struct gclass *gcl;
+ void *sq;
+ int retcode, ffound, ufound;
+ char *type, *file, *length;
+
+ type = file = length = NULL;
+
+ retcode = geom_gettree(&gm);
+ if (retcode != 0)
+ return (-1);
+ retcode = geom_stats_open();
+ if (retcode != 0)
+ return (-1);
+ sq = geom_stats_snapshot_get();
+ if (sq == NULL)
+ return (-1);
+
+ ffound = ufound = 0;
+ while ((gsp = geom_stats_snapshot_next(sq)) != NULL) {
+ gid = geom_lookupid(&gm, gsp->id);
+ if (gid == NULL)
+ continue;
+ if (gid->lg_what == ISPROVIDER) {
+ pp = gid->lg_ptr;
+ gg = pp->lg_geom;
+ gcl = gg->lg_class;
+ if (strcmp(gcl->lg_name, CLASS_NAME_MD) != 0)
+ continue;
+ if ((opt & OPT_UNIT) && (units != NULL)) {
+ retcode = md_find(units, pp->lg_name);
+ if (retcode != 1)
+ continue;
+ else
+ ufound = 1;
+ }
+ gc = &pp->lg_config;
+ type = geom_config_get(gc, "type");
+ if (strcmp(type, "vnode") == 0) {
+ file = geom_config_get(gc, "file");
+ if (fflag != NULL &&
+ strcmp(fflag, file) != 0)
+ continue;
+ else
+ ffound = 1;
+ } else if (fflag != NULL)
+ continue;
+ if (nflag && strncmp(pp->lg_name, MD_NAME, 2) == 0)
+ printf("%s", pp->lg_name + 2);
+ else
+ printf("%s", pp->lg_name);
+
+ if (opt & OPT_VERBOSE ||
+ ((opt & OPT_UNIT) && fflag == NULL)) {
+ length = geom_config_get(gc, "length");
+ printf("\t%s\t", type);
+ if (length != NULL)
+ md_prthumanval(length);
+ if (file != NULL) {
+ printf("\t%s", file);
+ file = NULL;
+ }
+ }
+ opt |= OPT_DONE;
+ if ((opt & OPT_LIST) && !(opt & OPT_VERBOSE))
+ printf(" ");
+ else
+ printf("\n");
+ }
+ }
+ if ((opt & OPT_LIST) && (opt & OPT_DONE) && !(opt & OPT_VERBOSE))
+ printf("\n");
+ /* XXX: Check if it's enough to clean everything. */
+ geom_stats_snapshot_free(sq);
+ if (opt & OPT_UNIT) {
+ if (((fflag == NULL) && ufound) ||
+ ((fflag == NULL) && (units != NULL) && ufound) ||
+ ((fflag != NULL) && ffound) ||
+ ((fflag != NULL) && (units != NULL) && ufound && ffound))
+ return (0);
+ } else if (opt & OPT_LIST) {
+ if ((fflag == NULL) ||
+ ((fflag != NULL) && ffound))
+ return (0);
+ }
+ return (-1);
+}
+
+/*
+ * Returns value of 'name' from gconfig structure.
+ */
+static char *
+geom_config_get(struct gconf *g, const char *name)
+{
+ struct gconfig *gce;
+
+ LIST_FOREACH(gce, g, lg_config) {
+ if (strcmp(gce->lg_name, name) == 0)
+ return (gce->lg_val);
+ }
+ return (NULL);
+}
+
+/*
+ * List is comma separated list of MD disks. name is a
+ * device name we look for. Returns 1 if found and 0
+ * otherwise.
+ */
+static int
+md_find(const char *list, const char *name)
+{
+ int ret;
+ char num[PATH_MAX];
+ char *ptr, *p, *u;
+
+ ret = 0;
+ ptr = strdup(list);
+ if (ptr == NULL)
+ return (-1);
+ for (p = ptr; (u = strsep(&p, ",")) != NULL;) {
+ if (strncmp(u, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
+ u += sizeof(_PATH_DEV) - 1;
+ /* Just in case user specified number instead of full name */
+ snprintf(num, sizeof(num), "%s%s", MD_NAME, u);
+ if (strcmp(u, name) == 0 || strcmp(num, name) == 0) {
+ ret = 1;
+ break;
+ }
+ }
+ free(ptr);
+ return (ret);
+}
+
+static void
+md_prthumanval(char *length)
+{
+ char buf[6];
+ uintmax_t bytes;
+ char *endptr;
+
+ errno = 0;
+ bytes = strtoumax(length, &endptr, 10);
+ if (errno != 0 || *endptr != '\0' || bytes > INT64_MAX)
+ return;
+ humanize_number(buf, sizeof(buf), (int64_t)bytes, "",
+ HN_AUTOSCALE, HN_B | HN_NOSPACE | HN_DECIMAL);
+ (void)printf("%6s", buf);
+}
+
+static int
+md_query(const char *name, const int opt, const char *fflag)
+{
+
+ return (md_list(name, opt | OPT_UNIT, fflag));
+}
diff --git a/sbin/mdconfig/tests/Makefile b/sbin/mdconfig/tests/Makefile
new file mode 100644
index 0000000..08a9e47
--- /dev/null
+++ b/sbin/mdconfig/tests/Makefile
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+TESTSDIR= ${TESTSBASE}/sbin/mdconfig
+
+ATF_TESTS_SH= mdconfig_test
+
+
+TEST_METADATA.mdconfig_test+= required_user="root"
+
+.include <bsd.test.mk>
diff --git a/sbin/mdconfig/tests/mdconfig_test.sh b/sbin/mdconfig/tests/mdconfig_test.sh
new file mode 100755
index 0000000..d12e565
--- /dev/null
+++ b/sbin/mdconfig/tests/mdconfig_test.sh
@@ -0,0 +1,281 @@
+# Copyright (c) 2012 Edward Tomasz Napierała <trasz@FreeBSD.org>
+# 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.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# $FreeBSD$
+#
+
+check_diskinfo()
+{
+ local md=$1
+ local mediasize_in_bytes=$2
+ local mediasize_in_sectors=$3
+ local sectorsize=${4:-512}
+ local stripesize=${5:-0}
+ local stripeoffset=${6:-0}
+
+ atf_check -s exit:0 \
+ -o match:"/dev/$md *$sectorsize *$mediasize_in_bytes *$mediasize_in_sectors *$stripesize *$stripeoffset" \
+ -x "diskinfo /dev/$md | expand"
+}
+
+cleanup_common()
+{
+ if [ -f mdconfig.out ]; then
+ mdconfig -d -u $(sed -e 's/md//' mdconfig.out)
+ fi
+}
+
+atf_test_case attach_vnode_non_explicit_type cleanup
+attach_vnode_non_explicit_type_head()
+{
+ atf_set "descr" "Tests out -a / -f without -t"
+}
+attach_vnode_non_explicit_type_body()
+{
+ local md
+ local size_in_mb=1024
+
+ atf_check -s exit:0 -x "truncate -s ${size_in_mb}m xxx"
+ atf_check -s exit:0 -o save:mdconfig.out -x 'mdconfig -af xxx'
+ md=$(cat mdconfig.out)
+ atf_check -s exit:0 -o match:'^md[0-9]+$' -x "echo $md"
+ check_diskinfo "$md" "1073741824" "2097152"
+ # This awk strips the file path.
+ atf_check -s exit:0 -o match:"^$md vnode ${size_in_mb}M$" \
+ -x "mdconfig -lv | awk '\$1 == \"$md\" { print \$1, \$2, \$3 }'"
+}
+attach_vnode_non_explicit_type_cleanup()
+{
+ cleanup_common
+}
+
+atf_test_case attach_vnode_implicit_a_f cleanup
+attach_vnode_implicit_a_f_head()
+{
+ atf_set "descr" "Tests out implied -a / -f without -t"
+}
+attach_vnode_implicit_a_f_body()
+{
+ local md
+ local size_in_mb=1024
+
+ atf_check -s exit:0 -x "truncate -s ${size_in_mb}m xxx"
+ atf_check -s exit:0 -o save:mdconfig.out -x 'mdconfig xxx'
+ md=$(cat mdconfig.out)
+ atf_check -s exit:0 -o match:'^md[0-9]+$' -x "echo $md"
+ check_diskinfo "$md" "1073741824" "2097152"
+ # This awk strips the file path.
+ atf_check -s exit:0 -o match:"^$md vnode ${size_in_mb}M$" \
+ -x "mdconfig -lv | awk '\$1 == \"$md\" { print \$1, \$2, \$3 }'"
+}
+attach_vnode_implicit_a_f_cleanup()
+{
+ cleanup_common
+}
+
+atf_test_case attach_vnode_explicit_type cleanup
+attach_vnode_explicit_type_head()
+{
+ atf_set "descr" "Tests out implied -a / -f with -t vnode"
+}
+attach_vnode_explicit_type_body()
+{
+ local md
+ local size_in_mb=1024
+
+ atf_check -s exit:0 -x "truncate -s ${size_in_mb}m xxx"
+ atf_check -s exit:0 -o save:mdconfig.out -x 'mdconfig -af xxx -t vnode'
+ md=$(cat mdconfig.out)
+ atf_check -s exit:0 -o match:'^md[0-9]+$' -x "echo $md"
+ check_diskinfo "$md" "1073741824" "2097152"
+ # This awk strips the file path.
+ atf_check -s exit:0 -o match:"^$md vnode ${size_in_mb}M$" \
+ -x "mdconfig -lv | awk '\$1 == \"$md\" { print \$1, \$2, \$3 }'"
+}
+attach_vnode_explicit_type_cleanup()
+{
+ [ -f mdconfig.out ] && mdconfig -d -u $(sed -e 's/md//' mdconfig.out)
+ rm -f mdconfig.out xxx
+}
+
+atf_test_case attach_vnode_smaller_than_file cleanup
+attach_vnode_smaller_than_file_head()
+{
+ atf_set "descr" "Tests mdconfig -s with size less than the file size"
+}
+attach_vnode_smaller_than_file_body()
+{
+ local md
+ local size_in_mb=128
+
+ atf_check -s exit:0 -x "truncate -s 1024m xxx"
+ atf_check -s exit:0 -o save:mdconfig.out \
+ -x "mdconfig -af xxx -s ${size_in_mb}m"
+ md=$(cat mdconfig.out)
+ atf_check -s exit:0 -o match:'^md[0-9]+$' -x "echo $md"
+ check_diskinfo "$md" "134217728" "262144"
+ # This awk strips the file path.
+ atf_check -s exit:0 -o match:"^$md vnode ${size_in_mb}M$" \
+ -x "mdconfig -lv | awk '\$1 == \"$md\" { print \$1, \$2, \$3 }'"
+}
+attach_vnode_smaller_than_file_cleanup()
+{
+ cleanup_common
+}
+
+atf_test_case attach_vnode_larger_than_file cleanup
+attach_vnode_larger_than_file_head()
+{
+ atf_set "descr" "Tests mdconfig -s with size greater than the file size"
+}
+attach_vnode_larger_than_file_body()
+{
+ local md
+ local size_in_gb=128
+
+ atf_check -s exit:0 -x "truncate -s 1024m xxx"
+ atf_check -s exit:0 -o save:mdconfig.out \
+ -x "mdconfig -af xxx -s ${size_in_gb}g"
+ md=$(cat mdconfig.out)
+ atf_check -s exit:0 -o match:'^md[0-9]+$' -x "echo $md"
+ check_diskinfo "$md" "137438953472" "268435456"
+ # This awk strips the file path.
+ atf_check -s exit:0 -o match:"^$md vnode ${size_in_gb}G$" \
+ -x "mdconfig -lv | awk '\$1 == \"$md\" { print \$1, \$2, \$3 }'"
+}
+attach_vnode_larger_than_file_cleanup()
+{
+ cleanup_common
+}
+
+atf_test_case attach_vnode_sector_size cleanup
+attach_vnode_sector_size_head()
+{
+ atf_set "descr" "Tests mdconfig -s with size greater than the file size"
+}
+attach_vnode_sector_size_body()
+{
+ local md
+ local size_in_mb=1024
+
+ atf_check -s exit:0 -x "truncate -s ${size_in_mb}m xxx"
+ atf_check -s exit:0 -o save:mdconfig.out \
+ -x "mdconfig -af xxx -S 2048"
+ md=$(cat mdconfig.out)
+ atf_check -s exit:0 -o match:'^md[0-9]+$' -x "echo $md"
+ check_diskinfo "$md" "1073741824" "524288" "2048"
+ # This awk strips the file path.
+ atf_check -s exit:0 -o match:"^$md vnode ${size_in_mb}M$" \
+ -x "mdconfig -lv | awk '\$1 == \"$md\" { print \$1, \$2, \$3 }'"
+}
+attach_vnode_sector_size_cleanup()
+{
+ cleanup_common
+}
+
+atf_test_case attach_malloc cleanup
+attach_malloc_head()
+{
+ atf_set "descr" "Tests mdconfig with -t malloc"
+}
+attach_malloc_body()
+{
+ local md
+ local size_in_mb=1024
+
+ atf_check -s exit:0 -o save:mdconfig.out \
+ -x 'mdconfig -a -t malloc -s 1g'
+ md=$(cat mdconfig.out)
+ atf_check -s exit:0 -o match:'^md[0-9]+$' -x "echo $md"
+ check_diskinfo "$md" "1073741824" "2097152"
+ # This awk strips the file path.
+ atf_check -s exit:0 -o match:"^$md malloc ${size_in_mb}M$" \
+ -x "mdconfig -lv | awk '\$1 == \"$md\" { print \$1, \$2, \$3 }'"
+}
+attach_malloc_cleanup()
+{
+ cleanup_common
+}
+
+atf_test_case attach_swap cleanup
+attach_swap_head()
+{
+ atf_set "descr" "Tests mdconfig with -t swap"
+}
+attach_swap_body()
+{
+ local md
+ local size_in_mb=1024
+
+ atf_check -s exit:0 -o save:mdconfig.out \
+ -x 'mdconfig -a -t swap -s 1g'
+ md=$(cat mdconfig.out)
+ atf_check -s exit:0 -o match:'^md[0-9]+$' -x "echo $md"
+ check_diskinfo "$md" "1073741824" "2097152"
+ # This awk strips the file path.
+ atf_check -s exit:0 -o match:"^$md swap ${size_in_mb}M$" \
+ -x "mdconfig -lv | awk '\$1 == \"$md\" { print \$1, \$2, \$3 }'"
+}
+attach_swap_cleanup()
+{
+ cleanup_common
+}
+
+atf_test_case attach_with_specific_unit_number cleanup
+attach_with_specific_unit_number_head()
+{
+ atf_set "descr" "Tests mdconfig with a unit specified by -u"
+}
+attach_with_specific_unit_number_body()
+{
+ local md_unit=99
+ local size_in_mb=10
+
+ local md="md${md_unit}"
+
+ echo "$md" > mdconfig.out
+
+ atf_check -s exit:0 -o empty \
+ -x "mdconfig -a -t malloc -s ${size_in_mb}m -u $md_unit"
+ check_diskinfo "$md" "10485760" "20480"
+ # This awk strips the file path.
+ atf_check -s exit:0 -o match:"^$md malloc "$size_in_mb"M$" \
+ -x "mdconfig -lv | awk '\$1 == \"$md\" { print \$1, \$2, \$3 }'"
+}
+attach_with_specific_unit_number_cleanup()
+{
+ cleanup_common
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case attach_vnode_non_explicit_type
+ atf_add_test_case attach_vnode_explicit_type
+ atf_add_test_case attach_vnode_smaller_than_file
+ atf_add_test_case attach_vnode_larger_than_file
+ atf_add_test_case attach_vnode_sector_size
+ atf_add_test_case attach_malloc
+ atf_add_test_case attach_swap
+ atf_add_test_case attach_with_specific_unit_number
+}
diff --git a/sbin/mdmfs/Makefile b/sbin/mdmfs/Makefile
new file mode 100644
index 0000000..999793f
--- /dev/null
+++ b/sbin/mdmfs/Makefile
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+PROG= mdmfs
+LINKS= ${BINDIR}/${PROG} ${BINDIR}/mount_mfs
+MAN= mdmfs.8
+MLINKS+= mdmfs.8 mount_mfs.8
+
+.include <bsd.prog.mk>
diff --git a/sbin/mdmfs/mdmfs.8 b/sbin/mdmfs/mdmfs.8
new file mode 100644
index 0000000..ab5e1be
--- /dev/null
+++ b/sbin/mdmfs/mdmfs.8
@@ -0,0 +1,377 @@
+.\"
+.\" Copyright (c) 2001 Dima Dorfman.
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd September 4, 2011
+.Dt MDMFS 8
+.Os
+.Sh NAME
+.Nm mdmfs ,
+.Nm mount_mfs
+.Nd configure and mount an in-memory file system using the
+.Xr md 4
+driver
+.Sh SYNOPSIS
+.Nm
+.Op Fl DLlMNPStUX
+.Op Fl a Ar maxcontig
+.Op Fl b Ar block-size
+.Op Fl c Ar blocks-per-cylinder-group
+.Op Fl d Ar max-extent-size
+.Op Fl E Ar path-mdconfig
+.Op Fl e Ar maxbpg
+.Op Fl F Ar file
+.Op Fl f Ar frag-size
+.Op Fl i Ar bytes
+.Op Fl m Ar percent-free
+.Op Fl n Ar rotational-positions
+.Op Fl O Ar optimization
+.Op Fl o Ar mount-options
+.Op Fl p Ar permissions
+.Op Fl s Ar size
+.Op Fl v Ar version
+.Op Fl w Ar user : Ns Ar group
+.Ar md-device
+.Ar mount-point
+.Sh DESCRIPTION
+The
+.Nm
+utility is designed to be a work-alike and look-alike of the deprecated
+.Xr mount_mfs 8 .
+The end result is essentially the same,
+but is accomplished in a completely different way.
+The
+.Nm
+utility configures an
+.Xr md 4
+disk using
+.Xr mdconfig 8 ,
+puts a UFS file system on it (unless
+.Fl P
+was specified) using
+.Xr newfs 8 ,
+and mounts it using
+.Xr mount 8 .
+It can handle
+.Xr geom_uzip 4
+compressed disk images, as long as the kernel supports this GEOM class.
+All the command line options are passed to the appropriate program
+at the appropriate stage in order to achieve the desired effect.
+.Pp
+By default,
+.Nm
+creates a swap-based
+.Pq Dv MD_SWAP
+disk with soft-updates enabled
+and mounts it on
+.Ar mount-point .
+It uses the
+.Xr md 4
+device specified by
+.Ar md-device .
+If
+.Ar md-device
+is
+.Ql md
+(no unit number),
+it will use
+.Xr md 4 Ns 's
+auto-unit feature to automatically select an unused device.
+Unless otherwise specified with one of the options below,
+it uses the default arguments to all the helper programs.
+.Pp
+The following options are available.
+Where possible,
+the option letter matches the one used by
+.Xr mount_mfs 8
+for the same thing.
+.Bl -tag -width indent
+.It Fl a Ar maxcontig
+Specify the maximum number of contiguous blocks that will be laid
+out before forcing a rotational delay
+(see the
+.Fl d
+option).
+.It Fl b Ar block-size
+The block size of the file system, in bytes.
+.It Fl c Ar blocks-per-cylinder-group
+The number of blocks per cylinder group in the file system.
+.It Fl D
+If not using auto-unit,
+do not run
+.Xr mdconfig 8
+to try to detach the unit before attaching it.
+.It Fl d Ar max-extent-size
+The file system may choose to store large files using extents.
+This parameter specifies the largest extent size that may be
+used. It is presently limited to its default value which is 16
+times the file system blocksize.
+.It Fl E Ar path-mdconfig
+Use
+.Ar path-mdconfig
+as a location of the
+.Xr mdconfig 8
+utility.
+.It Fl e Ar maxbpg
+Indicate the maximum number of blocks any single file can allocate
+out of a cylinder group before it is forced to begin allocating
+blocks from another cylinder group.
+.It Fl F Ar file
+Create a vnode-backed
+.Pq Dv MD_VNODE
+memory disk backed by
+.Ar file .
+.It Fl f Ar frag-size
+The fragment size of the file system in bytes.
+.It Fl i Ar bytes
+Number of bytes per inode.
+.It Fl l
+Enable multilabel MAC on the new file system.
+.It Fl L
+Show the output of the helper programs.
+By default,
+it is sent to
+.Pa /dev/null .
+.It Fl M
+Create a
+.Xr malloc 9
+backed disk
+.Pq Dv MD_MALLOC
+instead of a swap-backed disk.
+.It Fl m Ar percent-free
+The percentage of space reserved for the superuser.
+.It Fl N
+Do not actually run the helper programs.
+This is most useful in conjunction with
+.Fl X .
+.It Fl n Ar rotational-positions
+The default number of rotational positions to distinguish.
+.It Fl O Ar optimization
+Select the optimization preference;
+valid choices are
+.Cm space
+and
+.Cm time ,
+which will optimize for minimum space fragmentation and
+minimum time spent allocating blocks,
+respectively.
+.It Fl o Ar mount-options
+Specify the mount options with which to mount the file system.
+See
+.Xr mount 8
+for more information.
+.It Fl P
+Preserve the existing file system;
+do not run
+.Xr newfs 8 .
+This only makes sense if
+.Fl F
+is specified to create a vnode-backed disk.
+.It Fl p Ar permissions
+Set the file (directory) permissions of the mount point
+.Ar mount-point
+to
+.Ar permissions .
+The
+.Ar permissions
+argument can be in any of the mode formats recognized by
+.Xr chmod 1 .
+If symbolic permissions are specified,
+the operation characters
+.Dq +
+and
+.Dq -
+are interpreted relative to the initial permissions of
+.Dq a=rwx .
+.It Fl S
+Do not enable soft-updates on the file system.
+.It Fl s Ar size
+Specify the size of the disk to create.
+This only makes sense if
+.Fl F
+is
+.Em not
+specified.
+That is,
+this will work for the default swap-backed
+.Pq Dv MD_SWAP
+disks,
+and the optional
+.Pq Fl M
+.Xr malloc 9
+backed disks
+.Pq Dv MD_MALLOC .
+.It Fl t
+Turn on the TRIM enable flag for
+.Xr newfs 8 .
+The
+.Xr md 4
+device supports the BIO_DELETE command, enabling the TRIM on created
+filesystem allows return of freed memory to the system pool.
+.It Fl U
+Enable soft-updates on the file system.
+This is the default, and is accepted only
+for compatibility.
+It is only really useful to negate the
+.Fl S
+flag, should such a need occur.
+.It Fl v Ar version
+Specify the UFS version number for use on the file system; it may be
+either
+.Dv 1
+or
+.Dv 2 .
+The default is derived from the default of the
+.Xr newfs 8
+command.
+.It Fl w Ar user : Ns Ar group
+Set the owner and group to
+.Ar user
+and
+.Ar group ,
+respectively.
+The arguments have the same semantics as with
+.Xr chown 8 ,
+but specifying just a
+.Ar user
+or just a
+.Ar group
+is not supported.
+.It Fl X
+Print what command will be run before running it, and
+other assorted debugging information.
+.El
+.Pp
+The
+.Fl F
+and
+.Fl s
+options are passed to
+.Xr mdconfig 8
+as
+.Fl f
+and
+.Fl s ,
+respectively.
+The
+.Fl a , b , c , d , e , f , i , m
+and
+.Fl n
+options are passed to
+.Xr newfs 8
+with the same letter;
+the
+.Fl O
+option is passed to
+.Xr newfs 8
+as
+.Fl o .
+The
+.Fl o
+option is passed to
+.Xr mount 8
+with the same letter.
+See the programs that the options are passed to for more information
+on their semantics.
+.Sh EXAMPLES
+Create and mount a 32 megabyte swap-backed file system on
+.Pa /tmp :
+.Pp
+.Dl "mdmfs -s 32m md /tmp"
+.Pp
+The same file system created as an entry in
+.Pa /etc/fstab :
+.Pp
+.Dl "md /tmp mfs rw,-s32m 2 0"
+.Pp
+Create and mount a 16 megabyte malloc-backed file system on
+.Pa /tmp
+using the
+.Pa /dev/md1
+device;
+furthermore,
+do not use soft-updates on it and mount it
+.Cm async :
+.Pp
+.Dl "mdmfs -M -S -o async -s 16m md1 /tmp"
+.Pp
+Create and mount a
+.Xr geom_uzip 4
+based compressed disk image:
+.Pp
+.Dl "mdmfs -P -F foo.uzip -oro md.uzip /tmp/"
+.Pp
+Mount the same image, specifying the
+.Pa /dev/md1
+device:
+.Pp
+.Dl "mdmfs -P -F foo.uzip -oro md1.uzip /tmp/"
+.Pp
+Configure a vnode-backed file system and mount its first partition,
+using automatic device numbering:
+.Pp
+.Dl "mdmfs -P -F foo.img mds1a /tmp/"
+.Sh COMPATIBILITY
+The
+.Nm
+utility, while designed to be compatible with
+.Xr mount_mfs 8 ,
+can be useful by itself.
+Since
+.Xr mount_mfs 8
+had some silly defaults, a
+.Dq compatibility
+mode is provided for the case where bug-to-bug compatibility is desired.
+.Pp
+Compatibility is enabled by starting
+.Nm
+with the name
+.Li mount_mfs
+or
+.Li mfs
+(as returned by
+.Xr getprogname 3 ) .
+In this mode, the following behavior, as done by
+.Xr mount_mfs 8 ,
+is duplicated:
+.Bl -bullet -offset indent
+.It
+The file mode of
+.Ar mount-point
+is set by default to
+.Li 01777
+as if
+.Fl p Ar 1777
+was given on the command line.
+.El
+.Sh SEE ALSO
+.Xr md 4 ,
+.Xr fstab 5 ,
+.Xr mdconfig 8 ,
+.Xr mount 8 ,
+.Xr newfs 8
+.Sh AUTHORS
+.An Dima Dorfman
diff --git a/sbin/mdmfs/mdmfs.c b/sbin/mdmfs/mdmfs.c
new file mode 100644
index 0000000..66a6911
--- /dev/null
+++ b/sbin/mdmfs/mdmfs.c
@@ -0,0 +1,696 @@
+/*
+ * Copyright (c) 2001 Dima Dorfman.
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * mdmfs (md/MFS) is a wrapper around mdconfig(8),
+ * newfs(8), and mount(8) that mimics the command line option set of
+ * the deprecated mount_mfs(8).
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/mdioctl.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <assert.h>
+#include <err.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <paths.h>
+#include <pwd.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+
+typedef enum { false, true } bool;
+
+struct mtpt_info {
+ uid_t mi_uid;
+ bool mi_have_uid;
+ gid_t mi_gid;
+ bool mi_have_gid;
+ mode_t mi_mode;
+ bool mi_have_mode;
+ bool mi_forced_pw;
+};
+
+static bool debug; /* Emit debugging information? */
+static bool loudsubs; /* Suppress output from helper programs? */
+static bool norun; /* Actually run the helper programs? */
+static int unit; /* The unit we're working with. */
+static const char *mdname; /* Name of memory disk device (e.g., "md"). */
+static const char *mdsuffix; /* Suffix of memory disk device (e.g., ".uzip"). */
+static size_t mdnamelen; /* Length of mdname. */
+static const char *path_mdconfig =_PATH_MDCONFIG;
+
+static void argappend(char **, const char *, ...) __printflike(2, 3);
+static void debugprintf(const char *, ...) __printflike(1, 2);
+static void do_mdconfig_attach(const char *, const enum md_types);
+static void do_mdconfig_attach_au(const char *, const enum md_types);
+static void do_mdconfig_detach(void);
+static void do_mount(const char *, const char *);
+static void do_mtptsetup(const char *, struct mtpt_info *);
+static void do_newfs(const char *);
+static void extract_ugid(const char *, struct mtpt_info *);
+static int run(int *, const char *, ...) __printflike(2, 3);
+static void usage(void);
+
+int
+main(int argc, char **argv)
+{
+ struct mtpt_info mi; /* Mountpoint info. */
+ char *mdconfig_arg, *newfs_arg, /* Args to helper programs. */
+ *mount_arg;
+ enum md_types mdtype; /* The type of our memory disk. */
+ bool have_mdtype;
+ bool detach, softdep, autounit, newfs;
+ char *mtpoint, *unitstr;
+ char *p;
+ int ch;
+ void *set;
+ unsigned long ul;
+
+ /* Misc. initialization. */
+ (void)memset(&mi, '\0', sizeof(mi));
+ detach = true;
+ softdep = true;
+ autounit = false;
+ newfs = true;
+ have_mdtype = false;
+ mdtype = MD_SWAP;
+ mdname = MD_NAME;
+ mdnamelen = strlen(mdname);
+ /*
+ * Can't set these to NULL. They may be passed to the
+ * respective programs without modification. I.e., we may not
+ * receive any command-line options which will caused them to
+ * be modified.
+ */
+ mdconfig_arg = strdup("");
+ newfs_arg = strdup("");
+ mount_arg = strdup("");
+
+ /* If we were started as mount_mfs or mfs, imply -C. */
+ if (strcmp(getprogname(), "mount_mfs") == 0 ||
+ strcmp(getprogname(), "mfs") == 0) {
+ /* Make compatibility assumptions. */
+ mi.mi_mode = 01777;
+ mi.mi_have_mode = true;
+ }
+
+ while ((ch = getopt(argc, argv,
+ "a:b:Cc:Dd:E:e:F:f:hi:LlMm:NnO:o:Pp:Ss:tUv:w:X")) != -1)
+ switch (ch) {
+ case 'a':
+ argappend(&newfs_arg, "-a %s", optarg);
+ break;
+ case 'b':
+ argappend(&newfs_arg, "-b %s", optarg);
+ break;
+ case 'C':
+ /* Ignored for compatibility. */
+ break;
+ case 'c':
+ argappend(&newfs_arg, "-c %s", optarg);
+ break;
+ case 'D':
+ detach = false;
+ break;
+ case 'd':
+ argappend(&newfs_arg, "-d %s", optarg);
+ break;
+ case 'E':
+ path_mdconfig = optarg;
+ break;
+ case 'e':
+ argappend(&newfs_arg, "-e %s", optarg);
+ break;
+ case 'F':
+ if (have_mdtype)
+ usage();
+ mdtype = MD_VNODE;
+ have_mdtype = true;
+ argappend(&mdconfig_arg, "-f %s", optarg);
+ break;
+ case 'f':
+ argappend(&newfs_arg, "-f %s", optarg);
+ break;
+ case 'h':
+ usage();
+ break;
+ case 'i':
+ argappend(&newfs_arg, "-i %s", optarg);
+ break;
+ case 'L':
+ loudsubs = true;
+ break;
+ case 'l':
+ argappend(&newfs_arg, "-l");
+ break;
+ case 'M':
+ if (have_mdtype)
+ usage();
+ mdtype = MD_MALLOC;
+ have_mdtype = true;
+ break;
+ case 'm':
+ argappend(&newfs_arg, "-m %s", optarg);
+ break;
+ case 'N':
+ norun = true;
+ break;
+ case 'n':
+ argappend(&newfs_arg, "-n");
+ break;
+ case 'O':
+ argappend(&newfs_arg, "-o %s", optarg);
+ break;
+ case 'o':
+ argappend(&mount_arg, "-o %s", optarg);
+ break;
+ case 'P':
+ newfs = false;
+ break;
+ case 'p':
+ if ((set = setmode(optarg)) == NULL)
+ usage();
+ mi.mi_mode = getmode(set, S_IRWXU | S_IRWXG | S_IRWXO);
+ mi.mi_have_mode = true;
+ mi.mi_forced_pw = true;
+ free(set);
+ break;
+ case 'S':
+ softdep = false;
+ break;
+ case 's':
+ argappend(&mdconfig_arg, "-s %s", optarg);
+ break;
+ case 't':
+ argappend(&newfs_arg, "-t");
+ break;
+ case 'U':
+ softdep = true;
+ break;
+ case 'v':
+ argappend(&newfs_arg, "-O %s", optarg);
+ break;
+ case 'w':
+ extract_ugid(optarg, &mi);
+ mi.mi_forced_pw = true;
+ break;
+ case 'X':
+ debug = true;
+ break;
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc < 2)
+ usage();
+
+ /* Derive 'unit' (global). */
+ unitstr = argv[0];
+ if (strncmp(unitstr, "/dev/", 5) == 0)
+ unitstr += 5;
+ if (strncmp(unitstr, mdname, mdnamelen) == 0)
+ unitstr += mdnamelen;
+ if (!isdigit(*unitstr)) {
+ autounit = true;
+ unit = -1;
+ mdsuffix = unitstr;
+ } else {
+ ul = strtoul(unitstr, &p, 10);
+ if (ul == ULONG_MAX)
+ errx(1, "bad device unit: %s", unitstr);
+ unit = ul;
+ mdsuffix = p; /* can be empty */
+ }
+
+ mtpoint = argv[1];
+ if (!have_mdtype)
+ mdtype = MD_SWAP;
+ if (softdep)
+ argappend(&newfs_arg, "-U");
+ if (mdtype != MD_VNODE && !newfs)
+ errx(1, "-P requires a vnode-backed disk");
+
+ /* Do the work. */
+ if (detach && !autounit)
+ do_mdconfig_detach();
+ if (autounit)
+ do_mdconfig_attach_au(mdconfig_arg, mdtype);
+ else
+ do_mdconfig_attach(mdconfig_arg, mdtype);
+ if (newfs)
+ do_newfs(newfs_arg);
+ do_mount(mount_arg, mtpoint);
+ do_mtptsetup(mtpoint, &mi);
+
+ return (0);
+}
+
+/*
+ * Append the expansion of 'fmt' to the buffer pointed to by '*dstp';
+ * reallocate as required.
+ */
+static void
+argappend(char **dstp, const char *fmt, ...)
+{
+ char *old, *new;
+ va_list ap;
+
+ old = *dstp;
+ assert(old != NULL);
+
+ va_start(ap, fmt);
+ if (vasprintf(&new, fmt,ap) == -1)
+ errx(1, "vasprintf");
+ va_end(ap);
+
+ *dstp = new;
+ if (asprintf(&new, "%s %s", old, new) == -1)
+ errx(1, "asprintf");
+ free(*dstp);
+ free(old);
+
+ *dstp = new;
+}
+
+/*
+ * If run-time debugging is enabled, print the expansion of 'fmt'.
+ * Otherwise, do nothing.
+ */
+static void
+debugprintf(const char *fmt, ...)
+{
+ va_list ap;
+
+ if (!debug)
+ return;
+ fprintf(stderr, "DEBUG: ");
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ fprintf(stderr, "\n");
+ fflush(stderr);
+}
+
+/*
+ * Attach a memory disk with a known unit.
+ */
+static void
+do_mdconfig_attach(const char *args, const enum md_types mdtype)
+{
+ int rv;
+ const char *ta; /* Type arg. */
+
+ switch (mdtype) {
+ case MD_SWAP:
+ ta = "-t swap";
+ break;
+ case MD_VNODE:
+ ta = "-t vnode";
+ break;
+ case MD_MALLOC:
+ ta = "-t malloc";
+ break;
+ default:
+ abort();
+ }
+ rv = run(NULL, "%s -a %s%s -u %s%d", path_mdconfig, ta, args,
+ mdname, unit);
+ if (rv)
+ errx(1, "mdconfig (attach) exited with error code %d", rv);
+}
+
+/*
+ * Attach a memory disk with an unknown unit; use autounit.
+ */
+static void
+do_mdconfig_attach_au(const char *args, const enum md_types mdtype)
+{
+ const char *ta; /* Type arg. */
+ char *linep, *linebuf; /* Line pointer, line buffer. */
+ int fd; /* Standard output of mdconfig invocation. */
+ FILE *sfd;
+ int rv;
+ char *p;
+ size_t linelen;
+ unsigned long ul;
+
+ switch (mdtype) {
+ case MD_SWAP:
+ ta = "-t swap";
+ break;
+ case MD_VNODE:
+ ta = "-t vnode";
+ break;
+ case MD_MALLOC:
+ ta = "-t malloc";
+ break;
+ default:
+ abort();
+ }
+ rv = run(&fd, "%s -a %s%s", path_mdconfig, ta, args);
+ if (rv)
+ errx(1, "mdconfig (attach) exited with error code %d", rv);
+
+ /* Receive the unit number. */
+ if (norun) { /* Since we didn't run, we can't read. Fake it. */
+ unit = 0;
+ return;
+ }
+ sfd = fdopen(fd, "r");
+ if (sfd == NULL)
+ err(1, "fdopen");
+ linep = fgetln(sfd, &linelen);
+ if (linep == NULL && linelen < mdnamelen + 1)
+ errx(1, "unexpected output from mdconfig (attach)");
+ /* If the output format changes, we want to know about it. */
+ assert(strncmp(linep, mdname, mdnamelen) == 0);
+ linebuf = malloc(linelen - mdnamelen + 1);
+ assert(linebuf != NULL);
+ /* Can't use strlcpy because linep is not NULL-terminated. */
+ strncpy(linebuf, linep + mdnamelen, linelen);
+ linebuf[linelen] = '\0';
+ ul = strtoul(linebuf, &p, 10);
+ if (ul == ULONG_MAX || *p != '\n')
+ errx(1, "unexpected output from mdconfig (attach)");
+ unit = ul;
+
+ fclose(sfd);
+ close(fd);
+}
+
+/*
+ * Detach a memory disk.
+ */
+static void
+do_mdconfig_detach(void)
+{
+ int rv;
+
+ rv = run(NULL, "%s -d -u %s%d", path_mdconfig, mdname, unit);
+ if (rv && debug) /* This is allowed to fail. */
+ warnx("mdconfig (detach) exited with error code %d (ignored)",
+ rv);
+}
+
+/*
+ * Mount the configured memory disk.
+ */
+static void
+do_mount(const char *args, const char *mtpoint)
+{
+ int rv;
+
+ rv = run(NULL, "%s%s /dev/%s%d%s %s", _PATH_MOUNT, args,
+ mdname, unit, mdsuffix, mtpoint);
+ if (rv)
+ errx(1, "mount exited with error code %d", rv);
+}
+
+/*
+ * Various configuration of the mountpoint. Mostly, enact 'mip'.
+ */
+static void
+do_mtptsetup(const char *mtpoint, struct mtpt_info *mip)
+{
+ struct statfs sfs;
+
+ if (!mip->mi_have_mode && !mip->mi_have_uid && !mip->mi_have_gid)
+ return;
+
+ if (!norun) {
+ if (statfs(mtpoint, &sfs) == -1) {
+ warn("statfs: %s", mtpoint);
+ return;
+ }
+ if ((sfs.f_flags & MNT_RDONLY) != 0) {
+ if (mip->mi_forced_pw) {
+ warnx(
+ "Not changing mode/owner of %s since it is read-only",
+ mtpoint);
+ } else {
+ debugprintf(
+ "Not changing mode/owner of %s since it is read-only",
+ mtpoint);
+ }
+ return;
+ }
+ }
+
+ if (mip->mi_have_mode) {
+ debugprintf("changing mode of %s to %o.", mtpoint,
+ mip->mi_mode);
+ if (!norun)
+ if (chmod(mtpoint, mip->mi_mode) == -1)
+ err(1, "chmod: %s", mtpoint);
+ }
+ /*
+ * We have to do these separately because the user may have
+ * only specified one of them.
+ */
+ if (mip->mi_have_uid) {
+ debugprintf("changing owner (user) or %s to %u.", mtpoint,
+ mip->mi_uid);
+ if (!norun)
+ if (chown(mtpoint, mip->mi_uid, -1) == -1)
+ err(1, "chown %s to %u (user)", mtpoint,
+ mip->mi_uid);
+ }
+ if (mip->mi_have_gid) {
+ debugprintf("changing owner (group) or %s to %u.", mtpoint,
+ mip->mi_gid);
+ if (!norun)
+ if (chown(mtpoint, -1, mip->mi_gid) == -1)
+ err(1, "chown %s to %u (group)", mtpoint,
+ mip->mi_gid);
+ }
+}
+
+/*
+ * Put a file system on the memory disk.
+ */
+static void
+do_newfs(const char *args)
+{
+ int rv;
+
+ rv = run(NULL, "%s%s /dev/%s%d", _PATH_NEWFS, args, mdname, unit);
+ if (rv)
+ errx(1, "newfs exited with error code %d", rv);
+}
+
+/*
+ * 'str' should be a user and group name similar to the last argument
+ * to chown(1); i.e., a user, followed by a colon, followed by a
+ * group. The user and group in 'str' may be either a [ug]id or a
+ * name. Upon return, the uid and gid fields in 'mip' will contain
+ * the uid and gid of the user and group name in 'str', respectively.
+ *
+ * In other words, this derives a user and group id from a string
+ * formatted like the last argument to chown(1).
+ *
+ * Notice: At this point we don't support only a username or only a
+ * group name. do_mtptsetup already does, so when this feature is
+ * desired, this is the only routine that needs to be changed.
+ */
+static void
+extract_ugid(const char *str, struct mtpt_info *mip)
+{
+ char *ug; /* Writable 'str'. */
+ char *user, *group; /* Result of extracton. */
+ struct passwd *pw;
+ struct group *gr;
+ char *p;
+ uid_t *uid;
+ gid_t *gid;
+
+ uid = &mip->mi_uid;
+ gid = &mip->mi_gid;
+ mip->mi_have_uid = mip->mi_have_gid = false;
+
+ /* Extract the user and group from 'str'. Format above. */
+ ug = strdup(str);
+ assert(ug != NULL);
+ group = ug;
+ user = strsep(&group, ":");
+ if (user == NULL || group == NULL || *user == '\0' || *group == '\0')
+ usage();
+
+ /* Derive uid. */
+ *uid = strtoul(user, &p, 10);
+ if (*uid == (uid_t)ULONG_MAX)
+ usage();
+ if (*p != '\0') {
+ pw = getpwnam(user);
+ if (pw == NULL)
+ errx(1, "invalid user: %s", user);
+ *uid = pw->pw_uid;
+ }
+ mip->mi_have_uid = true;
+
+ /* Derive gid. */
+ *gid = strtoul(group, &p, 10);
+ if (*gid == (gid_t)ULONG_MAX)
+ usage();
+ if (*p != '\0') {
+ gr = getgrnam(group);
+ if (gr == NULL)
+ errx(1, "invalid group: %s", group);
+ *gid = gr->gr_gid;
+ }
+ mip->mi_have_gid = true;
+
+ free(ug);
+}
+
+/*
+ * Run a process with command name and arguments pointed to by the
+ * formatted string 'cmdline'. Since system(3) is not used, the first
+ * space-delimited token of 'cmdline' must be the full pathname of the
+ * program to run. The return value is the return code of the process
+ * spawned. If 'ofd' is non-NULL, it is set to the standard output of
+ * the program spawned (i.e., you can read from ofd and get the output
+ * of the program).
+ */
+static int
+run(int *ofd, const char *cmdline, ...)
+{
+ char **argv, **argvp; /* Result of splitting 'cmd'. */
+ int argc;
+ char *cmd; /* Expansion of 'cmdline'. */
+ int pid, status; /* Child info. */
+ int pfd[2]; /* Pipe to the child. */
+ int nfd; /* Null (/dev/null) file descriptor. */
+ bool dup2dn; /* Dup /dev/null to stdout? */
+ va_list ap;
+ char *p;
+ int rv, i;
+
+ dup2dn = true;
+ va_start(ap, cmdline);
+ rv = vasprintf(&cmd, cmdline, ap);
+ if (rv == -1)
+ err(1, "vasprintf");
+ va_end(ap);
+
+ /* Split up 'cmd' into 'argv' for use with execve. */
+ for (argc = 1, p = cmd; (p = strchr(p, ' ')) != NULL; p++)
+ argc++; /* 'argc' generation loop. */
+ argv = (char **)malloc(sizeof(*argv) * (argc + 1));
+ assert(argv != NULL);
+ for (p = cmd, argvp = argv; (*argvp = strsep(&p, " ")) != NULL;)
+ if (**argvp != '\0')
+ if (++argvp >= &argv[argc]) {
+ *argvp = NULL;
+ break;
+ }
+ assert(*argv);
+ /* The argv array ends up NULL-terminated here. */
+
+ /* Make sure the above loop works as expected. */
+ if (debug) {
+ /*
+ * We can't, but should, use debugprintf here. First,
+ * it appends a trailing newline to the output, and
+ * second it prepends "DEBUG: " to the output. The
+ * former is a problem for this would-be first call,
+ * and the latter for the would-be call inside the
+ * loop.
+ */
+ (void)fprintf(stderr, "DEBUG: running:");
+ /* Should be equivalent to 'cmd' (before strsep, of course). */
+ for (i = 0; argv[i] != NULL; i++)
+ (void)fprintf(stderr, " %s", argv[i]);
+ (void)fprintf(stderr, "\n");
+ }
+
+ /* Create a pipe if necessary and fork the helper program. */
+ if (ofd != NULL) {
+ if (pipe(&pfd[0]) == -1)
+ err(1, "pipe");
+ *ofd = pfd[0];
+ dup2dn = false;
+ }
+ pid = fork();
+ switch (pid) {
+ case 0:
+ /* XXX can we call err() in here? */
+ if (norun)
+ _exit(0);
+ if (ofd != NULL)
+ if (dup2(pfd[1], STDOUT_FILENO) < 0)
+ err(1, "dup2");
+ if (!loudsubs) {
+ nfd = open(_PATH_DEVNULL, O_RDWR);
+ if (nfd == -1)
+ err(1, "open: %s", _PATH_DEVNULL);
+ if (dup2(nfd, STDIN_FILENO) < 0)
+ err(1, "dup2");
+ if (dup2dn)
+ if (dup2(nfd, STDOUT_FILENO) < 0)
+ err(1, "dup2");
+ if (dup2(nfd, STDERR_FILENO) < 0)
+ err(1, "dup2");
+ }
+
+ (void)execv(argv[0], argv);
+ warn("exec: %s", argv[0]);
+ _exit(-1);
+ case -1:
+ err(1, "fork");
+ }
+
+ free(cmd);
+ free(argv);
+ while (waitpid(pid, &status, 0) != pid)
+ ;
+ return (WEXITSTATUS(status));
+}
+
+static void
+usage(void)
+{
+
+ fprintf(stderr,
+"usage: %s [-DLlMNnPStUX] [-a maxcontig] [-b block-size]\n"
+"\t[-c blocks-per-cylinder-group][-d max-extent-size] [-E path-mdconfig]\n"
+"\t[-e maxbpg] [-F file] [-f frag-size] [-i bytes] [-m percent-free]\n"
+"\t[-O optimization] [-o mount-options]\n"
+"\t[-p permissions] [-s size] [-v version] [-w user:group]\n"
+"\tmd-device mount-point\n", getprogname());
+ exit(1);
+}
diff --git a/sbin/mknod/Makefile b/sbin/mknod/Makefile
new file mode 100644
index 0000000..63235b2
--- /dev/null
+++ b/sbin/mknod/Makefile
@@ -0,0 +1,7 @@
+# @(#)Makefile 8.1 (Berkeley) 6/5/93
+# $FreeBSD$
+
+PROG= mknod
+MAN= mknod.8
+
+.include <bsd.prog.mk>
diff --git a/sbin/mknod/mknod.8 b/sbin/mknod/mknod.8
new file mode 100644
index 0000000..fd58ba1
--- /dev/null
+++ b/sbin/mknod/mknod.8
@@ -0,0 +1,152 @@
+.\" Copyright (c) 1980, 1991, 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.
+.\" 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.
+.\"
+.\" @(#)mknod.8 8.2 (Berkeley) 12/11/93
+.\" $FreeBSD$
+.\"
+.Dd January 31, 2010
+.Dt MKNOD 8
+.Os
+.Sh NAME
+.Nm mknod
+.Nd build special file
+.Sh SYNOPSIS
+.Nm
+.Ar name
+.Nm
+.Ar name
+.Op Cm b | c
+.Ar major minor
+.Op Ar owner : Ns Ar group
+.Sh DESCRIPTION
+.Bf -symbolic
+The
+.Nm
+utility is deprecated on modern
+.Fx
+systems.
+.Ef
+.Pp
+The
+.Nm
+utility creates device special files.
+To make nodes manually, the arguments are:
+.Bl -tag -width indent
+.It Ar name
+Device name, for example
+.Pa /dev/da0
+for a SCSI disk or
+.Pa /dev/pts/0
+for pseudo-terminals.
+.It Cm b | c
+Type of device.
+If the
+device is a block type device such as a tape or disk drive which needs
+both cooked and raw special files,
+the type is
+.Cm b .
+All other devices are character type devices, such as terminal
+and pseudo devices, and are type
+.Cm c .
+.It Ar major
+The major device number is an integer number which tells the kernel
+which device driver entry point to use.
+.It Ar minor
+The minor device number tells the kernel which subunit
+the node corresponds to on the device; for example,
+a subunit may be a file system partition
+or a tty line.
+.It Ar owner : Ns Ar group
+The
+.Ar owner
+.Ar group
+operand pair is optional, however, if one is specified, they both must be
+specified.
+The
+.Ar owner
+may be either a numeric user ID or a user name.
+If a user name is also a numeric user ID, the operand is used as a
+user name.
+The
+.Ar group
+may be either a numeric group ID or a group name.
+Similar to the user name,
+if a group name is also a numeric group ID, the operand is used as a
+group name.
+.El
+.Pp
+Major and minor device numbers can be given in any format acceptable to
+.Xr strtoul 3 ,
+so that a leading
+.Ql 0x
+indicates a hexadecimal number, and a leading
+.Ql 0
+will cause the number to be interpreted as octal.
+.Pp
+The
+.Nm
+utility can be used to recreate deleted device nodes under a
+.Xr devfs 5
+mount point by invoking it with only a filename as an argument.
+Example:
+.Pp
+.Dl "mknod /dev/cd0"
+.Pp
+where
+.Pa /dev/cd0
+is the name of the deleted device node.
+.Sh COMPATIBILITY
+The
+.Xr chown 8 Ns - Ns
+like functionality is specific to
+.Fx .
+.Pp
+As of
+.Fx 4.0 ,
+block devices were deprecated in favour of character
+devices.
+As of
+.Fx 5.0 ,
+device nodes are managed by the device file system
+.Xr devfs 5 ,
+making the
+.Nm
+utility superfluous.
+As of
+.Fx 6.0
+device nodes may be created in regular file systems but such
+nodes cannot be used to access devices.
+.Sh SEE ALSO
+.Xr mkfifo 1 ,
+.Xr mknod 2 ,
+.Xr devfs 5 ,
+.Xr chown 8
+.Sh HISTORY
+A
+.Nm
+utility appeared in
+.At v6 .
diff --git a/sbin/mknod/mknod.c b/sbin/mknod/mknod.c
new file mode 100644
index 0000000..76bb2e5
--- /dev/null
+++ b/sbin/mknod/mknod.c
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kevin Fall.
+ *
+ * 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.
+ * 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 lint
+static const char copyright[] =
+"@(#) Copyright (c) 1989, 1993\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)mknod.c 8.1 (Berkeley) 6/5/93";
+#endif
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <grp.h>
+#include <pwd.h>
+#include <string.h>
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr,
+ "usage: mknod name\n"
+ " mknod name [b | c] major minor [owner:group]\n");
+ exit(1);
+}
+
+static u_long
+id(const char *name, const char *type)
+{
+ u_long val;
+ char *ep;
+
+ /*
+ * XXX
+ * We know that uid_t's and gid_t's are unsigned longs.
+ */
+ errno = 0;
+ val = strtoul(name, &ep, 10);
+ if (errno)
+ err(1, "%s", name);
+ if (*ep != '\0')
+ errx(1, "%s: illegal %s name", name, type);
+ return (val);
+}
+
+static gid_t
+a_gid(const char *s)
+{
+ struct group *gr;
+
+ if (*s == '\0') /* Argument was "uid[:.]". */
+ errx(1, "group must be specified when the owner is");
+ return ((gr = getgrnam(s)) == NULL) ? id(s, "group") : gr->gr_gid;
+}
+
+static uid_t
+a_uid(const char *s)
+{
+ struct passwd *pw;
+
+ if (*s == '\0') /* Argument was "[:.]gid". */
+ errx(1, "owner must be specified when the group is");
+ return ((pw = getpwnam(s)) == NULL) ? id(s, "user") : pw->pw_uid;
+}
+
+int
+main(int argc, char **argv)
+{
+ int range_error;
+ uid_t uid;
+ gid_t gid;
+ mode_t mode;
+ dev_t dev;
+ char *cp, *endp;
+ long mymajor, myminor;
+
+ if (argc != 2 && argc != 5 && argc != 6)
+ usage();
+
+ if (argc >= 5) {
+ mode = 0666;
+ if (argv[2][0] == 'c')
+ mode |= S_IFCHR;
+ else if (argv[2][0] == 'b')
+ mode |= S_IFBLK;
+ else
+ errx(1, "node must be type 'b' or 'c'");
+
+ errno = 0;
+ mymajor = (long)strtoul(argv[3], &endp, 0);
+ if (endp == argv[3] || *endp != '\0')
+ errx(1, "%s: non-numeric major number", argv[3]);
+ range_error = errno;
+ errno = 0;
+ myminor = (long)strtoul(argv[4], &endp, 0);
+ if (endp == argv[4] || *endp != '\0')
+ errx(1, "%s: non-numeric minor number", argv[4]);
+ range_error |= errno;
+ dev = makedev(mymajor, myminor);
+ if (range_error || major(dev) != mymajor ||
+ (long)(u_int)minor(dev) != myminor)
+ errx(1, "major or minor number too large");
+ } else {
+ mode = 0666 | S_IFCHR;
+ dev = 0;
+ }
+
+ uid = gid = -1;
+ if (6 == argc) {
+ /* have owner:group */
+ if ((cp = strchr(argv[5], ':')) != NULL) {
+ *cp++ = '\0';
+ gid = a_gid(cp);
+ } else
+ usage();
+ uid = a_uid(argv[5]);
+ }
+
+ if (mknod(argv[1], mode, dev) != 0)
+ err(1, "%s", argv[1]);
+ if (6 == argc)
+ if (chown(argv[1], uid, gid))
+ err(1, "setting ownership on %s", argv[1]);
+ exit(0);
+}
diff --git a/sbin/mksnap_ffs/Makefile b/sbin/mksnap_ffs/Makefile
new file mode 100644
index 0000000..de96fa0
--- /dev/null
+++ b/sbin/mksnap_ffs/Makefile
@@ -0,0 +1,20 @@
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../mount
+
+PROG= mksnap_ffs
+SRCS= mksnap_ffs.c getmntopts.c
+MAN= mksnap_ffs.8
+
+WARNS?= 2
+CFLAGS+=-I${.CURDIR}/../mount
+
+.if defined(NOSUID)
+BINMODE=554
+.else
+BINMODE=4554
+BINOWN= root
+.endif
+BINGRP= operator
+
+.include <bsd.prog.mk>
diff --git a/sbin/mksnap_ffs/mksnap_ffs.8 b/sbin/mksnap_ffs/mksnap_ffs.8
new file mode 100644
index 0000000..df4bbf9
--- /dev/null
+++ b/sbin/mksnap_ffs/mksnap_ffs.8
@@ -0,0 +1,90 @@
+.\"
+.\" Copyright (c) 2003 Networks Associates Technology, Inc.
+.\" All rights reserved.
+.\"
+.\" This software was developed for the FreeBSD Project by Marshall
+.\" Kirk McKusick and Network Associates Laboratories, the Security
+.\" Research Division of Network Associates, Inc. under DARPA/SPAWAR
+.\" contract N66001-01-C-8035 ("CBOSS"), as part of the DARPA CHATS
+.\" research program.
+.\"
+.\" 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 names of the authors 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 AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd February 14, 2011
+.Dt MKSNAP_FFS 8
+.Os
+.Sh NAME
+.Nm mksnap_ffs
+.Nd take a file system snapshot
+.Sh SYNOPSIS
+.Nm
+.Ar snapshot_name
+.Sh DESCRIPTION
+The
+.Nm
+utility creates a snapshot named
+.Ar snapshot_name .
+.Pp
+The group ownership of the file is set to
+.Dq Li operator ;
+the owner of the file remains
+.Dq Li root .
+The mode of the snapshot is set to be readable by the owner
+or members of the
+.Dq Li operator
+group.
+.Sh EXAMPLES
+Create a snapshot of
+.Pa /usr/home
+file system and mount the snapshot elsewhere:
+.Bd -literal -offset indent
+mksnap_ffs /usr/home/snapshot
+mdconfig -a -t vnode -o readonly -f /usr/home/snapshot
+mount -o ro /dev/md0 /mnt/
+.Ed
+.Sh SEE ALSO
+.Xr chmod 2 ,
+.Xr chown 8 ,
+.Xr mdconfig 8 ,
+.Xr mount 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 5.0 .
+.Sh CAVEATS
+The disk full situation is not handled gracefully and may
+lead to a system panic when no free blocks are found.
+.Pp
+Every filesystem can have only up to 20 active snapshots.
+When this limit is reached, attempting to create more snapshots
+fails with
+.Er ENOSPC ,
+and
+.Nm
+reports that it is
+.Dq out of space .
diff --git a/sbin/mksnap_ffs/mksnap_ffs.c b/sbin/mksnap_ffs/mksnap_ffs.c
new file mode 100644
index 0000000..7dcf5c2
--- /dev/null
+++ b/sbin/mksnap_ffs/mksnap_ffs.c
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2003 Networks Associates Technology, Inc.
+ * All rights reserved.
+ *
+ * This software was developed for the FreeBSD Project by Marshall
+ * Kirk McKusick and Network Associates Laboratories, the Security
+ * Research Division of Network Associates, Inc. under DARPA/SPAWAR
+ * contract N66001-01-C-8035 ("CBOSS"), as part of the DARPA CHATS
+ * research program.
+ *
+ * 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 names of the authors 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 AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/param.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <ufs/ufs/ufsmount.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <limits.h>
+#include <mntopts.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+static void
+usage(void)
+{
+
+ errx(EX_USAGE, "usage: mksnap_ffs snapshot_name");
+}
+
+int
+main(int argc, char **argv)
+{
+ char errmsg[255], path[PATH_MAX];
+ char *cp, *snapname;
+ struct statfs stfsbuf;
+ struct group *grp;
+ struct stat stbuf;
+ struct iovec *iov;
+ int fd, iovlen;
+
+ if (argc == 2)
+ snapname = argv[1];
+ else if (argc == 3)
+ snapname = argv[2]; /* Old usage. */
+ else
+ usage();
+
+ /*
+ * Check that the user running this program has permission
+ * to create and remove a snapshot file from the directory
+ * in which they have requested to have it made. If the
+ * directory is sticky and not owned by the user, then they
+ * will not be able to remove the snapshot when they are
+ * done with it.
+ */
+ if (strlen(snapname) >= PATH_MAX)
+ errx(1, "pathname too long %s", snapname);
+ cp = strrchr(snapname, '/');
+ if (cp == NULL) {
+ strlcpy(path, ".", PATH_MAX);
+ } else if (cp == snapname) {
+ strlcpy(path, "/", PATH_MAX);
+ } else {
+ strlcpy(path, snapname, cp - snapname + 1);
+ }
+ if (statfs(path, &stfsbuf) < 0)
+ err(1, "%s", path);
+ if (stat(path, &stbuf) < 0)
+ err(1, "%s", path);
+ if (!S_ISDIR(stbuf.st_mode))
+ errx(1, "%s: Not a directory", path);
+ if (access(path, W_OK) < 0)
+ err(1, "Lack write permission in %s", path);
+ if ((stbuf.st_mode & S_ISTXT) && stbuf.st_uid != getuid())
+ errx(1, "Lack write permission in %s: Sticky bit set", path);
+
+ /*
+ * Having verified access to the directory in which the
+ * snapshot is to be built, proceed with creating it.
+ */
+ if ((grp = getgrnam("operator")) == NULL)
+ errx(1, "Cannot retrieve operator gid");
+
+ iov = NULL;
+ iovlen = 0;
+ build_iovec(&iov, &iovlen, "fstype", "ffs", 4);
+ build_iovec(&iov, &iovlen, "from", snapname, (size_t)-1);
+ build_iovec(&iov, &iovlen, "fspath", stfsbuf.f_mntonname, (size_t)-1);
+ build_iovec(&iov, &iovlen, "errmsg", errmsg, sizeof(errmsg));
+ build_iovec(&iov, &iovlen, "update", NULL, 0);
+ build_iovec(&iov, &iovlen, "snapshot", NULL, 0);
+
+ *errmsg = '\0';
+ if (nmount(iov, iovlen, stfsbuf.f_flags) < 0) {
+ errmsg[sizeof(errmsg) - 1] = '\0';
+ err(1, "Cannot create snapshot %s%s%s", snapname,
+ *errmsg != '\0' ? ": " : "", errmsg);
+ }
+ if ((fd = open(snapname, O_RDONLY)) < 0)
+ err(1, "Cannot open %s", snapname);
+ if (fstat(fd, &stbuf) != 0)
+ err(1, "Cannot stat %s", snapname);
+ if ((stbuf.st_flags & SF_SNAPSHOT) == 0)
+ errx(1, "File %s is not a snapshot", snapname);
+ if (fchown(fd, -1, grp->gr_gid) != 0)
+ err(1, "Cannot chown %s", snapname);
+ if (fchmod(fd, S_IRUSR | S_IRGRP) != 0)
+ err(1, "Cannot chmod %s", snapname);
+
+ exit(EXIT_SUCCESS);
+}
diff --git a/sbin/mount/Makefile b/sbin/mount/Makefile
new file mode 100644
index 0000000..f95085a
--- /dev/null
+++ b/sbin/mount/Makefile
@@ -0,0 +1,11 @@
+# @(#)Makefile 8.6 (Berkeley) 5/8/95
+# $FreeBSD$
+
+PROG= mount
+SRCS= mount.c mount_fs.c getmntopts.c vfslist.c
+MAN= mount.8 mount.conf.8
+# We do NOT install the getmntopts.3 man page.
+
+LIBADD= util
+
+.include <bsd.prog.mk>
diff --git a/sbin/mount/extern.h b/sbin/mount/extern.h
new file mode 100644
index 0000000..91e2ec4
--- /dev/null
+++ b/sbin/mount/extern.h
@@ -0,0 +1,33 @@
+/*-
+ * Copyright (c) 1997 FreeBSD Inc.
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+/* vfslist.c */
+int checkvfsname(const char *, const char **);
+const char **makevfslist(char *);
+
+int mount_fs(const char *, int, char *[]);
diff --git a/sbin/mount/getmntopts.3 b/sbin/mount/getmntopts.3
new file mode 100644
index 0000000..48c6940
--- /dev/null
+++ b/sbin/mount/getmntopts.3
@@ -0,0 +1,181 @@
+.\" Copyright (c) 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.
+.\" 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.
+.\"
+.\" @(#)getmntopts.3 8.3 (Berkeley) 3/30/95
+.\" $FreeBSD$
+.\"
+.Dd February 17, 2008
+.Dt GETMNTOPTS 3
+.Os
+.Sh NAME
+.Nm getmntopts
+.Nd scan mount options
+.Sh SYNOPSIS
+.Fd #include \&"mntopts.h"
+.Ft void
+.Fo getmntopts
+.Fa "const char *options" "const struct mntopt *mopts"
+.Fa "int *flagp" "int *altflagp"
+.Fc
+.Sh DESCRIPTION
+The
+.Fn getmntopts
+function takes a comma separated option list and a list
+of valid option names, and computes the bitmask
+corresponding to the requested set of options.
+.Pp
+The string
+.Fa options
+is broken down into a sequence of comma separated tokens.
+Each token is looked up in the table described by
+.Fa mopts
+and the bits in
+the word referenced by either
+.Fa flagp
+or
+.Fa altflagp
+(depending on the
+.Va m_altloc
+field of the option's table entry)
+are updated.
+The flag words are not initialized by
+.Fn getmntopts .
+The table,
+.Fa mopts ,
+has the following format:
+.Bd -literal
+struct mntopt {
+ char *m_option; /* option name */
+ int m_inverse; /* is this a negative option, e.g., "dev" */
+ int m_flag; /* bit to set, e.g., MNT_RDONLY */
+ int m_altloc; /* non-zero to use altflagp rather than flagp */
+};
+.Ed
+.Pp
+The members of this structure are:
+.Bl -tag -width m_inverse
+.It Va m_option
+the option name,
+for example
+.Dq Li suid .
+.It Va m_inverse
+tells
+.Fn getmntopts
+that the name has the inverse meaning of the
+bit.
+For example,
+.Dq Li suid
+is the string, whereas the
+mount flag is
+.Dv MNT_NOSUID .
+In this case, the sense of the string and the flag
+are inverted, so the
+.Va m_inverse
+flag should be set.
+.It Va m_flag
+the value of the bit to be set or cleared in
+the flag word when the option is recognized.
+The bit is set when the option is discovered,
+but cleared if the option name was preceded
+by the letters
+.Dq Li no .
+The
+.Va m_inverse
+flag causes these two operations to be reversed.
+.It Va m_altloc
+the bit should be set or cleared in
+.Fa altflagp
+rather than
+.Fa flagp .
+.El
+.Pp
+Each of the user visible
+.Dv MNT_
+flags has a corresponding
+.Dv MOPT_
+macro which defines an appropriate
+.Vt "struct mntopt"
+entry.
+To simplify the program interface and ensure consistency across all
+programs, a general purpose macro,
+.Dv MOPT_STDOPTS ,
+is defined which
+contains an entry for all the generic VFS options.
+In addition, the macros
+.Dv MOPT_FORCE
+and
+.Dv MOPT_UPDATE
+exist to enable the
+.Dv MNT_FORCE
+and
+.Dv MNT_UPDATE
+flags to be set.
+Finally, the table must be terminated by an entry with a
+.Dv NULL
+first element.
+.Sh EXAMPLES
+Most commands will use the standard option set.
+Local file systems which support the
+.Dv MNT_UPDATE
+flag, would also have an
+.Dv MOPT_UPDATE
+entry.
+This can be declared and used as follows:
+.Bd -literal
+#include "mntopts.h"
+
+struct mntopt mopts[] = {
+ MOPT_STDOPTS,
+ MOPT_UPDATE,
+ { NULL }
+};
+
+ ...
+ mntflags = mntaltflags = 0;
+ ...
+ getmntopts(options, mopts, &mntflags, &mntaltflags);
+ ...
+.Ed
+.Sh DIAGNOSTICS
+If the external integer variable
+.Va getmnt_silent
+is zero, then the
+.Fn getmntopts
+function displays an error message and exits if an
+unrecognized option is encountered.
+Otherwise unrecognized options are silently ignored.
+By default
+.Va getmnt_silent
+is zero.
+.Sh SEE ALSO
+.Xr err 3 ,
+.Xr mount 8
+.Sh HISTORY
+The
+.Fn getmntopts
+function appeared in
+.Bx 4.4 .
diff --git a/sbin/mount/getmntopts.c b/sbin/mount/getmntopts.c
new file mode 100644
index 0000000..f8a3453
--- /dev/null
+++ b/sbin/mount/getmntopts.c
@@ -0,0 +1,183 @@
+/*-
+ * Copyright (c) 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.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)getmntopts.c 8.3 (Berkeley) 3/29/95";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+
+#include <err.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mntopts.h"
+
+int getmnt_silent = 0;
+
+void
+getmntopts(const char *options, const struct mntopt *m0, int *flagp,
+ int *altflagp)
+{
+ const struct mntopt *m;
+ int negative, len;
+ char *opt, *optbuf, *p;
+ int *thisflagp;
+
+ /* Copy option string, since it is about to be torn asunder... */
+ if ((optbuf = strdup(options)) == NULL)
+ err(1, NULL);
+
+ for (opt = optbuf; (opt = strtok(opt, ",")) != NULL; opt = NULL) {
+ /* Check for "no" prefix. */
+ if (opt[0] == 'n' && opt[1] == 'o') {
+ negative = 1;
+ opt += 2;
+ } else
+ negative = 0;
+
+ /*
+ * for options with assignments in them (ie. quotas)
+ * ignore the assignment as it's handled elsewhere
+ */
+ p = strchr(opt, '=');
+ if (p != NULL)
+ *++p = '\0';
+
+ /* Scan option table. */
+ for (m = m0; m->m_option != NULL; ++m) {
+ len = strlen(m->m_option);
+ if (strncasecmp(opt, m->m_option, len) == 0)
+ if (opt[len] == '\0' || opt[len] == '=')
+ break;
+ }
+
+ /* Save flag, or fail if option is not recognized. */
+ if (m->m_option) {
+ thisflagp = m->m_altloc ? altflagp : flagp;
+ if (negative == m->m_inverse)
+ *thisflagp |= m->m_flag;
+ else
+ *thisflagp &= ~m->m_flag;
+ } else if (!getmnt_silent) {
+ errx(1, "-o %s: option not supported", opt);
+ }
+ }
+
+ free(optbuf);
+}
+
+void
+rmslashes(char *rrpin, char *rrpout)
+{
+ char *rrpoutstart;
+
+ *rrpout = *rrpin;
+ for (rrpoutstart = rrpout; *rrpin != '\0'; *rrpout++ = *rrpin++) {
+
+ /* skip all double slashes */
+ while (*rrpin == '/' && *(rrpin + 1) == '/')
+ rrpin++;
+ }
+
+ /* remove trailing slash if necessary */
+ if (rrpout - rrpoutstart > 1 && *(rrpout - 1) == '/')
+ *(rrpout - 1) = '\0';
+ else
+ *rrpout = '\0';
+}
+
+int
+checkpath(const char *path, char *resolved)
+{
+ struct stat sb;
+
+ if (realpath(path, resolved) == NULL || stat(resolved, &sb) != 0)
+ return (1);
+ if (!S_ISDIR(sb.st_mode)) {
+ errno = ENOTDIR;
+ return (1);
+ }
+ return (0);
+}
+
+void
+build_iovec(struct iovec **iov, int *iovlen, const char *name, void *val,
+ size_t len)
+{
+ int i;
+
+ if (*iovlen < 0)
+ return;
+ i = *iovlen;
+ *iov = realloc(*iov, sizeof **iov * (i + 2));
+ if (*iov == NULL) {
+ *iovlen = -1;
+ return;
+ }
+ (*iov)[i].iov_base = strdup(name);
+ (*iov)[i].iov_len = strlen(name) + 1;
+ i++;
+ (*iov)[i].iov_base = val;
+ if (len == (size_t)-1) {
+ if (val != NULL)
+ len = strlen(val) + 1;
+ else
+ len = 0;
+ }
+ (*iov)[i].iov_len = (int)len;
+ *iovlen = ++i;
+}
+
+/*
+ * This function is needed for compatibility with parameters
+ * which used to use the mount_argf() command for the old mount() syscall.
+ */
+void
+build_iovec_argf(struct iovec **iov, int *iovlen, const char *name,
+ const char *fmt, ...)
+{
+ va_list ap;
+ char val[255] = { 0 };
+
+ va_start(ap, fmt);
+ vsnprintf(val, sizeof(val), fmt, ap);
+ va_end(ap);
+ build_iovec(iov, iovlen, name, strdup(val), (size_t)-1);
+}
diff --git a/sbin/mount/mntopts.h b/sbin/mount/mntopts.h
new file mode 100644
index 0000000..d273dde
--- /dev/null
+++ b/sbin/mount/mntopts.h
@@ -0,0 +1,101 @@
+/*-
+ * Copyright (c) 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.
+ * 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.
+ *
+ * @(#)mntopts.h 8.7 (Berkeley) 3/29/95
+ * $FreeBSD$
+ */
+
+struct mntopt {
+ const char *m_option; /* option name */
+ int m_inverse; /* if a negative option, e.g. "atime" */
+ long long m_flag; /* bit to set, e.g. MNT_RDONLY */
+ int m_altloc; /* 1 => set bit in altflags */
+};
+
+/* User-visible MNT_ flags. */
+#define MOPT_ASYNC { "async", 0, MNT_ASYNC, 0 }
+#define MOPT_NOATIME { "atime", 1, MNT_NOATIME, 0 }
+#define MOPT_NOEXEC { "exec", 1, MNT_NOEXEC, 0 }
+#define MOPT_NOSUID { "suid", 1, MNT_NOSUID, 0 }
+#define MOPT_NOSYMFOLLOW { "symfollow", 1, MNT_NOSYMFOLLOW, 0 }
+#define MOPT_RDONLY { "rdonly", 0, MNT_RDONLY, 0 }
+#define MOPT_SYNC { "sync", 0, MNT_SYNCHRONOUS, 0 }
+#define MOPT_UNION { "union", 0, MNT_UNION, 0 }
+#define MOPT_USERQUOTA { "userquota", 0, 0, 0 }
+#define MOPT_GROUPQUOTA { "groupquota", 0, 0, 0 }
+#define MOPT_NOCLUSTERR { "clusterr", 1, MNT_NOCLUSTERR, 0 }
+#define MOPT_NOCLUSTERW { "clusterw", 1, MNT_NOCLUSTERW, 0 }
+#define MOPT_SUIDDIR { "suiddir", 0, MNT_SUIDDIR, 0 }
+#define MOPT_SNAPSHOT { "snapshot", 0, MNT_SNAPSHOT, 0 }
+#define MOPT_MULTILABEL { "multilabel", 0, MNT_MULTILABEL, 0 }
+#define MOPT_ACLS { "acls", 0, MNT_ACLS, 0 }
+#define MOPT_NFS4ACLS { "nfsv4acls", 0, MNT_NFS4ACLS, 0 }
+#define MOPT_AUTOMOUNTED { "automounted",0, MNT_AUTOMOUNTED, 0 }
+
+/* Control flags. */
+#define MOPT_FORCE { "force", 0, MNT_FORCE, 0 }
+#define MOPT_UPDATE { "update", 0, MNT_UPDATE, 0 }
+#define MOPT_RO { "ro", 0, MNT_RDONLY, 0 }
+#define MOPT_RW { "rw", 1, MNT_RDONLY, 0 }
+
+/* This is parsed by mount(8), but is ignored by specific mount_*(8)s. */
+#define MOPT_AUTO { "auto", 0, 0, 0 }
+
+/* A handy macro as terminator of MNT_ array. */
+#define MOPT_END { NULL, 0, 0, 0 }
+
+#define MOPT_FSTAB_COMPAT \
+ MOPT_RO, \
+ MOPT_RW, \
+ MOPT_AUTO
+
+/* Standard options which all mounts can understand. */
+#define MOPT_STDOPTS \
+ MOPT_USERQUOTA, \
+ MOPT_GROUPQUOTA, \
+ MOPT_FSTAB_COMPAT, \
+ MOPT_NOATIME, \
+ MOPT_NOEXEC, \
+ MOPT_SUIDDIR, /* must be before MOPT_NOSUID */ \
+ MOPT_NOSUID, \
+ MOPT_NOSYMFOLLOW, \
+ MOPT_RDONLY, \
+ MOPT_UNION, \
+ MOPT_NOCLUSTERR, \
+ MOPT_NOCLUSTERW, \
+ MOPT_MULTILABEL, \
+ MOPT_ACLS, \
+ MOPT_NFS4ACLS, \
+ MOPT_AUTOMOUNTED
+
+void getmntopts(const char *, const struct mntopt *, int *, int *);
+void rmslashes(char *, char *);
+int checkpath(const char *, char resolved_path[]);
+extern int getmnt_silent;
+void build_iovec(struct iovec **iov, int *iovlen, const char *name, void *val, size_t len);
+void build_iovec_argf(struct iovec **iov, int *iovlen, const char *name, const char *fmt, ...);
diff --git a/sbin/mount/mount.8 b/sbin/mount/mount.8
new file mode 100644
index 0000000..2b1ac52
--- /dev/null
+++ b/sbin/mount/mount.8
@@ -0,0 +1,592 @@
+.\" Copyright (c) 1980, 1989, 1991, 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.
+.\" 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.
+.\"
+.\" @(#)mount.8 8.8 (Berkeley) 6/16/94
+.\" $FreeBSD$
+.\"
+.Dd December 3, 2014
+.Dt MOUNT 8
+.Os
+.Sh NAME
+.Nm mount
+.Nd mount file systems
+.Sh SYNOPSIS
+.Nm
+.Op Fl adflpruvw
+.Op Fl F Ar fstab
+.Op Fl o Ar options
+.Op Fl t Cm ufs | Ar external_type
+.Nm
+.Op Fl dfpruvw
+.Ar special | node
+.Nm
+.Op Fl dfpruvw
+.Op Fl o Ar options
+.Op Fl t Cm ufs | Ar external_type
+.Ar special node
+.Sh DESCRIPTION
+The
+.Nm
+utility calls the
+.Xr nmount 2
+system call to prepare and graft a
+.Ar special
+device or the remote node (rhost:path) on to the file system tree at the point
+.Ar node .
+If either
+.Ar special
+or
+.Ar node
+are not provided, the appropriate information is taken from the
+.Xr fstab 5
+file.
+.Pp
+The system maintains a list of currently mounted file systems.
+If no arguments are given to
+.Nm ,
+this list is printed.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl a
+All the file systems described in
+.Xr fstab 5
+are mounted.
+Exceptions are those marked as
+.Dq Li noauto ,
+those marked as
+.Dq Li late
+(unless the
+.Fl l
+option was specified),
+those excluded by the
+.Fl t
+flag (see below), or if they are already mounted (except the
+root file system which is always remounted to preserve
+traditional single user mode behavior).
+.It Fl d
+Causes everything to be done except for the actual system call.
+This option is useful in conjunction with the
+.Fl v
+flag to
+determine what the
+.Nm
+command is trying to do.
+.It Fl F Ar fstab
+Specify the
+.Pa fstab
+file to use.
+.It Fl f
+Forces the revocation of write access when trying to downgrade
+a file system mount status from read-write to read-only.
+Also
+forces the R/W mount of an unclean file system (dangerous; use with
+caution).
+.It Fl L
+When used in conjunction with the
+.Fl a
+option, mount
+.Em only
+those file systems which are marked as
+.Dq Li late .
+.It Fl l
+When used in conjunction with the
+.Fl a
+option, also mount those file systems which are marked as
+.Dq Li late .
+.It Fl n
+For compatibility with some other implementations, this flag is
+currently a no-op.
+.It Fl o
+Options are specified with a
+.Fl o
+flag followed by a comma separated string of options.
+In case of conflicting options being specified, the rightmost option
+takes effect.
+The following options are available:
+.Bl -tag -width indent
+.It Cm acls
+Enable POSIX.1e Access Control Lists, or ACLs, which can be customized via the
+.Xr setfacl 1
+and
+.Xr getfacl 1
+commands.
+This flag is mutually exclusive with
+.Cm nfsv4acls
+flag.
+.It Cm async
+All
+.Tn I/O
+to the file system should be done asynchronously.
+This is a
+.Em dangerous
+flag to set, since it does not guarantee that the file system structure
+on the disk will remain consistent.
+For this reason, the
+.Cm async
+flag should be used sparingly, and only when some data recovery
+mechanism is present.
+.It Cm automounted
+This flag indicates that the file system was mounted by
+.Xr automountd 8 .
+Automounted file systems are automatically unmounted by
+.Xr autounmountd 8 .
+.It Cm current
+When used with the
+.Fl u
+flag, this is the same as specifying the options currently in effect for
+the mounted file system.
+.It Cm force
+The same as
+.Fl f ;
+forces the revocation of write access when trying to downgrade
+a file system mount status from read-write to read-only.
+Also
+forces the R/W mount of an unclean file system (dangerous; use with caution).
+.It Cm fstab
+When used with the
+.Fl u
+flag, this is the same as specifying all the options listed in the
+.Xr fstab 5
+file for the file system.
+.It Cm late
+This file system should be skipped when
+.Nm
+is run with the
+.Fl a
+flag but without the
+.Fl l
+flag.
+.It Cm mountprog Ns = Ns Aq Ar program
+Force
+.Nm
+to use the specified program to mount the file system, instead of calling
+.Xr nmount 2
+directly.
+For example:
+.Bd -literal
+mount -t foofs -o mountprog=/mydir/fooprog /dev/acd0 /mnt
+.Ed
+.It Cm multilabel
+Enable multi-label Mandatory Access Control, or MAC, on the specified file
+system.
+If the file system supports multilabel operation, individual labels will
+be maintained for each object in the file system, rather than using a
+single label for all objects.
+An alternative to the
+.Fl l
+flag in
+.Xr tunefs 8 .
+See
+.Xr mac 4
+for more information, which cause the multilabel mount flag to be set
+automatically at mount-time.
+.It Cm nfsv4acls
+Enable NFSv4 ACLs, which can be customized via the
+.Xr setfacl 1
+and
+.Xr getfacl 1
+commands.
+This flag is mutually exclusive with
+.Cm acls
+flag.
+.It Cm noasync
+Metadata I/O should be done synchronously, while data I/O should be done
+asynchronously.
+This is the default.
+.It Cm noatime
+Do not update the file access time when reading from a file.
+This option
+is useful on file systems where there are large numbers of files and
+performance is more critical than updating the file access time (which is
+rarely ever important).
+This option is currently only supported on local file systems.
+.It Cm noauto
+This file system should be skipped when
+.Nm
+is run with the
+.Fl a
+flag.
+.It Cm noclusterr
+Disable read clustering.
+.It Cm noclusterw
+Disable write clustering.
+.It Cm noexec
+Do not allow execution of any binaries on the mounted file system.
+This option is useful for a server that has file systems containing
+binaries for architectures other than its own.
+Note: This option was not designed as a security feature and no
+guarantee is made that it will prevent malicious code execution; for
+example, it is still possible to execute scripts which reside on a
+.Cm noexec
+mounted partition.
+.It Cm nosuid
+Do not allow set-user-identifier or set-group-identifier bits to take effect.
+Note: this option is worthless if a public available suid or sgid
+wrapper like
+.Xr suidperl 1
+is installed on your system.
+It is set automatically when the user does not have super-user privileges.
+.It Cm nosymfollow
+Do not follow symlinks
+on the mounted file system.
+.It Cm ro
+The same as
+.Fl r ;
+mount the file system read-only (even the super-user may not write it).
+.It Cm snapshot
+This option allows a snapshot of the specified file system to be taken.
+The
+.Fl u
+flag is required with this option.
+Note that snapshot files must be created in the file system that is being
+snapshotted.
+You may create up to 20 snapshots per file system.
+Active snapshots are recorded in the superblock, so they persist across unmount
+and remount operations and across system reboots.
+When you are done with a snapshot, it can be removed with the
+.Xr rm 1
+command.
+Snapshots may be removed in any order, however you may not get back all the
+space contained in the snapshot as another snapshot may claim some of the blocks
+that it is releasing.
+Note that the schg flag is set on snapshots to ensure that not even the root
+user can write to them.
+The unlink command makes an exception for snapshot files in that it allows them
+to be removed even though they have the schg flag set, so it is not necessary to
+clear the schg flag before removing a snapshot file.
+.Pp
+Once you have taken a snapshot, there are three interesting things that you can
+do with it:
+.Pp
+.Bl -enum -compact
+.It
+Run
+.Xr fsck 8
+on the snapshot file.
+Assuming that the file system was clean when it was mounted, you should always
+get a clean (and unchanging) result from running fsck on the snapshot.
+This is essentially what the background fsck process does.
+.Pp
+.It
+Run
+.Xr dump 8
+on the snapshot.
+You will get a dump that is consistent with the file system as of the timestamp
+of the snapshot.
+.Pp
+.It
+Mount the snapshot as a frozen image of the file system.
+To mount the snapshot
+.Pa /var/snapshot/snap1 :
+.Bd -literal
+mdconfig -a -t vnode -f /var/snapshot/snap1 -u 4
+mount -r /dev/md4 /mnt
+.Ed
+.Pp
+You can now cruise around your frozen
+.Pa /var
+file system at
+.Pa /mnt .
+Everything will be in the same state that it was at the time the snapshot was
+taken.
+The one exception is that any earlier snapshots will appear as zero length
+files.
+When you are done with the mounted snapshot:
+.Bd -literal
+umount /mnt
+mdconfig -d -u 4
+.Ed
+.El
+.It Cm suiddir
+A directory on the mounted file system will respond to the SUID bit
+being set, by setting the owner of any new files to be the same
+as the owner of the directory.
+New directories will inherit the bit from their parents.
+Execute bits are removed from
+the file, and it will not be given to root.
+.Pp
+This feature is designed for use on fileservers serving PC users via
+ftp, SAMBA, or netatalk.
+It provides security holes for shell users and as
+such should not be used on shell machines, especially on home directories.
+This option requires the SUIDDIR
+option in the kernel to work.
+Only UFS file systems support this option.
+See
+.Xr chmod 2
+for more information.
+.It Cm sync
+All
+.Tn I/O
+to the file system should be done synchronously.
+.It Cm update
+The same as
+.Fl u ;
+indicate that the status of an already mounted file system should be changed.
+.It Cm union
+Causes the namespace at the mount point to appear as the union
+of the mounted file system root and the existing directory.
+Lookups will be done in the mounted file system first.
+If those operations fail due to a non-existent file the underlying
+directory is then accessed.
+All creates are done in the mounted file system.
+.El
+.Pp
+Any additional options specific to a file system type that is not
+one of the internally known types (see the
+.Fl t
+option) may be passed as a comma separated list; these options are
+distinguished by a leading
+.Dq \&-
+(dash).
+For example, the
+.Nm
+command:
+.Bd -literal -offset indent
+mount -t cd9660 -o -e /dev/cd0 /cdrom
+.Ed
+.Pp
+causes
+.Nm
+to execute the equivalent of:
+.Bd -literal -offset indent
+/sbin/mount_cd9660 -e /dev/cd0 /cdrom
+.Ed
+.Pp
+Options that take a value are specified using the -option=value syntax:
+.Bd -literal -offset indent
+mount -t msdosfs -o -u=fred,-g=wheel /dev/da0s1 /mnt
+.Ed
+.Pp
+is equivalent to
+.Bd -literal -offset indent
+/sbin/mount_msdosfs -u fred -g wheel /dev/da0s1 /mnt
+.Ed
+.Pp
+Additional options specific to file system types
+which are not internally known
+(see the description of the
+.Fl t
+option below)
+may be described in the manual pages for the associated
+.Pa /sbin/mount_ Ns Sy XXX
+utilities.
+.It Fl p
+Print mount information in
+.Xr fstab 5
+format.
+Implies also the
+.Fl v
+option.
+.It Fl r
+The file system is to be mounted read-only.
+Mount the file system read-only (even the super-user may not write it).
+The same as the
+.Cm ro
+argument to the
+.Fl o
+option.
+.It Fl t Cm ufs | Ar external_type
+The argument following the
+.Fl t
+is used to indicate the file system type.
+The type
+.Cm ufs
+is the default.
+The
+.Fl t
+option can be used
+to indicate that the actions should only be taken on
+file systems of the specified type.
+More than one type may be specified in a comma separated list.
+The list of file system types can be prefixed with
+.Dq Li no
+to specify the file system types for which action should
+.Em not
+be taken.
+For example, the
+.Nm
+command:
+.Bd -literal -offset indent
+mount -a -t nonfs,nullfs
+.Ed
+.Pp
+mounts all file systems except those of type
+.Tn NFS
+and
+.Tn NULLFS .
+.Pp
+The default behavior of
+.Nm
+is to pass the
+.Fl t
+option directly to the
+.Xr nmount 2
+system call in the
+.Li fstype
+option.
+.Pp
+However, for the following file system types:
+.Cm cd9660 ,
+.Cm mfs ,
+.Cm msdosfs ,
+.Cm nfs ,
+.Cm nullfs ,
+.Cm smbfs ,
+.Cm udf ,
+and
+.Cm unionfs .
+.Nm
+will not call
+.Xr nmount 2
+directly and will instead attempt to execute a program in
+.Pa /sbin/mount_ Ns Sy XXX
+where
+.Sy XXX
+is replaced by the file system type name.
+For example, nfs file systems are mounted by the program
+.Pa /sbin/mount_nfs .
+.Pp
+Most file systems will be dynamically loaded by the kernel
+if not already present, and if the kernel module is available.
+.It Fl u
+The
+.Fl u
+flag indicates that the status of an already mounted file
+system should be changed.
+Any of the options discussed above (the
+.Fl o
+option)
+may be changed;
+also a file system can be changed from read-only to read-write
+or vice versa.
+An attempt to change from read-write to read-only will fail if any
+files on the file system are currently open for writing unless the
+.Fl f
+flag is also specified.
+The set of options is determined by applying the options specified
+in the argument to
+.Fl o
+and finally applying the
+.Fl r
+or
+.Fl w
+option.
+.It Fl v
+Verbose mode.
+If the
+.Fl v
+is used alone, show all file systems, including those that were mounted with the
+.Dv MNT_IGNORE
+flag and show additional information about each file system (including fsid
+when run by root).
+.It Fl w
+The file system object is to be read and write.
+.El
+.Sh ENVIRONMENT
+.Bl -tag -width ".Ev PATH_FSTAB"
+.It Ev PATH_FSTAB
+If the environment variable
+.Ev PATH_FSTAB
+is set, all operations are performed against the specified file.
+.Ev PATH_FSTAB
+will not be honored if the process environment or memory address space is
+considered
+.Dq tainted .
+(See
+.Xr issetugid 2
+for more information.)
+.El
+.Sh FILES
+.Bl -tag -width /etc/fstab -compact
+.It Pa /etc/fstab
+file system table
+.El
+.Sh DIAGNOSTICS
+Various, most of them are self-explanatory.
+.Pp
+.Dl XXXXX file system is not available
+.Pp
+The kernel does not support the respective file system type.
+Note that
+support for a particular file system might be provided either on a static
+(kernel compile-time), or dynamic basis (loaded as a kernel module by
+.Xr kldload 8 ) .
+.Sh SEE ALSO
+.Xr getfacl 1 ,
+.Xr setfacl 1 ,
+.Xr nmount 2 ,
+.Xr acl 3 ,
+.Xr mac 4 ,
+.Xr devfs 5 ,
+.Xr ext2fs 5 ,
+.Xr fstab 5 ,
+.Xr procfs 5 ,
+.Xr tmpfs 5 ,
+.Xr automount 8 ,
+.Xr fstyp 8 ,
+.Xr kldload 8 ,
+.Xr mount_cd9660 8 ,
+.Xr mount_msdosfs 8 ,
+.Xr mount_nfs 8 ,
+.Xr mount_nullfs 8 ,
+.Xr mount_smbfs 8 ,
+.Xr mount_udf 8 ,
+.Xr mount_unionfs 8 ,
+.Xr umount 8 ,
+.Xr zfs 8 ,
+.Xr zpool 8
+.Sh HISTORY
+A
+.Nm
+utility appeared in
+.At v1 .
+.Sh CAVEATS
+After a successful
+.Nm ,
+the permissions on the original mount point determine if
+.Pa ..\&
+is accessible from the mounted file system.
+The minimum permissions for
+the mount point for traversal across the mount point in both
+directions to be possible for all users is 0111 (execute for all).
+.Pp
+Use of the
+.Nm
+is preferred over the use of the file system specific
+.Pa mount_ Ns Sy XXX
+commands.
+In particular,
+.Xr mountd 8
+gets a
+.Dv SIGHUP
+signal (that causes an update of the export list)
+only when the file system is mounted via
+.Nm .
+.Sh BUGS
+It is possible for a corrupted file system to cause a crash.
diff --git a/sbin/mount/mount.c b/sbin/mount/mount.c
new file mode 100644
index 0000000..6427fc8
--- /dev/null
+++ b/sbin/mount/mount.c
@@ -0,0 +1,957 @@
+/*-
+ * Copyright (c) 1980, 1989, 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.
+ * 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 lint
+static const char copyright[] =
+"@(#) Copyright (c) 1980, 1989, 1993, 1994\n\
+ The Regents of the University of California. All rights reserved.\n";
+#if 0
+static char sccsid[] = "@(#)mount.c 8.25 (Berkeley) 5/8/95";
+#endif
+#endif /* not lint */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fstab.h>
+#include <paths.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <libutil.h>
+
+#include "extern.h"
+#include "mntopts.h"
+#include "pathnames.h"
+
+/* `meta' options */
+#define MOUNT_META_OPTION_FSTAB "fstab"
+#define MOUNT_META_OPTION_CURRENT "current"
+
+static int debug, fstab_style, verbose;
+
+struct cpa {
+ char **a;
+ ssize_t sz;
+ int c;
+};
+
+char *catopt(char *, const char *);
+struct statfs *getmntpt(const char *);
+int hasopt(const char *, const char *);
+int ismounted(struct fstab *, struct statfs *, int);
+int isremountable(const char *);
+void mangle(char *, struct cpa *);
+char *update_options(char *, char *, int);
+int mountfs(const char *, const char *, const char *,
+ int, const char *, const char *);
+void remopt(char *, const char *);
+void prmount(struct statfs *);
+void putfsent(struct statfs *);
+void usage(void);
+char *flags2opts(int);
+
+/* Map from mount options to printable formats. */
+static struct opt {
+ uint64_t o_opt;
+ const char *o_name;
+} optnames[] = {
+ { MNT_ASYNC, "asynchronous" },
+ { MNT_EXPORTED, "NFS exported" },
+ { MNT_LOCAL, "local" },
+ { MNT_NOATIME, "noatime" },
+ { MNT_NOEXEC, "noexec" },
+ { MNT_NOSUID, "nosuid" },
+ { MNT_NOSYMFOLLOW, "nosymfollow" },
+ { MNT_QUOTA, "with quotas" },
+ { MNT_RDONLY, "read-only" },
+ { MNT_SYNCHRONOUS, "synchronous" },
+ { MNT_UNION, "union" },
+ { MNT_NOCLUSTERR, "noclusterr" },
+ { MNT_NOCLUSTERW, "noclusterw" },
+ { MNT_SUIDDIR, "suiddir" },
+ { MNT_SOFTDEP, "soft-updates" },
+ { MNT_SUJ, "journaled soft-updates" },
+ { MNT_MULTILABEL, "multilabel" },
+ { MNT_ACLS, "acls" },
+ { MNT_NFS4ACLS, "nfsv4acls" },
+ { MNT_GJOURNAL, "gjournal" },
+ { MNT_AUTOMOUNTED, "automounted" },
+ { 0, NULL }
+};
+
+/*
+ * List of VFS types that can be remounted without becoming mounted on top
+ * of each other.
+ * XXX Is this list correct?
+ */
+static const char *
+remountable_fs_names[] = {
+ "ufs", "ffs", "ext2fs",
+ 0
+};
+
+static const char userquotaeq[] = "userquota=";
+static const char groupquotaeq[] = "groupquota=";
+
+static char *mountprog = NULL;
+
+static int
+use_mountprog(const char *vfstype)
+{
+ /* XXX: We need to get away from implementing external mount
+ * programs for every filesystem, and move towards having
+ * each filesystem properly implement the nmount() system call.
+ */
+ unsigned int i;
+ const char *fs[] = {
+ "cd9660", "mfs", "msdosfs", "nfs",
+ "nullfs", "smbfs", "udf", "unionfs",
+ NULL
+ };
+
+ if (mountprog != NULL)
+ return (1);
+
+ for (i = 0; fs[i] != NULL; ++i) {
+ if (strcmp(vfstype, fs[i]) == 0)
+ return (1);
+ }
+
+ return (0);
+}
+
+static int
+exec_mountprog(const char *name, const char *execname, char *const argv[])
+{
+ pid_t pid;
+ int status;
+
+ switch (pid = fork()) {
+ case -1: /* Error. */
+ warn("fork");
+ exit (1);
+ case 0: /* Child. */
+ /* Go find an executable. */
+ execvP(execname, _PATH_SYSPATH, argv);
+ if (errno == ENOENT) {
+ warn("exec %s not found", execname);
+ if (execname[0] != '/') {
+ warnx("in path: %s", _PATH_SYSPATH);
+ }
+ }
+ exit(1);
+ default: /* Parent. */
+ if (waitpid(pid, &status, 0) < 0) {
+ warn("waitpid");
+ return (1);
+ }
+
+ if (WIFEXITED(status)) {
+ if (WEXITSTATUS(status) != 0)
+ return (WEXITSTATUS(status));
+ } else if (WIFSIGNALED(status)) {
+ warnx("%s: %s", name, sys_siglist[WTERMSIG(status)]);
+ return (1);
+ }
+ break;
+ }
+
+ return (0);
+}
+
+static int
+specified_ro(const char *arg)
+{
+ char *optbuf, *opt;
+ int ret = 0;
+
+ optbuf = strdup(arg);
+ if (optbuf == NULL)
+ err(1, NULL);
+
+ for (opt = optbuf; (opt = strtok(opt, ",")) != NULL; opt = NULL) {
+ if (strcmp(opt, "ro") == 0) {
+ ret = 1;
+ break;
+ }
+ }
+ free(optbuf);
+ return (ret);
+}
+
+static void
+restart_mountd(void)
+{
+ struct pidfh *pfh;
+ pid_t mountdpid;
+
+ pfh = pidfile_open(_PATH_MOUNTDPID, 0600, &mountdpid);
+ if (pfh != NULL) {
+ /* Mountd is not running. */
+ pidfile_remove(pfh);
+ return;
+ }
+ if (errno != EEXIST) {
+ /* Cannot open pidfile for some reason. */
+ return;
+ }
+ /* We have mountd(8) PID in mountdpid varible, let's signal it. */
+ if (kill(mountdpid, SIGHUP) == -1)
+ err(1, "signal mountd");
+}
+
+int
+main(int argc, char *argv[])
+{
+ const char *mntfromname, **vfslist, *vfstype;
+ struct fstab *fs;
+ struct statfs *mntbuf;
+ int all, ch, i, init_flags, late, failok, mntsize, rval, have_fstab, ro;
+ int onlylate;
+ char *cp, *ep, *options;
+
+ all = init_flags = late = onlylate = 0;
+ ro = 0;
+ options = NULL;
+ vfslist = NULL;
+ vfstype = "ufs";
+ while ((ch = getopt(argc, argv, "adF:fLlno:prt:uvw")) != -1)
+ switch (ch) {
+ case 'a':
+ all = 1;
+ break;
+ case 'd':
+ debug = 1;
+ break;
+ case 'F':
+ setfstab(optarg);
+ break;
+ case 'f':
+ init_flags |= MNT_FORCE;
+ break;
+ case 'L':
+ onlylate = 1;
+ late = 1;
+ break;
+ case 'l':
+ late = 1;
+ break;
+ case 'n':
+ /* For compatibility with the Linux version of mount. */
+ break;
+ case 'o':
+ if (*optarg) {
+ options = catopt(options, optarg);
+ if (specified_ro(optarg))
+ ro = 1;
+ }
+ break;
+ case 'p':
+ fstab_style = 1;
+ verbose = 1;
+ break;
+ case 'r':
+ options = catopt(options, "ro");
+ ro = 1;
+ break;
+ case 't':
+ if (vfslist != NULL)
+ errx(1, "only one -t option may be specified");
+ vfslist = makevfslist(optarg);
+ vfstype = optarg;
+ break;
+ case 'u':
+ init_flags |= MNT_UPDATE;
+ break;
+ case 'v':
+ verbose = 1;
+ break;
+ case 'w':
+ options = catopt(options, "noro");
+ break;
+ case '?':
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ argc -= optind;
+ argv += optind;
+
+#define BADTYPE(type) \
+ (strcmp(type, FSTAB_RO) && \
+ strcmp(type, FSTAB_RW) && strcmp(type, FSTAB_RQ))
+
+ if ((init_flags & MNT_UPDATE) && (ro == 0))
+ options = catopt(options, "noro");
+
+ rval = 0;
+ switch (argc) {
+ case 0:
+ if ((mntsize = getmntinfo(&mntbuf, MNT_NOWAIT)) == 0)
+ err(1, "getmntinfo");
+ if (all) {
+ while ((fs = getfsent()) != NULL) {
+ if (BADTYPE(fs->fs_type))
+ continue;
+ if (checkvfsname(fs->fs_vfstype, vfslist))
+ continue;
+ if (hasopt(fs->fs_mntops, "noauto"))
+ continue;
+ if (!hasopt(fs->fs_mntops, "late") && onlylate)
+ continue;
+ if (hasopt(fs->fs_mntops, "late") && !late)
+ continue;
+ if (hasopt(fs->fs_mntops, "failok"))
+ failok = 1;
+ else
+ failok = 0;
+ if (!(init_flags & MNT_UPDATE) &&
+ ismounted(fs, mntbuf, mntsize))
+ continue;
+ options = update_options(options, fs->fs_mntops,
+ mntbuf->f_flags);
+ if (mountfs(fs->fs_vfstype, fs->fs_spec,
+ fs->fs_file, init_flags, options,
+ fs->fs_mntops) && !failok)
+ rval = 1;
+ }
+ } else if (fstab_style) {
+ for (i = 0; i < mntsize; i++) {
+ if (checkvfsname(mntbuf[i].f_fstypename, vfslist))
+ continue;
+ putfsent(&mntbuf[i]);
+ }
+ } else {
+ for (i = 0; i < mntsize; i++) {
+ if (checkvfsname(mntbuf[i].f_fstypename,
+ vfslist))
+ continue;
+ if (!verbose &&
+ (mntbuf[i].f_flags & MNT_IGNORE) != 0)
+ continue;
+ prmount(&mntbuf[i]);
+ }
+ }
+ exit(rval);
+ case 1:
+ if (vfslist != NULL)
+ usage();
+
+ rmslashes(*argv, *argv);
+ if (init_flags & MNT_UPDATE) {
+ mntfromname = NULL;
+ have_fstab = 0;
+ if ((mntbuf = getmntpt(*argv)) == NULL)
+ errx(1, "not currently mounted %s", *argv);
+ /*
+ * Only get the mntflags from fstab if both mntpoint
+ * and mntspec are identical. Also handle the special
+ * case where just '/' is mounted and 'spec' is not
+ * identical with the one from fstab ('/dev' is missing
+ * in the spec-string at boot-time).
+ */
+ if ((fs = getfsfile(mntbuf->f_mntonname)) != NULL) {
+ if (strcmp(fs->fs_spec,
+ mntbuf->f_mntfromname) == 0 &&
+ strcmp(fs->fs_file,
+ mntbuf->f_mntonname) == 0) {
+ have_fstab = 1;
+ mntfromname = mntbuf->f_mntfromname;
+ } else if (argv[0][0] == '/' &&
+ argv[0][1] == '\0') {
+ fs = getfsfile("/");
+ have_fstab = 1;
+ mntfromname = fs->fs_spec;
+ }
+ }
+ if (have_fstab) {
+ options = update_options(options, fs->fs_mntops,
+ mntbuf->f_flags);
+ } else {
+ mntfromname = mntbuf->f_mntfromname;
+ options = update_options(options, NULL,
+ mntbuf->f_flags);
+ }
+ rval = mountfs(mntbuf->f_fstypename, mntfromname,
+ mntbuf->f_mntonname, init_flags, options, 0);
+ break;
+ }
+ if ((fs = getfsfile(*argv)) == NULL &&
+ (fs = getfsspec(*argv)) == NULL)
+ errx(1, "%s: unknown special file or file system",
+ *argv);
+ if (BADTYPE(fs->fs_type))
+ errx(1, "%s has unknown file system type",
+ *argv);
+ rval = mountfs(fs->fs_vfstype, fs->fs_spec, fs->fs_file,
+ init_flags, options, fs->fs_mntops);
+ break;
+ case 2:
+ /*
+ * If -t flag has not been specified, the path cannot be
+ * found, spec contains either a ':' or a '@', then assume
+ * that an NFS file system is being specified ala Sun.
+ * Check if the hostname contains only allowed characters
+ * to reduce false positives. IPv6 addresses containing
+ * ':' will be correctly parsed only if the separator is '@'.
+ * The definition of a valid hostname is taken from RFC 1034.
+ */
+ if (vfslist == NULL && ((ep = strchr(argv[0], '@')) != NULL ||
+ (ep = strchr(argv[0], ':')) != NULL)) {
+ if (*ep == '@') {
+ cp = ep + 1;
+ ep = cp + strlen(cp);
+ } else
+ cp = argv[0];
+ while (cp != ep) {
+ if (!isdigit(*cp) && !isalpha(*cp) &&
+ *cp != '.' && *cp != '-' && *cp != ':')
+ break;
+ cp++;
+ }
+ if (cp == ep)
+ vfstype = "nfs";
+ }
+ rval = mountfs(vfstype,
+ argv[0], argv[1], init_flags, options, NULL);
+ break;
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+
+ /*
+ * If the mount was successfully, and done by root, tell mountd the
+ * good news.
+ */
+ if (rval == 0 && getuid() == 0)
+ restart_mountd();
+
+ exit(rval);
+}
+
+int
+ismounted(struct fstab *fs, struct statfs *mntbuf, int mntsize)
+{
+ char realfsfile[PATH_MAX];
+ int i;
+
+ if (fs->fs_file[0] == '/' && fs->fs_file[1] == '\0')
+ /* the root file system can always be remounted */
+ return (0);
+
+ /* The user may have specified a symlink in fstab, resolve the path */
+ if (realpath(fs->fs_file, realfsfile) == NULL) {
+ /* Cannot resolve the path, use original one */
+ strlcpy(realfsfile, fs->fs_file, sizeof(realfsfile));
+ }
+
+ for (i = mntsize - 1; i >= 0; --i)
+ if (strcmp(realfsfile, mntbuf[i].f_mntonname) == 0 &&
+ (!isremountable(fs->fs_vfstype) ||
+ strcmp(fs->fs_spec, mntbuf[i].f_mntfromname) == 0))
+ return (1);
+ return (0);
+}
+
+int
+isremountable(const char *vfsname)
+{
+ const char **cp;
+
+ for (cp = remountable_fs_names; *cp; cp++)
+ if (strcmp(*cp, vfsname) == 0)
+ return (1);
+ return (0);
+}
+
+int
+hasopt(const char *mntopts, const char *option)
+{
+ int negative, found;
+ char *opt, *optbuf;
+
+ if (option[0] == 'n' && option[1] == 'o') {
+ negative = 1;
+ option += 2;
+ } else
+ negative = 0;
+ optbuf = strdup(mntopts);
+ found = 0;
+ for (opt = optbuf; (opt = strtok(opt, ",")) != NULL; opt = NULL) {
+ if (opt[0] == 'n' && opt[1] == 'o') {
+ if (!strcasecmp(opt + 2, option))
+ found = negative;
+ } else if (!strcasecmp(opt, option))
+ found = !negative;
+ }
+ free(optbuf);
+ return (found);
+}
+
+static void
+append_arg(struct cpa *sa, char *arg)
+{
+ if (sa->c + 1 == sa->sz) {
+ sa->sz = sa->sz == 0 ? 8 : sa->sz * 2;
+ sa->a = realloc(sa->a, sizeof(sa->a) * sa->sz);
+ if (sa->a == NULL)
+ errx(1, "realloc failed");
+ }
+ sa->a[++sa->c] = arg;
+}
+
+int
+mountfs(const char *vfstype, const char *spec, const char *name, int flags,
+ const char *options, const char *mntopts)
+{
+ struct statfs sf;
+ int i, ret;
+ char *optbuf, execname[PATH_MAX], mntpath[PATH_MAX];
+ static struct cpa mnt_argv;
+
+ /* resolve the mountpoint with realpath(3) */
+ if (checkpath(name, mntpath) != 0) {
+ warn("%s", mntpath);
+ return (1);
+ }
+ name = mntpath;
+
+ if (mntopts == NULL)
+ mntopts = "";
+ optbuf = catopt(strdup(mntopts), options);
+
+ if (strcmp(name, "/") == 0)
+ flags |= MNT_UPDATE;
+ if (flags & MNT_FORCE)
+ optbuf = catopt(optbuf, "force");
+ if (flags & MNT_RDONLY)
+ optbuf = catopt(optbuf, "ro");
+ /*
+ * XXX
+ * The mount_mfs (newfs) command uses -o to select the
+ * optimization mode. We don't pass the default "-o rw"
+ * for that reason.
+ */
+ if (flags & MNT_UPDATE)
+ optbuf = catopt(optbuf, "update");
+
+ /* Compatibility glue. */
+ if (strcmp(vfstype, "msdos") == 0) {
+ warnx(
+ "Using \"-t msdosfs\", since \"-t msdos\" is deprecated.");
+ vfstype = "msdosfs";
+ }
+
+ /* Construct the name of the appropriate mount command */
+ (void)snprintf(execname, sizeof(execname), "mount_%s", vfstype);
+
+ mnt_argv.c = -1;
+ append_arg(&mnt_argv, execname);
+ mangle(optbuf, &mnt_argv);
+ if (mountprog != NULL)
+ strcpy(execname, mountprog);
+
+ append_arg(&mnt_argv, strdup(spec));
+ append_arg(&mnt_argv, strdup(name));
+ append_arg(&mnt_argv, NULL);
+
+ if (debug) {
+ if (use_mountprog(vfstype))
+ printf("exec: %s", execname);
+ else
+ printf("mount -t %s", vfstype);
+ for (i = 1; i < mnt_argv.c; i++)
+ (void)printf(" %s", mnt_argv.a[i]);
+ (void)printf("\n");
+ free(optbuf);
+ free(mountprog);
+ mountprog = NULL;
+ return (0);
+ }
+
+ if (use_mountprog(vfstype)) {
+ ret = exec_mountprog(name, execname, mnt_argv.a);
+ } else {
+ ret = mount_fs(vfstype, mnt_argv.c, mnt_argv.a);
+ }
+
+ free(optbuf);
+ free(mountprog);
+ mountprog = NULL;
+
+ if (verbose) {
+ if (statfs(name, &sf) < 0) {
+ warn("statfs %s", name);
+ return (1);
+ }
+ if (fstab_style)
+ putfsent(&sf);
+ else
+ prmount(&sf);
+ }
+
+ return (ret);
+}
+
+void
+prmount(struct statfs *sfp)
+{
+ uint64_t flags;
+ unsigned int i;
+ struct opt *o;
+ struct passwd *pw;
+
+ (void)printf("%s on %s (%s", sfp->f_mntfromname, sfp->f_mntonname,
+ sfp->f_fstypename);
+
+ flags = sfp->f_flags & MNT_VISFLAGMASK;
+ for (o = optnames; flags != 0 && o->o_opt != 0; o++)
+ if (flags & o->o_opt) {
+ (void)printf(", %s", o->o_name);
+ flags &= ~o->o_opt;
+ }
+ /*
+ * Inform when file system is mounted by an unprivileged user
+ * or privileged non-root user.
+ */
+ if ((flags & MNT_USER) != 0 || sfp->f_owner != 0) {
+ (void)printf(", mounted by ");
+ if ((pw = getpwuid(sfp->f_owner)) != NULL)
+ (void)printf("%s", pw->pw_name);
+ else
+ (void)printf("%d", sfp->f_owner);
+ }
+ if (verbose) {
+ if (sfp->f_syncwrites != 0 || sfp->f_asyncwrites != 0)
+ (void)printf(", writes: sync %ju async %ju",
+ (uintmax_t)sfp->f_syncwrites,
+ (uintmax_t)sfp->f_asyncwrites);
+ if (sfp->f_syncreads != 0 || sfp->f_asyncreads != 0)
+ (void)printf(", reads: sync %ju async %ju",
+ (uintmax_t)sfp->f_syncreads,
+ (uintmax_t)sfp->f_asyncreads);
+ if (sfp->f_fsid.val[0] != 0 || sfp->f_fsid.val[1] != 0) {
+ printf(", fsid ");
+ for (i = 0; i < sizeof(sfp->f_fsid); i++)
+ printf("%02x", ((u_char *)&sfp->f_fsid)[i]);
+ }
+ }
+ (void)printf(")\n");
+}
+
+struct statfs *
+getmntpt(const char *name)
+{
+ struct statfs *mntbuf;
+ int i, mntsize;
+
+ mntsize = getmntinfo(&mntbuf, MNT_NOWAIT);
+ for (i = mntsize - 1; i >= 0; i--) {
+ if (strcmp(mntbuf[i].f_mntfromname, name) == 0 ||
+ strcmp(mntbuf[i].f_mntonname, name) == 0)
+ return (&mntbuf[i]);
+ }
+ return (NULL);
+}
+
+char *
+catopt(char *s0, const char *s1)
+{
+ size_t i;
+ char *cp;
+
+ if (s1 == NULL || *s1 == '\0')
+ return (s0);
+
+ if (s0 && *s0) {
+ i = strlen(s0) + strlen(s1) + 1 + 1;
+ if ((cp = malloc(i)) == NULL)
+ errx(1, "malloc failed");
+ (void)snprintf(cp, i, "%s,%s", s0, s1);
+ } else
+ cp = strdup(s1);
+
+ if (s0)
+ free(s0);
+ return (cp);
+}
+
+void
+mangle(char *options, struct cpa *a)
+{
+ char *p, *s, *val;
+
+ for (s = options; (p = strsep(&s, ",")) != NULL;)
+ if (*p != '\0') {
+ if (strcmp(p, "noauto") == 0) {
+ /*
+ * Do not pass noauto option to nmount().
+ * or external mount program. noauto is
+ * only used to prevent mounting a filesystem
+ * when 'mount -a' is specified, and is
+ * not a real mount option.
+ */
+ continue;
+ } else if (strcmp(p, "late") == 0) {
+ /*
+ * "late" is used to prevent certain file
+ * systems from being mounted before late
+ * in the boot cycle; for instance,
+ * loopback NFS mounts can't be mounted
+ * before mountd starts.
+ */
+ continue;
+ } else if (strcmp(p, "failok") == 0) {
+ /*
+ * "failok" is used to prevent certain file
+ * systems from being causing the system to
+ * drop into single user mode in the boot
+ * cycle, and is not a real mount option.
+ */
+ continue;
+ } else if (strncmp(p, "mountprog", 9) == 0) {
+ /*
+ * "mountprog" is used to force the use of
+ * userland mount programs.
+ */
+ val = strchr(p, '=');
+ if (val != NULL) {
+ ++val;
+ if (*val != '\0')
+ mountprog = strdup(val);
+ }
+
+ if (mountprog == NULL) {
+ errx(1, "Need value for -o mountprog");
+ }
+ continue;
+ } else if (strcmp(p, "userquota") == 0) {
+ continue;
+ } else if (strncmp(p, userquotaeq,
+ sizeof(userquotaeq) - 1) == 0) {
+ continue;
+ } else if (strcmp(p, "groupquota") == 0) {
+ continue;
+ } else if (strncmp(p, groupquotaeq,
+ sizeof(groupquotaeq) - 1) == 0) {
+ continue;
+ } else if (*p == '-') {
+ append_arg(a, p);
+ p = strchr(p, '=');
+ if (p != NULL) {
+ *p = '\0';
+ append_arg(a, p + 1);
+ }
+ } else {
+ append_arg(a, strdup("-o"));
+ append_arg(a, p);
+ }
+ }
+}
+
+
+char *
+update_options(char *opts, char *fstab, int curflags)
+{
+ char *o, *p;
+ char *cur;
+ char *expopt, *newopt, *tmpopt;
+
+ if (opts == NULL)
+ return (strdup(""));
+
+ /* remove meta options from list */
+ remopt(fstab, MOUNT_META_OPTION_FSTAB);
+ remopt(fstab, MOUNT_META_OPTION_CURRENT);
+ cur = flags2opts(curflags);
+
+ /*
+ * Expand all meta-options passed to us first.
+ */
+ expopt = NULL;
+ for (p = opts; (o = strsep(&p, ",")) != NULL;) {
+ if (strcmp(MOUNT_META_OPTION_FSTAB, o) == 0)
+ expopt = catopt(expopt, fstab);
+ else if (strcmp(MOUNT_META_OPTION_CURRENT, o) == 0)
+ expopt = catopt(expopt, cur);
+ else
+ expopt = catopt(expopt, o);
+ }
+ free(cur);
+ free(opts);
+
+ /*
+ * Remove previous contradictory arguments. Given option "foo" we
+ * remove all the "nofoo" options. Given "nofoo" we remove "nonofoo"
+ * and "foo" - so we can deal with possible options like "notice".
+ */
+ newopt = NULL;
+ for (p = expopt; (o = strsep(&p, ",")) != NULL;) {
+ if ((tmpopt = malloc( strlen(o) + 2 + 1 )) == NULL)
+ errx(1, "malloc failed");
+
+ strcpy(tmpopt, "no");
+ strcat(tmpopt, o);
+ remopt(newopt, tmpopt);
+ free(tmpopt);
+
+ if (strncmp("no", o, 2) == 0)
+ remopt(newopt, o+2);
+
+ newopt = catopt(newopt, o);
+ }
+ free(expopt);
+
+ return (newopt);
+}
+
+void
+remopt(char *string, const char *opt)
+{
+ char *o, *p, *r;
+
+ if (string == NULL || *string == '\0' || opt == NULL || *opt == '\0')
+ return;
+
+ r = string;
+
+ for (p = string; (o = strsep(&p, ",")) != NULL;) {
+ if (strcmp(opt, o) != 0) {
+ if (*r == ',' && *o != '\0')
+ r++;
+ while ((*r++ = *o++) != '\0')
+ ;
+ *--r = ',';
+ }
+ }
+ *r = '\0';
+}
+
+void
+usage(void)
+{
+
+ (void)fprintf(stderr, "%s\n%s\n%s\n",
+"usage: mount [-adflpruvw] [-F fstab] [-o options] [-t ufs | external_type]",
+" mount [-dfpruvw] special | node",
+" mount [-dfpruvw] [-o options] [-t ufs | external_type] special node");
+ exit(1);
+}
+
+void
+putfsent(struct statfs *ent)
+{
+ struct fstab *fst;
+ char *opts, *rw;
+ int l;
+
+ opts = NULL;
+ /* flags2opts() doesn't return the "rw" option. */
+ if ((ent->f_flags & MNT_RDONLY) != 0)
+ rw = NULL;
+ else
+ rw = catopt(NULL, "rw");
+
+ opts = flags2opts(ent->f_flags);
+ opts = catopt(rw, opts);
+
+ if (strncmp(ent->f_mntfromname, "<below>", 7) == 0 ||
+ strncmp(ent->f_mntfromname, "<above>", 7) == 0) {
+ strcpy(ent->f_mntfromname, (strnstr(ent->f_mntfromname, ":", 8)
+ +1));
+ }
+
+ l = strlen(ent->f_mntfromname);
+ printf("%s%s%s%s", ent->f_mntfromname,
+ l < 8 ? "\t" : "",
+ l < 16 ? "\t" : "",
+ l < 24 ? "\t" : " ");
+ l = strlen(ent->f_mntonname);
+ printf("%s%s%s%s", ent->f_mntonname,
+ l < 8 ? "\t" : "",
+ l < 16 ? "\t" : "",
+ l < 24 ? "\t" : " ");
+ printf("%s\t", ent->f_fstypename);
+ l = strlen(opts);
+ printf("%s%s", opts,
+ l < 8 ? "\t" : " ");
+ free(opts);
+
+ if ((fst = getfsspec(ent->f_mntfromname)))
+ printf("\t%u %u\n", fst->fs_freq, fst->fs_passno);
+ else if ((fst = getfsfile(ent->f_mntonname)))
+ printf("\t%u %u\n", fst->fs_freq, fst->fs_passno);
+ else if (strcmp(ent->f_fstypename, "ufs") == 0) {
+ if (strcmp(ent->f_mntonname, "/") == 0)
+ printf("\t1 1\n");
+ else
+ printf("\t2 2\n");
+ } else
+ printf("\t0 0\n");
+}
+
+
+char *
+flags2opts(int flags)
+{
+ char *res;
+
+ res = NULL;
+
+ if (flags & MNT_RDONLY) res = catopt(res, "ro");
+ if (flags & MNT_SYNCHRONOUS) res = catopt(res, "sync");
+ if (flags & MNT_NOEXEC) res = catopt(res, "noexec");
+ if (flags & MNT_NOSUID) res = catopt(res, "nosuid");
+ if (flags & MNT_UNION) res = catopt(res, "union");
+ if (flags & MNT_ASYNC) res = catopt(res, "async");
+ if (flags & MNT_NOATIME) res = catopt(res, "noatime");
+ if (flags & MNT_NOCLUSTERR) res = catopt(res, "noclusterr");
+ if (flags & MNT_NOCLUSTERW) res = catopt(res, "noclusterw");
+ if (flags & MNT_NOSYMFOLLOW) res = catopt(res, "nosymfollow");
+ if (flags & MNT_SUIDDIR) res = catopt(res, "suiddir");
+ if (flags & MNT_MULTILABEL) res = catopt(res, "multilabel");
+ if (flags & MNT_ACLS) res = catopt(res, "acls");
+ if (flags & MNT_NFS4ACLS) res = catopt(res, "nfsv4acls");
+
+ return (res);
+}
diff --git a/sbin/mount/mount.conf.8 b/sbin/mount/mount.conf.8
new file mode 100644
index 0000000..45b8257
--- /dev/null
+++ b/sbin/mount/mount.conf.8
@@ -0,0 +1,252 @@
+.\" Copyright (c) 2013 Marcel Moolenaar
+.\" Copyright (c) 2013 Craig Rodrigues
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE 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 THE 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.
+.\"
+.\" $FreeBSD$
+.\"
+.\"
+.Dd July 7, 2013
+.Dt MOUNT.CONF 8
+.Os
+.Sh NAME
+.Nm mount.conf
+.Nd root file system mount configuration file
+.Sh SYNOPSIS
+.Pa /.mount.conf
+.Sh DESCRIPTION
+During the bootup process, the
+.Fx
+kernel will try to mount the root file system
+using the logic in the
+.Fn vfs_mountroot
+function in
+.Pa src/sys/kern/vfs_mountroot.c .
+The root mount logic can be described as follows:
+.Bl -enum
+.It
+The kernel will synthesize in memory a config file
+with default directives for mounting
+the root file system.
+The logic for this is in
+.Fn vfs_mountroot_conf0 .
+.It
+The kernel will first mount
+.Xr devfs 8
+as the root file system.
+.It
+Next, the kernel will parse the in-memory config file created in step 1
+and try to mount the actual root file system.
+See
+.Sx FILE FORMAT
+for the format of the config file.
+.It
+When the actual root file system is mounted,
+.Xr devfs
+will be re-mounted on the
+.Pa /dev
+directory.
+.It
+If a
+.Pa /.mount.conf
+file does not exist in the root file system which was
+just mounted, the root mount logic stops here.
+.It
+If a
+.Pa /.mount.conf
+file exists in the root file system which was just mounted,
+this file will be parsed, and the kernel will use this new config
+file to try to re-mount the root file system.
+See
+.Sx FILE FORMAT
+for the format of the config file.
+.It
+If the new root file system has a
+.Pa /.mount
+directory, the old root file system will be re-mounted
+on
+.Pa /.mount .
+.It
+The root mount logic will go back to step 4.
+.El
+.Pp
+The root mount logic is recursive, and step 8 will
+be repeated as long as each new root file system
+which is mounted has a
+.Pa /.mount.conf
+file.
+.Sh FILE FORMAT
+The kernel parses each line in
+.Pa .mount.conf
+and then tries to perform the action specified on that line as soon as it is parsed.
+.Bl -tag -width "XXXXXXXXXX"
+.It Ic #
+A line beginning with a # is a comment and is ignored.
+.It Ic {FS}:{MOUNTPOINT} {OPTIONS}
+The kernel will try to mount this in an
+operation equivalent to:
+.Bd -literal -offset indent
+mount -t {FS} -o {OPTIONS} {MOUNTPOINT} /
+.Ed
+.Pp
+If this is successfully mounted,
+further lines in
+.Pa .mount.conf
+are ignored.
+If all lines in
+.Pa .mount.conf
+have been processed and no root file system has been successfully
+mounted, then the action specified by
+.Ic .onfail
+is performed.
+.It Ic .ask
+When the kernel processes this line, a
+.Li mountroot>
+command-line prompt is displayed.
+At this prompt, the operator can enter the
+the root mount.
+.It Ic .md Ar file
+Create a memory backed
+.Xr md 4
+virtual disk, using
+.Ar file
+as the backing store.
+.It Ic .onfail Ar [panic|reboot|retry|continue]
+If after parsing all the lines in
+.Pa .mount.conf
+the kernel is unable to mount a root file system,
+the
+.Ic .onfail
+directive tells the kernel what action to perform.
+.It Ic .timeout Ar N
+Before trying to mount a root file system,
+if the root mount device does not exist, wait at most
+.Ar N
+seconds for the device to appear before trying to mount it.
+If
+.Ic .timeout
+is not specified, the default timeout is 3 seconds.
+.El
+.Sh EXAMPLES
+The following example
+.Pa .mount.conf
+will direct the kernel to try mounting the root file system
+first as an ISO CD9660 file system on
+.Pa /dev/cd0 ,
+then if that does not work, as an ISO CD9660 file system on
+.Pa /dev/acd0 ,
+and then if that does not work, as a UFS file system on
+.Pa /dev/ada0s1a .
+If that does not work, a
+.Li mountroot>
+command-line prompt will be displayed where the operator
+can manually enter the root file system to mount.
+Finally if that does not work, the kernel will panic.
+.Bd -literal -offset indent
+.Li .onfail panic
+.Li .timeout 3
+cd9660:/dev/cd0 ro
+.Li .timeout 0
+cd9660:/dev/acd0 ro
+.Li .timeout 3
+ufs:/dev/ada0s1a
+.Li .ask
+.Ed
+.Pp
+The following example
+.Pa .mount.conf
+will direct the kernel to create a
+.Xr md 4
+memory disk attached to the file
+.Pa /data/OS-1.0.iso
+and then mount the ISO CD9660 file system
+on the md device which was just created.
+The last line is a comment which is ignored.
+.Bd -literal -offset indent
+.Li .timeout 3
+.Li .md /data/OS-1.0.iso
+.Li cd9600:/dev/md# ro
+.Li # Can also use cd9660:/dev/md0 ro
+.Ed
+.Pp
+The following example
+.Pa .mount.conf
+will direct the kernel to create a
+.Xr md 4
+memory disk attached to the file
+.Pa /data/base.ufs.uzip
+and then mount the UFS file system
+on the md uzip device which was just created
+by the
+.Xr geom_uzip 4
+driver.
+.Bd -literal -offset indent
+.Li .md /data/base.ufs.uzip
+.Li ufs:/dev/md#.uzip ro
+.Li # Can also use ufs:/dev/md0.uzip ro
+.Ed
+.Pp
+The following example
+.Pa .mount.conf
+will direct the kernel to do a unionfs
+mount on a directory
+.Pa /jail/freebsd-8-stable
+which has a
+.Xr chroot 2
+environment.
+.Bd -literal -offset indent
+.Li .timeout 3
+.Li unionfs:/jail/freebsd-8-stable
+.Ed
+.Sh NOTES
+For each root file system which is mounted, a
+.Pa /dev
+directory
+.Em must
+exist so that the root mount logic can properly re-mount
+.Xr devfs 8 .
+If this directory does not exist, the system
+may hang during the bootup process.
+.Sh SEE ALSO
+.Xr nmount 2 ,
+.Xr md 4 ,
+.Xr boot.config 5 ,
+.Xr fstab 5 ,
+.Xr boot 8 ,
+.Xr loader 8 ,
+.Xr mount 8
+.Sh HISTORY
+The
+.Nm
+file first appeared in
+.Fx 9.0 .
+.Sh AUTHORS
+.An -nosplit
+The root mount logic in the
+.Fx
+kernel which parses
+.Pa /.mount.conf
+was written by
+.An Marcel Moolenaar Aq Mt marcel@FreeBSD.org .
+This man page was written by
+.An Craig Rodrigues Aq Mt rodrigc@FreeBSD.org .
diff --git a/sbin/mount/mount_fs.c b/sbin/mount/mount_fs.c
new file mode 100644
index 0000000..385cd0f
--- /dev/null
+++ b/sbin/mount/mount_fs.c
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 1992, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software donated to Berkeley by
+ * Jan-Simon Pendry.
+ *
+ * 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.
+ * 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 lint
+static const char copyright[] =
+"@(#) Copyright (c) 1992, 1993, 1994\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)mount_fs.c 8.6 (Berkeley) 4/26/95";
+#endif
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/mount.h>
+
+#include <err.h>
+#include <getopt.h>
+#include <libgen.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "extern.h"
+#include "mntopts.h"
+
+static struct mntopt mopts[] = {
+ MOPT_STDOPTS,
+ MOPT_END
+};
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr,
+ "usage: mount [-t fstype] [-o options] target_fs mount_point\n");
+ exit(1);
+}
+
+int
+mount_fs(const char *vfstype, int argc, char *argv[])
+{
+ struct iovec *iov;
+ int iovlen;
+ int mntflags = 0;
+ int ch;
+ char *dev, *dir, mntpath[MAXPATHLEN];
+ char fstype[32];
+ char errmsg[255];
+ char *p, *val;
+
+ strlcpy(fstype, vfstype, sizeof(fstype));
+ memset(errmsg, 0, sizeof(errmsg));
+
+ getmnt_silent = 1;
+ iov = NULL;
+ iovlen = 0;
+
+ optind = optreset = 1; /* Reset for parse of new argv. */
+ while ((ch = getopt(argc, argv, "o:")) != -1) {
+ switch(ch) {
+ case 'o':
+ getmntopts(optarg, mopts, &mntflags, 0);
+ p = strchr(optarg, '=');
+ val = NULL;
+ if (p != NULL) {
+ *p = '\0';
+ val = p + 1;
+ }
+ build_iovec(&iov, &iovlen, optarg, val, (size_t)-1);
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+ if (argc != 2)
+ usage();
+
+ dev = argv[0];
+ dir = argv[1];
+
+ if (checkpath(dir, mntpath) != 0) {
+ warn("%s", mntpath);
+ return (1);
+ }
+ (void)rmslashes(dev, dev);
+
+ build_iovec(&iov, &iovlen, "fstype", fstype, (size_t)-1);
+ build_iovec(&iov, &iovlen, "fspath", mntpath, (size_t)-1);
+ build_iovec(&iov, &iovlen, "from", dev, (size_t)-1);
+ build_iovec(&iov, &iovlen, "errmsg", errmsg, sizeof(errmsg));
+
+ if (nmount(iov, iovlen, mntflags) == -1) {
+ if (*errmsg != '\0')
+ warn("%s: %s", dev, errmsg);
+ else
+ warn("%s", dev);
+ return (1);
+ }
+ return (0);
+}
diff --git a/sbin/mount/pathnames.h b/sbin/mount/pathnames.h
new file mode 100644
index 0000000..aaa0a2c
--- /dev/null
+++ b/sbin/mount/pathnames.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 1989, 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.
+ * 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.
+ *
+ * @(#)pathnames.h 8.2 (Berkeley) 3/27/94
+ * $FreeBSD$
+ */
+
+#define _PATH_MOUNTDPID "/var/run/mountd.pid"
diff --git a/sbin/mount/vfslist.c b/sbin/mount/vfslist.c
new file mode 100644
index 0000000..fa5fdf6
--- /dev/null
+++ b/sbin/mount/vfslist.c
@@ -0,0 +1,89 @@
+/*-
+ * Copyright (c) 1995
+ * 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.
+ * 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 lint
+#if 0
+static char sccsid[] = "@(#)vfslist.c 8.1 (Berkeley) 5/8/95";
+#endif
+#endif /* not lint */
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <err.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "extern.h"
+
+static int skipvfs;
+
+int
+checkvfsname(const char *vfsname, const char **vfslist)
+{
+
+ if (vfslist == NULL)
+ return (0);
+ while (*vfslist != NULL) {
+ if (strcmp(vfsname, *vfslist) == 0)
+ return (skipvfs);
+ ++vfslist;
+ }
+ return (!skipvfs);
+}
+
+const char **
+makevfslist(char *fslist)
+{
+ const char **av;
+ int i;
+ char *nextcp;
+
+ if (fslist == NULL)
+ return (NULL);
+ if (fslist[0] == 'n' && fslist[1] == 'o') {
+ fslist += 2;
+ skipvfs = 1;
+ }
+ for (i = 0, nextcp = fslist; *nextcp; nextcp++)
+ if (*nextcp == ',')
+ i++;
+ if ((av = malloc((size_t)(i + 2) * sizeof(char *))) == NULL) {
+ warnx("malloc failed");
+ return (NULL);
+ }
+ nextcp = fslist;
+ i = 0;
+ av[i++] = nextcp;
+ while ((nextcp = strchr(nextcp, ',')) != NULL) {
+ *nextcp++ = '\0';
+ av[i++] = nextcp;
+ }
+ av[i++] = NULL;
+ return (av);
+}
diff --git a/sbin/mount_cd9660/Makefile b/sbin/mount_cd9660/Makefile
new file mode 100644
index 0000000..c70892c
--- /dev/null
+++ b/sbin/mount_cd9660/Makefile
@@ -0,0 +1,18 @@
+# @(#)Makefile 8.3 (Berkeley) 3/27/94
+# $FreeBSD$
+
+PROG= mount_cd9660
+SRCS= mount_cd9660.c getmntopts.c
+MAN= mount_cd9660.8
+LIBADD= kiconv
+
+MOUNT= ${.CURDIR}/../mount
+CFLAGS+= -I${MOUNT}
+
+# Needs to be dynamically linked for optional dlopen() access to
+# userland libiconv
+NO_SHARED?= NO
+
+.PATH: ${MOUNT}
+
+.include <bsd.prog.mk>
diff --git a/sbin/mount_cd9660/mount_cd9660.8 b/sbin/mount_cd9660/mount_cd9660.8
new file mode 100644
index 0000000..974ab3b
--- /dev/null
+++ b/sbin/mount_cd9660/mount_cd9660.8
@@ -0,0 +1,160 @@
+.\" Copyright (c) 1993, 1994
+.\" The Regents of the University of California. All rights reserved.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software donated to Berkeley by
+.\" Christopher G. Demetriou.
+.\"
+.\" 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.
+.\" 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.
+.\"
+.\" @(#)mount_cd9660.8 8.3 (Berkeley) 3/27/94
+.\" $FreeBSD$
+.\"
+.Dd March 5, 2013
+.Dt MOUNT_CD9660 8
+.Os
+.Sh NAME
+.Nm mount_cd9660
+.Nd mount an ISO-9660 file system
+.Sh SYNOPSIS
+.Nm
+.Op Fl begjrv
+.Op Fl C Ar charset
+.Op Fl o Ar options
+.Op Fl s Ar startsector
+.Ar special node
+.Sh DESCRIPTION
+The
+.Nm
+utility attaches the ISO-9660 file system residing on the device
+.Pa special
+to the global file system namespace at the location indicated by
+.Pa node .
+This command is normally executed by
+.Xr mount 8
+at boot time.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl b
+Relax checking for Supplementary Volume Descriptor Flags field
+which is set to a wrong value on some Joliet formatted disks.
+.It Fl e
+Enable the use of extended attributes.
+.It Fl g
+Do not strip version numbers on files.
+(By default, if there are files with different version numbers on the disk,
+only the last one will be listed.)
+In either case, files may be opened without explicitly stating a
+version number.
+.It Fl j
+Do not use any Joliet extensions included in the file system.
+.It Fl o
+Options are specified with a
+.Fl o
+flag followed by a comma separated string of options.
+See the
+.Xr mount 8
+man page for possible options and their meanings.
+The following cd9660 specific options are available:
+.Pp
+.Bl -tag -width "brokenjoliet" -compact
+.It Cm extatt
+Same as
+.Fl e .
+.It Cm gens
+Same as
+.Fl g .
+.It Cm nojoliet
+Same as
+.Fl j .
+.It Cm norrip
+Same as
+.Fl r .
+.It Cm brokenjoliet
+Same as
+.Fl b .
+.El
+.It Fl r
+Do not use any Rockridge extensions included in the file system.
+.It Fl s Ar startsector
+Start the file system at
+.Ar startsector .
+Normally, if the underlying device is a CD-ROM drive,
+.Nm
+will try to figure out the last track from the CD-ROM containing
+data, and start the file system there.
+If the device is not a CD-ROM,
+or the table of contents cannot be examined, the file system will be
+started at sector 0.
+This option can be used to override the behaviour.
+Note that
+.Ar startsector
+is measured in CD-ROM blocks, with 2048 bytes each.
+This is the same
+as for example the
+.Cm info
+command of
+.Xr cdcontrol 1
+is printing.
+It is possible to mount an arbitrary session of a multi-session CD by specifying
+the correct
+.Ar startsector
+here.
+.It Fl C Ar charset
+Specify local
+.Ar charset
+to convert Unicode file names when using Joliet extensions.
+.It Fl v
+Be verbose about the starting sector decisions made.
+.El
+.Sh EXAMPLES
+The following command can be used to mount a Kodak Photo-CD:
+.Pp
+.Dl "mount_cd9660 -o rw -v -s 0 /dev/cd0 /cdrom"
+.Sh SEE ALSO
+.Xr cdcontrol 1 ,
+.Xr mount 2 ,
+.Xr unmount 2 ,
+.Xr fstab 5 ,
+.Xr mount 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Bx 4.4 .
+.Pp
+The Unicode conversion routine was added by
+.An Ryuichiro Imura Aq Mt imura@ryu16.org
+in 2003.
+.Sh BUGS
+POSIX device node mapping is currently not supported.
+.Pp
+Version numbers are not stripped if Rockridge extensions are in use.
+In this case, accessing files that do not have Rockridge names without
+version numbers gets the one with the lowest version number and not
+the one with the highest.
+.Pp
+There is no ECMA support.
diff --git a/sbin/mount_cd9660/mount_cd9660.c b/sbin/mount_cd9660/mount_cd9660.c
new file mode 100644
index 0000000..7ea6064
--- /dev/null
+++ b/sbin/mount_cd9660/mount_cd9660.c
@@ -0,0 +1,270 @@
+/*
+ * Copyright (c) 1992, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley
+ * by Pace Willisson (pace@blitz.com). The Rock Ridge Extension
+ * Support code is derived from software contributed to Berkeley
+ * by Atsushi Murai (amurai@spec.co.jp).
+ *
+ * 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.
+ * 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.
+ *
+ * @(#)mount_cd9660.c 8.7 (Berkeley) 5/1/95
+ */
+
+#ifndef lint
+static const char copyright[] =
+"@(#) Copyright (c) 1992, 1993, 1994\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+/*
+static char sccsid[] = "@(#)mount_cd9660.c 8.7 (Berkeley) 5/1/95";
+*/
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <sys/cdio.h>
+#include <sys/file.h>
+#include <sys/param.h>
+#include <sys/mount.h>
+#include <sys/module.h>
+#include <sys/iconv.h>
+#include <sys/linker.h>
+
+#include <arpa/inet.h>
+
+#include <err.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include "mntopts.h"
+
+static struct mntopt mopts[] = {
+ MOPT_STDOPTS,
+ MOPT_UPDATE,
+ MOPT_END
+};
+
+static int get_ssector(const char *dev);
+static int set_charset(struct iovec **, int *iovlen, const char *);
+void usage(void);
+
+int
+main(int argc, char **argv)
+{
+ struct iovec *iov;
+ int iovlen;
+ int ch, mntflags;
+ char *dev, *dir, *p, *val, mntpath[MAXPATHLEN];
+ int verbose;
+ int ssector; /* starting sector, 0 for 1st session */
+ char fstype[] = "cd9660";
+
+ iov = NULL;
+ iovlen = 0;
+ mntflags = verbose = 0;
+ ssector = -1;
+
+ while ((ch = getopt(argc, argv, "begjo:rs:vC:")) != -1)
+ switch (ch) {
+ case 'b':
+ build_iovec(&iov, &iovlen, "brokenjoliet", NULL, (size_t)-1);
+ break;
+ case 'e':
+ build_iovec(&iov, &iovlen, "extatt", NULL, (size_t)-1);
+ break;
+ case 'g':
+ build_iovec(&iov, &iovlen, "gens", NULL, (size_t)-1);
+ break;
+ case 'j':
+ build_iovec(&iov, &iovlen, "nojoliet", NULL, (size_t)-1);
+ break;
+ case 'o':
+ getmntopts(optarg, mopts, &mntflags, NULL);
+ p = strchr(optarg, '=');
+ val = NULL;
+ if (p != NULL) {
+ *p = '\0';
+ val = p + 1;
+ }
+ build_iovec(&iov, &iovlen, optarg, val, (size_t)-1);
+ break;
+ case 'r':
+ build_iovec(&iov, &iovlen, "norrip", NULL, (size_t)-1);
+ break;
+ case 's':
+ ssector = atoi(optarg);
+ break;
+ case 'v':
+ verbose++;
+ break;
+ case 'C':
+ if (set_charset(&iov, &iovlen, optarg) == -1)
+ err(EX_OSERR, "cd9660_iconv");
+ build_iovec(&iov, &iovlen, "kiconv", NULL, (size_t)-1);
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 2)
+ usage();
+
+ dev = argv[0];
+ dir = argv[1];
+
+ /*
+ * Resolve the mountpoint with realpath(3) and remove unnecessary
+ * slashes from the devicename if there are any.
+ */
+ if (checkpath(dir, mntpath) != 0)
+ err(1, "%s", mntpath);
+ (void)rmslashes(dev, dev);
+
+ if (ssector == -1) {
+ /*
+ * The start of the session has not been specified on
+ * the command line. If we can successfully read the
+ * TOC of a CD-ROM, use the last data track we find.
+ * Otherwise, just use 0, in order to mount the very
+ * first session. This is compatible with the
+ * historic behaviour of mount_cd9660(8). If the user
+ * has specified -s <ssector> above, we don't get here
+ * and leave the user's will.
+ */
+ if ((ssector = get_ssector(dev)) == -1) {
+ if (verbose)
+ printf("could not determine starting sector, "
+ "using very first session\n");
+ ssector = 0;
+ } else if (verbose)
+ printf("using starting sector %d\n", ssector);
+ }
+ mntflags |= MNT_RDONLY;
+ build_iovec(&iov, &iovlen, "fstype", fstype, (size_t)-1);
+ build_iovec(&iov, &iovlen, "fspath", mntpath, (size_t)-1);
+ build_iovec(&iov, &iovlen, "from", dev, (size_t)-1);
+ build_iovec_argf(&iov, &iovlen, "ssector", "%d", ssector);
+
+ if (nmount(iov, iovlen, mntflags) < 0)
+ err(1, "%s", dev);
+ exit(0);
+}
+
+void
+usage(void)
+{
+ (void)fprintf(stderr,
+"usage: mount_cd9660 [-begjrv] [-C charset] [-o options] [-s startsector]\n"
+" special node\n");
+ exit(EX_USAGE);
+}
+
+static int
+get_ssector(const char *dev)
+{
+ struct ioc_toc_header h;
+ struct ioc_read_toc_entry t;
+ struct cd_toc_entry toc_buffer[100];
+ int fd, ntocentries, i;
+
+ if ((fd = open(dev, O_RDONLY)) == -1)
+ return -1;
+ if (ioctl(fd, CDIOREADTOCHEADER, &h) == -1) {
+ close(fd);
+ return -1;
+ }
+
+ ntocentries = h.ending_track - h.starting_track + 1;
+ if (ntocentries > 100) {
+ /* unreasonable, only 100 allowed */
+ close(fd);
+ return -1;
+ }
+ t.address_format = CD_LBA_FORMAT;
+ t.starting_track = 0;
+ t.data_len = ntocentries * sizeof(struct cd_toc_entry);
+ t.data = toc_buffer;
+
+ if (ioctl(fd, CDIOREADTOCENTRYS, (char *) &t) == -1) {
+ close(fd);
+ return -1;
+ }
+ close(fd);
+
+ for (i = ntocentries - 1; i >= 0; i--)
+ if ((toc_buffer[i].control & 4) != 0)
+ /* found a data track */
+ break;
+ if (i < 0)
+ return -1;
+
+ return ntohl(toc_buffer[i].addr.lba);
+}
+
+static int
+set_charset(struct iovec **iov, int *iovlen, const char *localcs)
+{
+ int error;
+ char *cs_disk; /* disk charset for Joliet cs conversion */
+ char *cs_local; /* local charset for Joliet cs conversion */
+
+ cs_disk = NULL;
+ cs_local = NULL;
+
+ if (modfind("cd9660_iconv") < 0)
+ if (kldload("cd9660_iconv") < 0 || modfind("cd9660_iconv") < 0) {
+ warnx( "cannot find or load \"cd9660_iconv\" kernel module");
+ return (-1);
+ }
+
+ if ((cs_disk = malloc(ICONV_CSNMAXLEN)) == NULL)
+ return (-1);
+ if ((cs_local = malloc(ICONV_CSNMAXLEN)) == NULL) {
+ free(cs_disk);
+ return (-1);
+ }
+ strncpy(cs_disk, ENCODING_UNICODE, ICONV_CSNMAXLEN);
+ strncpy(cs_local, kiconv_quirkcs(localcs, KICONV_VENDOR_MICSFT),
+ ICONV_CSNMAXLEN);
+ error = kiconv_add_xlat16_cspairs(cs_disk, cs_local);
+ if (error)
+ return (-1);
+
+ build_iovec(iov, iovlen, "cs_disk", cs_disk, (size_t)-1);
+ build_iovec(iov, iovlen, "cs_local", cs_local, (size_t)-1);
+
+ return (0);
+}
diff --git a/sbin/mount_fusefs/Makefile b/sbin/mount_fusefs/Makefile
new file mode 100644
index 0000000..9c84fa6
--- /dev/null
+++ b/sbin/mount_fusefs/Makefile
@@ -0,0 +1,32 @@
+# $FreeBSD$
+
+.if defined(DEBUG)
+DEBUG_FLAGS+= -D_DEBUG -g
+.endif
+
+.if defined(DEBUG2G)
+DEBUG_FLAGS+= -D_DEBUG2G -g
+.endif
+
+.if defined(DEBUG3G)
+DEBUG_FLAGS+= -D_DEBUG3G -g
+.endif
+
+.if defined(DEBUG_MSG)
+DEBUG_FLAGS+= -D_DEBUG_MSG
+.endif
+
+.if defined(F4BVERS)
+DEBUG_FLAGS+= -DFUSE4BSD_VERSION="\"${F4BVERS}\""
+.endif
+
+PROG= mount_fusefs
+SRCS= mount_fusefs.c getmntopts.c
+MAN8= mount_fusefs.8
+
+MOUNT= ${.CURDIR}/../mount
+CFLAGS+= -I${MOUNT}
+
+.PATH: ${MOUNT}
+
+.include <bsd.prog.mk>
diff --git a/sbin/mount_fusefs/mount_fusefs.8 b/sbin/mount_fusefs/mount_fusefs.8
new file mode 100644
index 0000000..3faf618
--- /dev/null
+++ b/sbin/mount_fusefs/mount_fusefs.8
@@ -0,0 +1,362 @@
+.\" Copyright (c) 1980, 1989, 1991, 1993
+.\" The Regents of the University of California.
+.\" Copyright (c) 2005, 2006 Csaba Henk
+.\" 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.
+.\" 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd October 11, 2012
+.Dt MOUNT_FUSEFS 8
+.Os
+.Sh NAME
+.Nm mount_fusefs
+.Nd mount a Fuse file system daemon
+.Sh SYNOPSIS
+.Nm
+.Op Fl A
+.Op Fl S
+.Op Fl v
+.Op Fl D Ar fuse_daemon
+.Op Fl O Ar daemon_opts
+.Op Fl s Ar special
+.Op Fl m Ar node
+.Op Fl h
+.Op Fl V
+.Op Fl o Ar option ...
+.Ar special node
+.Op Ar fuse_daemon ...
+.Sh DESCRIPTION
+Basic usage is to start a fuse daemon on the given
+.Ar special
+file. In practice, the daemon is assigned a
+.Ar special
+file automatically, which can then be indentified via
+.Xr fstat 1 .
+That special file can then be mounted by
+.Nm .
+.Pp
+However, the procedure of spawning a daemon will usually be automated
+so that it is performed by
+.Nm .
+If the command invoking a given
+.Ar fuse_daemon
+is appended to the list of arguments,
+.Nm
+will call the
+.Ar fuse_daemon
+via that command. In that way the
+.Ar fuse_daemon
+will be instructed to attach itself to
+.Ar special .
+From that on mounting goes as in the simple case. (See
+.Sx DAEMON MOUNTS . )
+.Pp
+The
+.Ar special
+argument will normally be treated as the path of the special file to mount.
+.Pp
+However, if
+.Pa auto
+is passed as
+.Ar special ,
+then
+.Nm
+will look for a suitable free fuse device by itself.
+.Pp
+Finally, if
+.Ar special
+is an integer it will be interpreted as the number
+of the file descriptor of an already open fuse device
+(used when the Fuse library invokes
+.Nm .
+(See
+.Sx DAEMON MOUNTS ) .
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl A , Ic --reject-allow_other
+Prohibit the
+.Cm allow_other
+mount flag. Intended for use in scripts and the
+.Xr sudoers 5
+file.
+.It Fl S , Ic --safe
+Run in safe mode (i.e. reject invoking a filesystem daemon)
+.It Fl v
+Be verbose
+.It Fl D, Ic --daemon Ar daemon
+Call the specified
+.Ar daemon
+.It Fl O, Ic --daemon_opts Ar opts
+Add
+.Ar opts
+to the daemon's command line
+.It Fl s, Ic --special Ar special
+Use
+.Ar special
+as special
+.It Fl m, Ic --mountpath Ar node
+Mount on
+.Ar node
+.It Fl h, Ic --help
+Show help
+.It Fl V, Ic --version
+Show version information
+.It Fl o
+Mount options are specified via
+.Fl o .
+The following options are available (and also their negated versions,
+by prefixing them with
+.Dq no ) :
+.Bl -tag -width indent
+.It Cm default_permissions
+Enable traditional (file mode based) permission checking in kernel
+.It Cm allow_other
+Do not apply
+.Sx STRICT ACCESS POLICY .
+Only root can use this option
+.It Cm max_read Ns = Ns Ar n
+Limit size of read requests to
+.Ar n
+.It Cm private
+Refuse shared mounting of the daemon. This is the default behaviour,
+to allow sharing, expicitly use
+.Fl o Cm noprivate
+.It Cm neglect_shares
+Do not refuse unmounting if there are secondary mounts
+.It Cm push_symlinks_in
+Prefix absolute symlinks with the mountpoint
+.El
+.El
+.Pp
+Besides the above mount options, there is a set of pseudo-mount options which
+are supported by the Fuse library. One can list these by passing
+.Fl h
+to a Fuse daemon. Most of these options have effect only on the behaviour of
+the daemon (that is, their scope is limited to userspace). However,
+there are some which do require in-kernel support.
+Currently the options supported by the kernel are:
+.Bl -tag -width indent
+.It Cm direct_io
+Bypass the buffer cache system
+.It Cm kernel_cache
+By default cached buffers of a given file are flushed at each
+.Xr open 2 .
+This option disables this behaviour
+.El
+.Sh DAEMON MOUNTS
+Usually users do not need to use
+.Nm
+directly, as the Fuse library enables Fuse daemons to invoke
+.Nm .
+That is,
+.Pp
+.Dl fuse_daemon device mountpoint
+.Pp
+has the same effect as
+.Pp
+.Dl mount_fusefs auto mountpoint fuse_daemon
+.Pp
+This is the recommended usage when you want basic usage
+(eg, run the daemon at a low privilege level but mount it as root).
+.Sh STRICT ACCESS POLICY
+The strict access policy for Fuse filesystems lets one to use the filesystem
+only if the filesystem daemon has the same credentials (uid, real uid, gid,
+real gid) as the user.
+.Pp
+This is applied for Fuse mounts by default and only root can mount without
+the strict access policy (ie. the
+.Cm allow_other
+mount option).
+.Pp
+This is to shield users from the daemon
+.Dq spying
+on their I/O activities.
+.Pp
+Users might opt to willingly relax strict access policy (as far they
+are concerned) by doing their own secondary mount (See
+.Sx SHARED MOUNTS ) .
+.Sh SHARED MOUNTS
+A Fuse daemon can be shared (ie. mounted multiple times).
+When doing the first (primary) mount, the spawner and the mounter of the daemon
+must have the same uid, or the mounter should be the superuser.
+.Pp
+After the primary mount is in place, secondary mounts can be done by anyone
+unless this feature is disabled by
+.Cm private .
+The behaviour of a secondary mount is analogous to that of symbolic
+links: they redirect all filesystem operations to the primary mount.
+.Pp
+Doing a secondary mount is like signing an agreement: by this action, the mounter
+agrees that the Fuse daemon can trace her I/O activities. From then on
+she is not banned from using the filesystem (either via her own mount or
+via the primary mount), regardless whether
+.Cm allow_other
+is used or not.
+.Pp
+The device name of a secondary mount is the device name of the corresponding
+primary mount, followed by a '#' character and the index of the secondary
+mount; e.g.
+.Pa /dev/fuse0#3 .
+.Sh SECURITY
+System administrators might want to use a custom mount policy (ie., one going
+beyond the
+.Va vfs.usermount
+sysctl). The primary tool for such purposes is
+.Xr sudo 8 .
+However, given that
+.Nm
+is capable of invoking an arbitrary program, one must be careful when doing this.
+.Nm
+is designed in a way such that it makes that easy. For this purpose,
+there are options which disable certain risky features (ie.
+.Fl S
+and
+.Fl A ) ,
+and command line parsing is done in a flexible way: mixing options and
+non-options is allowed, but processing them stops at the third non-option
+argument (after the first two has been utilized as device and mountpoint).
+The rest of the command line specifies the daemon and its arguments.
+(Alternatively, the daemon, the special and the mount path can be
+specified using the respective options.) Note that
+.Nm
+ignores the environment variable
+.Ev POSIXLY_CORRECT
+and always behaves as described.
+.Pp
+In general, to be as scripting /
+.Xr sudoers 5
+friendly as possible, no information has a fixed
+position in the command line, but once a given piece of information is
+provided, subsequent arguments/options cannot override it (with the
+exception of some non-critical ones).
+.Sh ENVIRONMENT
+.Bl -tag -width ".Ev MOUNT_FUSEFS_SAFE"
+.It Ev MOUNT_FUSEFS_SAFE
+This has the same effect as the
+.Fl S
+option.
+.It Ev MOUNT_FUSEFS_VERBOSE
+This has the same effect as the
+.Fl v
+option.
+.It Ev MOUNT_FUSEFS_IGNORE_UNKNOWN
+If set,
+.Nm
+will ignore uknown mount options.
+.It Ev MOUNT_FUSEFS_CALL_BY_LIB
+Adjust behaviour to the needs of the FUSE library. Currently it effects
+help output.
+.El
+.Pp
+Although the following variables do not have any effect on
+.Nm
+itself, they affect the behaviour of fuse daemons:
+.Bl -tag -width ".Ev FUSE_DEV_NAME"
+.It Ev FUSE_DEV_NAME
+Device to attach. If not set, the multiplexer path
+.Ar /dev/fuse
+is used.
+.It Ev FUSE_DEV_FD
+File desciptor of an opened Fuse device to use. Overrides
+.Ev FUSE_DEV_NAME .
+.It Ev FUSE_NO_MOUNT
+If set, the library will not attempt to mount the filesystem, even
+if a mountpoint argument is supplied.
+.El
+.Sh FILES
+.Bl -tag -width /dev/fuse
+.It Pa /dev/fuse
+Fuse device with which the kernel and Fuse daemons can communicate.
+.It Pa /dev/fuse
+The multiplexer path. An
+.Xr open 2
+performed on it automatically is passed to a free Fuse device by the kernel
+(which might be created just for this puprose).
+.El
+.Sh EXAMPLES
+Mount the example filesystem in the Fuse distribution (from its directory):
+either
+.Pp
+.Dl ./fusexmp /mnt/fuse
+.Pp
+or
+.Pp
+.Dl mount_fusefs auto /mnt/fuse ./fusexmp
+.Pp
+Doing the same in two steps, using
+.Pa /dev/fuse0 :
+.Pp
+.Dl FUSE_DEV_NAME=/dev/fuse ./fusexmp &&
+.Dl mount_fusefs /dev/fuse /mnt/fuse
+.Pp
+A script wrapper for fusexmp which ensures that
+.Nm
+does not call any external utility and also provides a hacky
+(non race-free) automatic device selection:
+.Pp
+.Dl #!/bin/sh -e
+.Pp
+.Dl FUSE_DEV_NAME=/dev/fuse fusexmp
+.Dl mount_fusefs -S /dev/fuse /mnt/fuse \(lq$@\(rq
+.Sh SEE ALSO
+.Xr fstat 1 ,
+.Xr mount 8 ,
+.Xr sudo 8 ,
+.Xr umount 8
+.Sh HISTORY
+.Nm
+appears as the part of the FreeBSD implementation of the Fuse userspace filesystem
+framework (see http://fuse.sourceforge.net). This user interface is FreeBSD specific.
+.Sh CAVEATS
+Secondary mounts should be unmounted via their device name. If an attempt is
+made to be unmount them via their filesystem root path, the unmount request
+will be forwarded to the primary mount path.
+In general, unmounting by device name is less error-prone than by mount path
+(although the latter will also work under normal circumstances).
+.Pp
+If the daemon is specified via the
+.Fl D
+and
+.Fl O
+options, it will be invoked via
+.Xr system 3 ,
+and the daemon's command line will also have an
+.Dq &
+control operator appended, so that we do not have to wait for its termination.
+You should use a simple command line when invoking the daemon via these options.
+.Sh BUGS
+.Ar special
+is treated as a multiplexer if and only if it is literally the same as
+.Pa auto
+or
+.Pa /dev/fuse .
+Other paths which are equivalent with
+.Pa /dev/fuse
+(eg.,
+.Pa /../dev/fuse )
+are not.
diff --git a/sbin/mount_fusefs/mount_fusefs.c b/sbin/mount_fusefs/mount_fusefs.c
new file mode 100644
index 0000000..7d4b9a1
--- /dev/null
+++ b/sbin/mount_fusefs/mount_fusefs.c
@@ -0,0 +1,506 @@
+/*-
+ * Copyright (c) 2005 Jean-Sebastien Pedron
+ * Copyright (c) 2005 Csaba Henk
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/mount.h>
+#include <sys/uio.h>
+#include <sys/stat.h>
+#include <sys/sysctl.h>
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <getopt.h>
+#include <limits.h>
+#include <osreldate.h>
+#include <paths.h>
+
+#include "mntopts.h"
+
+#ifndef FUSE4BSD_VERSION
+#define FUSE4BSD_VERSION "0.3.9-pre1"
+#endif
+
+void __usage_short(void);
+void usage(void);
+void helpmsg(void);
+void showversion(void);
+int init_backgrounded(void);
+
+static struct mntopt mopts[] = {
+ #define ALTF_PRIVATE 0x01
+ { "private", 0, ALTF_PRIVATE, 1 },
+ { "neglect_shares", 0, 0x02, 1 },
+ { "push_symlinks_in", 0, 0x04, 1 },
+ { "allow_other", 0, 0x08, 1 },
+ { "default_permissions", 0, 0x10, 1 },
+ #define ALTF_MAXREAD 0x20
+ { "max_read=", 0, ALTF_MAXREAD, 1 },
+ #define ALTF_SUBTYPE 0x40
+ { "subtype=", 0, ALTF_SUBTYPE, 1 },
+ #define ALTF_SYNC_UNMOUNT 0x80
+ { "sync_unmount", 0, ALTF_SYNC_UNMOUNT, 1 },
+ /*
+ * MOPT_AUTOMOUNTED, included by MOPT_STDOPTS, does not fit into
+ * the 'flags' argument to nmount(2). We have to abuse altflags
+ * to pass it, as string, via iovec.
+ */
+ #define ALTF_AUTOMOUNTED 0x100
+ { "automounted", 0, ALTF_AUTOMOUNTED, 1 },
+ /* Linux specific options, we silently ignore them */
+ { "fsname=", 0, 0x00, 1 },
+ { "fd=", 0, 0x00, 1 },
+ { "rootmode=", 0, 0x00, 1 },
+ { "user_id=", 0, 0x00, 1 },
+ { "group_id=", 0, 0x00, 1 },
+ { "large_read", 0, 0x00, 1 },
+ /* "nonempty", just the first two chars are stripped off during parsing */
+ { "nempty", 0, 0x00, 1 },
+ MOPT_STDOPTS,
+ MOPT_END
+};
+
+struct mntval {
+ int mv_flag;
+ void *mv_value;
+ int mv_len;
+};
+
+static struct mntval mvals[] = {
+ { ALTF_MAXREAD, NULL, 0 },
+ { ALTF_SUBTYPE, NULL, 0 },
+ { 0, NULL, 0 }
+};
+
+#define DEFAULT_MOUNT_FLAGS ALTF_PRIVATE | ALTF_SYNC_UNMOUNT
+
+int
+main(int argc, char *argv[])
+{
+ struct iovec *iov;
+ int mntflags, iovlen, verbose = 0;
+ char *dev = NULL, *dir = NULL, mntpath[MAXPATHLEN];
+ char *devo = NULL, *diro = NULL;
+ char ndev[128], fdstr[15];
+ int i, done = 0, reject_allow_other = 0, safe_level = 0;
+ int altflags = DEFAULT_MOUNT_FLAGS;
+ int __altflags = DEFAULT_MOUNT_FLAGS;
+ int ch = 0;
+ struct mntopt *mo;
+ struct mntval *mv;
+ static struct option longopts[] = {
+ {"reject-allow_other", no_argument, NULL, 'A'},
+ {"safe", no_argument, NULL, 'S'},
+ {"daemon", required_argument, NULL, 'D'},
+ {"daemon_opts", required_argument, NULL, 'O'},
+ {"special", required_argument, NULL, 's'},
+ {"mountpath", required_argument, NULL, 'm'},
+ {"version", no_argument, NULL, 'V'},
+ {"help", no_argument, NULL, 'h'},
+ {0,0,0,0}
+ };
+ int pid = 0;
+ int fd = -1, fdx;
+ char *ep;
+ char *daemon_str = NULL, *daemon_opts = NULL;
+
+ /*
+ * We want a parsing routine which is not sensitive to
+ * the position of args/opts; it should extract the
+ * first two args and stop at the beginning of the rest.
+ * (This makes it easier to call mount_fusefs from external
+ * utils than it is with a strict "util flags args" syntax.)
+ */
+
+ iov = NULL;
+ iovlen = 0;
+ mntflags = 0;
+ /* All in all, I feel it more robust this way... */
+ unsetenv("POSIXLY_CORRECT");
+ if (getenv("MOUNT_FUSEFS_IGNORE_UNKNOWN"))
+ getmnt_silent = 1;
+ if (getenv("MOUNT_FUSEFS_VERBOSE"))
+ verbose = 1;
+
+ do {
+ for (i = 0; i < 3; i++) {
+ if (optind < argc && argv[optind][0] != '-') {
+ if (dir) {
+ done = 1;
+ break;
+ }
+ if (dev)
+ dir = argv[optind];
+ else
+ dev = argv[optind];
+ optind++;
+ }
+ }
+ switch(ch) {
+ case 'A':
+ reject_allow_other = 1;
+ break;
+ case 'S':
+ safe_level = 1;
+ break;
+ case 'D':
+ if (daemon_str)
+ errx(1, "daemon specified inconsistently");
+ daemon_str = optarg;
+ break;
+ case 'O':
+ if (daemon_opts)
+ errx(1, "daemon opts specified inconsistently");
+ daemon_opts = optarg;
+ break;
+ case 'o':
+ getmntopts(optarg, mopts, &mntflags, &altflags);
+ for (mv = mvals; mv->mv_flag; ++mv) {
+ if (! (altflags & mv->mv_flag))
+ continue;
+ for (mo = mopts; mo->m_flag; ++mo) {
+ char *p, *q;
+
+ if (mo->m_flag != mv->mv_flag)
+ continue;
+ p = strstr(optarg, mo->m_option);
+ if (p) {
+ p += strlen(mo->m_option);
+ q = p;
+ while (*q != '\0' && *q != ',')
+ q++;
+ mv->mv_len = q - p + 1;
+ mv->mv_value = malloc(mv->mv_len);
+ memcpy(mv->mv_value, p, mv->mv_len - 1);
+ ((char *)mv->mv_value)[mv->mv_len - 1] = '\0';
+ break;
+ }
+ }
+ }
+ break;
+ case 's':
+ if (devo)
+ errx(1, "special specified inconsistently");
+ devo = optarg;
+ break;
+ case 'm':
+ if (diro)
+ errx(1, "mount path specified inconsistently");
+ diro = optarg;
+ break;
+ case 'v':
+ verbose = 1;
+ break;
+ case 'h':
+ helpmsg();
+ break;
+ case 'V':
+ showversion();
+ break;
+ case '\0':
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ if (done)
+ break;
+ } while ((ch = getopt_long(argc, argv, "AvVho:SD:O:s:m:", longopts, NULL)) != -1);
+
+ argc -= optind;
+ argv += optind;
+
+ if (devo) {
+ if (dev)
+ errx(1, "special specified inconsistently");
+ dev = devo;
+ } else if (diro)
+ errx(1, "if mountpoint is given via an option, special should also be given via an option");
+
+ if (diro) {
+ if (dir)
+ errx(1, "mount path specified inconsistently");
+ dir = diro;
+ }
+
+ if ((! dev) && argc > 0) {
+ dev = *argv++;
+ argc--;
+ }
+
+ if ((! dir) && argc > 0) {
+ dir = *argv++;
+ argc--;
+ }
+
+ if (! (dev && dir))
+ errx(1, "missing special and/or mountpoint");
+
+ for (mo = mopts; mo->m_flag; ++mo) {
+ if (altflags & mo->m_flag) {
+ int iov_done = 0;
+
+ if (reject_allow_other &&
+ strcmp(mo->m_option, "allow_other") == 0)
+ /*
+ * reject_allow_other is stronger than a
+ * negative of allow_other: if this is set,
+ * allow_other is blocked, period.
+ */
+ errx(1, "\"allow_other\" usage is banned by respective option");
+
+ for (mv = mvals; mv->mv_flag; ++mv) {
+ if (mo->m_flag != mv->mv_flag)
+ continue;
+ if (mv->mv_value) {
+ build_iovec(&iov, &iovlen, mo->m_option, mv->mv_value, mv->mv_len);
+ iov_done = 1;
+ break;
+ }
+ }
+ if (! iov_done)
+ build_iovec(&iov, &iovlen, mo->m_option,
+ __DECONST(void *, ""), -1);
+ }
+ if (__altflags & mo->m_flag) {
+ char *uscore_opt;
+
+ if (asprintf(&uscore_opt, "__%s", mo->m_option) == -1)
+ err(1, "failed to allocate memory");
+ build_iovec(&iov, &iovlen, uscore_opt,
+ __DECONST(void *, ""), -1);
+ free(uscore_opt);
+ }
+ }
+
+ if (getenv("MOUNT_FUSEFS_SAFE"))
+ safe_level = 1;
+
+ if (safe_level > 0 && (argc > 0 || daemon_str || daemon_opts))
+ errx(1, "safe mode, spawning daemon not allowed");
+
+ if ((argc > 0 && (daemon_str || daemon_opts)) ||
+ (daemon_opts && ! daemon_str))
+ errx(1, "daemon specified inconsistently");
+
+ /*
+ * Resolve the mountpoint with realpath(3) and remove unnecessary
+ * slashes from the devicename if there are any.
+ */
+ if (checkpath(dir, mntpath) != 0)
+ err(1, "%s", mntpath);
+ (void)rmslashes(dev, dev);
+
+ if (strcmp(dev, "auto") == 0)
+ dev = __DECONST(char *, "/dev/fuse");
+
+ if (strcmp(dev, "/dev/fuse") == 0) {
+ if (! (argc > 0 || daemon_str)) {
+ fprintf(stderr, "Please also specify the fuse daemon to run when mounting via the multiplexer!\n");
+ usage();
+ }
+ if ((fd = open(dev, O_RDWR)) < 0)
+ err(1, "failed to open fuse device");
+ } else {
+ fdx = strtol(dev, &ep, 10);
+ if (*ep == '\0')
+ fd = fdx;
+ }
+
+ /* Identifying device */
+ if (fd >= 0) {
+ struct stat sbuf;
+ char *ndevbas, *lep;
+
+ if (fstat(fd, &sbuf) == -1)
+ err(1, "cannot stat device file descriptor");
+
+ strcpy(ndev, _PATH_DEV);
+ ndevbas = ndev + strlen(_PATH_DEV);
+ devname_r(sbuf.st_rdev, S_IFCHR, ndevbas,
+ sizeof(ndev) - strlen(_PATH_DEV));
+
+ if (strncmp(ndevbas, "fuse", 4))
+ errx(1, "mounting inappropriate device");
+
+ strtol(ndevbas + 4, &lep, 10);
+ if (*lep != '\0')
+ errx(1, "mounting inappropriate device");
+
+ dev = ndev;
+ }
+
+ if (argc > 0 || daemon_str) {
+ char *fds;
+
+ if (fd < 0 && (fd = open(dev, O_RDWR)) < 0)
+ err(1, "failed to open fuse device");
+
+ if (asprintf(&fds, "%d", fd) == -1)
+ err(1, "failed to allocate memory");
+ setenv("FUSE_DEV_FD", fds, 1);
+ free(fds);
+ setenv("FUSE_NO_MOUNT", "1", 1);
+
+ if (daemon_str) {
+ char *bgdaemon;
+ int len;
+
+ if (! daemon_opts)
+ daemon_opts = __DECONST(char *, "");
+
+ len = strlen(daemon_str) + 1 + strlen(daemon_opts) +
+ 2 + 1;
+ bgdaemon = calloc(1, len);
+
+ if (! bgdaemon)
+ err(1, "failed to allocate memory");
+
+ strlcpy(bgdaemon, daemon_str, len);
+ strlcat(bgdaemon, " ", len);
+ strlcat(bgdaemon, daemon_opts, len);
+ strlcat(bgdaemon, " &", len);
+
+ if (system(bgdaemon))
+ err(1, "failed to call fuse daemon");
+ } else {
+ if ((pid = fork()) < 0)
+ err(1, "failed to fork for fuse daemon");
+
+ if (pid == 0) {
+ execvp(argv[0], argv);
+ err(1, "failed to exec fuse daemon");
+ }
+ }
+ }
+
+ if (fd >= 0 && ! init_backgrounded() && close(fd) < 0) {
+ if (pid)
+ kill(pid, SIGKILL);
+ err(1, "failed to close fuse device");
+ }
+
+ /* Prepare the options vector for nmount(). build_iovec() is declared
+ * in mntopts.h. */
+ sprintf(fdstr, "%d", fd);
+ build_iovec(&iov, &iovlen, "fstype", __DECONST(void *, "fusefs"), -1);
+ build_iovec(&iov, &iovlen, "fspath", mntpath, -1);
+ build_iovec(&iov, &iovlen, "from", dev, -1);
+ build_iovec(&iov, &iovlen, "fd", fdstr, -1);
+
+ if (verbose)
+ fprintf(stderr, "mounting fuse daemon on device %s\n", dev);
+
+ if (nmount(iov, iovlen, mntflags) < 0)
+ err(EX_OSERR, "%s on %s", dev, mntpath);
+
+ exit(0);
+}
+
+void
+__usage_short(void) {
+ fprintf(stderr,
+ "usage:\n%s [-A|-S|-v|-V|-h|-D daemon|-O args|-s special|-m node|-o option...] special node [daemon args...]\n\n",
+ getprogname());
+}
+
+void
+usage(void)
+{
+ struct mntopt *mo;
+
+ __usage_short();
+
+ fprintf(stderr, "known options:\n");
+ for (mo = mopts; mo->m_flag; ++mo)
+ fprintf(stderr, "\t%s\n", mo->m_option);
+
+ fprintf(stderr, "\n(use -h for a detailed description of these options)\n");
+ exit(EX_USAGE);
+}
+
+void
+helpmsg(void)
+{
+ if (! getenv("MOUNT_FUSEFS_CALL_BY_LIB")) {
+ __usage_short();
+ fprintf(stderr, "description of options:\n");
+ }
+
+ /*
+ * The main use case of this function is giving info embedded in general
+ * FUSE lib help output. Therefore the style and the content of the output
+ * tries to fit there as much as possible.
+ */
+ fprintf(stderr,
+ " -o allow_other allow access to other users\n"
+ /* " -o nonempty allow mounts over non-empty file/dir\n" */
+ " -o default_permissions enable permission checking by kernel\n"
+ /*
+ " -o fsname=NAME set filesystem name\n"
+ " -o large_read issue large read requests (2.4 only)\n"
+ */
+ " -o subtype=NAME set filesystem type\n"
+ " -o max_read=N set maximum size of read requests\n"
+ " -o noprivate allow secondary mounting of the filesystem\n"
+ " -o neglect_shares don't report EBUSY when unmount attempted\n"
+ " in presence of secondary mounts\n"
+ " -o push_symlinks_in prefix absolute symlinks with mountpoint\n"
+ " -o sync_unmount do unmount synchronously\n"
+ );
+ exit(EX_USAGE);
+}
+
+void
+showversion(void)
+{
+ puts("mount_fusefs [fuse4bsd] version: " FUSE4BSD_VERSION);
+ exit(EX_USAGE);
+}
+
+int
+init_backgrounded(void)
+{
+ int ibg;
+ size_t len;
+
+ len = sizeof(ibg);
+
+ if (sysctlbyname("vfs.fuse.init_backgrounded", &ibg, &len, NULL, 0))
+ return (0);
+
+ return (ibg);
+}
diff --git a/sbin/mount_msdosfs/Makefile b/sbin/mount_msdosfs/Makefile
new file mode 100644
index 0000000..ede86b4
--- /dev/null
+++ b/sbin/mount_msdosfs/Makefile
@@ -0,0 +1,19 @@
+#
+# $FreeBSD$
+#
+
+PROG= mount_msdosfs
+SRCS= mount_msdosfs.c getmntopts.c
+MAN= mount_msdosfs.8
+LIBADD= kiconv
+
+MOUNT= ${.CURDIR}/../mount
+CFLAGS+= -I${MOUNT}
+
+# Needs to be dynamically linked for optional dlopen() access to
+# userland libiconv
+NO_SHARED?= NO
+
+.PATH: ${MOUNT}
+
+.include <bsd.prog.mk>
diff --git a/sbin/mount_msdosfs/mount_msdosfs.8 b/sbin/mount_msdosfs/mount_msdosfs.8
new file mode 100644
index 0000000..ae69aeb
--- /dev/null
+++ b/sbin/mount_msdosfs/mount_msdosfs.8
@@ -0,0 +1,223 @@
+.\" $NetBSD: mount_msdos.8,v 1.13 1998/02/06 05:57:00 perry Exp $
+.\"
+.\" Copyright (c) 1993,1994 Christopher G. Demetriou
+.\" 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 Christopher G. Demetriou.
+.\" 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd October 1, 2013
+.Dt MOUNT_MSDOSFS 8
+.Os
+.Sh NAME
+.Nm mount_msdosfs
+.Nd mount an MS-DOS file system
+.Sh SYNOPSIS
+.Nm
+.Op Fl 9ls
+.Op Fl D Ar DOS_codepage
+.Op Fl g Ar gid
+.Op Fl L Ar locale
+.Op Fl M Ar mask
+.Op Fl m Ar mask
+.Op Fl o Ar options
+.Op Fl u Ar uid
+.Op Fl W Ar table
+.Ar special node
+.Sh DESCRIPTION
+The
+.Nm
+utility attaches the MS-DOS file system residing on
+the device
+.Pa special
+to the global file system namespace at the location
+indicated by
+.Pa node .
+This command is normally executed by
+.Xr mount 8
+at boot time, but can be used by any user to mount an
+MS-DOS file system on any directory that they own (provided,
+of course, that they have appropriate access to the device that
+contains the file system).
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl o Ar options
+Use the specified mount
+.Ar options ,
+as described in
+.Xr mount 8 .
+The following MSDOS file system-specific options are available:
+.Bl -tag -width indent
+.It Cm large
+Support file systems larger than 128 gigabytes at the expense
+of 32 bytes of kernel memory for each file on disk.
+This memory will not be reclaimed until the file system has
+been unmounted.
+.It Cm longnames
+Force Windows 95 long filenames to be visible.
+.It Cm shortnames
+Force only the old MS-DOS 8.3 style filenames to be visible.
+.It Cm nowin95
+Completely ignore Windows 95 extended file information.
+.El
+.It Fl u Ar uid
+Set the owner of the files in the file system to
+.Ar uid .
+The default owner is the owner of the directory
+on which the file system is being mounted.
+.It Fl g Ar gid
+Set the group of the files in the file system to
+.Ar gid .
+The default group is the group of the directory
+on which the file system is being mounted.
+.It Fl m Ar mask
+Specify the maximum file permissions for files
+in the file system.
+(For example, a
+.Ar mask
+of
+.Li 755
+specifies that, by default, the owner should have
+read, write, and execute permissions for files, but
+others should only have read and execute permissions.
+See
+.Xr chmod 1
+for more information about octal file modes.
+Only the nine low-order bits of
+.Ar mask
+are used.
+The value of
+.Ar -M
+is used if it is supplied and
+.Ar -m
+is omitted.
+The default
+.Ar mask
+is taken from the
+directory on which the file system is being mounted.
+.It Fl M Ar mask
+Specify the maximum file permissions for directories
+in the file system.
+The value of
+.Ar -m
+is used if it is supplied and
+.Ar -M
+is omitted.
+See the previous option's description for details.
+.It Fl s
+Force behaviour to
+ignore and not generate Win'95 long filenames.
+.It Fl l
+Force listing and generation of
+Win'95 long filenames
+and separate creation/modification/access dates.
+.Pp
+If neither
+.Fl s
+nor
+.Fl l
+are given,
+.Nm
+searches the root directory of the file system to
+be mounted for any existing Win'95 long filenames.
+If no such entries are found, but short DOS filenames are found,
+.Fl s
+is the default.
+Otherwise
+.Fl l
+is assumed.
+.It Fl 9
+Ignore the special Win'95 directory entries even
+if deleting or renaming a file.
+This forces
+.Fl s .
+.\".It Fl G
+.\"This option causes the file system to be interpreted as an Atari-Gemdos
+.\"file system.
+.\"The differences to the MS-DOS file system are minimal and
+.\"limited to the boot block.
+.\"This option enforces
+.\".Fl s .
+.It Fl L Ar locale
+Specify locale name used for file name conversions
+for DOS and Win'95 names.
+By default ISO 8859-1 assumed as local character set.
+.It Fl D Ar DOS_codepage
+Specify the MS-DOS code page (aka IBM/OEM code page) name used for
+file name conversions for DOS names.
+.It Fl W Ar table
+.Bf Em
+This option is preserved for backward compatibility purpose only,
+and will be removed in the future.
+Please avoid using this option.
+.Ef
+.Pp
+Specify text file name with conversion table:
+.Pa iso22dos , iso72dos , koi2dos , koi8u2dos .
+.El
+.Sh EXAMPLES
+To mount a Russian MS-DOS file system located in
+.Pa /dev/ada1s1 :
+.Pp
+.Dl "mount_msdosfs -L ru_RU.KOI8-R -D CP866 /dev/ada1s1 /mnt"
+.Pp
+To mount a Japanese MS-DOS file system located in
+.Pa /dev/ada1s1 :
+.Pp
+.Dl "mount_msdosfs -L ja_JP.eucJP -D CP932 /dev/ada1s1 /mnt"
+.Sh SEE ALSO
+.Xr mount 2 ,
+.Xr unmount 2 ,
+.Xr fstab 5 ,
+.Xr msdosfs 5 ,
+.Xr mount 8
+.Pp
+List of Localized MS Operating Systems:
+.Pa http://www.microsoft.com/globaldev/reference/oslocversion.mspx .
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 2.0 .
+Its predecessor, the
+.Nm mount_pcfs
+utility appeared in
+.Fx 1.0 ,
+and was abandoned in favor
+of the more aptly-named
+.Nm .
+.Pp
+The character code conversion routine was added by
+.An Ryuichiro Imura Aq Mt imura@ryu16.org
+in 2003.
+.Sh CAVEATS
+The use of the
+.Fl 9
+flag could result in damaged file systems,
+albeit the damage is in part taken care of by
+procedures similar to the ones used in Win'95.
diff --git a/sbin/mount_msdosfs/mount_msdosfs.c b/sbin/mount_msdosfs/mount_msdosfs.c
new file mode 100644
index 0000000..8814fdd
--- /dev/null
+++ b/sbin/mount_msdosfs/mount_msdosfs.c
@@ -0,0 +1,324 @@
+/* $NetBSD: mount_msdos.c,v 1.18 1997/09/16 12:24:18 lukem Exp $ */
+
+/*
+ * Copyright (c) 1994 Christopher G. Demetriou
+ * 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 Christopher G. Demetriou.
+ * 4. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef lint
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/iconv.h>
+#include <sys/linker.h>
+#include <sys/module.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <grp.h>
+#include <locale.h>
+#include <pwd.h>
+#include <stdio.h>
+/* must be after stdio to declare fparseln */
+#include <libutil.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include "mntopts.h"
+
+static gid_t a_gid(char *);
+static uid_t a_uid(char *);
+static mode_t a_mask(char *);
+static void usage(void) __dead2;
+static int set_charset(struct iovec **iov, int *iovlen, const char *, const char *);
+
+int
+main(int argc, char **argv)
+{
+ struct iovec *iov = NULL;
+ int iovlen = 0;
+ struct stat sb;
+ int c, set_gid, set_uid, set_mask, set_dirmask;
+ char *dev, *dir, mntpath[MAXPATHLEN], *csp;
+ char fstype[] = "msdosfs";
+ char errmsg[255] = {0};
+ char *cs_dos = NULL;
+ char *cs_local = NULL;
+ mode_t mask = 0, dirmask = 0;
+ uid_t uid = 0;
+ gid_t gid = 0;
+
+ set_gid = set_uid = set_mask = set_dirmask = 0;
+
+ while ((c = getopt(argc, argv, "sl9u:g:m:M:o:L:D:W:")) != -1) {
+ switch (c) {
+ case 's':
+ build_iovec(&iov, &iovlen, "shortnames", NULL, (size_t)-1);
+ break;
+ case 'l':
+ build_iovec(&iov, &iovlen, "longnames", NULL, (size_t)-1);
+ break;
+ case '9':
+ build_iovec_argf(&iov, &iovlen, "nowin95", "", (size_t)-1);
+ break;
+ case 'u':
+ uid = a_uid(optarg);
+ set_uid = 1;
+ break;
+ case 'g':
+ gid = a_gid(optarg);
+ set_gid = 1;
+ break;
+ case 'm':
+ mask = a_mask(optarg);
+ set_mask = 1;
+ break;
+ case 'M':
+ dirmask = a_mask(optarg);
+ set_dirmask = 1;
+ break;
+ case 'L': {
+ const char *quirk = NULL;
+ if (setlocale(LC_CTYPE, optarg) == NULL)
+ err(EX_CONFIG, "%s", optarg);
+ csp = strchr(optarg,'.');
+ if (!csp)
+ err(EX_CONFIG, "%s", optarg);
+ quirk = kiconv_quirkcs(csp + 1, KICONV_VENDOR_MICSFT);
+ build_iovec_argf(&iov, &iovlen, "cs_local", quirk);
+ cs_local = strdup(quirk);
+ }
+ break;
+ case 'D':
+ cs_dos = strdup(optarg);
+ build_iovec_argf(&iov, &iovlen, "cs_dos", cs_dos, (size_t)-1);
+ break;
+ case 'o': {
+ char *p = NULL;
+ char *val = strdup("");
+ p = strchr(optarg, '=');
+ if (p != NULL) {
+ free(val);
+ *p = '\0';
+ val = p + 1;
+ }
+ build_iovec(&iov, &iovlen, optarg, val, (size_t)-1);
+ }
+ break;
+ case 'W':
+ if (strcmp(optarg, "iso22dos") == 0) {
+ cs_local = strdup("ISO8859-2");
+ cs_dos = strdup("CP852");
+ } else if (strcmp(optarg, "iso72dos") == 0) {
+ cs_local = strdup("ISO8859-7");
+ cs_dos = strdup("CP737");
+ } else if (strcmp(optarg, "koi2dos") == 0) {
+ cs_local = strdup("KOI8-R");
+ cs_dos = strdup("CP866");
+ } else if (strcmp(optarg, "koi8u2dos") == 0) {
+ cs_local = strdup("KOI8-U");
+ cs_dos = strdup("CP866");
+ } else {
+ err(EX_NOINPUT, "%s", optarg);
+ }
+ build_iovec(&iov, &iovlen, "cs_local", cs_local, (size_t)-1);
+ build_iovec(&iov, &iovlen, "cs_dos", cs_dos, (size_t)-1);
+ break;
+ case '?':
+ default:
+ usage();
+ break;
+ }
+ }
+
+ if (optind + 2 != argc)
+ usage();
+
+ if (set_mask && !set_dirmask) {
+ dirmask = mask;
+ set_dirmask = 1;
+ }
+ else if (set_dirmask && !set_mask) {
+ mask = dirmask;
+ set_mask = 1;
+ }
+
+ dev = argv[optind];
+ dir = argv[optind + 1];
+
+ if (cs_local != NULL) {
+ if (set_charset(&iov, &iovlen, cs_local, cs_dos) == -1)
+ err(EX_OSERR, "msdosfs_iconv");
+ build_iovec_argf(&iov, &iovlen, "kiconv", "");
+ } else if (cs_dos != NULL) {
+ build_iovec_argf(&iov, &iovlen, "cs_local", "ISO8859-1");
+ if (set_charset(&iov, &iovlen, "ISO8859-1", cs_dos) == -1)
+ err(EX_OSERR, "msdosfs_iconv");
+ build_iovec_argf(&iov, &iovlen, "kiconv", "");
+ }
+
+ /*
+ * Resolve the mountpoint with realpath(3) and remove unnecessary
+ * slashes from the devicename if there are any.
+ */
+ if (checkpath(dir, mntpath) != 0)
+ err(EX_USAGE, "%s", mntpath);
+ (void)rmslashes(dev, dev);
+
+ if (!set_gid || !set_uid || !set_mask) {
+ if (stat(mntpath, &sb) == -1)
+ err(EX_OSERR, "stat %s", mntpath);
+
+ if (!set_uid)
+ uid = sb.st_uid;
+ if (!set_gid)
+ gid = sb.st_gid;
+ if (!set_mask)
+ mask = dirmask =
+ sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO);
+ }
+
+ build_iovec(&iov, &iovlen, "fstype", fstype, (size_t)-1);
+ build_iovec(&iov, &iovlen, "fspath", mntpath, (size_t)-1);
+ build_iovec(&iov, &iovlen, "from", dev, (size_t)-1);
+ build_iovec(&iov, &iovlen, "errmsg", errmsg, sizeof(errmsg));
+ build_iovec_argf(&iov, &iovlen, "uid", "%d", uid);
+ build_iovec_argf(&iov, &iovlen, "gid", "%u", gid);
+ build_iovec_argf(&iov, &iovlen, "mask", "%u", mask);
+ build_iovec_argf(&iov, &iovlen, "dirmask", "%u", dirmask);
+
+ if (nmount(iov, iovlen, 0) < 0) {
+ if (errmsg[0])
+ err(1, "%s: %s", dev, errmsg);
+ else
+ err(1, "%s", dev);
+ }
+
+ exit (0);
+}
+
+gid_t
+a_gid(char *s)
+{
+ struct group *gr;
+ char *gname;
+ gid_t gid;
+
+ if ((gr = getgrnam(s)) != NULL)
+ gid = gr->gr_gid;
+ else {
+ for (gname = s; *s && isdigit(*s); ++s);
+ if (!*s)
+ gid = atoi(gname);
+ else
+ errx(EX_NOUSER, "unknown group id: %s", gname);
+ }
+ return (gid);
+}
+
+uid_t
+a_uid(char *s)
+{
+ struct passwd *pw;
+ char *uname;
+ uid_t uid;
+
+ if ((pw = getpwnam(s)) != NULL)
+ uid = pw->pw_uid;
+ else {
+ for (uname = s; *s && isdigit(*s); ++s);
+ if (!*s)
+ uid = atoi(uname);
+ else
+ errx(EX_NOUSER, "unknown user id: %s", uname);
+ }
+ return (uid);
+}
+
+mode_t
+a_mask(char *s)
+{
+ int done, rv;
+ char *ep;
+
+ done = 0;
+ rv = -1;
+ if (*s >= '0' && *s <= '7') {
+ done = 1;
+ rv = strtol(optarg, &ep, 8);
+ }
+ if (!done || rv < 0 || *ep)
+ errx(EX_USAGE, "invalid file mode: %s", s);
+ return (rv);
+}
+
+void
+usage(void)
+{
+ fprintf(stderr, "%s\n%s\n%s\n",
+ "usage: mount_msdosfs [-9ls] [-D DOS_codepage] [-g gid] [-L locale]",
+ " [-M mask] [-m mask] [-o options] [-u uid]",
+ " [-W table] special node");
+ exit(EX_USAGE);
+}
+
+int
+set_charset(struct iovec **iov, int *iovlen, const char *cs_local, const char *cs_dos)
+{
+ int error;
+
+ if (modfind("msdosfs_iconv") < 0)
+ if (kldload("msdosfs_iconv") < 0 || modfind("msdosfs_iconv") < 0) {
+ warnx("cannot find or load \"msdosfs_iconv\" kernel module");
+ return (-1);
+ }
+
+ build_iovec_argf(iov, iovlen, "cs_win", ENCODING_UNICODE);
+ error = kiconv_add_xlat16_cspairs(ENCODING_UNICODE, cs_local);
+ if (error)
+ return (-1);
+ if (cs_dos != NULL) {
+ error = kiconv_add_xlat16_cspairs(cs_dos, cs_local);
+ if (error)
+ return (-1);
+ } else {
+ build_iovec_argf(iov, iovlen, "cs_dos", cs_local);
+ error = kiconv_add_xlat16_cspair(cs_local, cs_local,
+ KICONV_FROM_UPPER | KICONV_LOWER);
+ if (error)
+ return (-1);
+ }
+
+ return (0);
+}
diff --git a/sbin/mount_nfs/Makefile b/sbin/mount_nfs/Makefile
new file mode 100644
index 0000000..75f7817
--- /dev/null
+++ b/sbin/mount_nfs/Makefile
@@ -0,0 +1,15 @@
+# @(#)Makefile 8.2 (Berkeley) 3/27/94
+#
+# $FreeBSD$
+
+PROG= mount_nfs
+SRCS= mount_nfs.c getmntopts.c mounttab.c
+MAN= mount_nfs.8
+
+MOUNT= ${.CURDIR}/../mount
+UMNTALL= ${.CURDIR}/../../usr.sbin/rpc.umntall
+CFLAGS+= -DNFS -I${MOUNT} -I${UMNTALL}
+
+.PATH: ${MOUNT} ${UMNTALL}
+
+.include <bsd.prog.mk>
diff --git a/sbin/mount_nfs/mount_nfs.8 b/sbin/mount_nfs/mount_nfs.8
new file mode 100644
index 0000000..3a9cff5
--- /dev/null
+++ b/sbin/mount_nfs/mount_nfs.8
@@ -0,0 +1,526 @@
+.\" Copyright (c) 1992, 1993, 1994, 1995
+.\" 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.
+.\" 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.
+.\"
+.\" @(#)mount_nfs.8 8.3 (Berkeley) 3/29/95
+.\" $FreeBSD$
+.\"
+.Dd October 30, 2014
+.Dt MOUNT_NFS 8
+.Os
+.Sh NAME
+.Nm mount_nfs
+.Nd mount NFS file systems
+.Sh SYNOPSIS
+.Nm
+.Op Fl 23bcdiLlNPsTU
+.Op Fl a Ar maxreadahead
+.Op Fl D Ar deadthresh
+.Op Fl g Ar maxgroups
+.Op Fl I Ar readdirsize
+.Op Fl o Ar options
+.Op Fl R Ar retrycnt
+.Op Fl r Ar readsize
+.Op Fl t Ar timeout
+.Op Fl w Ar writesize
+.Op Fl x Ar retrans
+.Ar rhost : Ns Ar path node
+.Sh DESCRIPTION
+The
+.Nm
+utility calls the
+.Xr nmount 2
+system call to prepare and graft a remote NFS file system
+.Pq Ar rhost : Ns Ar path
+on to the file system tree at the point
+.Ar node .
+This command is normally executed by
+.Xr mount 8 .
+It implements the mount protocol as described in RFC 1094, Appendix A and
+.%T "NFS: Network File System Version 3 Protocol Specification" ,
+Appendix I.
+.Pp
+By default,
+.Nm
+keeps retrying until the mount succeeds.
+This behaviour is intended for file systems listed in
+.Xr fstab 5
+that are critical to the boot process.
+For non-critical file systems, the
+.Cm bg
+and
+.Cm retrycnt
+options provide mechanisms to prevent the boot process from hanging
+if the server is unavailable.
+.Pp
+If the server becomes unresponsive while an NFS file system is
+mounted, any new or outstanding file operations on that file system
+will hang uninterruptibly until the server comes back.
+To modify this default behaviour, see the
+.Cm intr
+and
+.Cm soft
+options.
+.Pp
+The options are:
+.Bl -tag -width indent
+.It Fl o
+Options are specified with a
+.Fl o
+flag followed by a comma separated string of options.
+See the
+.Xr mount 8
+man page for possible options and their meanings.
+The following NFS specific options are also available:
+.Bl -tag -width indent
+.It Cm acregmin Ns = Ns Aq Ar seconds
+.It Cm acregmax Ns = Ns Aq Ar seconds
+.It Cm acdirmin Ns = Ns Aq Ar seconds
+.It Cm acdirmax Ns = Ns Aq Ar seconds
+When attributes of files are cached, a timeout calculated to determine
+whether a given cache entry has expired.
+These four values determine the upper and lower bounds of the timeouts for
+.Dq directory
+attributes and
+.Dq regular
+(ie: everything else).
+The default values are 3 -> 60 seconds
+for regular files, and 30 -> 60 seconds for directories.
+The algorithm to calculate the timeout is based on the age of the file.
+The older the file,
+the longer the cache is considered valid, subject to the limits above.
+.It Cm actimeo Ns = Ns Aq Ar seconds
+Set four cache timeouts above to specified value.
+.It Cm allgssname
+This option can be used along with
+.Fl o Cm gssname
+to specify that all operations should use the host-based initiator
+credential.
+This may be used for clients that run system daemons that need to
+access files on the NFSv4 mounted volume.
+.It Cm bg
+If an initial attempt to contact the server fails, fork off a child to keep
+trying the mount in the background.
+Useful for
+.Xr fstab 5 ,
+where the file system mount is not critical to multiuser operation.
+.It Cm deadthresh Ns = Ns Aq Ar value
+Set the
+.Dq "dead server threshold"
+to the specified number of round trip timeout intervals before a
+.Dq "server not responding"
+message is displayed.
+.It Cm dumbtimer
+Turn off the dynamic retransmit timeout estimator.
+This may be useful for UDP mounts that exhibit high retry rates,
+since it is possible that the dynamically estimated timeout interval is too
+short.
+.It Cm fg
+Same as not specifying
+.Cm bg .
+.It Cm gssname Ns = Ns Aq Ar service-principal-name
+This option can be used with the KerberosV security flavors for NFSv4 mounts
+to specify the
+.Dq "service-principal-name"
+of a host-based entry in the default
+keytab file that is used for system operations.
+It allows the mount to be performed by
+.Dq "root"
+and avoids problems with
+cached credentials for the system operations expiring.
+The
+.Dq "service-prinicpal-name"
+should be specified without instance or domain and is typically
+.Dq "host" ,
+.Dq "nfs"
+or
+.Dq "root" .
+.It Cm hard
+Same as not specifying
+.Cm soft .
+.It Cm intr
+Make the mount interruptible, which implies that file system calls that
+are delayed due to an unresponsive server will fail with EINTR when a
+termination signal is posted for the process.
+.It Cm maxgroups Ns = Ns Aq Ar value
+Set the maximum size of the group list for the credentials to the
+specified value.
+This should be used for mounts on old servers that cannot handle a
+group list size of 16, as specified in RFC 1057.
+Try 8, if users in a lot of groups cannot get response from the mount
+point.
+.It Cm mntudp
+Force the mount protocol to use UDP transport, even for TCP NFS mounts.
+(Necessary for some old
+.Bx
+servers.)
+.It Cm nametimeo Ns = Ns Aq Ar value
+Override the default of NFS_DEFAULT_NAMETIMEO for the timeout (in seconds)
+for positive name cache entries.
+If this is set to 0 it disables positive name caching for the mount point.
+.It Cm negnametimeo Ns = Ns Aq Ar value
+Override the default of NFS_DEFAULT_NEGNAMETIMEO for the timeout (in seconds)
+for negative name cache entries. If this is set to 0 it disables negative
+name caching for the mount point.
+.It Cm nfsv2
+Use the NFS Version 2 protocol (the default is to try version 3 first
+then version 2).
+Note that NFS version 2 has a file size limit of 2 gigabytes.
+.It Cm nfsv3
+Use the NFS Version 3 protocol.
+.It Cm nfsv4
+Use the NFS Version 4 protocol.
+This option will force the mount to use
+TCP transport.
+.It Cm minorversion Ns = Ns Aq Ar value
+Override the default of 0 for the minor version of the NFS Version 4 protocol.
+The only minor version currently supported is 1.
+This option is only meaningful when used with the
+.Cm nfsv4
+option.
+.It Cm pnfs
+Enable support for parallel NFS (pNFS) for minor version 1 of the
+NFS Version 4 protocol.
+This option is only meaningful when used with the
+.Cm minorversion
+option.
+.It Cm noac
+Disable attribute caching.
+.It Cm noconn
+For UDP mount points, do not do a
+.Xr connect 2 .
+This must be used if the server does not reply to requests from the standard
+NFS port number 2049 or replies to requests using a different IP address
+(which can occur if the server is multi-homed).
+Setting the
+.Va vfs.nfs.nfs_ip_paranoia
+sysctl to 0 will make this option the default.
+.It Cm nocto
+Normally, NFS clients maintain the close-to-open cache coherency.
+This works by flushing at close time and checking at open time.
+Checking at open time is implemented by getting attributes from
+the server and purging the data cache if they do not match
+attributes cached by the client.
+.Pp
+This option disables checking at open time.
+It may improve performance for read-only mounts,
+but should only be used if the data on the server changes rarely.
+Be sure to understand the consequences before enabling this option.
+.It Cm noinet4 , noinet6
+Disables
+.Dv AF_INET
+or
+.Dv AF_INET6
+connections.
+Useful for hosts that have
+both an A record and an AAAA record for the same name.
+.It Cm nolockd
+Do
+.Em not
+forward
+.Xr fcntl 2
+locks over the wire.
+All locks will be local and not seen by the server
+and likewise not seen by other NFS clients.
+This removes the need to run the
+.Xr rpcbind 8
+service and the
+.Xr rpc.statd 8
+and
+.Xr rpc.lockd 8
+servers on the client.
+Note that this option will only be honored when performing the
+initial mount, it will be silently ignored if used while updating
+the mount options.
+.It Cm noncontigwr
+This mount option allows the NFS client to
+combine non-contiguous byte ranges being written
+such that the dirty byte range becomes a superset of the bytes
+that are dirty.
+This reduces the number of writes significantly for software
+builds.
+The merging of byte ranges isn't done if the file has been file
+locked, since most applications modifying a file from multiple
+clients will use file locking.
+As such, this option could result in a corrupted file for the
+rare case of an application modifying the file from multiple
+clients concurrently without using file locking.
+.It Cm principal
+For the RPCSEC_GSS security flavors, such as krb5, krb5i and krb5p,
+this option sets the name of the host based principal name expected
+by the server. This option overrides the default, which will be
+``nfs@<server-fqdn>'' and should normally be sufficient.
+.It Cm noresvport
+Do
+.Em not
+use a reserved socket port number (see below).
+.It Cm port Ns = Ns Aq Ar port_number
+Use specified port number for NFS requests.
+The default is to query the portmapper for the NFS port.
+.It Cm proto Ns = Ns Aq Ar protocol
+Specify transport protocol version to use.
+Currently, they are:
+.Bd -literal
+udp - Use UDP over IPv4
+tcp - Use TCP over IPv4
+udp6 - Use UDP over IPv6
+tcp6 - Use TCP over IPv6
+.Ed
+.It Cm rdirplus
+Used with NFSV3 to specify that the \fBReaddirPlus\fR RPC should
+be used.
+For NFSV4, setting this option has a similar effect, in that it will make
+the Readdir Operation get more attributes.
+This option reduces RPC traffic for cases such as
+.Dq "ls -l" ,
+but tends to flood the attribute and name caches with prefetched entries.
+Try this option and see whether performance improves or degrades.
+Probably
+most useful for client to server network interconnects with a large bandwidth
+times delay product.
+.It Cm readahead Ns = Ns Aq Ar value
+Set the read-ahead count to the specified value.
+This may be in the range of 0 - 4, and determines how many blocks
+will be read ahead when a large file is being read sequentially.
+Trying a value greater than 1 for this is suggested for
+mounts with a large bandwidth * delay product.
+.It Cm readdirsize Ns = Ns Aq Ar value
+Set the readdir read size to the specified value.
+The value should normally
+be a multiple of
+.Dv DIRBLKSIZ
+that is <= the read size for the mount.
+.It Cm resvport
+Use a reserved socket port number.
+This flag is obsolete, and only retained for compatibility reasons.
+Reserved port numbers are used by default now.
+(For the rare case where the client has a trusted root account
+but untrustworthy users and the network cables are in secure areas this does
+help, but for normal desktop clients this does not apply.)
+.It Cm retrans Ns = Ns Aq Ar value
+Set the retransmit timeout count for soft mounts to the specified value.
+.It Cm retrycnt Ns = Ns Aq Ar count
+Set the mount retry count to the specified value.
+The default is a retry count of zero, which means to keep retrying
+forever.
+There is a 60 second delay between each attempt.
+.It Cm rsize Ns = Ns Aq Ar value
+Set the read data size to the specified value.
+It should normally be a power of 2 greater than or equal to 1024.
+This should be used for UDP mounts when the
+.Dq "fragments dropped due to timeout"
+value is getting large while actively using a mount point.
+(Use
+.Xr netstat 1
+with the
+.Fl s
+option to see what the
+.Dq "fragments dropped due to timeout"
+value is.)
+.It Cm sec Ns = Ns Aq Ar flavor
+This option specifies what security flavor should be used for the mount.
+Currently, they are:
+.Bd -literal
+krb5 - Use KerberosV authentication
+krb5i - Use KerberosV authentication and
+ apply integrity checksums to RPCs
+krb5p - Use KerberosV authentication and
+ encrypt the RPC data
+sys - The default AUTH_SYS, which uses a
+ uid + gid list authenticator
+.Ed
+.It Cm soft
+A soft mount, which implies that file system calls will fail
+after
+.Ar retrycnt
+round trip timeout intervals.
+.It Cm tcp
+Use TCP transport.
+This is the default option, as it provides for increased reliability on both
+LAN and WAN configurations compared to UDP.
+Some old NFS servers do not support this method; UDP mounts may be required
+for interoperability.
+.It Cm timeout Ns = Ns Aq Ar value
+Set the initial retransmit timeout to the specified value,
+expressed in tenths of a second.
+May be useful for fine tuning UDP mounts over internetworks
+with high packet loss rates or an overloaded server.
+Try increasing the interval if
+.Xr nfsstat 1
+shows high retransmit rates while the file system is active or reducing the
+value if there is a low retransmit rate but long response delay observed.
+(Normally, the
+.Cm dumbtimer
+option should be specified when using this option to manually
+tune the timeout
+interval.)
+.It Cm timeo Ns = Ns Aq Ar value
+Alias for
+.Cm timeout .
+.It Cm udp
+Use UDP transport.
+.It Cm vers Ns = Ns Aq Ar vers_number
+Use the specified version number for NFS requests.
+See the
+.Cm nfsv2 ,
+.Cm nfsv3 ,
+and
+.Cm nfsv4
+options for details.
+.It Cm wcommitsize Ns = Ns Aq Ar value
+Set the maximum pending write commit size to the specified value.
+This determines the maximum amount of pending write data that the NFS
+client is willing to cache for each file.
+.It Cm wsize Ns = Ns Aq Ar value
+Set the write data size to the specified value.
+Ditto the comments w.r.t.\& the
+.Cm rsize
+option, but using the
+.Dq "fragments dropped due to timeout"
+value on the server instead of the client.
+Note that both the
+.Cm rsize
+and
+.Cm wsize
+options should only be used as a last ditch effort at improving performance
+when mounting servers that do not support TCP mounts.
+.El
+.El
+.Sh COMPATIBILITY
+The following command line flags are equivalent to
+.Fl o
+named options and are supported for compatibility with older
+installations.
+.Bl -tag -width indent
+.It Fl 2
+Same as
+.Fl o Cm nfsv2
+.It Fl 3
+Same as
+.Fl o Cm nfsv3
+.It Fl D
+Same as
+.Fl o Cm deadthresh
+.It Fl I
+Same as
+.Fl o Cm readdirsize Ns = Ns Aq Ar value
+.It Fl L
+Same as
+.Fl o Cm nolockd
+.It Fl N
+Same as
+.Fl o Cm noresvport
+.It Fl P
+Use a reserved socket port number.
+This flag is obsolete, and only retained for compatibility reasons.
+(For the rare case where the client has a trusted root account
+but untrustworthy users and the network cables are in secure areas this does
+help, but for normal desktop clients this does not apply.)
+.It Fl R
+Same as
+.Fl o Cm retrycnt Ns = Ns Aq Ar value
+.It Fl T
+Same as
+.Fl o Cm tcp
+.It Fl U
+Same as
+.Fl o Cm mntudp
+.It Fl a
+Same as
+.Fl o Cm readahead Ns = Ns Aq Ar value
+.It Fl b
+Same as
+.Fl o Cm bg
+.It Fl c
+Same as
+.Fl o Cm noconn
+.It Fl d
+Same as
+.Fl o Cm dumbtimer
+.It Fl g
+Same as
+.Fl o Cm maxgroups
+.It Fl i
+Same as
+.Fl o Cm intr
+.It Fl l
+Same as
+.Fl o Cm rdirplus
+.It Fl r
+Same as
+.Fl o Cm rsize Ns = Ns Aq Ar value
+.It Fl s
+Same as
+.Fl o Cm soft
+.It Fl t
+Same as
+.Fl o Cm retransmit Ns = Ns Aq Ar value
+.It Fl w
+Same as
+.Fl o Cm wsize Ns = Ns Aq Ar value
+.It Fl x
+Same as
+.Fl o Cm retrans Ns = Ns Aq Ar value
+.El
+.Pp
+The following
+.Fl o
+named options are equivalent to other
+.Fl o
+named options and are supported for compatibility with other
+operating systems (e.g., Linux, Solaris, and OSX) to ease usage of
+.Xr autofs 5
+support.
+.Bl -tag -width indent
+.It Fl o Cm vers Ns = Ns 2
+Same as
+.Fl o Cm nfsv2
+.It Fl o Cm vers Ns = Ns 3
+Same as
+.Fl o Cm nfsv3
+.It Fl o Cm vers Ns = Ns 4
+Same as
+.Fl o Cm nfsv4
+.El
+.Sh SEE ALSO
+.Xr nmount 2 ,
+.Xr unmount 2 ,
+.Xr nfsv4 4 ,
+.Xr fstab 5 ,
+.Xr gssd 8 ,
+.Xr mount 8 ,
+.Xr nfsd 8 ,
+.Xr nfsiod 8 ,
+.Xr showmount 8
+.Sh BUGS
+Since nfsv4 performs open/lock operations that have their ordering strictly
+enforced by the server, the options
+.Cm intr
+and
+.Cm soft
+cannot be safely used.
+.Cm hard
+nfsv4 mounts are strongly recommended.
diff --git a/sbin/mount_nfs/mount_nfs.c b/sbin/mount_nfs/mount_nfs.c
new file mode 100644
index 0000000..7f53581
--- /dev/null
+++ b/sbin/mount_nfs/mount_nfs.c
@@ -0,0 +1,1042 @@
+/*
+ * Copyright (c) 1992, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Rick Macklem at The University of Guelph.
+ *
+ * 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.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if 0
+#ifndef lint
+static const char copyright[] =
+"@(#) Copyright (c) 1992, 1993, 1994\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static char sccsid[] = "@(#)mount_nfs.c 8.11 (Berkeley) 5/4/95";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/linker.h>
+#include <sys/module.h>
+#include <sys/mount.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/syslog.h>
+#include <sys/uio.h>
+
+#include <rpc/rpc.h>
+#include <rpc/pmap_clnt.h>
+#include <rpc/pmap_prot.h>
+#include <rpcsvc/nfs_prot.h>
+#include <rpcsvc/mount.h>
+
+#include <nfsclient/nfs.h>
+
+#include <arpa/inet.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include "mntopts.h"
+#include "mounttab.h"
+
+/* Table for af,sotype -> netid conversions. */
+static struct nc_protos {
+ const char *netid;
+ int af;
+ int sotype;
+} nc_protos[] = {
+ {"udp", AF_INET, SOCK_DGRAM},
+ {"tcp", AF_INET, SOCK_STREAM},
+ {"udp6", AF_INET6, SOCK_DGRAM},
+ {"tcp6", AF_INET6, SOCK_STREAM},
+ {NULL, 0, 0}
+};
+
+struct nfhret {
+ u_long stat;
+ long vers;
+ long auth;
+ long fhsize;
+ u_char nfh[NFS3_FHSIZE];
+};
+#define BGRND 1
+#define ISBGRND 2
+#define OF_NOINET4 4
+#define OF_NOINET6 8
+static int retrycnt = -1;
+static int opflags = 0;
+static int nfsproto = IPPROTO_TCP;
+static int mnttcp_ok = 1;
+static int noconn = 0;
+/* The 'portspec' is the server nfs port; NULL means look up via rpcbind. */
+static const char *portspec = NULL;
+static struct sockaddr *addr;
+static int addrlen = 0;
+static u_char *fh = NULL;
+static int fhsize = 0;
+static int secflavor = -1;
+static int got_principal = 0;
+
+static enum mountmode {
+ ANY,
+ V2,
+ V3,
+ V4
+} mountmode = ANY;
+
+/* Return codes for nfs_tryproto. */
+enum tryret {
+ TRYRET_SUCCESS,
+ TRYRET_TIMEOUT, /* No response received. */
+ TRYRET_REMOTEERR, /* Error received from remote server. */
+ TRYRET_LOCALERR /* Local failure. */
+};
+
+static int sec_name_to_num(const char *sec);
+static const char *sec_num_to_name(int num);
+static int getnfsargs(char *, struct iovec **iov, int *iovlen);
+/* void set_rpc_maxgrouplist(int); */
+static struct netconfig *getnetconf_cached(const char *netid);
+static const char *netidbytype(int af, int sotype);
+static void usage(void) __dead2;
+static int xdr_dir(XDR *, char *);
+static int xdr_fh(XDR *, struct nfhret *);
+static enum tryret nfs_tryproto(struct addrinfo *ai, char *hostp, char *spec,
+ char **errstr, struct iovec **iov, int *iovlen);
+static enum tryret returncode(enum clnt_stat stat, struct rpc_err *rpcerr);
+
+int
+main(int argc, char *argv[])
+{
+ int c;
+ struct iovec *iov;
+ int num, iovlen;
+ char *mntname, *p, *spec, *tmp;
+ char mntpath[MAXPATHLEN], errmsg[255];
+ char hostname[MAXHOSTNAMELEN + 1], gssn[MAXHOSTNAMELEN + 50];
+ const char *fstype, *gssname;
+
+ iov = NULL;
+ iovlen = 0;
+ memset(errmsg, 0, sizeof(errmsg));
+ gssname = NULL;
+
+ fstype = strrchr(argv[0], '_');
+ if (fstype == NULL)
+ errx(EX_USAGE, "argv[0] must end in _fstype");
+
+ ++fstype;
+
+ while ((c = getopt(argc, argv,
+ "23a:bcdD:g:I:iLlNo:PR:r:sTt:w:x:U")) != -1)
+ switch (c) {
+ case '2':
+ mountmode = V2;
+ break;
+ case '3':
+ mountmode = V3;
+ break;
+ case 'a':
+ printf("-a deprecated, use -o readahead=<value>\n");
+ build_iovec(&iov, &iovlen, "readahead", optarg, (size_t)-1);
+ break;
+ case 'b':
+ opflags |= BGRND;
+ break;
+ case 'c':
+ printf("-c deprecated, use -o noconn\n");
+ build_iovec(&iov, &iovlen, "noconn", NULL, 0);
+ noconn = 1;
+ break;
+ case 'D':
+ printf("-D deprecated, use -o deadthresh=<value>\n");
+ build_iovec(&iov, &iovlen, "deadthresh", optarg, (size_t)-1);
+ break;
+ case 'd':
+ printf("-d deprecated, use -o dumbtimer");
+ build_iovec(&iov, &iovlen, "dumbtimer", NULL, 0);
+ break;
+ case 'g':
+ printf("-g deprecated, use -o maxgroups");
+ num = strtol(optarg, &p, 10);
+ if (*p || num <= 0)
+ errx(1, "illegal -g value -- %s", optarg);
+ //set_rpc_maxgrouplist(num);
+ build_iovec(&iov, &iovlen, "maxgroups", optarg, (size_t)-1);
+ break;
+ case 'I':
+ printf("-I deprecated, use -o readdirsize=<value>\n");
+ build_iovec(&iov, &iovlen, "readdirsize", optarg, (size_t)-1);
+ break;
+ case 'i':
+ printf("-i deprecated, use -o intr\n");
+ build_iovec(&iov, &iovlen, "intr", NULL, 0);
+ break;
+ case 'L':
+ printf("-L deprecated, use -o nolockd\n");
+ build_iovec(&iov, &iovlen, "nolockd", NULL, 0);
+ break;
+ case 'l':
+ printf("-l deprecated, -o rdirplus\n");
+ build_iovec(&iov, &iovlen, "rdirplus", NULL, 0);
+ break;
+ case 'N':
+ printf("-N deprecated, do not specify -o resvport\n");
+ break;
+ case 'o': {
+ int pass_flag_to_nmount;
+ char *opt = optarg;
+ while (opt) {
+ char *pval = NULL;
+ char *pnextopt = NULL;
+ const char *val = "";
+ pass_flag_to_nmount = 1;
+ pnextopt = strchr(opt, ',');
+ if (pnextopt != NULL) {
+ *pnextopt = '\0';
+ pnextopt++;
+ }
+ pval = strchr(opt, '=');
+ if (pval != NULL) {
+ *pval = '\0';
+ val = pval + 1;
+ }
+ if (strcmp(opt, "bg") == 0) {
+ opflags |= BGRND;
+ pass_flag_to_nmount=0;
+ } else if (strcmp(opt, "fg") == 0) {
+ /* same as not specifying -o bg */
+ pass_flag_to_nmount=0;
+ } else if (strcmp(opt, "gssname") == 0) {
+ pass_flag_to_nmount = 0;
+ gssname = val;
+ } else if (strcmp(opt, "mntudp") == 0) {
+ mnttcp_ok = 0;
+ nfsproto = IPPROTO_UDP;
+ } else if (strcmp(opt, "udp") == 0) {
+ nfsproto = IPPROTO_UDP;
+ } else if (strcmp(opt, "tcp") == 0) {
+ nfsproto = IPPROTO_TCP;
+ } else if (strcmp(opt, "noinet4") == 0) {
+ pass_flag_to_nmount=0;
+ opflags |= OF_NOINET4;
+ } else if (strcmp(opt, "noinet6") == 0) {
+ pass_flag_to_nmount=0;
+ opflags |= OF_NOINET6;
+ } else if (strcmp(opt, "noconn") == 0) {
+ noconn = 1;
+ } else if (strcmp(opt, "nfsv2") == 0) {
+ pass_flag_to_nmount=0;
+ mountmode = V2;
+ } else if (strcmp(opt, "nfsv3") == 0) {
+ mountmode = V3;
+ } else if (strcmp(opt, "nfsv4") == 0) {
+ pass_flag_to_nmount=0;
+ mountmode = V4;
+ fstype = "nfs";
+ nfsproto = IPPROTO_TCP;
+ if (portspec == NULL)
+ portspec = "2049";
+ } else if (strcmp(opt, "port") == 0) {
+ pass_flag_to_nmount=0;
+ asprintf(&tmp, "%d", atoi(val));
+ if (tmp == NULL)
+ err(1, "asprintf");
+ portspec = tmp;
+ } else if (strcmp(opt, "principal") == 0) {
+ got_principal = 1;
+ } else if (strcmp(opt, "proto") == 0) {
+ pass_flag_to_nmount=0;
+ if (strcmp(val, "tcp") == 0) {
+ nfsproto = IPPROTO_TCP;
+ opflags |= OF_NOINET6;
+ build_iovec(&iov, &iovlen,
+ "tcp", NULL, 0);
+ } else if (strcmp(val, "udp") == 0) {
+ mnttcp_ok = 0;
+ nfsproto = IPPROTO_UDP;
+ opflags |= OF_NOINET6;
+ build_iovec(&iov, &iovlen,
+ "udp", NULL, 0);
+ } else if (strcmp(val, "tcp6") == 0) {
+ nfsproto = IPPROTO_TCP;
+ opflags |= OF_NOINET4;
+ build_iovec(&iov, &iovlen,
+ "tcp", NULL, 0);
+ } else if (strcmp(val, "udp6") == 0) {
+ mnttcp_ok = 0;
+ nfsproto = IPPROTO_UDP;
+ opflags |= OF_NOINET4;
+ build_iovec(&iov, &iovlen,
+ "udp", NULL, 0);
+ } else {
+ errx(1,
+ "illegal proto value -- %s",
+ val);
+ }
+ } else if (strcmp(opt, "sec") == 0) {
+ /*
+ * Don't add this option to
+ * the iovec yet - we will
+ * negotiate which sec flavor
+ * to use with the remote
+ * mountd.
+ */
+ pass_flag_to_nmount=0;
+ secflavor = sec_name_to_num(val);
+ if (secflavor < 0) {
+ errx(1,
+ "illegal sec value -- %s",
+ val);
+ }
+ } else if (strcmp(opt, "retrycnt") == 0) {
+ pass_flag_to_nmount=0;
+ num = strtol(val, &p, 10);
+ if (*p || num < 0)
+ errx(1, "illegal retrycnt value -- %s", val);
+ retrycnt = num;
+ } else if (strcmp(opt, "maxgroups") == 0) {
+ num = strtol(val, &p, 10);
+ if (*p || num <= 0)
+ errx(1, "illegal maxgroups value -- %s", val);
+ //set_rpc_maxgrouplist(num);
+ } else if (strcmp(opt, "vers") == 0) {
+ num = strtol(val, &p, 10);
+ if (*p || num <= 0)
+ errx(1, "illegal vers value -- "
+ "%s", val);
+ switch (num) {
+ case 2:
+ mountmode = V2;
+ break;
+ case 3:
+ mountmode = V3;
+ build_iovec(&iov, &iovlen,
+ "nfsv3", NULL, 0);
+ break;
+ case 4:
+ mountmode = V4;
+ fstype = "nfs";
+ nfsproto = IPPROTO_TCP;
+ if (portspec == NULL)
+ portspec = "2049";
+ break;
+ default:
+ errx(1, "illegal nfs version "
+ "value -- %s", val);
+ }
+ pass_flag_to_nmount=0;
+ }
+ if (pass_flag_to_nmount) {
+ build_iovec(&iov, &iovlen, opt,
+ __DECONST(void *, val),
+ strlen(val) + 1);
+ }
+ opt = pnextopt;
+ }
+ }
+ break;
+ case 'P':
+ /* obsolete for -o noresvport now default */
+ printf("-P deprecated, use -o noresvport\n");
+ build_iovec(&iov, &iovlen, "noresvport", NULL, 0);
+ break;
+ case 'R':
+ printf("-R deprecated, use -o retrycnt=<retrycnt>\n");
+ num = strtol(optarg, &p, 10);
+ if (*p || num < 0)
+ errx(1, "illegal -R value -- %s", optarg);
+ retrycnt = num;
+ break;
+ case 'r':
+ printf("-r deprecated, use -o rsize=<rsize>\n");
+ build_iovec(&iov, &iovlen, "rsize", optarg, (size_t)-1);
+ break;
+ case 's':
+ printf("-s deprecated, use -o soft\n");
+ build_iovec(&iov, &iovlen, "soft", NULL, 0);
+ break;
+ case 'T':
+ nfsproto = IPPROTO_TCP;
+ printf("-T deprecated, use -o tcp\n");
+ break;
+ case 't':
+ printf("-t deprecated, use -o timeout=<value>\n");
+ build_iovec(&iov, &iovlen, "timeout", optarg, (size_t)-1);
+ break;
+ case 'w':
+ printf("-w deprecated, use -o wsize=<value>\n");
+ build_iovec(&iov, &iovlen, "wsize", optarg, (size_t)-1);
+ break;
+ case 'x':
+ printf("-x deprecated, use -o retrans=<value>\n");
+ build_iovec(&iov, &iovlen, "retrans", optarg, (size_t)-1);
+ break;
+ case 'U':
+ printf("-U deprecated, use -o mntudp\n");
+ mnttcp_ok = 0;
+ nfsproto = IPPROTO_UDP;
+ build_iovec(&iov, &iovlen, "mntudp", NULL, 0);
+ break;
+ default:
+ usage();
+ break;
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 2) {
+ usage();
+ /* NOTREACHED */
+ }
+
+ spec = *argv++;
+ mntname = *argv;
+
+ if (retrycnt == -1)
+ /* The default is to keep retrying forever. */
+ retrycnt = 0;
+
+ /*
+ * If the fstye is "oldnfs", run the old NFS client unless the
+ * "nfsv4" option was specified.
+ */
+ if (strcmp(fstype, "nfs") == 0) {
+ if (modfind("nfscl") < 0) {
+ /* Not present in kernel, try loading it */
+ if (kldload("nfscl") < 0 ||
+ modfind("nfscl") < 0)
+ errx(1, "nfscl is not available");
+ }
+ }
+
+ /*
+ * Add the fqdn to the gssname, as required.
+ */
+ if (gssname != NULL) {
+ if (strchr(gssname, '@') == NULL &&
+ gethostname(hostname, MAXHOSTNAMELEN) == 0) {
+ snprintf(gssn, sizeof (gssn), "%s@%s", gssname,
+ hostname);
+ gssname = gssn;
+ }
+ build_iovec(&iov, &iovlen, "gssname",
+ __DECONST(void *, gssname), strlen(gssname) + 1);
+ }
+
+ if (!getnfsargs(spec, &iov, &iovlen))
+ exit(1);
+
+ /* resolve the mountpoint with realpath(3) */
+ if (checkpath(mntname, mntpath) != 0)
+ err(1, "%s", mntpath);
+
+ build_iovec(&iov, &iovlen, "fstype",
+ __DECONST(void *, fstype), (size_t)-1);
+ build_iovec(&iov, &iovlen, "fspath", mntpath, (size_t)-1);
+ build_iovec(&iov, &iovlen, "errmsg", errmsg, sizeof(errmsg));
+
+ if (nmount(iov, iovlen, 0))
+ err(1, "%s, %s", mntpath, errmsg);
+
+ exit(0);
+}
+
+static int
+sec_name_to_num(const char *sec)
+{
+ if (!strcmp(sec, "krb5"))
+ return (RPCSEC_GSS_KRB5);
+ if (!strcmp(sec, "krb5i"))
+ return (RPCSEC_GSS_KRB5I);
+ if (!strcmp(sec, "krb5p"))
+ return (RPCSEC_GSS_KRB5P);
+ if (!strcmp(sec, "sys"))
+ return (AUTH_SYS);
+ return (-1);
+}
+
+static const char *
+sec_num_to_name(int flavor)
+{
+ switch (flavor) {
+ case RPCSEC_GSS_KRB5:
+ return ("krb5");
+ case RPCSEC_GSS_KRB5I:
+ return ("krb5i");
+ case RPCSEC_GSS_KRB5P:
+ return ("krb5p");
+ case AUTH_SYS:
+ return ("sys");
+ }
+ return (NULL);
+}
+
+static int
+getnfsargs(char *spec, struct iovec **iov, int *iovlen)
+{
+ struct addrinfo hints, *ai_nfs, *ai;
+ enum tryret ret;
+ int ecode, speclen, remoteerr, offset, have_bracket = 0;
+ char *hostp, *delimp, *errstr;
+ size_t len;
+ static char nam[MNAMELEN + 1], pname[MAXHOSTNAMELEN + 5];
+
+ if (*spec == '[' && (delimp = strchr(spec + 1, ']')) != NULL &&
+ *(delimp + 1) == ':') {
+ hostp = spec + 1;
+ spec = delimp + 2;
+ have_bracket = 1;
+ } else if ((delimp = strrchr(spec, ':')) != NULL) {
+ hostp = spec;
+ spec = delimp + 1;
+ } else if ((delimp = strrchr(spec, '@')) != NULL) {
+ warnx("path@server syntax is deprecated, use server:path");
+ hostp = delimp + 1;
+ } else {
+ warnx("no <host>:<dirpath> nfs-name");
+ return (0);
+ }
+ *delimp = '\0';
+
+ /*
+ * If there has been a trailing slash at mounttime it seems
+ * that some mountd implementations fail to remove the mount
+ * entries from their mountlist while unmounting.
+ */
+ for (speclen = strlen(spec);
+ speclen > 1 && spec[speclen - 1] == '/';
+ speclen--)
+ spec[speclen - 1] = '\0';
+ if (strlen(hostp) + strlen(spec) + 1 > MNAMELEN) {
+ warnx("%s:%s: %s", hostp, spec, strerror(ENAMETOOLONG));
+ return (0);
+ }
+ /* Make both '@' and ':' notations equal */
+ if (*hostp != '\0') {
+ len = strlen(hostp);
+ offset = 0;
+ if (have_bracket)
+ nam[offset++] = '[';
+ memmove(nam + offset, hostp, len);
+ if (have_bracket)
+ nam[len + offset++] = ']';
+ nam[len + offset++] = ':';
+ memmove(nam + len + offset, spec, speclen);
+ nam[len + speclen + offset] = '\0';
+ }
+
+ /*
+ * Handle an internet host address.
+ */
+ memset(&hints, 0, sizeof hints);
+ hints.ai_flags = AI_NUMERICHOST;
+ if (nfsproto == IPPROTO_TCP)
+ hints.ai_socktype = SOCK_STREAM;
+ else if (nfsproto == IPPROTO_UDP)
+ hints.ai_socktype = SOCK_DGRAM;
+
+ if (getaddrinfo(hostp, portspec, &hints, &ai_nfs) != 0) {
+ hints.ai_flags = AI_CANONNAME;
+ if ((ecode = getaddrinfo(hostp, portspec, &hints, &ai_nfs))
+ != 0) {
+ if (portspec == NULL)
+ errx(1, "%s: %s", hostp, gai_strerror(ecode));
+ else
+ errx(1, "%s:%s: %s", hostp, portspec,
+ gai_strerror(ecode));
+ return (0);
+ }
+
+ /*
+ * For a Kerberized nfs mount where the "principal"
+ * argument has not been set, add it here.
+ */
+ if (got_principal == 0 && secflavor >= 0 &&
+ secflavor != AUTH_SYS && ai_nfs->ai_canonname != NULL) {
+ snprintf(pname, sizeof (pname), "nfs@%s",
+ ai_nfs->ai_canonname);
+ build_iovec(iov, iovlen, "principal", pname,
+ strlen(pname) + 1);
+ }
+ }
+
+ ret = TRYRET_LOCALERR;
+ for (;;) {
+ /*
+ * Try each entry returned by getaddrinfo(). Note the
+ * occurrence of remote errors by setting `remoteerr'.
+ */
+ remoteerr = 0;
+ for (ai = ai_nfs; ai != NULL; ai = ai->ai_next) {
+ if ((ai->ai_family == AF_INET6) &&
+ (opflags & OF_NOINET6))
+ continue;
+ if ((ai->ai_family == AF_INET) &&
+ (opflags & OF_NOINET4))
+ continue;
+ ret = nfs_tryproto(ai, hostp, spec, &errstr, iov,
+ iovlen);
+ if (ret == TRYRET_SUCCESS)
+ break;
+ if (ret != TRYRET_LOCALERR)
+ remoteerr = 1;
+ if ((opflags & ISBGRND) == 0)
+ fprintf(stderr, "%s\n", errstr);
+ }
+ if (ret == TRYRET_SUCCESS)
+ break;
+
+ /* Exit if all errors were local. */
+ if (!remoteerr)
+ exit(1);
+
+ /*
+ * If retrycnt == 0, we are to keep retrying forever.
+ * Otherwise decrement it, and exit if it hits zero.
+ */
+ if (retrycnt != 0 && --retrycnt == 0)
+ exit(1);
+
+ if ((opflags & (BGRND | ISBGRND)) == BGRND) {
+ warnx("Cannot immediately mount %s:%s, backgrounding",
+ hostp, spec);
+ opflags |= ISBGRND;
+ if (daemon(0, 0) != 0)
+ err(1, "daemon");
+ }
+ sleep(60);
+ }
+ freeaddrinfo(ai_nfs);
+
+ build_iovec(iov, iovlen, "hostname", nam, (size_t)-1);
+ /* Add mounted file system to PATH_MOUNTTAB */
+ if (!add_mtab(hostp, spec))
+ warnx("can't update %s for %s:%s", PATH_MOUNTTAB, hostp, spec);
+ return (1);
+}
+
+/*
+ * Try to set up the NFS arguments according to the address
+ * family, protocol (and possibly port) specified in `ai'.
+ *
+ * Returns TRYRET_SUCCESS if successful, or:
+ * TRYRET_TIMEOUT The server did not respond.
+ * TRYRET_REMOTEERR The server reported an error.
+ * TRYRET_LOCALERR Local failure.
+ *
+ * In all error cases, *errstr will be set to a statically-allocated string
+ * describing the error.
+ */
+static enum tryret
+nfs_tryproto(struct addrinfo *ai, char *hostp, char *spec, char **errstr,
+ struct iovec **iov, int *iovlen)
+{
+ static char errbuf[256];
+ struct sockaddr_storage nfs_ss;
+ struct netbuf nfs_nb;
+ struct nfhret nfhret;
+ struct timeval try;
+ struct rpc_err rpcerr;
+ CLIENT *clp;
+ struct netconfig *nconf, *nconf_mnt;
+ const char *netid, *netid_mnt, *secname;
+ int doconnect, nfsvers, mntvers, sotype;
+ enum clnt_stat clntstat;
+ enum mountmode trymntmode;
+
+ sotype = 0;
+ trymntmode = mountmode;
+ errbuf[0] = '\0';
+ *errstr = errbuf;
+
+ if (nfsproto == IPPROTO_TCP)
+ sotype = SOCK_STREAM;
+ else if (nfsproto == IPPROTO_UDP)
+ sotype = SOCK_DGRAM;
+
+ if ((netid = netidbytype(ai->ai_family, sotype)) == NULL) {
+ snprintf(errbuf, sizeof errbuf,
+ "af %d sotype %d not supported", ai->ai_family, sotype);
+ return (TRYRET_LOCALERR);
+ }
+ if ((nconf = getnetconf_cached(netid)) == NULL) {
+ snprintf(errbuf, sizeof errbuf, "%s: %s", netid, nc_sperror());
+ return (TRYRET_LOCALERR);
+ }
+ /* The RPCPROG_MNT netid may be different. */
+ if (mnttcp_ok) {
+ netid_mnt = netid;
+ nconf_mnt = nconf;
+ } else {
+ if ((netid_mnt = netidbytype(ai->ai_family, SOCK_DGRAM))
+ == NULL) {
+ snprintf(errbuf, sizeof errbuf,
+ "af %d sotype SOCK_DGRAM not supported",
+ ai->ai_family);
+ return (TRYRET_LOCALERR);
+ }
+ if ((nconf_mnt = getnetconf_cached(netid_mnt)) == NULL) {
+ snprintf(errbuf, sizeof errbuf, "%s: %s", netid_mnt,
+ nc_sperror());
+ return (TRYRET_LOCALERR);
+ }
+ }
+
+tryagain:
+ if (trymntmode == V4) {
+ nfsvers = 4;
+ mntvers = 3; /* Workaround for GCC. */
+ } else if (trymntmode == V2) {
+ nfsvers = 2;
+ mntvers = 1;
+ } else {
+ nfsvers = 3;
+ mntvers = 3;
+ }
+
+ if (portspec != NULL) {
+ /* `ai' contains the complete nfsd sockaddr. */
+ nfs_nb.buf = ai->ai_addr;
+ nfs_nb.len = nfs_nb.maxlen = ai->ai_addrlen;
+ } else {
+ /* Ask the remote rpcbind. */
+ nfs_nb.buf = &nfs_ss;
+ nfs_nb.len = nfs_nb.maxlen = sizeof nfs_ss;
+
+ if (!rpcb_getaddr(NFS_PROGRAM, nfsvers, nconf, &nfs_nb,
+ hostp)) {
+ if (rpc_createerr.cf_stat == RPC_PROGVERSMISMATCH &&
+ trymntmode == ANY) {
+ trymntmode = V2;
+ goto tryagain;
+ }
+ snprintf(errbuf, sizeof errbuf, "[%s] %s:%s: %s",
+ netid, hostp, spec,
+ clnt_spcreateerror("RPCPROG_NFS"));
+ return (returncode(rpc_createerr.cf_stat,
+ &rpc_createerr.cf_error));
+ }
+ }
+
+ /* Check that the server (nfsd) responds on the port we have chosen. */
+ clp = clnt_tli_create(RPC_ANYFD, nconf, &nfs_nb, NFS_PROGRAM, nfsvers,
+ 0, 0);
+ if (clp == NULL) {
+ snprintf(errbuf, sizeof errbuf, "[%s] %s:%s: %s", netid,
+ hostp, spec, clnt_spcreateerror("nfsd: RPCPROG_NFS"));
+ return (returncode(rpc_createerr.cf_stat,
+ &rpc_createerr.cf_error));
+ }
+ if (sotype == SOCK_DGRAM && noconn == 0) {
+ /*
+ * Use connect(), to match what the kernel does. This
+ * catches cases where the server responds from the
+ * wrong source address.
+ */
+ doconnect = 1;
+ if (!clnt_control(clp, CLSET_CONNECT, (char *)&doconnect)) {
+ clnt_destroy(clp);
+ snprintf(errbuf, sizeof errbuf,
+ "[%s] %s:%s: CLSET_CONNECT failed", netid, hostp,
+ spec);
+ return (TRYRET_LOCALERR);
+ }
+ }
+
+ try.tv_sec = 10;
+ try.tv_usec = 0;
+ clntstat = clnt_call(clp, NFSPROC_NULL, (xdrproc_t)xdr_void, NULL,
+ (xdrproc_t)xdr_void, NULL, try);
+ if (clntstat != RPC_SUCCESS) {
+ if (clntstat == RPC_PROGVERSMISMATCH && trymntmode == ANY) {
+ clnt_destroy(clp);
+ trymntmode = V2;
+ goto tryagain;
+ }
+ clnt_geterr(clp, &rpcerr);
+ snprintf(errbuf, sizeof errbuf, "[%s] %s:%s: %s", netid,
+ hostp, spec, clnt_sperror(clp, "NFSPROC_NULL"));
+ clnt_destroy(clp);
+ return (returncode(clntstat, &rpcerr));
+ }
+ clnt_destroy(clp);
+
+ /*
+ * For NFSv4, there is no mount protocol.
+ */
+ if (trymntmode == V4) {
+ /*
+ * Store the server address in nfsargsp, making
+ * sure to copy any locally allocated structures.
+ */
+ addrlen = nfs_nb.len;
+ addr = malloc(addrlen);
+ if (addr == NULL)
+ err(1, "malloc");
+ bcopy(nfs_nb.buf, addr, addrlen);
+
+ build_iovec(iov, iovlen, "addr", addr, addrlen);
+ secname = sec_num_to_name(secflavor);
+ if (secname != NULL) {
+ build_iovec(iov, iovlen, "sec",
+ __DECONST(void *, secname), (size_t)-1);
+ }
+ build_iovec(iov, iovlen, "nfsv4", NULL, 0);
+ build_iovec(iov, iovlen, "dirpath", spec, (size_t)-1);
+
+ return (TRYRET_SUCCESS);
+ }
+
+ /* Send the MOUNTPROC_MNT RPC to get the root filehandle. */
+ try.tv_sec = 10;
+ try.tv_usec = 0;
+ clp = clnt_tp_create(hostp, MOUNTPROG, mntvers, nconf_mnt);
+ if (clp == NULL) {
+ snprintf(errbuf, sizeof errbuf, "[%s] %s:%s: %s", netid_mnt,
+ hostp, spec, clnt_spcreateerror("RPCMNT: clnt_create"));
+ return (returncode(rpc_createerr.cf_stat,
+ &rpc_createerr.cf_error));
+ }
+ clp->cl_auth = authsys_create_default();
+ nfhret.auth = secflavor;
+ nfhret.vers = mntvers;
+ clntstat = clnt_call(clp, MOUNTPROC_MNT, (xdrproc_t)xdr_dir, spec,
+ (xdrproc_t)xdr_fh, &nfhret,
+ try);
+ auth_destroy(clp->cl_auth);
+ if (clntstat != RPC_SUCCESS) {
+ if (clntstat == RPC_PROGVERSMISMATCH && trymntmode == ANY) {
+ clnt_destroy(clp);
+ trymntmode = V2;
+ goto tryagain;
+ }
+ clnt_geterr(clp, &rpcerr);
+ snprintf(errbuf, sizeof errbuf, "[%s] %s:%s: %s", netid_mnt,
+ hostp, spec, clnt_sperror(clp, "RPCPROG_MNT"));
+ clnt_destroy(clp);
+ return (returncode(clntstat, &rpcerr));
+ }
+ clnt_destroy(clp);
+
+ if (nfhret.stat != 0) {
+ snprintf(errbuf, sizeof errbuf, "[%s] %s:%s: %s", netid_mnt,
+ hostp, spec, strerror(nfhret.stat));
+ return (TRYRET_REMOTEERR);
+ }
+
+ /*
+ * Store the filehandle and server address in nfsargsp, making
+ * sure to copy any locally allocated structures.
+ */
+ addrlen = nfs_nb.len;
+ addr = malloc(addrlen);
+ fhsize = nfhret.fhsize;
+ fh = malloc(fhsize);
+ if (addr == NULL || fh == NULL)
+ err(1, "malloc");
+ bcopy(nfs_nb.buf, addr, addrlen);
+ bcopy(nfhret.nfh, fh, fhsize);
+
+ build_iovec(iov, iovlen, "addr", addr, addrlen);
+ build_iovec(iov, iovlen, "fh", fh, fhsize);
+ secname = sec_num_to_name(nfhret.auth);
+ if (secname) {
+ build_iovec(iov, iovlen, "sec",
+ __DECONST(void *, secname), (size_t)-1);
+ }
+ if (nfsvers == 3)
+ build_iovec(iov, iovlen, "nfsv3", NULL, 0);
+
+ return (TRYRET_SUCCESS);
+}
+
+/*
+ * Catagorise a RPC return status and error into an `enum tryret'
+ * return code.
+ */
+static enum tryret
+returncode(enum clnt_stat clntstat, struct rpc_err *rpcerr)
+{
+
+ switch (clntstat) {
+ case RPC_TIMEDOUT:
+ return (TRYRET_TIMEOUT);
+ case RPC_PMAPFAILURE:
+ case RPC_PROGNOTREGISTERED:
+ case RPC_PROGVERSMISMATCH:
+ /* XXX, these can be local or remote. */
+ case RPC_CANTSEND:
+ case RPC_CANTRECV:
+ return (TRYRET_REMOTEERR);
+ case RPC_SYSTEMERROR:
+ switch (rpcerr->re_errno) {
+ case ETIMEDOUT:
+ return (TRYRET_TIMEOUT);
+ case ENOMEM:
+ break;
+ default:
+ return (TRYRET_REMOTEERR);
+ }
+ /* FALLTHROUGH */
+ default:
+ break;
+ }
+ return (TRYRET_LOCALERR);
+}
+
+/*
+ * Look up a netid based on an address family and socket type.
+ * `af' is the address family, and `sotype' is SOCK_DGRAM or SOCK_STREAM.
+ *
+ * XXX there should be a library function for this.
+ */
+static const char *
+netidbytype(int af, int sotype)
+{
+ struct nc_protos *p;
+
+ for (p = nc_protos; p->netid != NULL; p++) {
+ if (af != p->af || sotype != p->sotype)
+ continue;
+ return (p->netid);
+ }
+ return (NULL);
+}
+
+/*
+ * Look up a netconfig entry based on a netid, and cache the result so
+ * that we don't need to remember to call freenetconfigent().
+ *
+ * Otherwise it behaves just like getnetconfigent(), so nc_*error()
+ * work on failure.
+ */
+static struct netconfig *
+getnetconf_cached(const char *netid)
+{
+ static struct nc_entry {
+ struct netconfig *nconf;
+ struct nc_entry *next;
+ } *head;
+ struct nc_entry *p;
+ struct netconfig *nconf;
+
+ for (p = head; p != NULL; p = p->next)
+ if (strcmp(netid, p->nconf->nc_netid) == 0)
+ return (p->nconf);
+
+ if ((nconf = getnetconfigent(netid)) == NULL)
+ return (NULL);
+ if ((p = malloc(sizeof(*p))) == NULL)
+ err(1, "malloc");
+ p->nconf = nconf;
+ p->next = head;
+ head = p;
+
+ return (p->nconf);
+}
+
+/*
+ * xdr routines for mount rpc's
+ */
+static int
+xdr_dir(XDR *xdrsp, char *dirp)
+{
+ return (xdr_string(xdrsp, &dirp, MNTPATHLEN));
+}
+
+static int
+xdr_fh(XDR *xdrsp, struct nfhret *np)
+{
+ int i;
+ long auth, authcnt, authfnd = 0;
+
+ if (!xdr_u_long(xdrsp, &np->stat))
+ return (0);
+ if (np->stat)
+ return (1);
+ switch (np->vers) {
+ case 1:
+ np->fhsize = NFS_FHSIZE;
+ return (xdr_opaque(xdrsp, (caddr_t)np->nfh, NFS_FHSIZE));
+ case 3:
+ if (!xdr_long(xdrsp, &np->fhsize))
+ return (0);
+ if (np->fhsize <= 0 || np->fhsize > NFS3_FHSIZE)
+ return (0);
+ if (!xdr_opaque(xdrsp, (caddr_t)np->nfh, np->fhsize))
+ return (0);
+ if (!xdr_long(xdrsp, &authcnt))
+ return (0);
+ for (i = 0; i < authcnt; i++) {
+ if (!xdr_long(xdrsp, &auth))
+ return (0);
+ if (np->auth == -1) {
+ np->auth = auth;
+ authfnd++;
+ } else if (auth == np->auth) {
+ authfnd++;
+ }
+ }
+ /*
+ * Some servers, such as DEC's OSF/1 return a nil authenticator
+ * list to indicate RPCAUTH_UNIX.
+ */
+ if (authcnt == 0 && np->auth == -1)
+ np->auth = AUTH_SYS;
+ if (!authfnd && (authcnt > 0 || np->auth != AUTH_SYS))
+ np->stat = EAUTH;
+ return (1);
+ };
+ return (0);
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr, "%s\n%s\n%s\n%s\n",
+"usage: mount_nfs [-23bcdiLlNPsTU] [-a maxreadahead] [-D deadthresh]",
+" [-g maxgroups] [-I readdirsize] [-o options] [-R retrycnt]",
+" [-r readsize] [-t timeout] [-w writesize] [-x retrans]",
+" rhost:path node");
+ exit(1);
+}
diff --git a/sbin/mount_nullfs/Makefile b/sbin/mount_nullfs/Makefile
new file mode 100644
index 0000000..0b2ecc5
--- /dev/null
+++ b/sbin/mount_nullfs/Makefile
@@ -0,0 +1,13 @@
+# @(#)Makefile 8.3 (Berkeley) 3/27/94
+# $FreeBSD$
+
+PROG= mount_nullfs
+SRCS= mount_nullfs.c getmntopts.c
+MAN= mount_nullfs.8
+
+MOUNT= ${.CURDIR}/../mount
+CFLAGS+=-I${MOUNT}
+
+.PATH: ${MOUNT}
+
+.include <bsd.prog.mk>
diff --git a/sbin/mount_nullfs/mount_nullfs.8 b/sbin/mount_nullfs/mount_nullfs.8
new file mode 100644
index 0000000..177ecdd
--- /dev/null
+++ b/sbin/mount_nullfs/mount_nullfs.8
@@ -0,0 +1,245 @@
+.\"
+.\" Copyright (c) 1992, 1993, 1994
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software donated to Berkeley by
+.\" John Heidemann of the UCLA Ficus project.
+.\"
+.\"
+.\" 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.
+.\" 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.
+.\"
+.\" @(#)mount_null.8 8.6 (Berkeley) 5/1/95
+.\" $FreeBSD$
+.\"
+.Dd May 1, 1995
+.Dt MOUNT_NULLFS 8
+.Os
+.Sh NAME
+.Nm mount_nullfs
+.Nd "mount a loopback file system sub-tree; demonstrate the use of a null file system layer"
+.Sh SYNOPSIS
+.Nm
+.Op Fl o Ar options
+.Ar target
+.Ar mount-point
+.Sh DESCRIPTION
+The
+.Nm
+utility creates a
+null layer, duplicating a sub-tree of the file system
+name space under another part of the global file system namespace.
+This allows existing files and directories to be accessed
+using a different pathname.
+.Pp
+The primary differences between a virtual copy of the file system
+and a symbolic link are that the
+.Xr getcwd 3
+functions work correctly in the virtual copy, and that other file systems
+may be mounted on the virtual copy without affecting the original.
+A different device number for the virtual copy is returned by
+.Xr stat 2 ,
+but in other respects it is indistinguishable from the original.
+.Pp
+The
+.Nm
+file system differs from a traditional
+loopback file system in two respects: it is implemented using
+a stackable layers techniques, and its
+.Do null-node Dc Ns s
+stack above
+all lower-layer vnodes, not just over directory vnodes.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl o
+Options are specified with a
+.Fl o
+flag followed by a comma separated string of options.
+See the
+.Xr mount 8
+man page for possible options and their meanings.
+.El
+.Pp
+The null layer has two purposes.
+First, it serves as a demonstration of layering by providing a layer
+which does nothing.
+(It actually does everything the loopback file system does,
+which is slightly more than nothing.)
+Second, the null layer can serve as a prototype layer.
+Since it provides all necessary layer framework,
+new file system layers can be created very easily by starting
+with a null layer.
+.Pp
+The remainder of this man page examines the null layer as a basis
+for constructing new layers.
+.\"
+.\"
+.Sh INSTANTIATING NEW NULL LAYERS
+New null layers are created with
+.Nm .
+The
+.Nm
+utility takes two arguments, the pathname
+of the lower vfs (target-pn) and the pathname where the null
+layer will appear in the namespace (mount-point-pn).
+After
+the null layer is put into place, the contents
+of target-pn subtree will be aliased under mount-point-pn.
+.\"
+.\"
+.Sh OPERATION OF A NULL LAYER
+The null layer is the minimum file system layer,
+simply bypassing all possible operations to the lower layer
+for processing there.
+The majority of its activity centers
+on the bypass routine, through which nearly all vnode operations
+pass.
+.Pp
+The bypass routine accepts arbitrary vnode operations for
+handling by the lower layer.
+It begins by examining vnode
+operation arguments and replacing any null-nodes by their
+lower-layer equivalents.
+It then invokes the operation
+on the lower layer.
+Finally, it replaces the null-nodes
+in the arguments and, if a vnode is returned by the operation,
+stacks a null-node on top of the returned vnode.
+.Pp
+Although bypass handles most operations,
+.Em vop_getattr ,
+.Em vop_inactive ,
+.Em vop_reclaim ,
+and
+.Em vop_print
+are not bypassed.
+.Em Vop_getattr
+must change the fsid being returned.
+.Em Vop_inactive
+and
+.Em vop_reclaim
+are not bypassed so that
+they can handle freeing null-layer specific data.
+.Em Vop_print
+is not bypassed to avoid excessive debugging
+information.
+.\"
+.\"
+.Sh INSTANTIATING VNODE STACKS
+Mounting associates the null layer with a lower layer,
+in effect stacking two VFSes.
+Vnode stacks are instead
+created on demand as files are accessed.
+.Pp
+The initial mount creates a single vnode stack for the
+root of the new null layer.
+All other vnode stacks
+are created as a result of vnode operations on
+this or other null vnode stacks.
+.Pp
+New vnode stacks come into existence as a result of
+an operation which returns a vnode.
+The bypass routine stacks a null-node above the new
+vnode before returning it to the caller.
+.Pp
+For example, imagine mounting a null layer with
+.Bd -literal -offset indent
+mount_nullfs /usr/include /dev/layer/null
+.Ed
+.Pp
+Changing directory to
+.Pa /dev/layer/null
+will assign
+the root null-node (which was created when the null layer was mounted).
+Now consider opening
+.Pa sys .
+A vop_lookup would be
+done on the root null-node.
+This operation would bypass through
+to the lower layer which would return a vnode representing
+the UFS
+.Pa sys .
+Null_bypass then builds a null-node
+aliasing the UFS
+.Pa sys
+and returns this to the caller.
+Later operations on the null-node
+.Pa sys
+will repeat this
+process when constructing other vnode stacks.
+.\"
+.\"
+.Sh CREATING OTHER FILE SYSTEM LAYERS
+One of the easiest ways to construct new file system layers is to make
+a copy of the null layer, rename all files and variables, and
+then begin modifying the copy.
+The
+.Xr sed 1
+utility can be used to easily rename
+all variables.
+.Pp
+The umap layer is an example of a layer descended from the
+null layer.
+.\"
+.\"
+.Sh INVOKING OPERATIONS ON LOWER LAYERS
+There are two techniques to invoke operations on a lower layer
+when the operation cannot be completely bypassed.
+Each method
+is appropriate in different situations.
+In both cases,
+it is the responsibility of the aliasing layer to make
+the operation arguments "correct" for the lower layer
+by mapping a vnode argument to the lower layer.
+.Pp
+The first approach is to call the aliasing layer's bypass routine.
+This method is most suitable when you wish to invoke the operation
+currently being handled on the lower layer.
+It has the advantage that
+the bypass routine already must do argument mapping.
+An example of this is
+.Em null_getattrs
+in the null layer.
+.Pp
+A second approach is to directly invoke vnode operations on
+the lower layer with the
+.Em VOP_OPERATIONNAME
+interface.
+The advantage of this method is that it is easy to invoke
+arbitrary operations on the lower layer.
+The disadvantage
+is that vnode arguments must be manually mapped.
+.\"
+.\"
+.Sh SEE ALSO
+.Xr mount 8
+.Pp
+UCLA Technical Report CSD-910056,
+.Em "Stackable Layers: an Architecture for File System Development" .
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Bx 4.4 .
diff --git a/sbin/mount_nullfs/mount_nullfs.c b/sbin/mount_nullfs/mount_nullfs.c
new file mode 100644
index 0000000..e08599f
--- /dev/null
+++ b/sbin/mount_nullfs/mount_nullfs.c
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 1992, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software donated to Berkeley by
+ * Jan-Simon Pendry.
+ *
+ * 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.
+ * 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 lint
+static const char copyright[] =
+"@(#) Copyright (c) 1992, 1993, 1994\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)mount_null.c 8.6 (Berkeley) 4/26/95";
+#endif
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/mount.h>
+#include <sys/uio.h>
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include "mntopts.h"
+
+int subdir(const char *, const char *);
+static void usage(void) __dead2;
+
+int
+main(int argc, char *argv[])
+{
+ struct iovec *iov;
+ char *p, *val;
+ char source[MAXPATHLEN];
+ char target[MAXPATHLEN];
+ char errmsg[255];
+ int ch, iovlen;
+ char nullfs[] = "nullfs";
+
+ iov = NULL;
+ iovlen = 0;
+ errmsg[0] = '\0';
+ while ((ch = getopt(argc, argv, "o:")) != -1)
+ switch(ch) {
+ case 'o':
+ val = strdup("");
+ p = strchr(optarg, '=');
+ if (p != NULL) {
+ free(val);
+ *p = '\0';
+ val = p + 1;
+ }
+ build_iovec(&iov, &iovlen, optarg, val, (size_t)-1);
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 2)
+ usage();
+
+ /* resolve target and source with realpath(3) */
+ if (checkpath(argv[0], target) != 0)
+ err(EX_USAGE, "%s", target);
+ if (checkpath(argv[1], source) != 0)
+ err(EX_USAGE, "%s", source);
+
+ if (subdir(target, source) || subdir(source, target))
+ errx(EX_USAGE, "%s (%s) and %s are not distinct paths",
+ argv[0], target, argv[1]);
+
+ build_iovec(&iov, &iovlen, "fstype", nullfs, (size_t)-1);
+ build_iovec(&iov, &iovlen, "fspath", source, (size_t)-1);
+ build_iovec(&iov, &iovlen, "target", target, (size_t)-1);
+ build_iovec(&iov, &iovlen, "errmsg", errmsg, sizeof(errmsg));
+ if (nmount(iov, iovlen, 0) < 0) {
+ if (errmsg[0] != 0)
+ err(1, "%s: %s", source, errmsg);
+ else
+ err(1, "%s", source);
+ }
+ exit(0);
+}
+
+int
+subdir(const char *p, const char *dir)
+{
+ int l;
+
+ l = strlen(dir);
+ if (l <= 1)
+ return (1);
+
+ if ((strncmp(p, dir, l) == 0) && (p[l] == '/' || p[l] == '\0'))
+ return (1);
+
+ return (0);
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr,
+ "usage: mount_nullfs [-o options] target mount-point\n");
+ exit(1);
+}
diff --git a/sbin/mount_udf/Makefile b/sbin/mount_udf/Makefile
new file mode 100644
index 0000000..12d5d58
--- /dev/null
+++ b/sbin/mount_udf/Makefile
@@ -0,0 +1,16 @@
+# $FreeBSD$
+
+PROG= mount_udf
+SRCS= mount_udf.c getmntopts.c
+MAN= mount_udf.8
+LIBADD= kiconv
+
+MOUNT= ${.CURDIR}/../mount
+CFLAGS+= -I${MOUNT} -I${.CURDIR}/../../sys
+.PATH: ${MOUNT}
+
+# Needs to be dynamically linked for optional dlopen() access to
+# userland libiconv
+NO_SHARED?= NO
+
+.include <bsd.prog.mk>
diff --git a/sbin/mount_udf/mount_udf.8 b/sbin/mount_udf/mount_udf.8
new file mode 100644
index 0000000..fb39419
--- /dev/null
+++ b/sbin/mount_udf/mount_udf.8
@@ -0,0 +1,76 @@
+.\" Copyright (c) 2002
+.\" Scott Long <scottl@FreeBSD.org>
+.\" Jeroen Ruigrok van der Werven <asmodai@wxs.nl>
+.\" 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.
+.\"
+.\" 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd March 23, 2002
+.Dt MOUNT_UDF 8
+.Os
+.Sh NAME
+.Nm mount_udf
+.Nd mount a UDF file system
+.Sh SYNOPSIS
+.Nm
+.Op Fl v
+.Op Fl o Ar options
+.Op Fl C Ar charset
+.Ar special node
+.Sh DESCRIPTION
+The
+.Nm
+utility attaches the UDF file system residing on the device
+.Ar special
+to the global file system namespace at the location indicated by
+.Ar node .
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl o
+Options are specified with a
+.Fl o
+flag followed by a comma separated string of options.
+See the
+.Xr mount 8
+man page for possible options and their meanings.
+The following UDF specific options are available:
+.It Fl v
+Be verbose about mounting the UDF file system.
+.It Fl C Ar charset
+Specify local
+.Ar charset
+to convert Unicode file names.
+.El
+.Sh SEE ALSO
+.Xr cdcontrol 1 ,
+.Xr mount 2 ,
+.Xr unmount 2 ,
+.Xr fstab 5 ,
+.Xr mount 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 5.0 .
diff --git a/sbin/mount_udf/mount_udf.c b/sbin/mount_udf/mount_udf.c
new file mode 100644
index 0000000..7585c50
--- /dev/null
+++ b/sbin/mount_udf/mount_udf.c
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) 1992, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 2002 Scott Long
+ *
+ * This code is derived from software contributed to Berkeley
+ * by Pace Willisson (pace@blitz.com). The Rock Ridge Extension
+ * Support code is derived from software contributed to Berkeley
+ * by Atsushi Murai (amurai@spec.co.jp).
+ *
+ * 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.
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+/*
+ * This is just a rip-off of mount_iso9660.c. It's been vastly simplified
+ * because UDF doesn't take any options at this time.
+ */
+
+#include <sys/cdio.h>
+#include <sys/file.h>
+#include <sys/iconv.h>
+#include <sys/param.h>
+#include <sys/linker.h>
+#include <sys/module.h>
+#include <sys/mount.h>
+#include <sys/uio.h>
+
+#include <fs/udf/udf_mount.h>
+
+#include <err.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include "mntopts.h"
+
+static struct mntopt mopts[] = {
+ MOPT_STDOPTS,
+ MOPT_UPDATE,
+ MOPT_END
+};
+
+int set_charset(char **, char **, const char *);
+void usage(void);
+
+int
+main(int argc, char **argv)
+{
+ char mntpath[MAXPATHLEN];
+ char fstype[] = "udf";
+ struct iovec *iov;
+ char *cs_disk, *cs_local, *dev, *dir;
+ int ch, iovlen, mntflags, udf_flags, verbose;
+
+ iovlen = mntflags = udf_flags = verbose = 0;
+ cs_disk = cs_local = NULL;
+ iov = NULL;
+ while ((ch = getopt(argc, argv, "o:vC:")) != -1)
+ switch (ch) {
+ case 'o':
+ getmntopts(optarg, mopts, &mntflags, NULL);
+ break;
+ case 'v':
+ verbose++;
+ break;
+ case 'C':
+ if (set_charset(&cs_disk, &cs_local, optarg) == -1)
+ err(EX_OSERR, "udf_iconv");
+ udf_flags |= UDFMNT_KICONV;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 2)
+ usage();
+
+ dev = argv[0];
+ dir = argv[1];
+
+ /*
+ * Resolve the mountpoint with realpath(3) and remove unnecessary
+ * slashes from the devicename if there are any.
+ */
+ if (checkpath(dir, mntpath) != 0)
+ err(EX_USAGE, "%s", mntpath);
+ (void)rmslashes(dev, dev);
+
+ /*
+ * UDF file systems are not writeable.
+ */
+ mntflags |= MNT_RDONLY;
+
+ build_iovec(&iov, &iovlen, "fstype", fstype, (size_t)-1);
+ build_iovec(&iov, &iovlen, "fspath", mntpath, (size_t)-1);
+ build_iovec(&iov, &iovlen, "from", dev, (size_t)-1);
+ build_iovec(&iov, &iovlen, "flags", &udf_flags, sizeof(udf_flags));
+ if (udf_flags & UDFMNT_KICONV) {
+ build_iovec(&iov, &iovlen, "cs_disk", cs_disk, (size_t)-1);
+ build_iovec(&iov, &iovlen, "cs_local", cs_local, (size_t)-1);
+ }
+ if (nmount(iov, iovlen, mntflags) < 0)
+ err(1, "%s", dev);
+ exit(0);
+}
+
+int
+set_charset(char **cs_disk, char **cs_local, const char *localcs)
+{
+ int error;
+
+ if (modfind("udf_iconv") < 0)
+ if (kldload("udf_iconv") < 0 || modfind("udf_iconv") < 0) {
+ warnx( "cannot find or load \"udf_iconv\" kernel module");
+ return (-1);
+ }
+
+ if ((*cs_disk = malloc(ICONV_CSNMAXLEN)) == NULL)
+ return (-1);
+ if ((*cs_local = malloc(ICONV_CSNMAXLEN)) == NULL)
+ return (-1);
+ strncpy(*cs_disk, ENCODING_UNICODE, ICONV_CSNMAXLEN);
+ strncpy(*cs_local, localcs, ICONV_CSNMAXLEN);
+ error = kiconv_add_xlat16_cspairs(*cs_disk, *cs_local);
+ if (error)
+ return (-1);
+
+ return (0);
+}
+
+void
+usage(void)
+{
+ (void)fprintf(stderr,
+ "usage: mount_udf [-v] [-o options] [-C charset] special node\n");
+ exit(EX_USAGE);
+}
diff --git a/sbin/mount_unionfs/Makefile b/sbin/mount_unionfs/Makefile
new file mode 100644
index 0000000..276fc74
--- /dev/null
+++ b/sbin/mount_unionfs/Makefile
@@ -0,0 +1,13 @@
+# @(#)Makefile 8.3 (Berkeley) 3/27/94
+# $FreeBSD$
+
+PROG= mount_unionfs
+SRCS= mount_unionfs.c getmntopts.c
+MAN= mount_unionfs.8
+
+MOUNT= ${.CURDIR}/../mount
+CFLAGS+=-I${MOUNT}
+
+.PATH: ${MOUNT}
+
+.include <bsd.prog.mk>
diff --git a/sbin/mount_unionfs/mount_unionfs.8 b/sbin/mount_unionfs/mount_unionfs.8
new file mode 100644
index 0000000..075eff1
--- /dev/null
+++ b/sbin/mount_unionfs/mount_unionfs.8
@@ -0,0 +1,395 @@
+.\" Copyright (c) 1994
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software donated to Berkeley by
+.\" Jan-Simon Pendry.
+.\"
+.\" 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.
+.\" 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.
+.\"
+.\" @(#)mount_union.8 8.6 (Berkeley) 3/27/94
+.\" $FreeBSD$
+.\"
+.Dd November 30, 2006
+.Dt MOUNT_UNIONFS 8
+.Os
+.Sh NAME
+.Nm mount_unionfs
+.Nd mount union file systems
+.Sh SYNOPSIS
+.Nm
+.Op Fl b
+.Op Fl o Ar options
+.Ar directory
+.Ar uniondir
+.Sh DESCRIPTION
+The
+.Nm
+utility attaches
+.Ar directory
+above
+.Ar uniondir
+in such a way that the contents of both directory trees remain visible.
+By default,
+.Ar directory
+becomes the
+.Em upper
+layer and
+.Ar uniondir
+becomes the
+.Em lower
+layer.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl b
+Deprecated.
+Use
+.Fl o Cm below
+instead.
+.It Fl o
+Options are specified with the
+.Fl o
+flag followed by an option.
+The following options are available:
+.Bl -tag -width indent
+.It Cm below
+Inverts the default position, so that
+.Ar directory
+becomes the lower layer and
+.Ar uniondir
+becomes the upper layer.
+However,
+.Ar uniondir
+remains the mount point.
+.It Sm Cm copymode No = Cm traditional | transparent | masquerade Sm
+Specifies the way to create a file or a directory in the upper layer
+automatically when needed.
+The
+.Cm traditional
+mode
+uses the same way as the old unionfs for backward compatibility, and
+.Cm transparent
+duplicates the file and directory mode bits and the ownership in the
+lower layer to the created file in the upper layer.
+For behavior of the
+.Cm masquerade
+mode, see
+.Sx MASQUERADE MODE
+below.
+.It Sm Cm whiteout No = Cm always | whenneeded Sm
+Specifies whether whiteouts should always be made in the upper layer
+when removing a file or directory or only when it already exists in the
+lower layer.
+.It Cm udir Ns = Ns Ar mode
+Specifies directory mode bits in octal for
+.Cm masquerade
+mode.
+.It Cm ufile Ns = Ns Ar mode
+Specifies file mode bits in octal for
+.Cm masquerade
+mode.
+.It Cm gid Ns = Ns Ar gid
+Specifies group for
+.Cm masquerade
+mode.
+.It Cm uid Ns = Ns Ar uid
+Specifies user for
+.Cm masquerade
+mode.
+.El
+.El
+.Pp
+To enforce file system security, the user mounting a file system
+must be superuser or else have write permission on the mounted-on
+directory.
+In addition, the
+.Va vfs.usermount
+.Xr sysctl 8
+variable must be set to 1 to permit file system mounting by ordinary users.
+However, note that
+.Cm transparent
+and
+.Cm masquerade
+modes require
+.Va vfs.usermount
+to be set to 0 because this functionality can only be used by superusers.
+.Pp
+Filenames are looked up in the upper layer and then in the
+lower layer.
+If a directory is found in the lower layer, and there is no entry
+in the upper layer, then a
+.Em shadow
+directory will be created in the upper layer.
+The ownership and the mode bits are set depending on the
+.Cm copymode
+option.
+In
+.Cm traditional
+mode, it will be owned by the user who originally did the
+union mount, with mode 0777
+.Pq Dq Li rwxrwxrwx
+modified by the umask in effect at that time.
+.Pp
+If a file exists in the upper layer then there is no way to access
+a file with the same name in the lower layer.
+If necessary, a combination of loopback and union mounts can be made
+which will still allow the lower files to be accessed by a different
+pathname.
+.Pp
+Except in the case of a directory,
+access to an object is granted via the normal file system access checks.
+For directories, the current user must have access to both the upper
+and lower directories (should they both exist).
+.Pp
+Requests to create or modify objects in
+.Ar uniondir
+are passed to the upper layer with the exception of a few special cases.
+An attempt to open for writing a file which exists in the lower layer
+causes a copy of the
+.Em entire
+file to be made to the upper layer, and then for the upper layer copy
+to be opened.
+Similarly, an attempt to truncate a lower layer file to zero length
+causes an empty file to be created in the upper layer.
+Any other operation which would ultimately require modification to
+the lower layer fails with
+.Er EROFS .
+.Pp
+The union file system manipulates the namespace, rather than
+individual file systems.
+The union operation applies recursively down the directory tree
+now rooted at
+.Ar uniondir .
+Thus any file systems which are mounted under
+.Ar uniondir
+will take part in the union operation.
+This differs from the
+.Cm union
+option to
+.Xr mount 8
+which only applies the union operation to the mount point itself,
+and then only for lookups.
+.Sh MASQUERADE MODE
+When a file
+(or a directory)
+is created in the upper layer, the
+.Cm masquerade
+mode sets it the fixed access mode bits given in
+.Cm ufile
+(for files)
+or
+.Cm udir
+(for directories)
+option and the owner given in
+.Cm udir
+and
+.Cm gid
+options, instead of ones in the lower layer.
+Note that in the
+.Cm masquerade
+mode and when owner of the file or directory matches
+one specified in
+.Cm uid
+option, only mode bits for the owner will be modified.
+More specifically, the file mode bits in the upper layer will
+be
+(mode in the lower layer)
+OR
+(mode given in
+.Cm ufile
+AND 0700), and the ownership will be the same as one in the lower layer.
+.Pp
+The default values for
+.Cm ufile , udir , uid ,
+and
+.Cm gid
+are as follow:
+.Pp
+.Bl -bullet -compact
+.It
+If none of
+.Cm ufile
+and
+.Cm udir
+were specified, access mode bits in the mount point will be used.
+.It
+If none of
+.Cm uid
+and
+.Cm gid
+were specified, ownership in the mount point will be used.
+.It
+If one of
+.Cm udir
+or
+.Cm ufile
+is not specified, the value of the other option will be used.
+.It
+If one of
+.Cm uid
+or
+.Cm gid
+is not specified, the value of the other option will be used.
+.El
+.Sh EXAMPLES
+The commands
+.Bd -literal -offset indent
+mount -t cd9660 -o ro /dev/cd0 /usr/src
+mount -t unionfs -o noatime /var/obj /usr/src
+.Ed
+.Pp
+mount the CD-ROM drive
+.Pa /dev/cd0
+on
+.Pa /usr/src
+and then attaches
+.Pa /var/obj
+on top.
+For most purposes the effect of this is to make the
+source tree appear writable
+even though it is stored on a CD-ROM.
+The
+.Fl o Cm noatime
+option is useful to avoid unnecessary copying from the lower to the
+upper layer.
+.Pp
+The commands
+.Bd -literal -offset indent
+mount -t cd9660 -o ro /dev/cd0 /usr/src
+chown 2020 /usr/src
+mount -t unionfs -o noatime -o copymode=masquerade -o uid=builder \\
+ -o udir=755 -o ufile=644 /var/obj /usr/src
+.Ed
+.Pp
+also mount the CD-ROM drive
+.Pa /dev/cd0
+on
+.Pa /usr/src
+and then attaches
+.Pa /var/obj
+on top.
+Furthermore, the owner of all files and directories in
+.Pa /usr/src
+is a regular user with UID 2020
+when seen from the upper layer.
+Note that for the access mode bits,
+ones in the lower layer
+(on the CD-ROM, in this example)
+are still used without change.
+Thus, write privilege to the upper layer can be controlled
+independently from access mode bits and ownership in the lower layer.
+If a user does not have read privilege from the lower layer,
+one cannot still read even when the upper layer is mounted by using
+.Cm masquerade
+mode.
+.Pp
+The command
+.Bd -literal -offset indent
+mount -t unionfs -o noatime -o below /sys $HOME/sys
+.Ed
+.Pp
+attaches the system source tree below the
+.Pa sys
+directory in the user's home directory.
+This allows individual users to make private changes
+to the source, and build new kernels, without those
+changes becoming visible to other users.
+Note that the files in the lower layer remain
+accessible via
+.Pa /sys .
+.Sh SEE ALSO
+.Xr intro 2 ,
+.Xr mount 2 ,
+.Xr unmount 2 ,
+.Xr fstab 5 ,
+.Xr mount 8 ,
+.Xr mount_nullfs 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Bx 4.4 .
+.Pp
+The
+.Fl r
+option for hiding the lower layer completely was removed in
+.Fx 7.0
+because this is identical to using
+.Xr mount_nullfs 8 .
+.Sh AUTHORS
+.An -nosplit
+In
+.Fx 7.0 ,
+.An Masanori OZAWA Aq Mt ozawa@ongs.co.jp
+reimplemented handling of locking, whiteout, and file mode bits, and
+.An Hiroki Sato Aq Mt hrs@FreeBSD.org
+wrote about the changes in this manual page.
+.Sh BUGS
+THIS FILE SYSTEM TYPE IS NOT YET FULLY SUPPORTED (READ: IT DOESN'T WORK)
+AND USING IT MAY, IN FACT, DESTROY DATA ON YOUR SYSTEM.
+USE AT YOUR
+OWN RISK.
+BEWARE OF DOG.
+SLIPPERY WHEN WET.
+BATTERIES NOT INCLUDED.
+.Pp
+This code also needs an owner in order to be less dangerous - serious
+hackers can apply by sending mail to
+.Aq Mt freebsd-fs@FreeBSD.org
+and announcing
+their intent to take it over.
+.Pp
+Without whiteout support from the file system backing the upper layer,
+there is no way that delete and rename operations on lower layer
+objects can be done.
+.Er EOPNOTSUPP
+is returned for this kind of operations as generated by VOP_WHITEOUT()
+along with any others which would make modifications to the lower
+layer, such as
+.Xr chmod 1 .
+.Pp
+Running
+.Xr find 1
+over a union tree has the side-effect of creating
+a tree of shadow directories in the upper layer.
+.Pp
+The current implementation does not support copying extended attributes
+for
+.Xr acl 9 ,
+.Xr mac 9 ,
+or so on to the upper layer.
+Note that this may be a security issue.
+.Pp
+A shadow directory, which is one automatically created in the upper
+layer when it exists in the lower layer and does not exist in the
+upper layer, is always created with the superuser privilege.
+However, a file copied from the lower layer in the same way
+is created by the user who accessed it.
+Because of this,
+if the user is not the superuser, even in
+.Cm transparent
+mode the access mode bits in the copied file in the upper layer
+will not always be the same as ones in the lower layer.
+This behavior should be fixed.
diff --git a/sbin/mount_unionfs/mount_unionfs.c b/sbin/mount_unionfs/mount_unionfs.c
new file mode 100644
index 0000000..1026ecf
--- /dev/null
+++ b/sbin/mount_unionfs/mount_unionfs.c
@@ -0,0 +1,195 @@
+/*-
+ * Copyright (c) 1992, 1993, 1994
+ * The Regents of the University of California.
+ * Copyright (c) 2005, 2006 Masanori Ozawa <ozawa@ongs.co.jp>, ONGS Inc.
+ * Copyright (c) 2006 Daichi Goto <daichi@freebsd.org>
+ * All rights reserved.
+ *
+ * This code is derived from software donated to Berkeley by
+ * Jan-Simon Pendry.
+ *
+ * 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.
+ * 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 lint
+static const char copyright[] =
+"@(#) Copyright (c) 1992, 1993, 1994\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)mount_union.c 8.5 (Berkeley) 3/27/94";
+#else
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/mount.h>
+#include <sys/uio.h>
+#include <sys/errno.h>
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+#include <grp.h>
+#include <pwd.h>
+
+#include "mntopts.h"
+
+static int
+subdir(const char *p, const char *dir)
+{
+ int l;
+
+ l = strlen(dir);
+ if (l <= 1)
+ return (1);
+
+ if ((strncmp(p, dir, l) == 0) && (p[l] == '/' || p[l] == '\0'))
+ return (1);
+
+ return (0);
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr,
+ "usage: mount_unionfs [-o options] directory uniondir\n");
+ exit(EX_USAGE);
+}
+
+static void
+parse_gid(const char *s, char *buf, size_t bufsize)
+{
+ struct group *gr;
+ char *inval;
+
+ if ((gr = getgrnam(s)) != NULL)
+ snprintf(buf, bufsize, "%d", gr->gr_gid);
+ else {
+ strtol(s, &inval, 10);
+ if (*inval != 0) {
+ errx(EX_NOUSER, "unknown group id: %s", s);
+ usage();
+ } else {
+ strncpy(buf, s, bufsize);
+ }
+ }
+}
+
+static void
+parse_uid(const char *s, char *buf, size_t bufsize)
+{
+ struct passwd *pw;
+ char *inval;
+
+ if ((pw = getpwnam(s)) != NULL)
+ snprintf(buf, bufsize, "%d", pw->pw_uid);
+ else {
+ strtol(s, &inval, 10);
+ if (*inval != 0) {
+ errx(EX_NOUSER, "unknown user id: %s", s);
+ usage();
+ } else {
+ strncpy(buf, s, bufsize);
+ }
+ }
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct iovec *iov;
+ int ch, iovlen;
+ char source [MAXPATHLEN], target[MAXPATHLEN], errmsg[255];
+ char uid_str[20], gid_str[20];
+ char fstype[] = "unionfs";
+ char *p, *val;
+
+ iov = NULL;
+ iovlen = 0;
+ memset(errmsg, 0, sizeof(errmsg));
+
+ while ((ch = getopt(argc, argv, "bo:")) != -1) {
+ switch (ch) {
+ case 'b':
+ printf("\n -b is deprecated. Use \"-o below\" instead\n");
+ build_iovec(&iov, &iovlen, "below", NULL, 0);
+ break;
+ case 'o':
+ p = strchr(optarg, '=');
+ val = NULL;
+ if (p != NULL) {
+ *p = '\0';
+ val = p + 1;
+ if (strcmp(optarg, "gid") == 0) {
+ parse_gid(val, gid_str, sizeof(gid_str));
+ val = gid_str;
+ }
+ else if (strcmp(optarg, "uid") == 0) {
+ parse_uid(val, uid_str, sizeof(uid_str));
+ val = uid_str;
+ }
+ }
+ build_iovec(&iov, &iovlen, optarg, val, (size_t)-1);
+ break;
+ case '?':
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 2)
+ usage();
+
+ /* resolve both target and source with realpath(3) */
+ if (checkpath(argv[0], target) != 0)
+ err(EX_USAGE, "%s", target);
+ if (checkpath(argv[1], source) != 0)
+ err(EX_USAGE, "%s", source);
+
+ if (subdir(target, source) || subdir(source, target))
+ errx(EX_USAGE, "%s (%s) and %s (%s) are not distinct paths",
+ argv[0], target, argv[1], source);
+
+ build_iovec(&iov, &iovlen, "fstype", fstype, (size_t)-1);
+ build_iovec(&iov, &iovlen, "fspath", source, (size_t)-1);
+ build_iovec(&iov, &iovlen, "from", target, (size_t)-1);
+ build_iovec(&iov, &iovlen, "errmsg", errmsg, sizeof(errmsg));
+
+ if (nmount(iov, iovlen, 0))
+ err(EX_OSERR, "%s: %s", source, errmsg);
+ exit(0);
+}
diff --git a/sbin/nandfs/Makefile b/sbin/nandfs/Makefile
new file mode 100644
index 0000000..5757f8c
--- /dev/null
+++ b/sbin/nandfs/Makefile
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+PROG= nandfs
+SRCS= nandfs.c lssnap.c mksnap.c rmsnap.c
+MAN= nandfs.8
+
+LIBADD= nandfs
+
+.include <bsd.prog.mk>
diff --git a/sbin/nandfs/lssnap.c b/sbin/nandfs/lssnap.c
new file mode 100644
index 0000000..fbee3cd
--- /dev/null
+++ b/sbin/nandfs/lssnap.c
@@ -0,0 +1,112 @@
+/*-
+ * Copyright (c) 2012 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was developed by Semihalf under sponsorship
+ * from the FreeBSD Foundation.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+#include <sys/types.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sysexits.h>
+#include <time.h>
+
+#include <fs/nandfs/nandfs_fs.h>
+#include <libnandfs.h>
+
+#include "nandfs.h"
+
+#define NCPINFO 512
+
+static void
+lssnap_usage(void)
+{
+
+ fprintf(stderr, "usage:\n");
+ fprintf(stderr, "\tlssnap node\n");
+}
+
+static void
+print_cpinfo(struct nandfs_cpinfo *cpinfo)
+{
+ struct tm tm;
+ time_t t;
+ char timebuf[128];
+
+ t = (time_t)cpinfo->nci_create;
+ localtime_r(&t, &tm);
+ strftime(timebuf, sizeof(timebuf), "%F %T", &tm);
+
+ printf("%20llu %s\n", (unsigned long long)cpinfo->nci_cno, timebuf);
+}
+
+int
+nandfs_lssnap(int argc, char **argv)
+{
+ struct nandfs_cpinfo *cpinfos;
+ struct nandfs fs;
+ uint64_t next;
+ int error, nsnap, i;
+
+ if (argc != 1) {
+ lssnap_usage();
+ return (EX_USAGE);
+ }
+
+ cpinfos = malloc(sizeof(*cpinfos) * NCPINFO);
+ if (cpinfos == NULL) {
+ fprintf(stderr, "cannot allocate memory\n");
+ return (-1);
+ }
+
+ nandfs_init(&fs, argv[0]);
+ error = nandfs_open(&fs);
+ if (error == -1) {
+ fprintf(stderr, "nandfs_open: %s\n", nandfs_errmsg(&fs));
+ goto out;
+ }
+
+ for (next = 1; next != 0; next = cpinfos[nsnap - 1].nci_next) {
+ nsnap = nandfs_get_snap(&fs, next, cpinfos, NCPINFO);
+ if (nsnap < 1)
+ break;
+
+ for (i = 0; i < nsnap; i++)
+ print_cpinfo(&cpinfos[i]);
+ }
+
+ if (nsnap == -1)
+ fprintf(stderr, "nandfs_get_snap: %s\n", nandfs_errmsg(&fs));
+
+out:
+ nandfs_close(&fs);
+ nandfs_destroy(&fs);
+ free(cpinfos);
+ return (error);
+}
diff --git a/sbin/nandfs/mksnap.c b/sbin/nandfs/mksnap.c
new file mode 100644
index 0000000..cd2d130
--- /dev/null
+++ b/sbin/nandfs/mksnap.c
@@ -0,0 +1,80 @@
+/*-
+ * Copyright (c) 2012 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was developed by Semihalf under sponsorship
+ * from the FreeBSD Foundation.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+#include <sys/types.h>
+
+#include <stdio.h>
+#include <sysexits.h>
+
+#include <fs/nandfs/nandfs_fs.h>
+#include <libnandfs.h>
+
+#include "nandfs.h"
+
+static void
+mksnap_usage(void)
+{
+
+ fprintf(stderr, "usage:\n");
+ fprintf(stderr, "\tmksnap node\n");
+}
+
+int
+nandfs_mksnap(int argc, char **argv)
+{
+ struct nandfs fs;
+ uint64_t cpno;
+ int error;
+
+ if (argc != 1) {
+ mksnap_usage();
+ return (EX_USAGE);
+ }
+
+ nandfs_init(&fs, argv[0]);
+ error = nandfs_open(&fs);
+ if (error == -1) {
+ fprintf(stderr, "nandfs_open: %s\n", nandfs_errmsg(&fs));
+ goto out;
+ }
+
+ error = nandfs_make_snap(&fs, &cpno);
+ if (error == -1)
+ fprintf(stderr, "nandfs_make_snap: %s\n", nandfs_errmsg(&fs));
+ else
+ printf("%jd\n", cpno);
+
+out:
+ nandfs_close(&fs);
+ nandfs_destroy(&fs);
+ return (error);
+}
diff --git a/sbin/nandfs/nandfs.8 b/sbin/nandfs/nandfs.8
new file mode 100644
index 0000000..e54f9f8
--- /dev/null
+++ b/sbin/nandfs/nandfs.8
@@ -0,0 +1,74 @@
+.\"
+.\" Copyright (c) 2012 The FreeBSD Foundation
+.\" All rights reserved.
+.\"
+.\" This software was developed by Semihalf under sponsorship
+.\" from the FreeBSD Foundation.
+.\"
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd February 28, 2012
+.Dt NANDFS 8
+.Os
+.Sh NAME
+.Nm nandfs
+.Nd manage mounted NAND FS
+.Sh SYNOPSIS
+.Nm
+.Cm lssnap
+.Ar node
+.Nm
+.Cm mksnap
+.Ar node
+.Nm
+.Cm rmsnap
+.Ar snapshot node
+.Sh DESCRIPTION
+The
+.Nm
+utility allows to manage snapshots of a mounted NAND FS.
+.Sh EXAMPLES
+Create a snapshot of filesystem mounted on
+.Em /nand .
+.Bd -literal -offset 2n
+.Li # Ic nandfs mksnap /nand
+1
+.Ed
+.Pp
+List snapshots of filesystem mounted on
+.Em /nand .
+.Bd -literal -offset 2n
+.Li # Ic nandfs lssnap /nand
+1 2012-02-28 18:49:45 ss 138 2
+.Ed
+.Pp
+Remove snapshot 1 of filesystem mounted on
+.Em /nand .
+.Bd -literal -offset 2n
+.Li # Ic nandfs rmsnap 1 /nand
+.Ed
+.Sh AUTHORS
+This utility and manual page were written by
+.An Mateusz Guzik .
diff --git a/sbin/nandfs/nandfs.c b/sbin/nandfs/nandfs.c
new file mode 100644
index 0000000..f7ddda1
--- /dev/null
+++ b/sbin/nandfs/nandfs.c
@@ -0,0 +1,74 @@
+/*-
+ * Copyright (c) 2012 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was developed by Semihalf under sponsorship
+ * from the FreeBSD Foundation.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+
+#include "nandfs.h"
+
+static void
+usage(void)
+{
+
+ fprintf(stderr, "usage: nandfs [lssnap | mksnap | rmsnap <snap>] "
+ "node\n");
+ exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+ int error = 0;
+ char *cmd;
+
+ if (argc < 2)
+ usage();
+
+ cmd = argv[1];
+ argc -= 2;
+ argv += 2;
+
+ if (strcmp(cmd, "lssnap") == 0)
+ error = nandfs_lssnap(argc, argv);
+ else if (strcmp(cmd, "mksnap") == 0)
+ error = nandfs_mksnap(argc, argv);
+ else if (strcmp(cmd, "rmsnap") == 0)
+ error = nandfs_rmsnap(argc, argv);
+ else
+ usage();
+
+ return (error);
+}
diff --git a/sbin/nandfs/nandfs.h b/sbin/nandfs/nandfs.h
new file mode 100644
index 0000000..f544cc2
--- /dev/null
+++ b/sbin/nandfs/nandfs.h
@@ -0,0 +1,40 @@
+/*-
+ * Copyright (c) 2012 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was developed by Semihalf under sponsorship
+ * from the FreeBSD Foundation.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef NANDFS_H
+#define NANDFS_H
+
+int nandfs_lssnap(int, char **);
+int nandfs_mksnap(int, char **);
+int nandfs_rmsnap(int, char **);
+
+#endif /* !NANDFS_H */
diff --git a/sbin/nandfs/rmsnap.c b/sbin/nandfs/rmsnap.c
new file mode 100644
index 0000000..df2cbd5
--- /dev/null
+++ b/sbin/nandfs/rmsnap.c
@@ -0,0 +1,87 @@
+/*-
+ * Copyright (c) 2012 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was developed by Semihalf under sponsorship
+ * from the FreeBSD Foundation.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+#include <sys/types.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <sysexits.h>
+
+#include <fs/nandfs/nandfs_fs.h>
+#include <libnandfs.h>
+
+#include "nandfs.h"
+
+static void
+rmsnap_usage(void)
+{
+
+ fprintf(stderr, "usage:\n");
+ fprintf(stderr, "\trmsnap snap node\n");
+}
+
+int
+nandfs_rmsnap(int argc, char **argv)
+{
+ struct nandfs fs;
+ uint64_t cpno;
+ int error;
+
+ if (argc != 2) {
+ rmsnap_usage();
+ return (EX_USAGE);
+ }
+
+ cpno = strtoll(argv[0], (char **)NULL, 10);
+ if (cpno == 0) {
+ fprintf(stderr, "%s must be a number greater than 0\n",
+ argv[0]);
+ return (EX_USAGE);
+ }
+
+ nandfs_init(&fs, argv[1]);
+ error = nandfs_open(&fs);
+ if (error == -1) {
+ fprintf(stderr, "nandfs_open: %s\n", nandfs_errmsg(&fs));
+ goto out;
+ }
+
+ error = nandfs_delete_snap(&fs, cpno);
+ if (error == -1)
+ fprintf(stderr, "nandfs_delete_snap: %s\n", nandfs_errmsg(&fs));
+
+out:
+ nandfs_close(&fs);
+ nandfs_destroy(&fs);
+ return (error);
+}
diff --git a/sbin/natd/HISTORY b/sbin/natd/HISTORY
new file mode 100644
index 0000000..f929e80
--- /dev/null
+++ b/sbin/natd/HISTORY
@@ -0,0 +1,146 @@
+* Version 0.1
+
+ Initial version of natd.
+
+* Version 0.2
+
+ - Alias address can now be set by giving interface name with
+ new (-n) command-line option.
+
+ - New Makefile based on bsd.prog.mk.
+
+ - Error messages are written to syslog
+ after natd has become a daemon.
+
+* Version 1.0
+
+ - Support for using only single socket (-p option)
+
+* Version 1.1
+
+ - -a option now understands a hostname also.
+ - -a option no longer dumps core.
+ - Packet aliasing software upgraded to v. 1.9
+ - added long option names (like -address)
+
+* Version 1.2
+
+ - Fixed core dump with -port option.
+ - Added -Wall to CFLAGS and some headers added to natd.c
+ to get clean compile by Brian Somers [brian@awfulhak.org].
+
+* Version 1.3
+
+ - Aliasing address initialization is delayed until first
+ packet arrives. This allows natd to start up before
+ interface address is set.
+ - SIGTERM is now catched to allow kernel to close
+ existing connections when system is shutting down.
+ - SIGHUP is now catched to allow natd to refresh aliasing
+ address from interface, which might be useful to tun devices.
+
+* Version 1.4
+
+ - Changed command line options to be compatible with
+ command names used in ppp+packetAlias package (which is the
+ original application using aliasing routines).
+
+ The options which map directly to packet aliasing options are:
+
+ -unregistered_only [yes|no]
+ -log [yes|no]
+ -deny_incoming [yes|no]
+ -use_sockets [yes|no]
+ -same_ports [yes|no]
+
+ The short option names are the same as in previous
+ releases.
+
+ - Command line parser rewritten to provide more flexible
+ way to support new packet aliasing options.
+
+ - Support for natd.cf configuration file has been added.
+
+ - SIGHUP no longer causes problems when running without
+ interface name option.
+
+ - When using -interface command line option, routing socket
+ is optionally listened for interface address changes. This
+ mode is activated by -dynamic option.
+
+ - Directory tree reorganized, alias package is now a library.
+
+ - Manual page written by Brian Somers <brian@awfulhak.org> added.
+ - README file updated.
+
+* Version 1.5
+
+ - Support for sending ICMP 'need fragmentation' messages
+ when packet size exceeds mtu size of outgoing network interface.
+
+ - ipfw rule example in manual page fixed.
+
+* Version 1.6
+
+ - Upgrade to new packet aliasing engine (2.1)
+ - redirect_port and redirect_address configuration
+ parameters added.
+ - It is no longer necessary to quote complex parameter values
+ in command line.
+ - Manual page fixed (same_port -> same_ports).
+
+* Version 1.7
+
+ - A bug in command-line parsing fixed (it appeared due
+ to changes made in 1.6).
+
+* Version 1.8
+
+ - Fixed problems with -dynamic option.
+ - Added /var/run/natd.pid
+
+* Version 1.9
+
+ - Changes to manual page by
+ Brian Somers <brian@awfulhak.org> integrated.
+ - Checksum for incoming packets is always recalculated
+ for FreeBSD 2.2 and never recalculated for newer
+ versions. This should fix the problem with wrong
+ checksum of fragmented packets.
+ - Buffer space problem found by Sergio Lenzi <lenzi@bsi.com.br>
+ fixed. Natd now waits with select(2) for buffer space
+ to become available if write fails.
+ - Packet aliasing library upgraded to 2.2.
+
+* Version 1.10
+
+ - Ignored incoming packets are now dropped when
+ deny_incoming option is set to yes.
+ - Packet aliasing library upgraded to 2.4.
+
+* Version 1.11
+
+ - Code cleanup work done in FreeBSD-current development merged.
+ - Port numbers are now unsigned as they should always have been.
+
+* Version 1.12
+
+ - Typos in comment fixed. Copyright message added to
+ source & header files that were missing it.
+ - A small patch to libalias to make static NAT work correctly.
+
+* Version 2.0
+
+ - Upgrade to libalias 3.0 which gives:
+ - Transparent proxy support.
+ - permanent_link is now obsolete, use redirect_port instead.
+ - Drop support for early FreeBSD 2.2 versions
+ - If separate input & output sockets are being used
+ use them to find out packet direction instead of
+ normal mechanism. This can be handy in complex environments
+ with multiple interfaces.
+ - libalias is no longer part of this distribution.
+ - New sample configuration file
+ from Ted Mittelstaedt <tedm@portsoft.com>.
+ - PPTP redirect support by Dru Nelson <dnelson@redwoodsoft.com> added.
+ - Logging enhancements from Martin Machacek <mm@i.cz> added.
diff --git a/sbin/natd/Makefile b/sbin/natd/Makefile
new file mode 100644
index 0000000..44e5b6f
--- /dev/null
+++ b/sbin/natd/Makefile
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+PROG = natd
+SRCS = natd.c icmp.c
+WARNS?= 3
+LIBADD = alias
+MAN = natd.8
+
+.include <bsd.prog.mk>
diff --git a/sbin/natd/README b/sbin/natd/README
new file mode 100644
index 0000000..d2e8a9a
--- /dev/null
+++ b/sbin/natd/README
@@ -0,0 +1,50 @@
+# $FreeBSD$
+
+ A Network Address Translation Daemon for FreeBSD
+
+
+1. WHAT IS NATD ?
+
+ This is a simple daemon based on FreeBSD divert sockets
+ which performs network address translation (or masquerading)
+ for IP packets (see related RFCs 1631 and 1918).
+ It is based on packet aliasing package (see README.alias)
+ written by Charles Mott <cm@linktel.net>.
+
+ This package works with any network interface (doesn't have
+ to be ppp). I run it on a computer having two ethernet cards,
+ one connected to internet and the other one to local network.
+
+2. GETTING IT RUNNING
+
+ 1) Get FreeBSD 2.2 - I think the divert sockets are
+ not available on earlier versions,
+
+ 2) Compile this software by executing "make".
+
+ 3) Install the software by executing "make install".
+
+ 4) See man natd for further instructions.
+
+3. FTP SITES FOR NATD
+
+ This package is available at ftp://ftp.suutari.iki.fi/pub/natd.
+
+4. AUTHORS
+
+ This program is the result of the efforts of many people
+ at different times:
+
+ Archie Cobbs <archie@whistle.com> Divert sockets
+ Charles Mott <cm@linktel.net> Packet aliasing engine
+ Eivind Eklund <eivind@dimaga.com> Packet aliasing engine
+ Ari Suutari <suutari@iki.fi> Natd
+ Brian Somers <brian@awfulhak.org> Manual page, glue and
+ bunch of good ideas.
+
+ Happy Networking - comments and fixes are welcome!
+
+ Ari S. (suutari@iki.fi)
+
+
+
diff --git a/sbin/natd/icmp.c b/sbin/natd/icmp.c
new file mode 100644
index 0000000..1509b96
--- /dev/null
+++ b/sbin/natd/icmp.c
@@ -0,0 +1,127 @@
+/*
+ * natd - Network Address Translation Daemon for FreeBSD.
+ *
+ * This software is provided free of charge, with no
+ * warranty of any kind, either expressed or implied.
+ * Use at your own risk.
+ *
+ * You may copy, modify and distribute this software (icmp.c) freely.
+ *
+ * Ari Suutari <suutari@iki.fi>
+ *
+ * $FreeBSD$
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <errno.h>
+#include <signal.h>
+
+#include <netdb.h>
+
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <netinet/ip_icmp.h>
+#include <machine/in_cksum.h>
+
+#include <alias.h>
+
+#include "natd.h"
+
+int SendNeedFragIcmp (int sock, struct ip* failedDgram, int mtu)
+{
+ char icmpBuf[IP_MAXPACKET];
+ struct ip* ip;
+ struct icmp* icmp;
+ int icmpLen;
+ int failBytes;
+ int failHdrLen;
+ struct sockaddr_in addr;
+ int wrote;
+ struct in_addr swap;
+/*
+ * Don't send error if packet is
+ * not the first fragment.
+ */
+ if (ntohs (failedDgram->ip_off) & ~(IP_MF | IP_DF))
+ return 0;
+/*
+ * Dont respond if failed datagram is ICMP.
+ */
+ if (failedDgram->ip_p == IPPROTO_ICMP)
+ return 0;
+/*
+ * Start building the message.
+ */
+ ip = (struct ip*) icmpBuf;
+ icmp = (struct icmp*) (icmpBuf + sizeof (struct ip));
+/*
+ * Complete ICMP part.
+ */
+ icmp->icmp_type = ICMP_UNREACH;
+ icmp->icmp_code = ICMP_UNREACH_NEEDFRAG;
+ icmp->icmp_cksum = 0;
+ icmp->icmp_void = 0;
+ icmp->icmp_nextmtu = htons (mtu);
+/*
+ * Copy header + 64 bits of original datagram.
+ */
+ failHdrLen = (failedDgram->ip_hl << 2);
+ failBytes = failedDgram->ip_len - failHdrLen;
+ if (failBytes > 8)
+ failBytes = 8;
+
+ failBytes += failHdrLen;
+ icmpLen = ICMP_MINLEN + failBytes;
+
+ memcpy (&icmp->icmp_ip, failedDgram, failBytes);
+/*
+ * Calculate checksum.
+ */
+ icmp->icmp_cksum = LibAliasInternetChecksum (mla, (u_short*) icmp,
+ icmpLen);
+/*
+ * Add IP header using old IP header as template.
+ */
+ memcpy (ip, failedDgram, sizeof (struct ip));
+
+ ip->ip_v = 4;
+ ip->ip_hl = 5;
+ ip->ip_len = htons (sizeof (struct ip) + icmpLen);
+ ip->ip_p = IPPROTO_ICMP;
+ ip->ip_tos = 0;
+
+ swap = ip->ip_dst;
+ ip->ip_dst = ip->ip_src;
+ ip->ip_src = swap;
+
+ LibAliasIn (mla, (char*) ip, IP_MAXPACKET);
+
+ addr.sin_family = AF_INET;
+ addr.sin_addr = ip->ip_dst;
+ addr.sin_port = 0;
+/*
+ * Put packet into processing queue.
+ */
+ wrote = sendto (sock,
+ icmp,
+ icmpLen,
+ 0,
+ (struct sockaddr*) &addr,
+ sizeof addr);
+
+ if (wrote != icmpLen)
+ Warn ("Cannot send ICMP message.");
+
+ return 1;
+}
+
+
diff --git a/sbin/natd/natd.8 b/sbin/natd/natd.8
new file mode 100644
index 0000000..7ccfbf7
--- /dev/null
+++ b/sbin/natd/natd.8
@@ -0,0 +1,830 @@
+.\" $FreeBSD$
+.Dd June 23, 2008
+.Dt NATD 8
+.Os
+.Sh NAME
+.Nm natd
+.Nd Network Address Translation daemon
+.Sh SYNOPSIS
+.Nm
+.Bk -words
+.Op Fl unregistered_only | u
+.Op Fl log | l
+.Op Fl proxy_only
+.Op Fl reverse
+.Op Fl deny_incoming | d
+.Op Fl use_sockets | s
+.Op Fl same_ports | m
+.Op Fl verbose | v
+.Op Fl dynamic
+.Op Fl in_port | i Ar port
+.Op Fl out_port | o Ar port
+.Op Fl port | p Ar port
+.Op Fl alias_address | a Ar address
+.Op Fl target_address | t Ar address
+.Op Fl interface | n Ar interface
+.Op Fl proxy_rule Ar proxyspec
+.Op Fl redirect_port Ar linkspec
+.Op Fl redirect_proto Ar linkspec
+.Op Fl redirect_address Ar linkspec
+.Op Fl config | f Ar configfile
+.Op Fl instance Ar instancename
+.Op Fl globalport Ar port
+.Op Fl log_denied
+.Op Fl log_facility Ar facility_name
+.Op Fl punch_fw Ar firewall_range
+.Op Fl skinny_port Ar port
+.Op Fl log_ipfw_denied
+.Op Fl pid_file | P Ar pidfile
+.Op Fl exit_delay | P Ar ms
+.Ek
+.Sh DESCRIPTION
+The
+.Nm
+utility provides a Network Address Translation facility for use
+with
+.Xr divert 4
+sockets under
+.Fx .
+.Pp
+(If you need NAT on a PPP link,
+.Xr ppp 8
+provides the
+.Fl nat
+option that gives most of the
+.Nm
+functionality, and uses the same
+.Xr libalias 3
+library.)
+.Pp
+The
+.Nm
+utility normally runs in the background as a daemon.
+It is passed raw IP packets as they travel into and out of the machine,
+and will possibly change these before re-injecting them back into the
+IP packet stream.
+.Pp
+It changes all packets destined for another host so that their source
+IP address is that of the current machine.
+For each packet changed in this manner, an internal table entry is
+created to record this fact.
+The source port number is also changed to indicate the table entry
+applying to the packet.
+Packets that are received with a target IP of the current host are
+checked against this internal table.
+If an entry is found, it is used to determine the correct target IP
+address and port to place in the packet.
+.Pp
+The following command line options are available:
+.Bl -tag -width Fl
+.It Fl log | l
+Log various aliasing statistics and information to the file
+.Pa /var/log/alias.log .
+This file is truncated each time
+.Nm
+is started.
+.It Fl deny_incoming | d
+Do not pass incoming packets that have no
+entry in the internal translation table.
+.Pp
+If this option is not used, then such a packet will be altered
+using the rules in
+.Fl target_address
+below, and the entry will be made in the internal translation table.
+.It Fl log_denied
+Log denied incoming packets via
+.Xr syslog 3
+(see also
+.Fl log_facility ) .
+.It Fl log_facility Ar facility_name
+Use specified log facility when logging information via
+.Xr syslog 3 .
+Argument
+.Ar facility_name
+is one of the keywords specified in
+.Xr syslog.conf 5 .
+.It Fl use_sockets | s
+Allocate a
+.Xr socket 2
+in order to establish an FTP data or IRC DCC send connection.
+This option uses more system resources, but guarantees successful
+connections when port numbers conflict.
+.It Fl same_ports | m
+Try to keep the same port number when altering outgoing packets.
+With this option, protocols such as RPC will have a better chance
+of working.
+If it is not possible to maintain the port number, it will be silently
+changed as per normal.
+.It Fl verbose | v
+Do not call
+.Xr daemon 3
+on startup.
+Instead, stay attached to the controlling terminal and display all packet
+alterations to the standard output.
+This option should only be used for debugging purposes.
+.It Fl unregistered_only | u
+Only alter outgoing packets with an
+.Em unregistered
+source address.
+According to RFC 1918, unregistered source addresses are 10.0.0.0/8,
+172.16.0.0/12 and 192.168.0.0/16.
+.It Fl redirect_port Ar proto Xo
+.Ar targetIP Ns : Ns Xo
+.Ar targetPORT Ns Oo - Ns Ar targetPORT Oc Xc
+.Oo Ar aliasIP Ns : Oc Ns Xo
+.Ar aliasPORT Ns Oo - Ns Ar aliasPORT Oc Xc
+.Oo Ar remoteIP Ns Oo : Ns
+.Ar remotePORT Ns Op - Ns Ar remotePORT
+.Oc Oc
+.Xc
+Redirect incoming connections arriving to given port(s) to another host
+and port(s).
+Argument
+.Ar proto
+is either
+.Ar tcp
+or
+.Ar udp ,
+.Ar targetIP
+is the desired target IP address,
+.Ar targetPORT
+is the desired target port number or range,
+.Ar aliasPORT
+is the requested port number or range, and
+.Ar aliasIP
+is the aliasing address.
+Arguments
+.Ar remoteIP
+and
+.Ar remotePORT
+can be used to specify the connection more accurately if necessary.
+If
+.Ar remotePORT
+is not specified, it is assumed to be all ports.
+.Pp
+Arguments
+.Ar targetIP , aliasIP
+and
+.Ar remoteIP
+can be given as IP addresses or as hostnames.
+The
+.Ar targetPORT , aliasPORT
+and
+.Ar remotePORT
+ranges need not be the same numerically, but must have the same size.
+When
+.Ar targetPORT , aliasPORT
+or
+.Ar remotePORT
+specifies a singular value (not a range), it can be given as a service
+name that is searched for in the
+.Xr services 5
+database.
+.Pp
+For example, the argument
+.Pp
+.Dl Ar tcp inside1:telnet 6666
+.Pp
+means that incoming TCP packets destined for port 6666 on this machine
+will be sent to the telnet port on the inside1 machine.
+.Pp
+.Dl Ar tcp inside2:2300-2399 3300-3399
+.Pp
+will redirect incoming connections on ports 3300-3399 to host
+inside2, ports 2300-2399.
+The mapping is 1:1 meaning port 3300 maps to 2300, 3301 maps to 2301, etc.
+.It Fl redirect_proto Ar proto localIP Oo
+.Ar publicIP Op Ar remoteIP
+.Oc
+Redirect incoming IP packets of protocol
+.Ar proto
+(see
+.Xr protocols 5 )
+destined for
+.Ar publicIP
+address to a
+.Ar localIP
+address and vice versa.
+.Pp
+If
+.Ar publicIP
+is not specified, then the default aliasing address is used.
+If
+.Ar remoteIP
+is specified, then only packets coming from/to
+.Ar remoteIP
+will match the rule.
+.It Fl redirect_address Ar localIP publicIP
+Redirect traffic for public IP address to a machine on the local
+network.
+This function is known as
+.Em static NAT .
+Normally static NAT is useful if your ISP has allocated a small block
+of IP addresses to you, but it can even be used in the case of single
+address:
+.Pp
+.Dl Ar redirect_address 10.0.0.8 0.0.0.0
+.Pp
+The above command would redirect all incoming traffic
+to machine 10.0.0.8.
+.Pp
+If several address aliases specify the same public address
+as follows
+.Bd -literal -offset indent
+redirect_address 192.168.0.2 public_addr
+redirect_address 192.168.0.3 public_addr
+redirect_address 192.168.0.4 public_addr
+.Ed
+.Pp
+the incoming traffic will be directed to the last
+translated local address (192.168.0.4), but outgoing
+traffic from the first two addresses will still be aliased
+to appear from the specified
+.Ar public_addr .
+.It Fl redirect_port Ar proto Xo
+.Ar targetIP Ns : Ns Xo
+.Ar targetPORT Ns Oo , Ns
+.Ar targetIP Ns : Ns Xo
+.Ar targetPORT Ns Oo , Ns
+.Ar ...\&
+.Oc Xc Oc Xc
+.Oo Ar aliasIP Ns : Oc Ns Xo
+.Ar aliasPORT
+.Xc
+.Oo Ar remoteIP Ns
+.Op : Ns Ar remotePORT
+.Oc
+.Xc
+.It Fl redirect_address Xo
+.Ar localIP Ns Oo , Ns
+.Ar localIP Ns Oo , Ns
+.Ar ...\&
+.Oc Oc
+.Ar publicIP
+.Xc
+These forms of
+.Fl redirect_port
+and
+.Fl redirect_address
+are used to transparently offload network load on a single server and
+distribute the load across a pool of servers.
+This function is known as
+.Em LSNAT
+(RFC 2391).
+For example, the argument
+.Pp
+.Dl Ar tcp www1:http,www2:http,www3:http www:http
+.Pp
+means that incoming HTTP requests for host www will be transparently
+redirected to one of the www1, www2 or www3, where a host is selected
+simply on a round-robin basis, without regard to load on the net.
+.It Fl dynamic
+If the
+.Fl n
+or
+.Fl interface
+option is used,
+.Nm
+will monitor the routing socket for alterations to the
+.Ar interface
+passed.
+If the interface's IP address is changed,
+.Nm
+will dynamically alter its concept of the alias address.
+.It Fl in_port | i Ar port
+Read from and write to
+.Xr divert 4
+port
+.Ar port ,
+treating all packets as
+.Dq incoming .
+.It Fl out_port | o Ar port
+Read from and write to
+.Xr divert 4
+port
+.Ar port ,
+treating all packets as
+.Dq outgoing .
+.It Fl port | p Ar port
+Read from and write to
+.Xr divert 4
+port
+.Ar port ,
+distinguishing packets as
+.Dq incoming
+or
+.Dq outgoing
+using the rules specified in
+.Xr divert 4 .
+If
+.Ar port
+is not numeric, it is searched for in the
+.Xr services 5
+database.
+If this option is not specified, the divert port named
+.Ar natd
+will be used as a default.
+.It Fl alias_address | a Ar address
+Use
+.Ar address
+as the aliasing address.
+Either this or the
+.Fl interface
+option must be used (but not both),
+if the
+.Fl proxy_only
+option is not specified.
+The specified address is usually the address assigned to the
+.Dq public
+network interface.
+.Pp
+All data passing
+.Em out
+will be rewritten with a source address equal to
+.Ar address .
+All data coming
+.Em in
+will be checked to see if it matches any already-aliased outgoing
+connection.
+If it does, the packet is altered accordingly.
+If not, all
+.Fl redirect_port ,
+.Fl redirect_proto
+and
+.Fl redirect_address
+assignments are checked and actioned.
+If no other action can be made and if
+.Fl deny_incoming
+is not specified, the packet is delivered to the local machine
+using the rules specified in
+.Fl target_address
+option below.
+.It Fl t | target_address Ar address
+Set the target address.
+When an incoming packet not associated with any pre-existing link
+arrives at the host machine, it will be sent to the specified
+.Ar address .
+.Pp
+The target address may be set to
+.Ar 255.255.255.255 ,
+in which case all new incoming packets go to the alias address set by
+.Fl alias_address
+or
+.Fl interface .
+.Pp
+If this option is not used, or called with the argument
+.Ar 0.0.0.0 ,
+then all new incoming packets go to the address specified in
+the packet.
+This allows external machines to talk directly to internal machines if
+they can route packets to the machine in question.
+.It Fl interface | n Ar interface
+Use
+.Ar interface
+to determine the aliasing address.
+If there is a possibility that the IP address associated with
+.Ar interface
+may change, the
+.Fl dynamic
+option should also be used.
+If this option is not specified, the
+.Fl alias_address
+option must be used.
+.Pp
+The specified
+.Ar interface
+is usually the
+.Dq public
+(or
+.Dq external )
+network interface.
+.It Fl config | f Ar file
+Read configuration from
+.Ar file .
+A
+.Ar file
+should contain a list of options, one per line, in the same form
+as the long form of the above command line options.
+For example, the line
+.Pp
+.Dl alias_address 158.152.17.1
+.Pp
+would specify an alias address of 158.152.17.1.
+Options that do not take an argument are specified with an argument of
+.Ar yes
+or
+.Ar no
+in the configuration file.
+For example, the line
+.Pp
+.Dl log yes
+.Pp
+is synonymous with
+.Fl log .
+.Pp
+Options can be divided to several sections.
+Each section applies to own
+.Nm
+instance.
+This ability allows to configure one
+.Nm
+process for several NAT instances.
+The first instance that always exists is a "default" instance.
+Each another instance should begin with
+.Pp
+.Dl instance Ar instance_name
+.Pp
+At the next should be placed a configuration option.
+Example:
+.Pp
+.Dl \&# default instance
+.Dl port 8668
+.Dl alias_address 158.152.17.1
+.Pp
+.Dl \&# second instance
+.Dl instance dsl1
+.Dl port 8888
+.Dl alias_address 192.168.0.1
+.Pp
+Trailing spaces and empty lines are ignored.
+A
+.Ql \&#
+sign will mark the rest of the line as a comment.
+.It Fl instance Ar instancename
+This option switches command line options processing to configure instance
+.Ar instancename
+(creating it if necessary) till the next
+.Fl instance
+option or end of command line.
+It is easier to set up multiple instances in the configuration file
+specified with the
+.Fl config
+option rather than on a command line.
+.It Fl globalport Ar port
+Read from and write to
+.Xr divert 4
+port
+.Ar port ,
+treating all packets as
+.Dq outgoing .
+This option is intended to be used with multiple instances:
+packets received on this port are checked against
+internal translation tables of every configured instance.
+If an entry is found, packet is aliased according to that entry.
+If no entry was found in any of the instances, packet is passed
+unchanged, and no new entry will be created.
+See the section
+.Sx MULTIPLE INSTANCES
+for more details.
+.It Fl reverse
+This option makes
+.Nm
+reverse the way it handles
+.Dq incoming
+and
+.Dq outgoing
+packets, allowing it to operate on the
+.Dq internal
+network interface rather than the
+.Dq external
+one.
+.Pp
+This can be useful in some transparent proxying situations
+when outgoing traffic is redirected to the local machine
+and
+.Nm
+is running on the internal interface (it usually runs on the
+external interface).
+.It Fl proxy_only
+Force
+.Nm
+to perform transparent proxying only.
+Normal address translation is not performed.
+.It Fl proxy_rule Xo
+.Op Ar type encode_ip_hdr | encode_tcp_stream
+.Ar port xxxx
+.Ar server a.b.c.d:yyyy
+.Xc
+Enable transparent proxying.
+Outgoing TCP packets with the given port going through this
+host to any other host are redirected to the given server and port.
+Optionally, the original target address can be encoded into the packet.
+Use
+.Ar encode_ip_hdr
+to put this information into the IP option field or
+.Ar encode_tcp_stream
+to inject the data into the beginning of the TCP stream.
+.It Fl punch_fw Xo
+.Ar basenumber Ns : Ns Ar count
+.Xc
+This option directs
+.Nm
+to
+.Dq punch holes
+in an
+.Xr ipfirewall 4
+based firewall for FTP/IRC DCC connections.
+This is done dynamically by installing temporary firewall rules which
+allow a particular connection (and only that connection) to go through
+the firewall.
+The rules are removed once the corresponding connection terminates.
+.Pp
+A maximum of
+.Ar count
+rules starting from the rule number
+.Ar basenumber
+will be used for punching firewall holes.
+The range will be cleared for all rules on startup.
+This option has no effect when the kernel is in security
+level 3, see
+.Xr init 8
+for more information.
+.It Fl skinny_port Ar port
+This option allows you to specify the TCP port used for
+the Skinny Station protocol.
+Skinny is used by Cisco IP phones to communicate with
+Cisco Call Managers to set up voice over IP calls.
+By default, Skinny aliasing is not performed.
+The typical port value for Skinny is 2000.
+.It Fl log_ipfw_denied
+Log when a packet cannot be re-injected because an
+.Xr ipfw 8
+rule blocks it.
+This is the default with
+.Fl verbose .
+.It Fl pid_file | P Ar file
+Specify an alternate file in which to store the process ID.
+The default is
+.Pa /var/run/natd.pid .
+.It Fl exit_delay Ar ms
+Specify delay in ms before daemon exit after signal.
+The default is
+.Pa 10000 .
+.El
+.Sh RUNNING NATD
+The following steps are necessary before attempting to run
+.Nm :
+.Bl -enum
+.It
+Build a custom kernel with the following options:
+.Bd -literal -offset indent
+options IPFIREWALL
+options IPDIVERT
+.Ed
+.Pp
+Refer to the handbook for detailed instructions on building a custom
+kernel.
+.It
+Ensure that your machine is acting as a gateway.
+This can be done by specifying the line
+.Pp
+.Dl gateway_enable=YES
+.Pp
+in the
+.Pa /etc/rc.conf
+file or using the command
+.Pp
+.Dl "sysctl net.inet.ip.forwarding=1"
+.It
+If you use the
+.Fl interface
+option, make sure that your interface is already configured.
+If, for example, you wish to specify
+.Ql tun0
+as your
+.Ar interface ,
+and you are using
+.Xr ppp 8
+on that interface, you must make sure that you start
+.Nm ppp
+prior to starting
+.Nm .
+.El
+.Pp
+Running
+.Nm
+is fairly straight forward.
+The line
+.Pp
+.Dl natd -interface ed0
+.Pp
+should suffice in most cases (substituting the correct interface name).
+Please check
+.Xr rc.conf 5
+on how to configure it to be started automatically during boot.
+Once
+.Nm
+is running, you must ensure that traffic is diverted to
+.Nm :
+.Bl -enum
+.It
+You will need to adjust the
+.Pa /etc/rc.firewall
+script to taste.
+If you are not interested in having a firewall, the
+following lines will do:
+.Bd -literal -offset indent
+/sbin/ipfw -f flush
+/sbin/ipfw add divert natd all from any to any via ed0
+/sbin/ipfw add pass all from any to any
+.Ed
+.Pp
+The second line depends on your interface (change
+.Ql ed0
+as appropriate).
+.Pp
+You should be aware of the fact that, with these firewall settings,
+everyone on your local network can fake his source-address using your
+host as gateway.
+If there are other hosts on your local network, you are strongly
+encouraged to create firewall rules that only allow traffic to and
+from trusted hosts.
+.Pp
+If you specify real firewall rules, it is best to specify line 2 at
+the start of the script so that
+.Nm
+sees all packets before they are dropped by the firewall.
+.Pp
+After translation by
+.Nm ,
+packets re-enter the firewall at the rule number following the rule number
+that caused the diversion (not the next rule if there are several at the
+same number).
+.It
+Enable your firewall by setting
+.Pp
+.Dl firewall_enable=YES
+.Pp
+in
+.Pa /etc/rc.conf .
+This tells the system startup scripts to run the
+.Pa /etc/rc.firewall
+script.
+If you do not wish to reboot now, just run this by hand from the console.
+NEVER run this from a remote session unless you put it into the background.
+If you do, you will lock yourself out after the flush takes place, and
+execution of
+.Pa /etc/rc.firewall
+will stop at this point - blocking all accesses permanently.
+Running the script in the background should be enough to prevent this
+disaster.
+.El
+.Sh MULTIPLE INSTANCES
+It is not so uncommon to have a need of aliasing to several external IP
+addresses.
+While this traditionally was achieved by running several
+.Nm
+processes with independent configurations,
+.Nm
+can have multiple aliasing instances in a single process,
+also allowing them to be not so independent of each other.
+For example, let us see a common task of load balancing two
+channels to different providers on a machine with two external
+interfaces
+.Ql sis0
+(with IP 1.2.3.4) and
+.Ql sis2
+(with IP 2.3.4.5):
+.Bd -literal -offset indent
+ net 1.2.3.0/24
+1.2.3.1 ------------------ sis0
+(router) (1.2.3.4)
+ net 10.0.0.0/24
+ sis1 ------------------- 10.0.0.2
+ (10.0.0.1)
+ net 2.3.4.0/24
+2.3.4.1 ------------------ sis2
+(router) (2.3.4.5)
+.Ed
+.Pp
+Default route is out via
+.Ql sis0 .
+.Pp
+Interior machine (10.0.0.2) is accessible on TCP port 122 through
+both exterior IPs, and outgoing connections choose a path randomly
+between
+.Ql sis0
+and
+.Ql sis2 .
+.Pp
+The way this works is that
+.Pa natd.conf
+builds two instances of the aliasing engine.
+.Pp
+In addition to these instances' private
+.Xr divert 4
+sockets, a third socket called the
+.Dq globalport
+is created; packets sent to
+.Nm
+via this one will be matched against all instances and translated
+if an existing entry is found, and unchanged if no entry is found.
+The following lines are placed into
+.Pa /etc/natd.conf :
+.Bd -literal -offset indent
+log
+deny_incoming
+verbose
+
+instance default
+interface sis0
+port 1000
+redirect_port tcp 10.0.0.2:122 122
+
+instance sis2
+interface sis2
+port 2000
+redirect_port tcp 10.0.0.2:122 122
+
+globalport 3000
+.Ed
+.Pp
+And the following
+.Xr ipfw 8
+rules are used:
+.Bd -literal -offset indent
+ipfw -f flush
+
+ipfw add allow ip from any to any via sis1
+
+ipfw add skipto 1000 ip from any to any in via sis0
+ipfw add skipto 2000 ip from any to any out via sis0
+ipfw add skipto 3000 ip from any to any in via sis2
+ipfw add skipto 4000 ip from any to any out via sis2
+
+ipfw add 1000 count ip from any to any
+
+ipfw add divert 1000 ip from any to any
+ipfw add allow ip from any to any
+
+ipfw add 2000 count ip from any to any
+
+ipfw add divert 3000 ip from any to any
+
+ipfw add allow ip from 1.2.3.4 to any
+ipfw add skipto 5000 ip from 2.3.4.5 to any
+
+ipfw add prob .5 skipto 4000 ip from any to any
+
+ipfw add divert 1000 ip from any to any
+ipfw add allow ip from any to any
+
+ipfw add 3000 count ip from any to any
+
+ipfw add divert 2000 ip from any to any
+ipfw add allow ip from any to any
+
+ipfw add 4000 count ip from any to any
+
+ipfw add divert 2000 ip from any to any
+
+ipfw add 5000 fwd 2.3.4.1 ip from 2.3.4.5 to not 2.3.4.0/24
+ipfw add allow ip from any to any
+.Ed
+.Pp
+Here the packet from internal network to Internet goes out via
+.Ql sis0
+(rule number 2000) and gets caught by the
+.Ic globalport
+socket (3000).
+After that, either a match is found in a translation table
+of one of the two instances, or the packet is passed to one
+of the two other
+.Xr divert 4
+ports (1000 or 2000), with equal probability.
+This ensures that load balancing is done on a per-flow basis
+(i.e., packets from a single TCP connection always flow through the
+same interface).
+Translated packets with source IP of a non-default interface
+.Pq Ql sis2
+are forwarded to the appropriate router on that interface.
+.Sh SEE ALSO
+.Xr libalias 3 ,
+.Xr divert 4 ,
+.Xr protocols 5 ,
+.Xr rc.conf 5 ,
+.Xr services 5 ,
+.Xr syslog.conf 5 ,
+.Xr init 8 ,
+.Xr ipfw 8 ,
+.Xr ppp 8
+.Sh AUTHORS
+This program is the result of the efforts of many people at different
+times:
+.Pp
+.An Archie Cobbs Aq Mt archie@FreeBSD.org
+(divert sockets)
+.An Charles Mott Aq Mt cm@linktel.net
+(packet aliasing)
+.An Eivind Eklund Aq Mt perhaps@yes.no
+(IRC support & misc additions)
+.An Ari Suutari Aq Mt suutari@iki.fi
+(natd)
+.An Dru Nelson Aq Mt dnelson@redwoodsoft.com
+(early PPTP support)
+.An Brian Somers Aq Mt brian@awfulhak.org
+(glue)
+.An Ruslan Ermilov Aq Mt ru@FreeBSD.org
+(natd, packet aliasing, glue)
+.An Poul-Henning Kamp Aq Mt phk@FreeBSD.org
+(multiple instances)
diff --git a/sbin/natd/natd.c b/sbin/natd/natd.c
new file mode 100644
index 0000000..959e1ee
--- /dev/null
+++ b/sbin/natd/natd.c
@@ -0,0 +1,2041 @@
+/*
+ * natd - Network Address Translation Daemon for FreeBSD.
+ *
+ * This software is provided free of charge, with no
+ * warranty of any kind, either expressed or implied.
+ * Use at your own risk.
+ *
+ * You may copy, modify and distribute this software (natd.c) freely.
+ *
+ * Ari Suutari <suutari@iki.fi>
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#define SYSLOG_NAMES
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+#include <sys/time.h>
+#include <sys/queue.h>
+
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <machine/in_cksum.h>
+#include <netinet/tcp.h>
+#include <netinet/udp.h>
+#include <netinet/ip_icmp.h>
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <net/route.h>
+#include <arpa/inet.h>
+
+#include <alias.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <netdb.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "natd.h"
+
+struct instance {
+ const char *name;
+ struct libalias *la;
+ LIST_ENTRY(instance) list;
+
+ int ifIndex;
+ int assignAliasAddr;
+ char* ifName;
+ int logDropped;
+ u_short inPort;
+ u_short outPort;
+ u_short inOutPort;
+ struct in_addr aliasAddr;
+ int ifMTU;
+ int aliasOverhead;
+ int dropIgnoredIncoming;
+ int divertIn;
+ int divertOut;
+ int divertInOut;
+};
+
+static LIST_HEAD(, instance) root = LIST_HEAD_INITIALIZER(root);
+
+struct libalias *mla;
+static struct instance *mip;
+static int ninstance = 1;
+
+/*
+ * Default values for input and output
+ * divert socket ports.
+ */
+
+#define DEFAULT_SERVICE "natd"
+
+/*
+ * Definition of a port range, and macros to deal with values.
+ * FORMAT: HI 16-bits == first port in range, 0 == all ports.
+ * LO 16-bits == number of ports in range
+ * NOTES: - Port values are not stored in network byte order.
+ */
+
+typedef u_long port_range;
+
+#define GETLOPORT(x) ((x) >> 0x10)
+#define GETNUMPORTS(x) ((x) & 0x0000ffff)
+#define GETHIPORT(x) (GETLOPORT((x)) + GETNUMPORTS((x)))
+
+/* Set y to be the low-port value in port_range variable x. */
+#define SETLOPORT(x,y) ((x) = ((x) & 0x0000ffff) | ((y) << 0x10))
+
+/* Set y to be the number of ports in port_range variable x. */
+#define SETNUMPORTS(x,y) ((x) = ((x) & 0xffff0000) | (y))
+
+/*
+ * Function prototypes.
+ */
+
+static void DoAliasing (int fd, int direction);
+static void DaemonMode (void);
+static void HandleRoutingInfo (int fd);
+static void Usage (void);
+static char* FormatPacket (struct ip*);
+static void PrintPacket (struct ip*);
+static void SyslogPacket (struct ip*, int priority, const char *label);
+static int SetAliasAddressFromIfName (const char *ifName);
+static void InitiateShutdown (int);
+static void Shutdown (int);
+static void RefreshAddr (int);
+static void ParseOption (const char* option, const char* parms);
+static void ReadConfigFile (const char* fileName);
+static void SetupPortRedirect (const char* parms);
+static void SetupProtoRedirect(const char* parms);
+static void SetupAddressRedirect (const char* parms);
+static void StrToAddr (const char* str, struct in_addr* addr);
+static u_short StrToPort (const char* str, const char* proto);
+static int StrToPortRange (const char* str, const char* proto, port_range *portRange);
+static int StrToProto (const char* str);
+static int StrToAddrAndPortRange (const char* str, struct in_addr* addr, char* proto, port_range *portRange);
+static void ParseArgs (int argc, char** argv);
+static void SetupPunchFW(const char *strValue);
+static void SetupSkinnyPort(const char *strValue);
+static void NewInstance(const char *name);
+static void DoGlobal (int fd);
+static int CheckIpfwRulenum(unsigned int rnum);
+
+/*
+ * Globals.
+ */
+
+static int verbose;
+static int background;
+static int running;
+static int logFacility;
+
+static int dynamicMode;
+static int icmpSock;
+static int logIpfwDenied;
+static const char* pidName;
+static int routeSock;
+static int globalPort;
+static int divertGlobal;
+static int exitDelay;
+
+
+int main (int argc, char** argv)
+{
+ struct sockaddr_in addr;
+ fd_set readMask;
+ int fdMax;
+ int rval;
+/*
+ * Initialize packet aliasing software.
+ * Done already here to be able to alter option bits
+ * during command line and configuration file processing.
+ */
+ NewInstance("default");
+
+/*
+ * Parse options.
+ */
+ verbose = 0;
+ background = 0;
+ running = 1;
+ dynamicMode = 0;
+ logFacility = LOG_DAEMON;
+ logIpfwDenied = -1;
+ pidName = PIDFILE;
+ routeSock = -1;
+ icmpSock = -1;
+ fdMax = -1;
+ divertGlobal = -1;
+ exitDelay = EXIT_DELAY;
+
+ ParseArgs (argc, argv);
+/*
+ * Log ipfw(8) denied packets by default in verbose mode.
+ */
+ if (logIpfwDenied == -1)
+ logIpfwDenied = verbose;
+/*
+ * Open syslog channel.
+ */
+ openlog ("natd", LOG_CONS | LOG_PID | (verbose ? LOG_PERROR : 0),
+ logFacility);
+
+ LIST_FOREACH(mip, &root, list) {
+ mla = mip->la;
+/*
+ * If not doing the transparent proxying only,
+ * check that valid aliasing address has been given.
+ */
+ if (mip->aliasAddr.s_addr == INADDR_NONE && mip->ifName == NULL &&
+ !(LibAliasSetMode(mla, 0,0) & PKT_ALIAS_PROXY_ONLY))
+ errx (1, "instance %s: aliasing address not given", mip->name);
+
+ if (mip->aliasAddr.s_addr != INADDR_NONE && mip->ifName != NULL)
+ errx (1, "both alias address and interface "
+ "name are not allowed");
+/*
+ * Check that valid port number is known.
+ */
+ if (mip->inPort != 0 || mip->outPort != 0)
+ if (mip->inPort == 0 || mip->outPort == 0)
+ errx (1, "both input and output ports are required");
+
+ if (mip->inPort == 0 && mip->outPort == 0 && mip->inOutPort == 0)
+ ParseOption ("port", DEFAULT_SERVICE);
+
+/*
+ * Check if ignored packets should be dropped.
+ */
+ mip->dropIgnoredIncoming = LibAliasSetMode (mla, 0, 0);
+ mip->dropIgnoredIncoming &= PKT_ALIAS_DENY_INCOMING;
+/*
+ * Create divert sockets. Use only one socket if -p was specified
+ * on command line. Otherwise, create separate sockets for
+ * outgoing and incoming connnections.
+ */
+ if (mip->inOutPort) {
+
+ mip->divertInOut = socket (PF_INET, SOCK_RAW, IPPROTO_DIVERT);
+ if (mip->divertInOut == -1)
+ Quit ("Unable to create divert socket.");
+ if (mip->divertInOut > fdMax)
+ fdMax = mip->divertInOut;
+
+ mip->divertIn = -1;
+ mip->divertOut = -1;
+/*
+ * Bind socket.
+ */
+
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = INADDR_ANY;
+ addr.sin_port = mip->inOutPort;
+
+ if (bind (mip->divertInOut,
+ (struct sockaddr*) &addr,
+ sizeof addr) == -1)
+ Quit ("Unable to bind divert socket.");
+ }
+ else {
+
+ mip->divertIn = socket (PF_INET, SOCK_RAW, IPPROTO_DIVERT);
+ if (mip->divertIn == -1)
+ Quit ("Unable to create incoming divert socket.");
+ if (mip->divertIn > fdMax)
+ fdMax = mip->divertIn;
+
+
+ mip->divertOut = socket (PF_INET, SOCK_RAW, IPPROTO_DIVERT);
+ if (mip->divertOut == -1)
+ Quit ("Unable to create outgoing divert socket.");
+ if (mip->divertOut > fdMax)
+ fdMax = mip->divertOut;
+
+ mip->divertInOut = -1;
+
+/*
+ * Bind divert sockets.
+ */
+
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = INADDR_ANY;
+ addr.sin_port = mip->inPort;
+
+ if (bind (mip->divertIn,
+ (struct sockaddr*) &addr,
+ sizeof addr) == -1)
+ Quit ("Unable to bind incoming divert socket.");
+
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = INADDR_ANY;
+ addr.sin_port = mip->outPort;
+
+ if (bind (mip->divertOut,
+ (struct sockaddr*) &addr,
+ sizeof addr) == -1)
+ Quit ("Unable to bind outgoing divert socket.");
+ }
+/*
+ * Create routing socket if interface name specified and in dynamic mode.
+ */
+ if (mip->ifName) {
+ if (dynamicMode) {
+
+ if (routeSock == -1)
+ routeSock = socket (PF_ROUTE, SOCK_RAW, 0);
+ if (routeSock == -1)
+ Quit ("Unable to create routing info socket.");
+ if (routeSock > fdMax)
+ fdMax = routeSock;
+
+ mip->assignAliasAddr = 1;
+ }
+ else {
+ do {
+ rval = SetAliasAddressFromIfName (mip->ifName);
+ if (background == 0 || dynamicMode == 0)
+ break;
+ if (rval == EAGAIN)
+ sleep(1);
+ } while (rval == EAGAIN);
+ if (rval != 0)
+ exit(1);
+ }
+ }
+
+ }
+ if (globalPort) {
+
+ divertGlobal = socket (PF_INET, SOCK_RAW, IPPROTO_DIVERT);
+ if (divertGlobal == -1)
+ Quit ("Unable to create divert socket.");
+ if (divertGlobal > fdMax)
+ fdMax = divertGlobal;
+
+/*
+* Bind socket.
+*/
+
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = INADDR_ANY;
+ addr.sin_port = globalPort;
+
+ if (bind (divertGlobal,
+ (struct sockaddr*) &addr,
+ sizeof addr) == -1)
+ Quit ("Unable to bind global divert socket.");
+ }
+/*
+ * Create socket for sending ICMP messages.
+ */
+ icmpSock = socket (AF_INET, SOCK_RAW, IPPROTO_ICMP);
+ if (icmpSock == -1)
+ Quit ("Unable to create ICMP socket.");
+
+/*
+ * And disable reads for the socket, otherwise it slowly fills
+ * up with received icmps which we do not use.
+ */
+ shutdown(icmpSock, SHUT_RD);
+
+/*
+ * Become a daemon unless verbose mode was requested.
+ */
+ if (!verbose)
+ DaemonMode ();
+/*
+ * Catch signals to manage shutdown and
+ * refresh of interface address.
+ */
+ siginterrupt(SIGTERM, 1);
+ siginterrupt(SIGHUP, 1);
+ if (exitDelay)
+ signal(SIGTERM, InitiateShutdown);
+ else
+ signal(SIGTERM, Shutdown);
+ signal (SIGHUP, RefreshAddr);
+/*
+ * Set alias address if it has been given.
+ */
+ mip = LIST_FIRST(&root); /* XXX: simon */
+ LIST_FOREACH(mip, &root, list) {
+ mla = mip->la;
+ if (mip->aliasAddr.s_addr != INADDR_NONE)
+ LibAliasSetAddress (mla, mip->aliasAddr);
+ }
+
+ while (running) {
+ mip = LIST_FIRST(&root); /* XXX: simon */
+
+ if (mip->divertInOut != -1 && !mip->ifName && ninstance == 1) {
+/*
+ * When using only one socket, just call
+ * DoAliasing repeatedly to process packets.
+ */
+ DoAliasing (mip->divertInOut, DONT_KNOW);
+ continue;
+ }
+/*
+ * Build read mask from socket descriptors to select.
+ */
+ FD_ZERO (&readMask);
+/*
+ * Check if new packets are available.
+ */
+ LIST_FOREACH(mip, &root, list) {
+ if (mip->divertIn != -1)
+ FD_SET (mip->divertIn, &readMask);
+
+ if (mip->divertOut != -1)
+ FD_SET (mip->divertOut, &readMask);
+
+ if (mip->divertInOut != -1)
+ FD_SET (mip->divertInOut, &readMask);
+ }
+/*
+ * Routing info is processed always.
+ */
+ if (routeSock != -1)
+ FD_SET (routeSock, &readMask);
+
+ if (divertGlobal != -1)
+ FD_SET (divertGlobal, &readMask);
+
+ if (select (fdMax + 1,
+ &readMask,
+ NULL,
+ NULL,
+ NULL) == -1) {
+
+ if (errno == EINTR)
+ continue;
+
+ Quit ("Select failed.");
+ }
+
+ if (divertGlobal != -1)
+ if (FD_ISSET (divertGlobal, &readMask))
+ DoGlobal (divertGlobal);
+ LIST_FOREACH(mip, &root, list) {
+ mla = mip->la;
+ if (mip->divertIn != -1)
+ if (FD_ISSET (mip->divertIn, &readMask))
+ DoAliasing (mip->divertIn, INPUT);
+
+ if (mip->divertOut != -1)
+ if (FD_ISSET (mip->divertOut, &readMask))
+ DoAliasing (mip->divertOut, OUTPUT);
+
+ if (mip->divertInOut != -1)
+ if (FD_ISSET (mip->divertInOut, &readMask))
+ DoAliasing (mip->divertInOut, DONT_KNOW);
+
+ }
+ if (routeSock != -1)
+ if (FD_ISSET (routeSock, &readMask))
+ HandleRoutingInfo (routeSock);
+ }
+
+ if (background)
+ unlink (pidName);
+
+ return 0;
+}
+
+static void DaemonMode(void)
+{
+ FILE* pidFile;
+
+ daemon (0, 0);
+ background = 1;
+
+ pidFile = fopen (pidName, "w");
+ if (pidFile) {
+
+ fprintf (pidFile, "%d\n", getpid ());
+ fclose (pidFile);
+ }
+}
+
+static void ParseArgs (int argc, char** argv)
+{
+ int arg;
+ char* opt;
+ char parmBuf[256];
+ int len; /* bounds checking */
+
+ for (arg = 1; arg < argc; arg++) {
+
+ opt = argv[arg];
+ if (*opt != '-') {
+
+ warnx ("invalid option %s", opt);
+ Usage ();
+ }
+
+ parmBuf[0] = '\0';
+ len = 0;
+
+ while (arg < argc - 1) {
+
+ if (argv[arg + 1][0] == '-')
+ break;
+
+ if (len) {
+ strncat (parmBuf, " ", sizeof(parmBuf) - (len + 1));
+ len += strlen(parmBuf + len);
+ }
+
+ ++arg;
+ strncat (parmBuf, argv[arg], sizeof(parmBuf) - (len + 1));
+ len += strlen(parmBuf + len);
+
+ }
+
+ ParseOption (opt + 1, (len ? parmBuf : NULL));
+
+ }
+}
+
+static void DoGlobal (int fd)
+{
+ int bytes;
+ int origBytes;
+ char buf[IP_MAXPACKET];
+ struct sockaddr_in addr;
+ int wrote;
+ socklen_t addrSize;
+ struct ip* ip;
+ char msgBuf[80];
+
+/*
+ * Get packet from socket.
+ */
+ addrSize = sizeof addr;
+ origBytes = recvfrom (fd,
+ buf,
+ sizeof buf,
+ 0,
+ (struct sockaddr*) &addr,
+ &addrSize);
+
+ if (origBytes == -1) {
+
+ if (errno != EINTR)
+ Warn ("read from divert socket failed");
+
+ return;
+ }
+
+#if 0
+ if (mip->assignAliasAddr) {
+ if (SetAliasAddressFromIfName (mip->ifName) != 0)
+ exit(1);
+ mip->assignAliasAddr = 0;
+ }
+#endif
+/*
+ * This is an IP packet.
+ */
+ ip = (struct ip*) buf;
+
+ if (verbose) {
+/*
+ * Print packet direction and protocol type.
+ */
+ printf ("Glb ");
+
+ switch (ip->ip_p) {
+ case IPPROTO_TCP:
+ printf ("[TCP] ");
+ break;
+
+ case IPPROTO_UDP:
+ printf ("[UDP] ");
+ break;
+
+ case IPPROTO_ICMP:
+ printf ("[ICMP] ");
+ break;
+
+ default:
+ printf ("[%d] ", ip->ip_p);
+ break;
+ }
+/*
+ * Print addresses.
+ */
+ PrintPacket (ip);
+ }
+
+ LIST_FOREACH(mip, &root, list) {
+ mla = mip->la;
+ if (LibAliasOutTry (mla, buf, IP_MAXPACKET, 0) != PKT_ALIAS_IGNORED)
+ break;
+ }
+/*
+ * Length might have changed during aliasing.
+ */
+ bytes = ntohs (ip->ip_len);
+/*
+ * Update alias overhead size for outgoing packets.
+ */
+ if (mip != NULL && bytes - origBytes > mip->aliasOverhead)
+ mip->aliasOverhead = bytes - origBytes;
+
+ if (verbose) {
+
+/*
+ * Print addresses after aliasing.
+ */
+ printf (" aliased to\n");
+ printf (" ");
+ PrintPacket (ip);
+ printf ("\n");
+ }
+
+/*
+ * Put packet back for processing.
+ */
+ wrote = sendto (fd,
+ buf,
+ bytes,
+ 0,
+ (struct sockaddr*) &addr,
+ sizeof addr);
+
+ if (wrote != bytes) {
+
+ if (errno == EMSGSIZE) {
+
+ if (mip->ifMTU != -1)
+ SendNeedFragIcmp (icmpSock,
+ (struct ip*) buf,
+ mip->ifMTU - mip->aliasOverhead);
+ }
+ else if (errno == EACCES && logIpfwDenied) {
+
+ sprintf (msgBuf, "failed to write packet back");
+ Warn (msgBuf);
+ }
+ }
+}
+
+
+static void DoAliasing (int fd, int direction)
+{
+ int bytes;
+ int origBytes;
+ char buf[IP_MAXPACKET];
+ struct sockaddr_in addr;
+ int wrote;
+ int status;
+ socklen_t addrSize;
+ struct ip* ip;
+ char msgBuf[80];
+ int rval;
+
+ if (mip->assignAliasAddr) {
+ do {
+ rval = SetAliasAddressFromIfName (mip->ifName);
+ if (background == 0 || dynamicMode == 0)
+ break;
+ if (rval == EAGAIN)
+ sleep(1);
+ } while (rval == EAGAIN);
+ if (rval != 0)
+ exit(1);
+ mip->assignAliasAddr = 0;
+ }
+/*
+ * Get packet from socket.
+ */
+ addrSize = sizeof addr;
+ origBytes = recvfrom (fd,
+ buf,
+ sizeof buf,
+ 0,
+ (struct sockaddr*) &addr,
+ &addrSize);
+
+ if (origBytes == -1) {
+
+ if (errno != EINTR)
+ Warn ("read from divert socket failed");
+
+ return;
+ }
+/*
+ * This is an IP packet.
+ */
+ ip = (struct ip*) buf;
+ if (direction == DONT_KNOW) {
+ if (addr.sin_addr.s_addr == INADDR_ANY)
+ direction = OUTPUT;
+ else
+ direction = INPUT;
+ }
+
+ if (verbose) {
+/*
+ * Print packet direction and protocol type.
+ */
+ printf (direction == OUTPUT ? "Out " : "In ");
+ if (ninstance > 1)
+ printf ("{%s}", mip->name);
+
+ switch (ip->ip_p) {
+ case IPPROTO_TCP:
+ printf ("[TCP] ");
+ break;
+
+ case IPPROTO_UDP:
+ printf ("[UDP] ");
+ break;
+
+ case IPPROTO_ICMP:
+ printf ("[ICMP] ");
+ break;
+
+ default:
+ printf ("[%d] ", ip->ip_p);
+ break;
+ }
+/*
+ * Print addresses.
+ */
+ PrintPacket (ip);
+ }
+
+ if (direction == OUTPUT) {
+/*
+ * Outgoing packets. Do aliasing.
+ */
+ LibAliasOut (mla, buf, IP_MAXPACKET);
+ }
+ else {
+
+/*
+ * Do aliasing.
+ */
+ status = LibAliasIn (mla, buf, IP_MAXPACKET);
+ if (status == PKT_ALIAS_IGNORED &&
+ mip->dropIgnoredIncoming) {
+
+ if (verbose)
+ printf (" dropped.\n");
+
+ if (mip->logDropped)
+ SyslogPacket (ip, LOG_WARNING, "denied");
+
+ return;
+ }
+ }
+/*
+ * Length might have changed during aliasing.
+ */
+ bytes = ntohs (ip->ip_len);
+/*
+ * Update alias overhead size for outgoing packets.
+ */
+ if (direction == OUTPUT &&
+ bytes - origBytes > mip->aliasOverhead)
+ mip->aliasOverhead = bytes - origBytes;
+
+ if (verbose) {
+
+/*
+ * Print addresses after aliasing.
+ */
+ printf (" aliased to\n");
+ printf (" ");
+ PrintPacket (ip);
+ printf ("\n");
+ }
+
+/*
+ * Put packet back for processing.
+ */
+ wrote = sendto (fd,
+ buf,
+ bytes,
+ 0,
+ (struct sockaddr*) &addr,
+ sizeof addr);
+
+ if (wrote != bytes) {
+
+ if (errno == EMSGSIZE) {
+
+ if (direction == OUTPUT &&
+ mip->ifMTU != -1)
+ SendNeedFragIcmp (icmpSock,
+ (struct ip*) buf,
+ mip->ifMTU - mip->aliasOverhead);
+ }
+ else if (errno == EACCES && logIpfwDenied) {
+
+ sprintf (msgBuf, "failed to write packet back");
+ Warn (msgBuf);
+ }
+ }
+}
+
+static void HandleRoutingInfo (int fd)
+{
+ int bytes;
+ struct if_msghdr ifMsg;
+/*
+ * Get packet from socket.
+ */
+ bytes = read (fd, &ifMsg, sizeof ifMsg);
+ if (bytes == -1) {
+
+ Warn ("read from routing socket failed");
+ return;
+ }
+
+ if (ifMsg.ifm_version != RTM_VERSION) {
+
+ Warn ("unexpected packet read from routing socket");
+ return;
+ }
+
+ if (verbose)
+ printf ("Routing message %#x received.\n", ifMsg.ifm_type);
+
+ if ((ifMsg.ifm_type == RTM_NEWADDR || ifMsg.ifm_type == RTM_IFINFO)) {
+ LIST_FOREACH(mip, &root, list) {
+ mla = mip->la;
+ if (ifMsg.ifm_index == mip->ifIndex) {
+ if (verbose)
+ printf("Interface address/MTU has probably changed.\n");
+ mip->assignAliasAddr = 1;
+ }
+ }
+ }
+}
+
+static void PrintPacket (struct ip* ip)
+{
+ printf ("%s", FormatPacket (ip));
+}
+
+static void SyslogPacket (struct ip* ip, int priority, const char *label)
+{
+ syslog (priority, "%s %s", label, FormatPacket (ip));
+}
+
+static char* FormatPacket (struct ip* ip)
+{
+ static char buf[256];
+ struct tcphdr* tcphdr;
+ struct udphdr* udphdr;
+ struct icmp* icmphdr;
+ char src[20];
+ char dst[20];
+
+ strcpy (src, inet_ntoa (ip->ip_src));
+ strcpy (dst, inet_ntoa (ip->ip_dst));
+
+ switch (ip->ip_p) {
+ case IPPROTO_TCP:
+ tcphdr = (struct tcphdr*) ((char*) ip + (ip->ip_hl << 2));
+ sprintf (buf, "[TCP] %s:%d -> %s:%d",
+ src,
+ ntohs (tcphdr->th_sport),
+ dst,
+ ntohs (tcphdr->th_dport));
+ break;
+
+ case IPPROTO_UDP:
+ udphdr = (struct udphdr*) ((char*) ip + (ip->ip_hl << 2));
+ sprintf (buf, "[UDP] %s:%d -> %s:%d",
+ src,
+ ntohs (udphdr->uh_sport),
+ dst,
+ ntohs (udphdr->uh_dport));
+ break;
+
+ case IPPROTO_ICMP:
+ icmphdr = (struct icmp*) ((char*) ip + (ip->ip_hl << 2));
+ sprintf (buf, "[ICMP] %s -> %s %u(%u)",
+ src,
+ dst,
+ icmphdr->icmp_type,
+ icmphdr->icmp_code);
+ break;
+
+ default:
+ sprintf (buf, "[%d] %s -> %s ", ip->ip_p, src, dst);
+ break;
+ }
+
+ return buf;
+}
+
+static int
+SetAliasAddressFromIfName(const char *ifn)
+{
+ size_t needed;
+ int mib[6];
+ char *buf, *lim, *next;
+ struct if_msghdr *ifm;
+ struct ifa_msghdr *ifam;
+ struct sockaddr_dl *sdl;
+ struct sockaddr_in *sin;
+
+ mib[0] = CTL_NET;
+ mib[1] = PF_ROUTE;
+ mib[2] = 0;
+ mib[3] = AF_INET; /* Only IP addresses please */
+ mib[4] = NET_RT_IFLIST;
+ mib[5] = 0; /* ifIndex??? */
+/*
+ * Get interface data.
+ */
+ if (sysctl(mib, 6, NULL, &needed, NULL, 0) == -1)
+ err(1, "iflist-sysctl-estimate");
+ if ((buf = malloc(needed)) == NULL)
+ errx(1, "malloc failed");
+ if (sysctl(mib, 6, buf, &needed, NULL, 0) == -1 && errno != ENOMEM)
+ err(1, "iflist-sysctl-get");
+ lim = buf + needed;
+/*
+ * Loop through interfaces until one with
+ * given name is found. This is done to
+ * find correct interface index for routing
+ * message processing.
+ */
+ mip->ifIndex = 0;
+ next = buf;
+ while (next < lim) {
+ ifm = (struct if_msghdr *)next;
+ next += ifm->ifm_msglen;
+ if (ifm->ifm_version != RTM_VERSION) {
+ if (verbose)
+ warnx("routing message version %d "
+ "not understood", ifm->ifm_version);
+ continue;
+ }
+ if (ifm->ifm_type == RTM_IFINFO) {
+ sdl = (struct sockaddr_dl *)(ifm + 1);
+ if (strlen(ifn) == sdl->sdl_nlen &&
+ strncmp(ifn, sdl->sdl_data, sdl->sdl_nlen) == 0) {
+ mip->ifIndex = ifm->ifm_index;
+ mip->ifMTU = ifm->ifm_data.ifi_mtu;
+ break;
+ }
+ }
+ }
+ if (!mip->ifIndex)
+ errx(1, "unknown interface name %s", ifn);
+/*
+ * Get interface address.
+ */
+ sin = NULL;
+ while (next < lim) {
+ ifam = (struct ifa_msghdr *)next;
+ next += ifam->ifam_msglen;
+ if (ifam->ifam_version != RTM_VERSION) {
+ if (verbose)
+ warnx("routing message version %d "
+ "not understood", ifam->ifam_version);
+ continue;
+ }
+ if (ifam->ifam_type != RTM_NEWADDR)
+ break;
+ if (ifam->ifam_addrs & RTA_IFA) {
+ int i;
+ char *cp = (char *)(ifam + 1);
+
+ for (i = 1; i < RTA_IFA; i <<= 1)
+ if (ifam->ifam_addrs & i)
+ cp += SA_SIZE((struct sockaddr *)cp);
+ if (((struct sockaddr *)cp)->sa_family == AF_INET) {
+ sin = (struct sockaddr_in *)cp;
+ break;
+ }
+ }
+ }
+ if (sin == NULL) {
+ warnx("%s: cannot get interface address", ifn);
+ free(buf);
+ return EAGAIN;
+ }
+
+ LibAliasSetAddress(mla, sin->sin_addr);
+ syslog(LOG_INFO, "Aliasing to %s, mtu %d bytes",
+ inet_ntoa(sin->sin_addr), mip->ifMTU);
+
+ free(buf);
+
+ return 0;
+}
+
+void Quit (const char* msg)
+{
+ Warn (msg);
+ exit (1);
+}
+
+void Warn (const char* msg)
+{
+ if (background)
+ syslog (LOG_ALERT, "%s (%m)", msg);
+ else
+ warn ("%s", msg);
+}
+
+static void RefreshAddr (int sig __unused)
+{
+ LibAliasRefreshModules();
+ if (mip != NULL && mip->ifName != NULL)
+ mip->assignAliasAddr = 1;
+}
+
+static void InitiateShutdown (int sig __unused)
+{
+/*
+ * Start timer to allow kernel gracefully
+ * shutdown existing connections when system
+ * is shut down.
+ */
+ siginterrupt(SIGALRM, 1);
+ signal (SIGALRM, Shutdown);
+ ualarm(exitDelay*1000, 1000);
+}
+
+static void Shutdown (int sig __unused)
+{
+ running = 0;
+}
+
+/*
+ * Different options recognized by this program.
+ */
+
+enum Option {
+
+ LibAliasOption,
+ Instance,
+ Verbose,
+ InPort,
+ OutPort,
+ Port,
+ GlobalPort,
+ AliasAddress,
+ TargetAddress,
+ InterfaceName,
+ RedirectPort,
+ RedirectProto,
+ RedirectAddress,
+ ConfigFile,
+ DynamicMode,
+ ProxyRule,
+ LogDenied,
+ LogFacility,
+ PunchFW,
+ SkinnyPort,
+ LogIpfwDenied,
+ PidFile,
+ ExitDelay
+};
+
+enum Param {
+
+ YesNo,
+ Numeric,
+ String,
+ None,
+ Address,
+ Service
+};
+
+/*
+ * Option information structure (used by ParseOption).
+ */
+
+struct OptionInfo {
+
+ enum Option type;
+ int packetAliasOpt;
+ enum Param parm;
+ const char* parmDescription;
+ const char* description;
+ const char* name;
+ const char* shortName;
+};
+
+/*
+ * Table of known options.
+ */
+
+static struct OptionInfo optionTable[] = {
+
+ { LibAliasOption,
+ PKT_ALIAS_UNREGISTERED_ONLY,
+ YesNo,
+ "[yes|no]",
+ "alias only unregistered addresses",
+ "unregistered_only",
+ "u" },
+
+ { LibAliasOption,
+ PKT_ALIAS_LOG,
+ YesNo,
+ "[yes|no]",
+ "enable logging",
+ "log",
+ "l" },
+
+ { LibAliasOption,
+ PKT_ALIAS_PROXY_ONLY,
+ YesNo,
+ "[yes|no]",
+ "proxy only",
+ "proxy_only",
+ NULL },
+
+ { LibAliasOption,
+ PKT_ALIAS_REVERSE,
+ YesNo,
+ "[yes|no]",
+ "operate in reverse mode",
+ "reverse",
+ NULL },
+
+ { LibAliasOption,
+ PKT_ALIAS_DENY_INCOMING,
+ YesNo,
+ "[yes|no]",
+ "allow incoming connections",
+ "deny_incoming",
+ "d" },
+
+ { LibAliasOption,
+ PKT_ALIAS_USE_SOCKETS,
+ YesNo,
+ "[yes|no]",
+ "use sockets to inhibit port conflict",
+ "use_sockets",
+ "s" },
+
+ { LibAliasOption,
+ PKT_ALIAS_SAME_PORTS,
+ YesNo,
+ "[yes|no]",
+ "try to keep original port numbers for connections",
+ "same_ports",
+ "m" },
+
+ { Verbose,
+ 0,
+ YesNo,
+ "[yes|no]",
+ "verbose mode, dump packet information",
+ "verbose",
+ "v" },
+
+ { DynamicMode,
+ 0,
+ YesNo,
+ "[yes|no]",
+ "dynamic mode, automatically detect interface address changes",
+ "dynamic",
+ NULL },
+
+ { InPort,
+ 0,
+ Service,
+ "number|service_name",
+ "set port for incoming packets",
+ "in_port",
+ "i" },
+
+ { OutPort,
+ 0,
+ Service,
+ "number|service_name",
+ "set port for outgoing packets",
+ "out_port",
+ "o" },
+
+ { Port,
+ 0,
+ Service,
+ "number|service_name",
+ "set port (defaults to natd/divert)",
+ "port",
+ "p" },
+
+ { GlobalPort,
+ 0,
+ Service,
+ "number|service_name",
+ "set globalport",
+ "globalport",
+ NULL },
+
+ { AliasAddress,
+ 0,
+ Address,
+ "x.x.x.x",
+ "address to use for aliasing",
+ "alias_address",
+ "a" },
+
+ { TargetAddress,
+ 0,
+ Address,
+ "x.x.x.x",
+ "address to use for incoming sessions",
+ "target_address",
+ "t" },
+
+ { InterfaceName,
+ 0,
+ String,
+ "network_if_name",
+ "take aliasing address from interface",
+ "interface",
+ "n" },
+
+ { ProxyRule,
+ 0,
+ String,
+ "[type encode_ip_hdr|encode_tcp_stream] port xxxx server "
+ "a.b.c.d:yyyy",
+ "add transparent proxying / destination NAT",
+ "proxy_rule",
+ NULL },
+
+ { RedirectPort,
+ 0,
+ String,
+ "tcp|udp local_addr:local_port_range[,...] [public_addr:]public_port_range"
+ " [remote_addr[:remote_port_range]]",
+ "redirect a port (or ports) for incoming traffic",
+ "redirect_port",
+ NULL },
+
+ { RedirectProto,
+ 0,
+ String,
+ "proto local_addr [public_addr] [remote_addr]",
+ "redirect packets of a given proto",
+ "redirect_proto",
+ NULL },
+
+ { RedirectAddress,
+ 0,
+ String,
+ "local_addr[,...] public_addr",
+ "define mapping between local and public addresses",
+ "redirect_address",
+ NULL },
+
+ { ConfigFile,
+ 0,
+ String,
+ "file_name",
+ "read options from configuration file",
+ "config",
+ "f" },
+
+ { LogDenied,
+ 0,
+ YesNo,
+ "[yes|no]",
+ "enable logging of denied incoming packets",
+ "log_denied",
+ NULL },
+
+ { LogFacility,
+ 0,
+ String,
+ "facility",
+ "name of syslog facility to use for logging",
+ "log_facility",
+ NULL },
+
+ { PunchFW,
+ 0,
+ String,
+ "basenumber:count",
+ "punch holes in the firewall for incoming FTP/IRC DCC connections",
+ "punch_fw",
+ NULL },
+
+ { SkinnyPort,
+ 0,
+ String,
+ "port",
+ "set the TCP port for use with the Skinny Station protocol",
+ "skinny_port",
+ NULL },
+
+ { LogIpfwDenied,
+ 0,
+ YesNo,
+ "[yes|no]",
+ "log packets converted by natd, but denied by ipfw",
+ "log_ipfw_denied",
+ NULL },
+
+ { PidFile,
+ 0,
+ String,
+ "file_name",
+ "store PID in an alternate file",
+ "pid_file",
+ "P" },
+ { Instance,
+ 0,
+ String,
+ "instance name",
+ "name of aliasing engine instance",
+ "instance",
+ NULL },
+ { ExitDelay,
+ 0,
+ Numeric,
+ "ms",
+ "delay in ms before daemon exit after signal",
+ "exit_delay",
+ NULL },
+};
+
+static void ParseOption (const char* option, const char* parms)
+{
+ int i;
+ struct OptionInfo* info;
+ int yesNoValue;
+ int aliasValue;
+ int numValue;
+ u_short uNumValue;
+ const char* strValue;
+ struct in_addr addrValue;
+ int max;
+ char* end;
+ const CODE* fac_record = NULL;
+/*
+ * Find option from table.
+ */
+ max = sizeof (optionTable) / sizeof (struct OptionInfo);
+ for (i = 0, info = optionTable; i < max; i++, info++) {
+
+ if (!strcmp (info->name, option))
+ break;
+
+ if (info->shortName)
+ if (!strcmp (info->shortName, option))
+ break;
+ }
+
+ if (i >= max) {
+
+ warnx ("unknown option %s", option);
+ Usage ();
+ }
+
+ uNumValue = 0;
+ yesNoValue = 0;
+ numValue = 0;
+ strValue = NULL;
+/*
+ * Check parameters.
+ */
+ switch (info->parm) {
+ case YesNo:
+ if (!parms)
+ parms = "yes";
+
+ if (!strcmp (parms, "yes"))
+ yesNoValue = 1;
+ else
+ if (!strcmp (parms, "no"))
+ yesNoValue = 0;
+ else
+ errx (1, "%s needs yes/no parameter", option);
+ break;
+
+ case Service:
+ if (!parms)
+ errx (1, "%s needs service name or "
+ "port number parameter",
+ option);
+
+ uNumValue = StrToPort (parms, "divert");
+ break;
+
+ case Numeric:
+ if (parms)
+ numValue = strtol (parms, &end, 10);
+ else
+ end = NULL;
+
+ if (end == parms)
+ errx (1, "%s needs numeric parameter", option);
+ break;
+
+ case String:
+ strValue = parms;
+ if (!strValue)
+ errx (1, "%s needs parameter", option);
+ break;
+
+ case None:
+ if (parms)
+ errx (1, "%s does not take parameters", option);
+ break;
+
+ case Address:
+ if (!parms)
+ errx (1, "%s needs address/host parameter", option);
+
+ StrToAddr (parms, &addrValue);
+ break;
+ }
+
+ switch (info->type) {
+ case LibAliasOption:
+
+ aliasValue = yesNoValue ? info->packetAliasOpt : 0;
+ LibAliasSetMode (mla, aliasValue, info->packetAliasOpt);
+ break;
+
+ case Verbose:
+ verbose = yesNoValue;
+ break;
+
+ case DynamicMode:
+ dynamicMode = yesNoValue;
+ break;
+
+ case InPort:
+ mip->inPort = uNumValue;
+ break;
+
+ case OutPort:
+ mip->outPort = uNumValue;
+ break;
+
+ case Port:
+ mip->inOutPort = uNumValue;
+ break;
+
+ case GlobalPort:
+ globalPort = uNumValue;
+ break;
+
+ case AliasAddress:
+ memcpy (&mip->aliasAddr, &addrValue, sizeof (struct in_addr));
+ break;
+
+ case TargetAddress:
+ LibAliasSetTarget(mla, addrValue);
+ break;
+
+ case RedirectPort:
+ SetupPortRedirect (strValue);
+ break;
+
+ case RedirectProto:
+ SetupProtoRedirect(strValue);
+ break;
+
+ case RedirectAddress:
+ SetupAddressRedirect (strValue);
+ break;
+
+ case ProxyRule:
+ LibAliasProxyRule (mla, strValue);
+ break;
+
+ case InterfaceName:
+ if (mip->ifName)
+ free (mip->ifName);
+
+ mip->ifName = strdup (strValue);
+ break;
+
+ case ConfigFile:
+ ReadConfigFile (strValue);
+ break;
+
+ case LogDenied:
+ mip->logDropped = yesNoValue;
+ break;
+
+ case LogFacility:
+
+ fac_record = facilitynames;
+ while (fac_record->c_name != NULL) {
+
+ if (!strcmp (fac_record->c_name, strValue)) {
+
+ logFacility = fac_record->c_val;
+ break;
+
+ }
+ else
+ fac_record++;
+ }
+
+ if(fac_record->c_name == NULL)
+ errx(1, "Unknown log facility name: %s", strValue);
+
+ break;
+
+ case PunchFW:
+ SetupPunchFW(strValue);
+ break;
+
+ case SkinnyPort:
+ SetupSkinnyPort(strValue);
+ break;
+
+ case LogIpfwDenied:
+ logIpfwDenied = yesNoValue;
+ break;
+
+ case PidFile:
+ pidName = strdup (strValue);
+ break;
+ case Instance:
+ NewInstance(strValue);
+ break;
+ case ExitDelay:
+ if (numValue < 0 || numValue > MAX_EXIT_DELAY)
+ errx(1, "Incorrect exit delay: %d", numValue);
+ exitDelay = numValue;
+ break;
+ }
+}
+
+void ReadConfigFile (const char* fileName)
+{
+ FILE* file;
+ char *buf;
+ size_t len;
+ char *ptr, *p;
+ char* option;
+
+ file = fopen (fileName, "r");
+ if (!file)
+ err(1, "cannot open config file %s", fileName);
+
+ while ((buf = fgetln(file, &len)) != NULL) {
+ if (buf[len - 1] == '\n')
+ buf[len - 1] = '\0';
+ else
+ errx(1, "config file format error: "
+ "last line should end with newline");
+
+/*
+ * Check for comments, strip off trailing spaces.
+ */
+ if ((ptr = strchr(buf, '#')))
+ *ptr = '\0';
+ for (ptr = buf; isspace(*ptr); ++ptr)
+ continue;
+ if (*ptr == '\0')
+ continue;
+ for (p = strchr(buf, '\0'); isspace(*--p);)
+ continue;
+ *++p = '\0';
+
+/*
+ * Extract option name.
+ */
+ option = ptr;
+ while (*ptr && !isspace (*ptr))
+ ++ptr;
+
+ if (*ptr != '\0') {
+
+ *ptr = '\0';
+ ++ptr;
+ }
+/*
+ * Skip white space between name and parms.
+ */
+ while (*ptr && isspace (*ptr))
+ ++ptr;
+
+ ParseOption (option, *ptr ? ptr : NULL);
+ }
+
+ fclose (file);
+}
+
+static void Usage(void)
+{
+ int i;
+ int max;
+ struct OptionInfo* info;
+
+ fprintf (stderr, "Recognized options:\n\n");
+
+ max = sizeof (optionTable) / sizeof (struct OptionInfo);
+ for (i = 0, info = optionTable; i < max; i++, info++) {
+
+ fprintf (stderr, "-%-20s %s\n", info->name,
+ info->parmDescription);
+
+ if (info->shortName)
+ fprintf (stderr, "-%-20s %s\n", info->shortName,
+ info->parmDescription);
+
+ fprintf (stderr, " %s\n\n", info->description);
+ }
+
+ exit (1);
+}
+
+void SetupPortRedirect (const char* parms)
+{
+ char *buf;
+ char* ptr;
+ char* serverPool;
+ struct in_addr localAddr;
+ struct in_addr publicAddr;
+ struct in_addr remoteAddr;
+ port_range portRange;
+ u_short localPort = 0;
+ u_short publicPort = 0;
+ u_short remotePort = 0;
+ u_short numLocalPorts = 0;
+ u_short numPublicPorts = 0;
+ u_short numRemotePorts = 0;
+ int proto;
+ char* protoName;
+ char* separator;
+ int i;
+ struct alias_link *aliaslink = NULL;
+
+ buf = strdup (parms);
+ if (!buf)
+ errx (1, "redirect_port: strdup() failed");
+/*
+ * Extract protocol.
+ */
+ protoName = strtok (buf, " \t");
+ if (!protoName)
+ errx (1, "redirect_port: missing protocol");
+
+ proto = StrToProto (protoName);
+/*
+ * Extract local address.
+ */
+ ptr = strtok (NULL, " \t");
+ if (!ptr)
+ errx (1, "redirect_port: missing local address");
+
+ separator = strchr(ptr, ',');
+ if (separator) { /* LSNAT redirection syntax. */
+ localAddr.s_addr = INADDR_NONE;
+ localPort = ~0;
+ numLocalPorts = 1;
+ serverPool = ptr;
+ } else {
+ if ( StrToAddrAndPortRange (ptr, &localAddr, protoName, &portRange) != 0 )
+ errx (1, "redirect_port: invalid local port range");
+
+ localPort = GETLOPORT(portRange);
+ numLocalPorts = GETNUMPORTS(portRange);
+ serverPool = NULL;
+ }
+
+/*
+ * Extract public port and optionally address.
+ */
+ ptr = strtok (NULL, " \t");
+ if (!ptr)
+ errx (1, "redirect_port: missing public port");
+
+ separator = strchr (ptr, ':');
+ if (separator) {
+ if (StrToAddrAndPortRange (ptr, &publicAddr, protoName, &portRange) != 0 )
+ errx (1, "redirect_port: invalid public port range");
+ }
+ else {
+ publicAddr.s_addr = INADDR_ANY;
+ if (StrToPortRange (ptr, protoName, &portRange) != 0)
+ errx (1, "redirect_port: invalid public port range");
+ }
+
+ publicPort = GETLOPORT(portRange);
+ numPublicPorts = GETNUMPORTS(portRange);
+
+/*
+ * Extract remote address and optionally port.
+ */
+ ptr = strtok (NULL, " \t");
+ if (ptr) {
+ separator = strchr (ptr, ':');
+ if (separator) {
+ if (StrToAddrAndPortRange (ptr, &remoteAddr, protoName, &portRange) != 0)
+ errx (1, "redirect_port: invalid remote port range");
+ } else {
+ SETLOPORT(portRange, 0);
+ SETNUMPORTS(portRange, 1);
+ StrToAddr (ptr, &remoteAddr);
+ }
+ }
+ else {
+ SETLOPORT(portRange, 0);
+ SETNUMPORTS(portRange, 1);
+ remoteAddr.s_addr = INADDR_ANY;
+ }
+
+ remotePort = GETLOPORT(portRange);
+ numRemotePorts = GETNUMPORTS(portRange);
+
+/*
+ * Make sure port ranges match up, then add the redirect ports.
+ */
+ if (numLocalPorts != numPublicPorts)
+ errx (1, "redirect_port: port ranges must be equal in size");
+
+ /* Remote port range is allowed to be '0' which means all ports. */
+ if (numRemotePorts != numLocalPorts && (numRemotePorts != 1 || remotePort != 0))
+ errx (1, "redirect_port: remote port must be 0 or equal to local port range in size");
+
+ for (i = 0 ; i < numPublicPorts ; ++i) {
+ /* If remotePort is all ports, set it to 0. */
+ u_short remotePortCopy = remotePort + i;
+ if (numRemotePorts == 1 && remotePort == 0)
+ remotePortCopy = 0;
+
+ aliaslink = LibAliasRedirectPort (mla, localAddr,
+ htons(localPort + i),
+ remoteAddr,
+ htons(remotePortCopy),
+ publicAddr,
+ htons(publicPort + i),
+ proto);
+ }
+
+/*
+ * Setup LSNAT server pool.
+ */
+ if (serverPool != NULL && aliaslink != NULL) {
+ ptr = strtok(serverPool, ",");
+ while (ptr != NULL) {
+ if (StrToAddrAndPortRange(ptr, &localAddr, protoName, &portRange) != 0)
+ errx(1, "redirect_port: invalid local port range");
+
+ localPort = GETLOPORT(portRange);
+ if (GETNUMPORTS(portRange) != 1)
+ errx(1, "redirect_port: local port must be single in this context");
+ LibAliasAddServer(mla, aliaslink, localAddr, htons(localPort));
+ ptr = strtok(NULL, ",");
+ }
+ }
+
+ free (buf);
+}
+
+void
+SetupProtoRedirect(const char* parms)
+{
+ char *buf;
+ char* ptr;
+ struct in_addr localAddr;
+ struct in_addr publicAddr;
+ struct in_addr remoteAddr;
+ int proto;
+ char* protoName;
+ struct protoent *protoent;
+
+ buf = strdup (parms);
+ if (!buf)
+ errx (1, "redirect_port: strdup() failed");
+/*
+ * Extract protocol.
+ */
+ protoName = strtok(buf, " \t");
+ if (!protoName)
+ errx(1, "redirect_proto: missing protocol");
+
+ protoent = getprotobyname(protoName);
+ if (protoent == NULL)
+ errx(1, "redirect_proto: unknown protocol %s", protoName);
+ else
+ proto = protoent->p_proto;
+/*
+ * Extract local address.
+ */
+ ptr = strtok(NULL, " \t");
+ if (!ptr)
+ errx(1, "redirect_proto: missing local address");
+ else
+ StrToAddr(ptr, &localAddr);
+/*
+ * Extract optional public address.
+ */
+ ptr = strtok(NULL, " \t");
+ if (ptr)
+ StrToAddr(ptr, &publicAddr);
+ else
+ publicAddr.s_addr = INADDR_ANY;
+/*
+ * Extract optional remote address.
+ */
+ ptr = strtok(NULL, " \t");
+ if (ptr)
+ StrToAddr(ptr, &remoteAddr);
+ else
+ remoteAddr.s_addr = INADDR_ANY;
+/*
+ * Create aliasing link.
+ */
+ (void)LibAliasRedirectProto(mla, localAddr, remoteAddr, publicAddr,
+ proto);
+
+ free (buf);
+}
+
+void SetupAddressRedirect (const char* parms)
+{
+ char *buf;
+ char* ptr;
+ char* separator;
+ struct in_addr localAddr;
+ struct in_addr publicAddr;
+ char* serverPool;
+ struct alias_link *aliaslink;
+
+ buf = strdup (parms);
+ if (!buf)
+ errx (1, "redirect_port: strdup() failed");
+/*
+ * Extract local address.
+ */
+ ptr = strtok (buf, " \t");
+ if (!ptr)
+ errx (1, "redirect_address: missing local address");
+
+ separator = strchr(ptr, ',');
+ if (separator) { /* LSNAT redirection syntax. */
+ localAddr.s_addr = INADDR_NONE;
+ serverPool = ptr;
+ } else {
+ StrToAddr (ptr, &localAddr);
+ serverPool = NULL;
+ }
+/*
+ * Extract public address.
+ */
+ ptr = strtok (NULL, " \t");
+ if (!ptr)
+ errx (1, "redirect_address: missing public address");
+
+ StrToAddr (ptr, &publicAddr);
+ aliaslink = LibAliasRedirectAddr(mla, localAddr, publicAddr);
+
+/*
+ * Setup LSNAT server pool.
+ */
+ if (serverPool != NULL && aliaslink != NULL) {
+ ptr = strtok(serverPool, ",");
+ while (ptr != NULL) {
+ StrToAddr(ptr, &localAddr);
+ LibAliasAddServer(mla, aliaslink, localAddr, htons(~0));
+ ptr = strtok(NULL, ",");
+ }
+ }
+
+ free (buf);
+}
+
+void StrToAddr (const char* str, struct in_addr* addr)
+{
+ struct hostent* hp;
+
+ if (inet_aton (str, addr))
+ return;
+
+ hp = gethostbyname (str);
+ if (!hp)
+ errx (1, "unknown host %s", str);
+
+ memcpy (addr, hp->h_addr, sizeof (struct in_addr));
+}
+
+u_short StrToPort (const char* str, const char* proto)
+{
+ u_short port;
+ struct servent* sp;
+ char* end;
+
+ port = strtol (str, &end, 10);
+ if (end != str)
+ return htons (port);
+
+ sp = getservbyname (str, proto);
+ if (!sp)
+ errx (1, "%s/%s: unknown service", str, proto);
+
+ return sp->s_port;
+}
+
+int StrToPortRange (const char* str, const char* proto, port_range *portRange)
+{
+ char* sep;
+ struct servent* sp;
+ char* end;
+ u_short loPort;
+ u_short hiPort;
+
+ /* First see if this is a service, return corresponding port if so. */
+ sp = getservbyname (str,proto);
+ if (sp) {
+ SETLOPORT(*portRange, ntohs(sp->s_port));
+ SETNUMPORTS(*portRange, 1);
+ return 0;
+ }
+
+ /* Not a service, see if it's a single port or port range. */
+ sep = strchr (str, '-');
+ if (sep == NULL) {
+ SETLOPORT(*portRange, strtol(str, &end, 10));
+ if (end != str) {
+ /* Single port. */
+ SETNUMPORTS(*portRange, 1);
+ return 0;
+ }
+
+ /* Error in port range field. */
+ errx (1, "%s/%s: unknown service", str, proto);
+ }
+
+ /* Port range, get the values and sanity check. */
+ sscanf (str, "%hu-%hu", &loPort, &hiPort);
+ SETLOPORT(*portRange, loPort);
+ SETNUMPORTS(*portRange, 0); /* Error by default */
+ if (loPort <= hiPort)
+ SETNUMPORTS(*portRange, hiPort - loPort + 1);
+
+ if (GETNUMPORTS(*portRange) == 0)
+ errx (1, "invalid port range %s", str);
+
+ return 0;
+}
+
+
+int StrToProto (const char* str)
+{
+ if (!strcmp (str, "tcp"))
+ return IPPROTO_TCP;
+
+ if (!strcmp (str, "udp"))
+ return IPPROTO_UDP;
+
+ errx (1, "unknown protocol %s. Expected tcp or udp", str);
+}
+
+int StrToAddrAndPortRange (const char* str, struct in_addr* addr, char* proto, port_range *portRange)
+{
+ char* ptr;
+
+ ptr = strchr (str, ':');
+ if (!ptr)
+ errx (1, "%s is missing port number", str);
+
+ *ptr = '\0';
+ ++ptr;
+
+ StrToAddr (str, addr);
+ return StrToPortRange (ptr, proto, portRange);
+}
+
+static void
+SetupPunchFW(const char *strValue)
+{
+ unsigned int base, num;
+
+ if (sscanf(strValue, "%u:%u", &base, &num) != 2)
+ errx(1, "punch_fw: basenumber:count parameter required");
+
+ if (CheckIpfwRulenum(base + num - 1) == -1)
+ errx(1, "punch_fw: basenumber:count parameter should fit "
+ "the maximum allowed rule numbers");
+
+ LibAliasSetFWBase(mla, base, num);
+ (void)LibAliasSetMode(mla, PKT_ALIAS_PUNCH_FW, PKT_ALIAS_PUNCH_FW);
+}
+
+static void
+SetupSkinnyPort(const char *strValue)
+{
+ unsigned int port;
+
+ if (sscanf(strValue, "%u", &port) != 1)
+ errx(1, "skinny_port: port parameter required");
+
+ LibAliasSetSkinnyPort(mla, port);
+}
+
+static void
+NewInstance(const char *name)
+{
+ struct instance *ip;
+
+ LIST_FOREACH(ip, &root, list) {
+ if (!strcmp(ip->name, name)) {
+ mla = ip->la;
+ mip = ip;
+ return;
+ }
+ }
+ ninstance++;
+ ip = calloc(sizeof *ip, 1);
+ ip->name = strdup(name);
+ ip->la = LibAliasInit (ip->la);
+ ip->assignAliasAddr = 0;
+ ip->ifName = NULL;
+ ip->logDropped = 0;
+ ip->inPort = 0;
+ ip->outPort = 0;
+ ip->inOutPort = 0;
+ ip->aliasAddr.s_addr = INADDR_NONE;
+ ip->ifMTU = -1;
+ ip->aliasOverhead = 12;
+ LIST_INSERT_HEAD(&root, ip, list);
+ mla = ip->la;
+ mip = ip;
+}
+
+static int
+CheckIpfwRulenum(unsigned int rnum)
+{
+ unsigned int default_rule;
+ size_t len = sizeof(default_rule);
+
+ if (sysctlbyname("net.inet.ip.fw.default_rule", &default_rule, &len,
+ NULL, 0) == -1) {
+ warn("Failed to get the default ipfw rule number, using "
+ "default historical value 65535. The reason was");
+ default_rule = 65535;
+ }
+ if (rnum >= default_rule) {
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/sbin/natd/natd.h b/sbin/natd/natd.h
new file mode 100644
index 0000000..32bffd8
--- /dev/null
+++ b/sbin/natd/natd.h
@@ -0,0 +1,27 @@
+/*
+ * natd - Network Address Translation Daemon for FreeBSD.
+ *
+ * This software is provided free of charge, with no
+ * warranty of any kind, either expressed or implied.
+ * Use at your own risk.
+ *
+ * You may copy, modify and distribute this software (natd.h) freely.
+ *
+ * Ari Suutari <suutari@iki.fi>
+ *
+ * $FreeBSD$
+ */
+
+#define PIDFILE "/var/run/natd.pid"
+#define INPUT 1
+#define OUTPUT 2
+#define DONT_KNOW 3
+
+#define EXIT_DELAY 10000
+#define MAX_EXIT_DELAY 999999
+
+extern void Quit (const char* msg);
+extern void Warn (const char* msg);
+extern int SendNeedFragIcmp (int sock, struct ip* failedDgram, int mtu);
+extern struct libalias *mla;
+
diff --git a/sbin/natd/samples/natd.cf.sample b/sbin/natd/samples/natd.cf.sample
new file mode 100644
index 0000000..d4dcd71
--- /dev/null
+++ b/sbin/natd/samples/natd.cf.sample
@@ -0,0 +1,92 @@
+#
+# $FreeBSD$
+#
+#
+# Configuration file for natd.
+#
+#
+# Enable logging to file /var/log/alias.log
+#
+log no
+#
+# Incoming connections. Should NEVER be set to "yes" if redirect_port
+# or redirect_address statements are activated in this file!
+#
+# Setting to yes provides additional anti-crack protection
+#
+deny_incoming no
+#
+# Use sockets to avoid port clashes. Uses additional system resources, but
+# guarantees successful connections when port numbers conflict
+#
+use_sockets no
+#
+# Avoid port changes if possible when altering outbound packets. Makes rlogin
+# work in most cases.
+#
+same_ports yes
+#
+# Verbose mode. Enables dumping of packets and disables
+# forking to background. Only set to yes for debugging.
+#
+verbose no
+#
+# Divert port. Can be a name in /etc/services or numeric value.
+#
+port 32000
+#
+# Interface name or address being aliased. Either one,
+# not both is required.
+#
+# Obtain interface name from the command output of "ifconfig -a"
+#
+# alias_address 192.168.0.1
+interface ep0
+#
+# Alias unregistered addresses or all addresses. Set this to yes if
+# the inside network is all RFC1918 addresses.
+#
+unregistered_only no
+#
+# Configure permanent links. If you use host names instead
+# of addresses here, be sure that name server works BEFORE
+# natd is up - this is usually not the case. So either use
+# numeric addresses or hosts that are in /etc/hosts.
+#
+# Note: Current versions of FreeBSD all call /etc/rc.firewall
+# BEFORE running named, so if the DNS server and NAT are on the same
+# machine, the nameserver won't be up if natd is called from /etc/rc.firewall
+#
+# Map connections coming to port 30000 to telnet in my_private_host.
+# Remember to allow the connection /etc/rc.firewall also.
+#
+#redirect_port tcp my_private_host:telnet 30000
+#
+# Map connections coming from host.xyz.com to port 30001 to
+# telnet in another_host.
+#redirect_port tcp another_host:telnet 30001 host.xyz.com
+#
+# Static NAT address mapping:
+#
+# ipconfig must apply any legal IP numbers that inside hosts
+# will be known by to the outside interface. These are sometimes known as
+# virtual IP numbers. It's suggested to use the "interface" directive
+# instead of the "alias_address" directive to make it more clear what is
+# going on. (although both will work)
+#
+# DNS in this situation can get hairy. For example, an inside host
+# named aweb.company.com is located at 192.168.1.56, and needs to be
+# accessible through a legal IP number like 198.105.232.1. If both
+# 192.168.1.56 and 198.105.232.1 are set up as address records in the DNS
+# for aweb.company.com, then external hosts attempting to access
+# aweb.company.com may use address 192.168.1.56 which is inaccessible to them.
+#
+# The obvious solution is to use only a single address for the name, the
+# outside address. However, this creates needless traffic through the
+# NAT, because inside hosts will go through the NAT to get to the legal
+# number, even when the inside number is on the same subnet as they are!
+#
+# It's probably not a good idea to use DNS names in redirect_address statements
+#
+#The following mapping points outside address 198.105.232.1 to 192.168.1.56
+#redirect_address 192.168.1.56 198.105.232.1
diff --git a/sbin/natd/samples/natd.test b/sbin/natd/samples/natd.test
new file mode 100644
index 0000000..cfdbd15
--- /dev/null
+++ b/sbin/natd/samples/natd.test
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+ if [ $# != 1 ]
+ then
+ echo "usage: natd.test ifname"
+ exit 1
+ fi
+
+ ipfw flush
+ ipfw add divert 32000 ip from any to any via $1
+ ipfw add pass ip from any to any
+
+ ./natd -port 32000 -interface $1 -verbose
+
diff --git a/sbin/newfs/Makefile b/sbin/newfs/Makefile
new file mode 100644
index 0000000..83801fd
--- /dev/null
+++ b/sbin/newfs/Makefile
@@ -0,0 +1,19 @@
+# @(#)Makefile 8.2 (Berkeley) 3/27/94
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../../sys/geom
+
+PROG= newfs
+LIBADD= ufs util
+SRCS= newfs.c mkfs.c geom_bsd_enc.c
+
+WARNS?= 3
+MAN= newfs.8
+
+.include <bsd.prog.mk>
+
+test: ${PROG}
+ sh ${.CURDIR}/runtest01.sh
+ sh ${.CURDIR}/runtest00.sh | tee _.test
+ diff --ignore-matching-lines=FreeBSD _.test ${.CURDIR}/ref.test
+ echo All Tests Passed
diff --git a/sbin/newfs/mkfs.c b/sbin/newfs/mkfs.c
new file mode 100644
index 0000000..ee6ed96
--- /dev/null
+++ b/sbin/newfs/mkfs.c
@@ -0,0 +1,1159 @@
+/*
+ * Copyright (c) 2002 Networks Associates Technology, Inc.
+ * All rights reserved.
+ *
+ * This software was developed for the FreeBSD Project by Marshall
+ * Kirk McKusick and Network Associates Laboratories, the Security
+ * Research Division of Network Associates, Inc. under DARPA/SPAWAR
+ * contract N66001-01-C-8035 ("CBOSS"), as part of the DARPA CHATS
+ * research program.
+ *
+ * Copyright (c) 1980, 1989, 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.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)mkfs.c 8.11 (Berkeley) 5/3/95";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/disklabel.h>
+#include <sys/file.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <err.h>
+#include <grp.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <time.h>
+#include <unistd.h>
+#include <ufs/ufs/dinode.h>
+#include <ufs/ufs/dir.h>
+#include <ufs/ffs/fs.h>
+#include "newfs.h"
+
+/*
+ * make file system for cylinder-group style file systems
+ */
+#define UMASK 0755
+#define POWEROF2(num) (((num) & ((num) - 1)) == 0)
+
+static struct csum *fscs;
+#define sblock disk.d_fs
+#define acg disk.d_cg
+
+union dinode {
+ struct ufs1_dinode dp1;
+ struct ufs2_dinode dp2;
+};
+#define DIP(dp, field) \
+ ((sblock.fs_magic == FS_UFS1_MAGIC) ? \
+ (dp)->dp1.field : (dp)->dp2.field)
+
+static caddr_t iobuf;
+static long iobufsize;
+static ufs2_daddr_t alloc(int size, int mode);
+static int charsperline(void);
+static void clrblock(struct fs *, unsigned char *, int);
+static void fsinit(time_t);
+static int ilog2(int);
+static void initcg(int, time_t);
+static int isblock(struct fs *, unsigned char *, int);
+static void iput(union dinode *, ino_t);
+static int makedir(struct direct *, int);
+static void setblock(struct fs *, unsigned char *, int);
+static void wtfs(ufs2_daddr_t, int, char *);
+static u_int32_t newfs_random(void);
+
+static int
+do_sbwrite(struct uufsd *disk)
+{
+ if (!disk->d_sblock)
+ disk->d_sblock = disk->d_fs.fs_sblockloc / disk->d_bsize;
+ return (pwrite(disk->d_fd, &disk->d_fs, SBLOCKSIZE, (off_t)((part_ofs +
+ disk->d_sblock) * disk->d_bsize)));
+}
+
+void
+mkfs(struct partition *pp, char *fsys)
+{
+ int fragsperinode, optimalfpg, origdensity, minfpg, lastminfpg;
+ long i, j, csfrags;
+ uint cg;
+ time_t utime;
+ quad_t sizepb;
+ int width;
+ ino_t maxinum;
+ int minfragsperinode; /* minimum ratio of frags to inodes */
+ char tmpbuf[100]; /* XXX this will break in about 2,500 years */
+ union {
+ struct fs fdummy;
+ char cdummy[SBLOCKSIZE];
+ } dummy;
+#define fsdummy dummy.fdummy
+#define chdummy dummy.cdummy
+
+ /*
+ * Our blocks == sector size, and the version of UFS we are using is
+ * specified by Oflag.
+ */
+ disk.d_bsize = sectorsize;
+ disk.d_ufs = Oflag;
+ if (Rflag)
+ utime = 1000000000;
+ else
+ time(&utime);
+ sblock.fs_old_flags = FS_FLAGS_UPDATED;
+ sblock.fs_flags = 0;
+ if (Uflag)
+ sblock.fs_flags |= FS_DOSOFTDEP;
+ if (Lflag)
+ strlcpy(sblock.fs_volname, volumelabel, MAXVOLLEN);
+ if (Jflag)
+ sblock.fs_flags |= FS_GJOURNAL;
+ if (lflag)
+ sblock.fs_flags |= FS_MULTILABEL;
+ if (tflag)
+ sblock.fs_flags |= FS_TRIM;
+ /*
+ * Validate the given file system size.
+ * Verify that its last block can actually be accessed.
+ * Convert to file system fragment sized units.
+ */
+ if (fssize <= 0) {
+ printf("preposterous size %jd\n", (intmax_t)fssize);
+ exit(13);
+ }
+ wtfs(fssize - (realsectorsize / DEV_BSIZE), realsectorsize,
+ (char *)&sblock);
+ /*
+ * collect and verify the file system density info
+ */
+ sblock.fs_avgfilesize = avgfilesize;
+ sblock.fs_avgfpdir = avgfilesperdir;
+ if (sblock.fs_avgfilesize <= 0)
+ printf("illegal expected average file size %d\n",
+ sblock.fs_avgfilesize), exit(14);
+ if (sblock.fs_avgfpdir <= 0)
+ printf("illegal expected number of files per directory %d\n",
+ sblock.fs_avgfpdir), exit(15);
+
+restart:
+ /*
+ * collect and verify the block and fragment sizes
+ */
+ sblock.fs_bsize = bsize;
+ sblock.fs_fsize = fsize;
+ if (!POWEROF2(sblock.fs_bsize)) {
+ printf("block size must be a power of 2, not %d\n",
+ sblock.fs_bsize);
+ exit(16);
+ }
+ if (!POWEROF2(sblock.fs_fsize)) {
+ printf("fragment size must be a power of 2, not %d\n",
+ sblock.fs_fsize);
+ exit(17);
+ }
+ if (sblock.fs_fsize < sectorsize) {
+ printf("increasing fragment size from %d to sector size (%d)\n",
+ sblock.fs_fsize, sectorsize);
+ sblock.fs_fsize = sectorsize;
+ }
+ if (sblock.fs_bsize > MAXBSIZE) {
+ printf("decreasing block size from %d to maximum (%d)\n",
+ sblock.fs_bsize, MAXBSIZE);
+ sblock.fs_bsize = MAXBSIZE;
+ }
+ if (sblock.fs_bsize < MINBSIZE) {
+ printf("increasing block size from %d to minimum (%d)\n",
+ sblock.fs_bsize, MINBSIZE);
+ sblock.fs_bsize = MINBSIZE;
+ }
+ if (sblock.fs_fsize > MAXBSIZE) {
+ printf("decreasing fragment size from %d to maximum (%d)\n",
+ sblock.fs_fsize, MAXBSIZE);
+ sblock.fs_fsize = MAXBSIZE;
+ }
+ if (sblock.fs_bsize < sblock.fs_fsize) {
+ printf("increasing block size from %d to fragment size (%d)\n",
+ sblock.fs_bsize, sblock.fs_fsize);
+ sblock.fs_bsize = sblock.fs_fsize;
+ }
+ if (sblock.fs_fsize * MAXFRAG < sblock.fs_bsize) {
+ printf(
+ "increasing fragment size from %d to block size / %d (%d)\n",
+ sblock.fs_fsize, MAXFRAG, sblock.fs_bsize / MAXFRAG);
+ sblock.fs_fsize = sblock.fs_bsize / MAXFRAG;
+ }
+ if (maxbsize == 0)
+ maxbsize = bsize;
+ if (maxbsize < bsize || !POWEROF2(maxbsize)) {
+ sblock.fs_maxbsize = sblock.fs_bsize;
+ printf("Extent size set to %d\n", sblock.fs_maxbsize);
+ } else if (sblock.fs_maxbsize > FS_MAXCONTIG * sblock.fs_bsize) {
+ sblock.fs_maxbsize = FS_MAXCONTIG * sblock.fs_bsize;
+ printf("Extent size reduced to %d\n", sblock.fs_maxbsize);
+ } else {
+ sblock.fs_maxbsize = maxbsize;
+ }
+ /*
+ * Maxcontig sets the default for the maximum number of blocks
+ * that may be allocated sequentially. With file system clustering
+ * it is possible to allocate contiguous blocks up to the maximum
+ * transfer size permitted by the controller or buffering.
+ */
+ if (maxcontig == 0)
+ maxcontig = MAX(1, MAXPHYS / bsize);
+ sblock.fs_maxcontig = maxcontig;
+ if (sblock.fs_maxcontig < sblock.fs_maxbsize / sblock.fs_bsize) {
+ sblock.fs_maxcontig = sblock.fs_maxbsize / sblock.fs_bsize;
+ printf("Maxcontig raised to %d\n", sblock.fs_maxbsize);
+ }
+ if (sblock.fs_maxcontig > 1)
+ sblock.fs_contigsumsize = MIN(sblock.fs_maxcontig,FS_MAXCONTIG);
+ sblock.fs_bmask = ~(sblock.fs_bsize - 1);
+ sblock.fs_fmask = ~(sblock.fs_fsize - 1);
+ sblock.fs_qbmask = ~sblock.fs_bmask;
+ sblock.fs_qfmask = ~sblock.fs_fmask;
+ sblock.fs_bshift = ilog2(sblock.fs_bsize);
+ sblock.fs_fshift = ilog2(sblock.fs_fsize);
+ sblock.fs_frag = numfrags(&sblock, sblock.fs_bsize);
+ sblock.fs_fragshift = ilog2(sblock.fs_frag);
+ if (sblock.fs_frag > MAXFRAG) {
+ printf("fragment size %d is still too small (can't happen)\n",
+ sblock.fs_bsize / MAXFRAG);
+ exit(21);
+ }
+ sblock.fs_fsbtodb = ilog2(sblock.fs_fsize / sectorsize);
+ sblock.fs_size = fssize = dbtofsb(&sblock, fssize);
+ sblock.fs_providersize = dbtofsb(&sblock, mediasize / sectorsize);
+
+ /*
+ * Before the filesystem is finally initialized, mark it
+ * as incompletely initialized.
+ */
+ sblock.fs_magic = FS_BAD_MAGIC;
+
+ if (Oflag == 1) {
+ sblock.fs_sblockloc = SBLOCK_UFS1;
+ sblock.fs_nindir = sblock.fs_bsize / sizeof(ufs1_daddr_t);
+ sblock.fs_inopb = sblock.fs_bsize / sizeof(struct ufs1_dinode);
+ sblock.fs_maxsymlinklen = ((NDADDR + NIADDR) *
+ sizeof(ufs1_daddr_t));
+ sblock.fs_old_inodefmt = FS_44INODEFMT;
+ sblock.fs_old_cgoffset = 0;
+ sblock.fs_old_cgmask = 0xffffffff;
+ sblock.fs_old_size = sblock.fs_size;
+ sblock.fs_old_rotdelay = 0;
+ sblock.fs_old_rps = 60;
+ sblock.fs_old_nspf = sblock.fs_fsize / sectorsize;
+ sblock.fs_old_cpg = 1;
+ sblock.fs_old_interleave = 1;
+ sblock.fs_old_trackskew = 0;
+ sblock.fs_old_cpc = 0;
+ sblock.fs_old_postblformat = 1;
+ sblock.fs_old_nrpos = 1;
+ } else {
+ sblock.fs_sblockloc = SBLOCK_UFS2;
+ sblock.fs_nindir = sblock.fs_bsize / sizeof(ufs2_daddr_t);
+ sblock.fs_inopb = sblock.fs_bsize / sizeof(struct ufs2_dinode);
+ sblock.fs_maxsymlinklen = ((NDADDR + NIADDR) *
+ sizeof(ufs2_daddr_t));
+ }
+ sblock.fs_sblkno =
+ roundup(howmany(sblock.fs_sblockloc + SBLOCKSIZE, sblock.fs_fsize),
+ sblock.fs_frag);
+ sblock.fs_cblkno = sblock.fs_sblkno +
+ roundup(howmany(SBLOCKSIZE, sblock.fs_fsize), sblock.fs_frag);
+ sblock.fs_iblkno = sblock.fs_cblkno + sblock.fs_frag;
+ sblock.fs_maxfilesize = sblock.fs_bsize * NDADDR - 1;
+ for (sizepb = sblock.fs_bsize, i = 0; i < NIADDR; i++) {
+ sizepb *= NINDIR(&sblock);
+ sblock.fs_maxfilesize += sizepb;
+ }
+
+ /*
+ * It's impossible to create a snapshot in case that fs_maxfilesize
+ * is smaller than the fssize.
+ */
+ if (sblock.fs_maxfilesize < (u_quad_t)fssize) {
+ warnx("WARNING: You will be unable to create snapshots on this "
+ "file system. Correct by using a larger blocksize.");
+ }
+
+ /*
+ * Calculate the number of blocks to put into each cylinder group.
+ *
+ * This algorithm selects the number of blocks per cylinder
+ * group. The first goal is to have at least enough data blocks
+ * in each cylinder group to meet the density requirement. Once
+ * this goal is achieved we try to expand to have at least
+ * MINCYLGRPS cylinder groups. Once this goal is achieved, we
+ * pack as many blocks into each cylinder group map as will fit.
+ *
+ * We start by calculating the smallest number of blocks that we
+ * can put into each cylinder group. If this is too big, we reduce
+ * the density until it fits.
+ */
+ maxinum = (((int64_t)(1)) << 32) - INOPB(&sblock);
+ minfragsperinode = 1 + fssize / maxinum;
+ if (density == 0) {
+ density = MAX(NFPI, minfragsperinode) * fsize;
+ } else if (density < minfragsperinode * fsize) {
+ origdensity = density;
+ density = minfragsperinode * fsize;
+ fprintf(stderr, "density increased from %d to %d\n",
+ origdensity, density);
+ }
+ origdensity = density;
+ for (;;) {
+ fragsperinode = MAX(numfrags(&sblock, density), 1);
+ if (fragsperinode < minfragsperinode) {
+ bsize <<= 1;
+ fsize <<= 1;
+ printf("Block size too small for a file system %s %d\n",
+ "of this size. Increasing blocksize to", bsize);
+ goto restart;
+ }
+ minfpg = fragsperinode * INOPB(&sblock);
+ if (minfpg > sblock.fs_size)
+ minfpg = sblock.fs_size;
+ sblock.fs_ipg = INOPB(&sblock);
+ sblock.fs_fpg = roundup(sblock.fs_iblkno +
+ sblock.fs_ipg / INOPF(&sblock), sblock.fs_frag);
+ if (sblock.fs_fpg < minfpg)
+ sblock.fs_fpg = minfpg;
+ sblock.fs_ipg = roundup(howmany(sblock.fs_fpg, fragsperinode),
+ INOPB(&sblock));
+ sblock.fs_fpg = roundup(sblock.fs_iblkno +
+ sblock.fs_ipg / INOPF(&sblock), sblock.fs_frag);
+ if (sblock.fs_fpg < minfpg)
+ sblock.fs_fpg = minfpg;
+ sblock.fs_ipg = roundup(howmany(sblock.fs_fpg, fragsperinode),
+ INOPB(&sblock));
+ if (CGSIZE(&sblock) < (unsigned long)sblock.fs_bsize)
+ break;
+ density -= sblock.fs_fsize;
+ }
+ if (density != origdensity)
+ printf("density reduced from %d to %d\n", origdensity, density);
+ /*
+ * Start packing more blocks into the cylinder group until
+ * it cannot grow any larger, the number of cylinder groups
+ * drops below MINCYLGRPS, or we reach the size requested.
+ * For UFS1 inodes per cylinder group are stored in an int16_t
+ * so fs_ipg is limited to 2^15 - 1.
+ */
+ for ( ; sblock.fs_fpg < maxblkspercg; sblock.fs_fpg += sblock.fs_frag) {
+ sblock.fs_ipg = roundup(howmany(sblock.fs_fpg, fragsperinode),
+ INOPB(&sblock));
+ if (Oflag > 1 || (Oflag == 1 && sblock.fs_ipg <= 0x7fff)) {
+ if (sblock.fs_size / sblock.fs_fpg < MINCYLGRPS)
+ break;
+ if (CGSIZE(&sblock) < (unsigned long)sblock.fs_bsize)
+ continue;
+ if (CGSIZE(&sblock) == (unsigned long)sblock.fs_bsize)
+ break;
+ }
+ sblock.fs_fpg -= sblock.fs_frag;
+ sblock.fs_ipg = roundup(howmany(sblock.fs_fpg, fragsperinode),
+ INOPB(&sblock));
+ break;
+ }
+ /*
+ * Check to be sure that the last cylinder group has enough blocks
+ * to be viable. If it is too small, reduce the number of blocks
+ * per cylinder group which will have the effect of moving more
+ * blocks into the last cylinder group.
+ */
+ optimalfpg = sblock.fs_fpg;
+ for (;;) {
+ sblock.fs_ncg = howmany(sblock.fs_size, sblock.fs_fpg);
+ lastminfpg = roundup(sblock.fs_iblkno +
+ sblock.fs_ipg / INOPF(&sblock), sblock.fs_frag);
+ if (sblock.fs_size < lastminfpg) {
+ printf("Filesystem size %jd < minimum size of %d\n",
+ (intmax_t)sblock.fs_size, lastminfpg);
+ exit(28);
+ }
+ if (sblock.fs_size % sblock.fs_fpg >= lastminfpg ||
+ sblock.fs_size % sblock.fs_fpg == 0)
+ break;
+ sblock.fs_fpg -= sblock.fs_frag;
+ sblock.fs_ipg = roundup(howmany(sblock.fs_fpg, fragsperinode),
+ INOPB(&sblock));
+ }
+ if (optimalfpg != sblock.fs_fpg)
+ printf("Reduced frags per cylinder group from %d to %d %s\n",
+ optimalfpg, sblock.fs_fpg, "to enlarge last cyl group");
+ sblock.fs_cgsize = fragroundup(&sblock, CGSIZE(&sblock));
+ sblock.fs_dblkno = sblock.fs_iblkno + sblock.fs_ipg / INOPF(&sblock);
+ if (Oflag == 1) {
+ sblock.fs_old_spc = sblock.fs_fpg * sblock.fs_old_nspf;
+ sblock.fs_old_nsect = sblock.fs_old_spc;
+ sblock.fs_old_npsect = sblock.fs_old_spc;
+ sblock.fs_old_ncyl = sblock.fs_ncg;
+ }
+ /*
+ * fill in remaining fields of the super block
+ */
+ sblock.fs_csaddr = cgdmin(&sblock, 0);
+ sblock.fs_cssize =
+ fragroundup(&sblock, sblock.fs_ncg * sizeof(struct csum));
+ fscs = (struct csum *)calloc(1, sblock.fs_cssize);
+ if (fscs == NULL)
+ errx(31, "calloc failed");
+ sblock.fs_sbsize = fragroundup(&sblock, sizeof(struct fs));
+ if (sblock.fs_sbsize > SBLOCKSIZE)
+ sblock.fs_sbsize = SBLOCKSIZE;
+ sblock.fs_minfree = minfree;
+ if (metaspace > 0 && metaspace < sblock.fs_fpg / 2)
+ sblock.fs_metaspace = blknum(&sblock, metaspace);
+ else if (metaspace != -1)
+ /* reserve half of minfree for metadata blocks */
+ sblock.fs_metaspace = blknum(&sblock,
+ (sblock.fs_fpg * minfree) / 200);
+ if (maxbpg == 0)
+ sblock.fs_maxbpg = MAXBLKPG(sblock.fs_bsize);
+ else
+ sblock.fs_maxbpg = maxbpg;
+ sblock.fs_optim = opt;
+ sblock.fs_cgrotor = 0;
+ sblock.fs_pendingblocks = 0;
+ sblock.fs_pendinginodes = 0;
+ sblock.fs_fmod = 0;
+ sblock.fs_ronly = 0;
+ sblock.fs_state = 0;
+ sblock.fs_clean = 1;
+ sblock.fs_id[0] = (long)utime;
+ sblock.fs_id[1] = newfs_random();
+ sblock.fs_fsmnt[0] = '\0';
+ csfrags = howmany(sblock.fs_cssize, sblock.fs_fsize);
+ sblock.fs_dsize = sblock.fs_size - sblock.fs_sblkno -
+ sblock.fs_ncg * (sblock.fs_dblkno - sblock.fs_sblkno);
+ sblock.fs_cstotal.cs_nbfree =
+ fragstoblks(&sblock, sblock.fs_dsize) -
+ howmany(csfrags, sblock.fs_frag);
+ sblock.fs_cstotal.cs_nffree =
+ fragnum(&sblock, sblock.fs_size) +
+ (fragnum(&sblock, csfrags) > 0 ?
+ sblock.fs_frag - fragnum(&sblock, csfrags) : 0);
+ sblock.fs_cstotal.cs_nifree = sblock.fs_ncg * sblock.fs_ipg - ROOTINO;
+ sblock.fs_cstotal.cs_ndir = 0;
+ sblock.fs_dsize -= csfrags;
+ sblock.fs_time = utime;
+ if (Oflag == 1) {
+ sblock.fs_old_time = utime;
+ sblock.fs_old_dsize = sblock.fs_dsize;
+ sblock.fs_old_csaddr = sblock.fs_csaddr;
+ sblock.fs_old_cstotal.cs_ndir = sblock.fs_cstotal.cs_ndir;
+ sblock.fs_old_cstotal.cs_nbfree = sblock.fs_cstotal.cs_nbfree;
+ sblock.fs_old_cstotal.cs_nifree = sblock.fs_cstotal.cs_nifree;
+ sblock.fs_old_cstotal.cs_nffree = sblock.fs_cstotal.cs_nffree;
+ }
+
+ /*
+ * Dump out summary information about file system.
+ */
+# define B2MBFACTOR (1 / (1024.0 * 1024.0))
+ printf("%s: %.1fMB (%jd sectors) block size %d, fragment size %d\n",
+ fsys, (float)sblock.fs_size * sblock.fs_fsize * B2MBFACTOR,
+ (intmax_t)fsbtodb(&sblock, sblock.fs_size), sblock.fs_bsize,
+ sblock.fs_fsize);
+ printf("\tusing %d cylinder groups of %.2fMB, %d blks, %d inodes.\n",
+ sblock.fs_ncg, (float)sblock.fs_fpg * sblock.fs_fsize * B2MBFACTOR,
+ sblock.fs_fpg / sblock.fs_frag, sblock.fs_ipg);
+ if (sblock.fs_flags & FS_DOSOFTDEP)
+ printf("\twith soft updates\n");
+# undef B2MBFACTOR
+
+ if (Eflag && !Nflag) {
+ printf("Erasing sectors [%jd...%jd]\n",
+ sblock.fs_sblockloc / disk.d_bsize,
+ fsbtodb(&sblock, sblock.fs_size) - 1);
+ berase(&disk, sblock.fs_sblockloc / disk.d_bsize,
+ sblock.fs_size * sblock.fs_fsize - sblock.fs_sblockloc);
+ }
+ /*
+ * Wipe out old UFS1 superblock(s) if necessary.
+ */
+ if (!Nflag && Oflag != 1) {
+ i = bread(&disk, part_ofs + SBLOCK_UFS1 / disk.d_bsize, chdummy, SBLOCKSIZE);
+ if (i == -1)
+ err(1, "can't read old UFS1 superblock: %s", disk.d_error);
+
+ if (fsdummy.fs_magic == FS_UFS1_MAGIC) {
+ fsdummy.fs_magic = 0;
+ bwrite(&disk, part_ofs + SBLOCK_UFS1 / disk.d_bsize,
+ chdummy, SBLOCKSIZE);
+ for (cg = 0; cg < fsdummy.fs_ncg; cg++) {
+ if (fsbtodb(&fsdummy, cgsblock(&fsdummy, cg)) > fssize)
+ break;
+ bwrite(&disk, part_ofs + fsbtodb(&fsdummy,
+ cgsblock(&fsdummy, cg)), chdummy, SBLOCKSIZE);
+ }
+ }
+ }
+ if (!Nflag)
+ do_sbwrite(&disk);
+ if (Xflag == 1) {
+ printf("** Exiting on Xflag 1\n");
+ exit(0);
+ }
+ if (Xflag == 2)
+ printf("** Leaving BAD MAGIC on Xflag 2\n");
+ else
+ sblock.fs_magic = (Oflag != 1) ? FS_UFS2_MAGIC : FS_UFS1_MAGIC;
+
+ /*
+ * Now build the cylinders group blocks and
+ * then print out indices of cylinder groups.
+ */
+ printf("super-block backups (for fsck_ffs -b #) at:\n");
+ i = 0;
+ width = charsperline();
+ /*
+ * allocate space for superblock, cylinder group map, and
+ * two sets of inode blocks.
+ */
+ if (sblock.fs_bsize < SBLOCKSIZE)
+ iobufsize = SBLOCKSIZE + 3 * sblock.fs_bsize;
+ else
+ iobufsize = 4 * sblock.fs_bsize;
+ if ((iobuf = calloc(1, iobufsize)) == 0) {
+ printf("Cannot allocate I/O buffer\n");
+ exit(38);
+ }
+ /*
+ * Make a copy of the superblock into the buffer that we will be
+ * writing out in each cylinder group.
+ */
+ bcopy((char *)&sblock, iobuf, SBLOCKSIZE);
+ for (cg = 0; cg < sblock.fs_ncg; cg++) {
+ initcg(cg, utime);
+ j = snprintf(tmpbuf, sizeof(tmpbuf), " %jd%s",
+ (intmax_t)fsbtodb(&sblock, cgsblock(&sblock, cg)),
+ cg < (sblock.fs_ncg-1) ? "," : "");
+ if (j < 0)
+ tmpbuf[j = 0] = '\0';
+ if (i + j >= width) {
+ printf("\n");
+ i = 0;
+ }
+ i += j;
+ printf("%s", tmpbuf);
+ fflush(stdout);
+ }
+ printf("\n");
+ if (Nflag)
+ exit(0);
+ /*
+ * Now construct the initial file system,
+ * then write out the super-block.
+ */
+ fsinit(utime);
+ if (Oflag == 1) {
+ sblock.fs_old_cstotal.cs_ndir = sblock.fs_cstotal.cs_ndir;
+ sblock.fs_old_cstotal.cs_nbfree = sblock.fs_cstotal.cs_nbfree;
+ sblock.fs_old_cstotal.cs_nifree = sblock.fs_cstotal.cs_nifree;
+ sblock.fs_old_cstotal.cs_nffree = sblock.fs_cstotal.cs_nffree;
+ }
+ if (Xflag == 3) {
+ printf("** Exiting on Xflag 3\n");
+ exit(0);
+ }
+ if (!Nflag) {
+ do_sbwrite(&disk);
+ /*
+ * For UFS1 filesystems with a blocksize of 64K, the first
+ * alternate superblock resides at the location used for
+ * the default UFS2 superblock. As there is a valid
+ * superblock at this location, the boot code will use
+ * it as its first choice. Thus we have to ensure that
+ * all of its statistcs on usage are correct.
+ */
+ if (Oflag == 1 && sblock.fs_bsize == 65536)
+ wtfs(fsbtodb(&sblock, cgsblock(&sblock, 0)),
+ sblock.fs_bsize, (char *)&sblock);
+ }
+ for (i = 0; i < sblock.fs_cssize; i += sblock.fs_bsize)
+ wtfs(fsbtodb(&sblock, sblock.fs_csaddr + numfrags(&sblock, i)),
+ sblock.fs_cssize - i < sblock.fs_bsize ?
+ sblock.fs_cssize - i : sblock.fs_bsize,
+ ((char *)fscs) + i);
+ /*
+ * Update information about this partition in pack
+ * label, to that it may be updated on disk.
+ */
+ if (pp != NULL) {
+ pp->p_fstype = FS_BSDFFS;
+ pp->p_fsize = sblock.fs_fsize;
+ pp->p_frag = sblock.fs_frag;
+ pp->p_cpg = sblock.fs_fpg;
+ }
+}
+
+/*
+ * Initialize a cylinder group.
+ */
+void
+initcg(int cylno, time_t utime)
+{
+ long blkno, start;
+ uint i, j, d, dlower, dupper;
+ ufs2_daddr_t cbase, dmax;
+ struct ufs1_dinode *dp1;
+ struct ufs2_dinode *dp2;
+ struct csum *cs;
+
+ /*
+ * Determine block bounds for cylinder group.
+ * Allow space for super block summary information in first
+ * cylinder group.
+ */
+ cbase = cgbase(&sblock, cylno);
+ dmax = cbase + sblock.fs_fpg;
+ if (dmax > sblock.fs_size)
+ dmax = sblock.fs_size;
+ dlower = cgsblock(&sblock, cylno) - cbase;
+ dupper = cgdmin(&sblock, cylno) - cbase;
+ if (cylno == 0)
+ dupper += howmany(sblock.fs_cssize, sblock.fs_fsize);
+ cs = &fscs[cylno];
+ memset(&acg, 0, sblock.fs_cgsize);
+ acg.cg_time = utime;
+ acg.cg_magic = CG_MAGIC;
+ acg.cg_cgx = cylno;
+ acg.cg_niblk = sblock.fs_ipg;
+ acg.cg_initediblk = sblock.fs_ipg < 2 * INOPB(&sblock) ?
+ sblock.fs_ipg : 2 * INOPB(&sblock);
+ acg.cg_ndblk = dmax - cbase;
+ if (sblock.fs_contigsumsize > 0)
+ acg.cg_nclusterblks = acg.cg_ndblk / sblock.fs_frag;
+ start = &acg.cg_space[0] - (u_char *)(&acg.cg_firstfield);
+ if (Oflag == 2) {
+ acg.cg_iusedoff = start;
+ } else {
+ acg.cg_old_ncyl = sblock.fs_old_cpg;
+ acg.cg_old_time = acg.cg_time;
+ acg.cg_time = 0;
+ acg.cg_old_niblk = acg.cg_niblk;
+ acg.cg_niblk = 0;
+ acg.cg_initediblk = 0;
+ acg.cg_old_btotoff = start;
+ acg.cg_old_boff = acg.cg_old_btotoff +
+ sblock.fs_old_cpg * sizeof(int32_t);
+ acg.cg_iusedoff = acg.cg_old_boff +
+ sblock.fs_old_cpg * sizeof(u_int16_t);
+ }
+ acg.cg_freeoff = acg.cg_iusedoff + howmany(sblock.fs_ipg, CHAR_BIT);
+ acg.cg_nextfreeoff = acg.cg_freeoff + howmany(sblock.fs_fpg, CHAR_BIT);
+ if (sblock.fs_contigsumsize > 0) {
+ acg.cg_clustersumoff =
+ roundup(acg.cg_nextfreeoff, sizeof(u_int32_t));
+ acg.cg_clustersumoff -= sizeof(u_int32_t);
+ acg.cg_clusteroff = acg.cg_clustersumoff +
+ (sblock.fs_contigsumsize + 1) * sizeof(u_int32_t);
+ acg.cg_nextfreeoff = acg.cg_clusteroff +
+ howmany(fragstoblks(&sblock, sblock.fs_fpg), CHAR_BIT);
+ }
+ if (acg.cg_nextfreeoff > (unsigned)sblock.fs_cgsize) {
+ printf("Panic: cylinder group too big\n");
+ exit(37);
+ }
+ acg.cg_cs.cs_nifree += sblock.fs_ipg;
+ if (cylno == 0)
+ for (i = 0; i < (long)ROOTINO; i++) {
+ setbit(cg_inosused(&acg), i);
+ acg.cg_cs.cs_nifree--;
+ }
+ if (cylno > 0) {
+ /*
+ * In cylno 0, beginning space is reserved
+ * for boot and super blocks.
+ */
+ for (d = 0; d < dlower; d += sblock.fs_frag) {
+ blkno = d / sblock.fs_frag;
+ setblock(&sblock, cg_blksfree(&acg), blkno);
+ if (sblock.fs_contigsumsize > 0)
+ setbit(cg_clustersfree(&acg), blkno);
+ acg.cg_cs.cs_nbfree++;
+ }
+ }
+ if ((i = dupper % sblock.fs_frag)) {
+ acg.cg_frsum[sblock.fs_frag - i]++;
+ for (d = dupper + sblock.fs_frag - i; dupper < d; dupper++) {
+ setbit(cg_blksfree(&acg), dupper);
+ acg.cg_cs.cs_nffree++;
+ }
+ }
+ for (d = dupper; d + sblock.fs_frag <= acg.cg_ndblk;
+ d += sblock.fs_frag) {
+ blkno = d / sblock.fs_frag;
+ setblock(&sblock, cg_blksfree(&acg), blkno);
+ if (sblock.fs_contigsumsize > 0)
+ setbit(cg_clustersfree(&acg), blkno);
+ acg.cg_cs.cs_nbfree++;
+ }
+ if (d < acg.cg_ndblk) {
+ acg.cg_frsum[acg.cg_ndblk - d]++;
+ for (; d < acg.cg_ndblk; d++) {
+ setbit(cg_blksfree(&acg), d);
+ acg.cg_cs.cs_nffree++;
+ }
+ }
+ if (sblock.fs_contigsumsize > 0) {
+ int32_t *sump = cg_clustersum(&acg);
+ u_char *mapp = cg_clustersfree(&acg);
+ int map = *mapp++;
+ int bit = 1;
+ int run = 0;
+
+ for (i = 0; i < acg.cg_nclusterblks; i++) {
+ if ((map & bit) != 0)
+ run++;
+ else if (run != 0) {
+ if (run > sblock.fs_contigsumsize)
+ run = sblock.fs_contigsumsize;
+ sump[run]++;
+ run = 0;
+ }
+ if ((i & (CHAR_BIT - 1)) != CHAR_BIT - 1)
+ bit <<= 1;
+ else {
+ map = *mapp++;
+ bit = 1;
+ }
+ }
+ if (run != 0) {
+ if (run > sblock.fs_contigsumsize)
+ run = sblock.fs_contigsumsize;
+ sump[run]++;
+ }
+ }
+ *cs = acg.cg_cs;
+ /*
+ * Write out the duplicate super block, the cylinder group map
+ * and two blocks worth of inodes in a single write.
+ */
+ start = sblock.fs_bsize > SBLOCKSIZE ? sblock.fs_bsize : SBLOCKSIZE;
+ bcopy((char *)&acg, &iobuf[start], sblock.fs_cgsize);
+ start += sblock.fs_bsize;
+ dp1 = (struct ufs1_dinode *)(&iobuf[start]);
+ dp2 = (struct ufs2_dinode *)(&iobuf[start]);
+ for (i = 0; i < acg.cg_initediblk; i++) {
+ if (sblock.fs_magic == FS_UFS1_MAGIC) {
+ dp1->di_gen = newfs_random();
+ dp1++;
+ } else {
+ dp2->di_gen = newfs_random();
+ dp2++;
+ }
+ }
+ wtfs(fsbtodb(&sblock, cgsblock(&sblock, cylno)), iobufsize, iobuf);
+ /*
+ * For the old file system, we have to initialize all the inodes.
+ */
+ if (Oflag == 1) {
+ for (i = 2 * sblock.fs_frag;
+ i < sblock.fs_ipg / INOPF(&sblock);
+ i += sblock.fs_frag) {
+ dp1 = (struct ufs1_dinode *)(&iobuf[start]);
+ for (j = 0; j < INOPB(&sblock); j++) {
+ dp1->di_gen = newfs_random();
+ dp1++;
+ }
+ wtfs(fsbtodb(&sblock, cgimin(&sblock, cylno) + i),
+ sblock.fs_bsize, &iobuf[start]);
+ }
+ }
+}
+
+/*
+ * initialize the file system
+ */
+#define ROOTLINKCNT 3
+
+static struct direct root_dir[] = {
+ { ROOTINO, sizeof(struct direct), DT_DIR, 1, "." },
+ { ROOTINO, sizeof(struct direct), DT_DIR, 2, ".." },
+ { ROOTINO + 1, sizeof(struct direct), DT_DIR, 5, ".snap" },
+};
+
+#define SNAPLINKCNT 2
+
+static struct direct snap_dir[] = {
+ { ROOTINO + 1, sizeof(struct direct), DT_DIR, 1, "." },
+ { ROOTINO, sizeof(struct direct), DT_DIR, 2, ".." },
+};
+
+void
+fsinit(time_t utime)
+{
+ union dinode node;
+ struct group *grp;
+ gid_t gid;
+ int entries;
+
+ memset(&node, 0, sizeof node);
+ if ((grp = getgrnam("operator")) != NULL) {
+ gid = grp->gr_gid;
+ } else {
+ warnx("Cannot retrieve operator gid, using gid 0.");
+ gid = 0;
+ }
+ entries = (nflag) ? ROOTLINKCNT - 1: ROOTLINKCNT;
+ if (sblock.fs_magic == FS_UFS1_MAGIC) {
+ /*
+ * initialize the node
+ */
+ node.dp1.di_atime = utime;
+ node.dp1.di_mtime = utime;
+ node.dp1.di_ctime = utime;
+ /*
+ * create the root directory
+ */
+ node.dp1.di_mode = IFDIR | UMASK;
+ node.dp1.di_nlink = entries;
+ node.dp1.di_size = makedir(root_dir, entries);
+ node.dp1.di_db[0] = alloc(sblock.fs_fsize, node.dp1.di_mode);
+ node.dp1.di_blocks =
+ btodb(fragroundup(&sblock, node.dp1.di_size));
+ wtfs(fsbtodb(&sblock, node.dp1.di_db[0]), sblock.fs_fsize,
+ iobuf);
+ iput(&node, ROOTINO);
+ if (!nflag) {
+ /*
+ * create the .snap directory
+ */
+ node.dp1.di_mode |= 020;
+ node.dp1.di_gid = gid;
+ node.dp1.di_nlink = SNAPLINKCNT;
+ node.dp1.di_size = makedir(snap_dir, SNAPLINKCNT);
+ node.dp1.di_db[0] =
+ alloc(sblock.fs_fsize, node.dp1.di_mode);
+ node.dp1.di_blocks =
+ btodb(fragroundup(&sblock, node.dp1.di_size));
+ wtfs(fsbtodb(&sblock, node.dp1.di_db[0]),
+ sblock.fs_fsize, iobuf);
+ iput(&node, ROOTINO + 1);
+ }
+ } else {
+ /*
+ * initialize the node
+ */
+ node.dp2.di_atime = utime;
+ node.dp2.di_mtime = utime;
+ node.dp2.di_ctime = utime;
+ node.dp2.di_birthtime = utime;
+ /*
+ * create the root directory
+ */
+ node.dp2.di_mode = IFDIR | UMASK;
+ node.dp2.di_nlink = entries;
+ node.dp2.di_size = makedir(root_dir, entries);
+ node.dp2.di_db[0] = alloc(sblock.fs_fsize, node.dp2.di_mode);
+ node.dp2.di_blocks =
+ btodb(fragroundup(&sblock, node.dp2.di_size));
+ wtfs(fsbtodb(&sblock, node.dp2.di_db[0]), sblock.fs_fsize,
+ iobuf);
+ iput(&node, ROOTINO);
+ if (!nflag) {
+ /*
+ * create the .snap directory
+ */
+ node.dp2.di_mode |= 020;
+ node.dp2.di_gid = gid;
+ node.dp2.di_nlink = SNAPLINKCNT;
+ node.dp2.di_size = makedir(snap_dir, SNAPLINKCNT);
+ node.dp2.di_db[0] =
+ alloc(sblock.fs_fsize, node.dp2.di_mode);
+ node.dp2.di_blocks =
+ btodb(fragroundup(&sblock, node.dp2.di_size));
+ wtfs(fsbtodb(&sblock, node.dp2.di_db[0]),
+ sblock.fs_fsize, iobuf);
+ iput(&node, ROOTINO + 1);
+ }
+ }
+}
+
+/*
+ * construct a set of directory entries in "iobuf".
+ * return size of directory.
+ */
+int
+makedir(struct direct *protodir, int entries)
+{
+ char *cp;
+ int i, spcleft;
+
+ spcleft = DIRBLKSIZ;
+ memset(iobuf, 0, DIRBLKSIZ);
+ for (cp = iobuf, i = 0; i < entries - 1; i++) {
+ protodir[i].d_reclen = DIRSIZ(0, &protodir[i]);
+ memmove(cp, &protodir[i], protodir[i].d_reclen);
+ cp += protodir[i].d_reclen;
+ spcleft -= protodir[i].d_reclen;
+ }
+ protodir[i].d_reclen = spcleft;
+ memmove(cp, &protodir[i], DIRSIZ(0, &protodir[i]));
+ return (DIRBLKSIZ);
+}
+
+/*
+ * allocate a block or frag
+ */
+ufs2_daddr_t
+alloc(int size, int mode)
+{
+ int i, blkno, frag;
+ uint d;
+
+ bread(&disk, part_ofs + fsbtodb(&sblock, cgtod(&sblock, 0)), (char *)&acg,
+ sblock.fs_cgsize);
+ if (acg.cg_magic != CG_MAGIC) {
+ printf("cg 0: bad magic number\n");
+ exit(38);
+ }
+ if (acg.cg_cs.cs_nbfree == 0) {
+ printf("first cylinder group ran out of space\n");
+ exit(39);
+ }
+ for (d = 0; d < acg.cg_ndblk; d += sblock.fs_frag)
+ if (isblock(&sblock, cg_blksfree(&acg), d / sblock.fs_frag))
+ goto goth;
+ printf("internal error: can't find block in cyl 0\n");
+ exit(40);
+goth:
+ blkno = fragstoblks(&sblock, d);
+ clrblock(&sblock, cg_blksfree(&acg), blkno);
+ if (sblock.fs_contigsumsize > 0)
+ clrbit(cg_clustersfree(&acg), blkno);
+ acg.cg_cs.cs_nbfree--;
+ sblock.fs_cstotal.cs_nbfree--;
+ fscs[0].cs_nbfree--;
+ if (mode & IFDIR) {
+ acg.cg_cs.cs_ndir++;
+ sblock.fs_cstotal.cs_ndir++;
+ fscs[0].cs_ndir++;
+ }
+ if (size != sblock.fs_bsize) {
+ frag = howmany(size, sblock.fs_fsize);
+ fscs[0].cs_nffree += sblock.fs_frag - frag;
+ sblock.fs_cstotal.cs_nffree += sblock.fs_frag - frag;
+ acg.cg_cs.cs_nffree += sblock.fs_frag - frag;
+ acg.cg_frsum[sblock.fs_frag - frag]++;
+ for (i = frag; i < sblock.fs_frag; i++)
+ setbit(cg_blksfree(&acg), d + i);
+ }
+ /* XXX cgwrite(&disk, 0)??? */
+ wtfs(fsbtodb(&sblock, cgtod(&sblock, 0)), sblock.fs_cgsize,
+ (char *)&acg);
+ return ((ufs2_daddr_t)d);
+}
+
+/*
+ * Allocate an inode on the disk
+ */
+void
+iput(union dinode *ip, ino_t ino)
+{
+ ufs2_daddr_t d;
+
+ bread(&disk, part_ofs + fsbtodb(&sblock, cgtod(&sblock, 0)), (char *)&acg,
+ sblock.fs_cgsize);
+ if (acg.cg_magic != CG_MAGIC) {
+ printf("cg 0: bad magic number\n");
+ exit(31);
+ }
+ acg.cg_cs.cs_nifree--;
+ setbit(cg_inosused(&acg), ino);
+ wtfs(fsbtodb(&sblock, cgtod(&sblock, 0)), sblock.fs_cgsize,
+ (char *)&acg);
+ sblock.fs_cstotal.cs_nifree--;
+ fscs[0].cs_nifree--;
+ if (ino >= (unsigned long)sblock.fs_ipg * sblock.fs_ncg) {
+ printf("fsinit: inode value out of range (%ju).\n",
+ (uintmax_t)ino);
+ exit(32);
+ }
+ d = fsbtodb(&sblock, ino_to_fsba(&sblock, ino));
+ bread(&disk, part_ofs + d, (char *)iobuf, sblock.fs_bsize);
+ if (sblock.fs_magic == FS_UFS1_MAGIC)
+ ((struct ufs1_dinode *)iobuf)[ino_to_fsbo(&sblock, ino)] =
+ ip->dp1;
+ else
+ ((struct ufs2_dinode *)iobuf)[ino_to_fsbo(&sblock, ino)] =
+ ip->dp2;
+ wtfs(d, sblock.fs_bsize, (char *)iobuf);
+}
+
+/*
+ * possibly write to disk
+ */
+static void
+wtfs(ufs2_daddr_t bno, int size, char *bf)
+{
+ if (Nflag)
+ return;
+ if (bwrite(&disk, part_ofs + bno, bf, size) < 0)
+ err(36, "wtfs: %d bytes at sector %jd", size, (intmax_t)bno);
+}
+
+/*
+ * check if a block is available
+ */
+static int
+isblock(struct fs *fs, unsigned char *cp, int h)
+{
+ unsigned char mask;
+
+ switch (fs->fs_frag) {
+ case 8:
+ return (cp[h] == 0xff);
+ case 4:
+ mask = 0x0f << ((h & 0x1) << 2);
+ return ((cp[h >> 1] & mask) == mask);
+ case 2:
+ mask = 0x03 << ((h & 0x3) << 1);
+ return ((cp[h >> 2] & mask) == mask);
+ case 1:
+ mask = 0x01 << (h & 0x7);
+ return ((cp[h >> 3] & mask) == mask);
+ default:
+ fprintf(stderr, "isblock bad fs_frag %d\n", fs->fs_frag);
+ return (0);
+ }
+}
+
+/*
+ * take a block out of the map
+ */
+static void
+clrblock(struct fs *fs, unsigned char *cp, int h)
+{
+ switch ((fs)->fs_frag) {
+ case 8:
+ cp[h] = 0;
+ return;
+ case 4:
+ cp[h >> 1] &= ~(0x0f << ((h & 0x1) << 2));
+ return;
+ case 2:
+ cp[h >> 2] &= ~(0x03 << ((h & 0x3) << 1));
+ return;
+ case 1:
+ cp[h >> 3] &= ~(0x01 << (h & 0x7));
+ return;
+ default:
+ fprintf(stderr, "clrblock bad fs_frag %d\n", fs->fs_frag);
+ return;
+ }
+}
+
+/*
+ * put a block into the map
+ */
+static void
+setblock(struct fs *fs, unsigned char *cp, int h)
+{
+ switch (fs->fs_frag) {
+ case 8:
+ cp[h] = 0xff;
+ return;
+ case 4:
+ cp[h >> 1] |= (0x0f << ((h & 0x1) << 2));
+ return;
+ case 2:
+ cp[h >> 2] |= (0x03 << ((h & 0x3) << 1));
+ return;
+ case 1:
+ cp[h >> 3] |= (0x01 << (h & 0x7));
+ return;
+ default:
+ fprintf(stderr, "setblock bad fs_frag %d\n", fs->fs_frag);
+ return;
+ }
+}
+
+/*
+ * Determine the number of characters in a
+ * single line.
+ */
+
+static int
+charsperline(void)
+{
+ int columns;
+ char *cp;
+ struct winsize ws;
+
+ columns = 0;
+ if (ioctl(0, TIOCGWINSZ, &ws) != -1)
+ columns = ws.ws_col;
+ if (columns == 0 && (cp = getenv("COLUMNS")))
+ columns = atoi(cp);
+ if (columns == 0)
+ columns = 80; /* last resort */
+ return (columns);
+}
+
+static int
+ilog2(int val)
+{
+ u_int n;
+
+ for (n = 0; n < sizeof(n) * CHAR_BIT; n++)
+ if (1 << n == val)
+ return (n);
+ errx(1, "ilog2: %d is not a power of 2\n", val);
+}
+
+/*
+ * For the regression test, return predictable random values.
+ * Otherwise use a true random number generator.
+ */
+static u_int32_t
+newfs_random(void)
+{
+ static int nextnum = 1;
+
+ if (Rflag)
+ return (nextnum++);
+ return (arc4random());
+}
diff --git a/sbin/newfs/newfs.8 b/sbin/newfs/newfs.8
new file mode 100644
index 0000000..a4c0358
--- /dev/null
+++ b/sbin/newfs/newfs.8
@@ -0,0 +1,333 @@
+.\" Copyright (c) 1983, 1987, 1991, 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.
+.\" 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.
+.\"
+.\" @(#)newfs.8 8.6 (Berkeley) 5/3/95
+.\" $FreeBSD$
+.\"
+.Dd October 1, 2013
+.Dt NEWFS 8
+.Os
+.Sh NAME
+.Nm newfs
+.Nd construct a new UFS1/UFS2 file system
+.Sh SYNOPSIS
+.Nm
+.Op Fl EJNUjlnt
+.Op Fl L Ar volname
+.Op Fl O Ar filesystem-type
+.Op Fl S Ar sector-size
+.Op Fl T Ar disktype
+.Op Fl a Ar maxcontig
+.Op Fl b Ar block-size
+.Op Fl c Ar blocks-per-cylinder-group
+.Op Fl d Ar max-extent-size
+.Op Fl e Ar maxbpg
+.Op Fl f Ar frag-size
+.Op Fl g Ar avgfilesize
+.Op Fl h Ar avgfpdir
+.Op Fl i Ar bytes
+.Op Fl k Ar held-for-metadata-blocks
+.Op Fl m Ar free-space
+.Op Fl o Ar optimization
+.Op Fl p Ar partition
+.Op Fl r Ar reserved
+.Op Fl s Ar size
+.Ar special
+.Sh DESCRIPTION
+The
+.Nm
+utility is used to initialize and clear file systems before first use.
+The
+.Nm
+utility builds a file system on the specified special file.
+(We often refer to the
+.Dq special file
+as the
+.Dq disk ,
+although the special file need not be a physical disk.
+In fact, it need not even be special.)
+Typically the defaults are reasonable, however
+.Nm
+has numerous options to allow the defaults to be selectively overridden.
+.Pp
+The following options define the general layout policies:
+.Bl -tag -width indent
+.It Fl E
+Erase the content of the disk before making the filesystem.
+The reserved area in front of the superblock (for bootcode) will not be erased.
+.Pp
+This option is only relevant for flash based storage devices that use
+wear-leveling algorithms.
+.Pp
+Erasing may take a long time as it writes to every sector on the disk.
+.It Fl J
+Enable journaling on the new file system via gjournal.
+See
+.Xr gjournal 8
+for details.
+.It Fl L Ar volname
+Add a volume label to the new file system.
+.It Fl N
+Cause the file system parameters to be printed out
+without really creating the file system.
+.It Fl O Ar filesystem-type
+Use 1 to specify that a UFS1 format file system be built;
+use 2 to specify that a UFS2 format file system be built.
+The default format is UFS2.
+.It Fl T Ar disktype
+For backward compatibility.
+.It Fl U
+Enable soft updates on the new file system.
+.It Fl a Ar maxcontig
+Specify the maximum number of contiguous blocks that will be
+laid out before forcing a rotational delay.
+The default value is 16.
+See
+.Xr tunefs 8
+for more details on how to set this option.
+.It Fl b Ar block-size
+The block size of the file system, in bytes.
+It must be a power of 2.
+The
+default size is 32768 bytes, and the smallest allowable size is 4096 bytes.
+The optimal block:fragment ratio is 8:1.
+Other ratios are possible, but are not recommended,
+and may produce poor results.
+.It Fl c Ar blocks-per-cylinder-group
+The number of blocks per cylinder group in a file system.
+The default is to compute the maximum allowed by the other parameters.
+This value is
+dependent on a number of other parameters, in particular the block size
+and the number of bytes per inode.
+.It Fl d Ar max-extent-size
+The file system may choose to store large files using extents.
+This parameter specifies the largest extent size that may be used.
+The default value is the file system blocksize.
+It is presently limited to a maximum value of 16 times the
+file system blocksize and a minimum value of the file system blocksize.
+.It Fl e Ar maxbpg
+Indicate the maximum number of blocks any single file can
+allocate out of a cylinder group before it is forced to begin
+allocating blocks from another cylinder group.
+The default is about one quarter of the total blocks in a cylinder group.
+See
+.Xr tunefs 8
+for more details on how to set this option.
+.It Fl f Ar frag-size
+The fragment size of the file system in bytes.
+It must be a power of two
+ranging in value between
+.Ar blocksize Ns /8
+and
+.Ar blocksize .
+The default is 4096 bytes.
+.It Fl g Ar avgfilesize
+The expected average file size for the file system.
+.It Fl h Ar avgfpdir
+The expected average number of files per directory on the file system.
+.It Fl i Ar bytes
+Specify the density of inodes in the file system.
+The default is to create an inode for every
+.Pq 4 * Ar frag-size
+bytes of data space.
+If fewer inodes are desired, a larger number should be used;
+to create more inodes a smaller number should be given.
+One inode is required for each distinct file, so this value effectively
+specifies the average file size on the file system.
+.It Fl j
+Enable soft updates journaling on the new file system.
+This flag is implemented by running the
+.Xr tunefs 8
+utility found in the user's
+.Dv $PATH .
+.It Fl k Ar held-for-metadata-blocks
+Set the amount of space to be held for metadata blocks in each cylinder group.
+When set, the file system preference routines will try to save
+the specified amount of space immediately following the inode blocks
+in each cylinder group for use by metadata blocks.
+Clustering the metadata blocks speeds up random file access
+and decreases the running time of
+.Xr fsck 8 .
+By default
+.Xr newfs 8
+sets it to half of the space reserved to minfree.
+.It Fl l
+Enable multilabel MAC on the new file system.
+.It Fl m Ar free-space
+The percentage of space reserved from normal users; the minimum free
+space threshold.
+The default value used is
+defined by
+.Dv MINFREE
+from
+.In ufs/ffs/fs.h ,
+currently 8%.
+See
+.Xr tunefs 8
+for more details on how to set this option.
+.It Fl n
+Do not create a
+.Pa .snap
+directory on the new file system.
+The resulting file system will not support snapshot generation, so
+.Xr dump 8
+in live mode and background
+.Xr fsck 8
+will not function properly.
+The traditional
+.Xr fsck 8
+and offline
+.Xr dump 8
+will work on the file system.
+This option is intended primarily for memory or vnode-backed file systems that
+do not require
+.Xr dump 8
+or
+.Xr fsck 8
+support.
+.It Fl o Ar optimization
+.Cm ( space
+or
+.Cm time ) .
+The file system can either be instructed to try to minimize the time spent
+allocating blocks, or to try to minimize the space fragmentation on the disk.
+If the value of minfree (see above) is less than 8%,
+the default is to optimize for
+.Cm space ;
+if the value of minfree is greater than or equal to 8%,
+the default is to optimize for
+.Cm time .
+See
+.Xr tunefs 8
+for more details on how to set this option.
+.It Fl p Ar partition
+The partition name (a..h) you want to use in case the underlying image
+is a file, so you do not have access to individual partitions through the
+filesystem.
+Can also be used with a device, e.g.
+.Nm
+.Fl p Ar f
+.Ar /dev/da1s3
+is equivalent to
+.Nm
+.Ar /dev/da1s3f .
+.It Fl r Ar reserved
+The size, in sectors, of reserved space
+at the end of the partition specified in
+.Ar special .
+This space will not be occupied by the file system;
+it can be used by other consumers such as
+.Xr geom 4 .
+Defaults to 0.
+.It Fl s Ar size
+The size of the file system in sectors.
+This value defaults to the size of the
+raw partition specified in
+.Ar special
+less the
+.Ar reserved
+space at its end (see
+.Fl r ) .
+A
+.Ar size
+of 0 can also be used to choose the default value.
+A valid
+.Ar size
+value cannot be larger than the default one,
+which means that the file system cannot extend into the reserved space.
+.It Fl t
+Turn on the TRIM enable flag.
+If enabled, and if the underlying device supports the BIO_DELETE
+command, the file system will send a delete request to the underlying
+device for each freed block.
+The trim enable flag is typically set when the underlying device
+uses flash-memory as the device can use the delete command to
+pre-zero or at least avoid copying blocks that have been deleted.
+.El
+.Pp
+The following options override the standard sizes for the disk geometry.
+Their default values are taken from the disk label.
+Changing these defaults is useful only when using
+.Nm
+to build a file system whose raw image will eventually be used on a
+different type of disk than the one on which it is initially created
+(for example on a write-once disk).
+Note that changing any of these values from their defaults will make
+it impossible for
+.Xr fsck 8
+to find the alternate superblocks if the standard superblock is lost.
+.Bl -tag -width indent
+.It Fl S Ar sector-size
+The size of a sector in bytes (almost never anything but 512).
+.El
+.Sh EXAMPLES
+.Dl newfs /dev/ada3s1a
+.Pp
+Creates a new ufs file system on
+.Pa ada3s1a .
+The
+.Nm
+utility will use a block size of 32768 bytes, a fragment size of 4096 bytes
+and the largest possible number of blocks per cylinders group.
+These values tend to produce better performance for most applications
+than the historical defaults
+(8192 byte block size and 1024 byte fragment size).
+This large fragment size may lead to much wasted space
+on file systems that contain many small files.
+.Sh SEE ALSO
+.Xr fdformat 1 ,
+.Xr geom 4 ,
+.Xr disktab 5 ,
+.Xr fs 5 ,
+.Xr bsdlabel 8 ,
+.Xr camcontrol 8 ,
+.Xr dump 8 ,
+.Xr dumpfs 8 ,
+.Xr fsck 8 ,
+.Xr gjournal 8 ,
+.Xr growfs 8 ,
+.Xr gvinum 8 ,
+.Xr makefs 8 ,
+.Xr mount 8 ,
+.Xr tunefs 8
+.Rs
+.%A M. McKusick
+.%A W. Joy
+.%A S. Leffler
+.%A R. Fabry
+.%T A Fast File System for UNIX
+.%J ACM Transactions on Computer Systems 2
+.%V 3
+.%P pp 181-197
+.%D August 1984
+.%O (reprinted in the BSD System Manager's Manual)
+.Re
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Bx 4.2 .
diff --git a/sbin/newfs/newfs.c b/sbin/newfs/newfs.c
new file mode 100644
index 0000000..658bd42
--- /dev/null
+++ b/sbin/newfs/newfs.c
@@ -0,0 +1,509 @@
+/*
+ * Copyright (c) 2002 Networks Associates Technology, Inc.
+ * All rights reserved.
+ *
+ * This software was developed for the FreeBSD Project by Marshall
+ * Kirk McKusick and Network Associates Laboratories, the Security
+ * Research Division of Network Associates, Inc. under DARPA/SPAWAR
+ * contract N66001-01-C-8035 ("CBOSS"), as part of the DARPA CHATS
+ * research program.
+ *
+ * Copyright (c) 1983, 1989, 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.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if 0
+#ifndef lint
+static const char copyright[] =
+"@(#) Copyright (c) 1983, 1989, 1993, 1994\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static char sccsid[] = "@(#)newfs.c 8.13 (Berkeley) 5/1/95";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*
+ * newfs: friendly front end to mkfs
+ */
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/disk.h>
+#include <sys/disklabel.h>
+#include <sys/file.h>
+#include <sys/mount.h>
+
+#include <ufs/ufs/dir.h>
+#include <ufs/ufs/dinode.h>
+#include <ufs/ffs/fs.h>
+#include <ufs/ufs/ufsmount.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <paths.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include <libutil.h>
+
+#include "newfs.h"
+
+int Eflag; /* Erase previous disk contents */
+int Lflag; /* add a volume label */
+int Nflag; /* run without writing file system */
+int Oflag = 2; /* file system format (1 => UFS1, 2 => UFS2) */
+int Rflag; /* regression test */
+int Uflag; /* enable soft updates for file system */
+int jflag; /* enable soft updates journaling for filesys */
+int Xflag = 0; /* exit in middle of newfs for testing */
+int Jflag; /* enable gjournal for file system */
+int lflag; /* enable multilabel for file system */
+int nflag; /* do not create .snap directory */
+int tflag; /* enable TRIM */
+intmax_t fssize; /* file system size */
+off_t mediasize; /* device size */
+int sectorsize; /* bytes/sector */
+int realsectorsize; /* bytes/sector in hardware */
+int fsize = 0; /* fragment size */
+int bsize = 0; /* block size */
+int maxbsize = 0; /* maximum clustering */
+int maxblkspercg = MAXBLKSPERCG; /* maximum blocks per cylinder group */
+int minfree = MINFREE; /* free space threshold */
+int metaspace; /* space held for metadata blocks */
+int opt = DEFAULTOPT; /* optimization preference (space or time) */
+int density; /* number of bytes per inode */
+int maxcontig = 0; /* max contiguous blocks to allocate */
+int maxbpg; /* maximum blocks per file in a cyl group */
+int avgfilesize = AVFILESIZ;/* expected average file size */
+int avgfilesperdir = AFPDIR;/* expected number of files per directory */
+u_char *volumelabel = NULL; /* volume label for filesystem */
+struct uufsd disk; /* libufs disk structure */
+
+static char device[MAXPATHLEN];
+static u_char bootarea[BBSIZE];
+static int is_file; /* work on a file, not a device */
+static char *dkname;
+static char *disktype;
+
+static void getfssize(intmax_t *, const char *p, intmax_t, intmax_t);
+static struct disklabel *getdisklabel(char *s);
+static void usage(void);
+static int expand_number_int(const char *buf, int *num);
+
+ufs2_daddr_t part_ofs; /* partition offset in blocks, used with files */
+
+int
+main(int argc, char *argv[])
+{
+ struct partition *pp;
+ struct disklabel *lp;
+ struct partition oldpartition;
+ struct stat st;
+ char *cp, *special;
+ intmax_t reserved;
+ int ch, i, rval;
+ char part_name; /* partition name, default to full disk */
+
+ part_name = 'c';
+ reserved = 0;
+ while ((ch = getopt(argc, argv,
+ "EJL:NO:RS:T:UXa:b:c:d:e:f:g:h:i:jk:lm:no:p:r:s:t")) != -1)
+ switch (ch) {
+ case 'E':
+ Eflag = 1;
+ break;
+ case 'J':
+ Jflag = 1;
+ break;
+ case 'L':
+ volumelabel = optarg;
+ i = -1;
+ while (isalnum(volumelabel[++i]));
+ if (volumelabel[i] != '\0') {
+ errx(1, "bad volume label. Valid characters are alphanumerics.");
+ }
+ if (strlen(volumelabel) >= MAXVOLLEN) {
+ errx(1, "bad volume label. Length is longer than %d.",
+ MAXVOLLEN);
+ }
+ Lflag = 1;
+ break;
+ case 'N':
+ Nflag = 1;
+ break;
+ case 'O':
+ if ((Oflag = atoi(optarg)) < 1 || Oflag > 2)
+ errx(1, "%s: bad file system format value",
+ optarg);
+ break;
+ case 'R':
+ Rflag = 1;
+ break;
+ case 'S':
+ rval = expand_number_int(optarg, &sectorsize);
+ if (rval < 0 || sectorsize <= 0)
+ errx(1, "%s: bad sector size", optarg);
+ break;
+ case 'T':
+ disktype = optarg;
+ break;
+ case 'j':
+ jflag = 1;
+ /* fall through to enable soft updates */
+ case 'U':
+ Uflag = 1;
+ break;
+ case 'X':
+ Xflag++;
+ break;
+ case 'a':
+ rval = expand_number_int(optarg, &maxcontig);
+ if (rval < 0 || maxcontig <= 0)
+ errx(1, "%s: bad maximum contiguous blocks",
+ optarg);
+ break;
+ case 'b':
+ rval = expand_number_int(optarg, &bsize);
+ if (rval < 0)
+ errx(1, "%s: bad block size",
+ optarg);
+ if (bsize < MINBSIZE)
+ errx(1, "%s: block size too small, min is %d",
+ optarg, MINBSIZE);
+ if (bsize > MAXBSIZE)
+ errx(1, "%s: block size too large, max is %d",
+ optarg, MAXBSIZE);
+ break;
+ case 'c':
+ rval = expand_number_int(optarg, &maxblkspercg);
+ if (rval < 0 || maxblkspercg <= 0)
+ errx(1, "%s: bad blocks per cylinder group",
+ optarg);
+ break;
+ case 'd':
+ rval = expand_number_int(optarg, &maxbsize);
+ if (rval < 0 || maxbsize < MINBSIZE)
+ errx(1, "%s: bad extent block size", optarg);
+ break;
+ case 'e':
+ rval = expand_number_int(optarg, &maxbpg);
+ if (rval < 0 || maxbpg <= 0)
+ errx(1, "%s: bad blocks per file in a cylinder group",
+ optarg);
+ break;
+ case 'f':
+ rval = expand_number_int(optarg, &fsize);
+ if (rval < 0 || fsize <= 0)
+ errx(1, "%s: bad fragment size", optarg);
+ break;
+ case 'g':
+ rval = expand_number_int(optarg, &avgfilesize);
+ if (rval < 0 || avgfilesize <= 0)
+ errx(1, "%s: bad average file size", optarg);
+ break;
+ case 'h':
+ rval = expand_number_int(optarg, &avgfilesperdir);
+ if (rval < 0 || avgfilesperdir <= 0)
+ errx(1, "%s: bad average files per dir", optarg);
+ break;
+ case 'i':
+ rval = expand_number_int(optarg, &density);
+ if (rval < 0 || density <= 0)
+ errx(1, "%s: bad bytes per inode", optarg);
+ break;
+ case 'l':
+ lflag = 1;
+ break;
+ case 'k':
+ if ((metaspace = atoi(optarg)) < 0)
+ errx(1, "%s: bad metadata space %%", optarg);
+ if (metaspace == 0)
+ /* force to stay zero in mkfs */
+ metaspace = -1;
+ break;
+ case 'm':
+ if ((minfree = atoi(optarg)) < 0 || minfree > 99)
+ errx(1, "%s: bad free space %%", optarg);
+ break;
+ case 'n':
+ nflag = 1;
+ break;
+ case 'o':
+ if (strcmp(optarg, "space") == 0)
+ opt = FS_OPTSPACE;
+ else if (strcmp(optarg, "time") == 0)
+ opt = FS_OPTTIME;
+ else
+ errx(1,
+ "%s: unknown optimization preference: use `space' or `time'",
+ optarg);
+ break;
+ case 'r':
+ errno = 0;
+ reserved = strtoimax(optarg, &cp, 0);
+ if (errno != 0 || cp == optarg ||
+ *cp != '\0' || reserved < 0)
+ errx(1, "%s: bad reserved size", optarg);
+ break;
+ case 'p':
+ is_file = 1;
+ part_name = optarg[0];
+ break;
+
+ case 's':
+ errno = 0;
+ fssize = strtoimax(optarg, &cp, 0);
+ if (errno != 0 || cp == optarg ||
+ *cp != '\0' || fssize < 0)
+ errx(1, "%s: bad file system size", optarg);
+ break;
+ case 't':
+ tflag = 1;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 1)
+ usage();
+
+ special = argv[0];
+ if (!special[0])
+ err(1, "empty file/special name");
+ cp = strrchr(special, '/');
+ if (cp == 0) {
+ /*
+ * No path prefix; try prefixing _PATH_DEV.
+ */
+ snprintf(device, sizeof(device), "%s%s", _PATH_DEV, special);
+ special = device;
+ }
+
+ if (is_file) {
+ /* bypass ufs_disk_fillout_blank */
+ bzero( &disk, sizeof(disk));
+ disk.d_bsize = 1;
+ disk.d_name = special;
+ disk.d_fd = open(special, O_RDONLY);
+ if (disk.d_fd < 0 ||
+ (!Nflag && ufs_disk_write(&disk) == -1))
+ errx(1, "%s: ", special);
+ } else if (ufs_disk_fillout_blank(&disk, special) == -1 ||
+ (!Nflag && ufs_disk_write(&disk) == -1)) {
+ if (disk.d_error != NULL)
+ errx(1, "%s: %s", special, disk.d_error);
+ else
+ err(1, "%s", special);
+ }
+ if (fstat(disk.d_fd, &st) < 0)
+ err(1, "%s", special);
+ if ((st.st_mode & S_IFMT) != S_IFCHR) {
+ warn("%s: not a character-special device", special);
+ is_file = 1; /* assume it is a file */
+ dkname = special;
+ if (sectorsize == 0)
+ sectorsize = 512;
+ mediasize = st.st_size;
+ /* set fssize from the partition */
+ } else {
+ if (sectorsize == 0)
+ if (ioctl(disk.d_fd, DIOCGSECTORSIZE, &sectorsize) == -1)
+ sectorsize = 0; /* back out on error for safety */
+ if (sectorsize && ioctl(disk.d_fd, DIOCGMEDIASIZE, &mediasize) != -1)
+ getfssize(&fssize, special, mediasize / sectorsize, reserved);
+ }
+ pp = NULL;
+ lp = getdisklabel(special);
+ if (lp != NULL) {
+ if (!is_file) /* already set for files */
+ part_name = special[strlen(special) - 1];
+ if ((part_name < 'a' || part_name - 'a' >= MAXPARTITIONS) &&
+ !isdigit(part_name))
+ errx(1, "%s: can't figure out file system partition",
+ special);
+ cp = &part_name;
+ if (isdigit(*cp))
+ pp = &lp->d_partitions[RAW_PART];
+ else
+ pp = &lp->d_partitions[*cp - 'a'];
+ oldpartition = *pp;
+ if (pp->p_size == 0)
+ errx(1, "%s: `%c' partition is unavailable",
+ special, *cp);
+ if (pp->p_fstype == FS_BOOT)
+ errx(1, "%s: `%c' partition overlaps boot program",
+ special, *cp);
+ getfssize(&fssize, special, pp->p_size, reserved);
+ if (sectorsize == 0)
+ sectorsize = lp->d_secsize;
+ if (fsize == 0)
+ fsize = pp->p_fsize;
+ if (bsize == 0)
+ bsize = pp->p_frag * pp->p_fsize;
+ if (is_file)
+ part_ofs = pp->p_offset;
+ }
+ if (sectorsize <= 0)
+ errx(1, "%s: no default sector size", special);
+ if (fsize <= 0)
+ fsize = MAX(DFL_FRAGSIZE, sectorsize);
+ if (bsize <= 0)
+ bsize = MIN(DFL_BLKSIZE, 8 * fsize);
+ if (minfree < MINFREE && opt != FS_OPTSPACE) {
+ fprintf(stderr, "Warning: changing optimization to space ");
+ fprintf(stderr, "because minfree is less than %d%%\n", MINFREE);
+ opt = FS_OPTSPACE;
+ }
+ realsectorsize = sectorsize;
+ if (sectorsize != DEV_BSIZE) { /* XXX */
+ int secperblk = sectorsize / DEV_BSIZE;
+
+ sectorsize = DEV_BSIZE;
+ fssize *= secperblk;
+ if (pp != NULL)
+ pp->p_size *= secperblk;
+ }
+ mkfs(pp, special);
+ ufs_disk_close(&disk);
+ if (!jflag)
+ exit(0);
+ if (execlp("tunefs", "newfs", "-j", "enable", special, NULL) < 0)
+ err(1, "Cannot enable soft updates journaling, tunefs");
+ /* NOT REACHED */
+}
+
+void
+getfssize(intmax_t *fsz, const char *s, intmax_t disksize, intmax_t reserved)
+{
+ intmax_t available;
+
+ available = disksize - reserved;
+ if (available <= 0)
+ errx(1, "%s: reserved not less than device size %jd",
+ s, disksize);
+ if (*fsz == 0)
+ *fsz = available;
+ else if (*fsz > available)
+ errx(1, "%s: maximum file system size is %jd",
+ s, available);
+}
+
+struct disklabel *
+getdisklabel(char *s)
+{
+ static struct disklabel lab;
+ struct disklabel *lp;
+
+ if (is_file) {
+ if (read(disk.d_fd, bootarea, BBSIZE) != BBSIZE)
+ err(4, "cannot read bootarea");
+ if (bsd_disklabel_le_dec(
+ bootarea + (0 /* labeloffset */ +
+ 1 /* labelsoffset */ * sectorsize),
+ &lab, MAXPARTITIONS))
+ errx(1, "no valid label found");
+
+ lp = &lab;
+ return &lab;
+ }
+
+ if (disktype) {
+ lp = getdiskbyname(disktype);
+ if (lp != NULL)
+ return (lp);
+ }
+ return (NULL);
+}
+
+static void
+usage()
+{
+ fprintf(stderr,
+ "usage: %s [ -fsoptions ] special-device%s\n",
+ getprogname(),
+ " [device-type]");
+ fprintf(stderr, "where fsoptions are:\n");
+ fprintf(stderr, "\t-E Erase previous disk content\n");
+ fprintf(stderr, "\t-J Enable journaling via gjournal\n");
+ fprintf(stderr, "\t-L volume label to add to superblock\n");
+ fprintf(stderr,
+ "\t-N do not create file system, just print out parameters\n");
+ fprintf(stderr, "\t-O file system format: 1 => UFS1, 2 => UFS2\n");
+ fprintf(stderr, "\t-R regression test, suppress random factors\n");
+ fprintf(stderr, "\t-S sector size\n");
+ fprintf(stderr, "\t-T disktype\n");
+ fprintf(stderr, "\t-U enable soft updates\n");
+ fprintf(stderr, "\t-a maximum contiguous blocks\n");
+ fprintf(stderr, "\t-b block size\n");
+ fprintf(stderr, "\t-c blocks per cylinders group\n");
+ fprintf(stderr, "\t-d maximum extent size\n");
+ fprintf(stderr, "\t-e maximum blocks per file in a cylinder group\n");
+ fprintf(stderr, "\t-f frag size\n");
+ fprintf(stderr, "\t-g average file size\n");
+ fprintf(stderr, "\t-h average files per directory\n");
+ fprintf(stderr, "\t-i number of bytes per inode\n");
+ fprintf(stderr, "\t-j enable soft updates journaling\n");
+ fprintf(stderr, "\t-k space to hold for metadata blocks\n");
+ fprintf(stderr, "\t-l enable multilabel MAC\n");
+ fprintf(stderr, "\t-n do not create .snap directory\n");
+ fprintf(stderr, "\t-m minimum free space %%\n");
+ fprintf(stderr, "\t-o optimization preference (`space' or `time')\n");
+ fprintf(stderr, "\t-p partition name (a..h)\n");
+ fprintf(stderr, "\t-r reserved sectors at the end of device\n");
+ fprintf(stderr, "\t-s file system size (sectors)\n");
+ fprintf(stderr, "\t-t enable TRIM\n");
+ exit(1);
+}
+
+static int
+expand_number_int(const char *buf, int *num)
+{
+ int64_t num64;
+ int rval;
+
+ rval = expand_number(buf, &num64);
+ if (rval < 0)
+ return (rval);
+ if (num64 > INT_MAX || num64 < INT_MIN) {
+ errno = ERANGE;
+ return (-1);
+ }
+ *num = (int)num64;
+ return (0);
+}
diff --git a/sbin/newfs/newfs.h b/sbin/newfs/newfs.h
new file mode 100644
index 0000000..72f4314
--- /dev/null
+++ b/sbin/newfs/newfs.h
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2002 Networks Associates Technology, Inc.
+ * All rights reserved.
+ *
+ * This software was developed for the FreeBSD Project by Marshall
+ * Kirk McKusick and Network Associates Laboratories, the Security
+ * Research Division of Network Associates, Inc. under DARPA/SPAWAR
+ * contract N66001-01-C-8035 ("CBOSS"), as part of the DARPA CHATS
+ * research program.
+ *
+ * Copyright (c) 1980, 1989, 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.
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+#include <libufs.h>
+
+/*
+ * The following two constants set the default block and fragment sizes.
+ * Both constants must be a power of 2 and meet the following constraints:
+ * MINBSIZE <= DESBLKSIZE <= MAXBSIZE
+ * sectorsize <= DESFRAGSIZE <= DESBLKSIZE
+ * DESBLKSIZE / DESFRAGSIZE <= 8
+ */
+#define DFL_FRAGSIZE 4096
+#define DFL_BLKSIZE 32768
+
+/*
+ * Cylinder groups may have up to MAXBLKSPERCG blocks. The actual
+ * number used depends upon how much information can be stored
+ * in a cylinder group map which must fit in a single file system
+ * block. The default is to use as many as possible blocks per group.
+ */
+#define MAXBLKSPERCG 0x7fffffff /* desired fs_fpg ("infinity") */
+
+/*
+ * MAXBLKPG determines the maximum number of data blocks which are
+ * placed in a single cylinder group. The default is one indirect
+ * block worth of data blocks.
+ */
+#define MAXBLKPG(bsize) ((bsize) / sizeof(ufs2_daddr_t))
+
+/*
+ * Each file system has a number of inodes statically allocated.
+ * We allocate one inode slot per NFPI fragments, expecting this
+ * to be far more than we will ever need.
+ */
+#define NFPI 2
+
+/*
+ * variables set up by front end.
+ */
+extern int Eflag; /* Erase previous disk contents */
+extern int Lflag; /* add a volume label */
+extern int Nflag; /* run mkfs without writing file system */
+extern int Oflag; /* build UFS1 format file system */
+extern int Rflag; /* regression test */
+extern int Uflag; /* enable soft updates for file system */
+extern int jflag; /* enable soft updates journaling for filesys */
+extern int Xflag; /* exit in middle of newfs for testing */
+extern int Jflag; /* enable gjournal for file system */
+extern int lflag; /* enable multilabel MAC for file system */
+extern int nflag; /* do not create .snap directory */
+extern int tflag; /* enable TRIM */
+extern intmax_t fssize; /* file system size */
+extern off_t mediasize; /* device size */
+extern int sectorsize; /* bytes/sector */
+extern int realsectorsize; /* bytes/sector in hardware*/
+extern int fsize; /* fragment size */
+extern int bsize; /* block size */
+extern int maxbsize; /* maximum clustering */
+extern int maxblkspercg; /* maximum blocks per cylinder group */
+extern int minfree; /* free space threshold */
+extern int metaspace; /* space held for metadata blocks */
+extern int opt; /* optimization preference (space or time) */
+extern int density; /* number of bytes per inode */
+extern int maxcontig; /* max contiguous blocks to allocate */
+extern int maxbpg; /* maximum blocks per file in a cyl group */
+extern int avgfilesize; /* expected average file size */
+extern int avgfilesperdir; /* expected number of files per directory */
+extern u_char *volumelabel; /* volume label for filesystem */
+extern struct uufsd disk; /* libufs disk structure */
+
+/*
+ * To override a limitation in libufs, export the offset (in sectors) of the
+ * partition on the underlying media (file or disk). The value is used as
+ * an offset for all accesses to the media through bread(), which is only
+ * invoked directly in this program.
+ * For bwrite() we need a different approach, namely override the library
+ * version with one defined here. This is because bwrite() is called also
+ * by the library function sbwrite() which we cannot intercept nor want to
+ * rewrite. As a consequence, the internal version of bwrite() adds the
+ * partition offset itself when calling the underlying function, pwrite().
+ *
+ * XXX This info really ought to go into the struct uufsd, at which point
+ * we can remove the above hack.
+ */
+extern ufs2_daddr_t part_ofs; /* partition offset in blocks */
+
+void mkfs (struct partition *, char *);
diff --git a/sbin/newfs/ref.test b/sbin/newfs/ref.test
new file mode 100644
index 0000000..1967e24
--- /dev/null
+++ b/sbin/newfs/ref.test
@@ -0,0 +1,7 @@
+# $FreeBSD$
+00c08266df6b0c79d2673515c182216a
+c00458f223a9119190591e8b8679bf97
+7d5b3c75244898dbb07a4cd20860c8a1
+a69179c925b67edc20c289c3321ae87a
+4d1c6cf3c563044a59c3d426bb890ece
+841ed8884da029d4590b56b2f033f404
diff --git a/sbin/newfs/runtest00.sh b/sbin/newfs/runtest00.sh
new file mode 100644
index 0000000..a95dbcc
--- /dev/null
+++ b/sbin/newfs/runtest00.sh
@@ -0,0 +1,19 @@
+#!/bin/sh
+# $FreeBSD$
+
+set -e
+
+MD=99
+(
+for s in 1m 4m 60m 120m 240m 1g
+do
+ (
+ mdconfig -d -u $MD || true
+ mdconfig -a -t malloc -s $s -u $MD
+ disklabel -r -w md$MD auto
+ ./newfs -R /dev/md${MD}c
+ ) 1>&2
+ md5 < /dev/md${MD}c
+done
+mdconfig -d -u $MD 1>&2 || true
+)
diff --git a/sbin/newfs/runtest01.sh b/sbin/newfs/runtest01.sh
new file mode 100644
index 0000000..4712832
--- /dev/null
+++ b/sbin/newfs/runtest01.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+# $FreeBSD$
+
+set -e
+
+MD=99
+ME=98
+s=1m
+mdconfig -d -u $MD || true
+mdconfig -d -u $ME || true
+mdconfig -a -t malloc -s $s -u $MD
+mdconfig -a -t malloc -s $s -u $ME
+disklabel -r -w md$MD auto
+disklabel -r -w md$ME auto
+./newfs -R /dev/md${MD}c
+./newfs -R /dev/md${ME}c
+if cmp /dev/md${MD}c /dev/md${ME}c ; then
+ echo "Test passed"
+ e=0
+else
+ echo "Test failed"
+ e=1
+fi
+mdconfig -d -u $MD || true
+mdconfig -d -u $ME || true
+exit $e
+
diff --git a/sbin/newfs_msdos/Makefile b/sbin/newfs_msdos/Makefile
new file mode 100644
index 0000000..0a9ffb4
--- /dev/null
+++ b/sbin/newfs_msdos/Makefile
@@ -0,0 +1,11 @@
+# $FreeBSD$
+
+PROG= newfs_msdos
+MAN= newfs_msdos.8
+
+# XXX - this is verboten
+.if ${MACHINE_CPUARCH} == "arm"
+WARNS?= 3
+.endif
+
+.include <bsd.prog.mk>
diff --git a/sbin/newfs_msdos/newfs_msdos.8 b/sbin/newfs_msdos/newfs_msdos.8
new file mode 100644
index 0000000..0f1abb4
--- /dev/null
+++ b/sbin/newfs_msdos/newfs_msdos.8
@@ -0,0 +1,241 @@
+.\" Copyright (c) 1998 Robert Nordier
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd April 9, 2015
+.Dt NEWFS_MSDOS 8
+.Os
+.Sh NAME
+.Nm newfs_msdos
+.Nd construct a new MS-DOS (FAT) file system
+.Sh SYNOPSIS
+.Nm
+.Op Fl N
+.Op Fl @ Ar offset
+.Op Fl B Ar boot
+.Op Fl C Ar create-size
+.Op Fl F Ar FAT-type
+.Op Fl I Ar VolumeId
+.Op Fl L Ar label
+.Op Fl O Ar OEM
+.Op Fl S Ar sector-size
+.Op Fl a Ar FAT-size
+.Op Fl b Ar block-size
+.Op Fl c Ar cluster-size
+.Op Fl e Ar DirEnts
+.Op Fl f Ar format
+.Op Fl h Ar heads
+.Op Fl i Ar info
+.Op Fl k Ar backup
+.Op Fl m Ar media
+.Op Fl n Ar FATs
+.Op Fl o Ar hidden
+.Op Fl r Ar reserved
+.Op Fl s Ar total
+.Op Fl u Ar track-size
+.Ar special
+.Op Ar disktype
+.Sh DESCRIPTION
+The
+.Nm
+utility creates a FAT12, FAT16, or FAT32 file system on device or file named
+.Ar special ,
+using
+.Xr disktab 5
+entry
+.Ar disktype
+to determine geometry, if required.
+.Pp
+If
+.Ar special
+does not contain a
+.Ar /
+and
+.Fl C
+is not used, it is assumed to be a device name and
+.Ar /dev/
+is prepended to the name to construct the actual device name.
+To work a file in the current directory use
+.Ar ./filename
+.Pp
+The options are as follow:
+.Bl -tag -width indent
+.It Fl N
+Do not create a file system: just print out parameters.
+.It Fl @ Ar offset
+Build the filesystem at the specified offset in bytes in the device or file.
+A suffix s, k, m, g (lower or upper case)
+appended to the offset specifies that the
+number is in sectors, kilobytes, megabytes or gigabytes, respectively.
+.It Fl B Ar boot
+Get bootstrap from file.
+.It Fl C Ar create-size
+Create the image file with the specified size.
+A suffix character appended to the size is interpreted as for the
+.Fl @
+option.
+The file is created by truncating any existing file with the same name and
+resizing it to the requested size.
+If the file system supports sparse files, the space occupied on disk may be
+smaller than the size specified as parameter.
+.It Fl F Ar FAT-type
+FAT type (one of 12, 16, or 32).
+.It Fl I Ar VolumeID
+Volume ID, a 32 bit number in decimal or hexadecimal (0x...) format.
+.It Fl L Ar label
+Volume label (up to 11 characters).
+The label should consist of
+only those characters permitted in regular DOS (8+3) filenames.
+.It Fl O Ar OEM
+OEM string (up to 8 characters).
+The default is
+.Qq Li "BSD4.4 " .
+.It Fl S Ar sector-size
+Number of bytes per sector.
+Acceptable values are powers of 2
+in the range 512 through 32768, inclusive.
+.It Fl a Ar FAT-size
+Number of sectors per FAT.
+.It Fl b Ar block-size
+File system block size (bytes per cluster).
+This should resolve to an
+acceptable number of sectors per cluster (see below).
+.It Fl c Ar cluster-size
+Sectors per cluster.
+Acceptable values are powers of 2 in the range
+1 through 128.
+If the block or cluster size are not specified, the code
+uses a cluster between 512 bytes and 32K depending on
+the filesystem size.
+.It Fl e Ar DirEnts
+Number of root directory entries (FAT12 and FAT16 only).
+.It Fl f Ar format
+Specify a standard (floppy disk) format.
+The standard formats
+are (capacities in kilobytes): 160, 180, 320, 360, 640, 720, 1200,
+1232, 1440, 2880.
+.It Fl h Ar heads
+Number of drive heads.
+.It Fl i Ar info
+Location of the file system info sector (FAT32 only).
+A value of 0xffff signifies no info sector.
+.It Fl k Ar backup
+Location of the backup boot sector (FAT32 only).
+A value
+of 0xffff signifies no backup sector.
+.It Fl m Ar media
+Media descriptor (acceptable range 0xf0 to 0xff).
+.It Fl n Ar FATs
+Number of FATs.
+Acceptable values are 1 to 16 inclusive.
+The default
+is 2.
+.It Fl o Ar hidden
+Number of hidden sectors.
+.It Fl r Ar reserved
+Number of reserved sectors.
+.It Fl s Ar total
+File system size.
+.It Fl u Ar track-size
+Number of sectors per track.
+.El
+.Sh NOTES
+If some parameters (e.g. size, number of sectors, etc.) are not specified
+through options or disktype, the program tries to generate them automatically.
+In particular, the size is determined as the device or file size minus the
+offset specified with the
+.Fl @
+option.
+When the geometry is not available, it is assumed to be 63 sectors, 255 heads.
+The size is then rounded to become a multiple of the track size and avoid
+complaints by some filesystem code.
+.Pp
+FAT file system parameters occupy a "Boot Sector BPB (BIOS Parameter
+Block)" in the first of the "reserved" sectors which precede the actual
+file system.
+For reference purposes, this structure is presented
+below.
+.Bd -literal
+struct bsbpb {
+ uint16_t bpbBytesPerSec; /* [-S] bytes per sector */
+ uint8_t bpbSecPerClust; /* [-c] sectors per cluster */
+ uint16_t bpbResSectors; /* [-r] reserved sectors */
+ uint8_t bpbFATs; /* [-n] number of FATs */
+ uint16_t bpbRootDirEnts; /* [-e] root directory entries */
+ uint16_t bpbSectors; /* [-s] total sectors */
+ uint8_t bpbMedia; /* [-m] media descriptor */
+ uint16_t bpbFATsecs; /* [-a] sectors per FAT */
+ uint16_t bpbSecPerTrack; /* [-u] sectors per track */
+ uint16_t bpbHeads; /* [-h] drive heads */
+ uint32_t bpbHiddenSecs; /* [-o] hidden sectors */
+ uint32_t bpbHugeSectors; /* [-s] big total sectors */
+};
+/* FAT32 extensions */
+struct bsxbpb {
+ uint32_t bpbBigFATsecs; /* [-a] big sectors per FAT */
+ uint16_t bpbExtFlags; /* control flags */
+ uint16_t bpbFSVers; /* file system version */
+ uint32_t bpbRootClust; /* root directory start cluster */
+ uint16_t bpbFSInfo; /* [-i] file system info sector */
+ uint16_t bpbBackup; /* [-k] backup boot sector */
+};
+.Ed
+.Sh LIMITATION
+The maximum file size is 4GB, even if the file system itself is bigger.
+.Sh EXIT STATUS
+Exit status is 0 on success and 1 on error.
+.Sh EXAMPLES
+Create a file system, using default parameters, on
+.Pa /dev/ada0s1 :
+.Bd -literal -offset indent
+newfs_msdos /dev/ada0s1
+.Ed
+.Pp
+Create a standard 1.44M file system, with volume label
+.Ar foo ,
+on
+.Pa /dev/fd0 :
+.Bd -literal -offset indent
+newfs_msdos -f 1440 -L foo fd0
+.Ed
+.Pp
+Create a 30MB image file, with the FAT partition starting
+63 sectors within the image file:
+.Bd -literal -offset indent
+newfs_msdos -C 30M -@63s ./somefile
+.Ed
+.Sh SEE ALSO
+.Xr disktab 5 ,
+.Xr disklabel 8 ,
+.Xr fdisk 8 ,
+.Xr newfs 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 3.0 .
+.Sh AUTHORS
+.An Robert Nordier Aq Mt rnordier@FreeBSD.org
diff --git a/sbin/newfs_msdos/newfs_msdos.c b/sbin/newfs_msdos/newfs_msdos.c
new file mode 100644
index 0000000..4685782
--- /dev/null
+++ b/sbin/newfs_msdos/newfs_msdos.c
@@ -0,0 +1,1061 @@
+/*
+ * Copyright (c) 1998 Robert Nordier
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) 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 lint
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/fdcio.h>
+#include <sys/disk.h>
+#include <sys/disklabel.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <paths.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#define MAXU16 0xffff /* maximum unsigned 16-bit quantity */
+#define BPN 4 /* bits per nibble */
+#define NPB 2 /* nibbles per byte */
+
+#define DOSMAGIC 0xaa55 /* DOS magic number */
+#define MINBPS 512 /* minimum bytes per sector */
+#define MAXSPC 128 /* maximum sectors per cluster */
+#define MAXNFT 16 /* maximum number of FATs */
+#define DEFBLK 4096 /* default block size */
+#define DEFBLK16 2048 /* default block size FAT16 */
+#define DEFRDE 512 /* default root directory entries */
+#define RESFTE 2 /* reserved FAT entries */
+#define MINCLS12 1U /* minimum FAT12 clusters */
+#define MINCLS16 0xff5U /* minimum FAT16 clusters */
+#define MINCLS32 0xfff5U /* minimum FAT32 clusters */
+#define MAXCLS12 0xff4U /* maximum FAT12 clusters */
+#define MAXCLS16 0xfff4U /* maximum FAT16 clusters */
+#define MAXCLS32 0xffffff4U /* maximum FAT32 clusters */
+
+#define mincls(fat) ((fat) == 12 ? MINCLS12 : \
+ (fat) == 16 ? MINCLS16 : \
+ MINCLS32)
+
+#define maxcls(fat) ((fat) == 12 ? MAXCLS12 : \
+ (fat) == 16 ? MAXCLS16 : \
+ MAXCLS32)
+
+#define mk1(p, x) \
+ (p) = (u_int8_t)(x)
+
+#define mk2(p, x) \
+ (p)[0] = (u_int8_t)(x), \
+ (p)[1] = (u_int8_t)((x) >> 010)
+
+#define mk4(p, x) \
+ (p)[0] = (u_int8_t)(x), \
+ (p)[1] = (u_int8_t)((x) >> 010), \
+ (p)[2] = (u_int8_t)((x) >> 020), \
+ (p)[3] = (u_int8_t)((x) >> 030)
+
+#define argto1(arg, lo, msg) argtou(arg, lo, 0xff, msg)
+#define argto2(arg, lo, msg) argtou(arg, lo, 0xffff, msg)
+#define argto4(arg, lo, msg) argtou(arg, lo, 0xffffffff, msg)
+#define argtox(arg, lo, msg) argtou(arg, lo, UINT_MAX, msg)
+
+struct bs {
+ u_int8_t bsJump[3]; /* bootstrap entry point */
+ u_int8_t bsOemName[8]; /* OEM name and version */
+} __packed;
+
+struct bsbpb {
+ u_int8_t bpbBytesPerSec[2]; /* bytes per sector */
+ u_int8_t bpbSecPerClust; /* sectors per cluster */
+ u_int8_t bpbResSectors[2]; /* reserved sectors */
+ u_int8_t bpbFATs; /* number of FATs */
+ u_int8_t bpbRootDirEnts[2]; /* root directory entries */
+ u_int8_t bpbSectors[2]; /* total sectors */
+ u_int8_t bpbMedia; /* media descriptor */
+ u_int8_t bpbFATsecs[2]; /* sectors per FAT */
+ u_int8_t bpbSecPerTrack[2]; /* sectors per track */
+ u_int8_t bpbHeads[2]; /* drive heads */
+ u_int8_t bpbHiddenSecs[4]; /* hidden sectors */
+ u_int8_t bpbHugeSectors[4]; /* big total sectors */
+} __packed;
+
+struct bsxbpb {
+ u_int8_t bpbBigFATsecs[4]; /* big sectors per FAT */
+ u_int8_t bpbExtFlags[2]; /* FAT control flags */
+ u_int8_t bpbFSVers[2]; /* file system version */
+ u_int8_t bpbRootClust[4]; /* root directory start cluster */
+ u_int8_t bpbFSInfo[2]; /* file system info sector */
+ u_int8_t bpbBackup[2]; /* backup boot sector */
+ u_int8_t bpbReserved[12]; /* reserved */
+} __packed;
+
+struct bsx {
+ u_int8_t exDriveNumber; /* drive number */
+ u_int8_t exReserved1; /* reserved */
+ u_int8_t exBootSignature; /* extended boot signature */
+ u_int8_t exVolumeID[4]; /* volume ID number */
+ u_int8_t exVolumeLabel[11]; /* volume label */
+ u_int8_t exFileSysType[8]; /* file system type */
+} __packed;
+
+struct de {
+ u_int8_t deName[11]; /* name and extension */
+ u_int8_t deAttributes; /* attributes */
+ u_int8_t rsvd[10]; /* reserved */
+ u_int8_t deMTime[2]; /* last-modified time */
+ u_int8_t deMDate[2]; /* last-modified date */
+ u_int8_t deStartCluster[2]; /* starting cluster */
+ u_int8_t deFileSize[4]; /* size */
+} __packed;
+
+struct bpb {
+ u_int bpbBytesPerSec; /* bytes per sector */
+ u_int bpbSecPerClust; /* sectors per cluster */
+ u_int bpbResSectors; /* reserved sectors */
+ u_int bpbFATs; /* number of FATs */
+ u_int bpbRootDirEnts; /* root directory entries */
+ u_int bpbSectors; /* total sectors */
+ u_int bpbMedia; /* media descriptor */
+ u_int bpbFATsecs; /* sectors per FAT */
+ u_int bpbSecPerTrack; /* sectors per track */
+ u_int bpbHeads; /* drive heads */
+ u_int bpbHiddenSecs; /* hidden sectors */
+ u_int bpbHugeSectors; /* big total sectors */
+ u_int bpbBigFATsecs; /* big sectors per FAT */
+ u_int bpbRootClust; /* root directory start cluster */
+ u_int bpbFSInfo; /* file system info sector */
+ u_int bpbBackup; /* backup boot sector */
+};
+
+#define BPBGAP 0, 0, 0, 0, 0, 0
+
+static struct {
+ const char *name;
+ struct bpb bpb;
+} const stdfmt[] = {
+ {"160", {512, 1, 1, 2, 64, 320, 0xfe, 1, 8, 1, BPBGAP}},
+ {"180", {512, 1, 1, 2, 64, 360, 0xfc, 2, 9, 1, BPBGAP}},
+ {"320", {512, 2, 1, 2, 112, 640, 0xff, 1, 8, 2, BPBGAP}},
+ {"360", {512, 2, 1, 2, 112, 720, 0xfd, 2, 9, 2, BPBGAP}},
+ {"640", {512, 2, 1, 2, 112, 1280, 0xfb, 2, 8, 2, BPBGAP}},
+ {"720", {512, 2, 1, 2, 112, 1440, 0xf9, 3, 9, 2, BPBGAP}},
+ {"1200", {512, 1, 1, 2, 224, 2400, 0xf9, 7, 15, 2, BPBGAP}},
+ {"1232", {1024,1, 1, 2, 192, 1232, 0xfe, 2, 8, 2, BPBGAP}},
+ {"1440", {512, 1, 1, 2, 224, 2880, 0xf0, 9, 18, 2, BPBGAP}},
+ {"2880", {512, 2, 1, 2, 240, 5760, 0xf0, 9, 36, 2, BPBGAP}}
+};
+
+static const u_int8_t bootcode[] = {
+ 0xfa, /* cli */
+ 0x31, 0xc0, /* xor ax,ax */
+ 0x8e, 0xd0, /* mov ss,ax */
+ 0xbc, 0x00, 0x7c, /* mov sp,7c00h */
+ 0xfb, /* sti */
+ 0x8e, 0xd8, /* mov ds,ax */
+ 0xe8, 0x00, 0x00, /* call $ + 3 */
+ 0x5e, /* pop si */
+ 0x83, 0xc6, 0x19, /* add si,+19h */
+ 0xbb, 0x07, 0x00, /* mov bx,0007h */
+ 0xfc, /* cld */
+ 0xac, /* lodsb */
+ 0x84, 0xc0, /* test al,al */
+ 0x74, 0x06, /* jz $ + 8 */
+ 0xb4, 0x0e, /* mov ah,0eh */
+ 0xcd, 0x10, /* int 10h */
+ 0xeb, 0xf5, /* jmp $ - 9 */
+ 0x30, 0xe4, /* xor ah,ah */
+ 0xcd, 0x16, /* int 16h */
+ 0xcd, 0x19, /* int 19h */
+ 0x0d, 0x0a,
+ 'N', 'o', 'n', '-', 's', 'y', 's', 't',
+ 'e', 'm', ' ', 'd', 'i', 's', 'k',
+ 0x0d, 0x0a,
+ 'P', 'r', 'e', 's', 's', ' ', 'a', 'n',
+ 'y', ' ', 'k', 'e', 'y', ' ', 't', 'o',
+ ' ', 'r', 'e', 'b', 'o', 'o', 't',
+ 0x0d, 0x0a,
+ 0
+};
+
+static volatile sig_atomic_t got_siginfo;
+static void infohandler(int);
+
+static void check_mounted(const char *, mode_t);
+static void getstdfmt(const char *, struct bpb *);
+static void getdiskinfo(int, const char *, const char *, int,
+ struct bpb *);
+static void print_bpb(struct bpb *);
+static u_int ckgeom(const char *, u_int, const char *);
+static u_int argtou(const char *, u_int, u_int, const char *);
+static off_t argtooff(const char *, const char *);
+static int oklabel(const char *);
+static void mklabel(u_int8_t *, const char *);
+static void setstr(u_int8_t *, const char *, size_t);
+static void usage(void);
+
+/*
+ * Construct a FAT12, FAT16, or FAT32 file system.
+ */
+int
+main(int argc, char *argv[])
+{
+ static const char opts[] = "@:NB:C:F:I:L:O:S:a:b:c:e:f:h:i:k:m:n:o:r:s:u:";
+ const char *opt_B = NULL, *opt_L = NULL, *opt_O = NULL, *opt_f = NULL;
+ u_int opt_F = 0, opt_I = 0, opt_S = 0, opt_a = 0, opt_b = 0, opt_c = 0;
+ u_int opt_e = 0, opt_h = 0, opt_i = 0, opt_k = 0, opt_m = 0, opt_n = 0;
+ u_int opt_o = 0, opt_r = 0, opt_s = 0, opt_u = 0;
+ int opt_N = 0;
+ int Iflag = 0, mflag = 0, oflag = 0;
+ char buf[MAXPATHLEN];
+ struct sigaction si_sa;
+ struct stat sb;
+ struct timeval tv;
+ struct bpb bpb;
+ struct tm *tm;
+ struct bs *bs;
+ struct bsbpb *bsbpb;
+ struct bsxbpb *bsxbpb;
+ struct bsx *bsx;
+ struct de *de;
+ u_int8_t *img;
+ const char *fname, *dtype, *bname;
+ ssize_t n;
+ time_t now;
+ u_int fat, bss, rds, cls, dir, lsn, x, x1, x2;
+ int ch, fd, fd1;
+ off_t opt_create = 0, opt_ofs = 0;
+
+ while ((ch = getopt(argc, argv, opts)) != -1)
+ switch (ch) {
+ case '@':
+ opt_ofs = argtooff(optarg, "offset");
+ break;
+ case 'N':
+ opt_N = 1;
+ break;
+ case 'B':
+ opt_B = optarg;
+ break;
+ case 'C':
+ opt_create = argtooff(optarg, "create size");
+ break;
+ case 'F':
+ if (strcmp(optarg, "12") &&
+ strcmp(optarg, "16") &&
+ strcmp(optarg, "32"))
+ errx(1, "%s: bad FAT type", optarg);
+ opt_F = atoi(optarg);
+ break;
+ case 'I':
+ opt_I = argto4(optarg, 0, "volume ID");
+ Iflag = 1;
+ break;
+ case 'L':
+ if (!oklabel(optarg))
+ errx(1, "%s: bad volume label", optarg);
+ opt_L = optarg;
+ break;
+ case 'O':
+ if (strlen(optarg) > 8)
+ errx(1, "%s: bad OEM string", optarg);
+ opt_O = optarg;
+ break;
+ case 'S':
+ opt_S = argto2(optarg, 1, "bytes/sector");
+ break;
+ case 'a':
+ opt_a = argto4(optarg, 1, "sectors/FAT");
+ break;
+ case 'b':
+ opt_b = argtox(optarg, 1, "block size");
+ opt_c = 0;
+ break;
+ case 'c':
+ opt_c = argto1(optarg, 1, "sectors/cluster");
+ opt_b = 0;
+ break;
+ case 'e':
+ opt_e = argto2(optarg, 1, "directory entries");
+ break;
+ case 'f':
+ opt_f = optarg;
+ break;
+ case 'h':
+ opt_h = argto2(optarg, 1, "drive heads");
+ break;
+ case 'i':
+ opt_i = argto2(optarg, 1, "info sector");
+ break;
+ case 'k':
+ opt_k = argto2(optarg, 1, "backup sector");
+ break;
+ case 'm':
+ opt_m = argto1(optarg, 0, "media descriptor");
+ mflag = 1;
+ break;
+ case 'n':
+ opt_n = argto1(optarg, 1, "number of FATs");
+ break;
+ case 'o':
+ opt_o = argto4(optarg, 0, "hidden sectors");
+ oflag = 1;
+ break;
+ case 'r':
+ opt_r = argto2(optarg, 1, "reserved sectors");
+ break;
+ case 's':
+ opt_s = argto4(optarg, 1, "file system size");
+ break;
+ case 'u':
+ opt_u = argto2(optarg, 1, "sectors/track");
+ break;
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc < 1 || argc > 2)
+ usage();
+ fname = *argv++;
+ if (!opt_create && !strchr(fname, '/')) {
+ snprintf(buf, sizeof(buf), "%s%s", _PATH_DEV, fname);
+ if (!(fname = strdup(buf)))
+ err(1, NULL);
+ }
+ dtype = *argv;
+ if (opt_create) {
+ if (opt_N)
+ errx(1, "create (-C) is incompatible with -N");
+ fd = open(fname, O_RDWR | O_CREAT | O_TRUNC, 0644);
+ if (fd == -1)
+ errx(1, "failed to create %s", fname);
+ if (ftruncate(fd, opt_create))
+ errx(1, "failed to initialize %jd bytes", (intmax_t)opt_create);
+ } else if ((fd = open(fname, opt_N ? O_RDONLY : O_RDWR)) == -1)
+ err(1, "%s", fname);
+ if (fstat(fd, &sb))
+ err(1, "%s", fname);
+ if (opt_create) {
+ if (!S_ISREG(sb.st_mode))
+ warnx("warning, %s is not a regular file", fname);
+ } else {
+ if (!S_ISCHR(sb.st_mode))
+ warnx("warning, %s is not a character device", fname);
+ }
+ if (!opt_N)
+ check_mounted(fname, sb.st_mode);
+ if (opt_ofs && opt_ofs != lseek(fd, opt_ofs, SEEK_SET))
+ errx(1, "cannot seek to %jd", (intmax_t)opt_ofs);
+ memset(&bpb, 0, sizeof(bpb));
+ if (opt_f) {
+ getstdfmt(opt_f, &bpb);
+ bpb.bpbHugeSectors = bpb.bpbSectors;
+ bpb.bpbSectors = 0;
+ bpb.bpbBigFATsecs = bpb.bpbFATsecs;
+ bpb.bpbFATsecs = 0;
+ }
+ if (opt_h)
+ bpb.bpbHeads = opt_h;
+ if (opt_u)
+ bpb.bpbSecPerTrack = opt_u;
+ if (opt_S)
+ bpb.bpbBytesPerSec = opt_S;
+ if (opt_s)
+ bpb.bpbHugeSectors = opt_s;
+ if (oflag)
+ bpb.bpbHiddenSecs = opt_o;
+ if (!(opt_f || (opt_h && opt_u && opt_S && opt_s && oflag))) {
+ off_t delta;
+ getdiskinfo(fd, fname, dtype, oflag, &bpb);
+ bpb.bpbHugeSectors -= (opt_ofs / bpb.bpbBytesPerSec);
+ delta = bpb.bpbHugeSectors % bpb.bpbSecPerTrack;
+ if (delta != 0) {
+ warnx("trim %d sectors to adjust to a multiple of %d",
+ (int)delta, bpb.bpbSecPerTrack);
+ bpb.bpbHugeSectors -= delta;
+ }
+ if (bpb.bpbSecPerClust == 0) { /* set defaults */
+ if (bpb.bpbHugeSectors <= 6000) /* about 3MB -> 512 bytes */
+ bpb.bpbSecPerClust = 1;
+ else if (bpb.bpbHugeSectors <= (1<<17)) /* 64M -> 4k */
+ bpb.bpbSecPerClust = 8;
+ else if (bpb.bpbHugeSectors <= (1<<19)) /* 256M -> 8k */
+ bpb.bpbSecPerClust = 16;
+ else if (bpb.bpbHugeSectors <= (1<<21)) /* 1G -> 16k */
+ bpb.bpbSecPerClust = 32;
+ else
+ bpb.bpbSecPerClust = 64; /* otherwise 32k */
+ }
+ }
+ if (!powerof2(bpb.bpbBytesPerSec))
+ errx(1, "bytes/sector (%u) is not a power of 2", bpb.bpbBytesPerSec);
+ if (bpb.bpbBytesPerSec < MINBPS)
+ errx(1, "bytes/sector (%u) is too small; minimum is %u",
+ bpb.bpbBytesPerSec, MINBPS);
+ if (!(fat = opt_F)) {
+ if (opt_f)
+ fat = 12;
+ else if (!opt_e && (opt_i || opt_k))
+ fat = 32;
+ }
+ if ((fat == 32 && opt_e) || (fat != 32 && (opt_i || opt_k)))
+ errx(1, "-%c is not a legal FAT%s option",
+ fat == 32 ? 'e' : opt_i ? 'i' : 'k',
+ fat == 32 ? "32" : "12/16");
+ if (opt_f && fat == 32)
+ bpb.bpbRootDirEnts = 0;
+ if (opt_b) {
+ if (!powerof2(opt_b))
+ errx(1, "block size (%u) is not a power of 2", opt_b);
+ if (opt_b < bpb.bpbBytesPerSec)
+ errx(1, "block size (%u) is too small; minimum is %u",
+ opt_b, bpb.bpbBytesPerSec);
+ if (opt_b > bpb.bpbBytesPerSec * MAXSPC)
+ errx(1, "block size (%u) is too large; maximum is %u",
+ opt_b, bpb.bpbBytesPerSec * MAXSPC);
+ bpb.bpbSecPerClust = opt_b / bpb.bpbBytesPerSec;
+ }
+ if (opt_c) {
+ if (!powerof2(opt_c))
+ errx(1, "sectors/cluster (%u) is not a power of 2", opt_c);
+ bpb.bpbSecPerClust = opt_c;
+ }
+ if (opt_r)
+ bpb.bpbResSectors = opt_r;
+ if (opt_n) {
+ if (opt_n > MAXNFT)
+ errx(1, "number of FATs (%u) is too large; maximum is %u",
+ opt_n, MAXNFT);
+ bpb.bpbFATs = opt_n;
+ }
+ if (opt_e)
+ bpb.bpbRootDirEnts = opt_e;
+ if (mflag) {
+ if (opt_m < 0xf0)
+ errx(1, "illegal media descriptor (%#x)", opt_m);
+ bpb.bpbMedia = opt_m;
+ }
+ if (opt_a)
+ bpb.bpbBigFATsecs = opt_a;
+ if (opt_i)
+ bpb.bpbFSInfo = opt_i;
+ if (opt_k)
+ bpb.bpbBackup = opt_k;
+ bss = 1;
+ bname = NULL;
+ fd1 = -1;
+ if (opt_B) {
+ bname = opt_B;
+ if (!strchr(bname, '/')) {
+ snprintf(buf, sizeof(buf), "/boot/%s", bname);
+ if (!(bname = strdup(buf)))
+ err(1, NULL);
+ }
+ if ((fd1 = open(bname, O_RDONLY)) == -1 || fstat(fd1, &sb))
+ err(1, "%s", bname);
+ if (!S_ISREG(sb.st_mode) || sb.st_size % bpb.bpbBytesPerSec ||
+ sb.st_size < bpb.bpbBytesPerSec ||
+ sb.st_size > bpb.bpbBytesPerSec * MAXU16)
+ errx(1, "%s: inappropriate file type or format", bname);
+ bss = sb.st_size / bpb.bpbBytesPerSec;
+ }
+ if (!bpb.bpbFATs)
+ bpb.bpbFATs = 2;
+ if (!fat) {
+ if (bpb.bpbHugeSectors < (bpb.bpbResSectors ? bpb.bpbResSectors : bss) +
+ howmany((RESFTE + (bpb.bpbSecPerClust ? MINCLS16 : MAXCLS12 + 1)) *
+ (bpb.bpbSecPerClust ? 16 : 12) / BPN,
+ bpb.bpbBytesPerSec * NPB) *
+ bpb.bpbFATs +
+ howmany(bpb.bpbRootDirEnts ? bpb.bpbRootDirEnts : DEFRDE,
+ bpb.bpbBytesPerSec / sizeof(struct de)) +
+ (bpb.bpbSecPerClust ? MINCLS16 : MAXCLS12 + 1) *
+ (bpb.bpbSecPerClust ? bpb.bpbSecPerClust :
+ howmany(DEFBLK, bpb.bpbBytesPerSec)))
+ fat = 12;
+ else if (bpb.bpbRootDirEnts || bpb.bpbHugeSectors <
+ (bpb.bpbResSectors ? bpb.bpbResSectors : bss) +
+ howmany((RESFTE + MAXCLS16) * 2, bpb.bpbBytesPerSec) *
+ bpb.bpbFATs +
+ howmany(DEFRDE, bpb.bpbBytesPerSec / sizeof(struct de)) +
+ (MAXCLS16 + 1) *
+ (bpb.bpbSecPerClust ? bpb.bpbSecPerClust :
+ howmany(8192, bpb.bpbBytesPerSec)))
+ fat = 16;
+ else
+ fat = 32;
+ }
+ x = bss;
+ if (fat == 32) {
+ if (!bpb.bpbFSInfo) {
+ if (x == MAXU16 || x == bpb.bpbBackup)
+ errx(1, "no room for info sector");
+ bpb.bpbFSInfo = x;
+ }
+ if (bpb.bpbFSInfo != MAXU16 && x <= bpb.bpbFSInfo)
+ x = bpb.bpbFSInfo + 1;
+ if (!bpb.bpbBackup) {
+ if (x == MAXU16)
+ errx(1, "no room for backup sector");
+ bpb.bpbBackup = x;
+ } else if (bpb.bpbBackup != MAXU16 && bpb.bpbBackup == bpb.bpbFSInfo)
+ errx(1, "backup sector would overwrite info sector");
+ if (bpb.bpbBackup != MAXU16 && x <= bpb.bpbBackup)
+ x = bpb.bpbBackup + 1;
+ }
+ if (!bpb.bpbResSectors)
+ bpb.bpbResSectors = fat == 32 ?
+ MAX(x, MAX(16384 / bpb.bpbBytesPerSec, 4)) : x;
+ else if (bpb.bpbResSectors < x)
+ errx(1, "too few reserved sectors (need %d have %d)", x,
+ bpb.bpbResSectors);
+ if (fat != 32 && !bpb.bpbRootDirEnts)
+ bpb.bpbRootDirEnts = DEFRDE;
+ rds = howmany(bpb.bpbRootDirEnts, bpb.bpbBytesPerSec / sizeof(struct de));
+ if (!bpb.bpbSecPerClust)
+ for (bpb.bpbSecPerClust = howmany(fat == 16 ? DEFBLK16 :
+ DEFBLK, bpb.bpbBytesPerSec);
+ bpb.bpbSecPerClust < MAXSPC &&
+ bpb.bpbResSectors +
+ howmany((RESFTE + maxcls(fat)) * (fat / BPN),
+ bpb.bpbBytesPerSec * NPB) *
+ bpb.bpbFATs +
+ rds +
+ (u_int64_t) (maxcls(fat) + 1) *
+ bpb.bpbSecPerClust <= bpb.bpbHugeSectors;
+ bpb.bpbSecPerClust <<= 1)
+ continue;
+ if (fat != 32 && bpb.bpbBigFATsecs > MAXU16)
+ errx(1, "too many sectors/FAT for FAT12/16");
+ x1 = bpb.bpbResSectors + rds;
+ x = bpb.bpbBigFATsecs ? bpb.bpbBigFATsecs : 1;
+ if (x1 + (u_int64_t)x * bpb.bpbFATs > bpb.bpbHugeSectors)
+ errx(1, "meta data exceeds file system size");
+ x1 += x * bpb.bpbFATs;
+ x = (u_int64_t)(bpb.bpbHugeSectors - x1) * bpb.bpbBytesPerSec * NPB /
+ (bpb.bpbSecPerClust * bpb.bpbBytesPerSec * NPB + fat /
+ BPN * bpb.bpbFATs);
+ x2 = howmany((RESFTE + MIN(x, maxcls(fat))) * (fat / BPN),
+ bpb.bpbBytesPerSec * NPB);
+ if (!bpb.bpbBigFATsecs) {
+ bpb.bpbBigFATsecs = x2;
+ x1 += (bpb.bpbBigFATsecs - 1) * bpb.bpbFATs;
+ }
+ cls = (bpb.bpbHugeSectors - x1) / bpb.bpbSecPerClust;
+ x = (u_int64_t)bpb.bpbBigFATsecs * bpb.bpbBytesPerSec * NPB / (fat / BPN) -
+ RESFTE;
+ if (cls > x)
+ cls = x;
+ if (bpb.bpbBigFATsecs < x2)
+ warnx("warning: sectors/FAT limits file system to %u clusters",
+ cls);
+ if (cls < mincls(fat))
+ errx(1, "%u clusters too few clusters for FAT%u, need %u", cls, fat,
+ mincls(fat));
+ if (cls > maxcls(fat)) {
+ cls = maxcls(fat);
+ bpb.bpbHugeSectors = x1 + (cls + 1) * bpb.bpbSecPerClust - 1;
+ warnx("warning: FAT type limits file system to %u sectors",
+ bpb.bpbHugeSectors);
+ }
+ printf("%s: %u sector%s in %u FAT%u cluster%s "
+ "(%u bytes/cluster)\n", fname, cls * bpb.bpbSecPerClust,
+ cls * bpb.bpbSecPerClust == 1 ? "" : "s", cls, fat,
+ cls == 1 ? "" : "s", bpb.bpbBytesPerSec * bpb.bpbSecPerClust);
+ if (!bpb.bpbMedia)
+ bpb.bpbMedia = !bpb.bpbHiddenSecs ? 0xf0 : 0xf8;
+ if (fat == 32)
+ bpb.bpbRootClust = RESFTE;
+ if (bpb.bpbHiddenSecs + bpb.bpbHugeSectors <= MAXU16) {
+ bpb.bpbSectors = bpb.bpbHugeSectors;
+ bpb.bpbHugeSectors = 0;
+ }
+ if (fat != 32) {
+ bpb.bpbFATsecs = bpb.bpbBigFATsecs;
+ bpb.bpbBigFATsecs = 0;
+ }
+ print_bpb(&bpb);
+ if (!opt_N) {
+ gettimeofday(&tv, NULL);
+ now = tv.tv_sec;
+ tm = localtime(&now);
+ if (!(img = malloc(bpb.bpbBytesPerSec)))
+ err(1, NULL);
+ dir = bpb.bpbResSectors + (bpb.bpbFATsecs ? bpb.bpbFATsecs :
+ bpb.bpbBigFATsecs) * bpb.bpbFATs;
+ memset(&si_sa, 0, sizeof(si_sa));
+ si_sa.sa_handler = infohandler;
+ if (sigaction(SIGINFO, &si_sa, NULL) == -1)
+ err(1, "sigaction SIGINFO");
+ for (lsn = 0; lsn < dir + (fat == 32 ? bpb.bpbSecPerClust : rds); lsn++) {
+ if (got_siginfo) {
+ fprintf(stderr,"%s: writing sector %u of %u (%u%%)\n",
+ fname, lsn,
+ (dir + (fat == 32 ? bpb.bpbSecPerClust: rds)),
+ (lsn * 100) / (dir +
+ (fat == 32 ? bpb.bpbSecPerClust: rds)));
+ got_siginfo = 0;
+ }
+ x = lsn;
+ if (opt_B &&
+ fat == 32 && bpb.bpbBackup != MAXU16 &&
+ bss <= bpb.bpbBackup && x >= bpb.bpbBackup) {
+ x -= bpb.bpbBackup;
+ if (!x && lseek(fd1, opt_ofs, SEEK_SET))
+ err(1, "%s", bname);
+ }
+ if (opt_B && x < bss) {
+ if ((n = read(fd1, img, bpb.bpbBytesPerSec)) == -1)
+ err(1, "%s", bname);
+ if ((unsigned)n != bpb.bpbBytesPerSec)
+ errx(1, "%s: can't read sector %u", bname, x);
+ } else
+ memset(img, 0, bpb.bpbBytesPerSec);
+ if (!lsn ||
+ (fat == 32 && bpb.bpbBackup != MAXU16 &&
+ lsn == bpb.bpbBackup)) {
+ x1 = sizeof(struct bs);
+ bsbpb = (struct bsbpb *)(img + x1);
+ mk2(bsbpb->bpbBytesPerSec, bpb.bpbBytesPerSec);
+ mk1(bsbpb->bpbSecPerClust, bpb.bpbSecPerClust);
+ mk2(bsbpb->bpbResSectors, bpb.bpbResSectors);
+ mk1(bsbpb->bpbFATs, bpb.bpbFATs);
+ mk2(bsbpb->bpbRootDirEnts, bpb.bpbRootDirEnts);
+ mk2(bsbpb->bpbSectors, bpb.bpbSectors);
+ mk1(bsbpb->bpbMedia, bpb.bpbMedia);
+ mk2(bsbpb->bpbFATsecs, bpb.bpbFATsecs);
+ mk2(bsbpb->bpbSecPerTrack, bpb.bpbSecPerTrack);
+ mk2(bsbpb->bpbHeads, bpb.bpbHeads);
+ mk4(bsbpb->bpbHiddenSecs, bpb.bpbHiddenSecs);
+ mk4(bsbpb->bpbHugeSectors, bpb.bpbHugeSectors);
+ x1 += sizeof(struct bsbpb);
+ if (fat == 32) {
+ bsxbpb = (struct bsxbpb *)(img + x1);
+ mk4(bsxbpb->bpbBigFATsecs, bpb.bpbBigFATsecs);
+ mk2(bsxbpb->bpbExtFlags, 0);
+ mk2(bsxbpb->bpbFSVers, 0);
+ mk4(bsxbpb->bpbRootClust, bpb.bpbRootClust);
+ mk2(bsxbpb->bpbFSInfo, bpb.bpbFSInfo);
+ mk2(bsxbpb->bpbBackup, bpb.bpbBackup);
+ x1 += sizeof(struct bsxbpb);
+ }
+ bsx = (struct bsx *)(img + x1);
+ mk1(bsx->exBootSignature, 0x29);
+ if (Iflag)
+ x = opt_I;
+ else
+ x = (((u_int)(1 + tm->tm_mon) << 8 |
+ (u_int)tm->tm_mday) +
+ ((u_int)tm->tm_sec << 8 |
+ (u_int)(tv.tv_usec / 10))) << 16 |
+ ((u_int)(1900 + tm->tm_year) +
+ ((u_int)tm->tm_hour << 8 |
+ (u_int)tm->tm_min));
+ mk4(bsx->exVolumeID, x);
+ mklabel(bsx->exVolumeLabel, opt_L ? opt_L : "NO NAME");
+ sprintf(buf, "FAT%u", fat);
+ setstr(bsx->exFileSysType, buf, sizeof(bsx->exFileSysType));
+ if (!opt_B) {
+ x1 += sizeof(struct bsx);
+ bs = (struct bs *)img;
+ mk1(bs->bsJump[0], 0xeb);
+ mk1(bs->bsJump[1], x1 - 2);
+ mk1(bs->bsJump[2], 0x90);
+ setstr(bs->bsOemName, opt_O ? opt_O : "BSD4.4 ",
+ sizeof(bs->bsOemName));
+ memcpy(img + x1, bootcode, sizeof(bootcode));
+ mk2(img + MINBPS - 2, DOSMAGIC);
+ }
+ } else if (fat == 32 && bpb.bpbFSInfo != MAXU16 &&
+ (lsn == bpb.bpbFSInfo ||
+ (bpb.bpbBackup != MAXU16 &&
+ lsn == bpb.bpbBackup + bpb.bpbFSInfo))) {
+ mk4(img, 0x41615252);
+ mk4(img + MINBPS - 28, 0x61417272);
+ mk4(img + MINBPS - 24, 0xffffffff);
+ mk4(img + MINBPS - 20, bpb.bpbRootClust);
+ mk2(img + MINBPS - 2, DOSMAGIC);
+ } else if (lsn >= bpb.bpbResSectors && lsn < dir &&
+ !((lsn - bpb.bpbResSectors) %
+ (bpb.bpbFATsecs ? bpb.bpbFATsecs :
+ bpb.bpbBigFATsecs))) {
+ mk1(img[0], bpb.bpbMedia);
+ for (x = 1; x < fat * (fat == 32 ? 3 : 2) / 8; x++)
+ mk1(img[x], fat == 32 && x % 4 == 3 ? 0x0f : 0xff);
+ } else if (lsn == dir && opt_L) {
+ de = (struct de *)img;
+ mklabel(de->deName, opt_L);
+ mk1(de->deAttributes, 050);
+ x = (u_int)tm->tm_hour << 11 |
+ (u_int)tm->tm_min << 5 |
+ (u_int)tm->tm_sec >> 1;
+ mk2(de->deMTime, x);
+ x = (u_int)(tm->tm_year - 80) << 9 |
+ (u_int)(tm->tm_mon + 1) << 5 |
+ (u_int)tm->tm_mday;
+ mk2(de->deMDate, x);
+ }
+ if ((n = write(fd, img, bpb.bpbBytesPerSec)) == -1)
+ err(1, "%s", fname);
+ if ((unsigned)n != bpb.bpbBytesPerSec)
+ errx(1, "%s: can't write sector %u", fname, lsn);
+ }
+ }
+ return 0;
+}
+
+/*
+ * Exit with error if file system is mounted.
+ */
+static void
+check_mounted(const char *fname, mode_t mode)
+{
+ struct statfs *mp;
+ const char *s1, *s2;
+ size_t len;
+ int n, r;
+
+ if (!(n = getmntinfo(&mp, MNT_NOWAIT)))
+ err(1, "getmntinfo");
+ len = strlen(_PATH_DEV);
+ s1 = fname;
+ if (!strncmp(s1, _PATH_DEV, len))
+ s1 += len;
+ r = S_ISCHR(mode) && s1 != fname && *s1 == 'r';
+ for (; n--; mp++) {
+ s2 = mp->f_mntfromname;
+ if (!strncmp(s2, _PATH_DEV, len))
+ s2 += len;
+ if ((r && s2 != mp->f_mntfromname && !strcmp(s1 + 1, s2)) ||
+ !strcmp(s1, s2))
+ errx(1, "%s is mounted on %s", fname, mp->f_mntonname);
+ }
+}
+
+/*
+ * Get a standard format.
+ */
+static void
+getstdfmt(const char *fmt, struct bpb *bpb)
+{
+ u_int x, i;
+
+ x = sizeof(stdfmt) / sizeof(stdfmt[0]);
+ for (i = 0; i < x && strcmp(fmt, stdfmt[i].name); i++);
+ if (i == x)
+ errx(1, "%s: unknown standard format", fmt);
+ *bpb = stdfmt[i].bpb;
+}
+
+/*
+ * Get disk slice, partition, and geometry information.
+ */
+static void
+getdiskinfo(int fd, const char *fname, const char *dtype, __unused int oflag,
+ struct bpb *bpb)
+{
+ struct disklabel *lp, dlp;
+ struct fd_type type;
+ off_t ms, hs = 0;
+
+ lp = NULL;
+
+ /* If the user specified a disk type, try to use that */
+ if (dtype != NULL) {
+ lp = getdiskbyname(dtype);
+ }
+
+ /* Maybe it's a floppy drive */
+ if (lp == NULL) {
+ if (ioctl(fd, DIOCGMEDIASIZE, &ms) == -1) {
+ struct stat st;
+
+ if (fstat(fd, &st))
+ err(1, "cannot get disk size");
+ /* create a fake geometry for a file image */
+ ms = st.st_size;
+ dlp.d_secsize = 512;
+ dlp.d_nsectors = 63;
+ dlp.d_ntracks = 255;
+ dlp.d_secperunit = ms / dlp.d_secsize;
+ lp = &dlp;
+ } else if (ioctl(fd, FD_GTYPE, &type) != -1) {
+ dlp.d_secsize = 128 << type.secsize;
+ dlp.d_nsectors = type.sectrac;
+ dlp.d_ntracks = type.heads;
+ dlp.d_secperunit = ms / dlp.d_secsize;
+ lp = &dlp;
+ }
+ }
+
+ /* Maybe it's a fixed drive */
+ if (lp == NULL) {
+ if (bpb->bpbBytesPerSec)
+ dlp.d_secsize = bpb->bpbBytesPerSec;
+ if (bpb->bpbBytesPerSec == 0 && ioctl(fd, DIOCGSECTORSIZE,
+ &dlp.d_secsize) == -1)
+ err(1, "cannot get sector size");
+
+ dlp.d_secperunit = ms / dlp.d_secsize;
+
+ if (bpb->bpbSecPerTrack == 0 && ioctl(fd, DIOCGFWSECTORS,
+ &dlp.d_nsectors) == -1) {
+ warn("cannot get number of sectors per track");
+ dlp.d_nsectors = 63;
+ }
+ if (bpb->bpbHeads == 0 &&
+ ioctl(fd, DIOCGFWHEADS, &dlp.d_ntracks) == -1) {
+ warn("cannot get number of heads");
+ if (dlp.d_secperunit <= 63*1*1024)
+ dlp.d_ntracks = 1;
+ else if (dlp.d_secperunit <= 63*16*1024)
+ dlp.d_ntracks = 16;
+ else
+ dlp.d_ntracks = 255;
+ }
+
+ hs = (ms / dlp.d_secsize) - dlp.d_secperunit;
+ lp = &dlp;
+ }
+
+ if (bpb->bpbBytesPerSec == 0)
+ bpb->bpbBytesPerSec = ckgeom(fname, lp->d_secsize, "bytes/sector");
+ if (bpb->bpbSecPerTrack == 0)
+ bpb->bpbSecPerTrack = ckgeom(fname, lp->d_nsectors, "sectors/track");
+ if (bpb->bpbHeads == 0)
+ bpb->bpbHeads = ckgeom(fname, lp->d_ntracks, "drive heads");
+ if (bpb->bpbHugeSectors == 0)
+ bpb->bpbHugeSectors = lp->d_secperunit;
+ if (bpb->bpbHiddenSecs == 0)
+ bpb->bpbHiddenSecs = hs;
+}
+
+/*
+ * Print out BPB values.
+ */
+static void
+print_bpb(struct bpb *bpb)
+{
+ printf("BytesPerSec=%u SecPerClust=%u ResSectors=%u FATs=%u",
+ bpb->bpbBytesPerSec, bpb->bpbSecPerClust, bpb->bpbResSectors,
+ bpb->bpbFATs);
+ if (bpb->bpbRootDirEnts)
+ printf(" RootDirEnts=%u", bpb->bpbRootDirEnts);
+ if (bpb->bpbSectors)
+ printf(" Sectors=%u", bpb->bpbSectors);
+ printf(" Media=%#x", bpb->bpbMedia);
+ if (bpb->bpbFATsecs)
+ printf(" FATsecs=%u", bpb->bpbFATsecs);
+ printf(" SecPerTrack=%u Heads=%u HiddenSecs=%u", bpb->bpbSecPerTrack,
+ bpb->bpbHeads, bpb->bpbHiddenSecs);
+ if (bpb->bpbHugeSectors)
+ printf(" HugeSectors=%u", bpb->bpbHugeSectors);
+ if (!bpb->bpbFATsecs) {
+ printf(" FATsecs=%u RootCluster=%u", bpb->bpbBigFATsecs,
+ bpb->bpbRootClust);
+ printf(" FSInfo=");
+ printf(bpb->bpbFSInfo == MAXU16 ? "%#x" : "%u", bpb->bpbFSInfo);
+ printf(" Backup=");
+ printf(bpb->bpbBackup == MAXU16 ? "%#x" : "%u", bpb->bpbBackup);
+ }
+ printf("\n");
+}
+
+/*
+ * Check a disk geometry value.
+ */
+static u_int
+ckgeom(const char *fname, u_int val, const char *msg)
+{
+ if (!val)
+ errx(1, "%s: no default %s", fname, msg);
+ if (val > MAXU16)
+ errx(1, "%s: illegal %s %d", fname, msg, val);
+ return val;
+}
+
+/*
+ * Convert and check a numeric option argument.
+ */
+static u_int
+argtou(const char *arg, u_int lo, u_int hi, const char *msg)
+{
+ char *s;
+ u_long x;
+
+ errno = 0;
+ x = strtoul(arg, &s, 0);
+ if (errno || !*arg || *s || x < lo || x > hi)
+ errx(1, "%s: bad %s", arg, msg);
+ return x;
+}
+
+/*
+ * Same for off_t, with optional skmgpP suffix
+ */
+static off_t
+argtooff(const char *arg, const char *msg)
+{
+ char *s;
+ off_t x;
+
+ errno = 0;
+ x = strtoll(arg, &s, 0);
+ /* allow at most one extra char */
+ if (errno || x < 0 || (s[0] && s[1]) )
+ errx(1, "%s: bad %s", arg, msg);
+ if (*s) { /* the extra char is the multiplier */
+ switch (*s) {
+ default:
+ errx(1, "%s: bad %s", arg, msg);
+ /* notreached */
+
+ case 's': /* sector */
+ case 'S':
+ x <<= 9; /* times 512 */
+ break;
+
+ case 'k': /* kilobyte */
+ case 'K':
+ x <<= 10; /* times 1024 */
+ break;
+
+ case 'm': /* megabyte */
+ case 'M':
+ x <<= 20; /* times 1024*1024 */
+ break;
+
+ case 'g': /* gigabyte */
+ case 'G':
+ x <<= 30; /* times 1024*1024*1024 */
+ break;
+
+ case 'p': /* partition start */
+ case 'P':
+ case 'l': /* partition length */
+ case 'L':
+ errx(1, "%s: not supported yet %s", arg, msg);
+ /* notreached */
+ }
+ }
+ return x;
+}
+
+/*
+ * Check a volume label.
+ */
+static int
+oklabel(const char *src)
+{
+ int c, i;
+
+ for (i = 0; i <= 11; i++) {
+ c = (u_char)*src++;
+ if (c < ' ' + !i || strchr("\"*+,./:;<=>?[\\]|", c))
+ break;
+ }
+ return i && !c;
+}
+
+/*
+ * Make a volume label.
+ */
+static void
+mklabel(u_int8_t *dest, const char *src)
+{
+ int c, i;
+
+ for (i = 0; i < 11; i++) {
+ c = *src ? toupper(*src++) : ' ';
+ *dest++ = !i && c == '\xe5' ? 5 : c;
+ }
+}
+
+/*
+ * Copy string, padding with spaces.
+ */
+static void
+setstr(u_int8_t *dest, const char *src, size_t len)
+{
+ while (len--)
+ *dest++ = *src ? *src++ : ' ';
+}
+
+/*
+ * Print usage message.
+ */
+static void
+usage(void)
+{
+ fprintf(stderr,
+ "usage: newfs_msdos [ -options ] special [disktype]\n"
+ "where the options are:\n"
+ "\t-@ create file system at specified offset\n"
+ "\t-B get bootstrap from file\n"
+ "\t-C create image file with specified size\n"
+ "\t-F FAT type (12, 16, or 32)\n"
+ "\t-I volume ID\n"
+ "\t-L volume label\n"
+ "\t-N don't create file system: just print out parameters\n"
+ "\t-O OEM string\n"
+ "\t-S bytes/sector\n"
+ "\t-a sectors/FAT\n"
+ "\t-b block size\n"
+ "\t-c sectors/cluster\n"
+ "\t-e root directory entries\n"
+ "\t-f standard format\n"
+ "\t-h drive heads\n"
+ "\t-i file system info sector\n"
+ "\t-k backup boot sector\n"
+ "\t-m media descriptor\n"
+ "\t-n number of FATs\n"
+ "\t-o hidden sectors\n"
+ "\t-r reserved sectors\n"
+ "\t-s file system size (sectors)\n"
+ "\t-u sectors/track\n");
+ exit(1);
+}
+
+static void
+infohandler(int sig __unused)
+{
+
+ got_siginfo = 1;
+}
diff --git a/sbin/newfs_nandfs/Makefile b/sbin/newfs_nandfs/Makefile
new file mode 100644
index 0000000..7a39fbb
--- /dev/null
+++ b/sbin/newfs_nandfs/Makefile
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+PROG= newfs_nandfs
+MAN= newfs_nandfs.8
+
+LIBADD= geom
+
+.include <bsd.prog.mk>
diff --git a/sbin/newfs_nandfs/newfs_nandfs.8 b/sbin/newfs_nandfs/newfs_nandfs.8
new file mode 100644
index 0000000..7a630bb
--- /dev/null
+++ b/sbin/newfs_nandfs/newfs_nandfs.8
@@ -0,0 +1,76 @@
+.\"
+.\" Copyright (c) 2010 Semihalf
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd October 1, 2013
+.Dt NEWFS_NANDFS 8
+.Os
+.Sh NAME
+.Nm newfs_nandfs
+.Nd construct a new NAND FS file system
+.Sh SYNOPSIS
+.Nm
+.Op Fl b Ar blocsize
+.Op Fl B Ar blocks-per-segment
+.Op Fl L Ar label
+.Op Fl m Ar reserved-segment-percent
+.Ar device
+.Sh DESCRIPTION
+The
+.Nm
+utility creates a NAND FS file system on device.
+.Pp
+The options are as follow:
+.Bl -tag -width indent
+.It Fl b Ar blocksize
+Size of block (1024 if not specified).
+.It Fl B Ar blocks_per_segment
+Number of blocks per segment (2048 if not specified).
+.It Fl L Ar label
+Volume label (up to 16 characters).
+.It Fl m Ar reserved_block_percent
+Percentage of reserved blocks (5 if not specified).
+.El
+.Sh EXIT STATUS
+Exit status is 0 on success and 1 on error.
+.Sh EXAMPLES
+Create a file system, using default parameters, on
+.Pa /dev/ada0s1 :
+.Bd -literal -offset indent
+newfs_nandfs /dev/ada0s1
+.Ed
+.Sh SEE ALSO
+.Xr disktab 5 ,
+.Xr disklabel 8 ,
+.Xr fdisk 8 ,
+.Xr newfs 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 10.0 .
+.Sh AUTHORS
+.An Grzegorz Bernacki
diff --git a/sbin/newfs_nandfs/newfs_nandfs.c b/sbin/newfs_nandfs/newfs_nandfs.c
new file mode 100644
index 0000000..fda2b9ed
--- /dev/null
+++ b/sbin/newfs_nandfs/newfs_nandfs.c
@@ -0,0 +1,1179 @@
+/*-
+ * Copyright (c) 2010-2012 Semihalf.
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/fdcio.h>
+#include <sys/disk.h>
+#include <sys/disklabel.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/endian.h>
+#include <sys/stddef.h>
+#include <sys/uuid.h>
+#include <sys/dirent.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <libgeom.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <fs/nandfs/nandfs_fs.h>
+#include <dev/nand/nand_dev.h>
+
+#define DEBUG
+#undef DEBUG
+#ifdef DEBUG
+#define debug(fmt, args...) do { \
+ printf("nandfs:" fmt "\n", ##args); } while (0)
+#else
+#define debug(fmt, args...)
+#endif
+
+#define NANDFS_FIRST_BLOCK nandfs_first_block()
+#define NANDFS_FIRST_CNO 1
+#define NANDFS_BLOCK_BAD 1
+#define NANDFS_BLOCK_GOOD 0
+
+struct file_info {
+ uint64_t ino;
+ const char *name;
+ uint32_t mode;
+ uint64_t size;
+ uint8_t nblocks;
+ uint32_t *blocks;
+ struct nandfs_inode *inode;
+};
+
+static struct file_info user_files[] = {
+ { NANDFS_ROOT_INO, NULL, S_IFDIR | 0755, 0, 1, NULL, NULL },
+};
+
+static struct file_info ifile =
+ { NANDFS_IFILE_INO, NULL, 0, 0, -1, NULL, NULL };
+static struct file_info sufile =
+ { NANDFS_SUFILE_INO, NULL, 0, 0, -1, NULL, NULL };
+static struct file_info cpfile =
+ { NANDFS_CPFILE_INO, NULL, 0, 0, -1, NULL, NULL };
+static struct file_info datfile =
+ { NANDFS_DAT_INO, NULL, 0, 0, -1, NULL, NULL };
+
+struct nandfs_block {
+ LIST_ENTRY(nandfs_block) block_link;
+ uint32_t number;
+ uint64_t offset;
+ void *data;
+};
+
+static LIST_HEAD(, nandfs_block) block_head =
+ LIST_HEAD_INITIALIZER(&block_head);
+
+/* Storage geometry */
+static off_t mediasize;
+static ssize_t sectorsize;
+static uint64_t nsegments;
+static uint64_t erasesize;
+static uint64_t segsize;
+
+static struct nandfs_fsdata fsdata;
+static struct nandfs_super_block super_block;
+
+static int is_nand;
+
+/* Nandfs parameters */
+static size_t blocksize = NANDFS_DEF_BLOCKSIZE;
+static long blocks_per_segment;
+static long rsv_segment_percent = 5;
+static time_t nandfs_time;
+static uint32_t bad_segments_count = 0;
+static uint32_t *bad_segments = NULL;
+static uint8_t fsdata_blocks_state[NANDFS_NFSAREAS];
+
+static u_char *volumelabel = NULL;
+
+static struct nandfs_super_root *sr;
+
+static uint32_t nuserfiles;
+static uint32_t seg_nblocks;
+static uint32_t seg_endblock;
+
+#define SIZE_TO_BLOCK(size) (((size) + (blocksize - 1)) / blocksize)
+
+static uint32_t
+nandfs_first_block(void)
+{
+ uint32_t i, first_free, start_bad_segments = 0;
+
+ for (i = 0; i < bad_segments_count; i++) {
+ if (i == bad_segments[i])
+ start_bad_segments++;
+ else
+ break;
+ }
+
+ first_free = SIZE_TO_BLOCK(NANDFS_DATA_OFFSET_BYTES(erasesize) +
+ (start_bad_segments * segsize));
+
+ if (first_free < (uint32_t)blocks_per_segment)
+ return (blocks_per_segment);
+ else
+ return (first_free);
+}
+
+static void
+usage(void)
+{
+
+ fprintf(stderr,
+ "usage: newfs_nandfs [ -options ] device\n"
+ "where the options are:\n"
+ "\t-b block-size\n"
+ "\t-B blocks-per-segment\n"
+ "\t-L volume label\n"
+ "\t-m reserved-segments-percentage\n");
+ exit(1);
+}
+
+static int
+nandfs_log2(unsigned n)
+{
+ unsigned count;
+
+ /*
+ * N.B. this function will return 0 if supplied 0.
+ */
+ for (count = 0; n/2; count++)
+ n /= 2;
+ return count;
+}
+
+/* from NetBSD's src/sys/net/if_ethersubr.c */
+static uint32_t
+crc32_le(uint32_t crc, const uint8_t *buf, size_t len)
+{
+ static const uint32_t crctab[] = {
+ 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac,
+ 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
+ 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c,
+ 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c
+ };
+ size_t i;
+
+ crc = crc ^ ~0U;
+
+ for (i = 0; i < len; i++) {
+ crc ^= buf[i];
+ crc = (crc >> 4) ^ crctab[crc & 0xf];
+ crc = (crc >> 4) ^ crctab[crc & 0xf];
+ }
+
+ return (crc ^ ~0U);
+}
+
+static void *
+get_block(uint32_t block_nr, uint64_t offset)
+{
+ struct nandfs_block *block, *new_block;
+
+ LIST_FOREACH(block, &block_head, block_link) {
+ if (block->number == block_nr)
+ return block->data;
+ }
+
+ debug("allocating block %x\n", block_nr);
+
+ new_block = malloc(sizeof(*block));
+ if (!new_block)
+ err(1, "cannot allocate block");
+
+ new_block->number = block_nr;
+ new_block->offset = offset;
+ new_block->data = malloc(blocksize);
+ if (!new_block->data)
+ err(1, "cannot allocate block data");
+
+ memset(new_block->data, 0, blocksize);
+
+ LIST_INSERT_HEAD(&block_head, new_block, block_link);
+
+ return (new_block->data);
+}
+
+static int
+nandfs_seg_usage_blk_offset(uint64_t seg, uint64_t *blk, uint64_t *offset)
+{
+ uint64_t off;
+ uint16_t seg_size;
+
+ seg_size = sizeof(struct nandfs_segment_usage);
+
+ off = roundup(sizeof(struct nandfs_sufile_header), seg_size);
+ off += (seg * seg_size);
+
+ *blk = off / blocksize;
+ *offset = (off % blocksize) / seg_size;
+ return (0);
+}
+
+static uint32_t
+segment_size(void)
+{
+ u_int size;
+
+ size = sizeof(struct nandfs_segment_summary );
+ size += seg_nblocks * sizeof(struct nandfs_binfo_v);
+
+ if (size > blocksize)
+ err(1, "segsum info bigger that blocksize");
+
+ return (size);
+}
+
+
+static void
+prepare_blockgrouped_file(uint32_t block)
+{
+ struct nandfs_block_group_desc *desc;
+ uint32_t i, entries;
+
+ desc = (struct nandfs_block_group_desc *)get_block(block, 0);
+ entries = blocksize / sizeof(struct nandfs_block_group_desc);
+ for (i = 0; i < entries; i++)
+ desc[i].bg_nfrees = blocksize * 8;
+}
+
+static void
+alloc_blockgrouped_file(uint32_t block, uint32_t entry)
+{
+ struct nandfs_block_group_desc *desc;
+ uint32_t desc_nr;
+ uint32_t *bitmap;
+
+ desc = (struct nandfs_block_group_desc *)get_block(block, 0);
+ bitmap = (uint32_t *)get_block(block + 1, 1);
+
+ bitmap += (entry >> 5);
+ if (*bitmap & (1 << (entry % 32))) {
+ printf("nandfs: blockgrouped entry %d already allocated\n",
+ entry);
+ }
+ *bitmap |= (1 << (entry % 32));
+
+ desc_nr = entry / (blocksize * 8);
+ desc[desc_nr].bg_nfrees--;
+}
+
+
+static uint64_t
+count_su_blocks(void)
+{
+ uint64_t maxblk, blk, offset, i;
+
+ maxblk = blk = 0;
+
+ for (i = 0; i < bad_segments_count; i++) {
+ nandfs_seg_usage_blk_offset(bad_segments[i], &blk, &offset);
+ debug("bad segment at block:%jx off: %jx", blk, offset);
+ if (blk > maxblk)
+ maxblk = blk;
+ }
+
+ debug("bad segment needs %#jx", blk);
+ if (blk >= NDADDR) {
+ printf("nandfs: file too big (%jd > %d)\n", blk, NDADDR);
+ exit(2);
+ }
+
+ sufile.size = (blk + 1) * blocksize;
+ return (blk + 1);
+}
+
+static void
+count_seg_blocks(void)
+{
+ uint32_t i;
+
+ for (i = 0; i < nuserfiles; i++)
+ if (user_files[i].nblocks) {
+ seg_nblocks += user_files[i].nblocks;
+ user_files[i].blocks = malloc(user_files[i].nblocks * sizeof(uint32_t));
+ }
+
+ ifile.nblocks = 2 +
+ SIZE_TO_BLOCK(sizeof(struct nandfs_inode) * (NANDFS_USER_INO + 1));
+ ifile.blocks = malloc(ifile.nblocks * sizeof(uint32_t));
+ seg_nblocks += ifile.nblocks;
+
+ cpfile.nblocks =
+ SIZE_TO_BLOCK((NANDFS_CPFILE_FIRST_CHECKPOINT_OFFSET + 1) *
+ sizeof(struct nandfs_checkpoint));
+ cpfile.blocks = malloc(cpfile.nblocks * sizeof(uint32_t));
+ seg_nblocks += cpfile.nblocks;
+
+ if (!bad_segments) {
+ sufile.nblocks =
+ SIZE_TO_BLOCK((NANDFS_SUFILE_FIRST_SEGMENT_USAGE_OFFSET + 1) *
+ sizeof(struct nandfs_segment_usage));
+ } else {
+ debug("bad blocks found: extra space for sufile");
+ sufile.nblocks = count_su_blocks();
+ }
+
+ sufile.blocks = malloc(sufile.nblocks * sizeof(uint32_t));
+ seg_nblocks += sufile.nblocks;
+
+ datfile.nblocks = 2 +
+ SIZE_TO_BLOCK((seg_nblocks) * sizeof(struct nandfs_dat_entry));
+ datfile.blocks = malloc(datfile.nblocks * sizeof(uint32_t));
+ seg_nblocks += datfile.nblocks;
+}
+
+static void
+assign_file_blocks(uint64_t start_block)
+{
+ uint32_t i, j;
+
+ for (i = 0; i < nuserfiles; i++)
+ for (j = 0; j < user_files[i].nblocks; j++) {
+ debug("user file %d at block %d at %#jx",
+ i, j, (uintmax_t)start_block);
+ user_files[i].blocks[j] = start_block++;
+ }
+
+ for (j = 0; j < ifile.nblocks; j++) {
+ debug("ifile block %d at %#jx", j, (uintmax_t)start_block);
+ ifile.blocks[j] = start_block++;
+ }
+
+ for (j = 0; j < cpfile.nblocks; j++) {
+ debug("cpfile block %d at %#jx", j, (uintmax_t)start_block);
+ cpfile.blocks[j] = start_block++;
+ }
+
+ for (j = 0; j < sufile.nblocks; j++) {
+ debug("sufile block %d at %#jx", j, (uintmax_t)start_block);
+ sufile.blocks[j] = start_block++;
+ }
+
+ for (j = 0; j < datfile.nblocks; j++) {
+ debug("datfile block %d at %#jx", j, (uintmax_t)start_block);
+ datfile.blocks[j] = start_block++;
+ }
+
+ /* add one for superroot */
+ debug("sr at block %#jx", (uintmax_t)start_block);
+ sr = (struct nandfs_super_root *)get_block(start_block++, 0);
+ seg_endblock = start_block;
+}
+
+static void
+save_datfile(void)
+{
+
+ prepare_blockgrouped_file(datfile.blocks[0]);
+}
+
+static uint64_t
+update_datfile(uint64_t block)
+{
+ struct nandfs_dat_entry *dat;
+ static uint64_t vblock = 0;
+ uint64_t allocated, i, off;
+
+ if (vblock == 0) {
+ alloc_blockgrouped_file(datfile.blocks[0], vblock);
+ vblock++;
+ }
+ allocated = vblock;
+ i = vblock / (blocksize / sizeof(*dat));
+ off = vblock % (blocksize / sizeof(*dat));
+ vblock++;
+
+ dat = (struct nandfs_dat_entry *)get_block(datfile.blocks[2 + i], 2 + i);
+
+ alloc_blockgrouped_file(datfile.blocks[0], allocated);
+ dat[off].de_blocknr = block;
+ dat[off].de_start = NANDFS_FIRST_CNO;
+ dat[off].de_end = UINTMAX_MAX;
+
+ return (allocated);
+}
+
+static union nandfs_binfo *
+update_block_info(union nandfs_binfo *binfo, struct file_info *file)
+{
+ nandfs_daddr_t vblock;
+ uint32_t i;
+
+ for (i = 0; i < file->nblocks; i++) {
+ debug("%s: blk %x", __func__, i);
+ if (file->ino != NANDFS_DAT_INO) {
+ vblock = update_datfile(file->blocks[i]);
+ binfo->bi_v.bi_vblocknr = vblock;
+ binfo->bi_v.bi_blkoff = i;
+ binfo->bi_v.bi_ino = file->ino;
+ file->inode->i_db[i] = vblock;
+ } else {
+ binfo->bi_dat.bi_blkoff = i;
+ binfo->bi_dat.bi_ino = file->ino;
+ file->inode->i_db[i] = datfile.blocks[i];
+ }
+ binfo++;
+ }
+
+ return (binfo);
+}
+
+static void
+save_segsum(struct nandfs_segment_summary *ss)
+{
+ union nandfs_binfo *binfo;
+ struct nandfs_block *block;
+ uint32_t sum_bytes, i;
+ uint8_t crc_data, crc_skip;
+
+ sum_bytes = segment_size();
+ ss->ss_magic = NANDFS_SEGSUM_MAGIC;
+ ss->ss_bytes = sizeof(struct nandfs_segment_summary);
+ ss->ss_flags = NANDFS_SS_LOGBGN | NANDFS_SS_LOGEND | NANDFS_SS_SR;
+ ss->ss_seq = 1;
+ ss->ss_create = nandfs_time;
+
+ ss->ss_next = nandfs_first_block() + blocks_per_segment;
+ /* nblocks = segment blocks + segsum block + superroot */
+ ss->ss_nblocks = seg_nblocks + 2;
+ ss->ss_nbinfos = seg_nblocks;
+ ss->ss_sumbytes = sum_bytes;
+
+ crc_skip = sizeof(ss->ss_datasum) + sizeof(ss->ss_sumsum);
+ ss->ss_sumsum = crc32_le(0, (uint8_t *)ss + crc_skip,
+ sum_bytes - crc_skip);
+ crc_data = 0;
+
+ binfo = (union nandfs_binfo *)(ss + 1);
+ for (i = 0; i < nuserfiles; i++) {
+ if (user_files[i].nblocks)
+ binfo = update_block_info(binfo, &user_files[i]);
+ }
+
+ binfo = update_block_info(binfo, &ifile);
+ binfo = update_block_info(binfo, &cpfile);
+ binfo = update_block_info(binfo, &sufile);
+ update_block_info(binfo, &datfile);
+
+ /* save superroot crc */
+ crc_skip = sizeof(sr->sr_sum);
+ sr->sr_sum = crc32_le(0, (uint8_t *)sr + crc_skip,
+ NANDFS_SR_BYTES - crc_skip);
+
+ /* segment checksup */
+ crc_skip = sizeof(ss->ss_datasum);
+ LIST_FOREACH(block, &block_head, block_link) {
+ if (block->number < NANDFS_FIRST_BLOCK)
+ continue;
+ if (block->number == NANDFS_FIRST_BLOCK)
+ crc_data = crc32_le(0,
+ (uint8_t *)block->data + crc_skip,
+ blocksize - crc_skip);
+ else
+ crc_data = crc32_le(crc_data, (uint8_t *)block->data,
+ blocksize);
+ }
+ ss->ss_datasum = crc_data;
+}
+
+static void
+create_fsdata(void)
+{
+
+ memset(&fsdata, 0, sizeof(struct nandfs_fsdata));
+
+ fsdata.f_magic = NANDFS_FSDATA_MAGIC;
+ fsdata.f_nsegments = nsegments;
+ fsdata.f_erasesize = erasesize;
+ fsdata.f_first_data_block = NANDFS_FIRST_BLOCK;
+ fsdata.f_blocks_per_segment = blocks_per_segment;
+ fsdata.f_r_segments_percentage = rsv_segment_percent;
+ fsdata.f_rev_level = NANDFS_CURRENT_REV;
+ fsdata.f_sbbytes = NANDFS_SB_BYTES;
+ fsdata.f_bytes = NANDFS_FSDATA_CRC_BYTES;
+ fsdata.f_ctime = nandfs_time;
+ fsdata.f_log_block_size = nandfs_log2(blocksize) - 10;
+ fsdata.f_errors = 1;
+ fsdata.f_inode_size = sizeof(struct nandfs_inode);
+ fsdata.f_dat_entry_size = sizeof(struct nandfs_dat_entry);
+ fsdata.f_checkpoint_size = sizeof(struct nandfs_checkpoint);
+ fsdata.f_segment_usage_size = sizeof(struct nandfs_segment_usage);
+
+ uuidgen(&fsdata.f_uuid, 1);
+
+ if (volumelabel)
+ memcpy(fsdata.f_volume_name, volumelabel, 16);
+
+ fsdata.f_sum = crc32_le(0, (const uint8_t *)&fsdata,
+ NANDFS_FSDATA_CRC_BYTES);
+}
+
+static void
+save_fsdata(void *data)
+{
+
+ memcpy(data, &fsdata, sizeof(fsdata));
+}
+
+static void
+create_super_block(void)
+{
+
+ memset(&super_block, 0, sizeof(struct nandfs_super_block));
+
+ super_block.s_magic = NANDFS_SUPER_MAGIC;
+ super_block.s_last_cno = NANDFS_FIRST_CNO;
+ super_block.s_last_pseg = NANDFS_FIRST_BLOCK;
+ super_block.s_last_seq = 1;
+ super_block.s_free_blocks_count =
+ (nsegments - bad_segments_count) * blocks_per_segment;
+ super_block.s_mtime = 0;
+ super_block.s_wtime = nandfs_time;
+ super_block.s_state = NANDFS_VALID_FS;
+
+ super_block.s_sum = crc32_le(0, (const uint8_t *)&super_block,
+ NANDFS_SB_BYTES);
+}
+
+static void
+save_super_block(void *data)
+{
+
+ memcpy(data, &super_block, sizeof(super_block));
+}
+
+static void
+save_super_root(void)
+{
+
+ sr->sr_bytes = NANDFS_SR_BYTES;
+ sr->sr_flags = 0;
+ sr->sr_nongc_ctime = nandfs_time;
+ datfile.inode = &sr->sr_dat;
+ cpfile.inode = &sr->sr_cpfile;
+ sufile.inode = &sr->sr_sufile;
+}
+
+static struct nandfs_dir_entry *
+add_de(void *block, struct nandfs_dir_entry *de, uint64_t ino,
+ const char *name, uint8_t type)
+{
+ uint16_t reclen;
+
+ /* modify last de */
+ de->rec_len = NANDFS_DIR_REC_LEN(de->name_len);
+ de = (void *)((uint8_t *)de + de->rec_len);
+
+ reclen = blocksize - ((uintptr_t)de - (uintptr_t)block);
+ if (reclen < NANDFS_DIR_REC_LEN(strlen(name))) {
+ printf("nandfs: too many dir entries for one block\n");
+ return (NULL);
+ }
+
+ de->inode = ino;
+ de->rec_len = reclen;
+ de->name_len = strlen(name);
+ de->file_type = type;
+ memset(de->name, 0,
+ (strlen(name) + NANDFS_DIR_PAD - 1) & ~NANDFS_DIR_ROUND);
+ memcpy(de->name, name, strlen(name));
+
+ return (de);
+}
+
+static struct nandfs_dir_entry *
+make_dir(void *block, uint64_t ino, uint64_t parent_ino)
+{
+ struct nandfs_dir_entry *de = (struct nandfs_dir_entry *)block;
+
+ /* create '..' entry */
+ de->inode = parent_ino;
+ de->rec_len = NANDFS_DIR_REC_LEN(2);
+ de->name_len = 2;
+ de->file_type = DT_DIR;
+ memset(de->name, 0, NANDFS_DIR_NAME_LEN(2));
+ memcpy(de->name, "..", 2);
+
+ /* create '.' entry */
+ de = (void *)((uint8_t *)block + NANDFS_DIR_REC_LEN(2));
+ de->inode = ino;
+ de->rec_len = blocksize - NANDFS_DIR_REC_LEN(2);
+ de->name_len = 1;
+ de->file_type = DT_DIR;
+ memset(de->name, 0, NANDFS_DIR_NAME_LEN(1));
+ memcpy(de->name, ".", 1);
+
+ return (de);
+}
+
+static void
+save_root_dir(void)
+{
+ struct file_info *root = &user_files[0];
+ struct nandfs_dir_entry *de;
+ uint32_t i;
+ void *block;
+
+ block = get_block(root->blocks[0], 0);
+
+ de = make_dir(block, root->ino, root->ino);
+ for (i = 1; i < nuserfiles; i++)
+ de = add_de(block, de, user_files[i].ino, user_files[i].name,
+ IFTODT(user_files[i].mode));
+
+ root->size = ((uintptr_t)de - (uintptr_t)block) +
+ NANDFS_DIR_REC_LEN(de->name_len);
+}
+
+static void
+save_sufile(void)
+{
+ struct nandfs_sufile_header *header;
+ struct nandfs_segment_usage *su;
+ uint64_t blk, i, off;
+ void *block;
+ int start;
+
+ /*
+ * At the beginning just zero-out everything
+ */
+ for (i = 0; i < sufile.nblocks; i++)
+ get_block(sufile.blocks[i], 0);
+
+ start = 0;
+
+ block = get_block(sufile.blocks[start], 0);
+ header = (struct nandfs_sufile_header *)block;
+ header->sh_ncleansegs = nsegments - bad_segments_count - 1;
+ header->sh_ndirtysegs = 1;
+ header->sh_last_alloc = 1;
+
+ su = (struct nandfs_segment_usage *)header;
+ off = NANDFS_SUFILE_FIRST_SEGMENT_USAGE_OFFSET;
+ /* Allocate data segment */
+ su[off].su_lastmod = nandfs_time;
+ /* nblocks = segment blocks + segsum block + superroot */
+ su[off].su_nblocks = seg_nblocks + 2;
+ su[off].su_flags = NANDFS_SEGMENT_USAGE_DIRTY;
+ off++;
+ /* Allocate next segment */
+ su[off].su_lastmod = nandfs_time;
+ su[off].su_nblocks = 0;
+ su[off].su_flags = NANDFS_SEGMENT_USAGE_DIRTY;
+ for (i = 0; i < bad_segments_count; i++) {
+ nandfs_seg_usage_blk_offset(bad_segments[i], &blk, &off);
+ debug("storing bad_segments[%jd]=%x at %jx off %jx\n", i,
+ bad_segments[i], blk, off);
+ block = get_block(sufile.blocks[blk],
+ off * sizeof(struct nandfs_segment_usage *));
+ su = (struct nandfs_segment_usage *)block;
+ su[off].su_lastmod = nandfs_time;
+ su[off].su_nblocks = 0;
+ su[off].su_flags = NANDFS_SEGMENT_USAGE_ERROR;
+ }
+}
+
+static void
+save_cpfile(void)
+{
+ struct nandfs_cpfile_header *header;
+ struct nandfs_checkpoint *cp, *initial_cp;
+ int i, entries = blocksize / sizeof(struct nandfs_checkpoint);
+ uint64_t cno;
+
+ header = (struct nandfs_cpfile_header *)get_block(cpfile.blocks[0], 0);
+ header->ch_ncheckpoints = 1;
+ header->ch_nsnapshots = 0;
+
+ cp = (struct nandfs_checkpoint *)header;
+
+ /* fill first checkpoint data*/
+ initial_cp = &cp[NANDFS_CPFILE_FIRST_CHECKPOINT_OFFSET];
+ initial_cp->cp_flags = 0;
+ initial_cp->cp_checkpoints_count = 0;
+ initial_cp->cp_cno = NANDFS_FIRST_CNO;
+ initial_cp->cp_create = nandfs_time;
+ initial_cp->cp_nblk_inc = seg_endblock - 1;
+ initial_cp->cp_blocks_count = seg_nblocks;
+ memset(&initial_cp->cp_snapshot_list, 0,
+ sizeof(struct nandfs_snapshot_list));
+
+ ifile.inode = &initial_cp->cp_ifile_inode;
+
+ /* mark rest of cp as invalid */
+ cno = NANDFS_FIRST_CNO + 1;
+ i = NANDFS_CPFILE_FIRST_CHECKPOINT_OFFSET + 1;
+ for (; i < entries; i++) {
+ cp[i].cp_cno = cno++;
+ cp[i].cp_flags = NANDFS_CHECKPOINT_INVALID;
+ }
+}
+
+static void
+init_inode(struct nandfs_inode *inode, struct file_info *file)
+{
+
+ inode->i_blocks = file->nblocks;
+ inode->i_ctime = nandfs_time;
+ inode->i_mtime = nandfs_time;
+ inode->i_mode = file->mode & 0xffff;
+ inode->i_links_count = 1;
+
+ if (file->size > 0)
+ inode->i_size = file->size;
+ else
+ inode->i_size = 0;
+
+ if (file->ino == NANDFS_USER_INO)
+ inode->i_flags = SF_NOUNLINK|UF_NOUNLINK;
+ else
+ inode->i_flags = 0;
+}
+
+static void
+save_ifile(void)
+{
+ struct nandfs_inode *inode;
+ struct file_info *file;
+ uint64_t ino, blk, off;
+ uint32_t i;
+
+ prepare_blockgrouped_file(ifile.blocks[0]);
+ for (i = 0; i <= NANDFS_USER_INO; i++)
+ alloc_blockgrouped_file(ifile.blocks[0], i);
+
+ for (i = 0; i < nuserfiles; i++) {
+ file = &user_files[i];
+ ino = file->ino;
+ blk = ino / (blocksize / sizeof(*inode));
+ off = ino % (blocksize / sizeof(*inode));
+ inode =
+ (struct nandfs_inode *)get_block(ifile.blocks[2 + blk], 2 + blk);
+ file->inode = &inode[off];
+ init_inode(file->inode, file);
+ }
+
+ init_inode(ifile.inode, &ifile);
+ init_inode(cpfile.inode, &cpfile);
+ init_inode(sufile.inode, &sufile);
+ init_inode(datfile.inode, &datfile);
+}
+
+static int
+create_fs(void)
+{
+ uint64_t start_block;
+ uint32_t segsum_size;
+ char *data;
+ int i;
+
+ nuserfiles = (sizeof(user_files) / sizeof(user_files[0]));
+
+ /* Count and assign blocks */
+ count_seg_blocks();
+ segsum_size = segment_size();
+ start_block = NANDFS_FIRST_BLOCK + SIZE_TO_BLOCK(segsum_size);
+ assign_file_blocks(start_block);
+
+ /* Create super root structure */
+ save_super_root();
+
+ /* Create root directory */
+ save_root_dir();
+
+ /* Fill in file contents */
+ save_sufile();
+ save_cpfile();
+ save_ifile();
+ save_datfile();
+
+ /* Save fsdata and superblocks */
+ create_fsdata();
+ create_super_block();
+
+ for (i = 0; i < NANDFS_NFSAREAS; i++) {
+ if (fsdata_blocks_state[i] != NANDFS_BLOCK_GOOD)
+ continue;
+
+ data = get_block((i * erasesize)/blocksize, 0);
+ save_fsdata(data);
+
+ data = get_block((i * erasesize + NANDFS_SBLOCK_OFFSET_BYTES) /
+ blocksize, 0);
+ if (blocksize > NANDFS_SBLOCK_OFFSET_BYTES)
+ data += NANDFS_SBLOCK_OFFSET_BYTES;
+ save_super_block(data);
+ memset(data + sizeof(struct nandfs_super_block), 0xff,
+ (blocksize - sizeof(struct nandfs_super_block) -
+ NANDFS_SBLOCK_OFFSET_BYTES));
+ }
+
+ /* Save segment summary and CRCs */
+ save_segsum(get_block(NANDFS_FIRST_BLOCK, 0));
+
+ return (0);
+}
+
+static void
+write_fs(int fda)
+{
+ struct nandfs_block *block;
+ char *data;
+ u_int ret;
+
+ /* Overwrite next block with ff if not nand device */
+ if (!is_nand) {
+ data = get_block(seg_endblock, 0);
+ memset(data, 0xff, blocksize);
+ }
+
+ LIST_FOREACH(block, &block_head, block_link) {
+ lseek(fda, block->number * blocksize, SEEK_SET);
+ ret = write(fda, block->data, blocksize);
+ if (ret != blocksize)
+ err(1, "cannot write filesystem data");
+ }
+}
+
+static void
+check_parameters(void)
+{
+ int i;
+
+ /* check blocksize */
+ if ((blocksize < NANDFS_MIN_BLOCKSIZE) || (blocksize > MAXBSIZE) ||
+ ((blocksize - 1) & blocksize)) {
+ errx(1, "Bad blocksize (%zu). Must be in range [%u-%u] "
+ "and a power of two.", blocksize, NANDFS_MIN_BLOCKSIZE,
+ MAXBSIZE);
+ }
+
+ /* check blocks per segments */
+ if ((blocks_per_segment < NANDFS_SEG_MIN_BLOCKS) ||
+ ((blocksize - 1) & blocksize))
+ errx(1, "Bad blocks per segment (%lu). Must be greater than "
+ "%u and a power of two.", blocks_per_segment,
+ NANDFS_SEG_MIN_BLOCKS);
+
+ /* check reserved segment percentage */
+ if ((rsv_segment_percent < 1) && (rsv_segment_percent > 99))
+ errx(1, "Bad reserved segment percentage. "
+ "Must in range 1..99.");
+
+ /* check volume label */
+ i = 0;
+ if (volumelabel) {
+ while (isalnum(volumelabel[++i]))
+ ;
+
+ if (volumelabel[i] != '\0') {
+ errx(1, "bad volume label. "
+ "Valid characters are alphanumerics.");
+ }
+
+ if (strlen(volumelabel) >= 16)
+ errx(1, "Bad volume label. Length is longer than %d.",
+ 16);
+ }
+
+ nandfs_time = time(NULL);
+}
+
+static void
+print_parameters(void)
+{
+
+ printf("filesystem parameters:\n");
+ printf("blocksize: %#zx sectorsize: %#zx\n", blocksize, sectorsize);
+ printf("erasesize: %#jx mediasize: %#jx\n", erasesize, mediasize);
+ printf("segment size: %#jx blocks per segment: %#x\n", segsize,
+ (uint32_t)blocks_per_segment);
+}
+
+/*
+ * Exit with error if file system is mounted.
+ */
+static void
+check_mounted(const char *fname, mode_t mode)
+{
+ struct statfs *mp;
+ const char *s1, *s2;
+ size_t len;
+ int n, r;
+
+ if (!(n = getmntinfo(&mp, MNT_NOWAIT)))
+ err(1, "getmntinfo");
+
+ len = strlen(_PATH_DEV);
+ s1 = fname;
+ if (!strncmp(s1, _PATH_DEV, len))
+ s1 += len;
+
+ r = S_ISCHR(mode) && s1 != fname && *s1 == 'r';
+
+ for (; n--; mp++) {
+ s2 = mp->f_mntfromname;
+
+ if (!strncmp(s2, _PATH_DEV, len))
+ s2 += len;
+ if ((r && s2 != mp->f_mntfromname && !strcmp(s1 + 1, s2)) ||
+ !strcmp(s1, s2))
+ errx(1, "%s is mounted on %s", fname, mp->f_mntonname);
+ }
+}
+
+static void
+calculate_geometry(int fd)
+{
+ struct chip_param_io chip_params;
+ char ident[DISK_IDENT_SIZE];
+ char medianame[MAXPATHLEN];
+
+ /* Check storage type */
+ g_get_ident(fd, ident, DISK_IDENT_SIZE);
+ g_get_name(ident, medianame, MAXPATHLEN);
+ debug("device name: %s", medianame);
+
+ is_nand = (strstr(medianame, "gnand") != NULL);
+ debug("is_nand = %d", is_nand);
+
+ sectorsize = g_sectorsize(fd);
+ debug("sectorsize: %#zx", sectorsize);
+
+ /* Get storage size */
+ mediasize = g_mediasize(fd);
+ debug("mediasize: %#jx", mediasize);
+
+ /* Get storage erase unit size */
+ if (!is_nand)
+ erasesize = NANDFS_DEF_ERASESIZE;
+ else if (ioctl(fd, NAND_IO_GET_CHIP_PARAM, &chip_params) != -1)
+ erasesize = chip_params.page_size * chip_params.pages_per_block;
+ else
+ errx(1, "Cannot ioctl(NAND_IO_GET_CHIP_PARAM)");
+
+ debug("erasesize: %#jx", (uintmax_t)erasesize);
+
+ if (blocks_per_segment == 0) {
+ if (erasesize >= NANDFS_MIN_SEGSIZE)
+ blocks_per_segment = erasesize / blocksize;
+ else
+ blocks_per_segment = NANDFS_MIN_SEGSIZE / blocksize;
+ }
+
+ /* Calculate number of segments */
+ segsize = blocksize * blocks_per_segment;
+ nsegments = ((mediasize - NANDFS_NFSAREAS * erasesize) / segsize) - 2;
+ debug("segsize: %#jx", segsize);
+ debug("nsegments: %#jx", nsegments);
+}
+
+static void
+erase_device(int fd)
+{
+ int rest, failed;
+ uint64_t i, nblocks;
+ off_t offset;
+
+ failed = 0;
+ for (i = 0; i < NANDFS_NFSAREAS; i++) {
+ debug("Deleting %jx\n", i * erasesize);
+ if (g_delete(fd, i * erasesize, erasesize)) {
+ printf("cannot delete %jx\n", i * erasesize);
+ fsdata_blocks_state[i] = NANDFS_BLOCK_BAD;
+ failed++;
+ } else
+ fsdata_blocks_state[i] = NANDFS_BLOCK_GOOD;
+ }
+
+ if (failed == NANDFS_NFSAREAS) {
+ printf("%d first blocks not usable. Unable to create "
+ "filesystem.\n", failed);
+ exit(1);
+ }
+
+ for (i = 0; i < nsegments; i++) {
+ offset = NANDFS_NFSAREAS * erasesize + i * segsize;
+ if (g_delete(fd, offset, segsize)) {
+ printf("cannot delete segment %jx (offset %jd)\n",
+ i, offset);
+ bad_segments_count++;
+ bad_segments = realloc(bad_segments,
+ bad_segments_count * sizeof(uint32_t));
+ bad_segments[bad_segments_count - 1] = i;
+ }
+ }
+
+ if (bad_segments_count == nsegments) {
+ printf("no valid segments\n");
+ exit(1);
+ }
+
+ /* Delete remaining blocks at the end of device */
+ rest = mediasize % segsize;
+ nblocks = rest / erasesize;
+ for (i = 0; i < nblocks; i++) {
+ offset = (segsize * nsegments) + (i * erasesize);
+ if (g_delete(fd, offset, erasesize)) {
+ printf("cannot delete space after last segment "
+ "- probably a bad block\n");
+ }
+ }
+}
+
+static void
+erase_initial(int fd)
+{
+ char buf[512];
+ u_int i;
+
+ memset(buf, 0xff, sizeof(buf));
+
+ lseek(fd, 0, SEEK_SET);
+ for (i = 0; i < NANDFS_NFSAREAS * erasesize; i += sizeof(buf))
+ write(fd, buf, sizeof(buf));
+}
+
+static void
+create_nandfs(int fd)
+{
+
+ create_fs();
+
+ write_fs(fd);
+}
+
+static void
+print_summary(void)
+{
+
+ printf("filesystem created succesfully\n");
+ printf("total segments: %#jx valid segments: %#jx\n", nsegments,
+ nsegments - bad_segments_count);
+ printf("total space: %ju MB free: %ju MB\n",
+ (nsegments *
+ blocks_per_segment * blocksize) / (1024 * 1024),
+ ((nsegments - bad_segments_count) *
+ blocks_per_segment * blocksize) / (1024 * 1024));
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct stat sb;
+ char buf[MAXPATHLEN];
+ const char opts[] = "b:B:L:m:";
+ const char *fname;
+ int ch, fd;
+
+ while ((ch = getopt(argc, argv, opts)) != -1) {
+ switch (ch) {
+ case 'b':
+ blocksize = strtol(optarg, (char **)NULL, 10);
+ if (blocksize == 0)
+ usage();
+ break;
+ case 'B':
+ blocks_per_segment = strtol(optarg, (char **)NULL, 10);
+ if (blocks_per_segment == 0)
+ usage();
+ break;
+ case 'L':
+ volumelabel = optarg;
+ break;
+ case 'm':
+ rsv_segment_percent = strtol(optarg, (char **)NULL, 10);
+ if (rsv_segment_percent == 0)
+ usage();
+ break;
+ default:
+ usage();
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+ if (argc < 1 || argc > 2)
+ usage();
+
+ /* construct proper device path */
+ fname = *argv++;
+ if (!strchr(fname, '/')) {
+ snprintf(buf, sizeof(buf), "%s%s", _PATH_DEV, fname);
+ if (!(fname = strdup(buf)))
+ err(1, NULL);
+ }
+
+ fd = g_open(fname, 1);
+ if (fd == -1)
+ err(1, "Cannot open %s", fname);
+
+ if (fstat(fd, &sb) == -1)
+ err(1, "Cannot stat %s", fname);
+ if (!S_ISCHR(sb.st_mode))
+ warnx("%s is not a character device", fname);
+
+ check_mounted(fname, sb.st_mode);
+
+ calculate_geometry(fd);
+
+ check_parameters();
+
+ print_parameters();
+
+ if (is_nand)
+ erase_device(fd);
+ else
+ erase_initial(fd);
+
+ create_nandfs(fd);
+
+ print_summary();
+
+ g_close(fd);
+
+ return (0);
+}
+
+
diff --git a/sbin/nfsiod/Makefile b/sbin/nfsiod/Makefile
new file mode 100644
index 0000000..47cd290
--- /dev/null
+++ b/sbin/nfsiod/Makefile
@@ -0,0 +1,7 @@
+# @(#)Makefile 8.1 (Berkeley) 6/5/93
+# $FreeBSD$
+
+PROG= nfsiod
+MAN= nfsiod.8
+
+.include <bsd.prog.mk>
diff --git a/sbin/nfsiod/nfsiod.8 b/sbin/nfsiod/nfsiod.8
new file mode 100644
index 0000000..20a2c9d
--- /dev/null
+++ b/sbin/nfsiod/nfsiod.8
@@ -0,0 +1,100 @@
+.\" Copyright (c) 1989, 1991, 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.
+.\" 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.
+.\"
+.\" From: @(#)nfsiod.8 8.2 (Berkeley) 2/22/94
+.\" $FreeBSD$
+.\"
+.Dd December 26, 2009
+.Dt NFSIOD 8
+.Os
+.Sh NAME
+.Nm nfsiod
+.Nd local
+.Tn NFS
+asynchronous I/O server
+.Sh SYNOPSIS
+.Nm
+.Op Fl n Ar num_servers
+.Sh DESCRIPTION
+The
+.Nm
+utility controls the maximum number of
+.Nm
+kernel processes which run on an
+.Tn NFS
+client machine to service asynchronous I/O requests to its server.
+Having
+.Nm
+kernel processes
+improves performance but is not required for correct operation.
+.Pp
+The option is as follows:
+.Bl -tag -width indent
+.It Fl n
+Specify how many processes are permitted to be started.
+.El
+.Pp
+Without an option,
+.Nm
+displays the current settings.
+A client should allow enough number of processes to handle its maximum
+level of concurrency, typically four to six.
+.Pp
+If
+.Nm
+detects that the running kernel does not include
+.Tn NFS
+support, it will attempt to load a kernel module containing
+.Tn NFS
+code, using
+.Xr kldload 2 .
+If this fails, or no
+.Tn NFS
+module was available,
+.Nm
+exits with an error.
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr nfsstat 1 ,
+.Xr kldload 2 ,
+.Xr nfssvc 2 ,
+.Xr mountd 8 ,
+.Xr nfsd 8 ,
+.Xr rpcbind 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Bx 4.4 .
+.Pp
+Starting with
+.Fx 5.0 ,
+the utility no longer starts daemons, but only serves as a vfs
+loader and
+.Xr sysctl 3
+wrapper.
diff --git a/sbin/nfsiod/nfsiod.c b/sbin/nfsiod/nfsiod.c
new file mode 100644
index 0000000..b1db1f1
--- /dev/null
+++ b/sbin/nfsiod/nfsiod.c
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Rick Macklem at The University of Guelph.
+ *
+ * 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.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if 0
+#ifndef lint
+static const char copyright[] =
+"@(#) Copyright (c) 1989, 1993\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif
+
+#ifndef lint
+static char sccsid[] = "@(#)nfsiod.c 8.4 (Berkeley) 5/3/95";
+#endif
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/syslog.h>
+#include <sys/wait.h>
+#include <sys/linker.h>
+#include <sys/mount.h>
+#include <sys/sysctl.h>
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#define MAXNFSDCNT 20
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr, "usage: nfsiod [-n num_servers]\n");
+ exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int ch;
+ struct xvfsconf vfc;
+ int error;
+ unsigned int iodmin, iodmax, num_servers;
+ size_t len;
+
+ error = getvfsbyname("nfs", &vfc);
+ if (error) {
+ if (kldload("nfs") == -1)
+ err(1, "kldload(nfs)");
+ error = getvfsbyname("nfs", &vfc);
+ }
+ if (error)
+ errx(1, "NFS support is not available in the running kernel");
+
+ num_servers = 0;
+ while ((ch = getopt(argc, argv, "n:")) != -1)
+ switch (ch) {
+ case 'n':
+ num_servers = atoi(optarg);
+ if (num_servers < 1) {
+ warnx("nfsiod count %u; reset to %d",
+ num_servers, 1);
+ num_servers = 1;
+ }
+ if (num_servers > MAXNFSDCNT) {
+ warnx("nfsiod count %u; reset to %d",
+ num_servers, MAXNFSDCNT);
+ num_servers = MAXNFSDCNT;
+ }
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc > 0)
+ usage();
+
+ len = sizeof iodmin;
+ error = sysctlbyname("vfs.nfs.iodmin", &iodmin, &len, NULL, 0);
+ if (error < 0)
+ err(1, "sysctlbyname(\"vfs.nfs.iodmin\")");
+ len = sizeof iodmax;
+ error = sysctlbyname("vfs.nfs.iodmax", &iodmax, &len, NULL, 0);
+ if (error < 0)
+ err(1, "sysctlbyname(\"vfs.nfs.iodmax\")");
+ if (num_servers == 0) { /* no change */
+ printf("vfs.nfs.iodmin=%u\nvfs.nfs.iodmax=%u\n",
+ iodmin, iodmax);
+ exit(0);
+ }
+ /* Catch the case where we're lowering num_servers below iodmin */
+ if (iodmin > num_servers) {
+ iodmin = num_servers;
+ error = sysctlbyname("vfs.nfs.iodmin", NULL, 0, &iodmin,
+ sizeof iodmin);
+ if (error < 0)
+ err(1, "sysctlbyname(\"vfs.nfs.iodmin\")");
+ }
+ iodmax = num_servers;
+ error = sysctlbyname("vfs.nfs.iodmax", NULL, 0, &iodmax, sizeof iodmax);
+ if (error < 0)
+ err(1, "sysctlbyname(\"vfs.nfs.iodmax\")");
+ exit (0);
+}
+
diff --git a/sbin/nos-tun/Makefile b/sbin/nos-tun/Makefile
new file mode 100644
index 0000000..9f1024f
--- /dev/null
+++ b/sbin/nos-tun/Makefile
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+PROG= nos-tun
+MAN= nos-tun.8
+WARNS?= 3
+
+.include <bsd.prog.mk>
+
diff --git a/sbin/nos-tun/nos-tun.8 b/sbin/nos-tun/nos-tun.8
new file mode 100644
index 0000000..8ea6d5c
--- /dev/null
+++ b/sbin/nos-tun/nos-tun.8
@@ -0,0 +1,92 @@
+.\"
+.\" ----------------------------------------------------------------------------
+.\" "THE BEER-WARE LICENSE" (Revision 42):
+.\" <phk@FreeBSD.org> wrote this file. As long as you retain this notice you
+.\" can do whatever you want with this stuff. If we meet some day, and you think
+.\" this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp
+.\" ----------------------------------------------------------------------------
+.\"
+.\" $FreeBSD$
+.\"
+.Dd April 11, 1998
+.Dt NOS-TUN 8
+.Os
+.Sh NAME
+.Nm nos-tun
+.Nd implement ``nos'' or ``ka9q'' style IP over IP tunnel
+.Sh SYNOPSIS
+.Nm
+.Fl t
+.Ar tunnel
+.Fl s
+.Ar source
+.Fl d
+.Ar destination
+.Fl p
+.Ar protocol_number
+.Op Ar source
+.Ar target
+.Sh DESCRIPTION
+The
+.Nm
+utility is used to establish an
+.Em nos
+style tunnel, (also known as
+.Em ka9q
+or
+.Em IP-IP
+tunnel) using a
+.Xr tun 4
+kernel interface.
+.Pp
+.Ar Tunnel
+is the name of the tunnel device
+.Pa /dev/tun0
+for example.
+.Pp
+.Ar Source
+and
+.Ar destination
+are the addresses used on the tunnel device.
+If you configure the tunnel against a cisco router, use a netmask of
+.Dq 255.255.255.252
+on the cisco.
+This is because the tunnel is a point-to-point interface
+in the
+.Fx
+end, a concept cisco does not really implement.
+.Pp
+.Ar Protocol number
+sets tunnel mode.
+Original KA9Q NOS uses 94 but many people use 4
+on the worldwide backbone of ampr.org.
+.Pp
+.Ar Target
+is the address of the remote tunnel device, this must match the source
+address set on the remote end.
+.Sh EXAMPLES
+This end, a
+.Fx
+box on address 192.168.59.34:
+.Bd -literal -offset indent
+nos-tun -t /dev/tun0 -s 192.168.61.1 -d 192.168.61.2 192.168.56.45
+.Ed
+.Pp
+Remote cisco on address 192.168.56.45:
+.Bd -literal -offset indent
+interface tunnel 0
+ip address 192.168.61.2 255.255.255.252
+tunnel mode nos
+tunnel destination 192.168.59.34
+tunnel source 192.168.56.45
+.Ed
+.Sh AUTHORS
+.An -nosplit
+.An Nickolay N. Dudorov Aq Mt nnd@itfs.nsk.su
+wrote the program,
+.An Poul-Henning Kamp Aq Mt phk@FreeBSD.org
+wrote the man-page.
+.An Isao SEKI Aq Mt iseki@gongon.com
+added a new flag, IP protocol number.
+.Sh BUGS
+We do not allow for setting our source address for multihomed machines.
diff --git a/sbin/nos-tun/nos-tun.c b/sbin/nos-tun/nos-tun.c
new file mode 100644
index 0000000..ee0fc4c
--- /dev/null
+++ b/sbin/nos-tun/nos-tun.c
@@ -0,0 +1,398 @@
+/*
+ * Copyright (c) 1996, Nickolay Dudorov
+ * 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 unmodified, 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+/*
+ * 'nos-tun' program configure tunN interface as a point-to-point
+ * connection with two "pseudo"-addresses between this host and
+ * 'target'.
+ *
+ * It uses Ip-over-Ip incapsulation ( protocol number 94 - IPIP)
+ * (known as NOS-incapsulation in CISCO-routers' terminology).
+ *
+ * 'nos-tun' can works with itself and CISCO-routers.
+ * (It may also work with Linux 'nos-tun's, but
+ * I have no Linux system here to test with).
+ *
+ * BUGS (or features ?):
+ * - you must specify ONE of the target host's addresses
+ * ( nos-tun sends and accepts packets only to/from this
+ * address )
+ * - there can be only ONE tunnel between two hosts,
+ * more precisely - between given host and (one of)
+ * target hosts' address(es)
+ * (and why do you want more ?)
+ */
+
+/*
+ * Mar. 23 1999 by Isao SEKI <iseki@gongon.com>
+ * I added a new flag for ip protocol number.
+ * We are using 4 as protocol number in ampr.org.
+ *
+ */
+
+#ifndef lint
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/signal.h>
+#include <sys/socket.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+
+#include <arpa/inet.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+/* Tunnel interface configuration stuff */
+static struct ifaliasreq ifra;
+static struct ifreq ifrq;
+
+/* Global descriptors */
+int net; /* socket descriptor */
+int tun; /* tunnel descriptor */
+
+static void usage(void);
+
+static int
+Set_address(char *addr, struct sockaddr_in *sin)
+{
+ struct hostent *hp;
+
+ bzero((char *)sin, sizeof(struct sockaddr));
+ sin->sin_family = AF_INET;
+ if((sin->sin_addr.s_addr = inet_addr(addr)) == INADDR_NONE) {
+ hp = gethostbyname(addr);
+ if (!hp) {
+ syslog(LOG_ERR,"unknown host %s", addr);
+ return 1;
+ }
+ sin->sin_family = hp->h_addrtype;
+ bcopy(hp->h_addr, (caddr_t)&sin->sin_addr, hp->h_length);
+ }
+ return 0;
+}
+
+static int
+tun_open(char *dev_name, struct sockaddr *ouraddr, char *theiraddr)
+{
+ int s;
+ struct sockaddr_in *sin;
+
+ /* Open tun device */
+ tun = open(dev_name, O_RDWR);
+ if (tun < 0) {
+ syslog(LOG_ERR,"can't open %s - %m", dev_name);
+ return(1);
+ }
+
+ /*
+ * At first, name the interface.
+ */
+ bzero((char *)&ifra, sizeof(ifra));
+ bzero((char *)&ifrq, sizeof(ifrq));
+
+ strncpy(ifrq.ifr_name, dev_name+5, IFNAMSIZ);
+ strncpy(ifra.ifra_name, dev_name+5, IFNAMSIZ);
+
+ s = socket(AF_INET, SOCK_DGRAM, 0);
+ if (s < 0) {
+ syslog(LOG_ERR,"can't open socket - %m");
+ goto tunc_return;
+ }
+
+ /*
+ * Delete (previous) addresses for interface
+ *
+ * !!!!
+ * On FreeBSD this ioctl returns error
+ * when tunN have no addresses, so - log and ignore it.
+ *
+ */
+ if (ioctl(s, SIOCDIFADDR, &ifra) < 0) {
+ syslog(LOG_ERR,"SIOCDIFADDR - %m");
+ }
+
+ /*
+ * Set interface address
+ */
+ sin = (struct sockaddr_in *)&(ifra.ifra_addr);
+ bcopy(ouraddr, sin, sizeof(struct sockaddr_in));
+ sin->sin_len = sizeof(*sin);
+
+ /*
+ * Set destination address
+ */
+ sin = (struct sockaddr_in *)&(ifra.ifra_broadaddr);
+ if(Set_address(theiraddr,sin)) {
+ syslog(LOG_ERR,"bad destination address: %s",theiraddr);
+ goto stunc_return;
+ }
+ sin->sin_len = sizeof(*sin);
+
+ if (ioctl(s, SIOCAIFADDR, &ifra) < 0) {
+ syslog(LOG_ERR,"can't set interface address - %m");
+ goto stunc_return;
+ }
+
+ /*
+ * Now, bring up the interface.
+ */
+ if (ioctl(s, SIOCGIFFLAGS, &ifrq) < 0) {
+ syslog(LOG_ERR,"can't get interface flags - %m");
+ goto stunc_return;
+ }
+
+ ifrq.ifr_flags |= IFF_UP;
+ if (!(ioctl(s, SIOCSIFFLAGS, &ifrq) < 0)) {
+ close(s);
+ return(0);
+ }
+ syslog(LOG_ERR,"can't set interface UP - %m");
+stunc_return:
+ close(s);
+tunc_return:
+ close(tun);
+ return(1);
+}
+
+static void
+Finish(int signum)
+{
+ int s;
+
+ syslog(LOG_INFO,"exiting");
+
+ close(net);
+
+ s = socket(AF_INET, SOCK_DGRAM, 0);
+ if (s < 0) {
+ syslog(LOG_ERR,"can't open socket - %m");
+ goto closing_tun;
+ }
+
+ /*
+ * Shut down interface.
+ */
+ if (ioctl(s, SIOCGIFFLAGS, &ifrq) < 0) {
+ syslog(LOG_ERR,"can't get interface flags - %m");
+ goto closing_fds;
+ }
+
+ ifrq.ifr_flags &= ~(IFF_UP|IFF_RUNNING);
+ if (ioctl(s, SIOCSIFFLAGS, &ifrq) < 0) {
+ syslog(LOG_ERR,"can't set interface DOWN - %m");
+ goto closing_fds;
+ }
+
+ /*
+ * Delete addresses for interface
+ */
+ bzero(&ifra.ifra_addr, sizeof(ifra.ifra_addr));
+ bzero(&ifra.ifra_broadaddr, sizeof(ifra.ifra_addr));
+ bzero(&ifra.ifra_mask, sizeof(ifra.ifra_addr));
+ if (ioctl(s, SIOCDIFADDR, &ifra) < 0) {
+ syslog(LOG_ERR,"can't delete interface's addresses - %m");
+ }
+closing_fds:
+ close(s);
+closing_tun:
+ close(tun);
+ closelog();
+ exit(signum);
+}
+
+int main (int argc, char **argv)
+{
+ int c, len, ipoff;
+
+ char *dev_name = NULL;
+ char *point_to = NULL;
+ char *to_point = NULL;
+ char *target;
+ char *source = NULL;
+ char *protocol = NULL;
+ int protnum;
+
+ struct sockaddr t_laddr; /* Source address of tunnel */
+ struct sockaddr whereto; /* Destination of tunnel */
+ struct sockaddr wherefrom; /* Source of tunnel */
+ struct sockaddr_in *to;
+
+ char buf[0x2000]; /* Packets buffer */
+ struct ip *ip = (struct ip *)buf;
+
+ fd_set rfds; /* File descriptors for select() */
+ int nfds; /* Return from select() */
+ int lastfd; /* highest fd we care about */
+
+
+ while ((c = getopt(argc, argv, "d:s:t:p:")) != -1) {
+ switch (c) {
+ case 'd':
+ to_point = optarg;
+ break;
+ case 's':
+ point_to = optarg;
+ break;
+ case 't':
+ dev_name = optarg;
+ break;
+ case 'p':
+ protocol = optarg;
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if ((argc != 1 && argc != 2) || (dev_name == NULL) ||
+ (point_to == NULL) || (to_point == NULL)) {
+ usage();
+ }
+
+ if(protocol == NULL)
+ protnum = 94;
+ else
+ protnum = atoi(protocol);
+
+ if (argc == 1) {
+ target = *argv;
+ } else {
+ source = *argv++; target = *argv;
+ }
+
+ /* Establish logging through 'syslog' */
+ openlog("nos-tun", LOG_PID, LOG_DAEMON);
+
+ if(Set_address(point_to, (struct sockaddr_in *)&t_laddr)) {
+ closelog();
+ exit(2);
+ }
+
+ if(tun_open(dev_name, &t_laddr, to_point)) {
+ closelog();
+ exit(3);
+ }
+
+ to = (struct sockaddr_in *)&whereto;
+ if(Set_address(target, to))
+ Finish(4);
+
+ if ((net = socket(AF_INET, SOCK_RAW, protnum)) < 0) {
+ syslog(LOG_ERR,"can't open socket - %m");
+ Finish(5);
+ }
+
+ if (source) {
+ if (Set_address(source, (struct sockaddr_in *)&wherefrom))
+ Finish(9);
+ if (bind(net, &wherefrom, sizeof(wherefrom)) < 0) {
+ syslog(LOG_ERR, "can't bind source address - %m");
+ Finish(10);
+ }
+ }
+
+ if (connect(net,&whereto,sizeof(struct sockaddr_in)) < 0 ) {
+ syslog(LOG_ERR,"can't connect to target - %m");
+ close(net);
+ Finish(6);
+ }
+
+ /* Demonize it */
+ daemon(0,0);
+
+ /* Install signal handlers */
+ (void)signal(SIGHUP,Finish);
+ (void)signal(SIGINT,Finish);
+ (void)signal(SIGTERM,Finish);
+
+ if (tun > net)
+ lastfd = tun;
+ else
+ lastfd = net;
+
+ for (;;) {
+ /* Set file descriptors for select() */
+ FD_ZERO(&rfds);
+ FD_SET(tun,&rfds); FD_SET(net,&rfds);
+
+ nfds = select(lastfd+1,&rfds,NULL,NULL,NULL);
+ if(nfds < 0) {
+ syslog(LOG_ERR,"interrupted select");
+ close(net);
+ Finish(7);
+ }
+ if(nfds == 0) { /* Impossible ? */
+ syslog(LOG_ERR,"timeout in select");
+ close(net);
+ Finish(8);
+ }
+
+
+ if(FD_ISSET(net,&rfds)) {
+ /* Read from socket ... */
+ len = read(net, buf, sizeof(buf));
+ /* Check if this is "our" packet */
+ if((ip->ip_src).s_addr == (to->sin_addr).s_addr) {
+ /* ... skip encapsulation headers ... */
+ ipoff = (ip->ip_hl << 2);
+ /* ... and write to tun-device */
+ write(tun,buf+ipoff,len-ipoff);
+ }
+ }
+
+ if(FD_ISSET(tun,&rfds)) {
+ /* Read from tun ... */
+ len = read(tun, buf, sizeof(buf));
+ /* ... and send to network */
+ if(send(net, buf, len,0) <= 0) {
+ syslog(LOG_ERR,"can't send - %m");
+ }
+ }
+ }
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr,
+"usage: nos-tun -t tunnel -s source -d destination -p protocol_number [source] target\n");
+ exit(1);
+}
+
diff --git a/sbin/nvmecontrol/Makefile b/sbin/nvmecontrol/Makefile
new file mode 100644
index 0000000..ea60da3
--- /dev/null
+++ b/sbin/nvmecontrol/Makefile
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+PROG= nvmecontrol
+SRCS= nvmecontrol.c devlist.c firmware.c identify.c logpage.c \
+ perftest.c reset.c nvme_util.c
+MAN= nvmecontrol.8
+
+.PATH: ${.CURDIR}/../../sys/dev/nvme
+
+.include <bsd.prog.mk>
diff --git a/sbin/nvmecontrol/devlist.c b/sbin/nvmecontrol/devlist.c
new file mode 100644
index 0000000..35a46c1
--- /dev/null
+++ b/sbin/nvmecontrol/devlist.c
@@ -0,0 +1,116 @@
+/*-
+ * Copyright (C) 2012-2013 Intel Corporation
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "nvmecontrol.h"
+
+static void
+devlist_usage(void)
+{
+ fprintf(stderr, "usage:\n");
+ fprintf(stderr, DEVLIST_USAGE);
+ exit(1);
+}
+
+static inline uint32_t
+ns_get_sector_size(struct nvme_namespace_data *nsdata)
+{
+
+ return (1 << nsdata->lbaf[nsdata->flbas.format].lbads);
+}
+
+void
+devlist(int argc, char *argv[])
+{
+ struct nvme_controller_data cdata;
+ struct nvme_namespace_data nsdata;
+ char name[64];
+ uint8_t mn[64];
+ uint32_t i;
+ int ch, ctrlr, fd, found, ret;
+
+ while ((ch = getopt(argc, argv, "")) != -1) {
+ switch ((char)ch) {
+ default:
+ devlist_usage();
+ }
+ }
+
+ ctrlr = -1;
+ found = 0;
+
+ while (1) {
+ ctrlr++;
+ sprintf(name, "%s%d", NVME_CTRLR_PREFIX, ctrlr);
+
+ ret = open_dev(name, &fd, 0, 0);
+
+ if (ret != 0) {
+ if (ret == EACCES) {
+ warnx("could not open "_PATH_DEV"%s\n", name);
+ continue;
+ } else
+ break;
+ }
+
+ found++;
+ read_controller_data(fd, &cdata);
+ nvme_strvis(mn, cdata.mn, sizeof(mn), NVME_MODEL_NUMBER_LENGTH);
+ printf("%6s: %s\n", name, mn);
+
+ for (i = 0; i < cdata.nn; i++) {
+ sprintf(name, "%s%d%s%d", NVME_CTRLR_PREFIX, ctrlr,
+ NVME_NS_PREFIX, i+1);
+ read_namespace_data(fd, i+1, &nsdata);
+ printf(" %10s (%lldMB)\n",
+ name,
+ nsdata.nsze *
+ (long long)ns_get_sector_size(&nsdata) /
+ 1024 / 1024);
+ }
+
+ close(fd);
+ }
+
+ if (found == 0)
+ printf("No NVMe controllers found.\n");
+
+ exit(1);
+}
diff --git a/sbin/nvmecontrol/firmware.c b/sbin/nvmecontrol/firmware.c
new file mode 100644
index 0000000..281fabe
--- /dev/null
+++ b/sbin/nvmecontrol/firmware.c
@@ -0,0 +1,322 @@
+/*-
+ * Copyright (c) 2013 EMC Corp.
+ * All rights reserved.
+ *
+ * Copyright (C) 2012-2013 Intel Corporation
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/ioccom.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "nvmecontrol.h"
+
+static int
+slot_has_valid_firmware(int fd, int slot)
+{
+ struct nvme_firmware_page fw;
+ int has_fw = false;
+
+ read_logpage(fd, NVME_LOG_FIRMWARE_SLOT,
+ NVME_GLOBAL_NAMESPACE_TAG, &fw, sizeof(fw));
+
+ if (fw.revision[slot-1] != 0LLU)
+ has_fw = true;
+
+ return (has_fw);
+}
+
+static void
+read_image_file(char *path, void **buf, int32_t *size)
+{
+ struct stat sb;
+ int32_t filesize;
+ int fd;
+
+ *size = 0;
+ *buf = NULL;
+
+ if ((fd = open(path, O_RDONLY)) < 0)
+ err(1, "unable to open '%s'", path);
+ if (fstat(fd, &sb) < 0)
+ err(1, "unable to stat '%s'", path);
+
+ /*
+ * The NVMe spec does not explicitly state a maximum firmware image
+ * size, although one can be inferred from the dword size limitation
+ * for the size and offset fields in the Firmware Image Download
+ * command.
+ *
+ * Technically, the max is UINT32_MAX * sizeof(uint32_t), since the
+ * size and offsets are specified in terms of dwords (not bytes), but
+ * realistically INT32_MAX is sufficient here and simplifies matters
+ * a bit.
+ */
+ if (sb.st_size > INT32_MAX)
+ errx(1, "size of file '%s' is too large (%jd bytes)",
+ path, (intmax_t)sb.st_size);
+ filesize = (int32_t)sb.st_size;
+ if ((*buf = malloc(filesize)) == NULL)
+ errx(1, "unable to malloc %d bytes", filesize);
+ if ((*size = read(fd, *buf, filesize)) < 0)
+ err(1, "error reading '%s'", path);
+ /* XXX assuming no short reads */
+ if (*size != filesize)
+ errx(1,
+ "error reading '%s' (read %d bytes, requested %d bytes)",
+ path, *size, filesize);
+}
+
+static void
+update_firmware(int fd, uint8_t *payload, int32_t payload_size)
+{
+ struct nvme_pt_command pt;
+ int32_t off, resid, size;
+ void *chunk;
+
+ off = 0;
+ resid = payload_size;
+
+ if ((chunk = malloc(NVME_MAX_XFER_SIZE)) == NULL)
+ errx(1, "unable to malloc %d bytes", NVME_MAX_XFER_SIZE);
+
+ while (resid > 0) {
+ size = (resid >= NVME_MAX_XFER_SIZE) ?
+ NVME_MAX_XFER_SIZE : resid;
+ memcpy(chunk, payload + off, size);
+
+ memset(&pt, 0, sizeof(pt));
+ pt.cmd.opc = NVME_OPC_FIRMWARE_IMAGE_DOWNLOAD;
+ pt.cmd.cdw10 = (size / sizeof(uint32_t)) - 1;
+ pt.cmd.cdw11 = (off / sizeof(uint32_t));
+ pt.buf = chunk;
+ pt.len = size;
+ pt.is_read = 0;
+
+ if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0)
+ err(1, "firmware download request failed");
+
+ if (nvme_completion_is_error(&pt.cpl))
+ errx(1, "firmware download request returned error");
+
+ resid -= size;
+ off += size;
+ }
+}
+
+static int
+activate_firmware(int fd, int slot, int activate_action)
+{
+ struct nvme_pt_command pt;
+
+ memset(&pt, 0, sizeof(pt));
+ pt.cmd.opc = NVME_OPC_FIRMWARE_ACTIVATE;
+ pt.cmd.cdw10 = (activate_action << 3) | slot;
+ pt.is_read = 0;
+
+ if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0)
+ err(1, "firmware activate request failed");
+
+ if (pt.cpl.status.sct == NVME_SCT_COMMAND_SPECIFIC &&
+ pt.cpl.status.sc == NVME_SC_FIRMWARE_REQUIRES_RESET)
+ return 1;
+
+ if (nvme_completion_is_error(&pt.cpl))
+ errx(1, "firmware activate request returned error");
+
+ return 0;
+}
+
+static void
+firmware_usage(void)
+{
+ fprintf(stderr, "usage:\n");
+ fprintf(stderr, FIRMWARE_USAGE);
+ exit(1);
+}
+
+void
+firmware(int argc, char *argv[])
+{
+ int fd = -1, slot = 0;
+ int a_flag, s_flag, f_flag;
+ int activate_action, reboot_required;
+ char ch, *p, *image = NULL;
+ char *controller = NULL, prompt[64];
+ void *buf = NULL;
+ int32_t size = 0;
+ struct nvme_controller_data cdata;
+
+ a_flag = s_flag = f_flag = false;
+
+ while ((ch = getopt(argc, argv, "af:s:")) != -1) {
+ switch (ch) {
+ case 'a':
+ a_flag = true;
+ break;
+ case 's':
+ slot = strtol(optarg, &p, 0);
+ if (p != NULL && *p != '\0') {
+ fprintf(stderr,
+ "\"%s\" not valid slot.\n",
+ optarg);
+ firmware_usage();
+ } else if (slot == 0) {
+ fprintf(stderr,
+ "0 is not a valid slot number. "
+ "Slot numbers start at 1.\n");
+ firmware_usage();
+ } else if (slot > 7) {
+ fprintf(stderr,
+ "Slot number %s specified which is "
+ "greater than max allowed slot number of "
+ "7.\n", optarg);
+ firmware_usage();
+ }
+ s_flag = true;
+ break;
+ case 'f':
+ image = optarg;
+ f_flag = true;
+ break;
+ }
+ }
+
+ /* Check that a controller (and not a namespace) was specified. */
+ if (optind >= argc || strstr(argv[optind], NVME_NS_PREFIX) != NULL)
+ firmware_usage();
+
+ if (!f_flag && !a_flag) {
+ fprintf(stderr,
+ "Neither a replace ([-f path_to_firmware]) nor "
+ "activate ([-a]) firmware image action\n"
+ "was specified.\n");
+ firmware_usage();
+ }
+
+ if (!f_flag && a_flag && slot == 0) {
+ fprintf(stderr,
+ "Slot number to activate not specified.\n");
+ firmware_usage();
+ }
+
+ controller = argv[optind];
+ open_dev(controller, &fd, 1, 1);
+ read_controller_data(fd, &cdata);
+
+ if (cdata.oacs.firmware == 0)
+ errx(1,
+ "controller does not support firmware activate/download");
+
+ if (f_flag && slot == 1 && cdata.frmw.slot1_ro)
+ errx(1, "slot %d is marked as read only", slot);
+
+ if (slot > cdata.frmw.num_slots)
+ errx(1,
+ "slot %d specified but controller only supports %d slots",
+ slot, cdata.frmw.num_slots);
+
+ if (a_flag && !f_flag && !slot_has_valid_firmware(fd, slot))
+ errx(1,
+ "slot %d does not contain valid firmware,\n"
+ "try 'nvmecontrol logpage -p 3 %s' to get a list "
+ "of available images\n",
+ slot, controller);
+
+ if (f_flag)
+ read_image_file(image, &buf, &size);
+
+ if (f_flag && a_flag)
+ printf("You are about to download and activate "
+ "firmware image (%s) to controller %s.\n"
+ "This may damage your controller and/or "
+ "overwrite an existing firmware image.\n",
+ image, controller);
+ else if (a_flag)
+ printf("You are about to activate a new firmware "
+ "image on controller %s.\n"
+ "This may damage your controller.\n",
+ controller);
+ else if (f_flag)
+ printf("You are about to download firmware image "
+ "(%s) to controller %s.\n"
+ "This may damage your controller and/or "
+ "overwrite an existing firmware image.\n",
+ image, controller);
+
+ printf("Are you sure you want to continue? (yes/no) ");
+ while (1) {
+ fgets(prompt, sizeof(prompt), stdin);
+ if (strncasecmp(prompt, "yes", 3) == 0)
+ break;
+ if (strncasecmp(prompt, "no", 2) == 0)
+ exit(1);
+ printf("Please answer \"yes\" or \"no\". ");
+ }
+
+ if (f_flag) {
+ update_firmware(fd, buf, size);
+ if (a_flag)
+ activate_action = NVME_AA_REPLACE_ACTIVATE;
+ else
+ activate_action = NVME_AA_REPLACE_NO_ACTIVATE;
+ } else {
+ activate_action = NVME_AA_ACTIVATE;
+ }
+
+ reboot_required = activate_firmware(fd, slot, activate_action);
+
+ if (a_flag) {
+ if (reboot_required) {
+ printf("New firmware image activated but requires "
+ "conventional reset (i.e. reboot) to "
+ "complete activation.\n");
+ } else {
+ printf("New firmware image activated and will take "
+ "effect after next controller reset.\n"
+ "Controller reset can be initiated via "
+ "'nvmecontrol reset %s'\n",
+ controller);
+ }
+ }
+
+ close(fd);
+ exit(0);
+}
diff --git a/sbin/nvmecontrol/identify.c b/sbin/nvmecontrol/identify.c
new file mode 100644
index 0000000..58492e2
--- /dev/null
+++ b/sbin/nvmecontrol/identify.c
@@ -0,0 +1,284 @@
+/*-
+ * Copyright (C) 2012-2013 Intel Corporation
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <fcntl.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "nvmecontrol.h"
+
+static void
+print_controller(struct nvme_controller_data *cdata)
+{
+ uint8_t str[128];
+
+ printf("Controller Capabilities/Features\n");
+ printf("================================\n");
+ printf("Vendor ID: %04x\n", cdata->vid);
+ printf("Subsystem Vendor ID: %04x\n", cdata->ssvid);
+ nvme_strvis(str, cdata->sn, sizeof(str), NVME_SERIAL_NUMBER_LENGTH);
+ printf("Serial Number: %s\n", str);
+ nvme_strvis(str, cdata->mn, sizeof(str), NVME_MODEL_NUMBER_LENGTH);
+ printf("Model Number: %s\n", str);
+ nvme_strvis(str, cdata->fr, sizeof(str), NVME_FIRMWARE_REVISION_LENGTH);
+ printf("Firmware Version: %s\n", str);
+ printf("Recommended Arb Burst: %d\n", cdata->rab);
+ printf("IEEE OUI Identifier: %02x %02x %02x\n",
+ cdata->ieee[0], cdata->ieee[1], cdata->ieee[2]);
+ printf("Multi-Interface Cap: %02x\n", cdata->mic);
+ /* TODO: Use CAP.MPSMIN to determine true memory page size. */
+ printf("Max Data Transfer Size: ");
+ if (cdata->mdts == 0)
+ printf("Unlimited\n");
+ else
+ printf("%d\n", PAGE_SIZE * (1 << cdata->mdts));
+ printf("\n");
+
+ printf("Admin Command Set Attributes\n");
+ printf("============================\n");
+ printf("Security Send/Receive: %s\n",
+ cdata->oacs.security ? "Supported" : "Not Supported");
+ printf("Format NVM: %s\n",
+ cdata->oacs.format ? "Supported" : "Not Supported");
+ printf("Firmware Activate/Download: %s\n",
+ cdata->oacs.firmware ? "Supported" : "Not Supported");
+ printf("Abort Command Limit: %d\n", cdata->acl+1);
+ printf("Async Event Request Limit: %d\n", cdata->aerl+1);
+ printf("Number of Firmware Slots: ");
+ if (cdata->oacs.firmware != 0)
+ printf("%d\n", cdata->frmw.num_slots);
+ else
+ printf("N/A\n");
+ printf("Firmware Slot 1 Read-Only: ");
+ if (cdata->oacs.firmware != 0)
+ printf("%s\n", cdata->frmw.slot1_ro ? "Yes" : "No");
+ else
+ printf("N/A\n");
+ printf("Per-Namespace SMART Log: %s\n",
+ cdata->lpa.ns_smart ? "Yes" : "No");
+ printf("Error Log Page Entries: %d\n", cdata->elpe+1);
+ printf("Number of Power States: %d\n", cdata->npss+1);
+ printf("\n");
+
+ printf("NVM Command Set Attributes\n");
+ printf("==========================\n");
+ printf("Submission Queue Entry Size\n");
+ printf(" Max: %d\n", 1 << cdata->sqes.max);
+ printf(" Min: %d\n", 1 << cdata->sqes.min);
+ printf("Completion Queue Entry Size\n");
+ printf(" Max: %d\n", 1 << cdata->cqes.max);
+ printf(" Min: %d\n", 1 << cdata->cqes.min);
+ printf("Number of Namespaces: %d\n", cdata->nn);
+ printf("Compare Command: %s\n",
+ cdata->oncs.compare ? "Supported" : "Not Supported");
+ printf("Write Uncorrectable Command: %s\n",
+ cdata->oncs.write_unc ? "Supported" : "Not Supported");
+ printf("Dataset Management Command: %s\n",
+ cdata->oncs.dsm ? "Supported" : "Not Supported");
+ printf("Volatile Write Cache: %s\n",
+ cdata->vwc.present ? "Present" : "Not Present");
+}
+
+static void
+print_namespace(struct nvme_namespace_data *nsdata)
+{
+ uint32_t i;
+
+ printf("Size (in LBAs): %lld (%lldM)\n",
+ (long long)nsdata->nsze,
+ (long long)nsdata->nsze / 1024 / 1024);
+ printf("Capacity (in LBAs): %lld (%lldM)\n",
+ (long long)nsdata->ncap,
+ (long long)nsdata->ncap / 1024 / 1024);
+ printf("Utilization (in LBAs): %lld (%lldM)\n",
+ (long long)nsdata->nuse,
+ (long long)nsdata->nuse / 1024 / 1024);
+ printf("Thin Provisioning: %s\n",
+ nsdata->nsfeat.thin_prov ? "Supported" : "Not Supported");
+ printf("Number of LBA Formats: %d\n", nsdata->nlbaf+1);
+ printf("Current LBA Format: LBA Format #%02d\n",
+ nsdata->flbas.format);
+ for (i = 0; i <= nsdata->nlbaf; i++)
+ printf("LBA Format #%02d: Data Size: %5d Metadata Size: %5d\n",
+ i, 1 << nsdata->lbaf[i].lbads, nsdata->lbaf[i].ms);
+}
+
+static void
+identify_usage(void)
+{
+ fprintf(stderr, "usage:\n");
+ fprintf(stderr, IDENTIFY_USAGE);
+ exit(1);
+}
+
+static void
+identify_ctrlr(int argc, char *argv[])
+{
+ struct nvme_controller_data cdata;
+ int ch, fd, hexflag = 0, hexlength;
+ int verboseflag = 0;
+
+ while ((ch = getopt(argc, argv, "vx")) != -1) {
+ switch ((char)ch) {
+ case 'v':
+ verboseflag = 1;
+ break;
+ case 'x':
+ hexflag = 1;
+ break;
+ default:
+ identify_usage();
+ }
+ }
+
+ /* Check that a controller was specified. */
+ if (optind >= argc)
+ identify_usage();
+
+ open_dev(argv[optind], &fd, 1, 1);
+ read_controller_data(fd, &cdata);
+ close(fd);
+
+ if (hexflag == 1) {
+ if (verboseflag == 1)
+ hexlength = sizeof(struct nvme_controller_data);
+ else
+ hexlength = offsetof(struct nvme_controller_data,
+ reserved5);
+ print_hex(&cdata, hexlength);
+ exit(0);
+ }
+
+ if (verboseflag == 1) {
+ fprintf(stderr, "-v not currently supported without -x\n");
+ identify_usage();
+ }
+
+ print_controller(&cdata);
+ exit(0);
+}
+
+static void
+identify_ns(int argc, char *argv[])
+{
+ struct nvme_namespace_data nsdata;
+ char path[64];
+ int ch, fd, hexflag = 0, hexlength, nsid;
+ int verboseflag = 0;
+
+ while ((ch = getopt(argc, argv, "vx")) != -1) {
+ switch ((char)ch) {
+ case 'v':
+ verboseflag = 1;
+ break;
+ case 'x':
+ hexflag = 1;
+ break;
+ default:
+ identify_usage();
+ }
+ }
+
+ /* Check that a namespace was specified. */
+ if (optind >= argc)
+ identify_usage();
+
+ /*
+ * Check if the specified device node exists before continuing.
+ * This is a cleaner check for cases where the correct controller
+ * is specified, but an invalid namespace on that controller.
+ */
+ open_dev(argv[optind], &fd, 1, 1);
+ close(fd);
+
+ /*
+ * We send IDENTIFY commands to the controller, not the namespace,
+ * since it is an admin cmd. The namespace ID will be specified in
+ * the IDENTIFY command itself. So parse the namespace's device node
+ * string to get the controller substring and namespace ID.
+ */
+ parse_ns_str(argv[optind], path, &nsid);
+ open_dev(path, &fd, 1, 1);
+ read_namespace_data(fd, nsid, &nsdata);
+ close(fd);
+
+ if (hexflag == 1) {
+ if (verboseflag == 1)
+ hexlength = sizeof(struct nvme_namespace_data);
+ else
+ hexlength = offsetof(struct nvme_namespace_data,
+ reserved6);
+ print_hex(&nsdata, hexlength);
+ exit(0);
+ }
+
+ if (verboseflag == 1) {
+ fprintf(stderr, "-v not currently supported without -x\n");
+ identify_usage();
+ }
+
+ print_namespace(&nsdata);
+ exit(0);
+}
+
+void
+identify(int argc, char *argv[])
+{
+ char *target;
+
+ if (argc < 2)
+ identify_usage();
+
+ while (getopt(argc, argv, "vx") != -1) ;
+
+ /* Check that a controller or namespace was specified. */
+ if (optind >= argc)
+ identify_usage();
+
+ target = argv[optind];
+
+ optreset = 1;
+ optind = 1;
+
+ /*
+ * If device node contains "ns", we consider it a namespace,
+ * otherwise, consider it a controller.
+ */
+ if (strstr(target, NVME_NS_PREFIX) == NULL)
+ identify_ctrlr(argc, argv);
+ else
+ identify_ns(argc, argv);
+}
diff --git a/sbin/nvmecontrol/logpage.c b/sbin/nvmecontrol/logpage.c
new file mode 100644
index 0000000..e330988
--- /dev/null
+++ b/sbin/nvmecontrol/logpage.c
@@ -0,0 +1,357 @@
+/*-
+ * Copyright (c) 2013 EMC Corp.
+ * All rights reserved.
+ *
+ * Copyright (C) 2012-2013 Intel Corporation
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/ioccom.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "nvmecontrol.h"
+
+#define DEFAULT_SIZE (4096)
+#define MAX_FW_SLOTS (7)
+
+typedef void (*print_fn_t)(void *buf, uint32_t size);
+
+static void *
+get_log_buffer(uint32_t size)
+{
+ void *buf;
+
+ if ((buf = malloc(size)) == NULL)
+ errx(1, "unable to malloc %u bytes", size);
+
+ memset(buf, 0, size);
+ return (buf);
+}
+
+void
+read_logpage(int fd, uint8_t log_page, int nsid, void *payload,
+ uint32_t payload_size)
+{
+ struct nvme_pt_command pt;
+
+ memset(&pt, 0, sizeof(pt));
+ pt.cmd.opc = NVME_OPC_GET_LOG_PAGE;
+ pt.cmd.nsid = nsid;
+ pt.cmd.cdw10 = ((payload_size/sizeof(uint32_t)) - 1) << 16;
+ pt.cmd.cdw10 |= log_page;
+ pt.buf = payload;
+ pt.len = payload_size;
+ pt.is_read = 1;
+
+ if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0)
+ err(1, "get log page request failed");
+
+ if (nvme_completion_is_error(&pt.cpl))
+ errx(1, "get log page request returned error");
+}
+
+static void
+print_log_error(void *buf, uint32_t size)
+{
+ int i, nentries;
+ struct nvme_error_information_entry *entry = buf;
+ struct nvme_status *status;
+
+ printf("Error Information Log\n");
+ printf("=====================\n");
+
+ if (entry->error_count == 0) {
+ printf("No error entries found\n");
+ return;
+ }
+
+ nentries = size/sizeof(struct nvme_error_information_entry);
+ for (i = 0; i < nentries; i++, entry++) {
+ if (entry->error_count == 0)
+ break;
+
+ status = &entry->status;
+ printf("Entry %02d\n", i + 1);
+ printf("=========\n");
+ printf(" Error count: %ju\n", entry->error_count);
+ printf(" Submission queue ID: %u\n", entry->sqid);
+ printf(" Command ID: %u\n", entry->cid);
+ /* TODO: Export nvme_status_string structures from kernel? */
+ printf(" Status:\n");
+ printf(" Phase tag: %d\n", status->p);
+ printf(" Status code: %d\n", status->sc);
+ printf(" Status code type: %d\n", status->sct);
+ printf(" More: %d\n", status->m);
+ printf(" DNR: %d\n", status->dnr);
+ printf(" Error location: %u\n", entry->error_location);
+ printf(" LBA: %ju\n", entry->lba);
+ printf(" Namespace ID: %u\n", entry->nsid);
+ printf(" Vendor specific info: %u\n", entry->vendor_specific);
+ }
+}
+
+static void
+print_log_health(void *buf, uint32_t size __unused)
+{
+ struct nvme_health_information_page *health = buf;
+
+ printf("SMART/Health Information Log\n");
+ printf("============================\n");
+
+ printf("Critical Warning State: 0x%02x\n",
+ health->critical_warning.raw);
+ printf(" Available spare: %d\n",
+ health->critical_warning.bits.available_spare);
+ printf(" Temperature: %d\n",
+ health->critical_warning.bits.temperature);
+ printf(" Device reliability: %d\n",
+ health->critical_warning.bits.device_reliability);
+ printf(" Read only: %d\n",
+ health->critical_warning.bits.read_only);
+ printf(" Volatile memory backup: %d\n",
+ health->critical_warning.bits.volatile_memory_backup);
+ printf("Temperature: %u K, %2.2f C, %3.2f F\n",
+ health->temperature,
+ (float)health->temperature - (float)273.15,
+ ((float)health->temperature * (float)9/5) - (float)459.67);
+ printf("Available spare: %u\n",
+ health->available_spare);
+ printf("Available spare threshold: %u\n",
+ health->available_spare_threshold);
+ printf("Percentage used: %u\n",
+ health->percentage_used);
+
+ /*
+ * TODO: These are pretty ugly in hex. Is there a library that
+ * will convert 128-bit unsigned values to decimal?
+ */
+ printf("Data units (512 byte) read: 0x%016jx%016jx\n",
+ health->data_units_read[1],
+ health->data_units_read[0]);
+ printf("Data units (512 byte) written: 0x%016jx%016jx\n",
+ health->data_units_written[1],
+ health->data_units_written[0]);
+ printf("Host read commands: 0x%016jx%016jx\n",
+ health->host_read_commands[1],
+ health->host_read_commands[0]);
+ printf("Host write commands: 0x%016jx%016jx\n",
+ health->host_write_commands[1],
+ health->host_write_commands[0]);
+ printf("Controller busy time (minutes): 0x%016jx%016jx\n",
+ health->controller_busy_time[1],
+ health->controller_busy_time[0]);
+ printf("Power cycles: 0x%016jx%016jx\n",
+ health->power_cycles[1],
+ health->power_cycles[0]);
+ printf("Power on hours: 0x%016jx%016jx\n",
+ health->power_on_hours[1],
+ health->power_on_hours[0]);
+ printf("Unsafe shutdowns: 0x%016jx%016jx\n",
+ health->unsafe_shutdowns[1],
+ health->unsafe_shutdowns[0]);
+ printf("Media errors: 0x%016jx%016jx\n",
+ health->media_errors[1],
+ health->media_errors[0]);
+ printf("No. error info log entries: 0x%016jx%016jx\n",
+ health->num_error_info_log_entries[1],
+ health->num_error_info_log_entries[0]);
+}
+
+static void
+print_log_firmware(void *buf, uint32_t size __unused)
+{
+ int i;
+ const char *status;
+ struct nvme_firmware_page *fw = buf;
+
+ printf("Firmware Slot Log\n");
+ printf("=================\n");
+
+ for (i = 0; i < MAX_FW_SLOTS; i++) {
+ printf("Slot %d: ", i + 1);
+ if (fw->afi.slot == i + 1)
+ status = " Active";
+ else
+ status = "Inactive";
+
+ if (fw->revision[i] == 0LLU)
+ printf("Empty\n");
+ else
+ if (isprint(*(char *)&fw->revision[i]))
+ printf("[%s] %.8s\n", status,
+ (char *)&fw->revision[i]);
+ else
+ printf("[%s] %016jx\n", status,
+ fw->revision[i]);
+ }
+}
+
+static struct logpage_function {
+ uint8_t log_page;
+ print_fn_t fn;
+} logfuncs[] = {
+ {NVME_LOG_ERROR, print_log_error },
+ {NVME_LOG_HEALTH_INFORMATION, print_log_health },
+ {NVME_LOG_FIRMWARE_SLOT, print_log_firmware },
+ {0, NULL },
+};
+
+static void
+logpage_usage(void)
+{
+ fprintf(stderr, "usage:\n");
+ fprintf(stderr, LOGPAGE_USAGE);
+ exit(1);
+}
+
+void
+logpage(int argc, char *argv[])
+{
+ int fd, nsid;
+ int log_page = 0, pageflag = false;
+ int hexflag = false, ns_specified;
+ char ch, *p;
+ char cname[64];
+ uint32_t size;
+ void *buf;
+ struct logpage_function *f;
+ struct nvme_controller_data cdata;
+ print_fn_t print_fn;
+
+ while ((ch = getopt(argc, argv, "p:x")) != -1) {
+ switch (ch) {
+ case 'p':
+ /* TODO: Add human-readable ASCII page IDs */
+ log_page = strtol(optarg, &p, 0);
+ if (p != NULL && *p != '\0') {
+ fprintf(stderr,
+ "\"%s\" not valid log page id.\n",
+ optarg);
+ logpage_usage();
+ /* TODO: Define valid log page id ranges in nvme.h? */
+ } else if (log_page == 0 ||
+ (log_page >= 0x04 && log_page <= 0x7F) ||
+ (log_page >= 0x80 && log_page <= 0xBF)) {
+ fprintf(stderr,
+ "\"%s\" not valid log page id.\n",
+ optarg);
+ logpage_usage();
+ }
+ pageflag = true;
+ break;
+ case 'x':
+ hexflag = true;
+ break;
+ }
+ }
+
+ if (!pageflag) {
+ printf("Missing page_id (-p).\n");
+ logpage_usage();
+ }
+
+ /* Check that a controller and/or namespace was specified. */
+ if (optind >= argc)
+ logpage_usage();
+
+ if (strstr(argv[optind], NVME_NS_PREFIX) != NULL) {
+ ns_specified = true;
+ parse_ns_str(argv[optind], cname, &nsid);
+ open_dev(cname, &fd, 1, 1);
+ } else {
+ ns_specified = false;
+ nsid = NVME_GLOBAL_NAMESPACE_TAG;
+ open_dev(argv[optind], &fd, 1, 1);
+ }
+
+ /*
+ * The log page attribtues indicate whether or not the controller
+ * supports the SMART/Health information log page on a per
+ * namespace basis.
+ */
+ if (ns_specified) {
+ if (log_page != NVME_LOG_HEALTH_INFORMATION)
+ errx(1, "log page %d valid only at controller level",
+ log_page);
+ read_controller_data(fd, &cdata);
+ if (cdata.lpa.ns_smart == 0)
+ errx(1,
+ "controller does not support per namespace "
+ "smart/health information");
+ }
+
+ print_fn = print_hex;
+ if (!hexflag) {
+ /*
+ * See if there is a pretty print function for the
+ * specified log page. If one isn't found, we
+ * just revert to the default (print_hex).
+ */
+ f = logfuncs;
+ while (f->log_page > 0) {
+ if (log_page == f->log_page) {
+ print_fn = f->fn;
+ break;
+ }
+ f++;
+ }
+ }
+
+ /* Read the log page */
+ switch (log_page) {
+ case NVME_LOG_ERROR:
+ size = sizeof(struct nvme_error_information_entry);
+ size *= (cdata.elpe + 1);
+ break;
+ case NVME_LOG_HEALTH_INFORMATION:
+ size = sizeof(struct nvme_health_information_page);
+ break;
+ case NVME_LOG_FIRMWARE_SLOT:
+ size = sizeof(struct nvme_firmware_page);
+ break;
+ default:
+ size = DEFAULT_SIZE;
+ break;
+ }
+
+ buf = get_log_buffer(size);
+ read_logpage(fd, log_page, nsid, buf, size);
+ print_fn(buf, size);
+
+ close(fd);
+ exit(0);
+}
diff --git a/sbin/nvmecontrol/nvmecontrol.8 b/sbin/nvmecontrol/nvmecontrol.8
new file mode 100644
index 0000000..3b4b5c2
--- /dev/null
+++ b/sbin/nvmecontrol/nvmecontrol.8
@@ -0,0 +1,130 @@
+.\"
+.\" Copyright (c) 2012 Intel Corporation
+.\" 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,
+.\" without modification.
+.\" 2. Redistributions in binary form must reproduce at minimum a disclaimer
+.\" substantially similar to the "NO WARRANTY" disclaimer below
+.\" ("Disclaimer") and any redistribution must be conditioned upon
+.\" including a substantially similar Disclaimer requirement for further
+.\" binary redistribution.
+.\"
+.\" NO WARRANTY
+.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+.\" "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
+.\" A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+.\" HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES.
+.\"
+.\" nvmecontrol man page.
+.\"
+.\" Author: Jim Harris <jimharris@FreeBSD.org>
+.\"
+.\" $FreeBSD$
+.\"
+.Dd March 26, 2013
+.Dt NVMECONTROL 8
+.Os
+.Sh NAME
+.Nm nvmecontrol
+.Nd NVM Express control utility
+.Sh SYNOPSIS
+.Nm
+.Ic devlist
+.Nm
+.Ic identify
+.Op Fl v
+.Op Fl x
+.Aq device id
+.Nm
+.Ic perftest
+.Aq Fl n Ar num_threads
+.Aq Fl o Ar read|write
+.Op Fl p
+.Aq Fl s Ar size_in_bytes
+.Aq Fl t Ar time_in_sec
+.Aq namespace id
+.Nm
+.Ic reset
+.Aq controller id
+.Nm
+.Ic logpage
+.Aq Fl p Ar page_id
+.Op Fl x
+.Aq device id
+.Aq namespace id
+.Nm
+.Ic firmware
+.Op Fl s Ar slot
+.Op Fl f Ar path_to_firmware
+.Op Fl a
+.Aq device id
+.Sh DESCRIPTION
+NVM Express (NVMe) is a storage protocol standard, for SSDs and other
+high-speed storage devices over PCI Express.
+.Sh EXAMPLES
+.Dl nvmecontrol devlist
+.Pp
+Display a list of NVMe controllers and namespaces along with their device nodes.
+.Pp
+.Dl nvmecontrol identify nvme0
+.Pp
+Display a human-readable summary of the nvme0 IDENTIFY_CONTROLLER data.
+.Pp
+.Dl nvmecontrol identify -x -v nvme0ns1
+.Pp
+Display a hexadecimal dump of the nvme0 IDENTIFY_NAMESPACE data for namespace
+1.
+.Pp
+.Dl nvmecontrol perftest -n 32 -o read -s 512 -t 30 nvme0ns1
+.Pp
+Run a performance test on nvme0ns1 using 32 kernel threads for 30 seconds. Each
+thread will issue a single 512 byte read command. Results are printed to
+stdout when 30 seconds expires.
+.Pp
+.Dl nvmecontrol reset nvme0
+.Pp
+Perform a controller-level reset of the nvme0 controller.
+.Pp
+.Dl nvmecontrol logpage -p 1 nvme0
+.Pp
+Display a human-readable summary of the nvme0 controller's Error Information Log.
+Log pages defined by the NVMe specification include Error Information Log (ID=1),
+SMART/Health Information Log (ID=2), and Firmware Slot Log (ID=3).
+.Pp
+.Dl nvmecontrol logpage -p 1 -x nvme0
+.Pp
+Display a hexidecimal dump of the nvme0 controller's Error Information Log.
+.Pp
+.Dl nvmecontrol firmware -s 2 -f /tmp/nvme_firmware nvme0
+.Pp
+Download the firmware image contained in "/tmp/nvme_firmware" to slot 2 of the
+nvme0 controller, but do not activate the image.
+.Pp
+.Dl nvmecontrol firmware -s 4 -a nvme0
+.Pp
+Activate the firmware in slot 4 of the nvme0 controller on the next reset.
+.Pp
+.Dl nvmecontrol firmware -s 7 -f /tmp/nvme_firmware -a nvme0
+.Pp
+Download the firmware image contained in "/tmp/nvme_firmware" to slot 7 of the
+nvme0 controller and activate it on the next reset.
+.Sh AUTHORS
+.An -nosplit
+.Nm
+was developed by Intel and originally written by
+.An Jim Harris Aq Mt jimharris@FreeBSD.org .
+.Pp
+This man page was written by
+.An Jim Harris Aq Mt jimharris@FreeBSD.org .
diff --git a/sbin/nvmecontrol/nvmecontrol.c b/sbin/nvmecontrol/nvmecontrol.c
new file mode 100644
index 0000000..4dee190
--- /dev/null
+++ b/sbin/nvmecontrol/nvmecontrol.c
@@ -0,0 +1,234 @@
+/*-
+ * Copyright (C) 2012-2013 Intel Corporation
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/ioccom.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "nvmecontrol.h"
+
+typedef void (*nvme_fn_t)(int argc, char *argv[]);
+
+static struct nvme_function {
+ const char *name;
+ nvme_fn_t fn;
+ const char *usage;
+} funcs[] = {
+ {"devlist", devlist, DEVLIST_USAGE},
+ {"identify", identify, IDENTIFY_USAGE},
+ {"perftest", perftest, PERFTEST_USAGE},
+ {"reset", reset, RESET_USAGE},
+ {"logpage", logpage, LOGPAGE_USAGE},
+ {"firmware", firmware, FIRMWARE_USAGE},
+ {NULL, NULL, NULL},
+};
+
+static void
+usage(void)
+{
+ struct nvme_function *f;
+
+ f = funcs;
+ fprintf(stderr, "usage:\n");
+ while (f->name != NULL) {
+ fprintf(stderr, "%s", f->usage);
+ f++;
+ }
+ exit(1);
+}
+
+static void
+print_bytes(void *data, uint32_t length)
+{
+ uint32_t i, j;
+ uint8_t *p, *end;
+
+ end = (uint8_t *)data + length;
+
+ for (i = 0; i < length; i++) {
+ p = (uint8_t *)data + (i*16);
+ printf("%03x: ", i*16);
+ for (j = 0; j < 16 && p < end; j++)
+ printf("%02x ", *p++);
+ if (p >= end)
+ break;
+ printf("\n");
+ }
+ printf("\n");
+}
+
+static void
+print_dwords(void *data, uint32_t length)
+{
+ uint32_t *p;
+ uint32_t i, j;
+
+ p = (uint32_t *)data;
+ length /= sizeof(uint32_t);
+
+ for (i = 0; i < length; i+=8) {
+ printf("%03x: ", i*4);
+ for (j = 0; j < 8; j++)
+ printf("%08x ", p[i+j]);
+ printf("\n");
+ }
+
+ printf("\n");
+}
+
+void
+print_hex(void *data, uint32_t length)
+{
+ if (length >= sizeof(uint32_t) || length % sizeof(uint32_t) == 0)
+ print_dwords(data, length);
+ else
+ print_bytes(data, length);
+}
+
+void
+read_controller_data(int fd, struct nvme_controller_data *cdata)
+{
+ struct nvme_pt_command pt;
+
+ memset(&pt, 0, sizeof(pt));
+ pt.cmd.opc = NVME_OPC_IDENTIFY;
+ pt.cmd.cdw10 = 1;
+ pt.buf = cdata;
+ pt.len = sizeof(*cdata);
+ pt.is_read = 1;
+
+ if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0)
+ err(1, "identify request failed");
+
+ if (nvme_completion_is_error(&pt.cpl))
+ errx(1, "identify request returned error");
+}
+
+void
+read_namespace_data(int fd, int nsid, struct nvme_namespace_data *nsdata)
+{
+ struct nvme_pt_command pt;
+
+ memset(&pt, 0, sizeof(pt));
+ pt.cmd.opc = NVME_OPC_IDENTIFY;
+ pt.cmd.nsid = nsid;
+ pt.buf = nsdata;
+ pt.len = sizeof(*nsdata);
+ pt.is_read = 1;
+
+ if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0)
+ err(1, "identify request failed");
+
+ if (nvme_completion_is_error(&pt.cpl))
+ errx(1, "identify request returned error");
+}
+
+int
+open_dev(const char *str, int *fd, int show_error, int exit_on_error)
+{
+ char full_path[64];
+
+ if (!strnstr(str, NVME_CTRLR_PREFIX, strlen(NVME_CTRLR_PREFIX))) {
+ if (show_error)
+ warnx("controller/namespace ids must begin with '%s'",
+ NVME_CTRLR_PREFIX);
+ if (exit_on_error)
+ exit(1);
+ else
+ return (EINVAL);
+ }
+
+ snprintf(full_path, sizeof(full_path), _PATH_DEV"%s", str);
+ *fd = open(full_path, O_RDWR);
+ if (*fd < 0) {
+ if (show_error)
+ warn("could not open %s", full_path);
+ if (exit_on_error)
+ exit(1);
+ else
+ return (errno);
+ }
+
+ return (0);
+}
+
+void
+parse_ns_str(const char *ns_str, char *ctrlr_str, int *nsid)
+{
+ char *nsloc;
+
+ /*
+ * Pull the namespace id from the string. +2 skips past the "ns" part
+ * of the string. Don't search past 10 characters into the string,
+ * otherwise we know it is malformed.
+ */
+ nsloc = strnstr(ns_str, NVME_NS_PREFIX, 10);
+ if (nsloc != NULL)
+ *nsid = strtol(nsloc + 2, NULL, 10);
+ if (nsloc == NULL || (*nsid == 0 && errno != 0))
+ errx(1, "invalid namespace ID '%s'", ns_str);
+
+ /*
+ * The controller string will include only the nvmX part of the
+ * nvmeXnsY string.
+ */
+ snprintf(ctrlr_str, nsloc - ns_str + 1, "%s", ns_str);
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct nvme_function *f;
+
+ if (argc < 2)
+ usage();
+
+ f = funcs;
+ while (f->name != NULL) {
+ if (strcmp(argv[1], f->name) == 0)
+ f->fn(argc-1, &argv[1]);
+ f++;
+ }
+
+ usage();
+
+ return (0);
+}
diff --git a/sbin/nvmecontrol/nvmecontrol.h b/sbin/nvmecontrol/nvmecontrol.h
new file mode 100644
index 0000000..8401dd7
--- /dev/null
+++ b/sbin/nvmecontrol/nvmecontrol.h
@@ -0,0 +1,74 @@
+/*-
+ * Copyright (C) 2012-2013 Intel Corporation
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef __NVMECONTROL_H__
+#define __NVMECONTROL_H__
+
+#include <dev/nvme/nvme.h>
+
+#define NVME_CTRLR_PREFIX "nvme"
+#define NVME_NS_PREFIX "ns"
+
+#define DEVLIST_USAGE \
+" nvmecontrol devlist\n"
+
+#define IDENTIFY_USAGE \
+" nvmecontrol identify [-x [-v]] <controller id|namespace id>\n"
+
+#define PERFTEST_USAGE \
+" nvmecontrol perftest <-n num_threads> <-o read|write>\n" \
+" <-s size_in_bytes> <-t time_in_seconds>\n" \
+" <-i intr|wait> [-f refthread] [-p]\n" \
+" <namespace id>\n"
+
+#define RESET_USAGE \
+" nvmecontrol reset <controller id>\n"
+
+#define LOGPAGE_USAGE \
+" nvmecontrol logpage <-p page_id> [-x] <controller id|namespace id>\n" \
+
+#define FIRMWARE_USAGE \
+" nvmecontrol firmware [-s slot] [-f path_to_firmware] [-a] <controller id>\n"
+
+void devlist(int argc, char *argv[]);
+void identify(int argc, char *argv[]);
+void perftest(int argc, char *argv[]);
+void reset(int argc, char *argv[]);
+void logpage(int argc, char *argv[]);
+void firmware(int argc, char *argv[]);
+
+int open_dev(const char *str, int *fd, int show_error, int exit_on_error);
+void parse_ns_str(const char *ns_str, char *ctrlr_str, int *nsid);
+void read_controller_data(int fd, struct nvme_controller_data *cdata);
+void read_namespace_data(int fd, int nsid, struct nvme_namespace_data *nsdata);
+void print_hex(void *data, uint32_t length);
+void read_logpage(int fd, uint8_t log_page, int nsid, void *payload,
+ uint32_t payload_size);
+
+#endif
+
diff --git a/sbin/nvmecontrol/perftest.c b/sbin/nvmecontrol/perftest.c
new file mode 100644
index 0000000..cc91198
--- /dev/null
+++ b/sbin/nvmecontrol/perftest.c
@@ -0,0 +1,176 @@
+/*-
+ * Copyright (C) 2012-2013 Intel Corporation
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/ioccom.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "nvmecontrol.h"
+
+static void
+print_perftest(struct nvme_io_test *io_test, bool perthread)
+{
+ uint64_t io_completed = 0, iops, mbps;
+ uint32_t i;
+
+ for (i = 0; i < io_test->num_threads; i++)
+ io_completed += io_test->io_completed[i];
+
+ iops = io_completed/io_test->time;
+ mbps = iops * io_test->size / (1024*1024);
+
+ printf("Threads: %2d Size: %6d %5s Time: %3d IO/s: %7ju MB/s: %4ju\n",
+ io_test->num_threads, io_test->size,
+ io_test->opc == NVME_OPC_READ ? "READ" : "WRITE",
+ io_test->time, (uintmax_t)iops, (uintmax_t)mbps);
+
+ if (perthread)
+ for (i = 0; i < io_test->num_threads; i++)
+ printf("\t%3d: %8ju IO/s\n", i,
+ (uintmax_t)io_test->io_completed[i]/io_test->time);
+}
+
+static void
+perftest_usage(void)
+{
+ fprintf(stderr, "usage:\n");
+ fprintf(stderr, PERFTEST_USAGE);
+ exit(1);
+}
+
+void
+perftest(int argc, char *argv[])
+{
+ struct nvme_io_test io_test;
+ int fd;
+ char ch;
+ char *p;
+ u_long ioctl_cmd = NVME_IO_TEST;
+ bool nflag, oflag, sflag, tflag;
+ int perthread = 0;
+
+ nflag = oflag = sflag = tflag = false;
+
+ memset(&io_test, 0, sizeof(io_test));
+
+ while ((ch = getopt(argc, argv, "f:i:n:o:ps:t:")) != -1) {
+ switch (ch) {
+ case 'f':
+ if (!strcmp(optarg, "refthread"))
+ io_test.flags |= NVME_TEST_FLAG_REFTHREAD;
+ break;
+ case 'i':
+ if (!strcmp(optarg, "bio") ||
+ !strcmp(optarg, "wait"))
+ ioctl_cmd = NVME_BIO_TEST;
+ else if (!strcmp(optarg, "io") ||
+ !strcmp(optarg, "intr"))
+ ioctl_cmd = NVME_IO_TEST;
+ break;
+ case 'n':
+ nflag = true;
+ io_test.num_threads = strtoul(optarg, &p, 0);
+ if (p != NULL && *p != '\0') {
+ fprintf(stderr,
+ "\"%s\" not valid number of threads.\n",
+ optarg);
+ perftest_usage();
+ } else if (io_test.num_threads == 0 ||
+ io_test.num_threads > 128) {
+ fprintf(stderr,
+ "\"%s\" not valid number of threads.\n",
+ optarg);
+ perftest_usage();
+ }
+ break;
+ case 'o':
+ oflag = true;
+ if (!strcmp(optarg, "read") || !strcmp(optarg, "READ"))
+ io_test.opc = NVME_OPC_READ;
+ else if (!strcmp(optarg, "write") ||
+ !strcmp(optarg, "WRITE"))
+ io_test.opc = NVME_OPC_WRITE;
+ else {
+ fprintf(stderr, "\"%s\" not valid opcode.\n",
+ optarg);
+ perftest_usage();
+ }
+ break;
+ case 'p':
+ perthread = 1;
+ break;
+ case 's':
+ sflag = true;
+ io_test.size = strtoul(optarg, &p, 0);
+ if (p == NULL || *p == '\0' || toupper(*p) == 'B') {
+ // do nothing
+ } else if (toupper(*p) == 'K') {
+ io_test.size *= 1024;
+ } else if (toupper(*p) == 'M') {
+ io_test.size *= 1024 * 1024;
+ } else {
+ fprintf(stderr, "\"%s\" not valid size.\n",
+ optarg);
+ perftest_usage();
+ }
+ break;
+ case 't':
+ tflag = true;
+ io_test.time = strtoul(optarg, &p, 0);
+ if (p != NULL && *p != '\0') {
+ fprintf(stderr,
+ "\"%s\" not valid time duration.\n",
+ optarg);
+ perftest_usage();
+ }
+ break;
+ }
+ }
+
+ if (!nflag || !oflag || !sflag || !tflag || optind >= argc)
+ perftest_usage();
+
+ open_dev(argv[optind], &fd, 1, 1);
+ if (ioctl(fd, ioctl_cmd, &io_test) < 0)
+ err(1, "ioctl NVME_IO_TEST failed");
+
+ close(fd);
+ print_perftest(&io_test, perthread);
+ exit(0);
+}
diff --git a/sbin/nvmecontrol/reset.c b/sbin/nvmecontrol/reset.c
new file mode 100644
index 0000000..8ce597e
--- /dev/null
+++ b/sbin/nvmecontrol/reset.c
@@ -0,0 +1,71 @@
+/*-
+ * Copyright (C) 2012-2013 Intel Corporation
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/ioccom.h>
+
+#include <err.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "nvmecontrol.h"
+
+static void
+reset_usage(void)
+{
+ fprintf(stderr, "usage:\n");
+ fprintf(stderr, RESET_USAGE);
+ exit(1);
+}
+
+void
+reset(int argc, char *argv[])
+{
+ int ch, fd;
+
+ while ((ch = getopt(argc, argv, "")) != -1) {
+ switch ((char)ch) {
+ default:
+ reset_usage();
+ }
+ }
+
+ /* Check that a controller was specified. */
+ if (optind >= argc)
+ reset_usage();
+
+ open_dev(argv[optind], &fd, 1, 1);
+ if (ioctl(fd, NVME_RESET_CONTROLLER) < 0)
+ err(1, "reset request to %s failed", argv[optind]);
+
+ exit(0);
+}
diff --git a/sbin/pfctl/Makefile b/sbin/pfctl/Makefile
new file mode 100644
index 0000000..de3b7ec
--- /dev/null
+++ b/sbin/pfctl/Makefile
@@ -0,0 +1,33 @@
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+# pf_ruleset.c is shared between kernel and pfctl
+.PATH: ${.CURDIR}/../../sys/netpfil/pf
+
+PROG= pfctl
+MAN= pfctl.8
+
+SRCS = pfctl.c parse.y pfctl_parser.c pf_print_state.c pfctl_altq.c
+SRCS+= pfctl_osfp.c pfctl_radix.c pfctl_table.c pfctl_qstats.c
+SRCS+= pfctl_optimize.c
+SRCS+= pf_ruleset.c
+
+WARNS?= 2
+CFLAGS+= -Wall -Wmissing-prototypes -Wno-uninitialized
+CFLAGS+= -Wstrict-prototypes
+CFLAGS+= -DENABLE_ALTQ -I${.CURDIR}
+
+# Need to use "WITH_" prefix to not conflict with the l/y INET/INET6 keywords
+.if ${MK_INET6_SUPPORT} != "no"
+CFLAGS+= -DWITH_INET6
+.endif
+.if ${MK_INET_SUPPORT} != "no"
+CFLAGS+= -DWITH_INET
+.endif
+
+YFLAGS=
+
+LIBADD= m md
+
+.include <bsd.prog.mk>
diff --git a/sbin/pfctl/parse.y b/sbin/pfctl/parse.y
new file mode 100644
index 0000000..aebf8a7
--- /dev/null
+++ b/sbin/pfctl/parse.y
@@ -0,0 +1,6038 @@
+/* $OpenBSD: parse.y,v 1.554 2008/10/17 12:59:53 henning Exp $ */
+
+/*
+ * Copyright (c) 2001 Markus Friedl. All rights reserved.
+ * Copyright (c) 2001 Daniel Hartmeier. All rights reserved.
+ * Copyright (c) 2001 Theo de Raadt. All rights reserved.
+ * Copyright (c) 2002,2003 Henning Brauer. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+%{
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#ifdef __FreeBSD__
+#include <sys/sysctl.h>
+#endif
+#include <net/if.h>
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <netinet/ip_icmp.h>
+#include <netinet/icmp6.h>
+#include <net/pfvar.h>
+#include <arpa/inet.h>
+#include <net/altq/altq.h>
+#include <net/altq/altq_cbq.h>
+#include <net/altq/altq_priq.h>
+#include <net/altq/altq_hfsc.h>
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <netdb.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+#include <math.h>
+#include <err.h>
+#include <limits.h>
+#include <pwd.h>
+#include <grp.h>
+#include <md5.h>
+
+#include "pfctl_parser.h"
+#include "pfctl.h"
+
+static struct pfctl *pf = NULL;
+static int debug = 0;
+static int rulestate = 0;
+static u_int16_t returnicmpdefault =
+ (ICMP_UNREACH << 8) | ICMP_UNREACH_PORT;
+static u_int16_t returnicmp6default =
+ (ICMP6_DST_UNREACH << 8) | ICMP6_DST_UNREACH_NOPORT;
+static int blockpolicy = PFRULE_DROP;
+static int require_order = 1;
+static int default_statelock;
+
+TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files);
+static struct file {
+ TAILQ_ENTRY(file) entry;
+ FILE *stream;
+ char *name;
+ int lineno;
+ int errors;
+} *file;
+struct file *pushfile(const char *, int);
+int popfile(void);
+int check_file_secrecy(int, const char *);
+int yyparse(void);
+int yylex(void);
+int yyerror(const char *, ...);
+int kw_cmp(const void *, const void *);
+int lookup(char *);
+int lgetc(int);
+int lungetc(int);
+int findeol(void);
+
+TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead);
+struct sym {
+ TAILQ_ENTRY(sym) entry;
+ int used;
+ int persist;
+ char *nam;
+ char *val;
+};
+int symset(const char *, const char *, int);
+char *symget(const char *);
+
+int atoul(char *, u_long *);
+
+enum {
+ PFCTL_STATE_NONE,
+ PFCTL_STATE_OPTION,
+ PFCTL_STATE_SCRUB,
+ PFCTL_STATE_QUEUE,
+ PFCTL_STATE_NAT,
+ PFCTL_STATE_FILTER
+};
+
+struct node_proto {
+ u_int8_t proto;
+ struct node_proto *next;
+ struct node_proto *tail;
+};
+
+struct node_port {
+ u_int16_t port[2];
+ u_int8_t op;
+ struct node_port *next;
+ struct node_port *tail;
+};
+
+struct node_uid {
+ uid_t uid[2];
+ u_int8_t op;
+ struct node_uid *next;
+ struct node_uid *tail;
+};
+
+struct node_gid {
+ gid_t gid[2];
+ u_int8_t op;
+ struct node_gid *next;
+ struct node_gid *tail;
+};
+
+struct node_icmp {
+ u_int8_t code;
+ u_int8_t type;
+ u_int8_t proto;
+ struct node_icmp *next;
+ struct node_icmp *tail;
+};
+
+enum { PF_STATE_OPT_MAX, PF_STATE_OPT_NOSYNC, PF_STATE_OPT_SRCTRACK,
+ PF_STATE_OPT_MAX_SRC_STATES, PF_STATE_OPT_MAX_SRC_CONN,
+ PF_STATE_OPT_MAX_SRC_CONN_RATE, PF_STATE_OPT_MAX_SRC_NODES,
+ PF_STATE_OPT_OVERLOAD, PF_STATE_OPT_STATELOCK,
+ PF_STATE_OPT_TIMEOUT, PF_STATE_OPT_SLOPPY, };
+
+enum { PF_SRCTRACK_NONE, PF_SRCTRACK, PF_SRCTRACK_GLOBAL, PF_SRCTRACK_RULE };
+
+struct node_state_opt {
+ int type;
+ union {
+ u_int32_t max_states;
+ u_int32_t max_src_states;
+ u_int32_t max_src_conn;
+ struct {
+ u_int32_t limit;
+ u_int32_t seconds;
+ } max_src_conn_rate;
+ struct {
+ u_int8_t flush;
+ char tblname[PF_TABLE_NAME_SIZE];
+ } overload;
+ u_int32_t max_src_nodes;
+ u_int8_t src_track;
+ u_int32_t statelock;
+ struct {
+ int number;
+ u_int32_t seconds;
+ } timeout;
+ } data;
+ struct node_state_opt *next;
+ struct node_state_opt *tail;
+};
+
+struct peer {
+ struct node_host *host;
+ struct node_port *port;
+};
+
+struct node_queue {
+ char queue[PF_QNAME_SIZE];
+ char parent[PF_QNAME_SIZE];
+ char ifname[IFNAMSIZ];
+ int scheduler;
+ struct node_queue *next;
+ struct node_queue *tail;
+} *queues = NULL;
+
+struct node_qassign {
+ char *qname;
+ char *pqname;
+};
+
+struct filter_opts {
+ int marker;
+#define FOM_FLAGS 0x01
+#define FOM_ICMP 0x02
+#define FOM_TOS 0x04
+#define FOM_KEEP 0x08
+#define FOM_SRCTRACK 0x10
+ struct node_uid *uid;
+ struct node_gid *gid;
+ struct {
+ u_int8_t b1;
+ u_int8_t b2;
+ u_int16_t w;
+ u_int16_t w2;
+ } flags;
+ struct node_icmp *icmpspec;
+ u_int32_t tos;
+ u_int32_t prob;
+ struct {
+ int action;
+ struct node_state_opt *options;
+ } keep;
+ int fragment;
+ int allowopts;
+ char *label;
+ struct node_qassign queues;
+ char *tag;
+ char *match_tag;
+ u_int8_t match_tag_not;
+ u_int rtableid;
+ struct {
+ struct node_host *addr;
+ u_int16_t port;
+ } divert;
+} filter_opts;
+
+struct antispoof_opts {
+ char *label;
+ u_int rtableid;
+} antispoof_opts;
+
+struct scrub_opts {
+ int marker;
+#define SOM_MINTTL 0x01
+#define SOM_MAXMSS 0x02
+#define SOM_FRAGCACHE 0x04
+#define SOM_SETTOS 0x08
+ int nodf;
+ int minttl;
+ int maxmss;
+ int settos;
+ int fragcache;
+ int randomid;
+ int reassemble_tcp;
+ char *match_tag;
+ u_int8_t match_tag_not;
+ u_int rtableid;
+} scrub_opts;
+
+struct queue_opts {
+ int marker;
+#define QOM_BWSPEC 0x01
+#define QOM_SCHEDULER 0x02
+#define QOM_PRIORITY 0x04
+#define QOM_TBRSIZE 0x08
+#define QOM_QLIMIT 0x10
+ struct node_queue_bw queue_bwspec;
+ struct node_queue_opt scheduler;
+ int priority;
+ int tbrsize;
+ int qlimit;
+} queue_opts;
+
+struct table_opts {
+ int flags;
+ int init_addr;
+ struct node_tinithead init_nodes;
+} table_opts;
+
+struct pool_opts {
+ int marker;
+#define POM_TYPE 0x01
+#define POM_STICKYADDRESS 0x02
+ u_int8_t opts;
+ int type;
+ int staticport;
+ struct pf_poolhashkey *key;
+
+} pool_opts;
+
+
+struct node_hfsc_opts hfsc_opts;
+struct node_state_opt *keep_state_defaults = NULL;
+
+int disallow_table(struct node_host *, const char *);
+int disallow_urpf_failed(struct node_host *, const char *);
+int disallow_alias(struct node_host *, const char *);
+int rule_consistent(struct pf_rule *, int);
+int filter_consistent(struct pf_rule *, int);
+int nat_consistent(struct pf_rule *);
+int rdr_consistent(struct pf_rule *);
+int process_tabledef(char *, struct table_opts *);
+void expand_label_str(char *, size_t, const char *, const char *);
+void expand_label_if(const char *, char *, size_t, const char *);
+void expand_label_addr(const char *, char *, size_t, u_int8_t,
+ struct node_host *);
+void expand_label_port(const char *, char *, size_t,
+ struct node_port *);
+void expand_label_proto(const char *, char *, size_t, u_int8_t);
+void expand_label_nr(const char *, char *, size_t);
+void expand_label(char *, size_t, const char *, u_int8_t,
+ struct node_host *, struct node_port *, struct node_host *,
+ struct node_port *, u_int8_t);
+void expand_rule(struct pf_rule *, struct node_if *,
+ struct node_host *, struct node_proto *, struct node_os *,
+ struct node_host *, struct node_port *, struct node_host *,
+ struct node_port *, struct node_uid *, struct node_gid *,
+ struct node_icmp *, const char *);
+int expand_altq(struct pf_altq *, struct node_if *,
+ struct node_queue *, struct node_queue_bw bwspec,
+ struct node_queue_opt *);
+int expand_queue(struct pf_altq *, struct node_if *,
+ struct node_queue *, struct node_queue_bw,
+ struct node_queue_opt *);
+int expand_skip_interface(struct node_if *);
+
+int check_rulestate(int);
+int getservice(char *);
+int rule_label(struct pf_rule *, char *);
+int rt_tableid_max(void);
+
+void mv_rules(struct pf_ruleset *, struct pf_ruleset *);
+void decide_address_family(struct node_host *, sa_family_t *);
+void remove_invalid_hosts(struct node_host **, sa_family_t *);
+int invalid_redirect(struct node_host *, sa_family_t);
+u_int16_t parseicmpspec(char *, sa_family_t);
+
+TAILQ_HEAD(loadanchorshead, loadanchors)
+ loadanchorshead = TAILQ_HEAD_INITIALIZER(loadanchorshead);
+
+struct loadanchors {
+ TAILQ_ENTRY(loadanchors) entries;
+ char *anchorname;
+ char *filename;
+};
+
+typedef struct {
+ union {
+ int64_t number;
+ double probability;
+ int i;
+ char *string;
+ u_int rtableid;
+ struct {
+ u_int8_t b1;
+ u_int8_t b2;
+ u_int16_t w;
+ u_int16_t w2;
+ } b;
+ struct range {
+ int a;
+ int b;
+ int t;
+ } range;
+ struct node_if *interface;
+ struct node_proto *proto;
+ struct node_icmp *icmp;
+ struct node_host *host;
+ struct node_os *os;
+ struct node_port *port;
+ struct node_uid *uid;
+ struct node_gid *gid;
+ struct node_state_opt *state_opt;
+ struct peer peer;
+ struct {
+ struct peer src, dst;
+ struct node_os *src_os;
+ } fromto;
+ struct {
+ struct node_host *host;
+ u_int8_t rt;
+ u_int8_t pool_opts;
+ sa_family_t af;
+ struct pf_poolhashkey *key;
+ } route;
+ struct redirection {
+ struct node_host *host;
+ struct range rport;
+ } *redirection;
+ struct {
+ int action;
+ struct node_state_opt *options;
+ } keep_state;
+ struct {
+ u_int8_t log;
+ u_int8_t logif;
+ u_int8_t quick;
+ } logquick;
+ struct {
+ int neg;
+ char *name;
+ } tagged;
+ struct pf_poolhashkey *hashkey;
+ struct node_queue *queue;
+ struct node_queue_opt queue_options;
+ struct node_queue_bw queue_bwspec;
+ struct node_qassign qassign;
+ struct filter_opts filter_opts;
+ struct antispoof_opts antispoof_opts;
+ struct queue_opts queue_opts;
+ struct scrub_opts scrub_opts;
+ struct table_opts table_opts;
+ struct pool_opts pool_opts;
+ struct node_hfsc_opts hfsc_opts;
+ } v;
+ int lineno;
+} YYSTYPE;
+
+#define PPORT_RANGE 1
+#define PPORT_STAR 2
+int parseport(char *, struct range *r, int);
+
+#define DYNIF_MULTIADDR(addr) ((addr).type == PF_ADDR_DYNIFTL && \
+ (!((addr).iflags & PFI_AFLAG_NOALIAS) || \
+ !isdigit((addr).v.ifname[strlen((addr).v.ifname)-1])))
+
+%}
+
+%token PASS BLOCK SCRUB RETURN IN OS OUT LOG QUICK ON FROM TO FLAGS
+%token RETURNRST RETURNICMP RETURNICMP6 PROTO INET INET6 ALL ANY ICMPTYPE
+%token ICMP6TYPE CODE KEEP MODULATE STATE PORT RDR NAT BINAT ARROW NODF
+%token MINTTL ERROR ALLOWOPTS FASTROUTE FILENAME ROUTETO DUPTO REPLYTO NO LABEL
+%token NOROUTE URPFFAILED FRAGMENT USER GROUP MAXMSS MAXIMUM TTL TOS DROP TABLE
+%token REASSEMBLE FRAGDROP FRAGCROP ANCHOR NATANCHOR RDRANCHOR BINATANCHOR
+%token SET OPTIMIZATION TIMEOUT LIMIT LOGINTERFACE BLOCKPOLICY RANDOMID
+%token REQUIREORDER SYNPROXY FINGERPRINTS NOSYNC DEBUG SKIP HOSTID
+%token ANTISPOOF FOR INCLUDE
+%token BITMASK RANDOM SOURCEHASH ROUNDROBIN STATICPORT PROBABILITY
+%token ALTQ CBQ PRIQ HFSC BANDWIDTH TBRSIZE LINKSHARE REALTIME UPPERLIMIT
+%token QUEUE PRIORITY QLIMIT RTABLE
+%token LOAD RULESET_OPTIMIZATION
+%token STICKYADDRESS MAXSRCSTATES MAXSRCNODES SOURCETRACK GLOBAL RULE
+%token MAXSRCCONN MAXSRCCONNRATE OVERLOAD FLUSH SLOPPY
+%token TAGGED TAG IFBOUND FLOATING STATEPOLICY STATEDEFAULTS ROUTE SETTOS
+%token DIVERTTO DIVERTREPLY
+%token <v.string> STRING
+%token <v.number> NUMBER
+%token <v.i> PORTBINARY
+%type <v.interface> interface if_list if_item_not if_item
+%type <v.number> number icmptype icmp6type uid gid
+%type <v.number> tos not yesno
+%type <v.probability> probability
+%type <v.i> no dir af fragcache optimizer
+%type <v.i> sourcetrack flush unaryop statelock
+%type <v.b> action nataction natpasslog scrubaction
+%type <v.b> flags flag blockspec
+%type <v.range> portplain portstar portrange
+%type <v.hashkey> hashkey
+%type <v.proto> proto proto_list proto_item
+%type <v.number> protoval
+%type <v.icmp> icmpspec
+%type <v.icmp> icmp_list icmp_item
+%type <v.icmp> icmp6_list icmp6_item
+%type <v.number> reticmpspec reticmp6spec
+%type <v.fromto> fromto
+%type <v.peer> ipportspec from to
+%type <v.host> ipspec toipspec xhost host dynaddr host_list
+%type <v.host> redir_host_list redirspec
+%type <v.host> route_host route_host_list routespec
+%type <v.os> os xos os_list
+%type <v.port> portspec port_list port_item
+%type <v.uid> uids uid_list uid_item
+%type <v.gid> gids gid_list gid_item
+%type <v.route> route
+%type <v.redirection> redirection redirpool
+%type <v.string> label stringall tag anchorname
+%type <v.string> string varstring numberstring
+%type <v.keep_state> keep
+%type <v.state_opt> state_opt_spec state_opt_list state_opt_item
+%type <v.logquick> logquick quick log logopts logopt
+%type <v.interface> antispoof_ifspc antispoof_iflst antispoof_if
+%type <v.qassign> qname
+%type <v.queue> qassign qassign_list qassign_item
+%type <v.queue_options> scheduler
+%type <v.number> cbqflags_list cbqflags_item
+%type <v.number> priqflags_list priqflags_item
+%type <v.hfsc_opts> hfscopts_list hfscopts_item hfsc_opts
+%type <v.queue_bwspec> bandwidth
+%type <v.filter_opts> filter_opts filter_opt filter_opts_l
+%type <v.antispoof_opts> antispoof_opts antispoof_opt antispoof_opts_l
+%type <v.queue_opts> queue_opts queue_opt queue_opts_l
+%type <v.scrub_opts> scrub_opts scrub_opt scrub_opts_l
+%type <v.table_opts> table_opts table_opt table_opts_l
+%type <v.pool_opts> pool_opts pool_opt pool_opts_l
+%type <v.tagged> tagged
+%type <v.rtableid> rtable
+%%
+
+ruleset : /* empty */
+ | ruleset include '\n'
+ | ruleset '\n'
+ | ruleset option '\n'
+ | ruleset scrubrule '\n'
+ | ruleset natrule '\n'
+ | ruleset binatrule '\n'
+ | ruleset pfrule '\n'
+ | ruleset anchorrule '\n'
+ | ruleset loadrule '\n'
+ | ruleset altqif '\n'
+ | ruleset queuespec '\n'
+ | ruleset varset '\n'
+ | ruleset antispoof '\n'
+ | ruleset tabledef '\n'
+ | '{' fakeanchor '}' '\n';
+ | ruleset error '\n' { file->errors++; }
+ ;
+
+include : INCLUDE STRING {
+ struct file *nfile;
+
+ if ((nfile = pushfile($2, 0)) == NULL) {
+ yyerror("failed to include file %s", $2);
+ free($2);
+ YYERROR;
+ }
+ free($2);
+
+ file = nfile;
+ lungetc('\n');
+ }
+ ;
+
+/*
+ * apply to previouslys specified rule: must be careful to note
+ * what that is: pf or nat or binat or rdr
+ */
+fakeanchor : fakeanchor '\n'
+ | fakeanchor anchorrule '\n'
+ | fakeanchor binatrule '\n'
+ | fakeanchor natrule '\n'
+ | fakeanchor pfrule '\n'
+ | fakeanchor error '\n'
+ ;
+
+optimizer : string {
+ if (!strcmp($1, "none"))
+ $$ = 0;
+ else if (!strcmp($1, "basic"))
+ $$ = PF_OPTIMIZE_BASIC;
+ else if (!strcmp($1, "profile"))
+ $$ = PF_OPTIMIZE_BASIC | PF_OPTIMIZE_PROFILE;
+ else {
+ yyerror("unknown ruleset-optimization %s", $1);
+ YYERROR;
+ }
+ }
+ ;
+
+option : SET OPTIMIZATION STRING {
+ if (check_rulestate(PFCTL_STATE_OPTION)) {
+ free($3);
+ YYERROR;
+ }
+ if (pfctl_set_optimization(pf, $3) != 0) {
+ yyerror("unknown optimization %s", $3);
+ free($3);
+ YYERROR;
+ }
+ free($3);
+ }
+ | SET RULESET_OPTIMIZATION optimizer {
+ if (!(pf->opts & PF_OPT_OPTIMIZE)) {
+ pf->opts |= PF_OPT_OPTIMIZE;
+ pf->optimize = $3;
+ }
+ }
+ | SET TIMEOUT timeout_spec
+ | SET TIMEOUT '{' optnl timeout_list '}'
+ | SET LIMIT limit_spec
+ | SET LIMIT '{' optnl limit_list '}'
+ | SET LOGINTERFACE stringall {
+ if (check_rulestate(PFCTL_STATE_OPTION)) {
+ free($3);
+ YYERROR;
+ }
+ if (pfctl_set_logif(pf, $3) != 0) {
+ yyerror("error setting loginterface %s", $3);
+ free($3);
+ YYERROR;
+ }
+ free($3);
+ }
+ | SET HOSTID number {
+ if ($3 == 0 || $3 > UINT_MAX) {
+ yyerror("hostid must be non-zero");
+ YYERROR;
+ }
+ if (pfctl_set_hostid(pf, $3) != 0) {
+ yyerror("error setting hostid %08x", $3);
+ YYERROR;
+ }
+ }
+ | SET BLOCKPOLICY DROP {
+ if (pf->opts & PF_OPT_VERBOSE)
+ printf("set block-policy drop\n");
+ if (check_rulestate(PFCTL_STATE_OPTION))
+ YYERROR;
+ blockpolicy = PFRULE_DROP;
+ }
+ | SET BLOCKPOLICY RETURN {
+ if (pf->opts & PF_OPT_VERBOSE)
+ printf("set block-policy return\n");
+ if (check_rulestate(PFCTL_STATE_OPTION))
+ YYERROR;
+ blockpolicy = PFRULE_RETURN;
+ }
+ | SET REQUIREORDER yesno {
+ if (pf->opts & PF_OPT_VERBOSE)
+ printf("set require-order %s\n",
+ $3 == 1 ? "yes" : "no");
+ require_order = $3;
+ }
+ | SET FINGERPRINTS STRING {
+ if (pf->opts & PF_OPT_VERBOSE)
+ printf("set fingerprints \"%s\"\n", $3);
+ if (check_rulestate(PFCTL_STATE_OPTION)) {
+ free($3);
+ YYERROR;
+ }
+ if (!pf->anchor->name[0]) {
+ if (pfctl_file_fingerprints(pf->dev,
+ pf->opts, $3)) {
+ yyerror("error loading "
+ "fingerprints %s", $3);
+ free($3);
+ YYERROR;
+ }
+ }
+ free($3);
+ }
+ | SET STATEPOLICY statelock {
+ if (pf->opts & PF_OPT_VERBOSE)
+ switch ($3) {
+ case 0:
+ printf("set state-policy floating\n");
+ break;
+ case PFRULE_IFBOUND:
+ printf("set state-policy if-bound\n");
+ break;
+ }
+ default_statelock = $3;
+ }
+ | SET DEBUG STRING {
+ if (check_rulestate(PFCTL_STATE_OPTION)) {
+ free($3);
+ YYERROR;
+ }
+ if (pfctl_set_debug(pf, $3) != 0) {
+ yyerror("error setting debuglevel %s", $3);
+ free($3);
+ YYERROR;
+ }
+ free($3);
+ }
+ | SET SKIP interface {
+ if (expand_skip_interface($3) != 0) {
+ yyerror("error setting skip interface(s)");
+ YYERROR;
+ }
+ }
+ | SET STATEDEFAULTS state_opt_list {
+ if (keep_state_defaults != NULL) {
+ yyerror("cannot redefine state-defaults");
+ YYERROR;
+ }
+ keep_state_defaults = $3;
+ }
+ ;
+
+stringall : STRING { $$ = $1; }
+ | ALL {
+ if (($$ = strdup("all")) == NULL) {
+ err(1, "stringall: strdup");
+ }
+ }
+ ;
+
+string : STRING string {
+ if (asprintf(&$$, "%s %s", $1, $2) == -1)
+ err(1, "string: asprintf");
+ free($1);
+ free($2);
+ }
+ | STRING
+ ;
+
+varstring : numberstring varstring {
+ if (asprintf(&$$, "%s %s", $1, $2) == -1)
+ err(1, "string: asprintf");
+ free($1);
+ free($2);
+ }
+ | numberstring
+ ;
+
+numberstring : NUMBER {
+ char *s;
+ if (asprintf(&s, "%lld", (long long)$1) == -1) {
+ yyerror("string: asprintf");
+ YYERROR;
+ }
+ $$ = s;
+ }
+ | STRING
+ ;
+
+varset : STRING '=' varstring {
+ if (pf->opts & PF_OPT_VERBOSE)
+ printf("%s = \"%s\"\n", $1, $3);
+ if (symset($1, $3, 0) == -1)
+ err(1, "cannot store variable %s", $1);
+ free($1);
+ free($3);
+ }
+ ;
+
+anchorname : STRING { $$ = $1; }
+ | /* empty */ { $$ = NULL; }
+ ;
+
+pfa_anchorlist : /* empty */
+ | pfa_anchorlist '\n'
+ | pfa_anchorlist pfrule '\n'
+ | pfa_anchorlist anchorrule '\n'
+ ;
+
+pfa_anchor : '{'
+ {
+ char ta[PF_ANCHOR_NAME_SIZE];
+ struct pf_ruleset *rs;
+
+ /* steping into a brace anchor */
+ pf->asd++;
+ pf->bn++;
+ pf->brace = 1;
+
+ /* create a holding ruleset in the root */
+ snprintf(ta, PF_ANCHOR_NAME_SIZE, "_%d", pf->bn);
+ rs = pf_find_or_create_ruleset(ta);
+ if (rs == NULL)
+ err(1, "pfa_anchor: pf_find_or_create_ruleset");
+ pf->astack[pf->asd] = rs->anchor;
+ pf->anchor = rs->anchor;
+ } '\n' pfa_anchorlist '}'
+ {
+ pf->alast = pf->anchor;
+ pf->asd--;
+ pf->anchor = pf->astack[pf->asd];
+ }
+ | /* empty */
+ ;
+
+anchorrule : ANCHOR anchorname dir quick interface af proto fromto
+ filter_opts pfa_anchor
+ {
+ struct pf_rule r;
+ struct node_proto *proto;
+
+ if (check_rulestate(PFCTL_STATE_FILTER)) {
+ if ($2)
+ free($2);
+ YYERROR;
+ }
+
+ if ($2 && ($2[0] == '_' || strstr($2, "/_") != NULL)) {
+ free($2);
+ yyerror("anchor names beginning with '_' "
+ "are reserved for internal use");
+ YYERROR;
+ }
+
+ memset(&r, 0, sizeof(r));
+ if (pf->astack[pf->asd + 1]) {
+ /* move inline rules into relative location */
+ pf_anchor_setup(&r,
+ &pf->astack[pf->asd]->ruleset,
+ $2 ? $2 : pf->alast->name);
+
+ if (r.anchor == NULL)
+ err(1, "anchorrule: unable to "
+ "create ruleset");
+
+ if (pf->alast != r.anchor) {
+ if (r.anchor->match) {
+ yyerror("inline anchor '%s' "
+ "already exists",
+ r.anchor->name);
+ YYERROR;
+ }
+ mv_rules(&pf->alast->ruleset,
+ &r.anchor->ruleset);
+ }
+ pf_remove_if_empty_ruleset(&pf->alast->ruleset);
+ pf->alast = r.anchor;
+ } else {
+ if (!$2) {
+ yyerror("anchors without explicit "
+ "rules must specify a name");
+ YYERROR;
+ }
+ }
+ r.direction = $3;
+ r.quick = $4.quick;
+ r.af = $6;
+ r.prob = $9.prob;
+ r.rtableid = $9.rtableid;
+
+ if ($9.tag)
+ if (strlcpy(r.tagname, $9.tag,
+ PF_TAG_NAME_SIZE) >= PF_TAG_NAME_SIZE) {
+ yyerror("tag too long, max %u chars",
+ PF_TAG_NAME_SIZE - 1);
+ YYERROR;
+ }
+ if ($9.match_tag)
+ if (strlcpy(r.match_tagname, $9.match_tag,
+ PF_TAG_NAME_SIZE) >= PF_TAG_NAME_SIZE) {
+ yyerror("tag too long, max %u chars",
+ PF_TAG_NAME_SIZE - 1);
+ YYERROR;
+ }
+ r.match_tag_not = $9.match_tag_not;
+ if (rule_label(&r, $9.label))
+ YYERROR;
+ free($9.label);
+ r.flags = $9.flags.b1;
+ r.flagset = $9.flags.b2;
+ if (($9.flags.b1 & $9.flags.b2) != $9.flags.b1) {
+ yyerror("flags always false");
+ YYERROR;
+ }
+ if ($9.flags.b1 || $9.flags.b2 || $8.src_os) {
+ for (proto = $7; proto != NULL &&
+ proto->proto != IPPROTO_TCP;
+ proto = proto->next)
+ ; /* nothing */
+ if (proto == NULL && $7 != NULL) {
+ if ($9.flags.b1 || $9.flags.b2)
+ yyerror(
+ "flags only apply to tcp");
+ if ($8.src_os)
+ yyerror(
+ "OS fingerprinting only "
+ "applies to tcp");
+ YYERROR;
+ }
+ }
+
+ r.tos = $9.tos;
+
+ if ($9.keep.action) {
+ yyerror("cannot specify state handling "
+ "on anchors");
+ YYERROR;
+ }
+
+ if ($9.match_tag)
+ if (strlcpy(r.match_tagname, $9.match_tag,
+ PF_TAG_NAME_SIZE) >= PF_TAG_NAME_SIZE) {
+ yyerror("tag too long, max %u chars",
+ PF_TAG_NAME_SIZE - 1);
+ YYERROR;
+ }
+ r.match_tag_not = $9.match_tag_not;
+
+ decide_address_family($8.src.host, &r.af);
+ decide_address_family($8.dst.host, &r.af);
+
+ expand_rule(&r, $5, NULL, $7, $8.src_os,
+ $8.src.host, $8.src.port, $8.dst.host, $8.dst.port,
+ $9.uid, $9.gid, $9.icmpspec,
+ pf->astack[pf->asd + 1] ? pf->alast->name : $2);
+ free($2);
+ pf->astack[pf->asd + 1] = NULL;
+ }
+ | NATANCHOR string interface af proto fromto rtable {
+ struct pf_rule r;
+
+ if (check_rulestate(PFCTL_STATE_NAT)) {
+ free($2);
+ YYERROR;
+ }
+
+ memset(&r, 0, sizeof(r));
+ r.action = PF_NAT;
+ r.af = $4;
+ r.rtableid = $7;
+
+ decide_address_family($6.src.host, &r.af);
+ decide_address_family($6.dst.host, &r.af);
+
+ expand_rule(&r, $3, NULL, $5, $6.src_os,
+ $6.src.host, $6.src.port, $6.dst.host, $6.dst.port,
+ 0, 0, 0, $2);
+ free($2);
+ }
+ | RDRANCHOR string interface af proto fromto rtable {
+ struct pf_rule r;
+
+ if (check_rulestate(PFCTL_STATE_NAT)) {
+ free($2);
+ YYERROR;
+ }
+
+ memset(&r, 0, sizeof(r));
+ r.action = PF_RDR;
+ r.af = $4;
+ r.rtableid = $7;
+
+ decide_address_family($6.src.host, &r.af);
+ decide_address_family($6.dst.host, &r.af);
+
+ if ($6.src.port != NULL) {
+ yyerror("source port parameter not supported"
+ " in rdr-anchor");
+ YYERROR;
+ }
+ if ($6.dst.port != NULL) {
+ if ($6.dst.port->next != NULL) {
+ yyerror("destination port list "
+ "expansion not supported in "
+ "rdr-anchor");
+ YYERROR;
+ } else if ($6.dst.port->op != PF_OP_EQ) {
+ yyerror("destination port operators"
+ " not supported in rdr-anchor");
+ YYERROR;
+ }
+ r.dst.port[0] = $6.dst.port->port[0];
+ r.dst.port[1] = $6.dst.port->port[1];
+ r.dst.port_op = $6.dst.port->op;
+ }
+
+ expand_rule(&r, $3, NULL, $5, $6.src_os,
+ $6.src.host, $6.src.port, $6.dst.host, $6.dst.port,
+ 0, 0, 0, $2);
+ free($2);
+ }
+ | BINATANCHOR string interface af proto fromto rtable {
+ struct pf_rule r;
+
+ if (check_rulestate(PFCTL_STATE_NAT)) {
+ free($2);
+ YYERROR;
+ }
+
+ memset(&r, 0, sizeof(r));
+ r.action = PF_BINAT;
+ r.af = $4;
+ r.rtableid = $7;
+ if ($5 != NULL) {
+ if ($5->next != NULL) {
+ yyerror("proto list expansion"
+ " not supported in binat-anchor");
+ YYERROR;
+ }
+ r.proto = $5->proto;
+ free($5);
+ }
+
+ if ($6.src.host != NULL || $6.src.port != NULL ||
+ $6.dst.host != NULL || $6.dst.port != NULL) {
+ yyerror("fromto parameter not supported"
+ " in binat-anchor");
+ YYERROR;
+ }
+
+ decide_address_family($6.src.host, &r.af);
+ decide_address_family($6.dst.host, &r.af);
+
+ pfctl_add_rule(pf, &r, $2);
+ free($2);
+ }
+ ;
+
+loadrule : LOAD ANCHOR string FROM string {
+ struct loadanchors *loadanchor;
+
+ if (strlen(pf->anchor->name) + 1 +
+ strlen($3) >= MAXPATHLEN) {
+ yyerror("anchorname %s too long, max %u\n",
+ $3, MAXPATHLEN - 1);
+ free($3);
+ YYERROR;
+ }
+ loadanchor = calloc(1, sizeof(struct loadanchors));
+ if (loadanchor == NULL)
+ err(1, "loadrule: calloc");
+ if ((loadanchor->anchorname = malloc(MAXPATHLEN)) ==
+ NULL)
+ err(1, "loadrule: malloc");
+ if (pf->anchor->name[0])
+ snprintf(loadanchor->anchorname, MAXPATHLEN,
+ "%s/%s", pf->anchor->name, $3);
+ else
+ strlcpy(loadanchor->anchorname, $3, MAXPATHLEN);
+ if ((loadanchor->filename = strdup($5)) == NULL)
+ err(1, "loadrule: strdup");
+
+ TAILQ_INSERT_TAIL(&loadanchorshead, loadanchor,
+ entries);
+
+ free($3);
+ free($5);
+ };
+
+scrubaction : no SCRUB {
+ $$.b2 = $$.w = 0;
+ if ($1)
+ $$.b1 = PF_NOSCRUB;
+ else
+ $$.b1 = PF_SCRUB;
+ }
+ ;
+
+scrubrule : scrubaction dir logquick interface af proto fromto scrub_opts
+ {
+ struct pf_rule r;
+
+ if (check_rulestate(PFCTL_STATE_SCRUB))
+ YYERROR;
+
+ memset(&r, 0, sizeof(r));
+
+ r.action = $1.b1;
+ r.direction = $2;
+
+ r.log = $3.log;
+ r.logif = $3.logif;
+ if ($3.quick) {
+ yyerror("scrub rules do not support 'quick'");
+ YYERROR;
+ }
+
+ r.af = $5;
+ if ($8.nodf)
+ r.rule_flag |= PFRULE_NODF;
+ if ($8.randomid)
+ r.rule_flag |= PFRULE_RANDOMID;
+ if ($8.reassemble_tcp) {
+ if (r.direction != PF_INOUT) {
+ yyerror("reassemble tcp rules can not "
+ "specify direction");
+ YYERROR;
+ }
+ r.rule_flag |= PFRULE_REASSEMBLE_TCP;
+ }
+ if ($8.minttl)
+ r.min_ttl = $8.minttl;
+ if ($8.maxmss)
+ r.max_mss = $8.maxmss;
+ if ($8.marker & SOM_SETTOS) {
+ r.rule_flag |= PFRULE_SET_TOS;
+ r.set_tos = $8.settos;
+ }
+ if ($8.fragcache)
+ r.rule_flag |= $8.fragcache;
+ if ($8.match_tag)
+ if (strlcpy(r.match_tagname, $8.match_tag,
+ PF_TAG_NAME_SIZE) >= PF_TAG_NAME_SIZE) {
+ yyerror("tag too long, max %u chars",
+ PF_TAG_NAME_SIZE - 1);
+ YYERROR;
+ }
+ r.match_tag_not = $8.match_tag_not;
+ r.rtableid = $8.rtableid;
+
+ expand_rule(&r, $4, NULL, $6, $7.src_os,
+ $7.src.host, $7.src.port, $7.dst.host, $7.dst.port,
+ NULL, NULL, NULL, "");
+ }
+ ;
+
+scrub_opts : {
+ bzero(&scrub_opts, sizeof scrub_opts);
+ scrub_opts.rtableid = -1;
+ }
+ scrub_opts_l
+ { $$ = scrub_opts; }
+ | /* empty */ {
+ bzero(&scrub_opts, sizeof scrub_opts);
+ scrub_opts.rtableid = -1;
+ $$ = scrub_opts;
+ }
+ ;
+
+scrub_opts_l : scrub_opts_l scrub_opt
+ | scrub_opt
+ ;
+
+scrub_opt : NODF {
+ if (scrub_opts.nodf) {
+ yyerror("no-df cannot be respecified");
+ YYERROR;
+ }
+ scrub_opts.nodf = 1;
+ }
+ | MINTTL NUMBER {
+ if (scrub_opts.marker & SOM_MINTTL) {
+ yyerror("min-ttl cannot be respecified");
+ YYERROR;
+ }
+ if ($2 < 0 || $2 > 255) {
+ yyerror("illegal min-ttl value %d", $2);
+ YYERROR;
+ }
+ scrub_opts.marker |= SOM_MINTTL;
+ scrub_opts.minttl = $2;
+ }
+ | MAXMSS NUMBER {
+ if (scrub_opts.marker & SOM_MAXMSS) {
+ yyerror("max-mss cannot be respecified");
+ YYERROR;
+ }
+ if ($2 < 0 || $2 > 65535) {
+ yyerror("illegal max-mss value %d", $2);
+ YYERROR;
+ }
+ scrub_opts.marker |= SOM_MAXMSS;
+ scrub_opts.maxmss = $2;
+ }
+ | SETTOS tos {
+ if (scrub_opts.marker & SOM_SETTOS) {
+ yyerror("set-tos cannot be respecified");
+ YYERROR;
+ }
+ scrub_opts.marker |= SOM_SETTOS;
+ scrub_opts.settos = $2;
+ }
+ | fragcache {
+ if (scrub_opts.marker & SOM_FRAGCACHE) {
+ yyerror("fragcache cannot be respecified");
+ YYERROR;
+ }
+ scrub_opts.marker |= SOM_FRAGCACHE;
+ scrub_opts.fragcache = $1;
+ }
+ | REASSEMBLE STRING {
+ if (strcasecmp($2, "tcp") != 0) {
+ yyerror("scrub reassemble supports only tcp, "
+ "not '%s'", $2);
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ if (scrub_opts.reassemble_tcp) {
+ yyerror("reassemble tcp cannot be respecified");
+ YYERROR;
+ }
+ scrub_opts.reassemble_tcp = 1;
+ }
+ | RANDOMID {
+ if (scrub_opts.randomid) {
+ yyerror("random-id cannot be respecified");
+ YYERROR;
+ }
+ scrub_opts.randomid = 1;
+ }
+ | RTABLE NUMBER {
+ if ($2 < 0 || $2 > rt_tableid_max()) {
+ yyerror("invalid rtable id");
+ YYERROR;
+ }
+ scrub_opts.rtableid = $2;
+ }
+ | not TAGGED string {
+ scrub_opts.match_tag = $3;
+ scrub_opts.match_tag_not = $1;
+ }
+ ;
+
+fragcache : FRAGMENT REASSEMBLE { $$ = 0; /* default */ }
+ | FRAGMENT FRAGCROP { $$ = PFRULE_FRAGCROP; }
+ | FRAGMENT FRAGDROP { $$ = PFRULE_FRAGDROP; }
+ ;
+
+antispoof : ANTISPOOF logquick antispoof_ifspc af antispoof_opts {
+ struct pf_rule r;
+ struct node_host *h = NULL, *hh;
+ struct node_if *i, *j;
+
+ if (check_rulestate(PFCTL_STATE_FILTER))
+ YYERROR;
+
+ for (i = $3; i; i = i->next) {
+ bzero(&r, sizeof(r));
+
+ r.action = PF_DROP;
+ r.direction = PF_IN;
+ r.log = $2.log;
+ r.logif = $2.logif;
+ r.quick = $2.quick;
+ r.af = $4;
+ if (rule_label(&r, $5.label))
+ YYERROR;
+ r.rtableid = $5.rtableid;
+ j = calloc(1, sizeof(struct node_if));
+ if (j == NULL)
+ err(1, "antispoof: calloc");
+ if (strlcpy(j->ifname, i->ifname,
+ sizeof(j->ifname)) >= sizeof(j->ifname)) {
+ free(j);
+ yyerror("interface name too long");
+ YYERROR;
+ }
+ j->not = 1;
+ if (i->dynamic) {
+ h = calloc(1, sizeof(*h));
+ if (h == NULL)
+ err(1, "address: calloc");
+ h->addr.type = PF_ADDR_DYNIFTL;
+ set_ipmask(h, 128);
+ if (strlcpy(h->addr.v.ifname, i->ifname,
+ sizeof(h->addr.v.ifname)) >=
+ sizeof(h->addr.v.ifname)) {
+ free(h);
+ yyerror(
+ "interface name too long");
+ YYERROR;
+ }
+ hh = malloc(sizeof(*hh));
+ if (hh == NULL)
+ err(1, "address: malloc");
+ bcopy(h, hh, sizeof(*hh));
+ h->addr.iflags = PFI_AFLAG_NETWORK;
+ } else {
+ h = ifa_lookup(j->ifname,
+ PFI_AFLAG_NETWORK);
+ hh = NULL;
+ }
+
+ if (h != NULL)
+ expand_rule(&r, j, NULL, NULL, NULL, h,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, "");
+
+ if ((i->ifa_flags & IFF_LOOPBACK) == 0) {
+ bzero(&r, sizeof(r));
+
+ r.action = PF_DROP;
+ r.direction = PF_IN;
+ r.log = $2.log;
+ r.logif = $2.logif;
+ r.quick = $2.quick;
+ r.af = $4;
+ if (rule_label(&r, $5.label))
+ YYERROR;
+ r.rtableid = $5.rtableid;
+ if (hh != NULL)
+ h = hh;
+ else
+ h = ifa_lookup(i->ifname, 0);
+ if (h != NULL)
+ expand_rule(&r, NULL, NULL,
+ NULL, NULL, h, NULL, NULL,
+ NULL, NULL, NULL, NULL, "");
+ } else
+ free(hh);
+ }
+ free($5.label);
+ }
+ ;
+
+antispoof_ifspc : FOR antispoof_if { $$ = $2; }
+ | FOR '{' optnl antispoof_iflst '}' { $$ = $4; }
+ ;
+
+antispoof_iflst : antispoof_if optnl { $$ = $1; }
+ | antispoof_iflst comma antispoof_if optnl {
+ $1->tail->next = $3;
+ $1->tail = $3;
+ $$ = $1;
+ }
+ ;
+
+antispoof_if : if_item { $$ = $1; }
+ | '(' if_item ')' {
+ $2->dynamic = 1;
+ $$ = $2;
+ }
+ ;
+
+antispoof_opts : {
+ bzero(&antispoof_opts, sizeof antispoof_opts);
+ antispoof_opts.rtableid = -1;
+ }
+ antispoof_opts_l
+ { $$ = antispoof_opts; }
+ | /* empty */ {
+ bzero(&antispoof_opts, sizeof antispoof_opts);
+ antispoof_opts.rtableid = -1;
+ $$ = antispoof_opts;
+ }
+ ;
+
+antispoof_opts_l : antispoof_opts_l antispoof_opt
+ | antispoof_opt
+ ;
+
+antispoof_opt : label {
+ if (antispoof_opts.label) {
+ yyerror("label cannot be redefined");
+ YYERROR;
+ }
+ antispoof_opts.label = $1;
+ }
+ | RTABLE NUMBER {
+ if ($2 < 0 || $2 > rt_tableid_max()) {
+ yyerror("invalid rtable id");
+ YYERROR;
+ }
+ antispoof_opts.rtableid = $2;
+ }
+ ;
+
+not : '!' { $$ = 1; }
+ | /* empty */ { $$ = 0; }
+ ;
+
+tabledef : TABLE '<' STRING '>' table_opts {
+ struct node_host *h, *nh;
+ struct node_tinit *ti, *nti;
+
+ if (strlen($3) >= PF_TABLE_NAME_SIZE) {
+ yyerror("table name too long, max %d chars",
+ PF_TABLE_NAME_SIZE - 1);
+ free($3);
+ YYERROR;
+ }
+ if (pf->loadopt & PFCTL_FLAG_TABLE)
+ if (process_tabledef($3, &$5)) {
+ free($3);
+ YYERROR;
+ }
+ free($3);
+ for (ti = SIMPLEQ_FIRST(&$5.init_nodes);
+ ti != SIMPLEQ_END(&$5.init_nodes); ti = nti) {
+ if (ti->file)
+ free(ti->file);
+ for (h = ti->host; h != NULL; h = nh) {
+ nh = h->next;
+ free(h);
+ }
+ nti = SIMPLEQ_NEXT(ti, entries);
+ free(ti);
+ }
+ }
+ ;
+
+table_opts : {
+ bzero(&table_opts, sizeof table_opts);
+ SIMPLEQ_INIT(&table_opts.init_nodes);
+ }
+ table_opts_l
+ { $$ = table_opts; }
+ | /* empty */
+ {
+ bzero(&table_opts, sizeof table_opts);
+ SIMPLEQ_INIT(&table_opts.init_nodes);
+ $$ = table_opts;
+ }
+ ;
+
+table_opts_l : table_opts_l table_opt
+ | table_opt
+ ;
+
+table_opt : STRING {
+ if (!strcmp($1, "const"))
+ table_opts.flags |= PFR_TFLAG_CONST;
+ else if (!strcmp($1, "persist"))
+ table_opts.flags |= PFR_TFLAG_PERSIST;
+ else if (!strcmp($1, "counters"))
+ table_opts.flags |= PFR_TFLAG_COUNTERS;
+ else {
+ yyerror("invalid table option '%s'", $1);
+ free($1);
+ YYERROR;
+ }
+ free($1);
+ }
+ | '{' optnl '}' { table_opts.init_addr = 1; }
+ | '{' optnl host_list '}' {
+ struct node_host *n;
+ struct node_tinit *ti;
+
+ for (n = $3; n != NULL; n = n->next) {
+ switch (n->addr.type) {
+ case PF_ADDR_ADDRMASK:
+ continue; /* ok */
+ case PF_ADDR_RANGE:
+ yyerror("address ranges are not "
+ "permitted inside tables");
+ break;
+ case PF_ADDR_DYNIFTL:
+ yyerror("dynamic addresses are not "
+ "permitted inside tables");
+ break;
+ case PF_ADDR_TABLE:
+ yyerror("tables cannot contain tables");
+ break;
+ case PF_ADDR_NOROUTE:
+ yyerror("\"no-route\" is not permitted "
+ "inside tables");
+ break;
+ case PF_ADDR_URPFFAILED:
+ yyerror("\"urpf-failed\" is not "
+ "permitted inside tables");
+ break;
+ default:
+ yyerror("unknown address type %d",
+ n->addr.type);
+ }
+ YYERROR;
+ }
+ if (!(ti = calloc(1, sizeof(*ti))))
+ err(1, "table_opt: calloc");
+ ti->host = $3;
+ SIMPLEQ_INSERT_TAIL(&table_opts.init_nodes, ti,
+ entries);
+ table_opts.init_addr = 1;
+ }
+ | FILENAME STRING {
+ struct node_tinit *ti;
+
+ if (!(ti = calloc(1, sizeof(*ti))))
+ err(1, "table_opt: calloc");
+ ti->file = $2;
+ SIMPLEQ_INSERT_TAIL(&table_opts.init_nodes, ti,
+ entries);
+ table_opts.init_addr = 1;
+ }
+ ;
+
+altqif : ALTQ interface queue_opts QUEUE qassign {
+ struct pf_altq a;
+
+ if (check_rulestate(PFCTL_STATE_QUEUE))
+ YYERROR;
+
+ memset(&a, 0, sizeof(a));
+ if ($3.scheduler.qtype == ALTQT_NONE) {
+ yyerror("no scheduler specified!");
+ YYERROR;
+ }
+ a.scheduler = $3.scheduler.qtype;
+ a.qlimit = $3.qlimit;
+ a.tbrsize = $3.tbrsize;
+ if ($5 == NULL) {
+ yyerror("no child queues specified");
+ YYERROR;
+ }
+ if (expand_altq(&a, $2, $5, $3.queue_bwspec,
+ &$3.scheduler))
+ YYERROR;
+ }
+ ;
+
+queuespec : QUEUE STRING interface queue_opts qassign {
+ struct pf_altq a;
+
+ if (check_rulestate(PFCTL_STATE_QUEUE)) {
+ free($2);
+ YYERROR;
+ }
+
+ memset(&a, 0, sizeof(a));
+
+ if (strlcpy(a.qname, $2, sizeof(a.qname)) >=
+ sizeof(a.qname)) {
+ yyerror("queue name too long (max "
+ "%d chars)", PF_QNAME_SIZE-1);
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ if ($4.tbrsize) {
+ yyerror("cannot specify tbrsize for queue");
+ YYERROR;
+ }
+ if ($4.priority > 255) {
+ yyerror("priority out of range: max 255");
+ YYERROR;
+ }
+ a.priority = $4.priority;
+ a.qlimit = $4.qlimit;
+ a.scheduler = $4.scheduler.qtype;
+ if (expand_queue(&a, $3, $5, $4.queue_bwspec,
+ &$4.scheduler)) {
+ yyerror("errors in queue definition");
+ YYERROR;
+ }
+ }
+ ;
+
+queue_opts : {
+ bzero(&queue_opts, sizeof queue_opts);
+ queue_opts.priority = DEFAULT_PRIORITY;
+ queue_opts.qlimit = DEFAULT_QLIMIT;
+ queue_opts.scheduler.qtype = ALTQT_NONE;
+ queue_opts.queue_bwspec.bw_percent = 100;
+ }
+ queue_opts_l
+ { $$ = queue_opts; }
+ | /* empty */ {
+ bzero(&queue_opts, sizeof queue_opts);
+ queue_opts.priority = DEFAULT_PRIORITY;
+ queue_opts.qlimit = DEFAULT_QLIMIT;
+ queue_opts.scheduler.qtype = ALTQT_NONE;
+ queue_opts.queue_bwspec.bw_percent = 100;
+ $$ = queue_opts;
+ }
+ ;
+
+queue_opts_l : queue_opts_l queue_opt
+ | queue_opt
+ ;
+
+queue_opt : BANDWIDTH bandwidth {
+ if (queue_opts.marker & QOM_BWSPEC) {
+ yyerror("bandwidth cannot be respecified");
+ YYERROR;
+ }
+ queue_opts.marker |= QOM_BWSPEC;
+ queue_opts.queue_bwspec = $2;
+ }
+ | PRIORITY NUMBER {
+ if (queue_opts.marker & QOM_PRIORITY) {
+ yyerror("priority cannot be respecified");
+ YYERROR;
+ }
+ if ($2 < 0 || $2 > 255) {
+ yyerror("priority out of range: max 255");
+ YYERROR;
+ }
+ queue_opts.marker |= QOM_PRIORITY;
+ queue_opts.priority = $2;
+ }
+ | QLIMIT NUMBER {
+ if (queue_opts.marker & QOM_QLIMIT) {
+ yyerror("qlimit cannot be respecified");
+ YYERROR;
+ }
+ if ($2 < 0 || $2 > 65535) {
+ yyerror("qlimit out of range: max 65535");
+ YYERROR;
+ }
+ queue_opts.marker |= QOM_QLIMIT;
+ queue_opts.qlimit = $2;
+ }
+ | scheduler {
+ if (queue_opts.marker & QOM_SCHEDULER) {
+ yyerror("scheduler cannot be respecified");
+ YYERROR;
+ }
+ queue_opts.marker |= QOM_SCHEDULER;
+ queue_opts.scheduler = $1;
+ }
+ | TBRSIZE NUMBER {
+ if (queue_opts.marker & QOM_TBRSIZE) {
+ yyerror("tbrsize cannot be respecified");
+ YYERROR;
+ }
+ if ($2 < 0 || $2 > 65535) {
+ yyerror("tbrsize too big: max 65535");
+ YYERROR;
+ }
+ queue_opts.marker |= QOM_TBRSIZE;
+ queue_opts.tbrsize = $2;
+ }
+ ;
+
+bandwidth : STRING {
+ double bps;
+ char *cp;
+
+ $$.bw_percent = 0;
+
+ bps = strtod($1, &cp);
+ if (cp != NULL) {
+ if (!strcmp(cp, "b"))
+ ; /* nothing */
+ else if (!strcmp(cp, "Kb"))
+ bps *= 1000;
+ else if (!strcmp(cp, "Mb"))
+ bps *= 1000 * 1000;
+ else if (!strcmp(cp, "Gb"))
+ bps *= 1000 * 1000 * 1000;
+ else if (!strcmp(cp, "%")) {
+ if (bps < 0 || bps > 100) {
+ yyerror("bandwidth spec "
+ "out of range");
+ free($1);
+ YYERROR;
+ }
+ $$.bw_percent = bps;
+ bps = 0;
+ } else {
+ yyerror("unknown unit %s", cp);
+ free($1);
+ YYERROR;
+ }
+ }
+ free($1);
+ $$.bw_absolute = (u_int32_t)bps;
+ }
+ | NUMBER {
+ if ($1 < 0 || $1 > UINT_MAX) {
+ yyerror("bandwidth number too big");
+ YYERROR;
+ }
+ $$.bw_percent = 0;
+ $$.bw_absolute = $1;
+ }
+ ;
+
+scheduler : CBQ {
+ $$.qtype = ALTQT_CBQ;
+ $$.data.cbq_opts.flags = 0;
+ }
+ | CBQ '(' cbqflags_list ')' {
+ $$.qtype = ALTQT_CBQ;
+ $$.data.cbq_opts.flags = $3;
+ }
+ | PRIQ {
+ $$.qtype = ALTQT_PRIQ;
+ $$.data.priq_opts.flags = 0;
+ }
+ | PRIQ '(' priqflags_list ')' {
+ $$.qtype = ALTQT_PRIQ;
+ $$.data.priq_opts.flags = $3;
+ }
+ | HFSC {
+ $$.qtype = ALTQT_HFSC;
+ bzero(&$$.data.hfsc_opts,
+ sizeof(struct node_hfsc_opts));
+ }
+ | HFSC '(' hfsc_opts ')' {
+ $$.qtype = ALTQT_HFSC;
+ $$.data.hfsc_opts = $3;
+ }
+ ;
+
+cbqflags_list : cbqflags_item { $$ |= $1; }
+ | cbqflags_list comma cbqflags_item { $$ |= $3; }
+ ;
+
+cbqflags_item : STRING {
+ if (!strcmp($1, "default"))
+ $$ = CBQCLF_DEFCLASS;
+ else if (!strcmp($1, "borrow"))
+ $$ = CBQCLF_BORROW;
+ else if (!strcmp($1, "red"))
+ $$ = CBQCLF_RED;
+ else if (!strcmp($1, "ecn"))
+ $$ = CBQCLF_RED|CBQCLF_ECN;
+ else if (!strcmp($1, "rio"))
+ $$ = CBQCLF_RIO;
+ else {
+ yyerror("unknown cbq flag \"%s\"", $1);
+ free($1);
+ YYERROR;
+ }
+ free($1);
+ }
+ ;
+
+priqflags_list : priqflags_item { $$ |= $1; }
+ | priqflags_list comma priqflags_item { $$ |= $3; }
+ ;
+
+priqflags_item : STRING {
+ if (!strcmp($1, "default"))
+ $$ = PRCF_DEFAULTCLASS;
+ else if (!strcmp($1, "red"))
+ $$ = PRCF_RED;
+ else if (!strcmp($1, "ecn"))
+ $$ = PRCF_RED|PRCF_ECN;
+ else if (!strcmp($1, "rio"))
+ $$ = PRCF_RIO;
+ else {
+ yyerror("unknown priq flag \"%s\"", $1);
+ free($1);
+ YYERROR;
+ }
+ free($1);
+ }
+ ;
+
+hfsc_opts : {
+ bzero(&hfsc_opts,
+ sizeof(struct node_hfsc_opts));
+ }
+ hfscopts_list {
+ $$ = hfsc_opts;
+ }
+ ;
+
+hfscopts_list : hfscopts_item
+ | hfscopts_list comma hfscopts_item
+ ;
+
+hfscopts_item : LINKSHARE bandwidth {
+ if (hfsc_opts.linkshare.used) {
+ yyerror("linkshare already specified");
+ YYERROR;
+ }
+ hfsc_opts.linkshare.m2 = $2;
+ hfsc_opts.linkshare.used = 1;
+ }
+ | LINKSHARE '(' bandwidth comma NUMBER comma bandwidth ')'
+ {
+ if ($5 < 0 || $5 > INT_MAX) {
+ yyerror("timing in curve out of range");
+ YYERROR;
+ }
+ if (hfsc_opts.linkshare.used) {
+ yyerror("linkshare already specified");
+ YYERROR;
+ }
+ hfsc_opts.linkshare.m1 = $3;
+ hfsc_opts.linkshare.d = $5;
+ hfsc_opts.linkshare.m2 = $7;
+ hfsc_opts.linkshare.used = 1;
+ }
+ | REALTIME bandwidth {
+ if (hfsc_opts.realtime.used) {
+ yyerror("realtime already specified");
+ YYERROR;
+ }
+ hfsc_opts.realtime.m2 = $2;
+ hfsc_opts.realtime.used = 1;
+ }
+ | REALTIME '(' bandwidth comma NUMBER comma bandwidth ')'
+ {
+ if ($5 < 0 || $5 > INT_MAX) {
+ yyerror("timing in curve out of range");
+ YYERROR;
+ }
+ if (hfsc_opts.realtime.used) {
+ yyerror("realtime already specified");
+ YYERROR;
+ }
+ hfsc_opts.realtime.m1 = $3;
+ hfsc_opts.realtime.d = $5;
+ hfsc_opts.realtime.m2 = $7;
+ hfsc_opts.realtime.used = 1;
+ }
+ | UPPERLIMIT bandwidth {
+ if (hfsc_opts.upperlimit.used) {
+ yyerror("upperlimit already specified");
+ YYERROR;
+ }
+ hfsc_opts.upperlimit.m2 = $2;
+ hfsc_opts.upperlimit.used = 1;
+ }
+ | UPPERLIMIT '(' bandwidth comma NUMBER comma bandwidth ')'
+ {
+ if ($5 < 0 || $5 > INT_MAX) {
+ yyerror("timing in curve out of range");
+ YYERROR;
+ }
+ if (hfsc_opts.upperlimit.used) {
+ yyerror("upperlimit already specified");
+ YYERROR;
+ }
+ hfsc_opts.upperlimit.m1 = $3;
+ hfsc_opts.upperlimit.d = $5;
+ hfsc_opts.upperlimit.m2 = $7;
+ hfsc_opts.upperlimit.used = 1;
+ }
+ | STRING {
+ if (!strcmp($1, "default"))
+ hfsc_opts.flags |= HFCF_DEFAULTCLASS;
+ else if (!strcmp($1, "red"))
+ hfsc_opts.flags |= HFCF_RED;
+ else if (!strcmp($1, "ecn"))
+ hfsc_opts.flags |= HFCF_RED|HFCF_ECN;
+ else if (!strcmp($1, "rio"))
+ hfsc_opts.flags |= HFCF_RIO;
+ else {
+ yyerror("unknown hfsc flag \"%s\"", $1);
+ free($1);
+ YYERROR;
+ }
+ free($1);
+ }
+ ;
+
+qassign : /* empty */ { $$ = NULL; }
+ | qassign_item { $$ = $1; }
+ | '{' optnl qassign_list '}' { $$ = $3; }
+ ;
+
+qassign_list : qassign_item optnl { $$ = $1; }
+ | qassign_list comma qassign_item optnl {
+ $1->tail->next = $3;
+ $1->tail = $3;
+ $$ = $1;
+ }
+ ;
+
+qassign_item : STRING {
+ $$ = calloc(1, sizeof(struct node_queue));
+ if ($$ == NULL)
+ err(1, "qassign_item: calloc");
+ if (strlcpy($$->queue, $1, sizeof($$->queue)) >=
+ sizeof($$->queue)) {
+ yyerror("queue name '%s' too long (max "
+ "%d chars)", $1, sizeof($$->queue)-1);
+ free($1);
+ free($$);
+ YYERROR;
+ }
+ free($1);
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ ;
+
+pfrule : action dir logquick interface route af proto fromto
+ filter_opts
+ {
+ struct pf_rule r;
+ struct node_state_opt *o;
+ struct node_proto *proto;
+ int srctrack = 0;
+ int statelock = 0;
+ int adaptive = 0;
+ int defaults = 0;
+
+ if (check_rulestate(PFCTL_STATE_FILTER))
+ YYERROR;
+
+ memset(&r, 0, sizeof(r));
+
+ r.action = $1.b1;
+ switch ($1.b2) {
+ case PFRULE_RETURNRST:
+ r.rule_flag |= PFRULE_RETURNRST;
+ r.return_ttl = $1.w;
+ break;
+ case PFRULE_RETURNICMP:
+ r.rule_flag |= PFRULE_RETURNICMP;
+ r.return_icmp = $1.w;
+ r.return_icmp6 = $1.w2;
+ break;
+ case PFRULE_RETURN:
+ r.rule_flag |= PFRULE_RETURN;
+ r.return_icmp = $1.w;
+ r.return_icmp6 = $1.w2;
+ break;
+ }
+ r.direction = $2;
+ r.log = $3.log;
+ r.logif = $3.logif;
+ r.quick = $3.quick;
+ r.prob = $9.prob;
+ r.rtableid = $9.rtableid;
+
+ r.af = $6;
+ if ($9.tag)
+ if (strlcpy(r.tagname, $9.tag,
+ PF_TAG_NAME_SIZE) >= PF_TAG_NAME_SIZE) {
+ yyerror("tag too long, max %u chars",
+ PF_TAG_NAME_SIZE - 1);
+ YYERROR;
+ }
+ if ($9.match_tag)
+ if (strlcpy(r.match_tagname, $9.match_tag,
+ PF_TAG_NAME_SIZE) >= PF_TAG_NAME_SIZE) {
+ yyerror("tag too long, max %u chars",
+ PF_TAG_NAME_SIZE - 1);
+ YYERROR;
+ }
+ r.match_tag_not = $9.match_tag_not;
+ if (rule_label(&r, $9.label))
+ YYERROR;
+ free($9.label);
+ r.flags = $9.flags.b1;
+ r.flagset = $9.flags.b2;
+ if (($9.flags.b1 & $9.flags.b2) != $9.flags.b1) {
+ yyerror("flags always false");
+ YYERROR;
+ }
+ if ($9.flags.b1 || $9.flags.b2 || $8.src_os) {
+ for (proto = $7; proto != NULL &&
+ proto->proto != IPPROTO_TCP;
+ proto = proto->next)
+ ; /* nothing */
+ if (proto == NULL && $7 != NULL) {
+ if ($9.flags.b1 || $9.flags.b2)
+ yyerror(
+ "flags only apply to tcp");
+ if ($8.src_os)
+ yyerror(
+ "OS fingerprinting only "
+ "apply to tcp");
+ YYERROR;
+ }
+#if 0
+ if (($9.flags.b1 & parse_flags("S")) == 0 &&
+ $8.src_os) {
+ yyerror("OS fingerprinting requires "
+ "the SYN TCP flag (flags S/SA)");
+ YYERROR;
+ }
+#endif
+ }
+
+ r.tos = $9.tos;
+ r.keep_state = $9.keep.action;
+ o = $9.keep.options;
+
+ /* 'keep state' by default on pass rules. */
+ if (!r.keep_state && !r.action &&
+ !($9.marker & FOM_KEEP)) {
+ r.keep_state = PF_STATE_NORMAL;
+ o = keep_state_defaults;
+ defaults = 1;
+ }
+
+ while (o) {
+ struct node_state_opt *p = o;
+
+ switch (o->type) {
+ case PF_STATE_OPT_MAX:
+ if (r.max_states) {
+ yyerror("state option 'max' "
+ "multiple definitions");
+ YYERROR;
+ }
+ r.max_states = o->data.max_states;
+ break;
+ case PF_STATE_OPT_NOSYNC:
+ if (r.rule_flag & PFRULE_NOSYNC) {
+ yyerror("state option 'sync' "
+ "multiple definitions");
+ YYERROR;
+ }
+ r.rule_flag |= PFRULE_NOSYNC;
+ break;
+ case PF_STATE_OPT_SRCTRACK:
+ if (srctrack) {
+ yyerror("state option "
+ "'source-track' "
+ "multiple definitions");
+ YYERROR;
+ }
+ srctrack = o->data.src_track;
+ r.rule_flag |= PFRULE_SRCTRACK;
+ break;
+ case PF_STATE_OPT_MAX_SRC_STATES:
+ if (r.max_src_states) {
+ yyerror("state option "
+ "'max-src-states' "
+ "multiple definitions");
+ YYERROR;
+ }
+ if (o->data.max_src_states == 0) {
+ yyerror("'max-src-states' must "
+ "be > 0");
+ YYERROR;
+ }
+ r.max_src_states =
+ o->data.max_src_states;
+ r.rule_flag |= PFRULE_SRCTRACK;
+ break;
+ case PF_STATE_OPT_OVERLOAD:
+ if (r.overload_tblname[0]) {
+ yyerror("multiple 'overload' "
+ "table definitions");
+ YYERROR;
+ }
+ if (strlcpy(r.overload_tblname,
+ o->data.overload.tblname,
+ PF_TABLE_NAME_SIZE) >=
+ PF_TABLE_NAME_SIZE) {
+ yyerror("state option: "
+ "strlcpy");
+ YYERROR;
+ }
+ r.flush = o->data.overload.flush;
+ break;
+ case PF_STATE_OPT_MAX_SRC_CONN:
+ if (r.max_src_conn) {
+ yyerror("state option "
+ "'max-src-conn' "
+ "multiple definitions");
+ YYERROR;
+ }
+ if (o->data.max_src_conn == 0) {
+ yyerror("'max-src-conn' "
+ "must be > 0");
+ YYERROR;
+ }
+ r.max_src_conn =
+ o->data.max_src_conn;
+ r.rule_flag |= PFRULE_SRCTRACK |
+ PFRULE_RULESRCTRACK;
+ break;
+ case PF_STATE_OPT_MAX_SRC_CONN_RATE:
+ if (r.max_src_conn_rate.limit) {
+ yyerror("state option "
+ "'max-src-conn-rate' "
+ "multiple definitions");
+ YYERROR;
+ }
+ if (!o->data.max_src_conn_rate.limit ||
+ !o->data.max_src_conn_rate.seconds) {
+ yyerror("'max-src-conn-rate' "
+ "values must be > 0");
+ YYERROR;
+ }
+ if (o->data.max_src_conn_rate.limit >
+ PF_THRESHOLD_MAX) {
+ yyerror("'max-src-conn-rate' "
+ "maximum rate must be < %u",
+ PF_THRESHOLD_MAX);
+ YYERROR;
+ }
+ r.max_src_conn_rate.limit =
+ o->data.max_src_conn_rate.limit;
+ r.max_src_conn_rate.seconds =
+ o->data.max_src_conn_rate.seconds;
+ r.rule_flag |= PFRULE_SRCTRACK |
+ PFRULE_RULESRCTRACK;
+ break;
+ case PF_STATE_OPT_MAX_SRC_NODES:
+ if (r.max_src_nodes) {
+ yyerror("state option "
+ "'max-src-nodes' "
+ "multiple definitions");
+ YYERROR;
+ }
+ if (o->data.max_src_nodes == 0) {
+ yyerror("'max-src-nodes' must "
+ "be > 0");
+ YYERROR;
+ }
+ r.max_src_nodes =
+ o->data.max_src_nodes;
+ r.rule_flag |= PFRULE_SRCTRACK |
+ PFRULE_RULESRCTRACK;
+ break;
+ case PF_STATE_OPT_STATELOCK:
+ if (statelock) {
+ yyerror("state locking option: "
+ "multiple definitions");
+ YYERROR;
+ }
+ statelock = 1;
+ r.rule_flag |= o->data.statelock;
+ break;
+ case PF_STATE_OPT_SLOPPY:
+ if (r.rule_flag & PFRULE_STATESLOPPY) {
+ yyerror("state sloppy option: "
+ "multiple definitions");
+ YYERROR;
+ }
+ r.rule_flag |= PFRULE_STATESLOPPY;
+ break;
+ case PF_STATE_OPT_TIMEOUT:
+ if (o->data.timeout.number ==
+ PFTM_ADAPTIVE_START ||
+ o->data.timeout.number ==
+ PFTM_ADAPTIVE_END)
+ adaptive = 1;
+ if (r.timeout[o->data.timeout.number]) {
+ yyerror("state timeout %s "
+ "multiple definitions",
+ pf_timeouts[o->data.
+ timeout.number].name);
+ YYERROR;
+ }
+ r.timeout[o->data.timeout.number] =
+ o->data.timeout.seconds;
+ }
+ o = o->next;
+ if (!defaults)
+ free(p);
+ }
+
+ /* 'flags S/SA' by default on stateful rules */
+ if (!r.action && !r.flags && !r.flagset &&
+ !$9.fragment && !($9.marker & FOM_FLAGS) &&
+ r.keep_state) {
+ r.flags = parse_flags("S");
+ r.flagset = parse_flags("SA");
+ }
+ if (!adaptive && r.max_states) {
+ r.timeout[PFTM_ADAPTIVE_START] =
+ (r.max_states / 10) * 6;
+ r.timeout[PFTM_ADAPTIVE_END] =
+ (r.max_states / 10) * 12;
+ }
+ if (r.rule_flag & PFRULE_SRCTRACK) {
+ if (srctrack == PF_SRCTRACK_GLOBAL &&
+ r.max_src_nodes) {
+ yyerror("'max-src-nodes' is "
+ "incompatible with "
+ "'source-track global'");
+ YYERROR;
+ }
+ if (srctrack == PF_SRCTRACK_GLOBAL &&
+ r.max_src_conn) {
+ yyerror("'max-src-conn' is "
+ "incompatible with "
+ "'source-track global'");
+ YYERROR;
+ }
+ if (srctrack == PF_SRCTRACK_GLOBAL &&
+ r.max_src_conn_rate.seconds) {
+ yyerror("'max-src-conn-rate' is "
+ "incompatible with "
+ "'source-track global'");
+ YYERROR;
+ }
+ if (r.timeout[PFTM_SRC_NODE] <
+ r.max_src_conn_rate.seconds)
+ r.timeout[PFTM_SRC_NODE] =
+ r.max_src_conn_rate.seconds;
+ r.rule_flag |= PFRULE_SRCTRACK;
+ if (srctrack == PF_SRCTRACK_RULE)
+ r.rule_flag |= PFRULE_RULESRCTRACK;
+ }
+ if (r.keep_state && !statelock)
+ r.rule_flag |= default_statelock;
+
+ if ($9.fragment)
+ r.rule_flag |= PFRULE_FRAGMENT;
+ r.allow_opts = $9.allowopts;
+
+ decide_address_family($8.src.host, &r.af);
+ decide_address_family($8.dst.host, &r.af);
+
+ if ($5.rt) {
+ if (!r.direction) {
+ yyerror("direction must be explicit "
+ "with rules that specify routing");
+ YYERROR;
+ }
+ r.rt = $5.rt;
+ r.rpool.opts = $5.pool_opts;
+ if ($5.key != NULL)
+ memcpy(&r.rpool.key, $5.key,
+ sizeof(struct pf_poolhashkey));
+ }
+ if (r.rt && r.rt != PF_FASTROUTE) {
+ decide_address_family($5.host, &r.af);
+ remove_invalid_hosts(&$5.host, &r.af);
+ if ($5.host == NULL) {
+ yyerror("no routing address with "
+ "matching address family found.");
+ YYERROR;
+ }
+ if ((r.rpool.opts & PF_POOL_TYPEMASK) ==
+ PF_POOL_NONE && ($5.host->next != NULL ||
+ $5.host->addr.type == PF_ADDR_TABLE ||
+ DYNIF_MULTIADDR($5.host->addr)))
+ r.rpool.opts |= PF_POOL_ROUNDROBIN;
+ if ((r.rpool.opts & PF_POOL_TYPEMASK) !=
+ PF_POOL_ROUNDROBIN &&
+ disallow_table($5.host, "tables are only "
+ "supported in round-robin routing pools"))
+ YYERROR;
+ if ((r.rpool.opts & PF_POOL_TYPEMASK) !=
+ PF_POOL_ROUNDROBIN &&
+ disallow_alias($5.host, "interface (%s) "
+ "is only supported in round-robin "
+ "routing pools"))
+ YYERROR;
+ if ($5.host->next != NULL) {
+ if ((r.rpool.opts & PF_POOL_TYPEMASK) !=
+ PF_POOL_ROUNDROBIN) {
+ yyerror("r.rpool.opts must "
+ "be PF_POOL_ROUNDROBIN");
+ YYERROR;
+ }
+ }
+ }
+ if ($9.queues.qname != NULL) {
+ if (strlcpy(r.qname, $9.queues.qname,
+ sizeof(r.qname)) >= sizeof(r.qname)) {
+ yyerror("rule qname too long (max "
+ "%d chars)", sizeof(r.qname)-1);
+ YYERROR;
+ }
+ free($9.queues.qname);
+ }
+ if ($9.queues.pqname != NULL) {
+ if (strlcpy(r.pqname, $9.queues.pqname,
+ sizeof(r.pqname)) >= sizeof(r.pqname)) {
+ yyerror("rule pqname too long (max "
+ "%d chars)", sizeof(r.pqname)-1);
+ YYERROR;
+ }
+ free($9.queues.pqname);
+ }
+#ifdef __FreeBSD__
+ r.divert.port = $9.divert.port;
+#else
+ if ((r.divert.port = $9.divert.port)) {
+ if (r.direction == PF_OUT) {
+ if ($9.divert.addr) {
+ yyerror("address specified "
+ "for outgoing divert");
+ YYERROR;
+ }
+ bzero(&r.divert.addr,
+ sizeof(r.divert.addr));
+ } else {
+ if (!$9.divert.addr) {
+ yyerror("no address specified "
+ "for incoming divert");
+ YYERROR;
+ }
+ if ($9.divert.addr->af != r.af) {
+ yyerror("address family "
+ "mismatch for divert");
+ YYERROR;
+ }
+ r.divert.addr =
+ $9.divert.addr->addr.v.a.addr;
+ }
+ }
+#endif
+
+ expand_rule(&r, $4, $5.host, $7, $8.src_os,
+ $8.src.host, $8.src.port, $8.dst.host, $8.dst.port,
+ $9.uid, $9.gid, $9.icmpspec, "");
+ }
+ ;
+
+filter_opts : {
+ bzero(&filter_opts, sizeof filter_opts);
+ filter_opts.rtableid = -1;
+ }
+ filter_opts_l
+ { $$ = filter_opts; }
+ | /* empty */ {
+ bzero(&filter_opts, sizeof filter_opts);
+ filter_opts.rtableid = -1;
+ $$ = filter_opts;
+ }
+ ;
+
+filter_opts_l : filter_opts_l filter_opt
+ | filter_opt
+ ;
+
+filter_opt : USER uids {
+ if (filter_opts.uid)
+ $2->tail->next = filter_opts.uid;
+ filter_opts.uid = $2;
+ }
+ | GROUP gids {
+ if (filter_opts.gid)
+ $2->tail->next = filter_opts.gid;
+ filter_opts.gid = $2;
+ }
+ | flags {
+ if (filter_opts.marker & FOM_FLAGS) {
+ yyerror("flags cannot be redefined");
+ YYERROR;
+ }
+ filter_opts.marker |= FOM_FLAGS;
+ filter_opts.flags.b1 |= $1.b1;
+ filter_opts.flags.b2 |= $1.b2;
+ filter_opts.flags.w |= $1.w;
+ filter_opts.flags.w2 |= $1.w2;
+ }
+ | icmpspec {
+ if (filter_opts.marker & FOM_ICMP) {
+ yyerror("icmp-type cannot be redefined");
+ YYERROR;
+ }
+ filter_opts.marker |= FOM_ICMP;
+ filter_opts.icmpspec = $1;
+ }
+ | TOS tos {
+ if (filter_opts.marker & FOM_TOS) {
+ yyerror("tos cannot be redefined");
+ YYERROR;
+ }
+ filter_opts.marker |= FOM_TOS;
+ filter_opts.tos = $2;
+ }
+ | keep {
+ if (filter_opts.marker & FOM_KEEP) {
+ yyerror("modulate or keep cannot be redefined");
+ YYERROR;
+ }
+ filter_opts.marker |= FOM_KEEP;
+ filter_opts.keep.action = $1.action;
+ filter_opts.keep.options = $1.options;
+ }
+ | FRAGMENT {
+ filter_opts.fragment = 1;
+ }
+ | ALLOWOPTS {
+ filter_opts.allowopts = 1;
+ }
+ | label {
+ if (filter_opts.label) {
+ yyerror("label cannot be redefined");
+ YYERROR;
+ }
+ filter_opts.label = $1;
+ }
+ | qname {
+ if (filter_opts.queues.qname) {
+ yyerror("queue cannot be redefined");
+ YYERROR;
+ }
+ filter_opts.queues = $1;
+ }
+ | TAG string {
+ filter_opts.tag = $2;
+ }
+ | not TAGGED string {
+ filter_opts.match_tag = $3;
+ filter_opts.match_tag_not = $1;
+ }
+ | PROBABILITY probability {
+ double p;
+
+ p = floor($2 * UINT_MAX + 0.5);
+ if (p < 0.0 || p > UINT_MAX) {
+ yyerror("invalid probability: %lf", p);
+ YYERROR;
+ }
+ filter_opts.prob = (u_int32_t)p;
+ if (filter_opts.prob == 0)
+ filter_opts.prob = 1;
+ }
+ | RTABLE NUMBER {
+ if ($2 < 0 || $2 > rt_tableid_max()) {
+ yyerror("invalid rtable id");
+ YYERROR;
+ }
+ filter_opts.rtableid = $2;
+ }
+ | DIVERTTO portplain {
+#ifdef __FreeBSD__
+ filter_opts.divert.port = $2.a;
+ if (!filter_opts.divert.port) {
+ yyerror("invalid divert port: %u", ntohs($2.a));
+ YYERROR;
+ }
+#endif
+ }
+ | DIVERTTO STRING PORT portplain {
+#ifndef __FreeBSD__
+ if ((filter_opts.divert.addr = host($2)) == NULL) {
+ yyerror("could not parse divert address: %s",
+ $2);
+ free($2);
+ YYERROR;
+ }
+#else
+ if ($2)
+#endif
+ free($2);
+ filter_opts.divert.port = $4.a;
+ if (!filter_opts.divert.port) {
+ yyerror("invalid divert port: %u", ntohs($4.a));
+ YYERROR;
+ }
+ }
+ | DIVERTREPLY {
+#ifdef __FreeBSD__
+ yyerror("divert-reply has no meaning in FreeBSD pf(4)");
+ YYERROR;
+#else
+ filter_opts.divert.port = 1; /* some random value */
+#endif
+ }
+ ;
+
+probability : STRING {
+ char *e;
+ double p = strtod($1, &e);
+
+ if (*e == '%') {
+ p *= 0.01;
+ e++;
+ }
+ if (*e) {
+ yyerror("invalid probability: %s", $1);
+ free($1);
+ YYERROR;
+ }
+ free($1);
+ $$ = p;
+ }
+ | NUMBER {
+ $$ = (double)$1;
+ }
+ ;
+
+
+action : PASS { $$.b1 = PF_PASS; $$.b2 = $$.w = 0; }
+ | BLOCK blockspec { $$ = $2; $$.b1 = PF_DROP; }
+ ;
+
+blockspec : /* empty */ {
+ $$.b2 = blockpolicy;
+ $$.w = returnicmpdefault;
+ $$.w2 = returnicmp6default;
+ }
+ | DROP {
+ $$.b2 = PFRULE_DROP;
+ $$.w = 0;
+ $$.w2 = 0;
+ }
+ | RETURNRST {
+ $$.b2 = PFRULE_RETURNRST;
+ $$.w = 0;
+ $$.w2 = 0;
+ }
+ | RETURNRST '(' TTL NUMBER ')' {
+ if ($4 < 0 || $4 > 255) {
+ yyerror("illegal ttl value %d", $4);
+ YYERROR;
+ }
+ $$.b2 = PFRULE_RETURNRST;
+ $$.w = $4;
+ $$.w2 = 0;
+ }
+ | RETURNICMP {
+ $$.b2 = PFRULE_RETURNICMP;
+ $$.w = returnicmpdefault;
+ $$.w2 = returnicmp6default;
+ }
+ | RETURNICMP6 {
+ $$.b2 = PFRULE_RETURNICMP;
+ $$.w = returnicmpdefault;
+ $$.w2 = returnicmp6default;
+ }
+ | RETURNICMP '(' reticmpspec ')' {
+ $$.b2 = PFRULE_RETURNICMP;
+ $$.w = $3;
+ $$.w2 = returnicmpdefault;
+ }
+ | RETURNICMP6 '(' reticmp6spec ')' {
+ $$.b2 = PFRULE_RETURNICMP;
+ $$.w = returnicmpdefault;
+ $$.w2 = $3;
+ }
+ | RETURNICMP '(' reticmpspec comma reticmp6spec ')' {
+ $$.b2 = PFRULE_RETURNICMP;
+ $$.w = $3;
+ $$.w2 = $5;
+ }
+ | RETURN {
+ $$.b2 = PFRULE_RETURN;
+ $$.w = returnicmpdefault;
+ $$.w2 = returnicmp6default;
+ }
+ ;
+
+reticmpspec : STRING {
+ if (!($$ = parseicmpspec($1, AF_INET))) {
+ free($1);
+ YYERROR;
+ }
+ free($1);
+ }
+ | NUMBER {
+ u_int8_t icmptype;
+
+ if ($1 < 0 || $1 > 255) {
+ yyerror("invalid icmp code %lu", $1);
+ YYERROR;
+ }
+ icmptype = returnicmpdefault >> 8;
+ $$ = (icmptype << 8 | $1);
+ }
+ ;
+
+reticmp6spec : STRING {
+ if (!($$ = parseicmpspec($1, AF_INET6))) {
+ free($1);
+ YYERROR;
+ }
+ free($1);
+ }
+ | NUMBER {
+ u_int8_t icmptype;
+
+ if ($1 < 0 || $1 > 255) {
+ yyerror("invalid icmp code %lu", $1);
+ YYERROR;
+ }
+ icmptype = returnicmp6default >> 8;
+ $$ = (icmptype << 8 | $1);
+ }
+ ;
+
+dir : /* empty */ { $$ = PF_INOUT; }
+ | IN { $$ = PF_IN; }
+ | OUT { $$ = PF_OUT; }
+ ;
+
+quick : /* empty */ { $$.quick = 0; }
+ | QUICK { $$.quick = 1; }
+ ;
+
+logquick : /* empty */ { $$.log = 0; $$.quick = 0; $$.logif = 0; }
+ | log { $$ = $1; $$.quick = 0; }
+ | QUICK { $$.quick = 1; $$.log = 0; $$.logif = 0; }
+ | log QUICK { $$ = $1; $$.quick = 1; }
+ | QUICK log { $$ = $2; $$.quick = 1; }
+ ;
+
+log : LOG { $$.log = PF_LOG; $$.logif = 0; }
+ | LOG '(' logopts ')' {
+ $$.log = PF_LOG | $3.log;
+ $$.logif = $3.logif;
+ }
+ ;
+
+logopts : logopt { $$ = $1; }
+ | logopts comma logopt {
+ $$.log = $1.log | $3.log;
+ $$.logif = $3.logif;
+ if ($$.logif == 0)
+ $$.logif = $1.logif;
+ }
+ ;
+
+logopt : ALL { $$.log = PF_LOG_ALL; $$.logif = 0; }
+ | USER { $$.log = PF_LOG_SOCKET_LOOKUP; $$.logif = 0; }
+ | GROUP { $$.log = PF_LOG_SOCKET_LOOKUP; $$.logif = 0; }
+ | TO string {
+ const char *errstr;
+ u_int i;
+
+ $$.log = 0;
+ if (strncmp($2, "pflog", 5)) {
+ yyerror("%s: should be a pflog interface", $2);
+ free($2);
+ YYERROR;
+ }
+ i = strtonum($2 + 5, 0, 255, &errstr);
+ if (errstr) {
+ yyerror("%s: %s", $2, errstr);
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ $$.logif = i;
+ }
+ ;
+
+interface : /* empty */ { $$ = NULL; }
+ | ON if_item_not { $$ = $2; }
+ | ON '{' optnl if_list '}' { $$ = $4; }
+ ;
+
+if_list : if_item_not optnl { $$ = $1; }
+ | if_list comma if_item_not optnl {
+ $1->tail->next = $3;
+ $1->tail = $3;
+ $$ = $1;
+ }
+ ;
+
+if_item_not : not if_item { $$ = $2; $$->not = $1; }
+ ;
+
+if_item : STRING {
+ struct node_host *n;
+
+ $$ = calloc(1, sizeof(struct node_if));
+ if ($$ == NULL)
+ err(1, "if_item: calloc");
+ if (strlcpy($$->ifname, $1, sizeof($$->ifname)) >=
+ sizeof($$->ifname)) {
+ free($1);
+ free($$);
+ yyerror("interface name too long");
+ YYERROR;
+ }
+
+ if ((n = ifa_exists($1)) != NULL)
+ $$->ifa_flags = n->ifa_flags;
+
+ free($1);
+ $$->not = 0;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ ;
+
+af : /* empty */ { $$ = 0; }
+ | INET { $$ = AF_INET; }
+ | INET6 { $$ = AF_INET6; }
+ ;
+
+proto : /* empty */ { $$ = NULL; }
+ | PROTO proto_item { $$ = $2; }
+ | PROTO '{' optnl proto_list '}' { $$ = $4; }
+ ;
+
+proto_list : proto_item optnl { $$ = $1; }
+ | proto_list comma proto_item optnl {
+ $1->tail->next = $3;
+ $1->tail = $3;
+ $$ = $1;
+ }
+ ;
+
+proto_item : protoval {
+ u_int8_t pr;
+
+ pr = (u_int8_t)$1;
+ if (pr == 0) {
+ yyerror("proto 0 cannot be used");
+ YYERROR;
+ }
+ $$ = calloc(1, sizeof(struct node_proto));
+ if ($$ == NULL)
+ err(1, "proto_item: calloc");
+ $$->proto = pr;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ ;
+
+protoval : STRING {
+ struct protoent *p;
+
+ p = getprotobyname($1);
+ if (p == NULL) {
+ yyerror("unknown protocol %s", $1);
+ free($1);
+ YYERROR;
+ }
+ $$ = p->p_proto;
+ free($1);
+ }
+ | NUMBER {
+ if ($1 < 0 || $1 > 255) {
+ yyerror("protocol outside range");
+ YYERROR;
+ }
+ }
+ ;
+
+fromto : ALL {
+ $$.src.host = NULL;
+ $$.src.port = NULL;
+ $$.dst.host = NULL;
+ $$.dst.port = NULL;
+ $$.src_os = NULL;
+ }
+ | from os to {
+ $$.src = $1;
+ $$.src_os = $2;
+ $$.dst = $3;
+ }
+ ;
+
+os : /* empty */ { $$ = NULL; }
+ | OS xos { $$ = $2; }
+ | OS '{' optnl os_list '}' { $$ = $4; }
+ ;
+
+xos : STRING {
+ $$ = calloc(1, sizeof(struct node_os));
+ if ($$ == NULL)
+ err(1, "os: calloc");
+ $$->os = $1;
+ $$->tail = $$;
+ }
+ ;
+
+os_list : xos optnl { $$ = $1; }
+ | os_list comma xos optnl {
+ $1->tail->next = $3;
+ $1->tail = $3;
+ $$ = $1;
+ }
+ ;
+
+from : /* empty */ {
+ $$.host = NULL;
+ $$.port = NULL;
+ }
+ | FROM ipportspec {
+ $$ = $2;
+ }
+ ;
+
+to : /* empty */ {
+ $$.host = NULL;
+ $$.port = NULL;
+ }
+ | TO ipportspec {
+ if (disallow_urpf_failed($2.host, "\"urpf-failed\" is "
+ "not permitted in a destination address"))
+ YYERROR;
+ $$ = $2;
+ }
+ ;
+
+ipportspec : ipspec {
+ $$.host = $1;
+ $$.port = NULL;
+ }
+ | ipspec PORT portspec {
+ $$.host = $1;
+ $$.port = $3;
+ }
+ | PORT portspec {
+ $$.host = NULL;
+ $$.port = $2;
+ }
+ ;
+
+optnl : '\n' optnl
+ |
+ ;
+
+ipspec : ANY { $$ = NULL; }
+ | xhost { $$ = $1; }
+ | '{' optnl host_list '}' { $$ = $3; }
+ ;
+
+toipspec : TO ipspec { $$ = $2; }
+ | /* empty */ { $$ = NULL; }
+ ;
+
+host_list : ipspec optnl { $$ = $1; }
+ | host_list comma ipspec optnl {
+ if ($3 == NULL)
+ $$ = $1;
+ else if ($1 == NULL)
+ $$ = $3;
+ else {
+ $1->tail->next = $3;
+ $1->tail = $3->tail;
+ $$ = $1;
+ }
+ }
+ ;
+
+xhost : not host {
+ struct node_host *n;
+
+ for (n = $2; n != NULL; n = n->next)
+ n->not = $1;
+ $$ = $2;
+ }
+ | not NOROUTE {
+ $$ = calloc(1, sizeof(struct node_host));
+ if ($$ == NULL)
+ err(1, "xhost: calloc");
+ $$->addr.type = PF_ADDR_NOROUTE;
+ $$->next = NULL;
+ $$->not = $1;
+ $$->tail = $$;
+ }
+ | not URPFFAILED {
+ $$ = calloc(1, sizeof(struct node_host));
+ if ($$ == NULL)
+ err(1, "xhost: calloc");
+ $$->addr.type = PF_ADDR_URPFFAILED;
+ $$->next = NULL;
+ $$->not = $1;
+ $$->tail = $$;
+ }
+ ;
+
+host : STRING {
+ if (($$ = host($1)) == NULL) {
+ /* error. "any" is handled elsewhere */
+ free($1);
+ yyerror("could not parse host specification");
+ YYERROR;
+ }
+ free($1);
+
+ }
+ | STRING '-' STRING {
+ struct node_host *b, *e;
+
+ if ((b = host($1)) == NULL || (e = host($3)) == NULL) {
+ free($1);
+ free($3);
+ yyerror("could not parse host specification");
+ YYERROR;
+ }
+ if (b->af != e->af ||
+ b->addr.type != PF_ADDR_ADDRMASK ||
+ e->addr.type != PF_ADDR_ADDRMASK ||
+ unmask(&b->addr.v.a.mask, b->af) !=
+ (b->af == AF_INET ? 32 : 128) ||
+ unmask(&e->addr.v.a.mask, e->af) !=
+ (e->af == AF_INET ? 32 : 128) ||
+ b->next != NULL || b->not ||
+ e->next != NULL || e->not) {
+ free(b);
+ free(e);
+ free($1);
+ free($3);
+ yyerror("invalid address range");
+ YYERROR;
+ }
+ memcpy(&b->addr.v.a.mask, &e->addr.v.a.addr,
+ sizeof(b->addr.v.a.mask));
+ b->addr.type = PF_ADDR_RANGE;
+ $$ = b;
+ free(e);
+ free($1);
+ free($3);
+ }
+ | STRING '/' NUMBER {
+ char *buf;
+
+ if (asprintf(&buf, "%s/%lld", $1, (long long)$3) == -1)
+ err(1, "host: asprintf");
+ free($1);
+ if (($$ = host(buf)) == NULL) {
+ /* error. "any" is handled elsewhere */
+ free(buf);
+ yyerror("could not parse host specification");
+ YYERROR;
+ }
+ free(buf);
+ }
+ | NUMBER '/' NUMBER {
+ char *buf;
+
+ /* ie. for 10/8 parsing */
+#ifdef __FreeBSD__
+ if (asprintf(&buf, "%lld/%lld", (long long)$1, (long long)$3) == -1)
+#else
+ if (asprintf(&buf, "%lld/%lld", $1, $3) == -1)
+#endif
+ err(1, "host: asprintf");
+ if (($$ = host(buf)) == NULL) {
+ /* error. "any" is handled elsewhere */
+ free(buf);
+ yyerror("could not parse host specification");
+ YYERROR;
+ }
+ free(buf);
+ }
+ | dynaddr
+ | dynaddr '/' NUMBER {
+ struct node_host *n;
+
+ if ($3 < 0 || $3 > 128) {
+ yyerror("bit number too big");
+ YYERROR;
+ }
+ $$ = $1;
+ for (n = $1; n != NULL; n = n->next)
+ set_ipmask(n, $3);
+ }
+ | '<' STRING '>' {
+ if (strlen($2) >= PF_TABLE_NAME_SIZE) {
+ yyerror("table name '%s' too long", $2);
+ free($2);
+ YYERROR;
+ }
+ $$ = calloc(1, sizeof(struct node_host));
+ if ($$ == NULL)
+ err(1, "host: calloc");
+ $$->addr.type = PF_ADDR_TABLE;
+ if (strlcpy($$->addr.v.tblname, $2,
+ sizeof($$->addr.v.tblname)) >=
+ sizeof($$->addr.v.tblname))
+ errx(1, "host: strlcpy");
+ free($2);
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ ;
+
+number : NUMBER
+ | STRING {
+ u_long ulval;
+
+ if (atoul($1, &ulval) == -1) {
+ yyerror("%s is not a number", $1);
+ free($1);
+ YYERROR;
+ } else
+ $$ = ulval;
+ free($1);
+ }
+ ;
+
+dynaddr : '(' STRING ')' {
+ int flags = 0;
+ char *p, *op;
+
+ op = $2;
+ if (!isalpha(op[0])) {
+ yyerror("invalid interface name '%s'", op);
+ free(op);
+ YYERROR;
+ }
+ while ((p = strrchr($2, ':')) != NULL) {
+ if (!strcmp(p+1, "network"))
+ flags |= PFI_AFLAG_NETWORK;
+ else if (!strcmp(p+1, "broadcast"))
+ flags |= PFI_AFLAG_BROADCAST;
+ else if (!strcmp(p+1, "peer"))
+ flags |= PFI_AFLAG_PEER;
+ else if (!strcmp(p+1, "0"))
+ flags |= PFI_AFLAG_NOALIAS;
+ else {
+ yyerror("interface %s has bad modifier",
+ $2);
+ free(op);
+ YYERROR;
+ }
+ *p = '\0';
+ }
+ if (flags & (flags - 1) & PFI_AFLAG_MODEMASK) {
+ free(op);
+ yyerror("illegal combination of "
+ "interface modifiers");
+ YYERROR;
+ }
+ $$ = calloc(1, sizeof(struct node_host));
+ if ($$ == NULL)
+ err(1, "address: calloc");
+ $$->af = 0;
+ set_ipmask($$, 128);
+ $$->addr.type = PF_ADDR_DYNIFTL;
+ $$->addr.iflags = flags;
+ if (strlcpy($$->addr.v.ifname, $2,
+ sizeof($$->addr.v.ifname)) >=
+ sizeof($$->addr.v.ifname)) {
+ free(op);
+ free($$);
+ yyerror("interface name too long");
+ YYERROR;
+ }
+ free(op);
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ ;
+
+portspec : port_item { $$ = $1; }
+ | '{' optnl port_list '}' { $$ = $3; }
+ ;
+
+port_list : port_item optnl { $$ = $1; }
+ | port_list comma port_item optnl {
+ $1->tail->next = $3;
+ $1->tail = $3;
+ $$ = $1;
+ }
+ ;
+
+port_item : portrange {
+ $$ = calloc(1, sizeof(struct node_port));
+ if ($$ == NULL)
+ err(1, "port_item: calloc");
+ $$->port[0] = $1.a;
+ $$->port[1] = $1.b;
+ if ($1.t)
+ $$->op = PF_OP_RRG;
+ else
+ $$->op = PF_OP_EQ;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ | unaryop portrange {
+ if ($2.t) {
+ yyerror("':' cannot be used with an other "
+ "port operator");
+ YYERROR;
+ }
+ $$ = calloc(1, sizeof(struct node_port));
+ if ($$ == NULL)
+ err(1, "port_item: calloc");
+ $$->port[0] = $2.a;
+ $$->port[1] = $2.b;
+ $$->op = $1;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ | portrange PORTBINARY portrange {
+ if ($1.t || $3.t) {
+ yyerror("':' cannot be used with an other "
+ "port operator");
+ YYERROR;
+ }
+ $$ = calloc(1, sizeof(struct node_port));
+ if ($$ == NULL)
+ err(1, "port_item: calloc");
+ $$->port[0] = $1.a;
+ $$->port[1] = $3.a;
+ $$->op = $2;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ ;
+
+portplain : numberstring {
+ if (parseport($1, &$$, 0) == -1) {
+ free($1);
+ YYERROR;
+ }
+ free($1);
+ }
+ ;
+
+portrange : numberstring {
+ if (parseport($1, &$$, PPORT_RANGE) == -1) {
+ free($1);
+ YYERROR;
+ }
+ free($1);
+ }
+ ;
+
+uids : uid_item { $$ = $1; }
+ | '{' optnl uid_list '}' { $$ = $3; }
+ ;
+
+uid_list : uid_item optnl { $$ = $1; }
+ | uid_list comma uid_item optnl {
+ $1->tail->next = $3;
+ $1->tail = $3;
+ $$ = $1;
+ }
+ ;
+
+uid_item : uid {
+ $$ = calloc(1, sizeof(struct node_uid));
+ if ($$ == NULL)
+ err(1, "uid_item: calloc");
+ $$->uid[0] = $1;
+ $$->uid[1] = $1;
+ $$->op = PF_OP_EQ;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ | unaryop uid {
+ if ($2 == UID_MAX && $1 != PF_OP_EQ && $1 != PF_OP_NE) {
+ yyerror("user unknown requires operator = or "
+ "!=");
+ YYERROR;
+ }
+ $$ = calloc(1, sizeof(struct node_uid));
+ if ($$ == NULL)
+ err(1, "uid_item: calloc");
+ $$->uid[0] = $2;
+ $$->uid[1] = $2;
+ $$->op = $1;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ | uid PORTBINARY uid {
+ if ($1 == UID_MAX || $3 == UID_MAX) {
+ yyerror("user unknown requires operator = or "
+ "!=");
+ YYERROR;
+ }
+ $$ = calloc(1, sizeof(struct node_uid));
+ if ($$ == NULL)
+ err(1, "uid_item: calloc");
+ $$->uid[0] = $1;
+ $$->uid[1] = $3;
+ $$->op = $2;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ ;
+
+uid : STRING {
+ if (!strcmp($1, "unknown"))
+ $$ = UID_MAX;
+ else {
+ struct passwd *pw;
+
+ if ((pw = getpwnam($1)) == NULL) {
+ yyerror("unknown user %s", $1);
+ free($1);
+ YYERROR;
+ }
+ $$ = pw->pw_uid;
+ }
+ free($1);
+ }
+ | NUMBER {
+ if ($1 < 0 || $1 >= UID_MAX) {
+ yyerror("illegal uid value %lu", $1);
+ YYERROR;
+ }
+ $$ = $1;
+ }
+ ;
+
+gids : gid_item { $$ = $1; }
+ | '{' optnl gid_list '}' { $$ = $3; }
+ ;
+
+gid_list : gid_item optnl { $$ = $1; }
+ | gid_list comma gid_item optnl {
+ $1->tail->next = $3;
+ $1->tail = $3;
+ $$ = $1;
+ }
+ ;
+
+gid_item : gid {
+ $$ = calloc(1, sizeof(struct node_gid));
+ if ($$ == NULL)
+ err(1, "gid_item: calloc");
+ $$->gid[0] = $1;
+ $$->gid[1] = $1;
+ $$->op = PF_OP_EQ;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ | unaryop gid {
+ if ($2 == GID_MAX && $1 != PF_OP_EQ && $1 != PF_OP_NE) {
+ yyerror("group unknown requires operator = or "
+ "!=");
+ YYERROR;
+ }
+ $$ = calloc(1, sizeof(struct node_gid));
+ if ($$ == NULL)
+ err(1, "gid_item: calloc");
+ $$->gid[0] = $2;
+ $$->gid[1] = $2;
+ $$->op = $1;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ | gid PORTBINARY gid {
+ if ($1 == GID_MAX || $3 == GID_MAX) {
+ yyerror("group unknown requires operator = or "
+ "!=");
+ YYERROR;
+ }
+ $$ = calloc(1, sizeof(struct node_gid));
+ if ($$ == NULL)
+ err(1, "gid_item: calloc");
+ $$->gid[0] = $1;
+ $$->gid[1] = $3;
+ $$->op = $2;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ ;
+
+gid : STRING {
+ if (!strcmp($1, "unknown"))
+ $$ = GID_MAX;
+ else {
+ struct group *grp;
+
+ if ((grp = getgrnam($1)) == NULL) {
+ yyerror("unknown group %s", $1);
+ free($1);
+ YYERROR;
+ }
+ $$ = grp->gr_gid;
+ }
+ free($1);
+ }
+ | NUMBER {
+ if ($1 < 0 || $1 >= GID_MAX) {
+ yyerror("illegal gid value %lu", $1);
+ YYERROR;
+ }
+ $$ = $1;
+ }
+ ;
+
+flag : STRING {
+ int f;
+
+ if ((f = parse_flags($1)) < 0) {
+ yyerror("bad flags %s", $1);
+ free($1);
+ YYERROR;
+ }
+ free($1);
+ $$.b1 = f;
+ }
+ ;
+
+flags : FLAGS flag '/' flag { $$.b1 = $2.b1; $$.b2 = $4.b1; }
+ | FLAGS '/' flag { $$.b1 = 0; $$.b2 = $3.b1; }
+ | FLAGS ANY { $$.b1 = 0; $$.b2 = 0; }
+ ;
+
+icmpspec : ICMPTYPE icmp_item { $$ = $2; }
+ | ICMPTYPE '{' optnl icmp_list '}' { $$ = $4; }
+ | ICMP6TYPE icmp6_item { $$ = $2; }
+ | ICMP6TYPE '{' optnl icmp6_list '}' { $$ = $4; }
+ ;
+
+icmp_list : icmp_item optnl { $$ = $1; }
+ | icmp_list comma icmp_item optnl {
+ $1->tail->next = $3;
+ $1->tail = $3;
+ $$ = $1;
+ }
+ ;
+
+icmp6_list : icmp6_item optnl { $$ = $1; }
+ | icmp6_list comma icmp6_item optnl {
+ $1->tail->next = $3;
+ $1->tail = $3;
+ $$ = $1;
+ }
+ ;
+
+icmp_item : icmptype {
+ $$ = calloc(1, sizeof(struct node_icmp));
+ if ($$ == NULL)
+ err(1, "icmp_item: calloc");
+ $$->type = $1;
+ $$->code = 0;
+ $$->proto = IPPROTO_ICMP;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ | icmptype CODE STRING {
+ const struct icmpcodeent *p;
+
+ if ((p = geticmpcodebyname($1-1, $3, AF_INET)) == NULL) {
+ yyerror("unknown icmp-code %s", $3);
+ free($3);
+ YYERROR;
+ }
+
+ free($3);
+ $$ = calloc(1, sizeof(struct node_icmp));
+ if ($$ == NULL)
+ err(1, "icmp_item: calloc");
+ $$->type = $1;
+ $$->code = p->code + 1;
+ $$->proto = IPPROTO_ICMP;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ | icmptype CODE NUMBER {
+ if ($3 < 0 || $3 > 255) {
+ yyerror("illegal icmp-code %lu", $3);
+ YYERROR;
+ }
+ $$ = calloc(1, sizeof(struct node_icmp));
+ if ($$ == NULL)
+ err(1, "icmp_item: calloc");
+ $$->type = $1;
+ $$->code = $3 + 1;
+ $$->proto = IPPROTO_ICMP;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ ;
+
+icmp6_item : icmp6type {
+ $$ = calloc(1, sizeof(struct node_icmp));
+ if ($$ == NULL)
+ err(1, "icmp_item: calloc");
+ $$->type = $1;
+ $$->code = 0;
+ $$->proto = IPPROTO_ICMPV6;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ | icmp6type CODE STRING {
+ const struct icmpcodeent *p;
+
+ if ((p = geticmpcodebyname($1-1, $3, AF_INET6)) == NULL) {
+ yyerror("unknown icmp6-code %s", $3);
+ free($3);
+ YYERROR;
+ }
+ free($3);
+
+ $$ = calloc(1, sizeof(struct node_icmp));
+ if ($$ == NULL)
+ err(1, "icmp_item: calloc");
+ $$->type = $1;
+ $$->code = p->code + 1;
+ $$->proto = IPPROTO_ICMPV6;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ | icmp6type CODE NUMBER {
+ if ($3 < 0 || $3 > 255) {
+ yyerror("illegal icmp-code %lu", $3);
+ YYERROR;
+ }
+ $$ = calloc(1, sizeof(struct node_icmp));
+ if ($$ == NULL)
+ err(1, "icmp_item: calloc");
+ $$->type = $1;
+ $$->code = $3 + 1;
+ $$->proto = IPPROTO_ICMPV6;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ ;
+
+icmptype : STRING {
+ const struct icmptypeent *p;
+
+ if ((p = geticmptypebyname($1, AF_INET)) == NULL) {
+ yyerror("unknown icmp-type %s", $1);
+ free($1);
+ YYERROR;
+ }
+ $$ = p->type + 1;
+ free($1);
+ }
+ | NUMBER {
+ if ($1 < 0 || $1 > 255) {
+ yyerror("illegal icmp-type %lu", $1);
+ YYERROR;
+ }
+ $$ = $1 + 1;
+ }
+ ;
+
+icmp6type : STRING {
+ const struct icmptypeent *p;
+
+ if ((p = geticmptypebyname($1, AF_INET6)) ==
+ NULL) {
+ yyerror("unknown icmp6-type %s", $1);
+ free($1);
+ YYERROR;
+ }
+ $$ = p->type + 1;
+ free($1);
+ }
+ | NUMBER {
+ if ($1 < 0 || $1 > 255) {
+ yyerror("illegal icmp6-type %lu", $1);
+ YYERROR;
+ }
+ $$ = $1 + 1;
+ }
+ ;
+
+tos : STRING {
+ if (!strcmp($1, "lowdelay"))
+ $$ = IPTOS_LOWDELAY;
+ else if (!strcmp($1, "throughput"))
+ $$ = IPTOS_THROUGHPUT;
+ else if (!strcmp($1, "reliability"))
+ $$ = IPTOS_RELIABILITY;
+ else if ($1[0] == '0' && $1[1] == 'x')
+ $$ = strtoul($1, NULL, 16);
+ else
+ $$ = 0; /* flag bad argument */
+ if (!$$ || $$ > 255) {
+ yyerror("illegal tos value %s", $1);
+ free($1);
+ YYERROR;
+ }
+ free($1);
+ }
+ | NUMBER {
+ $$ = $1;
+ if (!$$ || $$ > 255) {
+ yyerror("illegal tos value %s", $1);
+ YYERROR;
+ }
+ }
+ ;
+
+sourcetrack : SOURCETRACK { $$ = PF_SRCTRACK; }
+ | SOURCETRACK GLOBAL { $$ = PF_SRCTRACK_GLOBAL; }
+ | SOURCETRACK RULE { $$ = PF_SRCTRACK_RULE; }
+ ;
+
+statelock : IFBOUND {
+ $$ = PFRULE_IFBOUND;
+ }
+ | FLOATING {
+ $$ = 0;
+ }
+ ;
+
+keep : NO STATE {
+ $$.action = 0;
+ $$.options = NULL;
+ }
+ | KEEP STATE state_opt_spec {
+ $$.action = PF_STATE_NORMAL;
+ $$.options = $3;
+ }
+ | MODULATE STATE state_opt_spec {
+ $$.action = PF_STATE_MODULATE;
+ $$.options = $3;
+ }
+ | SYNPROXY STATE state_opt_spec {
+ $$.action = PF_STATE_SYNPROXY;
+ $$.options = $3;
+ }
+ ;
+
+flush : /* empty */ { $$ = 0; }
+ | FLUSH { $$ = PF_FLUSH; }
+ | FLUSH GLOBAL {
+ $$ = PF_FLUSH | PF_FLUSH_GLOBAL;
+ }
+ ;
+
+state_opt_spec : '(' state_opt_list ')' { $$ = $2; }
+ | /* empty */ { $$ = NULL; }
+ ;
+
+state_opt_list : state_opt_item { $$ = $1; }
+ | state_opt_list comma state_opt_item {
+ $1->tail->next = $3;
+ $1->tail = $3;
+ $$ = $1;
+ }
+ ;
+
+state_opt_item : MAXIMUM NUMBER {
+ if ($2 < 0 || $2 > UINT_MAX) {
+ yyerror("only positive values permitted");
+ YYERROR;
+ }
+ $$ = calloc(1, sizeof(struct node_state_opt));
+ if ($$ == NULL)
+ err(1, "state_opt_item: calloc");
+ $$->type = PF_STATE_OPT_MAX;
+ $$->data.max_states = $2;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ | NOSYNC {
+ $$ = calloc(1, sizeof(struct node_state_opt));
+ if ($$ == NULL)
+ err(1, "state_opt_item: calloc");
+ $$->type = PF_STATE_OPT_NOSYNC;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ | MAXSRCSTATES NUMBER {
+ if ($2 < 0 || $2 > UINT_MAX) {
+ yyerror("only positive values permitted");
+ YYERROR;
+ }
+ $$ = calloc(1, sizeof(struct node_state_opt));
+ if ($$ == NULL)
+ err(1, "state_opt_item: calloc");
+ $$->type = PF_STATE_OPT_MAX_SRC_STATES;
+ $$->data.max_src_states = $2;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ | MAXSRCCONN NUMBER {
+ if ($2 < 0 || $2 > UINT_MAX) {
+ yyerror("only positive values permitted");
+ YYERROR;
+ }
+ $$ = calloc(1, sizeof(struct node_state_opt));
+ if ($$ == NULL)
+ err(1, "state_opt_item: calloc");
+ $$->type = PF_STATE_OPT_MAX_SRC_CONN;
+ $$->data.max_src_conn = $2;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ | MAXSRCCONNRATE NUMBER '/' NUMBER {
+ if ($2 < 0 || $2 > UINT_MAX ||
+ $4 < 0 || $4 > UINT_MAX) {
+ yyerror("only positive values permitted");
+ YYERROR;
+ }
+ $$ = calloc(1, sizeof(struct node_state_opt));
+ if ($$ == NULL)
+ err(1, "state_opt_item: calloc");
+ $$->type = PF_STATE_OPT_MAX_SRC_CONN_RATE;
+ $$->data.max_src_conn_rate.limit = $2;
+ $$->data.max_src_conn_rate.seconds = $4;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ | OVERLOAD '<' STRING '>' flush {
+ if (strlen($3) >= PF_TABLE_NAME_SIZE) {
+ yyerror("table name '%s' too long", $3);
+ free($3);
+ YYERROR;
+ }
+ $$ = calloc(1, sizeof(struct node_state_opt));
+ if ($$ == NULL)
+ err(1, "state_opt_item: calloc");
+ if (strlcpy($$->data.overload.tblname, $3,
+ PF_TABLE_NAME_SIZE) >= PF_TABLE_NAME_SIZE)
+ errx(1, "state_opt_item: strlcpy");
+ free($3);
+ $$->type = PF_STATE_OPT_OVERLOAD;
+ $$->data.overload.flush = $5;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ | MAXSRCNODES NUMBER {
+ if ($2 < 0 || $2 > UINT_MAX) {
+ yyerror("only positive values permitted");
+ YYERROR;
+ }
+ $$ = calloc(1, sizeof(struct node_state_opt));
+ if ($$ == NULL)
+ err(1, "state_opt_item: calloc");
+ $$->type = PF_STATE_OPT_MAX_SRC_NODES;
+ $$->data.max_src_nodes = $2;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ | sourcetrack {
+ $$ = calloc(1, sizeof(struct node_state_opt));
+ if ($$ == NULL)
+ err(1, "state_opt_item: calloc");
+ $$->type = PF_STATE_OPT_SRCTRACK;
+ $$->data.src_track = $1;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ | statelock {
+ $$ = calloc(1, sizeof(struct node_state_opt));
+ if ($$ == NULL)
+ err(1, "state_opt_item: calloc");
+ $$->type = PF_STATE_OPT_STATELOCK;
+ $$->data.statelock = $1;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ | SLOPPY {
+ $$ = calloc(1, sizeof(struct node_state_opt));
+ if ($$ == NULL)
+ err(1, "state_opt_item: calloc");
+ $$->type = PF_STATE_OPT_SLOPPY;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ | STRING NUMBER {
+ int i;
+
+ if ($2 < 0 || $2 > UINT_MAX) {
+ yyerror("only positive values permitted");
+ YYERROR;
+ }
+ for (i = 0; pf_timeouts[i].name &&
+ strcmp(pf_timeouts[i].name, $1); ++i)
+ ; /* nothing */
+ if (!pf_timeouts[i].name) {
+ yyerror("illegal timeout name %s", $1);
+ free($1);
+ YYERROR;
+ }
+ if (strchr(pf_timeouts[i].name, '.') == NULL) {
+ yyerror("illegal state timeout %s", $1);
+ free($1);
+ YYERROR;
+ }
+ free($1);
+ $$ = calloc(1, sizeof(struct node_state_opt));
+ if ($$ == NULL)
+ err(1, "state_opt_item: calloc");
+ $$->type = PF_STATE_OPT_TIMEOUT;
+ $$->data.timeout.number = pf_timeouts[i].timeout;
+ $$->data.timeout.seconds = $2;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ ;
+
+label : LABEL STRING {
+ $$ = $2;
+ }
+ ;
+
+qname : QUEUE STRING {
+ $$.qname = $2;
+ $$.pqname = NULL;
+ }
+ | QUEUE '(' STRING ')' {
+ $$.qname = $3;
+ $$.pqname = NULL;
+ }
+ | QUEUE '(' STRING comma STRING ')' {
+ $$.qname = $3;
+ $$.pqname = $5;
+ }
+ ;
+
+no : /* empty */ { $$ = 0; }
+ | NO { $$ = 1; }
+ ;
+
+portstar : numberstring {
+ if (parseport($1, &$$, PPORT_RANGE|PPORT_STAR) == -1) {
+ free($1);
+ YYERROR;
+ }
+ free($1);
+ }
+ ;
+
+redirspec : host { $$ = $1; }
+ | '{' optnl redir_host_list '}' { $$ = $3; }
+ ;
+
+redir_host_list : host optnl { $$ = $1; }
+ | redir_host_list comma host optnl {
+ $1->tail->next = $3;
+ $1->tail = $3->tail;
+ $$ = $1;
+ }
+ ;
+
+redirpool : /* empty */ { $$ = NULL; }
+ | ARROW redirspec {
+ $$ = calloc(1, sizeof(struct redirection));
+ if ($$ == NULL)
+ err(1, "redirection: calloc");
+ $$->host = $2;
+ $$->rport.a = $$->rport.b = $$->rport.t = 0;
+ }
+ | ARROW redirspec PORT portstar {
+ $$ = calloc(1, sizeof(struct redirection));
+ if ($$ == NULL)
+ err(1, "redirection: calloc");
+ $$->host = $2;
+ $$->rport = $4;
+ }
+ ;
+
+hashkey : /* empty */
+ {
+ $$ = calloc(1, sizeof(struct pf_poolhashkey));
+ if ($$ == NULL)
+ err(1, "hashkey: calloc");
+ $$->key32[0] = arc4random();
+ $$->key32[1] = arc4random();
+ $$->key32[2] = arc4random();
+ $$->key32[3] = arc4random();
+ }
+ | string
+ {
+ if (!strncmp($1, "0x", 2)) {
+ if (strlen($1) != 34) {
+ free($1);
+ yyerror("hex key must be 128 bits "
+ "(32 hex digits) long");
+ YYERROR;
+ }
+ $$ = calloc(1, sizeof(struct pf_poolhashkey));
+ if ($$ == NULL)
+ err(1, "hashkey: calloc");
+
+ if (sscanf($1, "0x%8x%8x%8x%8x",
+ &$$->key32[0], &$$->key32[1],
+ &$$->key32[2], &$$->key32[3]) != 4) {
+ free($$);
+ free($1);
+ yyerror("invalid hex key");
+ YYERROR;
+ }
+ } else {
+ MD5_CTX context;
+
+ $$ = calloc(1, sizeof(struct pf_poolhashkey));
+ if ($$ == NULL)
+ err(1, "hashkey: calloc");
+ MD5Init(&context);
+ MD5Update(&context, (unsigned char *)$1,
+ strlen($1));
+ MD5Final((unsigned char *)$$, &context);
+ HTONL($$->key32[0]);
+ HTONL($$->key32[1]);
+ HTONL($$->key32[2]);
+ HTONL($$->key32[3]);
+ }
+ free($1);
+ }
+ ;
+
+pool_opts : { bzero(&pool_opts, sizeof pool_opts); }
+ pool_opts_l
+ { $$ = pool_opts; }
+ | /* empty */ {
+ bzero(&pool_opts, sizeof pool_opts);
+ $$ = pool_opts;
+ }
+ ;
+
+pool_opts_l : pool_opts_l pool_opt
+ | pool_opt
+ ;
+
+pool_opt : BITMASK {
+ if (pool_opts.type) {
+ yyerror("pool type cannot be redefined");
+ YYERROR;
+ }
+ pool_opts.type = PF_POOL_BITMASK;
+ }
+ | RANDOM {
+ if (pool_opts.type) {
+ yyerror("pool type cannot be redefined");
+ YYERROR;
+ }
+ pool_opts.type = PF_POOL_RANDOM;
+ }
+ | SOURCEHASH hashkey {
+ if (pool_opts.type) {
+ yyerror("pool type cannot be redefined");
+ YYERROR;
+ }
+ pool_opts.type = PF_POOL_SRCHASH;
+ pool_opts.key = $2;
+ }
+ | ROUNDROBIN {
+ if (pool_opts.type) {
+ yyerror("pool type cannot be redefined");
+ YYERROR;
+ }
+ pool_opts.type = PF_POOL_ROUNDROBIN;
+ }
+ | STATICPORT {
+ if (pool_opts.staticport) {
+ yyerror("static-port cannot be redefined");
+ YYERROR;
+ }
+ pool_opts.staticport = 1;
+ }
+ | STICKYADDRESS {
+ if (filter_opts.marker & POM_STICKYADDRESS) {
+ yyerror("sticky-address cannot be redefined");
+ YYERROR;
+ }
+ pool_opts.marker |= POM_STICKYADDRESS;
+ pool_opts.opts |= PF_POOL_STICKYADDR;
+ }
+ ;
+
+redirection : /* empty */ { $$ = NULL; }
+ | ARROW host {
+ $$ = calloc(1, sizeof(struct redirection));
+ if ($$ == NULL)
+ err(1, "redirection: calloc");
+ $$->host = $2;
+ $$->rport.a = $$->rport.b = $$->rport.t = 0;
+ }
+ | ARROW host PORT portstar {
+ $$ = calloc(1, sizeof(struct redirection));
+ if ($$ == NULL)
+ err(1, "redirection: calloc");
+ $$->host = $2;
+ $$->rport = $4;
+ }
+ ;
+
+natpasslog : /* empty */ { $$.b1 = $$.b2 = 0; $$.w2 = 0; }
+ | PASS { $$.b1 = 1; $$.b2 = 0; $$.w2 = 0; }
+ | PASS log { $$.b1 = 1; $$.b2 = $2.log; $$.w2 = $2.logif; }
+ | log { $$.b1 = 0; $$.b2 = $1.log; $$.w2 = $1.logif; }
+ ;
+
+nataction : no NAT natpasslog {
+ if ($1 && $3.b1) {
+ yyerror("\"pass\" not valid with \"no\"");
+ YYERROR;
+ }
+ if ($1)
+ $$.b1 = PF_NONAT;
+ else
+ $$.b1 = PF_NAT;
+ $$.b2 = $3.b1;
+ $$.w = $3.b2;
+ $$.w2 = $3.w2;
+ }
+ | no RDR natpasslog {
+ if ($1 && $3.b1) {
+ yyerror("\"pass\" not valid with \"no\"");
+ YYERROR;
+ }
+ if ($1)
+ $$.b1 = PF_NORDR;
+ else
+ $$.b1 = PF_RDR;
+ $$.b2 = $3.b1;
+ $$.w = $3.b2;
+ $$.w2 = $3.w2;
+ }
+ ;
+
+natrule : nataction interface af proto fromto tag tagged rtable
+ redirpool pool_opts
+ {
+ struct pf_rule r;
+
+ if (check_rulestate(PFCTL_STATE_NAT))
+ YYERROR;
+
+ memset(&r, 0, sizeof(r));
+
+ r.action = $1.b1;
+ r.natpass = $1.b2;
+ r.log = $1.w;
+ r.logif = $1.w2;
+ r.af = $3;
+
+ if (!r.af) {
+ if ($5.src.host && $5.src.host->af &&
+ !$5.src.host->ifindex)
+ r.af = $5.src.host->af;
+ else if ($5.dst.host && $5.dst.host->af &&
+ !$5.dst.host->ifindex)
+ r.af = $5.dst.host->af;
+ }
+
+ if ($6 != NULL)
+ if (strlcpy(r.tagname, $6, PF_TAG_NAME_SIZE) >=
+ PF_TAG_NAME_SIZE) {
+ yyerror("tag too long, max %u chars",
+ PF_TAG_NAME_SIZE - 1);
+ YYERROR;
+ }
+
+ if ($7.name)
+ if (strlcpy(r.match_tagname, $7.name,
+ PF_TAG_NAME_SIZE) >= PF_TAG_NAME_SIZE) {
+ yyerror("tag too long, max %u chars",
+ PF_TAG_NAME_SIZE - 1);
+ YYERROR;
+ }
+ r.match_tag_not = $7.neg;
+ r.rtableid = $8;
+
+ if (r.action == PF_NONAT || r.action == PF_NORDR) {
+ if ($9 != NULL) {
+ yyerror("translation rule with 'no' "
+ "does not need '->'");
+ YYERROR;
+ }
+ } else {
+ if ($9 == NULL || $9->host == NULL) {
+ yyerror("translation rule requires '-> "
+ "address'");
+ YYERROR;
+ }
+ if (!r.af && ! $9->host->ifindex)
+ r.af = $9->host->af;
+
+ remove_invalid_hosts(&$9->host, &r.af);
+ if (invalid_redirect($9->host, r.af))
+ YYERROR;
+ if (check_netmask($9->host, r.af))
+ YYERROR;
+
+ r.rpool.proxy_port[0] = ntohs($9->rport.a);
+
+ switch (r.action) {
+ case PF_RDR:
+ if (!$9->rport.b && $9->rport.t &&
+ $5.dst.port != NULL) {
+ r.rpool.proxy_port[1] =
+ ntohs($9->rport.a) +
+ (ntohs(
+ $5.dst.port->port[1]) -
+ ntohs(
+ $5.dst.port->port[0]));
+ } else
+ r.rpool.proxy_port[1] =
+ ntohs($9->rport.b);
+ break;
+ case PF_NAT:
+ r.rpool.proxy_port[1] =
+ ntohs($9->rport.b);
+ if (!r.rpool.proxy_port[0] &&
+ !r.rpool.proxy_port[1]) {
+ r.rpool.proxy_port[0] =
+ PF_NAT_PROXY_PORT_LOW;
+ r.rpool.proxy_port[1] =
+ PF_NAT_PROXY_PORT_HIGH;
+ } else if (!r.rpool.proxy_port[1])
+ r.rpool.proxy_port[1] =
+ r.rpool.proxy_port[0];
+ break;
+ default:
+ break;
+ }
+
+ r.rpool.opts = $10.type;
+ if ((r.rpool.opts & PF_POOL_TYPEMASK) ==
+ PF_POOL_NONE && ($9->host->next != NULL ||
+ $9->host->addr.type == PF_ADDR_TABLE ||
+ DYNIF_MULTIADDR($9->host->addr)))
+ r.rpool.opts = PF_POOL_ROUNDROBIN;
+ if ((r.rpool.opts & PF_POOL_TYPEMASK) !=
+ PF_POOL_ROUNDROBIN &&
+ disallow_table($9->host, "tables are only "
+ "supported in round-robin redirection "
+ "pools"))
+ YYERROR;
+ if ((r.rpool.opts & PF_POOL_TYPEMASK) !=
+ PF_POOL_ROUNDROBIN &&
+ disallow_alias($9->host, "interface (%s) "
+ "is only supported in round-robin "
+ "redirection pools"))
+ YYERROR;
+ if ($9->host->next != NULL) {
+ if ((r.rpool.opts & PF_POOL_TYPEMASK) !=
+ PF_POOL_ROUNDROBIN) {
+ yyerror("only round-robin "
+ "valid for multiple "
+ "redirection addresses");
+ YYERROR;
+ }
+ }
+ }
+
+ if ($10.key != NULL)
+ memcpy(&r.rpool.key, $10.key,
+ sizeof(struct pf_poolhashkey));
+
+ if ($10.opts)
+ r.rpool.opts |= $10.opts;
+
+ if ($10.staticport) {
+ if (r.action != PF_NAT) {
+ yyerror("the 'static-port' option is "
+ "only valid with nat rules");
+ YYERROR;
+ }
+ if (r.rpool.proxy_port[0] !=
+ PF_NAT_PROXY_PORT_LOW &&
+ r.rpool.proxy_port[1] !=
+ PF_NAT_PROXY_PORT_HIGH) {
+ yyerror("the 'static-port' option can't"
+ " be used when specifying a port"
+ " range");
+ YYERROR;
+ }
+ r.rpool.proxy_port[0] = 0;
+ r.rpool.proxy_port[1] = 0;
+ }
+
+ expand_rule(&r, $2, $9 == NULL ? NULL : $9->host, $4,
+ $5.src_os, $5.src.host, $5.src.port, $5.dst.host,
+ $5.dst.port, 0, 0, 0, "");
+ free($9);
+ }
+ ;
+
+binatrule : no BINAT natpasslog interface af proto FROM host toipspec tag
+ tagged rtable redirection
+ {
+ struct pf_rule binat;
+ struct pf_pooladdr *pa;
+
+ if (check_rulestate(PFCTL_STATE_NAT))
+ YYERROR;
+ if (disallow_urpf_failed($9, "\"urpf-failed\" is not "
+ "permitted as a binat destination"))
+ YYERROR;
+
+ memset(&binat, 0, sizeof(binat));
+
+ if ($1 && $3.b1) {
+ yyerror("\"pass\" not valid with \"no\"");
+ YYERROR;
+ }
+ if ($1)
+ binat.action = PF_NOBINAT;
+ else
+ binat.action = PF_BINAT;
+ binat.natpass = $3.b1;
+ binat.log = $3.b2;
+ binat.logif = $3.w2;
+ binat.af = $5;
+ if (!binat.af && $8 != NULL && $8->af)
+ binat.af = $8->af;
+ if (!binat.af && $9 != NULL && $9->af)
+ binat.af = $9->af;
+
+ if (!binat.af && $13 != NULL && $13->host)
+ binat.af = $13->host->af;
+ if (!binat.af) {
+ yyerror("address family (inet/inet6) "
+ "undefined");
+ YYERROR;
+ }
+
+ if ($4 != NULL) {
+ memcpy(binat.ifname, $4->ifname,
+ sizeof(binat.ifname));
+ binat.ifnot = $4->not;
+ free($4);
+ }
+
+ if ($10 != NULL)
+ if (strlcpy(binat.tagname, $10,
+ PF_TAG_NAME_SIZE) >= PF_TAG_NAME_SIZE) {
+ yyerror("tag too long, max %u chars",
+ PF_TAG_NAME_SIZE - 1);
+ YYERROR;
+ }
+ if ($11.name)
+ if (strlcpy(binat.match_tagname, $11.name,
+ PF_TAG_NAME_SIZE) >= PF_TAG_NAME_SIZE) {
+ yyerror("tag too long, max %u chars",
+ PF_TAG_NAME_SIZE - 1);
+ YYERROR;
+ }
+ binat.match_tag_not = $11.neg;
+ binat.rtableid = $12;
+
+ if ($6 != NULL) {
+ binat.proto = $6->proto;
+ free($6);
+ }
+
+ if ($8 != NULL && disallow_table($8, "invalid use of "
+ "table <%s> as the source address of a binat rule"))
+ YYERROR;
+ if ($8 != NULL && disallow_alias($8, "invalid use of "
+ "interface (%s) as the source address of a binat "
+ "rule"))
+ YYERROR;
+ if ($13 != NULL && $13->host != NULL && disallow_table(
+ $13->host, "invalid use of table <%s> as the "
+ "redirect address of a binat rule"))
+ YYERROR;
+ if ($13 != NULL && $13->host != NULL && disallow_alias(
+ $13->host, "invalid use of interface (%s) as the "
+ "redirect address of a binat rule"))
+ YYERROR;
+
+ if ($8 != NULL) {
+ if ($8->next) {
+ yyerror("multiple binat ip addresses");
+ YYERROR;
+ }
+ if ($8->addr.type == PF_ADDR_DYNIFTL)
+ $8->af = binat.af;
+ if ($8->af != binat.af) {
+ yyerror("binat ip versions must match");
+ YYERROR;
+ }
+ if (check_netmask($8, binat.af))
+ YYERROR;
+ memcpy(&binat.src.addr, &$8->addr,
+ sizeof(binat.src.addr));
+ free($8);
+ }
+ if ($9 != NULL) {
+ if ($9->next) {
+ yyerror("multiple binat ip addresses");
+ YYERROR;
+ }
+ if ($9->af != binat.af && $9->af) {
+ yyerror("binat ip versions must match");
+ YYERROR;
+ }
+ if (check_netmask($9, binat.af))
+ YYERROR;
+ memcpy(&binat.dst.addr, &$9->addr,
+ sizeof(binat.dst.addr));
+ binat.dst.neg = $9->not;
+ free($9);
+ }
+
+ if (binat.action == PF_NOBINAT) {
+ if ($13 != NULL) {
+ yyerror("'no binat' rule does not need"
+ " '->'");
+ YYERROR;
+ }
+ } else {
+ if ($13 == NULL || $13->host == NULL) {
+ yyerror("'binat' rule requires"
+ " '-> address'");
+ YYERROR;
+ }
+
+ remove_invalid_hosts(&$13->host, &binat.af);
+ if (invalid_redirect($13->host, binat.af))
+ YYERROR;
+ if ($13->host->next != NULL) {
+ yyerror("binat rule must redirect to "
+ "a single address");
+ YYERROR;
+ }
+ if (check_netmask($13->host, binat.af))
+ YYERROR;
+
+ if (!PF_AZERO(&binat.src.addr.v.a.mask,
+ binat.af) &&
+ !PF_AEQ(&binat.src.addr.v.a.mask,
+ &$13->host->addr.v.a.mask, binat.af)) {
+ yyerror("'binat' source mask and "
+ "redirect mask must be the same");
+ YYERROR;
+ }
+
+ TAILQ_INIT(&binat.rpool.list);
+ pa = calloc(1, sizeof(struct pf_pooladdr));
+ if (pa == NULL)
+ err(1, "binat: calloc");
+ pa->addr = $13->host->addr;
+ pa->ifname[0] = 0;
+ TAILQ_INSERT_TAIL(&binat.rpool.list,
+ pa, entries);
+
+ free($13);
+ }
+
+ pfctl_add_rule(pf, &binat, "");
+ }
+ ;
+
+tag : /* empty */ { $$ = NULL; }
+ | TAG STRING { $$ = $2; }
+ ;
+
+tagged : /* empty */ { $$.neg = 0; $$.name = NULL; }
+ | not TAGGED string { $$.neg = $1; $$.name = $3; }
+ ;
+
+rtable : /* empty */ { $$ = -1; }
+ | RTABLE NUMBER {
+ if ($2 < 0 || $2 > rt_tableid_max()) {
+ yyerror("invalid rtable id");
+ YYERROR;
+ }
+ $$ = $2;
+ }
+ ;
+
+route_host : STRING {
+ $$ = calloc(1, sizeof(struct node_host));
+ if ($$ == NULL)
+ err(1, "route_host: calloc");
+ $$->ifname = $1;
+ set_ipmask($$, 128);
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ | '(' STRING host ')' {
+ $$ = $3;
+ $$->ifname = $2;
+ }
+ ;
+
+route_host_list : route_host optnl { $$ = $1; }
+ | route_host_list comma route_host optnl {
+ if ($1->af == 0)
+ $1->af = $3->af;
+ if ($1->af != $3->af) {
+ yyerror("all pool addresses must be in the "
+ "same address family");
+ YYERROR;
+ }
+ $1->tail->next = $3;
+ $1->tail = $3->tail;
+ $$ = $1;
+ }
+ ;
+
+routespec : route_host { $$ = $1; }
+ | '{' optnl route_host_list '}' { $$ = $3; }
+ ;
+
+route : /* empty */ {
+ $$.host = NULL;
+ $$.rt = 0;
+ $$.pool_opts = 0;
+ }
+ | FASTROUTE {
+ $$.host = NULL;
+ $$.rt = PF_FASTROUTE;
+ $$.pool_opts = 0;
+ }
+ | ROUTETO routespec pool_opts {
+ $$.host = $2;
+ $$.rt = PF_ROUTETO;
+ $$.pool_opts = $3.type | $3.opts;
+ if ($3.key != NULL)
+ $$.key = $3.key;
+ }
+ | REPLYTO routespec pool_opts {
+ $$.host = $2;
+ $$.rt = PF_REPLYTO;
+ $$.pool_opts = $3.type | $3.opts;
+ if ($3.key != NULL)
+ $$.key = $3.key;
+ }
+ | DUPTO routespec pool_opts {
+ $$.host = $2;
+ $$.rt = PF_DUPTO;
+ $$.pool_opts = $3.type | $3.opts;
+ if ($3.key != NULL)
+ $$.key = $3.key;
+ }
+ ;
+
+timeout_spec : STRING NUMBER
+ {
+ if (check_rulestate(PFCTL_STATE_OPTION)) {
+ free($1);
+ YYERROR;
+ }
+ if ($2 < 0 || $2 > UINT_MAX) {
+ yyerror("only positive values permitted");
+ YYERROR;
+ }
+ if (pfctl_set_timeout(pf, $1, $2, 0) != 0) {
+ yyerror("unknown timeout %s", $1);
+ free($1);
+ YYERROR;
+ }
+ free($1);
+ }
+ ;
+
+timeout_list : timeout_list comma timeout_spec optnl
+ | timeout_spec optnl
+ ;
+
+limit_spec : STRING NUMBER
+ {
+ if (check_rulestate(PFCTL_STATE_OPTION)) {
+ free($1);
+ YYERROR;
+ }
+ if ($2 < 0 || $2 > UINT_MAX) {
+ yyerror("only positive values permitted");
+ YYERROR;
+ }
+ if (pfctl_set_limit(pf, $1, $2) != 0) {
+ yyerror("unable to set limit %s %u", $1, $2);
+ free($1);
+ YYERROR;
+ }
+ free($1);
+ }
+ ;
+
+limit_list : limit_list comma limit_spec optnl
+ | limit_spec optnl
+ ;
+
+comma : ','
+ | /* empty */
+ ;
+
+yesno : NO { $$ = 0; }
+ | STRING {
+ if (!strcmp($1, "yes"))
+ $$ = 1;
+ else {
+ yyerror("invalid value '%s', expected 'yes' "
+ "or 'no'", $1);
+ free($1);
+ YYERROR;
+ }
+ free($1);
+ }
+ ;
+
+unaryop : '=' { $$ = PF_OP_EQ; }
+ | '!' '=' { $$ = PF_OP_NE; }
+ | '<' '=' { $$ = PF_OP_LE; }
+ | '<' { $$ = PF_OP_LT; }
+ | '>' '=' { $$ = PF_OP_GE; }
+ | '>' { $$ = PF_OP_GT; }
+ ;
+
+%%
+
+int
+yyerror(const char *fmt, ...)
+{
+ va_list ap;
+
+ file->errors++;
+ va_start(ap, fmt);
+ fprintf(stderr, "%s:%d: ", file->name, yylval.lineno);
+ vfprintf(stderr, fmt, ap);
+ fprintf(stderr, "\n");
+ va_end(ap);
+ return (0);
+}
+
+int
+disallow_table(struct node_host *h, const char *fmt)
+{
+ for (; h != NULL; h = h->next)
+ if (h->addr.type == PF_ADDR_TABLE) {
+ yyerror(fmt, h->addr.v.tblname);
+ return (1);
+ }
+ return (0);
+}
+
+int
+disallow_urpf_failed(struct node_host *h, const char *fmt)
+{
+ for (; h != NULL; h = h->next)
+ if (h->addr.type == PF_ADDR_URPFFAILED) {
+ yyerror(fmt);
+ return (1);
+ }
+ return (0);
+}
+
+int
+disallow_alias(struct node_host *h, const char *fmt)
+{
+ for (; h != NULL; h = h->next)
+ if (DYNIF_MULTIADDR(h->addr)) {
+ yyerror(fmt, h->addr.v.tblname);
+ return (1);
+ }
+ return (0);
+}
+
+int
+rule_consistent(struct pf_rule *r, int anchor_call)
+{
+ int problems = 0;
+
+ switch (r->action) {
+ case PF_PASS:
+ case PF_DROP:
+ case PF_SCRUB:
+ case PF_NOSCRUB:
+ problems = filter_consistent(r, anchor_call);
+ break;
+ case PF_NAT:
+ case PF_NONAT:
+ problems = nat_consistent(r);
+ break;
+ case PF_RDR:
+ case PF_NORDR:
+ problems = rdr_consistent(r);
+ break;
+ case PF_BINAT:
+ case PF_NOBINAT:
+ default:
+ break;
+ }
+ return (problems);
+}
+
+int
+filter_consistent(struct pf_rule *r, int anchor_call)
+{
+ int problems = 0;
+
+ if (r->proto != IPPROTO_TCP && r->proto != IPPROTO_UDP &&
+ (r->src.port_op || r->dst.port_op)) {
+ yyerror("port only applies to tcp/udp");
+ problems++;
+ }
+ if (r->proto != IPPROTO_ICMP && r->proto != IPPROTO_ICMPV6 &&
+ (r->type || r->code)) {
+ yyerror("icmp-type/code only applies to icmp");
+ problems++;
+ }
+ if (!r->af && (r->type || r->code)) {
+ yyerror("must indicate address family with icmp-type/code");
+ problems++;
+ }
+ if (r->overload_tblname[0] &&
+ r->max_src_conn == 0 && r->max_src_conn_rate.seconds == 0) {
+ yyerror("'overload' requires 'max-src-conn' "
+ "or 'max-src-conn-rate'");
+ problems++;
+ }
+ if ((r->proto == IPPROTO_ICMP && r->af == AF_INET6) ||
+ (r->proto == IPPROTO_ICMPV6 && r->af == AF_INET)) {
+ yyerror("proto %s doesn't match address family %s",
+ r->proto == IPPROTO_ICMP ? "icmp" : "icmp6",
+ r->af == AF_INET ? "inet" : "inet6");
+ problems++;
+ }
+ if (r->allow_opts && r->action != PF_PASS) {
+ yyerror("allow-opts can only be specified for pass rules");
+ problems++;
+ }
+ if (r->rule_flag & PFRULE_FRAGMENT && (r->src.port_op ||
+ r->dst.port_op || r->flagset || r->type || r->code)) {
+ yyerror("fragments can be filtered only on IP header fields");
+ problems++;
+ }
+ if (r->rule_flag & PFRULE_RETURNRST && r->proto != IPPROTO_TCP) {
+ yyerror("return-rst can only be applied to TCP rules");
+ problems++;
+ }
+ if (r->max_src_nodes && !(r->rule_flag & PFRULE_RULESRCTRACK)) {
+ yyerror("max-src-nodes requires 'source-track rule'");
+ problems++;
+ }
+ if (r->action == PF_DROP && r->keep_state) {
+ yyerror("keep state on block rules doesn't make sense");
+ problems++;
+ }
+ if (r->rule_flag & PFRULE_STATESLOPPY &&
+ (r->keep_state == PF_STATE_MODULATE ||
+ r->keep_state == PF_STATE_SYNPROXY)) {
+ yyerror("sloppy state matching cannot be used with "
+ "synproxy state or modulate state");
+ problems++;
+ }
+ return (-problems);
+}
+
+int
+nat_consistent(struct pf_rule *r)
+{
+ return (0); /* yeah! */
+}
+
+int
+rdr_consistent(struct pf_rule *r)
+{
+ int problems = 0;
+
+ if (r->proto != IPPROTO_TCP && r->proto != IPPROTO_UDP) {
+ if (r->src.port_op) {
+ yyerror("src port only applies to tcp/udp");
+ problems++;
+ }
+ if (r->dst.port_op) {
+ yyerror("dst port only applies to tcp/udp");
+ problems++;
+ }
+ if (r->rpool.proxy_port[0]) {
+ yyerror("rpool port only applies to tcp/udp");
+ problems++;
+ }
+ }
+ if (r->dst.port_op &&
+ r->dst.port_op != PF_OP_EQ && r->dst.port_op != PF_OP_RRG) {
+ yyerror("invalid port operator for rdr destination port");
+ problems++;
+ }
+ return (-problems);
+}
+
+int
+process_tabledef(char *name, struct table_opts *opts)
+{
+ struct pfr_buffer ab;
+ struct node_tinit *ti;
+
+ bzero(&ab, sizeof(ab));
+ ab.pfrb_type = PFRB_ADDRS;
+ SIMPLEQ_FOREACH(ti, &opts->init_nodes, entries) {
+ if (ti->file)
+ if (pfr_buf_load(&ab, ti->file, 0, append_addr)) {
+ if (errno)
+ yyerror("cannot load \"%s\": %s",
+ ti->file, strerror(errno));
+ else
+ yyerror("file \"%s\" contains bad data",
+ ti->file);
+ goto _error;
+ }
+ if (ti->host)
+ if (append_addr_host(&ab, ti->host, 0, 0)) {
+ yyerror("cannot create address buffer: %s",
+ strerror(errno));
+ goto _error;
+ }
+ }
+ if (pf->opts & PF_OPT_VERBOSE)
+ print_tabledef(name, opts->flags, opts->init_addr,
+ &opts->init_nodes);
+ if (!(pf->opts & PF_OPT_NOACTION) &&
+ pfctl_define_table(name, opts->flags, opts->init_addr,
+ pf->anchor->name, &ab, pf->anchor->ruleset.tticket)) {
+ yyerror("cannot define table %s: %s", name,
+ pfr_strerror(errno));
+ goto _error;
+ }
+ pf->tdirty = 1;
+ pfr_buf_clear(&ab);
+ return (0);
+_error:
+ pfr_buf_clear(&ab);
+ return (-1);
+}
+
+struct keywords {
+ const char *k_name;
+ int k_val;
+};
+
+/* macro gore, but you should've seen the prior indentation nightmare... */
+
+#define FREE_LIST(T,r) \
+ do { \
+ T *p, *node = r; \
+ while (node != NULL) { \
+ p = node; \
+ node = node->next; \
+ free(p); \
+ } \
+ } while (0)
+
+#define LOOP_THROUGH(T,n,r,C) \
+ do { \
+ T *n; \
+ if (r == NULL) { \
+ r = calloc(1, sizeof(T)); \
+ if (r == NULL) \
+ err(1, "LOOP: calloc"); \
+ r->next = NULL; \
+ } \
+ n = r; \
+ while (n != NULL) { \
+ do { \
+ C; \
+ } while (0); \
+ n = n->next; \
+ } \
+ } while (0)
+
+void
+expand_label_str(char *label, size_t len, const char *srch, const char *repl)
+{
+ char *tmp;
+ char *p, *q;
+
+ if ((tmp = calloc(1, len)) == NULL)
+ err(1, "expand_label_str: calloc");
+ p = q = label;
+ while ((q = strstr(p, srch)) != NULL) {
+ *q = '\0';
+ if ((strlcat(tmp, p, len) >= len) ||
+ (strlcat(tmp, repl, len) >= len))
+ errx(1, "expand_label: label too long");
+ q += strlen(srch);
+ p = q;
+ }
+ if (strlcat(tmp, p, len) >= len)
+ errx(1, "expand_label: label too long");
+ strlcpy(label, tmp, len); /* always fits */
+ free(tmp);
+}
+
+void
+expand_label_if(const char *name, char *label, size_t len, const char *ifname)
+{
+ if (strstr(label, name) != NULL) {
+ if (!*ifname)
+ expand_label_str(label, len, name, "any");
+ else
+ expand_label_str(label, len, name, ifname);
+ }
+}
+
+void
+expand_label_addr(const char *name, char *label, size_t len, sa_family_t af,
+ struct node_host *h)
+{
+ char tmp[64], tmp_not[66];
+
+ if (strstr(label, name) != NULL) {
+ switch (h->addr.type) {
+ case PF_ADDR_DYNIFTL:
+ snprintf(tmp, sizeof(tmp), "(%s)", h->addr.v.ifname);
+ break;
+ case PF_ADDR_TABLE:
+ snprintf(tmp, sizeof(tmp), "<%s>", h->addr.v.tblname);
+ break;
+ case PF_ADDR_NOROUTE:
+ snprintf(tmp, sizeof(tmp), "no-route");
+ break;
+ case PF_ADDR_URPFFAILED:
+ snprintf(tmp, sizeof(tmp), "urpf-failed");
+ break;
+ case PF_ADDR_ADDRMASK:
+ if (!af || (PF_AZERO(&h->addr.v.a.addr, af) &&
+ PF_AZERO(&h->addr.v.a.mask, af)))
+ snprintf(tmp, sizeof(tmp), "any");
+ else {
+ char a[48];
+ int bits;
+
+ if (inet_ntop(af, &h->addr.v.a.addr, a,
+ sizeof(a)) == NULL)
+ snprintf(tmp, sizeof(tmp), "?");
+ else {
+ bits = unmask(&h->addr.v.a.mask, af);
+ if ((af == AF_INET && bits < 32) ||
+ (af == AF_INET6 && bits < 128))
+ snprintf(tmp, sizeof(tmp),
+ "%s/%d", a, bits);
+ else
+ snprintf(tmp, sizeof(tmp),
+ "%s", a);
+ }
+ }
+ break;
+ default:
+ snprintf(tmp, sizeof(tmp), "?");
+ break;
+ }
+
+ if (h->not) {
+ snprintf(tmp_not, sizeof(tmp_not), "! %s", tmp);
+ expand_label_str(label, len, name, tmp_not);
+ } else
+ expand_label_str(label, len, name, tmp);
+ }
+}
+
+void
+expand_label_port(const char *name, char *label, size_t len,
+ struct node_port *port)
+{
+ char a1[6], a2[6], op[13] = "";
+
+ if (strstr(label, name) != NULL) {
+ snprintf(a1, sizeof(a1), "%u", ntohs(port->port[0]));
+ snprintf(a2, sizeof(a2), "%u", ntohs(port->port[1]));
+ if (!port->op)
+ ;
+ else if (port->op == PF_OP_IRG)
+ snprintf(op, sizeof(op), "%s><%s", a1, a2);
+ else if (port->op == PF_OP_XRG)
+ snprintf(op, sizeof(op), "%s<>%s", a1, a2);
+ else if (port->op == PF_OP_EQ)
+ snprintf(op, sizeof(op), "%s", a1);
+ else if (port->op == PF_OP_NE)
+ snprintf(op, sizeof(op), "!=%s", a1);
+ else if (port->op == PF_OP_LT)
+ snprintf(op, sizeof(op), "<%s", a1);
+ else if (port->op == PF_OP_LE)
+ snprintf(op, sizeof(op), "<=%s", a1);
+ else if (port->op == PF_OP_GT)
+ snprintf(op, sizeof(op), ">%s", a1);
+ else if (port->op == PF_OP_GE)
+ snprintf(op, sizeof(op), ">=%s", a1);
+ expand_label_str(label, len, name, op);
+ }
+}
+
+void
+expand_label_proto(const char *name, char *label, size_t len, u_int8_t proto)
+{
+ struct protoent *pe;
+ char n[4];
+
+ if (strstr(label, name) != NULL) {
+ pe = getprotobynumber(proto);
+ if (pe != NULL)
+ expand_label_str(label, len, name, pe->p_name);
+ else {
+ snprintf(n, sizeof(n), "%u", proto);
+ expand_label_str(label, len, name, n);
+ }
+ }
+}
+
+void
+expand_label_nr(const char *name, char *label, size_t len)
+{
+ char n[11];
+
+ if (strstr(label, name) != NULL) {
+ snprintf(n, sizeof(n), "%u", pf->anchor->match);
+ expand_label_str(label, len, name, n);
+ }
+}
+
+void
+expand_label(char *label, size_t len, const char *ifname, sa_family_t af,
+ struct node_host *src_host, struct node_port *src_port,
+ struct node_host *dst_host, struct node_port *dst_port,
+ u_int8_t proto)
+{
+ expand_label_if("$if", label, len, ifname);
+ expand_label_addr("$srcaddr", label, len, af, src_host);
+ expand_label_addr("$dstaddr", label, len, af, dst_host);
+ expand_label_port("$srcport", label, len, src_port);
+ expand_label_port("$dstport", label, len, dst_port);
+ expand_label_proto("$proto", label, len, proto);
+ expand_label_nr("$nr", label, len);
+}
+
+int
+expand_altq(struct pf_altq *a, struct node_if *interfaces,
+ struct node_queue *nqueues, struct node_queue_bw bwspec,
+ struct node_queue_opt *opts)
+{
+ struct pf_altq pa, pb;
+ char qname[PF_QNAME_SIZE];
+ struct node_queue *n;
+ struct node_queue_bw bw;
+ int errs = 0;
+
+ if ((pf->loadopt & PFCTL_FLAG_ALTQ) == 0) {
+ FREE_LIST(struct node_if, interfaces);
+ FREE_LIST(struct node_queue, nqueues);
+ return (0);
+ }
+
+ LOOP_THROUGH(struct node_if, interface, interfaces,
+ memcpy(&pa, a, sizeof(struct pf_altq));
+ if (strlcpy(pa.ifname, interface->ifname,
+ sizeof(pa.ifname)) >= sizeof(pa.ifname))
+ errx(1, "expand_altq: strlcpy");
+
+ if (interface->not) {
+ yyerror("altq on ! <interface> is not supported");
+ errs++;
+ } else {
+ if (eval_pfaltq(pf, &pa, &bwspec, opts))
+ errs++;
+ else
+ if (pfctl_add_altq(pf, &pa))
+ errs++;
+
+ if (pf->opts & PF_OPT_VERBOSE) {
+ print_altq(&pf->paltq->altq, 0,
+ &bwspec, opts);
+ if (nqueues && nqueues->tail) {
+ printf("queue { ");
+ LOOP_THROUGH(struct node_queue, queue,
+ nqueues,
+ printf("%s ",
+ queue->queue);
+ );
+ printf("}");
+ }
+ printf("\n");
+ }
+
+ if (pa.scheduler == ALTQT_CBQ ||
+ pa.scheduler == ALTQT_HFSC) {
+ /* now create a root queue */
+ memset(&pb, 0, sizeof(struct pf_altq));
+ if (strlcpy(qname, "root_", sizeof(qname)) >=
+ sizeof(qname))
+ errx(1, "expand_altq: strlcpy");
+ if (strlcat(qname, interface->ifname,
+ sizeof(qname)) >= sizeof(qname))
+ errx(1, "expand_altq: strlcat");
+ if (strlcpy(pb.qname, qname,
+ sizeof(pb.qname)) >= sizeof(pb.qname))
+ errx(1, "expand_altq: strlcpy");
+ if (strlcpy(pb.ifname, interface->ifname,
+ sizeof(pb.ifname)) >= sizeof(pb.ifname))
+ errx(1, "expand_altq: strlcpy");
+ pb.qlimit = pa.qlimit;
+ pb.scheduler = pa.scheduler;
+ bw.bw_absolute = pa.ifbandwidth;
+ bw.bw_percent = 0;
+ if (eval_pfqueue(pf, &pb, &bw, opts))
+ errs++;
+ else
+ if (pfctl_add_altq(pf, &pb))
+ errs++;
+ }
+
+ LOOP_THROUGH(struct node_queue, queue, nqueues,
+ n = calloc(1, sizeof(struct node_queue));
+ if (n == NULL)
+ err(1, "expand_altq: calloc");
+ if (pa.scheduler == ALTQT_CBQ ||
+ pa.scheduler == ALTQT_HFSC)
+ if (strlcpy(n->parent, qname,
+ sizeof(n->parent)) >=
+ sizeof(n->parent))
+ errx(1, "expand_altq: strlcpy");
+ if (strlcpy(n->queue, queue->queue,
+ sizeof(n->queue)) >= sizeof(n->queue))
+ errx(1, "expand_altq: strlcpy");
+ if (strlcpy(n->ifname, interface->ifname,
+ sizeof(n->ifname)) >= sizeof(n->ifname))
+ errx(1, "expand_altq: strlcpy");
+ n->scheduler = pa.scheduler;
+ n->next = NULL;
+ n->tail = n;
+ if (queues == NULL)
+ queues = n;
+ else {
+ queues->tail->next = n;
+ queues->tail = n;
+ }
+ );
+ }
+ );
+ FREE_LIST(struct node_if, interfaces);
+ FREE_LIST(struct node_queue, nqueues);
+
+ return (errs);
+}
+
+int
+expand_queue(struct pf_altq *a, struct node_if *interfaces,
+ struct node_queue *nqueues, struct node_queue_bw bwspec,
+ struct node_queue_opt *opts)
+{
+ struct node_queue *n, *nq;
+ struct pf_altq pa;
+ u_int8_t found = 0;
+ u_int8_t errs = 0;
+
+ if ((pf->loadopt & PFCTL_FLAG_ALTQ) == 0) {
+ FREE_LIST(struct node_queue, nqueues);
+ return (0);
+ }
+
+ if (queues == NULL) {
+ yyerror("queue %s has no parent", a->qname);
+ FREE_LIST(struct node_queue, nqueues);
+ return (1);
+ }
+
+ LOOP_THROUGH(struct node_if, interface, interfaces,
+ LOOP_THROUGH(struct node_queue, tqueue, queues,
+ if (!strncmp(a->qname, tqueue->queue, PF_QNAME_SIZE) &&
+ (interface->ifname[0] == 0 ||
+ (!interface->not && !strncmp(interface->ifname,
+ tqueue->ifname, IFNAMSIZ)) ||
+ (interface->not && strncmp(interface->ifname,
+ tqueue->ifname, IFNAMSIZ)))) {
+ /* found ourself in queues */
+ found++;
+
+ memcpy(&pa, a, sizeof(struct pf_altq));
+
+ if (pa.scheduler != ALTQT_NONE &&
+ pa.scheduler != tqueue->scheduler) {
+ yyerror("exactly one scheduler type "
+ "per interface allowed");
+ return (1);
+ }
+ pa.scheduler = tqueue->scheduler;
+
+ /* scheduler dependent error checking */
+ switch (pa.scheduler) {
+ case ALTQT_PRIQ:
+ if (nqueues != NULL) {
+ yyerror("priq queues cannot "
+ "have child queues");
+ return (1);
+ }
+ if (bwspec.bw_absolute > 0 ||
+ bwspec.bw_percent < 100) {
+ yyerror("priq doesn't take "
+ "bandwidth");
+ return (1);
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (strlcpy(pa.ifname, tqueue->ifname,
+ sizeof(pa.ifname)) >= sizeof(pa.ifname))
+ errx(1, "expand_queue: strlcpy");
+ if (strlcpy(pa.parent, tqueue->parent,
+ sizeof(pa.parent)) >= sizeof(pa.parent))
+ errx(1, "expand_queue: strlcpy");
+
+ if (eval_pfqueue(pf, &pa, &bwspec, opts))
+ errs++;
+ else
+ if (pfctl_add_altq(pf, &pa))
+ errs++;
+
+ for (nq = nqueues; nq != NULL; nq = nq->next) {
+ if (!strcmp(a->qname, nq->queue)) {
+ yyerror("queue cannot have "
+ "itself as child");
+ errs++;
+ continue;
+ }
+ n = calloc(1,
+ sizeof(struct node_queue));
+ if (n == NULL)
+ err(1, "expand_queue: calloc");
+ if (strlcpy(n->parent, a->qname,
+ sizeof(n->parent)) >=
+ sizeof(n->parent))
+ errx(1, "expand_queue strlcpy");
+ if (strlcpy(n->queue, nq->queue,
+ sizeof(n->queue)) >=
+ sizeof(n->queue))
+ errx(1, "expand_queue strlcpy");
+ if (strlcpy(n->ifname, tqueue->ifname,
+ sizeof(n->ifname)) >=
+ sizeof(n->ifname))
+ errx(1, "expand_queue strlcpy");
+ n->scheduler = tqueue->scheduler;
+ n->next = NULL;
+ n->tail = n;
+ if (queues == NULL)
+ queues = n;
+ else {
+ queues->tail->next = n;
+ queues->tail = n;
+ }
+ }
+ if ((pf->opts & PF_OPT_VERBOSE) && (
+ (found == 1 && interface->ifname[0] == 0) ||
+ (found > 0 && interface->ifname[0] != 0))) {
+ print_queue(&pf->paltq->altq, 0,
+ &bwspec, interface->ifname[0] != 0,
+ opts);
+ if (nqueues && nqueues->tail) {
+ printf("{ ");
+ LOOP_THROUGH(struct node_queue,
+ queue, nqueues,
+ printf("%s ",
+ queue->queue);
+ );
+ printf("}");
+ }
+ printf("\n");
+ }
+ }
+ );
+ );
+
+ FREE_LIST(struct node_queue, nqueues);
+ FREE_LIST(struct node_if, interfaces);
+
+ if (!found) {
+ yyerror("queue %s has no parent", a->qname);
+ errs++;
+ }
+
+ if (errs)
+ return (1);
+ else
+ return (0);
+}
+
+void
+expand_rule(struct pf_rule *r,
+ struct node_if *interfaces, struct node_host *rpool_hosts,
+ struct node_proto *protos, struct node_os *src_oses,
+ struct node_host *src_hosts, struct node_port *src_ports,
+ struct node_host *dst_hosts, struct node_port *dst_ports,
+ struct node_uid *uids, struct node_gid *gids, struct node_icmp *icmp_types,
+ const char *anchor_call)
+{
+ sa_family_t af = r->af;
+ int added = 0, error = 0;
+ char ifname[IF_NAMESIZE];
+ char label[PF_RULE_LABEL_SIZE];
+ char tagname[PF_TAG_NAME_SIZE];
+ char match_tagname[PF_TAG_NAME_SIZE];
+ struct pf_pooladdr *pa;
+ struct node_host *h;
+ u_int8_t flags, flagset, keep_state;
+
+ if (strlcpy(label, r->label, sizeof(label)) >= sizeof(label))
+ errx(1, "expand_rule: strlcpy");
+ if (strlcpy(tagname, r->tagname, sizeof(tagname)) >= sizeof(tagname))
+ errx(1, "expand_rule: strlcpy");
+ if (strlcpy(match_tagname, r->match_tagname, sizeof(match_tagname)) >=
+ sizeof(match_tagname))
+ errx(1, "expand_rule: strlcpy");
+ flags = r->flags;
+ flagset = r->flagset;
+ keep_state = r->keep_state;
+
+ LOOP_THROUGH(struct node_if, interface, interfaces,
+ LOOP_THROUGH(struct node_proto, proto, protos,
+ LOOP_THROUGH(struct node_icmp, icmp_type, icmp_types,
+ LOOP_THROUGH(struct node_host, src_host, src_hosts,
+ LOOP_THROUGH(struct node_port, src_port, src_ports,
+ LOOP_THROUGH(struct node_os, src_os, src_oses,
+ LOOP_THROUGH(struct node_host, dst_host, dst_hosts,
+ LOOP_THROUGH(struct node_port, dst_port, dst_ports,
+ LOOP_THROUGH(struct node_uid, uid, uids,
+ LOOP_THROUGH(struct node_gid, gid, gids,
+
+ r->af = af;
+ /* for link-local IPv6 address, interface must match up */
+ if ((r->af && src_host->af && r->af != src_host->af) ||
+ (r->af && dst_host->af && r->af != dst_host->af) ||
+ (src_host->af && dst_host->af &&
+ src_host->af != dst_host->af) ||
+ (src_host->ifindex && dst_host->ifindex &&
+ src_host->ifindex != dst_host->ifindex) ||
+ (src_host->ifindex && *interface->ifname &&
+ src_host->ifindex != if_nametoindex(interface->ifname)) ||
+ (dst_host->ifindex && *interface->ifname &&
+ dst_host->ifindex != if_nametoindex(interface->ifname)))
+ continue;
+ if (!r->af && src_host->af)
+ r->af = src_host->af;
+ else if (!r->af && dst_host->af)
+ r->af = dst_host->af;
+
+ if (*interface->ifname)
+ strlcpy(r->ifname, interface->ifname,
+ sizeof(r->ifname));
+ else if (if_indextoname(src_host->ifindex, ifname))
+ strlcpy(r->ifname, ifname, sizeof(r->ifname));
+ else if (if_indextoname(dst_host->ifindex, ifname))
+ strlcpy(r->ifname, ifname, sizeof(r->ifname));
+ else
+ memset(r->ifname, '\0', sizeof(r->ifname));
+
+ if (strlcpy(r->label, label, sizeof(r->label)) >=
+ sizeof(r->label))
+ errx(1, "expand_rule: strlcpy");
+ if (strlcpy(r->tagname, tagname, sizeof(r->tagname)) >=
+ sizeof(r->tagname))
+ errx(1, "expand_rule: strlcpy");
+ if (strlcpy(r->match_tagname, match_tagname,
+ sizeof(r->match_tagname)) >= sizeof(r->match_tagname))
+ errx(1, "expand_rule: strlcpy");
+ expand_label(r->label, PF_RULE_LABEL_SIZE, r->ifname, r->af,
+ src_host, src_port, dst_host, dst_port, proto->proto);
+ expand_label(r->tagname, PF_TAG_NAME_SIZE, r->ifname, r->af,
+ src_host, src_port, dst_host, dst_port, proto->proto);
+ expand_label(r->match_tagname, PF_TAG_NAME_SIZE, r->ifname,
+ r->af, src_host, src_port, dst_host, dst_port,
+ proto->proto);
+
+ error += check_netmask(src_host, r->af);
+ error += check_netmask(dst_host, r->af);
+
+ r->ifnot = interface->not;
+ r->proto = proto->proto;
+ r->src.addr = src_host->addr;
+ r->src.neg = src_host->not;
+ r->src.port[0] = src_port->port[0];
+ r->src.port[1] = src_port->port[1];
+ r->src.port_op = src_port->op;
+ r->dst.addr = dst_host->addr;
+ r->dst.neg = dst_host->not;
+ r->dst.port[0] = dst_port->port[0];
+ r->dst.port[1] = dst_port->port[1];
+ r->dst.port_op = dst_port->op;
+ r->uid.op = uid->op;
+ r->uid.uid[0] = uid->uid[0];
+ r->uid.uid[1] = uid->uid[1];
+ r->gid.op = gid->op;
+ r->gid.gid[0] = gid->gid[0];
+ r->gid.gid[1] = gid->gid[1];
+ r->type = icmp_type->type;
+ r->code = icmp_type->code;
+
+ if ((keep_state == PF_STATE_MODULATE ||
+ keep_state == PF_STATE_SYNPROXY) &&
+ r->proto && r->proto != IPPROTO_TCP)
+ r->keep_state = PF_STATE_NORMAL;
+ else
+ r->keep_state = keep_state;
+
+ if (r->proto && r->proto != IPPROTO_TCP) {
+ r->flags = 0;
+ r->flagset = 0;
+ } else {
+ r->flags = flags;
+ r->flagset = flagset;
+ }
+ if (icmp_type->proto && r->proto != icmp_type->proto) {
+ yyerror("icmp-type mismatch");
+ error++;
+ }
+
+ if (src_os && src_os->os) {
+ r->os_fingerprint = pfctl_get_fingerprint(src_os->os);
+ if ((pf->opts & PF_OPT_VERBOSE2) &&
+ r->os_fingerprint == PF_OSFP_NOMATCH)
+ fprintf(stderr,
+ "warning: unknown '%s' OS fingerprint\n",
+ src_os->os);
+ } else {
+ r->os_fingerprint = PF_OSFP_ANY;
+ }
+
+ TAILQ_INIT(&r->rpool.list);
+ for (h = rpool_hosts; h != NULL; h = h->next) {
+ pa = calloc(1, sizeof(struct pf_pooladdr));
+ if (pa == NULL)
+ err(1, "expand_rule: calloc");
+ pa->addr = h->addr;
+ if (h->ifname != NULL) {
+ if (strlcpy(pa->ifname, h->ifname,
+ sizeof(pa->ifname)) >=
+ sizeof(pa->ifname))
+ errx(1, "expand_rule: strlcpy");
+ } else
+ pa->ifname[0] = 0;
+ TAILQ_INSERT_TAIL(&r->rpool.list, pa, entries);
+ }
+
+ if (rule_consistent(r, anchor_call[0]) < 0 || error)
+ yyerror("skipping rule due to errors");
+ else {
+ r->nr = pf->astack[pf->asd]->match++;
+ pfctl_add_rule(pf, r, anchor_call);
+ added++;
+ }
+
+ ))))))))));
+
+ FREE_LIST(struct node_if, interfaces);
+ FREE_LIST(struct node_proto, protos);
+ FREE_LIST(struct node_host, src_hosts);
+ FREE_LIST(struct node_port, src_ports);
+ FREE_LIST(struct node_os, src_oses);
+ FREE_LIST(struct node_host, dst_hosts);
+ FREE_LIST(struct node_port, dst_ports);
+ FREE_LIST(struct node_uid, uids);
+ FREE_LIST(struct node_gid, gids);
+ FREE_LIST(struct node_icmp, icmp_types);
+ FREE_LIST(struct node_host, rpool_hosts);
+
+ if (!added)
+ yyerror("rule expands to no valid combination");
+}
+
+int
+expand_skip_interface(struct node_if *interfaces)
+{
+ int errs = 0;
+
+ if (!interfaces || (!interfaces->next && !interfaces->not &&
+ !strcmp(interfaces->ifname, "none"))) {
+ if (pf->opts & PF_OPT_VERBOSE)
+ printf("set skip on none\n");
+ errs = pfctl_set_interface_flags(pf, "", PFI_IFLAG_SKIP, 0);
+ return (errs);
+ }
+
+ if (pf->opts & PF_OPT_VERBOSE)
+ printf("set skip on {");
+ LOOP_THROUGH(struct node_if, interface, interfaces,
+ if (pf->opts & PF_OPT_VERBOSE)
+ printf(" %s", interface->ifname);
+ if (interface->not) {
+ yyerror("skip on ! <interface> is not supported");
+ errs++;
+ } else
+ errs += pfctl_set_interface_flags(pf,
+ interface->ifname, PFI_IFLAG_SKIP, 1);
+ );
+ if (pf->opts & PF_OPT_VERBOSE)
+ printf(" }\n");
+
+ FREE_LIST(struct node_if, interfaces);
+
+ if (errs)
+ return (1);
+ else
+ return (0);
+}
+
+#undef FREE_LIST
+#undef LOOP_THROUGH
+
+int
+check_rulestate(int desired_state)
+{
+ if (require_order && (rulestate > desired_state)) {
+ yyerror("Rules must be in order: options, normalization, "
+ "queueing, translation, filtering");
+ return (1);
+ }
+ rulestate = desired_state;
+ return (0);
+}
+
+int
+kw_cmp(const void *k, const void *e)
+{
+ return (strcmp(k, ((const struct keywords *)e)->k_name));
+}
+
+int
+lookup(char *s)
+{
+ /* this has to be sorted always */
+ static const struct keywords keywords[] = {
+ { "all", ALL},
+ { "allow-opts", ALLOWOPTS},
+ { "altq", ALTQ},
+ { "anchor", ANCHOR},
+ { "antispoof", ANTISPOOF},
+ { "any", ANY},
+ { "bandwidth", BANDWIDTH},
+ { "binat", BINAT},
+ { "binat-anchor", BINATANCHOR},
+ { "bitmask", BITMASK},
+ { "block", BLOCK},
+ { "block-policy", BLOCKPOLICY},
+ { "cbq", CBQ},
+ { "code", CODE},
+ { "crop", FRAGCROP},
+ { "debug", DEBUG},
+ { "divert-reply", DIVERTREPLY},
+ { "divert-to", DIVERTTO},
+ { "drop", DROP},
+ { "drop-ovl", FRAGDROP},
+ { "dup-to", DUPTO},
+ { "fastroute", FASTROUTE},
+ { "file", FILENAME},
+ { "fingerprints", FINGERPRINTS},
+ { "flags", FLAGS},
+ { "floating", FLOATING},
+ { "flush", FLUSH},
+ { "for", FOR},
+ { "fragment", FRAGMENT},
+ { "from", FROM},
+ { "global", GLOBAL},
+ { "group", GROUP},
+ { "hfsc", HFSC},
+ { "hostid", HOSTID},
+ { "icmp-type", ICMPTYPE},
+ { "icmp6-type", ICMP6TYPE},
+ { "if-bound", IFBOUND},
+ { "in", IN},
+ { "include", INCLUDE},
+ { "inet", INET},
+ { "inet6", INET6},
+ { "keep", KEEP},
+ { "label", LABEL},
+ { "limit", LIMIT},
+ { "linkshare", LINKSHARE},
+ { "load", LOAD},
+ { "log", LOG},
+ { "loginterface", LOGINTERFACE},
+ { "max", MAXIMUM},
+ { "max-mss", MAXMSS},
+ { "max-src-conn", MAXSRCCONN},
+ { "max-src-conn-rate", MAXSRCCONNRATE},
+ { "max-src-nodes", MAXSRCNODES},
+ { "max-src-states", MAXSRCSTATES},
+ { "min-ttl", MINTTL},
+ { "modulate", MODULATE},
+ { "nat", NAT},
+ { "nat-anchor", NATANCHOR},
+ { "no", NO},
+ { "no-df", NODF},
+ { "no-route", NOROUTE},
+ { "no-sync", NOSYNC},
+ { "on", ON},
+ { "optimization", OPTIMIZATION},
+ { "os", OS},
+ { "out", OUT},
+ { "overload", OVERLOAD},
+ { "pass", PASS},
+ { "port", PORT},
+ { "priority", PRIORITY},
+ { "priq", PRIQ},
+ { "probability", PROBABILITY},
+ { "proto", PROTO},
+ { "qlimit", QLIMIT},
+ { "queue", QUEUE},
+ { "quick", QUICK},
+ { "random", RANDOM},
+ { "random-id", RANDOMID},
+ { "rdr", RDR},
+ { "rdr-anchor", RDRANCHOR},
+ { "realtime", REALTIME},
+ { "reassemble", REASSEMBLE},
+ { "reply-to", REPLYTO},
+ { "require-order", REQUIREORDER},
+ { "return", RETURN},
+ { "return-icmp", RETURNICMP},
+ { "return-icmp6", RETURNICMP6},
+ { "return-rst", RETURNRST},
+ { "round-robin", ROUNDROBIN},
+ { "route", ROUTE},
+ { "route-to", ROUTETO},
+ { "rtable", RTABLE},
+ { "rule", RULE},
+ { "ruleset-optimization", RULESET_OPTIMIZATION},
+ { "scrub", SCRUB},
+ { "set", SET},
+ { "set-tos", SETTOS},
+ { "skip", SKIP},
+ { "sloppy", SLOPPY},
+ { "source-hash", SOURCEHASH},
+ { "source-track", SOURCETRACK},
+ { "state", STATE},
+ { "state-defaults", STATEDEFAULTS},
+ { "state-policy", STATEPOLICY},
+ { "static-port", STATICPORT},
+ { "sticky-address", STICKYADDRESS},
+ { "synproxy", SYNPROXY},
+ { "table", TABLE},
+ { "tag", TAG},
+ { "tagged", TAGGED},
+ { "tbrsize", TBRSIZE},
+ { "timeout", TIMEOUT},
+ { "to", TO},
+ { "tos", TOS},
+ { "ttl", TTL},
+ { "upperlimit", UPPERLIMIT},
+ { "urpf-failed", URPFFAILED},
+ { "user", USER},
+ };
+ const struct keywords *p;
+
+ p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
+ sizeof(keywords[0]), kw_cmp);
+
+ if (p) {
+ if (debug > 1)
+ fprintf(stderr, "%s: %d\n", s, p->k_val);
+ return (p->k_val);
+ } else {
+ if (debug > 1)
+ fprintf(stderr, "string: %s\n", s);
+ return (STRING);
+ }
+}
+
+#define MAXPUSHBACK 128
+
+char *parsebuf;
+int parseindex;
+char pushback_buffer[MAXPUSHBACK];
+int pushback_index = 0;
+
+int
+lgetc(int quotec)
+{
+ int c, next;
+
+ if (parsebuf) {
+ /* Read character from the parsebuffer instead of input. */
+ if (parseindex >= 0) {
+ c = parsebuf[parseindex++];
+ if (c != '\0')
+ return (c);
+ parsebuf = NULL;
+ } else
+ parseindex++;
+ }
+
+ if (pushback_index)
+ return (pushback_buffer[--pushback_index]);
+
+ if (quotec) {
+ if ((c = getc(file->stream)) == EOF) {
+ yyerror("reached end of file while parsing quoted string");
+ if (popfile() == EOF)
+ return (EOF);
+ return (quotec);
+ }
+ return (c);
+ }
+
+ while ((c = getc(file->stream)) == '\\') {
+ next = getc(file->stream);
+ if (next != '\n') {
+ c = next;
+ break;
+ }
+ yylval.lineno = file->lineno;
+ file->lineno++;
+ }
+
+ while (c == EOF) {
+ if (popfile() == EOF)
+ return (EOF);
+ c = getc(file->stream);
+ }
+ return (c);
+}
+
+int
+lungetc(int c)
+{
+ if (c == EOF)
+ return (EOF);
+ if (parsebuf) {
+ parseindex--;
+ if (parseindex >= 0)
+ return (c);
+ }
+ if (pushback_index < MAXPUSHBACK-1)
+ return (pushback_buffer[pushback_index++] = c);
+ else
+ return (EOF);
+}
+
+int
+findeol(void)
+{
+ int c;
+
+ parsebuf = NULL;
+
+ /* skip to either EOF or the first real EOL */
+ while (1) {
+ if (pushback_index)
+ c = pushback_buffer[--pushback_index];
+ else
+ c = lgetc(0);
+ if (c == '\n') {
+ file->lineno++;
+ break;
+ }
+ if (c == EOF)
+ break;
+ }
+ return (ERROR);
+}
+
+int
+yylex(void)
+{
+ char buf[8096];
+ char *p, *val;
+ int quotec, next, c;
+ int token;
+
+top:
+ p = buf;
+ while ((c = lgetc(0)) == ' ' || c == '\t')
+ ; /* nothing */
+
+ yylval.lineno = file->lineno;
+ if (c == '#')
+ while ((c = lgetc(0)) != '\n' && c != EOF)
+ ; /* nothing */
+ if (c == '$' && parsebuf == NULL) {
+ while (1) {
+ if ((c = lgetc(0)) == EOF)
+ return (0);
+
+ if (p + 1 >= buf + sizeof(buf) - 1) {
+ yyerror("string too long");
+ return (findeol());
+ }
+ if (isalnum(c) || c == '_') {
+ *p++ = (char)c;
+ continue;
+ }
+ *p = '\0';
+ lungetc(c);
+ break;
+ }
+ val = symget(buf);
+ if (val == NULL) {
+ yyerror("macro '%s' not defined", buf);
+ return (findeol());
+ }
+ parsebuf = val;
+ parseindex = 0;
+ goto top;
+ }
+
+ switch (c) {
+ case '\'':
+ case '"':
+ quotec = c;
+ while (1) {
+ if ((c = lgetc(quotec)) == EOF)
+ return (0);
+ if (c == '\n') {
+ file->lineno++;
+ continue;
+ } else if (c == '\\') {
+ if ((next = lgetc(quotec)) == EOF)
+ return (0);
+ if (next == quotec || c == ' ' || c == '\t')
+ c = next;
+ else if (next == '\n')
+ continue;
+ else
+ lungetc(next);
+ } else if (c == quotec) {
+ *p = '\0';
+ break;
+ }
+ if (p + 1 >= buf + sizeof(buf) - 1) {
+ yyerror("string too long");
+ return (findeol());
+ }
+ *p++ = (char)c;
+ }
+ yylval.v.string = strdup(buf);
+ if (yylval.v.string == NULL)
+ err(1, "yylex: strdup");
+ return (STRING);
+ case '<':
+ next = lgetc(0);
+ if (next == '>') {
+ yylval.v.i = PF_OP_XRG;
+ return (PORTBINARY);
+ }
+ lungetc(next);
+ break;
+ case '>':
+ next = lgetc(0);
+ if (next == '<') {
+ yylval.v.i = PF_OP_IRG;
+ return (PORTBINARY);
+ }
+ lungetc(next);
+ break;
+ case '-':
+ next = lgetc(0);
+ if (next == '>')
+ return (ARROW);
+ lungetc(next);
+ break;
+ }
+
+#define allowed_to_end_number(x) \
+ (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
+
+ if (c == '-' || isdigit(c)) {
+ do {
+ *p++ = c;
+ if ((unsigned)(p-buf) >= sizeof(buf)) {
+ yyerror("string too long");
+ return (findeol());
+ }
+ } while ((c = lgetc(0)) != EOF && isdigit(c));
+ lungetc(c);
+ if (p == buf + 1 && buf[0] == '-')
+ goto nodigits;
+ if (c == EOF || allowed_to_end_number(c)) {
+ const char *errstr = NULL;
+
+ *p = '\0';
+ yylval.v.number = strtonum(buf, LLONG_MIN,
+ LLONG_MAX, &errstr);
+ if (errstr) {
+ yyerror("\"%s\" invalid number: %s",
+ buf, errstr);
+ return (findeol());
+ }
+ return (NUMBER);
+ } else {
+nodigits:
+ while (p > buf + 1)
+ lungetc(*--p);
+ c = *--p;
+ if (c == '-')
+ return (c);
+ }
+ }
+
+#define allowed_in_string(x) \
+ (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
+ x != '{' && x != '}' && x != '<' && x != '>' && \
+ x != '!' && x != '=' && x != '/' && x != '#' && \
+ x != ','))
+
+ if (isalnum(c) || c == ':' || c == '_') {
+ do {
+ *p++ = c;
+ if ((unsigned)(p-buf) >= sizeof(buf)) {
+ yyerror("string too long");
+ return (findeol());
+ }
+ } while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));
+ lungetc(c);
+ *p = '\0';
+ if ((token = lookup(buf)) == STRING)
+ if ((yylval.v.string = strdup(buf)) == NULL)
+ err(1, "yylex: strdup");
+ return (token);
+ }
+ if (c == '\n') {
+ yylval.lineno = file->lineno;
+ file->lineno++;
+ }
+ if (c == EOF)
+ return (0);
+ return (c);
+}
+
+int
+check_file_secrecy(int fd, const char *fname)
+{
+ struct stat st;
+
+ if (fstat(fd, &st)) {
+ warn("cannot stat %s", fname);
+ return (-1);
+ }
+ if (st.st_uid != 0 && st.st_uid != getuid()) {
+ warnx("%s: owner not root or current user", fname);
+ return (-1);
+ }
+ if (st.st_mode & (S_IRWXG | S_IRWXO)) {
+ warnx("%s: group/world readable/writeable", fname);
+ return (-1);
+ }
+ return (0);
+}
+
+struct file *
+pushfile(const char *name, int secret)
+{
+ struct file *nfile;
+
+ if ((nfile = calloc(1, sizeof(struct file))) == NULL ||
+ (nfile->name = strdup(name)) == NULL) {
+ warn("malloc");
+ return (NULL);
+ }
+ if (TAILQ_FIRST(&files) == NULL && strcmp(nfile->name, "-") == 0) {
+ nfile->stream = stdin;
+ free(nfile->name);
+ if ((nfile->name = strdup("stdin")) == NULL) {
+ warn("strdup");
+ free(nfile);
+ return (NULL);
+ }
+ } else if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
+ warn("%s", nfile->name);
+ free(nfile->name);
+ free(nfile);
+ return (NULL);
+ } else if (secret &&
+ check_file_secrecy(fileno(nfile->stream), nfile->name)) {
+ fclose(nfile->stream);
+ free(nfile->name);
+ free(nfile);
+ return (NULL);
+ }
+ nfile->lineno = 1;
+ TAILQ_INSERT_TAIL(&files, nfile, entry);
+ return (nfile);
+}
+
+int
+popfile(void)
+{
+ struct file *prev;
+
+ if ((prev = TAILQ_PREV(file, files, entry)) != NULL) {
+ prev->errors += file->errors;
+ TAILQ_REMOVE(&files, file, entry);
+ fclose(file->stream);
+ free(file->name);
+ free(file);
+ file = prev;
+ return (0);
+ }
+ return (EOF);
+}
+
+int
+parse_config(char *filename, struct pfctl *xpf)
+{
+ int errors = 0;
+ struct sym *sym;
+
+ pf = xpf;
+ errors = 0;
+ rulestate = PFCTL_STATE_NONE;
+ returnicmpdefault = (ICMP_UNREACH << 8) | ICMP_UNREACH_PORT;
+ returnicmp6default =
+ (ICMP6_DST_UNREACH << 8) | ICMP6_DST_UNREACH_NOPORT;
+ blockpolicy = PFRULE_DROP;
+ require_order = 1;
+
+ if ((file = pushfile(filename, 0)) == NULL) {
+ warn("cannot open the main config file!");
+ return (-1);
+ }
+
+ yyparse();
+ errors = file->errors;
+ popfile();
+
+ /* Free macros and check which have not been used. */
+ while ((sym = TAILQ_FIRST(&symhead))) {
+ if ((pf->opts & PF_OPT_VERBOSE2) && !sym->used)
+ fprintf(stderr, "warning: macro '%s' not "
+ "used\n", sym->nam);
+ free(sym->nam);
+ free(sym->val);
+ TAILQ_REMOVE(&symhead, sym, entry);
+ free(sym);
+ }
+
+ return (errors ? -1 : 0);
+}
+
+int
+symset(const char *nam, const char *val, int persist)
+{
+ struct sym *sym;
+
+ for (sym = TAILQ_FIRST(&symhead); sym && strcmp(nam, sym->nam);
+ sym = TAILQ_NEXT(sym, entry))
+ ; /* nothing */
+
+ if (sym != NULL) {
+ if (sym->persist == 1)
+ return (0);
+ else {
+ free(sym->nam);
+ free(sym->val);
+ TAILQ_REMOVE(&symhead, sym, entry);
+ free(sym);
+ }
+ }
+ if ((sym = calloc(1, sizeof(*sym))) == NULL)
+ return (-1);
+
+ sym->nam = strdup(nam);
+ if (sym->nam == NULL) {
+ free(sym);
+ return (-1);
+ }
+ sym->val = strdup(val);
+ if (sym->val == NULL) {
+ free(sym->nam);
+ free(sym);
+ return (-1);
+ }
+ sym->used = 0;
+ sym->persist = persist;
+ TAILQ_INSERT_TAIL(&symhead, sym, entry);
+ return (0);
+}
+
+int
+pfctl_cmdline_symset(char *s)
+{
+ char *sym, *val;
+ int ret;
+
+ if ((val = strrchr(s, '=')) == NULL)
+ return (-1);
+
+ if ((sym = malloc(strlen(s) - strlen(val) + 1)) == NULL)
+ err(1, "pfctl_cmdline_symset: malloc");
+
+ strlcpy(sym, s, strlen(s) - strlen(val) + 1);
+
+ ret = symset(sym, val + 1, 1);
+ free(sym);
+
+ return (ret);
+}
+
+char *
+symget(const char *nam)
+{
+ struct sym *sym;
+
+ TAILQ_FOREACH(sym, &symhead, entry)
+ if (strcmp(nam, sym->nam) == 0) {
+ sym->used = 1;
+ return (sym->val);
+ }
+ return (NULL);
+}
+
+void
+mv_rules(struct pf_ruleset *src, struct pf_ruleset *dst)
+{
+ int i;
+ struct pf_rule *r;
+
+ for (i = 0; i < PF_RULESET_MAX; ++i) {
+ while ((r = TAILQ_FIRST(src->rules[i].active.ptr))
+ != NULL) {
+ TAILQ_REMOVE(src->rules[i].active.ptr, r, entries);
+ TAILQ_INSERT_TAIL(dst->rules[i].active.ptr, r, entries);
+ dst->anchor->match++;
+ }
+ src->anchor->match = 0;
+ while ((r = TAILQ_FIRST(src->rules[i].inactive.ptr))
+ != NULL) {
+ TAILQ_REMOVE(src->rules[i].inactive.ptr, r, entries);
+ TAILQ_INSERT_TAIL(dst->rules[i].inactive.ptr,
+ r, entries);
+ }
+ }
+}
+
+void
+decide_address_family(struct node_host *n, sa_family_t *af)
+{
+ if (*af != 0 || n == NULL)
+ return;
+ *af = n->af;
+ while ((n = n->next) != NULL) {
+ if (n->af != *af) {
+ *af = 0;
+ return;
+ }
+ }
+}
+
+void
+remove_invalid_hosts(struct node_host **nh, sa_family_t *af)
+{
+ struct node_host *n = *nh, *prev = NULL;
+
+ while (n != NULL) {
+ if (*af && n->af && n->af != *af) {
+ /* unlink and free n */
+ struct node_host *next = n->next;
+
+ /* adjust tail pointer */
+ if (n == (*nh)->tail)
+ (*nh)->tail = prev;
+ /* adjust previous node's next pointer */
+ if (prev == NULL)
+ *nh = next;
+ else
+ prev->next = next;
+ /* free node */
+ if (n->ifname != NULL)
+ free(n->ifname);
+ free(n);
+ n = next;
+ } else {
+ if (n->af && !*af)
+ *af = n->af;
+ prev = n;
+ n = n->next;
+ }
+ }
+}
+
+int
+invalid_redirect(struct node_host *nh, sa_family_t af)
+{
+ if (!af) {
+ struct node_host *n;
+
+ /* tables and dyniftl are ok without an address family */
+ for (n = nh; n != NULL; n = n->next) {
+ if (n->addr.type != PF_ADDR_TABLE &&
+ n->addr.type != PF_ADDR_DYNIFTL) {
+ yyerror("address family not given and "
+ "translation address expands to multiple "
+ "address families");
+ return (1);
+ }
+ }
+ }
+ if (nh == NULL) {
+ yyerror("no translation address with matching address family "
+ "found.");
+ return (1);
+ }
+ return (0);
+}
+
+int
+atoul(char *s, u_long *ulvalp)
+{
+ u_long ulval;
+ char *ep;
+
+ errno = 0;
+ ulval = strtoul(s, &ep, 0);
+ if (s[0] == '\0' || *ep != '\0')
+ return (-1);
+ if (errno == ERANGE && ulval == ULONG_MAX)
+ return (-1);
+ *ulvalp = ulval;
+ return (0);
+}
+
+int
+getservice(char *n)
+{
+ struct servent *s;
+ u_long ulval;
+
+ if (atoul(n, &ulval) == 0) {
+ if (ulval > 65535) {
+ yyerror("illegal port value %lu", ulval);
+ return (-1);
+ }
+ return (htons(ulval));
+ } else {
+ s = getservbyname(n, "tcp");
+ if (s == NULL)
+ s = getservbyname(n, "udp");
+ if (s == NULL) {
+ yyerror("unknown port %s", n);
+ return (-1);
+ }
+ return (s->s_port);
+ }
+}
+
+int
+rule_label(struct pf_rule *r, char *s)
+{
+ if (s) {
+ if (strlcpy(r->label, s, sizeof(r->label)) >=
+ sizeof(r->label)) {
+ yyerror("rule label too long (max %d chars)",
+ sizeof(r->label)-1);
+ return (-1);
+ }
+ }
+ return (0);
+}
+
+u_int16_t
+parseicmpspec(char *w, sa_family_t af)
+{
+ const struct icmpcodeent *p;
+ u_long ulval;
+ u_int8_t icmptype;
+
+ if (af == AF_INET)
+ icmptype = returnicmpdefault >> 8;
+ else
+ icmptype = returnicmp6default >> 8;
+
+ if (atoul(w, &ulval) == -1) {
+ if ((p = geticmpcodebyname(icmptype, w, af)) == NULL) {
+ yyerror("unknown icmp code %s", w);
+ return (0);
+ }
+ ulval = p->code;
+ }
+ if (ulval > 255) {
+ yyerror("invalid icmp code %lu", ulval);
+ return (0);
+ }
+ return (icmptype << 8 | ulval);
+}
+
+int
+parseport(char *port, struct range *r, int extensions)
+{
+ char *p = strchr(port, ':');
+
+ if (p == NULL) {
+ if ((r->a = getservice(port)) == -1)
+ return (-1);
+ r->b = 0;
+ r->t = PF_OP_NONE;
+ return (0);
+ }
+ if ((extensions & PPORT_STAR) && !strcmp(p+1, "*")) {
+ *p = 0;
+ if ((r->a = getservice(port)) == -1)
+ return (-1);
+ r->b = 0;
+ r->t = PF_OP_IRG;
+ return (0);
+ }
+ if ((extensions & PPORT_RANGE)) {
+ *p++ = 0;
+ if ((r->a = getservice(port)) == -1 ||
+ (r->b = getservice(p)) == -1)
+ return (-1);
+ if (r->a == r->b) {
+ r->b = 0;
+ r->t = PF_OP_NONE;
+ } else
+ r->t = PF_OP_RRG;
+ return (0);
+ }
+ return (-1);
+}
+
+int
+pfctl_load_anchors(int dev, struct pfctl *pf, struct pfr_buffer *trans)
+{
+ struct loadanchors *la;
+
+ TAILQ_FOREACH(la, &loadanchorshead, entries) {
+ if (pf->opts & PF_OPT_VERBOSE)
+ fprintf(stderr, "\nLoading anchor %s from %s\n",
+ la->anchorname, la->filename);
+ if (pfctl_rules(dev, la->filename, pf->opts, pf->optimize,
+ la->anchorname, trans) == -1)
+ return (-1);
+ }
+
+ return (0);
+}
+
+int
+rt_tableid_max(void)
+{
+#ifdef __FreeBSD__
+ int fibs;
+ size_t l = sizeof(fibs);
+
+ if (sysctlbyname("net.fibs", &fibs, &l, NULL, 0) == -1)
+ fibs = 16; /* XXX RT_MAXFIBS, at least limit it some. */
+ /*
+ * As the OpenBSD code only compares > and not >= we need to adjust
+ * here given we only accept values of 0..n and want to avoid #ifdefs
+ * in the grammer.
+ */
+ return (fibs - 1);
+#else
+ return (RT_TABLEID_MAX);
+#endif
+}
diff --git a/sbin/pfctl/pf_print_state.c b/sbin/pfctl/pf_print_state.c
new file mode 100644
index 0000000..46d4523
--- /dev/null
+++ b/sbin/pfctl/pf_print_state.c
@@ -0,0 +1,367 @@
+/* $OpenBSD: pf_print_state.c,v 1.52 2008/08/12 16:40:18 david Exp $ */
+
+/*
+ * Copyright (c) 2001 Daniel Hartmeier
+ * All rights reserved.
+ *
+ * Redistribution and use 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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
+ * COPYRIGHT HOLDERS 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 <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/endian.h>
+#include <net/if.h>
+#define TCPSTATES
+#include <netinet/tcp_fsm.h>
+#include <net/pfvar.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "pfctl_parser.h"
+#include "pfctl.h"
+
+void print_name(struct pf_addr *, sa_family_t);
+
+void
+print_addr(struct pf_addr_wrap *addr, sa_family_t af, int verbose)
+{
+ switch (addr->type) {
+ case PF_ADDR_DYNIFTL:
+ printf("(%s", addr->v.ifname);
+ if (addr->iflags & PFI_AFLAG_NETWORK)
+ printf(":network");
+ if (addr->iflags & PFI_AFLAG_BROADCAST)
+ printf(":broadcast");
+ if (addr->iflags & PFI_AFLAG_PEER)
+ printf(":peer");
+ if (addr->iflags & PFI_AFLAG_NOALIAS)
+ printf(":0");
+ if (verbose) {
+ if (addr->p.dyncnt <= 0)
+ printf(":*");
+ else
+ printf(":%d", addr->p.dyncnt);
+ }
+ printf(")");
+ break;
+ case PF_ADDR_TABLE:
+ if (verbose)
+ if (addr->p.tblcnt == -1)
+ printf("<%s:*>", addr->v.tblname);
+ else
+ printf("<%s:%d>", addr->v.tblname,
+ addr->p.tblcnt);
+ else
+ printf("<%s>", addr->v.tblname);
+ return;
+ case PF_ADDR_RANGE: {
+ char buf[48];
+
+ if (inet_ntop(af, &addr->v.a.addr, buf, sizeof(buf)) == NULL)
+ printf("?");
+ else
+ printf("%s", buf);
+ if (inet_ntop(af, &addr->v.a.mask, buf, sizeof(buf)) == NULL)
+ printf(" - ?");
+ else
+ printf(" - %s", buf);
+ break;
+ }
+ case PF_ADDR_ADDRMASK:
+ if (PF_AZERO(&addr->v.a.addr, AF_INET6) &&
+ PF_AZERO(&addr->v.a.mask, AF_INET6))
+ printf("any");
+ else {
+ char buf[48];
+
+ if (inet_ntop(af, &addr->v.a.addr, buf,
+ sizeof(buf)) == NULL)
+ printf("?");
+ else
+ printf("%s", buf);
+ }
+ break;
+ case PF_ADDR_NOROUTE:
+ printf("no-route");
+ return;
+ case PF_ADDR_URPFFAILED:
+ printf("urpf-failed");
+ return;
+ default:
+ printf("?");
+ return;
+ }
+
+ /* mask if not _both_ address and mask are zero */
+ if (addr->type != PF_ADDR_RANGE &&
+ !(PF_AZERO(&addr->v.a.addr, AF_INET6) &&
+ PF_AZERO(&addr->v.a.mask, AF_INET6))) {
+ int bits = unmask(&addr->v.a.mask, af);
+
+ if (bits != (af == AF_INET ? 32 : 128))
+ printf("/%d", bits);
+ }
+}
+
+void
+print_name(struct pf_addr *addr, sa_family_t af)
+{
+ char host[NI_MAXHOST];
+
+ strlcpy(host, "?", sizeof(host));
+ switch (af) {
+ case AF_INET: {
+ struct sockaddr_in sin;
+
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_len = sizeof(sin);
+ sin.sin_family = AF_INET;
+ sin.sin_addr = addr->v4;
+ getnameinfo((struct sockaddr *)&sin, sin.sin_len,
+ host, sizeof(host), NULL, 0, NI_NOFQDN);
+ break;
+ }
+ case AF_INET6: {
+ struct sockaddr_in6 sin6;
+
+ memset(&sin6, 0, sizeof(sin6));
+ sin6.sin6_len = sizeof(sin6);
+ sin6.sin6_family = AF_INET6;
+ sin6.sin6_addr = addr->v6;
+ getnameinfo((struct sockaddr *)&sin6, sin6.sin6_len,
+ host, sizeof(host), NULL, 0, NI_NOFQDN);
+ break;
+ }
+ }
+ printf("%s", host);
+}
+
+void
+print_host(struct pf_addr *addr, u_int16_t port, sa_family_t af, int opts)
+{
+ if (opts & PF_OPT_USEDNS)
+ print_name(addr, af);
+ else {
+ struct pf_addr_wrap aw;
+
+ memset(&aw, 0, sizeof(aw));
+ aw.v.a.addr = *addr;
+ if (af == AF_INET)
+ aw.v.a.mask.addr32[0] = 0xffffffff;
+ else {
+ memset(&aw.v.a.mask, 0xff, sizeof(aw.v.a.mask));
+ af = AF_INET6;
+ }
+ print_addr(&aw, af, opts & PF_OPT_VERBOSE2);
+ }
+
+ if (port) {
+ if (af == AF_INET)
+ printf(":%u", ntohs(port));
+ else
+ printf("[%u]", ntohs(port));
+ }
+}
+
+void
+print_seq(struct pfsync_state_peer *p)
+{
+ if (p->seqdiff)
+ printf("[%u + %u](+%u)", ntohl(p->seqlo),
+ ntohl(p->seqhi) - ntohl(p->seqlo), ntohl(p->seqdiff));
+ else
+ printf("[%u + %u]", ntohl(p->seqlo),
+ ntohl(p->seqhi) - ntohl(p->seqlo));
+}
+
+void
+print_state(struct pfsync_state *s, int opts)
+{
+ struct pfsync_state_peer *src, *dst;
+ struct pfsync_state_key *sk, *nk;
+ struct protoent *p;
+ int min, sec;
+
+ if (s->direction == PF_OUT) {
+ src = &s->src;
+ dst = &s->dst;
+ sk = &s->key[PF_SK_STACK];
+ nk = &s->key[PF_SK_WIRE];
+ if (s->proto == IPPROTO_ICMP || s->proto == IPPROTO_ICMPV6)
+ sk->port[0] = nk->port[0];
+ } else {
+ src = &s->dst;
+ dst = &s->src;
+ sk = &s->key[PF_SK_WIRE];
+ nk = &s->key[PF_SK_STACK];
+ if (s->proto == IPPROTO_ICMP || s->proto == IPPROTO_ICMPV6)
+ sk->port[1] = nk->port[1];
+ }
+ printf("%s ", s->ifname);
+ if ((p = getprotobynumber(s->proto)) != NULL)
+ printf("%s ", p->p_name);
+ else
+ printf("%u ", s->proto);
+
+ print_host(&nk->addr[1], nk->port[1], s->af, opts);
+ if (PF_ANEQ(&nk->addr[1], &sk->addr[1], s->af) ||
+ nk->port[1] != sk->port[1]) {
+ printf(" (");
+ print_host(&sk->addr[1], sk->port[1], s->af, opts);
+ printf(")");
+ }
+ if (s->direction == PF_OUT)
+ printf(" -> ");
+ else
+ printf(" <- ");
+ print_host(&nk->addr[0], nk->port[0], s->af, opts);
+ if (PF_ANEQ(&nk->addr[0], &sk->addr[0], s->af) ||
+ nk->port[0] != sk->port[0]) {
+ printf(" (");
+ print_host(&sk->addr[0], sk->port[0], s->af, opts);
+ printf(")");
+ }
+
+ printf(" ");
+ if (s->proto == IPPROTO_TCP) {
+ if (src->state <= TCPS_TIME_WAIT &&
+ dst->state <= TCPS_TIME_WAIT)
+ printf(" %s:%s\n", tcpstates[src->state],
+ tcpstates[dst->state]);
+ else if (src->state == PF_TCPS_PROXY_SRC ||
+ dst->state == PF_TCPS_PROXY_SRC)
+ printf(" PROXY:SRC\n");
+ else if (src->state == PF_TCPS_PROXY_DST ||
+ dst->state == PF_TCPS_PROXY_DST)
+ printf(" PROXY:DST\n");
+ else
+ printf(" <BAD STATE LEVELS %u:%u>\n",
+ src->state, dst->state);
+ if (opts & PF_OPT_VERBOSE) {
+ printf(" ");
+ print_seq(src);
+ if (src->wscale && dst->wscale)
+ printf(" wscale %u",
+ src->wscale & PF_WSCALE_MASK);
+ printf(" ");
+ print_seq(dst);
+ if (src->wscale && dst->wscale)
+ printf(" wscale %u",
+ dst->wscale & PF_WSCALE_MASK);
+ printf("\n");
+ }
+ } else if (s->proto == IPPROTO_UDP && src->state < PFUDPS_NSTATES &&
+ dst->state < PFUDPS_NSTATES) {
+ const char *states[] = PFUDPS_NAMES;
+
+ printf(" %s:%s\n", states[src->state], states[dst->state]);
+#ifndef INET6
+ } else if (s->proto != IPPROTO_ICMP && src->state < PFOTHERS_NSTATES &&
+ dst->state < PFOTHERS_NSTATES) {
+#else
+ } else if (s->proto != IPPROTO_ICMP && s->proto != IPPROTO_ICMPV6 &&
+ src->state < PFOTHERS_NSTATES && dst->state < PFOTHERS_NSTATES) {
+#endif
+ /* XXX ICMP doesn't really have state levels */
+ const char *states[] = PFOTHERS_NAMES;
+
+ printf(" %s:%s\n", states[src->state], states[dst->state]);
+ } else {
+ printf(" %u:%u\n", src->state, dst->state);
+ }
+
+ if (opts & PF_OPT_VERBOSE) {
+ u_int64_t packets[2];
+ u_int64_t bytes[2];
+ u_int32_t creation = ntohl(s->creation);
+ u_int32_t expire = ntohl(s->expire);
+
+ sec = creation % 60;
+ creation /= 60;
+ min = creation % 60;
+ creation /= 60;
+ printf(" age %.2u:%.2u:%.2u", creation, min, sec);
+ sec = expire % 60;
+ expire /= 60;
+ min = expire % 60;
+ expire /= 60;
+ printf(", expires in %.2u:%.2u:%.2u", expire, min, sec);
+
+ bcopy(s->packets[0], &packets[0], sizeof(u_int64_t));
+ bcopy(s->packets[1], &packets[1], sizeof(u_int64_t));
+ bcopy(s->bytes[0], &bytes[0], sizeof(u_int64_t));
+ bcopy(s->bytes[1], &bytes[1], sizeof(u_int64_t));
+ printf(", %ju:%ju pkts, %ju:%ju bytes",
+ (uintmax_t )be64toh(packets[0]),
+ (uintmax_t )be64toh(packets[1]),
+ (uintmax_t )be64toh(bytes[0]),
+ (uintmax_t )be64toh(bytes[1]));
+ if (ntohl(s->anchor) != -1)
+ printf(", anchor %u", ntohl(s->anchor));
+ if (ntohl(s->rule) != -1)
+ printf(", rule %u", ntohl(s->rule));
+ if (s->state_flags & PFSTATE_SLOPPY)
+ printf(", sloppy");
+ if (s->sync_flags & PFSYNC_FLAG_SRCNODE)
+ printf(", source-track");
+ if (s->sync_flags & PFSYNC_FLAG_NATSRCNODE)
+ printf(", sticky-address");
+ printf("\n");
+ }
+ if (opts & PF_OPT_VERBOSE2) {
+ u_int64_t id;
+
+ bcopy(&s->id, &id, sizeof(u_int64_t));
+ printf(" id: %016jx creatorid: %08x",
+ (uintmax_t )be64toh(id), ntohl(s->creatorid));
+ printf("\n");
+ }
+}
+
+int
+unmask(struct pf_addr *m, sa_family_t af)
+{
+ int i = 31, j = 0, b = 0;
+ u_int32_t tmp;
+
+ while (j < 4 && m->addr32[j] == 0xffffffff) {
+ b += 32;
+ j++;
+ }
+ if (j < 4) {
+ tmp = ntohl(m->addr32[j]);
+ for (i = 31; tmp & (1 << i); --i)
+ b++;
+ }
+ return (b);
+}
diff --git a/sbin/pfctl/pfctl.8 b/sbin/pfctl/pfctl.8
new file mode 100644
index 0000000..5908cf1
--- /dev/null
+++ b/sbin/pfctl/pfctl.8
@@ -0,0 +1,686 @@
+.\" $OpenBSD: pfctl.8,v 1.138 2008/06/10 20:55:02 mcbride Exp $
+.\"
+.\" Copyright (c) 2001 Kjell Wooding. 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd June 21, 2011
+.Dt PFCTL 8
+.Os
+.Sh NAME
+.Nm pfctl
+.Nd control the packet filter (PF) device
+.Sh SYNOPSIS
+.Nm pfctl
+.Bk -words
+.Op Fl AdeghmNnOPqRrvz
+.Op Fl a Ar anchor
+.Oo Fl D Ar macro Ns =
+.Ar value Oc
+.Op Fl F Ar modifier
+.Op Fl f Ar file
+.Op Fl i Ar interface
+.Op Fl K Ar host | network
+.Xo
+.Oo Fl k
+.Ar host | network | label | id
+.Oc Xc
+.Op Fl o Ar level
+.Op Fl p Ar device
+.Op Fl s Ar modifier
+.Xo
+.Oo Fl t Ar table
+.Fl T Ar command
+.Op Ar address ...
+.Oc Xc
+.Op Fl x Ar level
+.Ek
+.Sh DESCRIPTION
+The
+.Nm
+utility communicates with the packet filter device using the
+ioctl interface described in
+.Xr pf 4 .
+It allows ruleset and parameter configuration and retrieval of status
+information from the packet filter.
+.Pp
+Packet filtering restricts the types of packets that pass through
+network interfaces entering or leaving the host based on filter
+rules as described in
+.Xr pf.conf 5 .
+The packet filter can also replace addresses and ports of packets.
+Replacing source addresses and ports of outgoing packets is called
+NAT (Network Address Translation) and is used to connect an internal
+network (usually reserved address space) to an external one (the
+Internet) by making all connections to external hosts appear to
+come from the gateway.
+Replacing destination addresses and ports of incoming packets
+is used to redirect connections to different hosts and/or ports.
+A combination of both translations, bidirectional NAT, is also
+supported.
+Translation rules are described in
+.Xr pf.conf 5 .
+.Pp
+When the variable
+.Va pf
+is set to
+.Dv YES
+in
+.Xr rc.conf 5 ,
+the rule file specified with the variable
+.Va pf_rules
+is loaded automatically by the
+.Xr rc 8
+scripts and the packet filter is enabled.
+.Pp
+The packet filter does not itself forward packets between interfaces.
+Forwarding can be enabled by setting the
+.Xr sysctl 8
+variables
+.Em net.inet.ip.forwarding
+and/or
+.Em net.inet6.ip6.forwarding
+to 1.
+Set them permanently in
+.Xr sysctl.conf 5 .
+.Pp
+The
+.Nm
+utility provides several commands.
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl A
+Load only the queue rules present in the rule file.
+Other rules and options are ignored.
+.It Fl a Ar anchor
+Apply flags
+.Fl f ,
+.Fl F ,
+and
+.Fl s
+only to the rules in the specified
+.Ar anchor .
+In addition to the main ruleset,
+.Nm
+can load and manipulate additional rulesets by name,
+called anchors.
+The main ruleset is the default anchor.
+.Pp
+Anchors are referenced by name and may be nested,
+with the various components of the anchor path separated by
+.Sq /
+characters, similar to how file system hierarchies are laid out.
+The last component of the anchor path is where ruleset operations are
+performed.
+.Pp
+Evaluation of
+.Ar anchor
+rules from the main ruleset is described in
+.Xr pf.conf 5 .
+.Pp
+For example, the following will show all filter rules (see the
+.Fl s
+flag below) inside the anchor
+.Dq authpf/smith(1234) ,
+which would have been created for user
+.Dq smith
+by
+.Xr authpf 8 ,
+PID 1234:
+.Bd -literal -offset indent
+# pfctl -a "authpf/smith(1234)" -s rules
+.Ed
+.Pp
+Private tables can also be put inside anchors, either by having table
+statements in the
+.Xr pf.conf 5
+file that is loaded in the anchor, or by using regular table commands, as in:
+.Bd -literal -offset indent
+# pfctl -a foo/bar -t mytable -T add 1.2.3.4 5.6.7.8
+.Ed
+.Pp
+When a rule referring to a table is loaded in an anchor, the rule will use the
+private table if one is defined, and then fall back to the table defined in the
+main ruleset, if there is one.
+This is similar to C rules for variable scope.
+It is possible to create distinct tables with the same name in the global
+ruleset and in an anchor, but this is often bad design and a warning will be
+issued in that case.
+.Pp
+By default, recursive inline printing of anchors applies only to unnamed
+anchors specified inline in the ruleset.
+If the anchor name is terminated with a
+.Sq *
+character, the
+.Fl s
+flag will recursively print all anchors in a brace delimited block.
+For example the following will print the
+.Dq authpf
+ruleset recursively:
+.Bd -literal -offset indent
+# pfctl -a 'authpf/*' -sr
+.Ed
+.Pp
+To print the main ruleset recursively, specify only
+.Sq *
+as the anchor name:
+.Bd -literal -offset indent
+# pfctl -a '*' -sr
+.Ed
+.It Fl D Ar macro Ns = Ns Ar value
+Define
+.Ar macro
+to be set to
+.Ar value
+on the command line.
+Overrides the definition of
+.Ar macro
+in the ruleset.
+.It Fl d
+Disable the packet filter.
+.It Fl e
+Enable the packet filter.
+.It Fl F Ar modifier
+Flush the filter parameters specified by
+.Ar modifier
+(may be abbreviated):
+.Pp
+.Bl -tag -width xxxxxxxxxxxx -compact
+.It Fl F Cm nat
+Flush the NAT rules.
+.It Fl F Cm queue
+Flush the queue rules.
+.It Fl F Cm rules
+Flush the filter rules.
+.It Fl F Cm states
+Flush the state table (NAT and filter).
+.It Fl F Cm Sources
+Flush the source tracking table.
+.It Fl F Cm info
+Flush the filter information (statistics that are not bound to rules).
+.It Fl F Cm Tables
+Flush the tables.
+.It Fl F Cm osfp
+Flush the passive operating system fingerprints.
+.It Fl F Cm all
+Flush all of the above.
+.El
+.It Fl f Ar file
+Load the rules contained in
+.Ar file .
+This
+.Ar file
+may contain macros, tables, options, and normalization, queueing,
+translation, and filtering rules.
+With the exception of macros and tables, the statements must appear in that
+order.
+.It Fl g
+Include output helpful for debugging.
+.It Fl h
+Help.
+.It Fl i Ar interface
+Restrict the operation to the given
+.Ar interface .
+.It Fl K Ar host | network
+Kill all of the source tracking entries originating from the specified
+.Ar host
+or
+.Ar network .
+A second
+.Fl K Ar host
+or
+.Fl K Ar network
+option may be specified, which will kill all the source tracking
+entries from the first host/network to the second.
+.It Xo
+.Fl k
+.Ar host | network | label | id
+.Xc
+Kill all of the state entries matching the specified
+.Ar host ,
+.Ar network ,
+.Ar label ,
+or
+.Ar id .
+.Pp
+For example, to kill all of the state entries originating from
+.Dq host :
+.Pp
+.Dl # pfctl -k host
+.Pp
+A second
+.Fl k Ar host
+or
+.Fl k Ar network
+option may be specified, which will kill all the state entries
+from the first host/network to the second.
+To kill all of the state entries from
+.Dq host1
+to
+.Dq host2 :
+.Pp
+.Dl # pfctl -k host1 -k host2
+.Pp
+To kill all states originating from 192.168.1.0/24 to 172.16.0.0/16:
+.Pp
+.Dl # pfctl -k 192.168.1.0/24 -k 172.16.0.0/16
+.Pp
+A network prefix length of 0 can be used as a wildcard.
+To kill all states with the target
+.Dq host2 :
+.Pp
+.Dl # pfctl -k 0.0.0.0/0 -k host2
+.Pp
+It is also possible to kill states by rule label or state ID.
+In this mode the first
+.Fl k
+argument is used to specify the type
+of the second argument.
+The following command would kill all states that have been created
+from rules carrying the label
+.Dq foobar :
+.Pp
+.Dl # pfctl -k label -k foobar
+.Pp
+To kill one specific state by its unique state ID
+(as shown by pfctl -s state -vv),
+use the
+.Ar id
+modifier and as a second argument the state ID and optional creator ID.
+To kill a state with ID 4823e84500000003 use:
+.Pp
+.Dl # pfctl -k id -k 4823e84500000003
+.Pp
+To kill a state with ID 4823e84500000018 created from a backup
+firewall with hostid 00000002 use:
+.Pp
+.Dl # pfctl -k id -k 4823e84500000018/2
+.It Fl m
+Merge in explicitly given options without resetting those
+which are omitted.
+Allows single options to be modified without disturbing the others:
+.Bd -literal -offset indent
+# echo "set loginterface fxp0" | pfctl -mf -
+.Ed
+.It Fl N
+Load only the NAT rules present in the rule file.
+Other rules and options are ignored.
+.It Fl n
+Do not actually load rules, just parse them.
+.It Fl O
+Load only the options present in the rule file.
+Other rules and options are ignored.
+.It Fl o Ar level
+Control the ruleset optimizer, overriding any rule file settings.
+.Pp
+.Bl -tag -width xxxxxxxxxxxx -compact
+.It Fl o Cm none
+Disable the ruleset optimizer.
+.It Fl o Cm basic
+Enable basic ruleset optimizations.
+This is the default behaviour.
+.It Fl o Cm profile
+Enable basic ruleset optimizations with profiling.
+.El
+For further information on the ruleset optimizer, see
+.Xr pf.conf 5 .
+.It Fl P
+Do not perform service name lookup for port specific rules,
+instead display the ports numerically.
+.It Fl p Ar device
+Use the device file
+.Ar device
+instead of the default
+.Pa /dev/pf .
+.It Fl q
+Only print errors and warnings.
+.It Fl R
+Load only the filter rules present in the rule file.
+Other rules and options are ignored.
+.It Fl r
+Perform reverse DNS lookups on states when displaying them.
+.It Fl s Ar modifier
+Show the filter parameters specified by
+.Ar modifier
+(may be abbreviated):
+.Pp
+.Bl -tag -width xxxxxxxxxxxxx -compact
+.It Fl s Cm nat
+Show the currently loaded NAT rules.
+.It Fl s Cm queue
+Show the currently loaded queue rules.
+When used together with
+.Fl v ,
+per-queue statistics are also shown.
+When used together with
+.Fl v v ,
+.Nm
+will loop and show updated queue statistics every five seconds, including
+measured bandwidth and packets per second.
+.It Fl s Cm rules
+Show the currently loaded filter rules.
+When used together with
+.Fl v ,
+the per-rule statistics (number of evaluations,
+packets and bytes) are also shown.
+Note that the
+.Dq skip step
+optimization done automatically by the kernel
+will skip evaluation of rules where possible.
+Packets passed statefully are counted in the rule that created the state
+(even though the rule isn't evaluated more than once for the entire
+connection).
+.It Fl s Cm Anchors
+Show the currently loaded anchors directly attached to the main ruleset.
+If
+.Fl a Ar anchor
+is specified as well, the anchors loaded directly below the given
+.Ar anchor
+are shown instead.
+If
+.Fl v
+is specified, all anchors attached under the target anchor will be
+displayed recursively.
+.It Fl s Cm states
+Show the contents of the state table.
+.It Fl s Cm Sources
+Show the contents of the source tracking table.
+.It Fl s Cm info
+Show filter information (statistics and counters).
+When used together with
+.Fl v ,
+source tracking statistics are also shown.
+.It Fl s Cm labels
+Show per-rule statistics (label, evaluations, packets total, bytes total,
+packets in, bytes in, packets out, bytes out, state creations) of
+filter rules with labels, useful for accounting.
+.It Fl s Cm timeouts
+Show the current global timeouts.
+.It Fl s Cm memory
+Show the current pool memory hard limits.
+.It Fl s Cm Tables
+Show the list of tables.
+.It Fl s Cm osfp
+Show the list of operating system fingerprints.
+.It Fl s Cm Interfaces
+Show the list of interfaces and interface drivers available to PF.
+When used together with
+.Fl v ,
+it additionally lists which interfaces have skip rules activated.
+When used together with
+.Fl vv ,
+interface statistics are also shown.
+.Fl i
+can be used to select an interface or a group of interfaces.
+.It Fl s Cm all
+Show all of the above, except for the lists of interfaces and operating
+system fingerprints.
+.El
+.It Fl T Ar command Op Ar address ...
+Specify the
+.Ar command
+(may be abbreviated) to apply to the table.
+Commands include:
+.Pp
+.Bl -tag -width xxxxxxxxxxxx -compact
+.It Fl T Cm kill
+Kill a table.
+.It Fl T Cm flush
+Flush all addresses of a table.
+.It Fl T Cm add
+Add one or more addresses in a table.
+Automatically create a nonexisting table.
+.It Fl T Cm delete
+Delete one or more addresses from a table.
+.It Fl T Cm expire Ar number
+Delete addresses which had their statistics cleared more than
+.Ar number
+seconds ago.
+For entries which have never had their statistics cleared,
+.Ar number
+refers to the time they were added to the table.
+.It Fl T Cm replace
+Replace the addresses of the table.
+Automatically create a nonexisting table.
+.It Fl T Cm show
+Show the content (addresses) of a table.
+.It Fl T Cm test
+Test if the given addresses match a table.
+.It Fl T Cm zero
+Clear all the statistics of a table.
+.It Fl T Cm load
+Load only the table definitions from
+.Xr pf.conf 5 .
+This is used in conjunction with the
+.Fl f
+flag, as in:
+.Bd -literal -offset indent
+# pfctl -Tl -f pf.conf
+.Ed
+.El
+.Pp
+For the
+.Cm add ,
+.Cm delete ,
+.Cm replace ,
+and
+.Cm test
+commands, the list of addresses can be specified either directly on the command
+line and/or in an unformatted text file, using the
+.Fl f
+flag.
+Comments starting with a
+.Sq #
+are allowed in the text file.
+With these commands, the
+.Fl v
+flag can also be used once or twice, in which case
+.Nm
+will print the
+detailed result of the operation for each individual address, prefixed by
+one of the following letters:
+.Pp
+.Bl -tag -width XXX -compact
+.It A
+The address/network has been added.
+.It C
+The address/network has been changed (negated).
+.It D
+The address/network has been deleted.
+.It M
+The address matches
+.Po
+.Cm test
+operation only
+.Pc .
+.It X
+The address/network is duplicated and therefore ignored.
+.It Y
+The address/network cannot be added/deleted due to conflicting
+.Sq \&!
+attributes.
+.It Z
+The address/network has been cleared (statistics).
+.El
+.Pp
+Each table can maintain a set of counters that can be retrieved using the
+.Fl v
+flag of
+.Nm .
+For example, the following commands define a wide open firewall which will keep
+track of packets going to or coming from the
+.Ox
+FTP server.
+The following commands configure the firewall and send 10 pings to the FTP
+server:
+.Bd -literal -offset indent
+# printf "table <test> counters { ftp.openbsd.org }\en \e
+ pass out to <test>\en" | pfctl -f-
+# ping -qc10 ftp.openbsd.org
+.Ed
+.Pp
+We can now use the table
+.Cm show
+command to output, for each address and packet direction, the number of packets
+and bytes that are being passed or blocked by rules referencing the table.
+The time at which the current accounting started is also shown with the
+.Dq Cleared
+line.
+.Bd -literal -offset indent
+# pfctl -t test -vTshow
+ 129.128.5.191
+ Cleared: Thu Feb 13 18:55:18 2003
+ In/Block: [ Packets: 0 Bytes: 0 ]
+ In/Pass: [ Packets: 10 Bytes: 840 ]
+ Out/Block: [ Packets: 0 Bytes: 0 ]
+ Out/Pass: [ Packets: 10 Bytes: 840 ]
+.Ed
+.Pp
+Similarly, it is possible to view global information about the tables
+by using the
+.Fl v
+modifier twice and the
+.Fl s
+.Cm Tables
+command.
+This will display the number of addresses on each table,
+the number of rules which reference the table, and the global
+packet statistics for the whole table:
+.Bd -literal -offset indent
+# pfctl -vvsTables
+--a-r-C test
+ Addresses: 1
+ Cleared: Thu Feb 13 18:55:18 2003
+ References: [ Anchors: 0 Rules: 1 ]
+ Evaluations: [ NoMatch: 3496 Match: 1 ]
+ In/Block: [ Packets: 0 Bytes: 0 ]
+ In/Pass: [ Packets: 10 Bytes: 840 ]
+ In/XPass: [ Packets: 0 Bytes: 0 ]
+ Out/Block: [ Packets: 0 Bytes: 0 ]
+ Out/Pass: [ Packets: 10 Bytes: 840 ]
+ Out/XPass: [ Packets: 0 Bytes: 0 ]
+.Ed
+.Pp
+As we can see here, only one packet \- the initial ping request \- matched the
+table, but all packets passing as the result of the state are correctly
+accounted for.
+Reloading the table(s) or ruleset will not affect packet accounting in any way.
+The two
+.Dq XPass
+counters are incremented instead of the
+.Dq Pass
+counters when a
+.Dq stateful
+packet is passed but doesn't match the table anymore.
+This will happen in our example if someone flushes the table while the
+.Xr ping 8
+command is running.
+.Pp
+When used with a single
+.Fl v ,
+.Nm
+will only display the first line containing the table flags and name.
+The flags are defined as follows:
+.Pp
+.Bl -tag -width XXX -compact
+.It c
+For constant tables, which cannot be altered outside
+.Xr pf.conf 5 .
+.It p
+For persistent tables, which don't get automatically killed when no rules
+refer to them.
+.It a
+For tables which are part of the
+.Em active
+tableset.
+Tables without this flag do not really exist, cannot contain addresses, and are
+only listed if the
+.Fl g
+flag is given.
+.It i
+For tables which are part of the
+.Em inactive
+tableset.
+This flag can only be witnessed briefly during the loading of
+.Xr pf.conf 5 .
+.It r
+For tables which are referenced (used) by rules.
+.It h
+This flag is set when a table in the main ruleset is hidden by one or more
+tables of the same name from anchors attached below it.
+.It C
+This flag is set when per-address counters are enabled on the table.
+.El
+.It Fl t Ar table
+Specify the name of the table.
+.It Fl v
+Produce more verbose output.
+A second use of
+.Fl v
+will produce even more verbose output including ruleset warnings.
+See the previous section for its effect on table commands.
+.It Fl x Ar level
+Set the debug
+.Ar level
+(may be abbreviated) to one of the following:
+.Pp
+.Bl -tag -width xxxxxxxxxxxx -compact
+.It Fl x Cm none
+Don't generate debug messages.
+.It Fl x Cm urgent
+Generate debug messages only for serious errors.
+.It Fl x Cm misc
+Generate debug messages for various errors.
+.It Fl x Cm loud
+Generate debug messages for common conditions.
+.El
+.It Fl z
+Clear per-rule statistics.
+.El
+.Sh FILES
+.Bl -tag -width "/etc/pf.conf" -compact
+.It Pa /etc/pf.conf
+Packet filter rules file.
+.It Pa /etc/pf.os
+Passive operating system fingerprint database.
+.El
+.Sh SEE ALSO
+.Xr pf 4 ,
+.Xr pf.conf 5 ,
+.Xr pf.os 5 ,
+.Xr rc.conf 5 ,
+.Xr services 5 ,
+.Xr sysctl.conf 5 ,
+.Xr authpf 8 ,
+.Xr ftp-proxy 8 ,
+.Xr rc 8 ,
+.Xr sysctl 8
+.Sh HISTORY
+The
+.Nm
+program and the
+.Xr pf 4
+filter mechanism first appeared in
+.Ox 3.0 .
diff --git a/sbin/pfctl/pfctl.c b/sbin/pfctl/pfctl.c
new file mode 100644
index 0000000..c1ba12f
--- /dev/null
+++ b/sbin/pfctl/pfctl.c
@@ -0,0 +1,2387 @@
+/* $OpenBSD: pfctl.c,v 1.278 2008/08/31 20:18:17 jmc Exp $ */
+
+/*
+ * Copyright (c) 2001 Daniel Hartmeier
+ * Copyright (c) 2002,2003 Henning Brauer
+ * All rights reserved.
+ *
+ * Redistribution and use 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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
+ * COPYRIGHT HOLDERS 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 <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/endian.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <net/pfvar.h>
+#include <arpa/inet.h>
+#include <net/altq/altq.h>
+#include <sys/sysctl.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <netdb.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "pfctl_parser.h"
+#include "pfctl.h"
+
+void usage(void);
+int pfctl_enable(int, int);
+int pfctl_disable(int, int);
+int pfctl_clear_stats(int, int);
+int pfctl_clear_interface_flags(int, int);
+int pfctl_clear_rules(int, int, char *);
+int pfctl_clear_nat(int, int, char *);
+int pfctl_clear_altq(int, int);
+int pfctl_clear_src_nodes(int, int);
+int pfctl_clear_states(int, const char *, int);
+void pfctl_addrprefix(char *, struct pf_addr *);
+int pfctl_kill_src_nodes(int, const char *, int);
+int pfctl_net_kill_states(int, const char *, int);
+int pfctl_label_kill_states(int, const char *, int);
+int pfctl_id_kill_states(int, const char *, int);
+void pfctl_init_options(struct pfctl *);
+int pfctl_load_options(struct pfctl *);
+int pfctl_load_limit(struct pfctl *, unsigned int, unsigned int);
+int pfctl_load_timeout(struct pfctl *, unsigned int, unsigned int);
+int pfctl_load_debug(struct pfctl *, unsigned int);
+int pfctl_load_logif(struct pfctl *, char *);
+int pfctl_load_hostid(struct pfctl *, unsigned int);
+int pfctl_get_pool(int, struct pf_pool *, u_int32_t, u_int32_t, int,
+ char *);
+void pfctl_print_rule_counters(struct pf_rule *, int);
+int pfctl_show_rules(int, char *, int, enum pfctl_show, char *, int);
+int pfctl_show_nat(int, int, char *);
+int pfctl_show_src_nodes(int, int);
+int pfctl_show_states(int, const char *, int);
+int pfctl_show_status(int, int);
+int pfctl_show_timeouts(int, int);
+int pfctl_show_limits(int, int);
+void pfctl_debug(int, u_int32_t, int);
+int pfctl_test_altqsupport(int, int);
+int pfctl_show_anchors(int, int, char *);
+int pfctl_ruleset_trans(struct pfctl *, char *, struct pf_anchor *);
+int pfctl_load_ruleset(struct pfctl *, char *,
+ struct pf_ruleset *, int, int);
+int pfctl_load_rule(struct pfctl *, char *, struct pf_rule *, int);
+const char *pfctl_lookup_option(char *, const char **);
+
+struct pf_anchor_global pf_anchors;
+struct pf_anchor pf_main_anchor;
+
+const char *clearopt;
+char *rulesopt;
+const char *showopt;
+const char *debugopt;
+char *anchoropt;
+const char *optiopt = NULL;
+char *pf_device = "/dev/pf";
+char *ifaceopt;
+char *tableopt;
+const char *tblcmdopt;
+int src_node_killers;
+char *src_node_kill[2];
+int state_killers;
+char *state_kill[2];
+int loadopt;
+int altqsupport;
+
+int dev = -1;
+int first_title = 1;
+int labels = 0;
+
+#define INDENT(d, o) do { \
+ if (o) { \
+ int i; \
+ for (i=0; i < d; i++) \
+ printf(" "); \
+ } \
+ } while (0); \
+
+
+static const struct {
+ const char *name;
+ int index;
+} pf_limits[] = {
+ { "states", PF_LIMIT_STATES },
+ { "src-nodes", PF_LIMIT_SRC_NODES },
+ { "frags", PF_LIMIT_FRAGS },
+ { "table-entries", PF_LIMIT_TABLE_ENTRIES },
+ { NULL, 0 }
+};
+
+struct pf_hint {
+ const char *name;
+ int timeout;
+};
+static const struct pf_hint pf_hint_normal[] = {
+ { "tcp.first", 2 * 60 },
+ { "tcp.opening", 30 },
+ { "tcp.established", 24 * 60 * 60 },
+ { "tcp.closing", 15 * 60 },
+ { "tcp.finwait", 45 },
+ { "tcp.closed", 90 },
+ { "tcp.tsdiff", 30 },
+ { NULL, 0 }
+};
+static const struct pf_hint pf_hint_satellite[] = {
+ { "tcp.first", 3 * 60 },
+ { "tcp.opening", 30 + 5 },
+ { "tcp.established", 24 * 60 * 60 },
+ { "tcp.closing", 15 * 60 + 5 },
+ { "tcp.finwait", 45 + 5 },
+ { "tcp.closed", 90 + 5 },
+ { "tcp.tsdiff", 60 },
+ { NULL, 0 }
+};
+static const struct pf_hint pf_hint_conservative[] = {
+ { "tcp.first", 60 * 60 },
+ { "tcp.opening", 15 * 60 },
+ { "tcp.established", 5 * 24 * 60 * 60 },
+ { "tcp.closing", 60 * 60 },
+ { "tcp.finwait", 10 * 60 },
+ { "tcp.closed", 3 * 60 },
+ { "tcp.tsdiff", 60 },
+ { NULL, 0 }
+};
+static const struct pf_hint pf_hint_aggressive[] = {
+ { "tcp.first", 30 },
+ { "tcp.opening", 5 },
+ { "tcp.established", 5 * 60 * 60 },
+ { "tcp.closing", 60 },
+ { "tcp.finwait", 30 },
+ { "tcp.closed", 30 },
+ { "tcp.tsdiff", 10 },
+ { NULL, 0 }
+};
+
+static const struct {
+ const char *name;
+ const struct pf_hint *hint;
+} pf_hints[] = {
+ { "normal", pf_hint_normal },
+ { "satellite", pf_hint_satellite },
+ { "high-latency", pf_hint_satellite },
+ { "conservative", pf_hint_conservative },
+ { "aggressive", pf_hint_aggressive },
+ { NULL, NULL }
+};
+
+static const char *clearopt_list[] = {
+ "nat", "queue", "rules", "Sources",
+ "states", "info", "Tables", "osfp", "all", NULL
+};
+
+static const char *showopt_list[] = {
+ "nat", "queue", "rules", "Anchors", "Sources", "states", "info",
+ "Interfaces", "labels", "timeouts", "memory", "Tables", "osfp",
+ "all", NULL
+};
+
+static const char *tblcmdopt_list[] = {
+ "kill", "flush", "add", "delete", "load", "replace", "show",
+ "test", "zero", "expire", NULL
+};
+
+static const char *debugopt_list[] = {
+ "none", "urgent", "misc", "loud", NULL
+};
+
+static const char *optiopt_list[] = {
+ "none", "basic", "profile", NULL
+};
+
+void
+usage(void)
+{
+ extern char *__progname;
+
+ fprintf(stderr,
+"usage: %s [-AdeghmNnOPqRrvz] [-a anchor] [-D macro=value] [-F modifier]\n"
+ "\t[-f file] [-i interface] [-K host | network]\n"
+ "\t[-k host | network | label | id] [-o level] [-p device]\n"
+ "\t[-s modifier] [-t table -T command [address ...]] [-x level]\n",
+ __progname);
+
+ exit(1);
+}
+
+int
+pfctl_enable(int dev, int opts)
+{
+ if (ioctl(dev, DIOCSTART)) {
+ if (errno == EEXIST)
+ errx(1, "pf already enabled");
+ else if (errno == ESRCH)
+ errx(1, "pfil registeration failed");
+ else
+ err(1, "DIOCSTART");
+ }
+ if ((opts & PF_OPT_QUIET) == 0)
+ fprintf(stderr, "pf enabled\n");
+
+ if (altqsupport && ioctl(dev, DIOCSTARTALTQ))
+ if (errno != EEXIST)
+ err(1, "DIOCSTARTALTQ");
+
+ return (0);
+}
+
+int
+pfctl_disable(int dev, int opts)
+{
+ if (ioctl(dev, DIOCSTOP)) {
+ if (errno == ENOENT)
+ errx(1, "pf not enabled");
+ else
+ err(1, "DIOCSTOP");
+ }
+ if ((opts & PF_OPT_QUIET) == 0)
+ fprintf(stderr, "pf disabled\n");
+
+ if (altqsupport && ioctl(dev, DIOCSTOPALTQ))
+ if (errno != ENOENT)
+ err(1, "DIOCSTOPALTQ");
+
+ return (0);
+}
+
+int
+pfctl_clear_stats(int dev, int opts)
+{
+ if (ioctl(dev, DIOCCLRSTATUS))
+ err(1, "DIOCCLRSTATUS");
+ if ((opts & PF_OPT_QUIET) == 0)
+ fprintf(stderr, "pf: statistics cleared\n");
+ return (0);
+}
+
+int
+pfctl_clear_interface_flags(int dev, int opts)
+{
+ struct pfioc_iface pi;
+
+ if ((opts & PF_OPT_NOACTION) == 0) {
+ bzero(&pi, sizeof(pi));
+ pi.pfiio_flags = PFI_IFLAG_SKIP;
+
+ if (ioctl(dev, DIOCCLRIFFLAG, &pi))
+ err(1, "DIOCCLRIFFLAG");
+ if ((opts & PF_OPT_QUIET) == 0)
+ fprintf(stderr, "pf: interface flags reset\n");
+ }
+ return (0);
+}
+
+int
+pfctl_clear_rules(int dev, int opts, char *anchorname)
+{
+ struct pfr_buffer t;
+
+ memset(&t, 0, sizeof(t));
+ t.pfrb_type = PFRB_TRANS;
+ if (pfctl_add_trans(&t, PF_RULESET_SCRUB, anchorname) ||
+ pfctl_add_trans(&t, PF_RULESET_FILTER, anchorname) ||
+ pfctl_trans(dev, &t, DIOCXBEGIN, 0) ||
+ pfctl_trans(dev, &t, DIOCXCOMMIT, 0))
+ err(1, "pfctl_clear_rules");
+ if ((opts & PF_OPT_QUIET) == 0)
+ fprintf(stderr, "rules cleared\n");
+ return (0);
+}
+
+int
+pfctl_clear_nat(int dev, int opts, char *anchorname)
+{
+ struct pfr_buffer t;
+
+ memset(&t, 0, sizeof(t));
+ t.pfrb_type = PFRB_TRANS;
+ if (pfctl_add_trans(&t, PF_RULESET_NAT, anchorname) ||
+ pfctl_add_trans(&t, PF_RULESET_BINAT, anchorname) ||
+ pfctl_add_trans(&t, PF_RULESET_RDR, anchorname) ||
+ pfctl_trans(dev, &t, DIOCXBEGIN, 0) ||
+ pfctl_trans(dev, &t, DIOCXCOMMIT, 0))
+ err(1, "pfctl_clear_nat");
+ if ((opts & PF_OPT_QUIET) == 0)
+ fprintf(stderr, "nat cleared\n");
+ return (0);
+}
+
+int
+pfctl_clear_altq(int dev, int opts)
+{
+ struct pfr_buffer t;
+
+ if (!altqsupport)
+ return (-1);
+ memset(&t, 0, sizeof(t));
+ t.pfrb_type = PFRB_TRANS;
+ if (pfctl_add_trans(&t, PF_RULESET_ALTQ, "") ||
+ pfctl_trans(dev, &t, DIOCXBEGIN, 0) ||
+ pfctl_trans(dev, &t, DIOCXCOMMIT, 0))
+ err(1, "pfctl_clear_altq");
+ if ((opts & PF_OPT_QUIET) == 0)
+ fprintf(stderr, "altq cleared\n");
+ return (0);
+}
+
+int
+pfctl_clear_src_nodes(int dev, int opts)
+{
+ if (ioctl(dev, DIOCCLRSRCNODES))
+ err(1, "DIOCCLRSRCNODES");
+ if ((opts & PF_OPT_QUIET) == 0)
+ fprintf(stderr, "source tracking entries cleared\n");
+ return (0);
+}
+
+int
+pfctl_clear_states(int dev, const char *iface, int opts)
+{
+ struct pfioc_state_kill psk;
+
+ memset(&psk, 0, sizeof(psk));
+ if (iface != NULL && strlcpy(psk.psk_ifname, iface,
+ sizeof(psk.psk_ifname)) >= sizeof(psk.psk_ifname))
+ errx(1, "invalid interface: %s", iface);
+
+ if (ioctl(dev, DIOCCLRSTATES, &psk))
+ err(1, "DIOCCLRSTATES");
+ if ((opts & PF_OPT_QUIET) == 0)
+ fprintf(stderr, "%d states cleared\n", psk.psk_killed);
+ return (0);
+}
+
+void
+pfctl_addrprefix(char *addr, struct pf_addr *mask)
+{
+ char *p;
+ const char *errstr;
+ int prefix, ret_ga, q, r;
+ struct addrinfo hints, *res;
+
+ if ((p = strchr(addr, '/')) == NULL)
+ return;
+
+ *p++ = '\0';
+ prefix = strtonum(p, 0, 128, &errstr);
+ if (errstr)
+ errx(1, "prefix is %s: %s", errstr, p);
+
+ bzero(&hints, sizeof(hints));
+ /* prefix only with numeric addresses */
+ hints.ai_flags |= AI_NUMERICHOST;
+
+ if ((ret_ga = getaddrinfo(addr, NULL, &hints, &res))) {
+ errx(1, "getaddrinfo: %s", gai_strerror(ret_ga));
+ /* NOTREACHED */
+ }
+
+ if (res->ai_family == AF_INET && prefix > 32)
+ errx(1, "prefix too long for AF_INET");
+ else if (res->ai_family == AF_INET6 && prefix > 128)
+ errx(1, "prefix too long for AF_INET6");
+
+ q = prefix >> 3;
+ r = prefix & 7;
+ switch (res->ai_family) {
+ case AF_INET:
+ bzero(&mask->v4, sizeof(mask->v4));
+ mask->v4.s_addr = htonl((u_int32_t)
+ (0xffffffffffULL << (32 - prefix)));
+ break;
+ case AF_INET6:
+ bzero(&mask->v6, sizeof(mask->v6));
+ if (q > 0)
+ memset((void *)&mask->v6, 0xff, q);
+ if (r > 0)
+ *((u_char *)&mask->v6 + q) =
+ (0xff00 >> r) & 0xff;
+ break;
+ }
+ freeaddrinfo(res);
+}
+
+int
+pfctl_kill_src_nodes(int dev, const char *iface, int opts)
+{
+ struct pfioc_src_node_kill psnk;
+ struct addrinfo *res[2], *resp[2];
+ struct sockaddr last_src, last_dst;
+ int killed, sources, dests;
+ int ret_ga;
+
+ killed = sources = dests = 0;
+
+ memset(&psnk, 0, sizeof(psnk));
+ memset(&psnk.psnk_src.addr.v.a.mask, 0xff,
+ sizeof(psnk.psnk_src.addr.v.a.mask));
+ memset(&last_src, 0xff, sizeof(last_src));
+ memset(&last_dst, 0xff, sizeof(last_dst));
+
+ pfctl_addrprefix(src_node_kill[0], &psnk.psnk_src.addr.v.a.mask);
+
+ if ((ret_ga = getaddrinfo(src_node_kill[0], NULL, NULL, &res[0]))) {
+ errx(1, "getaddrinfo: %s", gai_strerror(ret_ga));
+ /* NOTREACHED */
+ }
+ for (resp[0] = res[0]; resp[0]; resp[0] = resp[0]->ai_next) {
+ if (resp[0]->ai_addr == NULL)
+ continue;
+ /* We get lots of duplicates. Catch the easy ones */
+ if (memcmp(&last_src, resp[0]->ai_addr, sizeof(last_src)) == 0)
+ continue;
+ last_src = *(struct sockaddr *)resp[0]->ai_addr;
+
+ psnk.psnk_af = resp[0]->ai_family;
+ sources++;
+
+ if (psnk.psnk_af == AF_INET)
+ psnk.psnk_src.addr.v.a.addr.v4 =
+ ((struct sockaddr_in *)resp[0]->ai_addr)->sin_addr;
+ else if (psnk.psnk_af == AF_INET6)
+ psnk.psnk_src.addr.v.a.addr.v6 =
+ ((struct sockaddr_in6 *)resp[0]->ai_addr)->
+ sin6_addr;
+ else
+ errx(1, "Unknown address family %d", psnk.psnk_af);
+
+ if (src_node_killers > 1) {
+ dests = 0;
+ memset(&psnk.psnk_dst.addr.v.a.mask, 0xff,
+ sizeof(psnk.psnk_dst.addr.v.a.mask));
+ memset(&last_dst, 0xff, sizeof(last_dst));
+ pfctl_addrprefix(src_node_kill[1],
+ &psnk.psnk_dst.addr.v.a.mask);
+ if ((ret_ga = getaddrinfo(src_node_kill[1], NULL, NULL,
+ &res[1]))) {
+ errx(1, "getaddrinfo: %s",
+ gai_strerror(ret_ga));
+ /* NOTREACHED */
+ }
+ for (resp[1] = res[1]; resp[1];
+ resp[1] = resp[1]->ai_next) {
+ if (resp[1]->ai_addr == NULL)
+ continue;
+ if (psnk.psnk_af != resp[1]->ai_family)
+ continue;
+
+ if (memcmp(&last_dst, resp[1]->ai_addr,
+ sizeof(last_dst)) == 0)
+ continue;
+ last_dst = *(struct sockaddr *)resp[1]->ai_addr;
+
+ dests++;
+
+ if (psnk.psnk_af == AF_INET)
+ psnk.psnk_dst.addr.v.a.addr.v4 =
+ ((struct sockaddr_in *)resp[1]->
+ ai_addr)->sin_addr;
+ else if (psnk.psnk_af == AF_INET6)
+ psnk.psnk_dst.addr.v.a.addr.v6 =
+ ((struct sockaddr_in6 *)resp[1]->
+ ai_addr)->sin6_addr;
+ else
+ errx(1, "Unknown address family %d",
+ psnk.psnk_af);
+
+ if (ioctl(dev, DIOCKILLSRCNODES, &psnk))
+ err(1, "DIOCKILLSRCNODES");
+ killed += psnk.psnk_killed;
+ }
+ freeaddrinfo(res[1]);
+ } else {
+ if (ioctl(dev, DIOCKILLSRCNODES, &psnk))
+ err(1, "DIOCKILLSRCNODES");
+ killed += psnk.psnk_killed;
+ }
+ }
+
+ freeaddrinfo(res[0]);
+
+ if ((opts & PF_OPT_QUIET) == 0)
+ fprintf(stderr, "killed %d src nodes from %d sources and %d "
+ "destinations\n", killed, sources, dests);
+ return (0);
+}
+
+int
+pfctl_net_kill_states(int dev, const char *iface, int opts)
+{
+ struct pfioc_state_kill psk;
+ struct addrinfo *res[2], *resp[2];
+ struct sockaddr last_src, last_dst;
+ int killed, sources, dests;
+ int ret_ga;
+
+ killed = sources = dests = 0;
+
+ memset(&psk, 0, sizeof(psk));
+ memset(&psk.psk_src.addr.v.a.mask, 0xff,
+ sizeof(psk.psk_src.addr.v.a.mask));
+ memset(&last_src, 0xff, sizeof(last_src));
+ memset(&last_dst, 0xff, sizeof(last_dst));
+ if (iface != NULL && strlcpy(psk.psk_ifname, iface,
+ sizeof(psk.psk_ifname)) >= sizeof(psk.psk_ifname))
+ errx(1, "invalid interface: %s", iface);
+
+ pfctl_addrprefix(state_kill[0], &psk.psk_src.addr.v.a.mask);
+
+ if ((ret_ga = getaddrinfo(state_kill[0], NULL, NULL, &res[0]))) {
+ errx(1, "getaddrinfo: %s", gai_strerror(ret_ga));
+ /* NOTREACHED */
+ }
+ for (resp[0] = res[0]; resp[0]; resp[0] = resp[0]->ai_next) {
+ if (resp[0]->ai_addr == NULL)
+ continue;
+ /* We get lots of duplicates. Catch the easy ones */
+ if (memcmp(&last_src, resp[0]->ai_addr, sizeof(last_src)) == 0)
+ continue;
+ last_src = *(struct sockaddr *)resp[0]->ai_addr;
+
+ psk.psk_af = resp[0]->ai_family;
+ sources++;
+
+ if (psk.psk_af == AF_INET)
+ psk.psk_src.addr.v.a.addr.v4 =
+ ((struct sockaddr_in *)resp[0]->ai_addr)->sin_addr;
+ else if (psk.psk_af == AF_INET6)
+ psk.psk_src.addr.v.a.addr.v6 =
+ ((struct sockaddr_in6 *)resp[0]->ai_addr)->
+ sin6_addr;
+ else
+ errx(1, "Unknown address family %d", psk.psk_af);
+
+ if (state_killers > 1) {
+ dests = 0;
+ memset(&psk.psk_dst.addr.v.a.mask, 0xff,
+ sizeof(psk.psk_dst.addr.v.a.mask));
+ memset(&last_dst, 0xff, sizeof(last_dst));
+ pfctl_addrprefix(state_kill[1],
+ &psk.psk_dst.addr.v.a.mask);
+ if ((ret_ga = getaddrinfo(state_kill[1], NULL, NULL,
+ &res[1]))) {
+ errx(1, "getaddrinfo: %s",
+ gai_strerror(ret_ga));
+ /* NOTREACHED */
+ }
+ for (resp[1] = res[1]; resp[1];
+ resp[1] = resp[1]->ai_next) {
+ if (resp[1]->ai_addr == NULL)
+ continue;
+ if (psk.psk_af != resp[1]->ai_family)
+ continue;
+
+ if (memcmp(&last_dst, resp[1]->ai_addr,
+ sizeof(last_dst)) == 0)
+ continue;
+ last_dst = *(struct sockaddr *)resp[1]->ai_addr;
+
+ dests++;
+
+ if (psk.psk_af == AF_INET)
+ psk.psk_dst.addr.v.a.addr.v4 =
+ ((struct sockaddr_in *)resp[1]->
+ ai_addr)->sin_addr;
+ else if (psk.psk_af == AF_INET6)
+ psk.psk_dst.addr.v.a.addr.v6 =
+ ((struct sockaddr_in6 *)resp[1]->
+ ai_addr)->sin6_addr;
+ else
+ errx(1, "Unknown address family %d",
+ psk.psk_af);
+
+ if (ioctl(dev, DIOCKILLSTATES, &psk))
+ err(1, "DIOCKILLSTATES");
+ killed += psk.psk_killed;
+ }
+ freeaddrinfo(res[1]);
+ } else {
+ if (ioctl(dev, DIOCKILLSTATES, &psk))
+ err(1, "DIOCKILLSTATES");
+ killed += psk.psk_killed;
+ }
+ }
+
+ freeaddrinfo(res[0]);
+
+ if ((opts & PF_OPT_QUIET) == 0)
+ fprintf(stderr, "killed %d states from %d sources and %d "
+ "destinations\n", killed, sources, dests);
+ return (0);
+}
+
+int
+pfctl_label_kill_states(int dev, const char *iface, int opts)
+{
+ struct pfioc_state_kill psk;
+
+ if (state_killers != 2 || (strlen(state_kill[1]) == 0)) {
+ warnx("no label specified");
+ usage();
+ }
+ memset(&psk, 0, sizeof(psk));
+ if (iface != NULL && strlcpy(psk.psk_ifname, iface,
+ sizeof(psk.psk_ifname)) >= sizeof(psk.psk_ifname))
+ errx(1, "invalid interface: %s", iface);
+
+ if (strlcpy(psk.psk_label, state_kill[1], sizeof(psk.psk_label)) >=
+ sizeof(psk.psk_label))
+ errx(1, "label too long: %s", state_kill[1]);
+
+ if (ioctl(dev, DIOCKILLSTATES, &psk))
+ err(1, "DIOCKILLSTATES");
+
+ if ((opts & PF_OPT_QUIET) == 0)
+ fprintf(stderr, "killed %d states\n", psk.psk_killed);
+
+ return (0);
+}
+
+int
+pfctl_id_kill_states(int dev, const char *iface, int opts)
+{
+ struct pfioc_state_kill psk;
+
+ if (state_killers != 2 || (strlen(state_kill[1]) == 0)) {
+ warnx("no id specified");
+ usage();
+ }
+
+ memset(&psk, 0, sizeof(psk));
+ if ((sscanf(state_kill[1], "%jx/%x",
+ &psk.psk_pfcmp.id, &psk.psk_pfcmp.creatorid)) == 2)
+ HTONL(psk.psk_pfcmp.creatorid);
+ else if ((sscanf(state_kill[1], "%jx", &psk.psk_pfcmp.id)) == 1) {
+ psk.psk_pfcmp.creatorid = 0;
+ } else {
+ warnx("wrong id format specified");
+ usage();
+ }
+ if (psk.psk_pfcmp.id == 0) {
+ warnx("cannot kill id 0");
+ usage();
+ }
+
+ psk.psk_pfcmp.id = htobe64(psk.psk_pfcmp.id);
+ if (ioctl(dev, DIOCKILLSTATES, &psk))
+ err(1, "DIOCKILLSTATES");
+
+ if ((opts & PF_OPT_QUIET) == 0)
+ fprintf(stderr, "killed %d states\n", psk.psk_killed);
+
+ return (0);
+}
+
+int
+pfctl_get_pool(int dev, struct pf_pool *pool, u_int32_t nr,
+ u_int32_t ticket, int r_action, char *anchorname)
+{
+ struct pfioc_pooladdr pp;
+ struct pf_pooladdr *pa;
+ u_int32_t pnr, mpnr;
+
+ memset(&pp, 0, sizeof(pp));
+ memcpy(pp.anchor, anchorname, sizeof(pp.anchor));
+ pp.r_action = r_action;
+ pp.r_num = nr;
+ pp.ticket = ticket;
+ if (ioctl(dev, DIOCGETADDRS, &pp)) {
+ warn("DIOCGETADDRS");
+ return (-1);
+ }
+ mpnr = pp.nr;
+ TAILQ_INIT(&pool->list);
+ for (pnr = 0; pnr < mpnr; ++pnr) {
+ pp.nr = pnr;
+ if (ioctl(dev, DIOCGETADDR, &pp)) {
+ warn("DIOCGETADDR");
+ return (-1);
+ }
+ pa = calloc(1, sizeof(struct pf_pooladdr));
+ if (pa == NULL)
+ err(1, "calloc");
+ bcopy(&pp.addr, pa, sizeof(struct pf_pooladdr));
+ TAILQ_INSERT_TAIL(&pool->list, pa, entries);
+ }
+
+ return (0);
+}
+
+void
+pfctl_move_pool(struct pf_pool *src, struct pf_pool *dst)
+{
+ struct pf_pooladdr *pa;
+
+ while ((pa = TAILQ_FIRST(&src->list)) != NULL) {
+ TAILQ_REMOVE(&src->list, pa, entries);
+ TAILQ_INSERT_TAIL(&dst->list, pa, entries);
+ }
+}
+
+void
+pfctl_clear_pool(struct pf_pool *pool)
+{
+ struct pf_pooladdr *pa;
+
+ while ((pa = TAILQ_FIRST(&pool->list)) != NULL) {
+ TAILQ_REMOVE(&pool->list, pa, entries);
+ free(pa);
+ }
+}
+
+void
+pfctl_print_rule_counters(struct pf_rule *rule, int opts)
+{
+ if (opts & PF_OPT_DEBUG) {
+ const char *t[PF_SKIP_COUNT] = { "i", "d", "f",
+ "p", "sa", "sp", "da", "dp" };
+ int i;
+
+ printf(" [ Skip steps: ");
+ for (i = 0; i < PF_SKIP_COUNT; ++i) {
+ if (rule->skip[i].nr == rule->nr + 1)
+ continue;
+ printf("%s=", t[i]);
+ if (rule->skip[i].nr == -1)
+ printf("end ");
+ else
+ printf("%u ", rule->skip[i].nr);
+ }
+ printf("]\n");
+
+ printf(" [ queue: qname=%s qid=%u pqname=%s pqid=%u ]\n",
+ rule->qname, rule->qid, rule->pqname, rule->pqid);
+ }
+ if (opts & PF_OPT_VERBOSE) {
+ printf(" [ Evaluations: %-8llu Packets: %-8llu "
+ "Bytes: %-10llu States: %-6ju]\n",
+ (unsigned long long)rule->evaluations,
+ (unsigned long long)(rule->packets[0] +
+ rule->packets[1]),
+ (unsigned long long)(rule->bytes[0] +
+ rule->bytes[1]), (uintmax_t)rule->u_states_cur);
+ if (!(opts & PF_OPT_DEBUG))
+ printf(" [ Inserted: uid %u pid %u "
+ "State Creations: %-6ju]\n",
+ (unsigned)rule->cuid, (unsigned)rule->cpid,
+ (uintmax_t)rule->u_states_tot);
+ }
+}
+
+void
+pfctl_print_title(char *title)
+{
+ if (!first_title)
+ printf("\n");
+ first_title = 0;
+ printf("%s\n", title);
+}
+
+int
+pfctl_show_rules(int dev, char *path, int opts, enum pfctl_show format,
+ char *anchorname, int depth)
+{
+ struct pfioc_rule pr;
+ u_int32_t nr, mnr, header = 0;
+ int rule_numbers = opts & (PF_OPT_VERBOSE2 | PF_OPT_DEBUG);
+ int numeric = opts & PF_OPT_NUMERIC;
+ int len = strlen(path);
+ int brace;
+ char *p;
+
+ if (path[0])
+ snprintf(&path[len], MAXPATHLEN - len, "/%s", anchorname);
+ else
+ snprintf(&path[len], MAXPATHLEN - len, "%s", anchorname);
+
+ memset(&pr, 0, sizeof(pr));
+ memcpy(pr.anchor, path, sizeof(pr.anchor));
+ if (opts & PF_OPT_SHOWALL) {
+ pr.rule.action = PF_PASS;
+ if (ioctl(dev, DIOCGETRULES, &pr)) {
+ warn("DIOCGETRULES");
+ goto error;
+ }
+ header++;
+ }
+ pr.rule.action = PF_SCRUB;
+ if (ioctl(dev, DIOCGETRULES, &pr)) {
+ warn("DIOCGETRULES");
+ goto error;
+ }
+ if (opts & PF_OPT_SHOWALL) {
+ if (format == PFCTL_SHOW_RULES && (pr.nr > 0 || header))
+ pfctl_print_title("FILTER RULES:");
+ else if (format == PFCTL_SHOW_LABELS && labels)
+ pfctl_print_title("LABEL COUNTERS:");
+ }
+ mnr = pr.nr;
+ if (opts & PF_OPT_CLRRULECTRS)
+ pr.action = PF_GET_CLR_CNTR;
+
+ for (nr = 0; nr < mnr; ++nr) {
+ pr.nr = nr;
+ if (ioctl(dev, DIOCGETRULE, &pr)) {
+ warn("DIOCGETRULE");
+ goto error;
+ }
+
+ if (pfctl_get_pool(dev, &pr.rule.rpool,
+ nr, pr.ticket, PF_SCRUB, path) != 0)
+ goto error;
+
+ switch (format) {
+ case PFCTL_SHOW_LABELS:
+ break;
+ case PFCTL_SHOW_RULES:
+ if (pr.rule.label[0] && (opts & PF_OPT_SHOWALL))
+ labels = 1;
+ print_rule(&pr.rule, pr.anchor_call, rule_numbers, numeric);
+ printf("\n");
+ pfctl_print_rule_counters(&pr.rule, opts);
+ break;
+ case PFCTL_SHOW_NOTHING:
+ break;
+ }
+ pfctl_clear_pool(&pr.rule.rpool);
+ }
+ pr.rule.action = PF_PASS;
+ if (ioctl(dev, DIOCGETRULES, &pr)) {
+ warn("DIOCGETRULES");
+ goto error;
+ }
+ mnr = pr.nr;
+ for (nr = 0; nr < mnr; ++nr) {
+ pr.nr = nr;
+ if (ioctl(dev, DIOCGETRULE, &pr)) {
+ warn("DIOCGETRULE");
+ goto error;
+ }
+
+ if (pfctl_get_pool(dev, &pr.rule.rpool,
+ nr, pr.ticket, PF_PASS, path) != 0)
+ goto error;
+
+ switch (format) {
+ case PFCTL_SHOW_LABELS:
+ if (pr.rule.label[0]) {
+ printf("%s %llu %llu %llu %llu"
+ " %llu %llu %llu %ju\n",
+ pr.rule.label,
+ (unsigned long long)pr.rule.evaluations,
+ (unsigned long long)(pr.rule.packets[0] +
+ pr.rule.packets[1]),
+ (unsigned long long)(pr.rule.bytes[0] +
+ pr.rule.bytes[1]),
+ (unsigned long long)pr.rule.packets[0],
+ (unsigned long long)pr.rule.bytes[0],
+ (unsigned long long)pr.rule.packets[1],
+ (unsigned long long)pr.rule.bytes[1],
+ (uintmax_t)pr.rule.u_states_tot);
+ }
+ break;
+ case PFCTL_SHOW_RULES:
+ brace = 0;
+ if (pr.rule.label[0] && (opts & PF_OPT_SHOWALL))
+ labels = 1;
+ INDENT(depth, !(opts & PF_OPT_VERBOSE));
+ if (pr.anchor_call[0] &&
+ ((((p = strrchr(pr.anchor_call, '_')) != NULL) &&
+ ((void *)p == (void *)pr.anchor_call ||
+ *(--p) == '/')) || (opts & PF_OPT_RECURSE))) {
+ brace++;
+ if ((p = strrchr(pr.anchor_call, '/')) !=
+ NULL)
+ p++;
+ else
+ p = &pr.anchor_call[0];
+ } else
+ p = &pr.anchor_call[0];
+
+ print_rule(&pr.rule, p, rule_numbers, numeric);
+ if (brace)
+ printf(" {\n");
+ else
+ printf("\n");
+ pfctl_print_rule_counters(&pr.rule, opts);
+ if (brace) {
+ pfctl_show_rules(dev, path, opts, format,
+ p, depth + 1);
+ INDENT(depth, !(opts & PF_OPT_VERBOSE));
+ printf("}\n");
+ }
+ break;
+ case PFCTL_SHOW_NOTHING:
+ break;
+ }
+ pfctl_clear_pool(&pr.rule.rpool);
+ }
+ path[len] = '\0';
+ return (0);
+
+ error:
+ path[len] = '\0';
+ return (-1);
+}
+
+int
+pfctl_show_nat(int dev, int opts, char *anchorname)
+{
+ struct pfioc_rule pr;
+ u_int32_t mnr, nr;
+ static int nattype[3] = { PF_NAT, PF_RDR, PF_BINAT };
+ int i, dotitle = opts & PF_OPT_SHOWALL;
+
+ memset(&pr, 0, sizeof(pr));
+ memcpy(pr.anchor, anchorname, sizeof(pr.anchor));
+ for (i = 0; i < 3; i++) {
+ pr.rule.action = nattype[i];
+ if (ioctl(dev, DIOCGETRULES, &pr)) {
+ warn("DIOCGETRULES");
+ return (-1);
+ }
+ mnr = pr.nr;
+ for (nr = 0; nr < mnr; ++nr) {
+ pr.nr = nr;
+ if (ioctl(dev, DIOCGETRULE, &pr)) {
+ warn("DIOCGETRULE");
+ return (-1);
+ }
+ if (pfctl_get_pool(dev, &pr.rule.rpool, nr,
+ pr.ticket, nattype[i], anchorname) != 0)
+ return (-1);
+ if (dotitle) {
+ pfctl_print_title("TRANSLATION RULES:");
+ dotitle = 0;
+ }
+ print_rule(&pr.rule, pr.anchor_call,
+ opts & PF_OPT_VERBOSE2, opts & PF_OPT_NUMERIC);
+ printf("\n");
+ pfctl_print_rule_counters(&pr.rule, opts);
+ pfctl_clear_pool(&pr.rule.rpool);
+ }
+ }
+ return (0);
+}
+
+int
+pfctl_show_src_nodes(int dev, int opts)
+{
+ struct pfioc_src_nodes psn;
+ struct pf_src_node *p;
+ char *inbuf = NULL, *newinbuf = NULL;
+ unsigned int len = 0;
+ int i;
+
+ memset(&psn, 0, sizeof(psn));
+ for (;;) {
+ psn.psn_len = len;
+ if (len) {
+ newinbuf = realloc(inbuf, len);
+ if (newinbuf == NULL)
+ err(1, "realloc");
+ psn.psn_buf = inbuf = newinbuf;
+ }
+ if (ioctl(dev, DIOCGETSRCNODES, &psn) < 0) {
+ warn("DIOCGETSRCNODES");
+ free(inbuf);
+ return (-1);
+ }
+ if (psn.psn_len + sizeof(struct pfioc_src_nodes) < len)
+ break;
+ if (len == 0 && psn.psn_len == 0)
+ goto done;
+ if (len == 0 && psn.psn_len != 0)
+ len = psn.psn_len;
+ if (psn.psn_len == 0)
+ goto done; /* no src_nodes */
+ len *= 2;
+ }
+ p = psn.psn_src_nodes;
+ if (psn.psn_len > 0 && (opts & PF_OPT_SHOWALL))
+ pfctl_print_title("SOURCE TRACKING NODES:");
+ for (i = 0; i < psn.psn_len; i += sizeof(*p)) {
+ print_src_node(p, opts);
+ p++;
+ }
+done:
+ free(inbuf);
+ return (0);
+}
+
+int
+pfctl_show_states(int dev, const char *iface, int opts)
+{
+ struct pfioc_states ps;
+ struct pfsync_state *p;
+ char *inbuf = NULL, *newinbuf = NULL;
+ unsigned int len = 0;
+ int i, dotitle = (opts & PF_OPT_SHOWALL);
+
+ memset(&ps, 0, sizeof(ps));
+ for (;;) {
+ ps.ps_len = len;
+ if (len) {
+ newinbuf = realloc(inbuf, len);
+ if (newinbuf == NULL)
+ err(1, "realloc");
+ ps.ps_buf = inbuf = newinbuf;
+ }
+ if (ioctl(dev, DIOCGETSTATES, &ps) < 0) {
+ warn("DIOCGETSTATES");
+ free(inbuf);
+ return (-1);
+ }
+ if (ps.ps_len + sizeof(struct pfioc_states) < len)
+ break;
+ if (len == 0 && ps.ps_len == 0)
+ goto done;
+ if (len == 0 && ps.ps_len != 0)
+ len = ps.ps_len;
+ if (ps.ps_len == 0)
+ goto done; /* no states */
+ len *= 2;
+ }
+ p = ps.ps_states;
+ for (i = 0; i < ps.ps_len; i += sizeof(*p), p++) {
+ if (iface != NULL && strcmp(p->ifname, iface))
+ continue;
+ if (dotitle) {
+ pfctl_print_title("STATES:");
+ dotitle = 0;
+ }
+ print_state(p, opts);
+ }
+done:
+ free(inbuf);
+ return (0);
+}
+
+int
+pfctl_show_status(int dev, int opts)
+{
+ struct pf_status status;
+
+ if (ioctl(dev, DIOCGETSTATUS, &status)) {
+ warn("DIOCGETSTATUS");
+ return (-1);
+ }
+ if (opts & PF_OPT_SHOWALL)
+ pfctl_print_title("INFO:");
+ print_status(&status, opts);
+ return (0);
+}
+
+int
+pfctl_show_timeouts(int dev, int opts)
+{
+ struct pfioc_tm pt;
+ int i;
+
+ if (opts & PF_OPT_SHOWALL)
+ pfctl_print_title("TIMEOUTS:");
+ memset(&pt, 0, sizeof(pt));
+ for (i = 0; pf_timeouts[i].name; i++) {
+ pt.timeout = pf_timeouts[i].timeout;
+ if (ioctl(dev, DIOCGETTIMEOUT, &pt))
+ err(1, "DIOCGETTIMEOUT");
+ printf("%-20s %10d", pf_timeouts[i].name, pt.seconds);
+ if (pf_timeouts[i].timeout >= PFTM_ADAPTIVE_START &&
+ pf_timeouts[i].timeout <= PFTM_ADAPTIVE_END)
+ printf(" states");
+ else
+ printf("s");
+ printf("\n");
+ }
+ return (0);
+
+}
+
+int
+pfctl_show_limits(int dev, int opts)
+{
+ struct pfioc_limit pl;
+ int i;
+
+ if (opts & PF_OPT_SHOWALL)
+ pfctl_print_title("LIMITS:");
+ memset(&pl, 0, sizeof(pl));
+ for (i = 0; pf_limits[i].name; i++) {
+ pl.index = pf_limits[i].index;
+ if (ioctl(dev, DIOCGETLIMIT, &pl))
+ err(1, "DIOCGETLIMIT");
+ printf("%-13s ", pf_limits[i].name);
+ if (pl.limit == UINT_MAX)
+ printf("unlimited\n");
+ else
+ printf("hard limit %8u\n", pl.limit);
+ }
+ return (0);
+}
+
+/* callbacks for rule/nat/rdr/addr */
+int
+pfctl_add_pool(struct pfctl *pf, struct pf_pool *p, sa_family_t af)
+{
+ struct pf_pooladdr *pa;
+
+ if ((pf->opts & PF_OPT_NOACTION) == 0) {
+ if (ioctl(pf->dev, DIOCBEGINADDRS, &pf->paddr))
+ err(1, "DIOCBEGINADDRS");
+ }
+
+ pf->paddr.af = af;
+ TAILQ_FOREACH(pa, &p->list, entries) {
+ memcpy(&pf->paddr.addr, pa, sizeof(struct pf_pooladdr));
+ if ((pf->opts & PF_OPT_NOACTION) == 0) {
+ if (ioctl(pf->dev, DIOCADDADDR, &pf->paddr))
+ err(1, "DIOCADDADDR");
+ }
+ }
+ return (0);
+}
+
+int
+pfctl_add_rule(struct pfctl *pf, struct pf_rule *r, const char *anchor_call)
+{
+ u_int8_t rs_num;
+ struct pf_rule *rule;
+ struct pf_ruleset *rs;
+ char *p;
+
+ rs_num = pf_get_ruleset_number(r->action);
+ if (rs_num == PF_RULESET_MAX)
+ errx(1, "Invalid rule type %d", r->action);
+
+ rs = &pf->anchor->ruleset;
+
+ if (anchor_call[0] && r->anchor == NULL) {
+ /*
+ * Don't make non-brace anchors part of the main anchor pool.
+ */
+ if ((r->anchor = calloc(1, sizeof(*r->anchor))) == NULL)
+ err(1, "pfctl_add_rule: calloc");
+
+ pf_init_ruleset(&r->anchor->ruleset);
+ r->anchor->ruleset.anchor = r->anchor;
+ if (strlcpy(r->anchor->path, anchor_call,
+ sizeof(rule->anchor->path)) >= sizeof(rule->anchor->path))
+ errx(1, "pfctl_add_rule: strlcpy");
+ if ((p = strrchr(anchor_call, '/')) != NULL) {
+ if (!strlen(p))
+ err(1, "pfctl_add_rule: bad anchor name %s",
+ anchor_call);
+ } else
+ p = (char *)anchor_call;
+ if (strlcpy(r->anchor->name, p,
+ sizeof(rule->anchor->name)) >= sizeof(rule->anchor->name))
+ errx(1, "pfctl_add_rule: strlcpy");
+ }
+
+ if ((rule = calloc(1, sizeof(*rule))) == NULL)
+ err(1, "calloc");
+ bcopy(r, rule, sizeof(*rule));
+ TAILQ_INIT(&rule->rpool.list);
+ pfctl_move_pool(&r->rpool, &rule->rpool);
+
+ TAILQ_INSERT_TAIL(rs->rules[rs_num].active.ptr, rule, entries);
+ return (0);
+}
+
+int
+pfctl_ruleset_trans(struct pfctl *pf, char *path, struct pf_anchor *a)
+{
+ int osize = pf->trans->pfrb_size;
+
+ if ((pf->loadopt & PFCTL_FLAG_NAT) != 0) {
+ if (pfctl_add_trans(pf->trans, PF_RULESET_NAT, path) ||
+ pfctl_add_trans(pf->trans, PF_RULESET_BINAT, path) ||
+ pfctl_add_trans(pf->trans, PF_RULESET_RDR, path))
+ return (1);
+ }
+ if (a == pf->astack[0] && ((altqsupport &&
+ (pf->loadopt & PFCTL_FLAG_ALTQ) != 0))) {
+ if (pfctl_add_trans(pf->trans, PF_RULESET_ALTQ, path))
+ return (2);
+ }
+ if ((pf->loadopt & PFCTL_FLAG_FILTER) != 0) {
+ if (pfctl_add_trans(pf->trans, PF_RULESET_SCRUB, path) ||
+ pfctl_add_trans(pf->trans, PF_RULESET_FILTER, path))
+ return (3);
+ }
+ if (pf->loadopt & PFCTL_FLAG_TABLE)
+ if (pfctl_add_trans(pf->trans, PF_RULESET_TABLE, path))
+ return (4);
+ if (pfctl_trans(pf->dev, pf->trans, DIOCXBEGIN, osize))
+ return (5);
+
+ return (0);
+}
+
+int
+pfctl_load_ruleset(struct pfctl *pf, char *path, struct pf_ruleset *rs,
+ int rs_num, int depth)
+{
+ struct pf_rule *r;
+ int error, len = strlen(path);
+ int brace = 0;
+
+ pf->anchor = rs->anchor;
+
+ if (path[0])
+ snprintf(&path[len], MAXPATHLEN - len, "/%s", pf->anchor->name);
+ else
+ snprintf(&path[len], MAXPATHLEN - len, "%s", pf->anchor->name);
+
+ if (depth) {
+ if (TAILQ_FIRST(rs->rules[rs_num].active.ptr) != NULL) {
+ brace++;
+ if (pf->opts & PF_OPT_VERBOSE)
+ printf(" {\n");
+ if ((pf->opts & PF_OPT_NOACTION) == 0 &&
+ (error = pfctl_ruleset_trans(pf,
+ path, rs->anchor))) {
+ printf("pfctl_load_rulesets: "
+ "pfctl_ruleset_trans %d\n", error);
+ goto error;
+ }
+ } else if (pf->opts & PF_OPT_VERBOSE)
+ printf("\n");
+
+ }
+
+ if (pf->optimize && rs_num == PF_RULESET_FILTER)
+ pfctl_optimize_ruleset(pf, rs);
+
+ while ((r = TAILQ_FIRST(rs->rules[rs_num].active.ptr)) != NULL) {
+ TAILQ_REMOVE(rs->rules[rs_num].active.ptr, r, entries);
+ if ((error = pfctl_load_rule(pf, path, r, depth)))
+ goto error;
+ if (r->anchor) {
+ if ((error = pfctl_load_ruleset(pf, path,
+ &r->anchor->ruleset, rs_num, depth + 1)))
+ goto error;
+ } else if (pf->opts & PF_OPT_VERBOSE)
+ printf("\n");
+ free(r);
+ }
+ if (brace && pf->opts & PF_OPT_VERBOSE) {
+ INDENT(depth - 1, (pf->opts & PF_OPT_VERBOSE));
+ printf("}\n");
+ }
+ path[len] = '\0';
+ return (0);
+
+ error:
+ path[len] = '\0';
+ return (error);
+
+}
+
+int
+pfctl_load_rule(struct pfctl *pf, char *path, struct pf_rule *r, int depth)
+{
+ u_int8_t rs_num = pf_get_ruleset_number(r->action);
+ char *name;
+ struct pfioc_rule pr;
+ int len = strlen(path);
+
+ bzero(&pr, sizeof(pr));
+ /* set up anchor before adding to path for anchor_call */
+ if ((pf->opts & PF_OPT_NOACTION) == 0)
+ pr.ticket = pfctl_get_ticket(pf->trans, rs_num, path);
+ if (strlcpy(pr.anchor, path, sizeof(pr.anchor)) >= sizeof(pr.anchor))
+ errx(1, "pfctl_load_rule: strlcpy");
+
+ if (r->anchor) {
+ if (r->anchor->match) {
+ if (path[0])
+ snprintf(&path[len], MAXPATHLEN - len,
+ "/%s", r->anchor->name);
+ else
+ snprintf(&path[len], MAXPATHLEN - len,
+ "%s", r->anchor->name);
+ name = path;
+ } else
+ name = r->anchor->path;
+ } else
+ name = "";
+
+ if ((pf->opts & PF_OPT_NOACTION) == 0) {
+ if (pfctl_add_pool(pf, &r->rpool, r->af))
+ return (1);
+ pr.pool_ticket = pf->paddr.ticket;
+ memcpy(&pr.rule, r, sizeof(pr.rule));
+ if (r->anchor && strlcpy(pr.anchor_call, name,
+ sizeof(pr.anchor_call)) >= sizeof(pr.anchor_call))
+ errx(1, "pfctl_load_rule: strlcpy");
+ if (ioctl(pf->dev, DIOCADDRULE, &pr))
+ err(1, "DIOCADDRULE");
+ }
+
+ if (pf->opts & PF_OPT_VERBOSE) {
+ INDENT(depth, !(pf->opts & PF_OPT_VERBOSE2));
+ print_rule(r, r->anchor ? r->anchor->name : "",
+ pf->opts & PF_OPT_VERBOSE2,
+ pf->opts & PF_OPT_NUMERIC);
+ }
+ path[len] = '\0';
+ pfctl_clear_pool(&r->rpool);
+ return (0);
+}
+
+int
+pfctl_add_altq(struct pfctl *pf, struct pf_altq *a)
+{
+ if (altqsupport &&
+ (loadopt & PFCTL_FLAG_ALTQ) != 0) {
+ memcpy(&pf->paltq->altq, a, sizeof(struct pf_altq));
+ if ((pf->opts & PF_OPT_NOACTION) == 0) {
+ if (ioctl(pf->dev, DIOCADDALTQ, pf->paltq)) {
+ if (errno == ENXIO)
+ errx(1, "qtype not configured");
+ else if (errno == ENODEV)
+ errx(1, "%s: driver does not support "
+ "altq", a->ifname);
+ else
+ err(1, "DIOCADDALTQ");
+ }
+ }
+ pfaltq_store(&pf->paltq->altq);
+ }
+ return (0);
+}
+
+int
+pfctl_rules(int dev, char *filename, int opts, int optimize,
+ char *anchorname, struct pfr_buffer *trans)
+{
+#define ERR(x) do { warn(x); goto _error; } while(0)
+#define ERRX(x) do { warnx(x); goto _error; } while(0)
+
+ struct pfr_buffer *t, buf;
+ struct pfioc_altq pa;
+ struct pfctl pf;
+ struct pf_ruleset *rs;
+ struct pfr_table trs;
+ char *path;
+ int osize;
+
+ RB_INIT(&pf_anchors);
+ memset(&pf_main_anchor, 0, sizeof(pf_main_anchor));
+ pf_init_ruleset(&pf_main_anchor.ruleset);
+ pf_main_anchor.ruleset.anchor = &pf_main_anchor;
+ if (trans == NULL) {
+ bzero(&buf, sizeof(buf));
+ buf.pfrb_type = PFRB_TRANS;
+ t = &buf;
+ osize = 0;
+ } else {
+ t = trans;
+ osize = t->pfrb_size;
+ }
+
+ memset(&pa, 0, sizeof(pa));
+ memset(&pf, 0, sizeof(pf));
+ memset(&trs, 0, sizeof(trs));
+ if ((path = calloc(1, MAXPATHLEN)) == NULL)
+ ERRX("pfctl_rules: calloc");
+ if (strlcpy(trs.pfrt_anchor, anchorname,
+ sizeof(trs.pfrt_anchor)) >= sizeof(trs.pfrt_anchor))
+ ERRX("pfctl_rules: strlcpy");
+ pf.dev = dev;
+ pf.opts = opts;
+ pf.optimize = optimize;
+ pf.loadopt = loadopt;
+
+ /* non-brace anchor, create without resolving the path */
+ if ((pf.anchor = calloc(1, sizeof(*pf.anchor))) == NULL)
+ ERRX("pfctl_rules: calloc");
+ rs = &pf.anchor->ruleset;
+ pf_init_ruleset(rs);
+ rs->anchor = pf.anchor;
+ if (strlcpy(pf.anchor->path, anchorname,
+ sizeof(pf.anchor->path)) >= sizeof(pf.anchor->path))
+ errx(1, "pfctl_add_rule: strlcpy");
+ if (strlcpy(pf.anchor->name, anchorname,
+ sizeof(pf.anchor->name)) >= sizeof(pf.anchor->name))
+ errx(1, "pfctl_add_rule: strlcpy");
+
+
+ pf.astack[0] = pf.anchor;
+ pf.asd = 0;
+ if (anchorname[0])
+ pf.loadopt &= ~PFCTL_FLAG_ALTQ;
+ pf.paltq = &pa;
+ pf.trans = t;
+ pfctl_init_options(&pf);
+
+ if ((opts & PF_OPT_NOACTION) == 0) {
+ /*
+ * XXX For the time being we need to open transactions for
+ * the main ruleset before parsing, because tables are still
+ * loaded at parse time.
+ */
+ if (pfctl_ruleset_trans(&pf, anchorname, pf.anchor))
+ ERRX("pfctl_rules");
+ if (altqsupport && (pf.loadopt & PFCTL_FLAG_ALTQ))
+ pa.ticket =
+ pfctl_get_ticket(t, PF_RULESET_ALTQ, anchorname);
+ if (pf.loadopt & PFCTL_FLAG_TABLE)
+ pf.astack[0]->ruleset.tticket =
+ pfctl_get_ticket(t, PF_RULESET_TABLE, anchorname);
+ }
+
+ if (parse_config(filename, &pf) < 0) {
+ if ((opts & PF_OPT_NOACTION) == 0)
+ ERRX("Syntax error in config file: "
+ "pf rules not loaded");
+ else
+ goto _error;
+ }
+
+ if ((pf.loadopt & PFCTL_FLAG_FILTER &&
+ (pfctl_load_ruleset(&pf, path, rs, PF_RULESET_SCRUB, 0))) ||
+ (pf.loadopt & PFCTL_FLAG_NAT &&
+ (pfctl_load_ruleset(&pf, path, rs, PF_RULESET_NAT, 0) ||
+ pfctl_load_ruleset(&pf, path, rs, PF_RULESET_RDR, 0) ||
+ pfctl_load_ruleset(&pf, path, rs, PF_RULESET_BINAT, 0))) ||
+ (pf.loadopt & PFCTL_FLAG_FILTER &&
+ pfctl_load_ruleset(&pf, path, rs, PF_RULESET_FILTER, 0))) {
+ if ((opts & PF_OPT_NOACTION) == 0)
+ ERRX("Unable to load rules into kernel");
+ else
+ goto _error;
+ }
+
+ if ((altqsupport && (pf.loadopt & PFCTL_FLAG_ALTQ) != 0))
+ if (check_commit_altq(dev, opts) != 0)
+ ERRX("errors in altq config");
+
+ /* process "load anchor" directives */
+ if (!anchorname[0])
+ if (pfctl_load_anchors(dev, &pf, t) == -1)
+ ERRX("load anchors");
+
+ if (trans == NULL && (opts & PF_OPT_NOACTION) == 0) {
+ if (!anchorname[0])
+ if (pfctl_load_options(&pf))
+ goto _error;
+ if (pfctl_trans(dev, t, DIOCXCOMMIT, osize))
+ ERR("DIOCXCOMMIT");
+ }
+ return (0);
+
+_error:
+ if (trans == NULL) { /* main ruleset */
+ if ((opts & PF_OPT_NOACTION) == 0)
+ if (pfctl_trans(dev, t, DIOCXROLLBACK, osize))
+ err(1, "DIOCXROLLBACK");
+ exit(1);
+ } else { /* sub ruleset */
+ return (-1);
+ }
+
+#undef ERR
+#undef ERRX
+}
+
+FILE *
+pfctl_fopen(const char *name, const char *mode)
+{
+ struct stat st;
+ FILE *fp;
+
+ fp = fopen(name, mode);
+ if (fp == NULL)
+ return (NULL);
+ if (fstat(fileno(fp), &st)) {
+ fclose(fp);
+ return (NULL);
+ }
+ if (S_ISDIR(st.st_mode)) {
+ fclose(fp);
+ errno = EISDIR;
+ return (NULL);
+ }
+ return (fp);
+}
+
+void
+pfctl_init_options(struct pfctl *pf)
+{
+
+ pf->timeout[PFTM_TCP_FIRST_PACKET] = PFTM_TCP_FIRST_PACKET_VAL;
+ pf->timeout[PFTM_TCP_OPENING] = PFTM_TCP_OPENING_VAL;
+ pf->timeout[PFTM_TCP_ESTABLISHED] = PFTM_TCP_ESTABLISHED_VAL;
+ pf->timeout[PFTM_TCP_CLOSING] = PFTM_TCP_CLOSING_VAL;
+ pf->timeout[PFTM_TCP_FIN_WAIT] = PFTM_TCP_FIN_WAIT_VAL;
+ pf->timeout[PFTM_TCP_CLOSED] = PFTM_TCP_CLOSED_VAL;
+ pf->timeout[PFTM_UDP_FIRST_PACKET] = PFTM_UDP_FIRST_PACKET_VAL;
+ pf->timeout[PFTM_UDP_SINGLE] = PFTM_UDP_SINGLE_VAL;
+ pf->timeout[PFTM_UDP_MULTIPLE] = PFTM_UDP_MULTIPLE_VAL;
+ pf->timeout[PFTM_ICMP_FIRST_PACKET] = PFTM_ICMP_FIRST_PACKET_VAL;
+ pf->timeout[PFTM_ICMP_ERROR_REPLY] = PFTM_ICMP_ERROR_REPLY_VAL;
+ pf->timeout[PFTM_OTHER_FIRST_PACKET] = PFTM_OTHER_FIRST_PACKET_VAL;
+ pf->timeout[PFTM_OTHER_SINGLE] = PFTM_OTHER_SINGLE_VAL;
+ pf->timeout[PFTM_OTHER_MULTIPLE] = PFTM_OTHER_MULTIPLE_VAL;
+ pf->timeout[PFTM_FRAG] = PFTM_FRAG_VAL;
+ pf->timeout[PFTM_INTERVAL] = PFTM_INTERVAL_VAL;
+ pf->timeout[PFTM_SRC_NODE] = PFTM_SRC_NODE_VAL;
+ pf->timeout[PFTM_TS_DIFF] = PFTM_TS_DIFF_VAL;
+ pf->timeout[PFTM_ADAPTIVE_START] = PFSTATE_ADAPT_START;
+ pf->timeout[PFTM_ADAPTIVE_END] = PFSTATE_ADAPT_END;
+
+ pf->limit[PF_LIMIT_STATES] = PFSTATE_HIWAT;
+ pf->limit[PF_LIMIT_FRAGS] = PFFRAG_FRENT_HIWAT;
+ pf->limit[PF_LIMIT_SRC_NODES] = PFSNODE_HIWAT;
+ pf->limit[PF_LIMIT_TABLE_ENTRIES] = PFR_KENTRY_HIWAT;
+
+ pf->debug = PF_DEBUG_URGENT;
+}
+
+int
+pfctl_load_options(struct pfctl *pf)
+{
+ int i, error = 0;
+
+ if ((loadopt & PFCTL_FLAG_OPTION) == 0)
+ return (0);
+
+ /* load limits */
+ for (i = 0; i < PF_LIMIT_MAX; i++) {
+ if ((pf->opts & PF_OPT_MERGE) && !pf->limit_set[i])
+ continue;
+ if (pfctl_load_limit(pf, i, pf->limit[i]))
+ error = 1;
+ }
+
+ /*
+ * If we've set the limit, but haven't explicitly set adaptive
+ * timeouts, do it now with a start of 60% and end of 120%.
+ */
+ if (pf->limit_set[PF_LIMIT_STATES] &&
+ !pf->timeout_set[PFTM_ADAPTIVE_START] &&
+ !pf->timeout_set[PFTM_ADAPTIVE_END]) {
+ pf->timeout[PFTM_ADAPTIVE_START] =
+ (pf->limit[PF_LIMIT_STATES] / 10) * 6;
+ pf->timeout_set[PFTM_ADAPTIVE_START] = 1;
+ pf->timeout[PFTM_ADAPTIVE_END] =
+ (pf->limit[PF_LIMIT_STATES] / 10) * 12;
+ pf->timeout_set[PFTM_ADAPTIVE_END] = 1;
+ }
+
+ /* load timeouts */
+ for (i = 0; i < PFTM_MAX; i++) {
+ if ((pf->opts & PF_OPT_MERGE) && !pf->timeout_set[i])
+ continue;
+ if (pfctl_load_timeout(pf, i, pf->timeout[i]))
+ error = 1;
+ }
+
+ /* load debug */
+ if (!(pf->opts & PF_OPT_MERGE) || pf->debug_set)
+ if (pfctl_load_debug(pf, pf->debug))
+ error = 1;
+
+ /* load logif */
+ if (!(pf->opts & PF_OPT_MERGE) || pf->ifname_set)
+ if (pfctl_load_logif(pf, pf->ifname))
+ error = 1;
+
+ /* load hostid */
+ if (!(pf->opts & PF_OPT_MERGE) || pf->hostid_set)
+ if (pfctl_load_hostid(pf, pf->hostid))
+ error = 1;
+
+ return (error);
+}
+
+int
+pfctl_set_limit(struct pfctl *pf, const char *opt, unsigned int limit)
+{
+ int i;
+
+
+ for (i = 0; pf_limits[i].name; i++) {
+ if (strcasecmp(opt, pf_limits[i].name) == 0) {
+ pf->limit[pf_limits[i].index] = limit;
+ pf->limit_set[pf_limits[i].index] = 1;
+ break;
+ }
+ }
+ if (pf_limits[i].name == NULL) {
+ warnx("Bad pool name.");
+ return (1);
+ }
+
+ if (pf->opts & PF_OPT_VERBOSE)
+ printf("set limit %s %d\n", opt, limit);
+
+ return (0);
+}
+
+int
+pfctl_load_limit(struct pfctl *pf, unsigned int index, unsigned int limit)
+{
+ struct pfioc_limit pl;
+
+ memset(&pl, 0, sizeof(pl));
+ pl.index = index;
+ pl.limit = limit;
+ if (ioctl(pf->dev, DIOCSETLIMIT, &pl)) {
+ if (errno == EBUSY)
+ warnx("Current pool size exceeds requested hard limit");
+ else
+ warnx("DIOCSETLIMIT");
+ return (1);
+ }
+ return (0);
+}
+
+int
+pfctl_set_timeout(struct pfctl *pf, const char *opt, int seconds, int quiet)
+{
+ int i;
+
+ if ((loadopt & PFCTL_FLAG_OPTION) == 0)
+ return (0);
+
+ for (i = 0; pf_timeouts[i].name; i++) {
+ if (strcasecmp(opt, pf_timeouts[i].name) == 0) {
+ pf->timeout[pf_timeouts[i].timeout] = seconds;
+ pf->timeout_set[pf_timeouts[i].timeout] = 1;
+ break;
+ }
+ }
+
+ if (pf_timeouts[i].name == NULL) {
+ warnx("Bad timeout name.");
+ return (1);
+ }
+
+
+ if (pf->opts & PF_OPT_VERBOSE && ! quiet)
+ printf("set timeout %s %d\n", opt, seconds);
+
+ return (0);
+}
+
+int
+pfctl_load_timeout(struct pfctl *pf, unsigned int timeout, unsigned int seconds)
+{
+ struct pfioc_tm pt;
+
+ memset(&pt, 0, sizeof(pt));
+ pt.timeout = timeout;
+ pt.seconds = seconds;
+ if (ioctl(pf->dev, DIOCSETTIMEOUT, &pt)) {
+ warnx("DIOCSETTIMEOUT");
+ return (1);
+ }
+ return (0);
+}
+
+int
+pfctl_set_optimization(struct pfctl *pf, const char *opt)
+{
+ const struct pf_hint *hint;
+ int i, r;
+
+ if ((loadopt & PFCTL_FLAG_OPTION) == 0)
+ return (0);
+
+ for (i = 0; pf_hints[i].name; i++)
+ if (strcasecmp(opt, pf_hints[i].name) == 0)
+ break;
+
+ hint = pf_hints[i].hint;
+ if (hint == NULL) {
+ warnx("invalid state timeouts optimization");
+ return (1);
+ }
+
+ for (i = 0; hint[i].name; i++)
+ if ((r = pfctl_set_timeout(pf, hint[i].name,
+ hint[i].timeout, 1)))
+ return (r);
+
+ if (pf->opts & PF_OPT_VERBOSE)
+ printf("set optimization %s\n", opt);
+
+ return (0);
+}
+
+int
+pfctl_set_logif(struct pfctl *pf, char *ifname)
+{
+
+ if ((loadopt & PFCTL_FLAG_OPTION) == 0)
+ return (0);
+
+ if (!strcmp(ifname, "none")) {
+ free(pf->ifname);
+ pf->ifname = NULL;
+ } else {
+ pf->ifname = strdup(ifname);
+ if (!pf->ifname)
+ errx(1, "pfctl_set_logif: strdup");
+ }
+ pf->ifname_set = 1;
+
+ if (pf->opts & PF_OPT_VERBOSE)
+ printf("set loginterface %s\n", ifname);
+
+ return (0);
+}
+
+int
+pfctl_load_logif(struct pfctl *pf, char *ifname)
+{
+ struct pfioc_if pi;
+
+ memset(&pi, 0, sizeof(pi));
+ if (ifname && strlcpy(pi.ifname, ifname,
+ sizeof(pi.ifname)) >= sizeof(pi.ifname)) {
+ warnx("pfctl_load_logif: strlcpy");
+ return (1);
+ }
+ if (ioctl(pf->dev, DIOCSETSTATUSIF, &pi)) {
+ warnx("DIOCSETSTATUSIF");
+ return (1);
+ }
+ return (0);
+}
+
+int
+pfctl_set_hostid(struct pfctl *pf, u_int32_t hostid)
+{
+ if ((loadopt & PFCTL_FLAG_OPTION) == 0)
+ return (0);
+
+ HTONL(hostid);
+
+ pf->hostid = hostid;
+ pf->hostid_set = 1;
+
+ if (pf->opts & PF_OPT_VERBOSE)
+ printf("set hostid 0x%08x\n", ntohl(hostid));
+
+ return (0);
+}
+
+int
+pfctl_load_hostid(struct pfctl *pf, u_int32_t hostid)
+{
+ if (ioctl(dev, DIOCSETHOSTID, &hostid)) {
+ warnx("DIOCSETHOSTID");
+ return (1);
+ }
+ return (0);
+}
+
+int
+pfctl_set_debug(struct pfctl *pf, char *d)
+{
+ u_int32_t level;
+
+ if ((loadopt & PFCTL_FLAG_OPTION) == 0)
+ return (0);
+
+ if (!strcmp(d, "none"))
+ pf->debug = PF_DEBUG_NONE;
+ else if (!strcmp(d, "urgent"))
+ pf->debug = PF_DEBUG_URGENT;
+ else if (!strcmp(d, "misc"))
+ pf->debug = PF_DEBUG_MISC;
+ else if (!strcmp(d, "loud"))
+ pf->debug = PF_DEBUG_NOISY;
+ else {
+ warnx("unknown debug level \"%s\"", d);
+ return (-1);
+ }
+
+ pf->debug_set = 1;
+
+ if ((pf->opts & PF_OPT_NOACTION) == 0)
+ if (ioctl(dev, DIOCSETDEBUG, &level))
+ err(1, "DIOCSETDEBUG");
+
+ if (pf->opts & PF_OPT_VERBOSE)
+ printf("set debug %s\n", d);
+
+ return (0);
+}
+
+int
+pfctl_load_debug(struct pfctl *pf, unsigned int level)
+{
+ if (ioctl(pf->dev, DIOCSETDEBUG, &level)) {
+ warnx("DIOCSETDEBUG");
+ return (1);
+ }
+ return (0);
+}
+
+int
+pfctl_set_interface_flags(struct pfctl *pf, char *ifname, int flags, int how)
+{
+ struct pfioc_iface pi;
+
+ if ((loadopt & PFCTL_FLAG_OPTION) == 0)
+ return (0);
+
+ bzero(&pi, sizeof(pi));
+
+ pi.pfiio_flags = flags;
+
+ if (strlcpy(pi.pfiio_name, ifname, sizeof(pi.pfiio_name)) >=
+ sizeof(pi.pfiio_name))
+ errx(1, "pfctl_set_interface_flags: strlcpy");
+
+ if ((pf->opts & PF_OPT_NOACTION) == 0) {
+ if (how == 0) {
+ if (ioctl(pf->dev, DIOCCLRIFFLAG, &pi))
+ err(1, "DIOCCLRIFFLAG");
+ } else {
+ if (ioctl(pf->dev, DIOCSETIFFLAG, &pi))
+ err(1, "DIOCSETIFFLAG");
+ }
+ }
+ return (0);
+}
+
+void
+pfctl_debug(int dev, u_int32_t level, int opts)
+{
+ if (ioctl(dev, DIOCSETDEBUG, &level))
+ err(1, "DIOCSETDEBUG");
+ if ((opts & PF_OPT_QUIET) == 0) {
+ fprintf(stderr, "debug level set to '");
+ switch (level) {
+ case PF_DEBUG_NONE:
+ fprintf(stderr, "none");
+ break;
+ case PF_DEBUG_URGENT:
+ fprintf(stderr, "urgent");
+ break;
+ case PF_DEBUG_MISC:
+ fprintf(stderr, "misc");
+ break;
+ case PF_DEBUG_NOISY:
+ fprintf(stderr, "loud");
+ break;
+ default:
+ fprintf(stderr, "<invalid>");
+ break;
+ }
+ fprintf(stderr, "'\n");
+ }
+}
+
+int
+pfctl_test_altqsupport(int dev, int opts)
+{
+ struct pfioc_altq pa;
+
+ if (ioctl(dev, DIOCGETALTQS, &pa)) {
+ if (errno == ENODEV) {
+ if (!(opts & PF_OPT_QUIET))
+ fprintf(stderr, "No ALTQ support in kernel\n"
+ "ALTQ related functions disabled\n");
+ return (0);
+ } else
+ err(1, "DIOCGETALTQS");
+ }
+ return (1);
+}
+
+int
+pfctl_show_anchors(int dev, int opts, char *anchorname)
+{
+ struct pfioc_ruleset pr;
+ u_int32_t mnr, nr;
+
+ memset(&pr, 0, sizeof(pr));
+ memcpy(pr.path, anchorname, sizeof(pr.path));
+ if (ioctl(dev, DIOCGETRULESETS, &pr)) {
+ if (errno == EINVAL)
+ fprintf(stderr, "Anchor '%s' not found.\n",
+ anchorname);
+ else
+ err(1, "DIOCGETRULESETS");
+ return (-1);
+ }
+ mnr = pr.nr;
+ for (nr = 0; nr < mnr; ++nr) {
+ char sub[MAXPATHLEN];
+
+ pr.nr = nr;
+ if (ioctl(dev, DIOCGETRULESET, &pr))
+ err(1, "DIOCGETRULESET");
+ if (!strcmp(pr.name, PF_RESERVED_ANCHOR))
+ continue;
+ sub[0] = 0;
+ if (pr.path[0]) {
+ strlcat(sub, pr.path, sizeof(sub));
+ strlcat(sub, "/", sizeof(sub));
+ }
+ strlcat(sub, pr.name, sizeof(sub));
+ if (sub[0] != '_' || (opts & PF_OPT_VERBOSE))
+ printf(" %s\n", sub);
+ if ((opts & PF_OPT_VERBOSE) && pfctl_show_anchors(dev, opts, sub))
+ return (-1);
+ }
+ return (0);
+}
+
+const char *
+pfctl_lookup_option(char *cmd, const char **list)
+{
+ if (cmd != NULL && *cmd)
+ for (; *list; list++)
+ if (!strncmp(cmd, *list, strlen(cmd)))
+ return (*list);
+ return (NULL);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int error = 0;
+ int ch;
+ int mode = O_RDONLY;
+ int opts = 0;
+ int optimize = PF_OPTIMIZE_BASIC;
+ char anchorname[MAXPATHLEN];
+ char *path;
+
+ if (argc < 2)
+ usage();
+
+ while ((ch = getopt(argc, argv,
+ "a:AdD:eqf:F:ghi:k:K:mnNOo:Pp:rRs:t:T:vx:z")) != -1) {
+ switch (ch) {
+ case 'a':
+ anchoropt = optarg;
+ break;
+ case 'd':
+ opts |= PF_OPT_DISABLE;
+ mode = O_RDWR;
+ break;
+ case 'D':
+ if (pfctl_cmdline_symset(optarg) < 0)
+ warnx("could not parse macro definition %s",
+ optarg);
+ break;
+ case 'e':
+ opts |= PF_OPT_ENABLE;
+ mode = O_RDWR;
+ break;
+ case 'q':
+ opts |= PF_OPT_QUIET;
+ break;
+ case 'F':
+ clearopt = pfctl_lookup_option(optarg, clearopt_list);
+ if (clearopt == NULL) {
+ warnx("Unknown flush modifier '%s'", optarg);
+ usage();
+ }
+ mode = O_RDWR;
+ break;
+ case 'i':
+ ifaceopt = optarg;
+ break;
+ case 'k':
+ if (state_killers >= 2) {
+ warnx("can only specify -k twice");
+ usage();
+ /* NOTREACHED */
+ }
+ state_kill[state_killers++] = optarg;
+ mode = O_RDWR;
+ break;
+ case 'K':
+ if (src_node_killers >= 2) {
+ warnx("can only specify -K twice");
+ usage();
+ /* NOTREACHED */
+ }
+ src_node_kill[src_node_killers++] = optarg;
+ mode = O_RDWR;
+ break;
+ case 'm':
+ opts |= PF_OPT_MERGE;
+ break;
+ case 'n':
+ opts |= PF_OPT_NOACTION;
+ break;
+ case 'N':
+ loadopt |= PFCTL_FLAG_NAT;
+ break;
+ case 'r':
+ opts |= PF_OPT_USEDNS;
+ break;
+ case 'f':
+ rulesopt = optarg;
+ mode = O_RDWR;
+ break;
+ case 'g':
+ opts |= PF_OPT_DEBUG;
+ break;
+ case 'A':
+ loadopt |= PFCTL_FLAG_ALTQ;
+ break;
+ case 'R':
+ loadopt |= PFCTL_FLAG_FILTER;
+ break;
+ case 'o':
+ optiopt = pfctl_lookup_option(optarg, optiopt_list);
+ if (optiopt == NULL) {
+ warnx("Unknown optimization '%s'", optarg);
+ usage();
+ }
+ opts |= PF_OPT_OPTIMIZE;
+ break;
+ case 'O':
+ loadopt |= PFCTL_FLAG_OPTION;
+ break;
+ case 'p':
+ pf_device = optarg;
+ break;
+ case 'P':
+ opts |= PF_OPT_NUMERIC;
+ break;
+ case 's':
+ showopt = pfctl_lookup_option(optarg, showopt_list);
+ if (showopt == NULL) {
+ warnx("Unknown show modifier '%s'", optarg);
+ usage();
+ }
+ break;
+ case 't':
+ tableopt = optarg;
+ break;
+ case 'T':
+ tblcmdopt = pfctl_lookup_option(optarg, tblcmdopt_list);
+ if (tblcmdopt == NULL) {
+ warnx("Unknown table command '%s'", optarg);
+ usage();
+ }
+ break;
+ case 'v':
+ if (opts & PF_OPT_VERBOSE)
+ opts |= PF_OPT_VERBOSE2;
+ opts |= PF_OPT_VERBOSE;
+ break;
+ case 'x':
+ debugopt = pfctl_lookup_option(optarg, debugopt_list);
+ if (debugopt == NULL) {
+ warnx("Unknown debug level '%s'", optarg);
+ usage();
+ }
+ mode = O_RDWR;
+ break;
+ case 'z':
+ opts |= PF_OPT_CLRRULECTRS;
+ mode = O_RDWR;
+ break;
+ case 'h':
+ /* FALLTHROUGH */
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ }
+
+ if (tblcmdopt != NULL) {
+ argc -= optind;
+ argv += optind;
+ ch = *tblcmdopt;
+ if (ch == 'l') {
+ loadopt |= PFCTL_FLAG_TABLE;
+ tblcmdopt = NULL;
+ } else
+ mode = strchr("acdefkrz", ch) ? O_RDWR : O_RDONLY;
+ } else if (argc != optind) {
+ warnx("unknown command line argument: %s ...", argv[optind]);
+ usage();
+ /* NOTREACHED */
+ }
+ if (loadopt == 0)
+ loadopt = ~0;
+
+ if ((path = calloc(1, MAXPATHLEN)) == NULL)
+ errx(1, "pfctl: calloc");
+ memset(anchorname, 0, sizeof(anchorname));
+ if (anchoropt != NULL) {
+ int len = strlen(anchoropt);
+
+ if (anchoropt[len - 1] == '*') {
+ if (len >= 2 && anchoropt[len - 2] == '/')
+ anchoropt[len - 2] = '\0';
+ else
+ anchoropt[len - 1] = '\0';
+ opts |= PF_OPT_RECURSE;
+ }
+ if (strlcpy(anchorname, anchoropt,
+ sizeof(anchorname)) >= sizeof(anchorname))
+ errx(1, "anchor name '%s' too long",
+ anchoropt);
+ loadopt &= PFCTL_FLAG_FILTER|PFCTL_FLAG_NAT|PFCTL_FLAG_TABLE;
+ }
+
+ if ((opts & PF_OPT_NOACTION) == 0) {
+ dev = open(pf_device, mode);
+ if (dev == -1)
+ err(1, "%s", pf_device);
+ altqsupport = pfctl_test_altqsupport(dev, opts);
+ } else {
+ dev = open(pf_device, O_RDONLY);
+ if (dev >= 0)
+ opts |= PF_OPT_DUMMYACTION;
+ /* turn off options */
+ opts &= ~ (PF_OPT_DISABLE | PF_OPT_ENABLE);
+ clearopt = showopt = debugopt = NULL;
+#if !defined(ENABLE_ALTQ)
+ altqsupport = 0;
+#else
+ altqsupport = 1;
+#endif
+ }
+
+ if (opts & PF_OPT_DISABLE)
+ if (pfctl_disable(dev, opts))
+ error = 1;
+
+ if (showopt != NULL) {
+ switch (*showopt) {
+ case 'A':
+ pfctl_show_anchors(dev, opts, anchorname);
+ break;
+ case 'r':
+ pfctl_load_fingerprints(dev, opts);
+ pfctl_show_rules(dev, path, opts, PFCTL_SHOW_RULES,
+ anchorname, 0);
+ break;
+ case 'l':
+ pfctl_load_fingerprints(dev, opts);
+ pfctl_show_rules(dev, path, opts, PFCTL_SHOW_LABELS,
+ anchorname, 0);
+ break;
+ case 'n':
+ pfctl_load_fingerprints(dev, opts);
+ pfctl_show_nat(dev, opts, anchorname);
+ break;
+ case 'q':
+ pfctl_show_altq(dev, ifaceopt, opts,
+ opts & PF_OPT_VERBOSE2);
+ break;
+ case 's':
+ pfctl_show_states(dev, ifaceopt, opts);
+ break;
+ case 'S':
+ pfctl_show_src_nodes(dev, opts);
+ break;
+ case 'i':
+ pfctl_show_status(dev, opts);
+ break;
+ case 't':
+ pfctl_show_timeouts(dev, opts);
+ break;
+ case 'm':
+ pfctl_show_limits(dev, opts);
+ break;
+ case 'a':
+ opts |= PF_OPT_SHOWALL;
+ pfctl_load_fingerprints(dev, opts);
+
+ pfctl_show_nat(dev, opts, anchorname);
+ pfctl_show_rules(dev, path, opts, 0, anchorname, 0);
+ pfctl_show_altq(dev, ifaceopt, opts, 0);
+ pfctl_show_states(dev, ifaceopt, opts);
+ pfctl_show_src_nodes(dev, opts);
+ pfctl_show_status(dev, opts);
+ pfctl_show_rules(dev, path, opts, 1, anchorname, 0);
+ pfctl_show_timeouts(dev, opts);
+ pfctl_show_limits(dev, opts);
+ pfctl_show_tables(anchorname, opts);
+ pfctl_show_fingerprints(opts);
+ break;
+ case 'T':
+ pfctl_show_tables(anchorname, opts);
+ break;
+ case 'o':
+ pfctl_load_fingerprints(dev, opts);
+ pfctl_show_fingerprints(opts);
+ break;
+ case 'I':
+ pfctl_show_ifaces(ifaceopt, opts);
+ break;
+ }
+ }
+
+ if ((opts & PF_OPT_CLRRULECTRS) && showopt == NULL)
+ pfctl_show_rules(dev, path, opts, PFCTL_SHOW_NOTHING,
+ anchorname, 0);
+
+ if (clearopt != NULL) {
+ if (anchorname[0] == '_' || strstr(anchorname, "/_") != NULL)
+ errx(1, "anchor names beginning with '_' cannot "
+ "be modified from the command line");
+
+ switch (*clearopt) {
+ case 'r':
+ pfctl_clear_rules(dev, opts, anchorname);
+ break;
+ case 'n':
+ pfctl_clear_nat(dev, opts, anchorname);
+ break;
+ case 'q':
+ pfctl_clear_altq(dev, opts);
+ break;
+ case 's':
+ pfctl_clear_states(dev, ifaceopt, opts);
+ break;
+ case 'S':
+ pfctl_clear_src_nodes(dev, opts);
+ break;
+ case 'i':
+ pfctl_clear_stats(dev, opts);
+ break;
+ case 'a':
+ pfctl_clear_rules(dev, opts, anchorname);
+ pfctl_clear_nat(dev, opts, anchorname);
+ pfctl_clear_tables(anchorname, opts);
+ if (!*anchorname) {
+ pfctl_clear_altq(dev, opts);
+ pfctl_clear_states(dev, ifaceopt, opts);
+ pfctl_clear_src_nodes(dev, opts);
+ pfctl_clear_stats(dev, opts);
+ pfctl_clear_fingerprints(dev, opts);
+ pfctl_clear_interface_flags(dev, opts);
+ }
+ break;
+ case 'o':
+ pfctl_clear_fingerprints(dev, opts);
+ break;
+ case 'T':
+ pfctl_clear_tables(anchorname, opts);
+ break;
+ }
+ }
+ if (state_killers) {
+ if (!strcmp(state_kill[0], "label"))
+ pfctl_label_kill_states(dev, ifaceopt, opts);
+ else if (!strcmp(state_kill[0], "id"))
+ pfctl_id_kill_states(dev, ifaceopt, opts);
+ else
+ pfctl_net_kill_states(dev, ifaceopt, opts);
+ }
+
+ if (src_node_killers)
+ pfctl_kill_src_nodes(dev, ifaceopt, opts);
+
+ if (tblcmdopt != NULL) {
+ error = pfctl_command_tables(argc, argv, tableopt,
+ tblcmdopt, rulesopt, anchorname, opts);
+ rulesopt = NULL;
+ }
+ if (optiopt != NULL) {
+ switch (*optiopt) {
+ case 'n':
+ optimize = 0;
+ break;
+ case 'b':
+ optimize |= PF_OPTIMIZE_BASIC;
+ break;
+ case 'o':
+ case 'p':
+ optimize |= PF_OPTIMIZE_PROFILE;
+ break;
+ }
+ }
+
+ if ((rulesopt != NULL) && (loadopt & PFCTL_FLAG_OPTION) &&
+ !anchorname[0])
+ if (pfctl_clear_interface_flags(dev, opts | PF_OPT_QUIET))
+ error = 1;
+
+ if (rulesopt != NULL && !(opts & (PF_OPT_MERGE|PF_OPT_NOACTION)) &&
+ !anchorname[0] && (loadopt & PFCTL_FLAG_OPTION))
+ if (pfctl_file_fingerprints(dev, opts, PF_OSFP_FILE))
+ error = 1;
+
+ if (rulesopt != NULL) {
+ if (anchorname[0] == '_' || strstr(anchorname, "/_") != NULL)
+ errx(1, "anchor names beginning with '_' cannot "
+ "be modified from the command line");
+ if (pfctl_rules(dev, rulesopt, opts, optimize,
+ anchorname, NULL))
+ error = 1;
+ else if (!(opts & PF_OPT_NOACTION) &&
+ (loadopt & PFCTL_FLAG_TABLE))
+ warn_namespace_collision(NULL);
+ }
+
+ if (opts & PF_OPT_ENABLE)
+ if (pfctl_enable(dev, opts))
+ error = 1;
+
+ if (debugopt != NULL) {
+ switch (*debugopt) {
+ case 'n':
+ pfctl_debug(dev, PF_DEBUG_NONE, opts);
+ break;
+ case 'u':
+ pfctl_debug(dev, PF_DEBUG_URGENT, opts);
+ break;
+ case 'm':
+ pfctl_debug(dev, PF_DEBUG_MISC, opts);
+ break;
+ case 'l':
+ pfctl_debug(dev, PF_DEBUG_NOISY, opts);
+ break;
+ }
+ }
+
+ exit(error);
+}
diff --git a/sbin/pfctl/pfctl.h b/sbin/pfctl/pfctl.h
new file mode 100644
index 0000000..2c69bc2
--- /dev/null
+++ b/sbin/pfctl/pfctl.h
@@ -0,0 +1,130 @@
+/* $OpenBSD: pfctl.h,v 1.42 2007/12/05 12:01:47 chl Exp $ */
+
+/*
+ * Copyright (c) 2001 Daniel Hartmeier
+ * All rights reserved.
+ *
+ * Redistribution and use 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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
+ * COPYRIGHT HOLDERS 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _PFCTL_H_
+#define _PFCTL_H_
+
+enum pfctl_show { PFCTL_SHOW_RULES, PFCTL_SHOW_LABELS, PFCTL_SHOW_NOTHING };
+
+enum { PFRB_TABLES = 1, PFRB_TSTATS, PFRB_ADDRS, PFRB_ASTATS,
+ PFRB_IFACES, PFRB_TRANS, PFRB_MAX };
+struct pfr_buffer {
+ int pfrb_type; /* type of content, see enum above */
+ int pfrb_size; /* number of objects in buffer */
+ int pfrb_msize; /* maximum number of objects in buffer */
+ void *pfrb_caddr; /* malloc'ated memory area */
+};
+#define PFRB_FOREACH(var, buf) \
+ for ((var) = pfr_buf_next((buf), NULL); \
+ (var) != NULL; \
+ (var) = pfr_buf_next((buf), (var)))
+
+int pfr_get_fd(void);
+int pfr_clr_tables(struct pfr_table *, int *, int);
+int pfr_add_tables(struct pfr_table *, int, int *, int);
+int pfr_del_tables(struct pfr_table *, int, int *, int);
+int pfr_get_tables(struct pfr_table *, struct pfr_table *, int *, int);
+int pfr_get_tstats(struct pfr_table *, struct pfr_tstats *, int *, int);
+int pfr_clr_tstats(struct pfr_table *, int, int *, int);
+int pfr_clr_addrs(struct pfr_table *, int *, int);
+int pfr_add_addrs(struct pfr_table *, struct pfr_addr *, int, int *, int);
+int pfr_del_addrs(struct pfr_table *, struct pfr_addr *, int, int *, int);
+int pfr_set_addrs(struct pfr_table *, struct pfr_addr *, int, int *,
+ int *, int *, int *, int);
+int pfr_get_addrs(struct pfr_table *, struct pfr_addr *, int *, int);
+int pfr_get_astats(struct pfr_table *, struct pfr_astats *, int *, int);
+int pfr_tst_addrs(struct pfr_table *, struct pfr_addr *, int, int *, int);
+int pfr_ina_define(struct pfr_table *, struct pfr_addr *, int, int *,
+ int *, int, int);
+void pfr_buf_clear(struct pfr_buffer *);
+int pfr_buf_add(struct pfr_buffer *, const void *);
+void *pfr_buf_next(struct pfr_buffer *, const void *);
+int pfr_buf_grow(struct pfr_buffer *, int);
+int pfr_buf_load(struct pfr_buffer *, char *, int,
+ int (*)(struct pfr_buffer *, char *, int));
+char *pfr_strerror(int);
+int pfi_get_ifaces(const char *, struct pfi_kif *, int *);
+int pfi_clr_istats(const char *, int *, int);
+
+void pfctl_print_title(char *);
+int pfctl_clear_tables(const char *, int);
+int pfctl_show_tables(const char *, int);
+int pfctl_command_tables(int, char *[], char *, const char *, char *,
+ const char *, int);
+int pfctl_show_altq(int, const char *, int, int);
+void warn_namespace_collision(const char *);
+int pfctl_show_ifaces(const char *, int);
+FILE *pfctl_fopen(const char *, const char *);
+
+#ifdef __FreeBSD__
+extern int altqsupport;
+extern int dummynetsupport;
+#define HTONL(x) (x) = htonl((__uint32_t)(x))
+#endif
+
+#ifndef DEFAULT_PRIORITY
+#define DEFAULT_PRIORITY 1
+#endif
+
+#ifndef DEFAULT_QLIMIT
+#define DEFAULT_QLIMIT 50
+#endif
+
+/*
+ * generalized service curve used for admission control
+ */
+struct segment {
+ LIST_ENTRY(segment) _next;
+ double x, y, d, m;
+};
+
+extern int loadopt;
+
+int check_commit_altq(int, int);
+void pfaltq_store(struct pf_altq *);
+struct pf_altq *pfaltq_lookup(const char *);
+char *rate2str(double);
+
+void print_addr(struct pf_addr_wrap *, sa_family_t, int);
+void print_host(struct pf_addr *, u_int16_t p, sa_family_t, int);
+void print_seq(struct pfsync_state_peer *);
+void print_state(struct pfsync_state *, int);
+int unmask(struct pf_addr *, sa_family_t);
+
+int pfctl_cmdline_symset(char *);
+int pfctl_add_trans(struct pfr_buffer *, int, const char *);
+u_int32_t
+ pfctl_get_ticket(struct pfr_buffer *, int, const char *);
+int pfctl_trans(int, struct pfr_buffer *, u_long, int);
+
+#endif /* _PFCTL_H_ */
diff --git a/sbin/pfctl/pfctl_altq.c b/sbin/pfctl/pfctl_altq.c
new file mode 100644
index 0000000..64f474c
--- /dev/null
+++ b/sbin/pfctl/pfctl_altq.c
@@ -0,0 +1,1258 @@
+/* $OpenBSD: pfctl_altq.c,v 1.93 2007/10/15 02:16:35 deraadt Exp $ */
+
+/*
+ * Copyright (c) 2002
+ * Sony Computer Science Laboratories Inc.
+ * Copyright (c) 2002, 2003 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <net/pfvar.h>
+
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <net/altq/altq.h>
+#include <net/altq/altq_cbq.h>
+#include <net/altq/altq_priq.h>
+#include <net/altq/altq_hfsc.h>
+
+#include "pfctl_parser.h"
+#include "pfctl.h"
+
+#define is_sc_null(sc) (((sc) == NULL) || ((sc)->m1 == 0 && (sc)->m2 == 0))
+
+TAILQ_HEAD(altqs, pf_altq) altqs = TAILQ_HEAD_INITIALIZER(altqs);
+LIST_HEAD(gen_sc, segment) rtsc, lssc;
+
+struct pf_altq *qname_to_pfaltq(const char *, const char *);
+u_int32_t qname_to_qid(const char *);
+
+static int eval_pfqueue_cbq(struct pfctl *, struct pf_altq *);
+static int cbq_compute_idletime(struct pfctl *, struct pf_altq *);
+static int check_commit_cbq(int, int, struct pf_altq *);
+static int print_cbq_opts(const struct pf_altq *);
+
+static int eval_pfqueue_priq(struct pfctl *, struct pf_altq *);
+static int check_commit_priq(int, int, struct pf_altq *);
+static int print_priq_opts(const struct pf_altq *);
+
+static int eval_pfqueue_hfsc(struct pfctl *, struct pf_altq *);
+static int check_commit_hfsc(int, int, struct pf_altq *);
+static int print_hfsc_opts(const struct pf_altq *,
+ const struct node_queue_opt *);
+
+static void gsc_add_sc(struct gen_sc *, struct service_curve *);
+static int is_gsc_under_sc(struct gen_sc *,
+ struct service_curve *);
+static void gsc_destroy(struct gen_sc *);
+static struct segment *gsc_getentry(struct gen_sc *, double);
+static int gsc_add_seg(struct gen_sc *, double, double, double,
+ double);
+static double sc_x2y(struct service_curve *, double);
+
+#ifdef __FreeBSD__
+u_int32_t getifspeed(int, char *);
+#else
+u_int32_t getifspeed(char *);
+#endif
+u_long getifmtu(char *);
+int eval_queue_opts(struct pf_altq *, struct node_queue_opt *,
+ u_int32_t);
+u_int32_t eval_bwspec(struct node_queue_bw *, u_int32_t);
+void print_hfsc_sc(const char *, u_int, u_int, u_int,
+ const struct node_hfsc_sc *);
+
+void
+pfaltq_store(struct pf_altq *a)
+{
+ struct pf_altq *altq;
+
+ if ((altq = malloc(sizeof(*altq))) == NULL)
+ err(1, "malloc");
+ memcpy(altq, a, sizeof(struct pf_altq));
+ TAILQ_INSERT_TAIL(&altqs, altq, entries);
+}
+
+struct pf_altq *
+pfaltq_lookup(const char *ifname)
+{
+ struct pf_altq *altq;
+
+ TAILQ_FOREACH(altq, &altqs, entries) {
+ if (strncmp(ifname, altq->ifname, IFNAMSIZ) == 0 &&
+ altq->qname[0] == 0)
+ return (altq);
+ }
+ return (NULL);
+}
+
+struct pf_altq *
+qname_to_pfaltq(const char *qname, const char *ifname)
+{
+ struct pf_altq *altq;
+
+ TAILQ_FOREACH(altq, &altqs, entries) {
+ if (strncmp(ifname, altq->ifname, IFNAMSIZ) == 0 &&
+ strncmp(qname, altq->qname, PF_QNAME_SIZE) == 0)
+ return (altq);
+ }
+ return (NULL);
+}
+
+u_int32_t
+qname_to_qid(const char *qname)
+{
+ struct pf_altq *altq;
+
+ /*
+ * We guarantee that same named queues on different interfaces
+ * have the same qid, so we do NOT need to limit matching on
+ * one interface!
+ */
+
+ TAILQ_FOREACH(altq, &altqs, entries) {
+ if (strncmp(qname, altq->qname, PF_QNAME_SIZE) == 0)
+ return (altq->qid);
+ }
+ return (0);
+}
+
+void
+print_altq(const struct pf_altq *a, unsigned int level,
+ struct node_queue_bw *bw, struct node_queue_opt *qopts)
+{
+ if (a->qname[0] != 0) {
+ print_queue(a, level, bw, 1, qopts);
+ return;
+ }
+
+#ifdef __FreeBSD__
+ if (a->local_flags & PFALTQ_FLAG_IF_REMOVED)
+ printf("INACTIVE ");
+#endif
+
+ printf("altq on %s ", a->ifname);
+
+ switch (a->scheduler) {
+ case ALTQT_CBQ:
+ if (!print_cbq_opts(a))
+ printf("cbq ");
+ break;
+ case ALTQT_PRIQ:
+ if (!print_priq_opts(a))
+ printf("priq ");
+ break;
+ case ALTQT_HFSC:
+ if (!print_hfsc_opts(a, qopts))
+ printf("hfsc ");
+ break;
+ }
+
+ if (bw != NULL && bw->bw_percent > 0) {
+ if (bw->bw_percent < 100)
+ printf("bandwidth %u%% ", bw->bw_percent);
+ } else
+ printf("bandwidth %s ", rate2str((double)a->ifbandwidth));
+
+ if (a->qlimit != DEFAULT_QLIMIT)
+ printf("qlimit %u ", a->qlimit);
+ printf("tbrsize %u ", a->tbrsize);
+}
+
+void
+print_queue(const struct pf_altq *a, unsigned int level,
+ struct node_queue_bw *bw, int print_interface,
+ struct node_queue_opt *qopts)
+{
+ unsigned int i;
+
+#ifdef __FreeBSD__
+ if (a->local_flags & PFALTQ_FLAG_IF_REMOVED)
+ printf("INACTIVE ");
+#endif
+ printf("queue ");
+ for (i = 0; i < level; ++i)
+ printf(" ");
+ printf("%s ", a->qname);
+ if (print_interface)
+ printf("on %s ", a->ifname);
+ if (a->scheduler == ALTQT_CBQ || a->scheduler == ALTQT_HFSC) {
+ if (bw != NULL && bw->bw_percent > 0) {
+ if (bw->bw_percent < 100)
+ printf("bandwidth %u%% ", bw->bw_percent);
+ } else
+ printf("bandwidth %s ", rate2str((double)a->bandwidth));
+ }
+ if (a->priority != DEFAULT_PRIORITY)
+ printf("priority %u ", a->priority);
+ if (a->qlimit != DEFAULT_QLIMIT)
+ printf("qlimit %u ", a->qlimit);
+ switch (a->scheduler) {
+ case ALTQT_CBQ:
+ print_cbq_opts(a);
+ break;
+ case ALTQT_PRIQ:
+ print_priq_opts(a);
+ break;
+ case ALTQT_HFSC:
+ print_hfsc_opts(a, qopts);
+ break;
+ }
+}
+
+/*
+ * eval_pfaltq computes the discipline parameters.
+ */
+int
+eval_pfaltq(struct pfctl *pf, struct pf_altq *pa, struct node_queue_bw *bw,
+ struct node_queue_opt *opts)
+{
+ u_int rate, size, errors = 0;
+
+ if (bw->bw_absolute > 0)
+ pa->ifbandwidth = bw->bw_absolute;
+ else
+#ifdef __FreeBSD__
+ if ((rate = getifspeed(pf->dev, pa->ifname)) == 0) {
+#else
+ if ((rate = getifspeed(pa->ifname)) == 0) {
+#endif
+ fprintf(stderr, "interface %s does not know its bandwidth, "
+ "please specify an absolute bandwidth\n",
+ pa->ifname);
+ errors++;
+ } else if ((pa->ifbandwidth = eval_bwspec(bw, rate)) == 0)
+ pa->ifbandwidth = rate;
+
+ errors += eval_queue_opts(pa, opts, pa->ifbandwidth);
+
+ /* if tbrsize is not specified, use heuristics */
+ if (pa->tbrsize == 0) {
+ rate = pa->ifbandwidth;
+ if (rate <= 1 * 1000 * 1000)
+ size = 1;
+ else if (rate <= 10 * 1000 * 1000)
+ size = 4;
+ else if (rate <= 200 * 1000 * 1000)
+ size = 8;
+ else
+ size = 24;
+ size = size * getifmtu(pa->ifname);
+ if (size > 0xffff)
+ size = 0xffff;
+ pa->tbrsize = size;
+ }
+ return (errors);
+}
+
+/*
+ * check_commit_altq does consistency check for each interface
+ */
+int
+check_commit_altq(int dev, int opts)
+{
+ struct pf_altq *altq;
+ int error = 0;
+
+ /* call the discipline check for each interface. */
+ TAILQ_FOREACH(altq, &altqs, entries) {
+ if (altq->qname[0] == 0) {
+ switch (altq->scheduler) {
+ case ALTQT_CBQ:
+ error = check_commit_cbq(dev, opts, altq);
+ break;
+ case ALTQT_PRIQ:
+ error = check_commit_priq(dev, opts, altq);
+ break;
+ case ALTQT_HFSC:
+ error = check_commit_hfsc(dev, opts, altq);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ return (error);
+}
+
+/*
+ * eval_pfqueue computes the queue parameters.
+ */
+int
+eval_pfqueue(struct pfctl *pf, struct pf_altq *pa, struct node_queue_bw *bw,
+ struct node_queue_opt *opts)
+{
+ /* should be merged with expand_queue */
+ struct pf_altq *if_pa, *parent, *altq;
+ u_int32_t bwsum;
+ int error = 0;
+
+ /* find the corresponding interface and copy fields used by queues */
+ if ((if_pa = pfaltq_lookup(pa->ifname)) == NULL) {
+ fprintf(stderr, "altq not defined on %s\n", pa->ifname);
+ return (1);
+ }
+ pa->scheduler = if_pa->scheduler;
+ pa->ifbandwidth = if_pa->ifbandwidth;
+
+ if (qname_to_pfaltq(pa->qname, pa->ifname) != NULL) {
+ fprintf(stderr, "queue %s already exists on interface %s\n",
+ pa->qname, pa->ifname);
+ return (1);
+ }
+ pa->qid = qname_to_qid(pa->qname);
+
+ parent = NULL;
+ if (pa->parent[0] != 0) {
+ parent = qname_to_pfaltq(pa->parent, pa->ifname);
+ if (parent == NULL) {
+ fprintf(stderr, "parent %s not found for %s\n",
+ pa->parent, pa->qname);
+ return (1);
+ }
+ pa->parent_qid = parent->qid;
+ }
+ if (pa->qlimit == 0)
+ pa->qlimit = DEFAULT_QLIMIT;
+
+ if (pa->scheduler == ALTQT_CBQ || pa->scheduler == ALTQT_HFSC) {
+ pa->bandwidth = eval_bwspec(bw,
+ parent == NULL ? 0 : parent->bandwidth);
+
+ if (pa->bandwidth > pa->ifbandwidth) {
+ fprintf(stderr, "bandwidth for %s higher than "
+ "interface\n", pa->qname);
+ return (1);
+ }
+ /* check the sum of the child bandwidth is under parent's */
+ if (parent != NULL) {
+ if (pa->bandwidth > parent->bandwidth) {
+ warnx("bandwidth for %s higher than parent",
+ pa->qname);
+ return (1);
+ }
+ bwsum = 0;
+ TAILQ_FOREACH(altq, &altqs, entries) {
+ if (strncmp(altq->ifname, pa->ifname,
+ IFNAMSIZ) == 0 &&
+ altq->qname[0] != 0 &&
+ strncmp(altq->parent, pa->parent,
+ PF_QNAME_SIZE) == 0)
+ bwsum += altq->bandwidth;
+ }
+ bwsum += pa->bandwidth;
+ if (bwsum > parent->bandwidth) {
+ warnx("the sum of the child bandwidth higher"
+ " than parent \"%s\"", parent->qname);
+ }
+ }
+ }
+
+ if (eval_queue_opts(pa, opts, parent == NULL? 0 : parent->bandwidth))
+ return (1);
+
+ switch (pa->scheduler) {
+ case ALTQT_CBQ:
+ error = eval_pfqueue_cbq(pf, pa);
+ break;
+ case ALTQT_PRIQ:
+ error = eval_pfqueue_priq(pf, pa);
+ break;
+ case ALTQT_HFSC:
+ error = eval_pfqueue_hfsc(pf, pa);
+ break;
+ default:
+ break;
+ }
+ return (error);
+}
+
+/*
+ * CBQ support functions
+ */
+#define RM_FILTER_GAIN 5 /* log2 of gain, e.g., 5 => 31/32 */
+#define RM_NS_PER_SEC (1000000000)
+
+static int
+eval_pfqueue_cbq(struct pfctl *pf, struct pf_altq *pa)
+{
+ struct cbq_opts *opts;
+ u_int ifmtu;
+
+ if (pa->priority >= CBQ_MAXPRI) {
+ warnx("priority out of range: max %d", CBQ_MAXPRI - 1);
+ return (-1);
+ }
+
+ ifmtu = getifmtu(pa->ifname);
+ opts = &pa->pq_u.cbq_opts;
+
+ if (opts->pktsize == 0) { /* use default */
+ opts->pktsize = ifmtu;
+ if (opts->pktsize > MCLBYTES) /* do what TCP does */
+ opts->pktsize &= ~MCLBYTES;
+ } else if (opts->pktsize > ifmtu)
+ opts->pktsize = ifmtu;
+ if (opts->maxpktsize == 0) /* use default */
+ opts->maxpktsize = ifmtu;
+ else if (opts->maxpktsize > ifmtu)
+ opts->pktsize = ifmtu;
+
+ if (opts->pktsize > opts->maxpktsize)
+ opts->pktsize = opts->maxpktsize;
+
+ if (pa->parent[0] == 0)
+ opts->flags |= (CBQCLF_ROOTCLASS | CBQCLF_WRR);
+
+ cbq_compute_idletime(pf, pa);
+ return (0);
+}
+
+/*
+ * compute ns_per_byte, maxidle, minidle, and offtime
+ */
+static int
+cbq_compute_idletime(struct pfctl *pf, struct pf_altq *pa)
+{
+ struct cbq_opts *opts;
+ double maxidle_s, maxidle, minidle;
+ double offtime, nsPerByte, ifnsPerByte, ptime, cptime;
+ double z, g, f, gton, gtom;
+ u_int minburst, maxburst;
+
+ opts = &pa->pq_u.cbq_opts;
+ ifnsPerByte = (1.0 / (double)pa->ifbandwidth) * RM_NS_PER_SEC * 8;
+ minburst = opts->minburst;
+ maxburst = opts->maxburst;
+
+ if (pa->bandwidth == 0)
+ f = 0.0001; /* small enough? */
+ else
+ f = ((double) pa->bandwidth / (double) pa->ifbandwidth);
+
+ nsPerByte = ifnsPerByte / f;
+ ptime = (double)opts->pktsize * ifnsPerByte;
+ cptime = ptime * (1.0 - f) / f;
+
+ if (nsPerByte * (double)opts->maxpktsize > (double)INT_MAX) {
+ /*
+ * this causes integer overflow in kernel!
+ * (bandwidth < 6Kbps when max_pkt_size=1500)
+ */
+ if (pa->bandwidth != 0 && (pf->opts & PF_OPT_QUIET) == 0)
+ warnx("queue bandwidth must be larger than %s",
+ rate2str(ifnsPerByte * (double)opts->maxpktsize /
+ (double)INT_MAX * (double)pa->ifbandwidth));
+ fprintf(stderr, "cbq: queue %s is too slow!\n",
+ pa->qname);
+ nsPerByte = (double)(INT_MAX / opts->maxpktsize);
+ }
+
+ if (maxburst == 0) { /* use default */
+ if (cptime > 10.0 * 1000000)
+ maxburst = 4;
+ else
+ maxburst = 16;
+ }
+ if (minburst == 0) /* use default */
+ minburst = 2;
+ if (minburst > maxburst)
+ minburst = maxburst;
+
+ z = (double)(1 << RM_FILTER_GAIN);
+ g = (1.0 - 1.0 / z);
+ gton = pow(g, (double)maxburst);
+ gtom = pow(g, (double)(minburst-1));
+ maxidle = ((1.0 / f - 1.0) * ((1.0 - gton) / gton));
+ maxidle_s = (1.0 - g);
+ if (maxidle > maxidle_s)
+ maxidle = ptime * maxidle;
+ else
+ maxidle = ptime * maxidle_s;
+ offtime = cptime * (1.0 + 1.0/(1.0 - g) * (1.0 - gtom) / gtom);
+ minidle = -((double)opts->maxpktsize * (double)nsPerByte);
+
+ /* scale parameters */
+ maxidle = ((maxidle * 8.0) / nsPerByte) *
+ pow(2.0, (double)RM_FILTER_GAIN);
+ offtime = (offtime * 8.0) / nsPerByte *
+ pow(2.0, (double)RM_FILTER_GAIN);
+ minidle = ((minidle * 8.0) / nsPerByte) *
+ pow(2.0, (double)RM_FILTER_GAIN);
+
+ maxidle = maxidle / 1000.0;
+ offtime = offtime / 1000.0;
+ minidle = minidle / 1000.0;
+
+ opts->minburst = minburst;
+ opts->maxburst = maxburst;
+ opts->ns_per_byte = (u_int)nsPerByte;
+ opts->maxidle = (u_int)fabs(maxidle);
+ opts->minidle = (int)minidle;
+ opts->offtime = (u_int)fabs(offtime);
+
+ return (0);
+}
+
+static int
+check_commit_cbq(int dev, int opts, struct pf_altq *pa)
+{
+ struct pf_altq *altq;
+ int root_class, default_class;
+ int error = 0;
+
+ /*
+ * check if cbq has one root queue and one default queue
+ * for this interface
+ */
+ root_class = default_class = 0;
+ TAILQ_FOREACH(altq, &altqs, entries) {
+ if (strncmp(altq->ifname, pa->ifname, IFNAMSIZ) != 0)
+ continue;
+ if (altq->qname[0] == 0) /* this is for interface */
+ continue;
+ if (altq->pq_u.cbq_opts.flags & CBQCLF_ROOTCLASS)
+ root_class++;
+ if (altq->pq_u.cbq_opts.flags & CBQCLF_DEFCLASS)
+ default_class++;
+ }
+ if (root_class != 1) {
+ warnx("should have one root queue on %s", pa->ifname);
+ error++;
+ }
+ if (default_class != 1) {
+ warnx("should have one default queue on %s", pa->ifname);
+ error++;
+ }
+ return (error);
+}
+
+static int
+print_cbq_opts(const struct pf_altq *a)
+{
+ const struct cbq_opts *opts;
+
+ opts = &a->pq_u.cbq_opts;
+ if (opts->flags) {
+ printf("cbq(");
+ if (opts->flags & CBQCLF_RED)
+ printf(" red");
+ if (opts->flags & CBQCLF_ECN)
+ printf(" ecn");
+ if (opts->flags & CBQCLF_RIO)
+ printf(" rio");
+ if (opts->flags & CBQCLF_CLEARDSCP)
+ printf(" cleardscp");
+ if (opts->flags & CBQCLF_FLOWVALVE)
+ printf(" flowvalve");
+ if (opts->flags & CBQCLF_BORROW)
+ printf(" borrow");
+ if (opts->flags & CBQCLF_WRR)
+ printf(" wrr");
+ if (opts->flags & CBQCLF_EFFICIENT)
+ printf(" efficient");
+ if (opts->flags & CBQCLF_ROOTCLASS)
+ printf(" root");
+ if (opts->flags & CBQCLF_DEFCLASS)
+ printf(" default");
+ printf(" ) ");
+
+ return (1);
+ } else
+ return (0);
+}
+
+/*
+ * PRIQ support functions
+ */
+static int
+eval_pfqueue_priq(struct pfctl *pf, struct pf_altq *pa)
+{
+ struct pf_altq *altq;
+
+ if (pa->priority >= PRIQ_MAXPRI) {
+ warnx("priority out of range: max %d", PRIQ_MAXPRI - 1);
+ return (-1);
+ }
+ /* the priority should be unique for the interface */
+ TAILQ_FOREACH(altq, &altqs, entries) {
+ if (strncmp(altq->ifname, pa->ifname, IFNAMSIZ) == 0 &&
+ altq->qname[0] != 0 && altq->priority == pa->priority) {
+ warnx("%s and %s have the same priority",
+ altq->qname, pa->qname);
+ return (-1);
+ }
+ }
+
+ return (0);
+}
+
+static int
+check_commit_priq(int dev, int opts, struct pf_altq *pa)
+{
+ struct pf_altq *altq;
+ int default_class;
+ int error = 0;
+
+ /*
+ * check if priq has one default class for this interface
+ */
+ default_class = 0;
+ TAILQ_FOREACH(altq, &altqs, entries) {
+ if (strncmp(altq->ifname, pa->ifname, IFNAMSIZ) != 0)
+ continue;
+ if (altq->qname[0] == 0) /* this is for interface */
+ continue;
+ if (altq->pq_u.priq_opts.flags & PRCF_DEFAULTCLASS)
+ default_class++;
+ }
+ if (default_class != 1) {
+ warnx("should have one default queue on %s", pa->ifname);
+ error++;
+ }
+ return (error);
+}
+
+static int
+print_priq_opts(const struct pf_altq *a)
+{
+ const struct priq_opts *opts;
+
+ opts = &a->pq_u.priq_opts;
+
+ if (opts->flags) {
+ printf("priq(");
+ if (opts->flags & PRCF_RED)
+ printf(" red");
+ if (opts->flags & PRCF_ECN)
+ printf(" ecn");
+ if (opts->flags & PRCF_RIO)
+ printf(" rio");
+ if (opts->flags & PRCF_CLEARDSCP)
+ printf(" cleardscp");
+ if (opts->flags & PRCF_DEFAULTCLASS)
+ printf(" default");
+ printf(" ) ");
+
+ return (1);
+ } else
+ return (0);
+}
+
+/*
+ * HFSC support functions
+ */
+static int
+eval_pfqueue_hfsc(struct pfctl *pf, struct pf_altq *pa)
+{
+ struct pf_altq *altq, *parent;
+ struct hfsc_opts *opts;
+ struct service_curve sc;
+
+ opts = &pa->pq_u.hfsc_opts;
+
+ if (pa->parent[0] == 0) {
+ /* root queue */
+ opts->lssc_m1 = pa->ifbandwidth;
+ opts->lssc_m2 = pa->ifbandwidth;
+ opts->lssc_d = 0;
+ return (0);
+ }
+
+ LIST_INIT(&rtsc);
+ LIST_INIT(&lssc);
+
+ /* if link_share is not specified, use bandwidth */
+ if (opts->lssc_m2 == 0)
+ opts->lssc_m2 = pa->bandwidth;
+
+ if ((opts->rtsc_m1 > 0 && opts->rtsc_m2 == 0) ||
+ (opts->lssc_m1 > 0 && opts->lssc_m2 == 0) ||
+ (opts->ulsc_m1 > 0 && opts->ulsc_m2 == 0)) {
+ warnx("m2 is zero for %s", pa->qname);
+ return (-1);
+ }
+
+ if ((opts->rtsc_m1 < opts->rtsc_m2 && opts->rtsc_m1 != 0) ||
+ (opts->lssc_m1 < opts->lssc_m2 && opts->lssc_m1 != 0) ||
+ (opts->ulsc_m1 < opts->ulsc_m2 && opts->ulsc_m1 != 0)) {
+ warnx("m1 must be zero for convex curve: %s", pa->qname);
+ return (-1);
+ }
+
+ /*
+ * admission control:
+ * for the real-time service curve, the sum of the service curves
+ * should not exceed 80% of the interface bandwidth. 20% is reserved
+ * not to over-commit the actual interface bandwidth.
+ * for the linkshare service curve, the sum of the child service
+ * curve should not exceed the parent service curve.
+ * for the upper-limit service curve, the assigned bandwidth should
+ * be smaller than the interface bandwidth, and the upper-limit should
+ * be larger than the real-time service curve when both are defined.
+ */
+ parent = qname_to_pfaltq(pa->parent, pa->ifname);
+ if (parent == NULL)
+ errx(1, "parent %s not found for %s", pa->parent, pa->qname);
+
+ TAILQ_FOREACH(altq, &altqs, entries) {
+ if (strncmp(altq->ifname, pa->ifname, IFNAMSIZ) != 0)
+ continue;
+ if (altq->qname[0] == 0) /* this is for interface */
+ continue;
+
+ /* if the class has a real-time service curve, add it. */
+ if (opts->rtsc_m2 != 0 && altq->pq_u.hfsc_opts.rtsc_m2 != 0) {
+ sc.m1 = altq->pq_u.hfsc_opts.rtsc_m1;
+ sc.d = altq->pq_u.hfsc_opts.rtsc_d;
+ sc.m2 = altq->pq_u.hfsc_opts.rtsc_m2;
+ gsc_add_sc(&rtsc, &sc);
+ }
+
+ if (strncmp(altq->parent, pa->parent, PF_QNAME_SIZE) != 0)
+ continue;
+
+ /* if the class has a linkshare service curve, add it. */
+ if (opts->lssc_m2 != 0 && altq->pq_u.hfsc_opts.lssc_m2 != 0) {
+ sc.m1 = altq->pq_u.hfsc_opts.lssc_m1;
+ sc.d = altq->pq_u.hfsc_opts.lssc_d;
+ sc.m2 = altq->pq_u.hfsc_opts.lssc_m2;
+ gsc_add_sc(&lssc, &sc);
+ }
+ }
+
+ /* check the real-time service curve. reserve 20% of interface bw */
+ if (opts->rtsc_m2 != 0) {
+ /* add this queue to the sum */
+ sc.m1 = opts->rtsc_m1;
+ sc.d = opts->rtsc_d;
+ sc.m2 = opts->rtsc_m2;
+ gsc_add_sc(&rtsc, &sc);
+ /* compare the sum with 80% of the interface */
+ sc.m1 = 0;
+ sc.d = 0;
+ sc.m2 = pa->ifbandwidth / 100 * 80;
+ if (!is_gsc_under_sc(&rtsc, &sc)) {
+ warnx("real-time sc exceeds 80%% of the interface "
+ "bandwidth (%s)", rate2str((double)sc.m2));
+ goto err_ret;
+ }
+ }
+
+ /* check the linkshare service curve. */
+ if (opts->lssc_m2 != 0) {
+ /* add this queue to the child sum */
+ sc.m1 = opts->lssc_m1;
+ sc.d = opts->lssc_d;
+ sc.m2 = opts->lssc_m2;
+ gsc_add_sc(&lssc, &sc);
+ /* compare the sum of the children with parent's sc */
+ sc.m1 = parent->pq_u.hfsc_opts.lssc_m1;
+ sc.d = parent->pq_u.hfsc_opts.lssc_d;
+ sc.m2 = parent->pq_u.hfsc_opts.lssc_m2;
+ if (!is_gsc_under_sc(&lssc, &sc)) {
+ warnx("linkshare sc exceeds parent's sc");
+ goto err_ret;
+ }
+ }
+
+ /* check the upper-limit service curve. */
+ if (opts->ulsc_m2 != 0) {
+ if (opts->ulsc_m1 > pa->ifbandwidth ||
+ opts->ulsc_m2 > pa->ifbandwidth) {
+ warnx("upper-limit larger than interface bandwidth");
+ goto err_ret;
+ }
+ if (opts->rtsc_m2 != 0 && opts->rtsc_m2 > opts->ulsc_m2) {
+ warnx("upper-limit sc smaller than real-time sc");
+ goto err_ret;
+ }
+ }
+
+ gsc_destroy(&rtsc);
+ gsc_destroy(&lssc);
+
+ return (0);
+
+err_ret:
+ gsc_destroy(&rtsc);
+ gsc_destroy(&lssc);
+ return (-1);
+}
+
+static int
+check_commit_hfsc(int dev, int opts, struct pf_altq *pa)
+{
+ struct pf_altq *altq, *def = NULL;
+ int default_class;
+ int error = 0;
+
+ /* check if hfsc has one default queue for this interface */
+ default_class = 0;
+ TAILQ_FOREACH(altq, &altqs, entries) {
+ if (strncmp(altq->ifname, pa->ifname, IFNAMSIZ) != 0)
+ continue;
+ if (altq->qname[0] == 0) /* this is for interface */
+ continue;
+ if (altq->parent[0] == 0) /* dummy root */
+ continue;
+ if (altq->pq_u.hfsc_opts.flags & HFCF_DEFAULTCLASS) {
+ default_class++;
+ def = altq;
+ }
+ }
+ if (default_class != 1) {
+ warnx("should have one default queue on %s", pa->ifname);
+ return (1);
+ }
+ /* make sure the default queue is a leaf */
+ TAILQ_FOREACH(altq, &altqs, entries) {
+ if (strncmp(altq->ifname, pa->ifname, IFNAMSIZ) != 0)
+ continue;
+ if (altq->qname[0] == 0) /* this is for interface */
+ continue;
+ if (strncmp(altq->parent, def->qname, PF_QNAME_SIZE) == 0) {
+ warnx("default queue is not a leaf");
+ error++;
+ }
+ }
+ return (error);
+}
+
+static int
+print_hfsc_opts(const struct pf_altq *a, const struct node_queue_opt *qopts)
+{
+ const struct hfsc_opts *opts;
+ const struct node_hfsc_sc *rtsc, *lssc, *ulsc;
+
+ opts = &a->pq_u.hfsc_opts;
+ if (qopts == NULL)
+ rtsc = lssc = ulsc = NULL;
+ else {
+ rtsc = &qopts->data.hfsc_opts.realtime;
+ lssc = &qopts->data.hfsc_opts.linkshare;
+ ulsc = &qopts->data.hfsc_opts.upperlimit;
+ }
+
+ if (opts->flags || opts->rtsc_m2 != 0 || opts->ulsc_m2 != 0 ||
+ (opts->lssc_m2 != 0 && (opts->lssc_m2 != a->bandwidth ||
+ opts->lssc_d != 0))) {
+ printf("hfsc(");
+ if (opts->flags & HFCF_RED)
+ printf(" red");
+ if (opts->flags & HFCF_ECN)
+ printf(" ecn");
+ if (opts->flags & HFCF_RIO)
+ printf(" rio");
+ if (opts->flags & HFCF_CLEARDSCP)
+ printf(" cleardscp");
+ if (opts->flags & HFCF_DEFAULTCLASS)
+ printf(" default");
+ if (opts->rtsc_m2 != 0)
+ print_hfsc_sc("realtime", opts->rtsc_m1, opts->rtsc_d,
+ opts->rtsc_m2, rtsc);
+ if (opts->lssc_m2 != 0 && (opts->lssc_m2 != a->bandwidth ||
+ opts->lssc_d != 0))
+ print_hfsc_sc("linkshare", opts->lssc_m1, opts->lssc_d,
+ opts->lssc_m2, lssc);
+ if (opts->ulsc_m2 != 0)
+ print_hfsc_sc("upperlimit", opts->ulsc_m1, opts->ulsc_d,
+ opts->ulsc_m2, ulsc);
+ printf(" ) ");
+
+ return (1);
+ } else
+ return (0);
+}
+
+/*
+ * admission control using generalized service curve
+ */
+
+/* add a new service curve to a generalized service curve */
+static void
+gsc_add_sc(struct gen_sc *gsc, struct service_curve *sc)
+{
+ if (is_sc_null(sc))
+ return;
+ if (sc->d != 0)
+ gsc_add_seg(gsc, 0.0, 0.0, (double)sc->d, (double)sc->m1);
+ gsc_add_seg(gsc, (double)sc->d, 0.0, INFINITY, (double)sc->m2);
+}
+
+/*
+ * check whether all points of a generalized service curve have
+ * their y-coordinates no larger than a given two-piece linear
+ * service curve.
+ */
+static int
+is_gsc_under_sc(struct gen_sc *gsc, struct service_curve *sc)
+{
+ struct segment *s, *last, *end;
+ double y;
+
+ if (is_sc_null(sc)) {
+ if (LIST_EMPTY(gsc))
+ return (1);
+ LIST_FOREACH(s, gsc, _next) {
+ if (s->m != 0)
+ return (0);
+ }
+ return (1);
+ }
+ /*
+ * gsc has a dummy entry at the end with x = INFINITY.
+ * loop through up to this dummy entry.
+ */
+ end = gsc_getentry(gsc, INFINITY);
+ if (end == NULL)
+ return (1);
+ last = NULL;
+ for (s = LIST_FIRST(gsc); s != end; s = LIST_NEXT(s, _next)) {
+ if (s->y > sc_x2y(sc, s->x))
+ return (0);
+ last = s;
+ }
+ /* last now holds the real last segment */
+ if (last == NULL)
+ return (1);
+ if (last->m > sc->m2)
+ return (0);
+ if (last->x < sc->d && last->m > sc->m1) {
+ y = last->y + (sc->d - last->x) * last->m;
+ if (y > sc_x2y(sc, sc->d))
+ return (0);
+ }
+ return (1);
+}
+
+static void
+gsc_destroy(struct gen_sc *gsc)
+{
+ struct segment *s;
+
+ while ((s = LIST_FIRST(gsc)) != NULL) {
+ LIST_REMOVE(s, _next);
+ free(s);
+ }
+}
+
+/*
+ * return a segment entry starting at x.
+ * if gsc has no entry starting at x, a new entry is created at x.
+ */
+static struct segment *
+gsc_getentry(struct gen_sc *gsc, double x)
+{
+ struct segment *new, *prev, *s;
+
+ prev = NULL;
+ LIST_FOREACH(s, gsc, _next) {
+ if (s->x == x)
+ return (s); /* matching entry found */
+ else if (s->x < x)
+ prev = s;
+ else
+ break;
+ }
+
+ /* we have to create a new entry */
+ if ((new = calloc(1, sizeof(struct segment))) == NULL)
+ return (NULL);
+
+ new->x = x;
+ if (x == INFINITY || s == NULL)
+ new->d = 0;
+ else if (s->x == INFINITY)
+ new->d = INFINITY;
+ else
+ new->d = s->x - x;
+ if (prev == NULL) {
+ /* insert the new entry at the head of the list */
+ new->y = 0;
+ new->m = 0;
+ LIST_INSERT_HEAD(gsc, new, _next);
+ } else {
+ /*
+ * the start point intersects with the segment pointed by
+ * prev. divide prev into 2 segments
+ */
+ if (x == INFINITY) {
+ prev->d = INFINITY;
+ if (prev->m == 0)
+ new->y = prev->y;
+ else
+ new->y = INFINITY;
+ } else {
+ prev->d = x - prev->x;
+ new->y = prev->d * prev->m + prev->y;
+ }
+ new->m = prev->m;
+ LIST_INSERT_AFTER(prev, new, _next);
+ }
+ return (new);
+}
+
+/* add a segment to a generalized service curve */
+static int
+gsc_add_seg(struct gen_sc *gsc, double x, double y, double d, double m)
+{
+ struct segment *start, *end, *s;
+ double x2;
+
+ if (d == INFINITY)
+ x2 = INFINITY;
+ else
+ x2 = x + d;
+ start = gsc_getentry(gsc, x);
+ end = gsc_getentry(gsc, x2);
+ if (start == NULL || end == NULL)
+ return (-1);
+
+ for (s = start; s != end; s = LIST_NEXT(s, _next)) {
+ s->m += m;
+ s->y += y + (s->x - x) * m;
+ }
+
+ end = gsc_getentry(gsc, INFINITY);
+ for (; s != end; s = LIST_NEXT(s, _next)) {
+ s->y += m * d;
+ }
+
+ return (0);
+}
+
+/* get y-projection of a service curve */
+static double
+sc_x2y(struct service_curve *sc, double x)
+{
+ double y;
+
+ if (x <= (double)sc->d)
+ /* y belongs to the 1st segment */
+ y = x * (double)sc->m1;
+ else
+ /* y belongs to the 2nd segment */
+ y = (double)sc->d * (double)sc->m1
+ + (x - (double)sc->d) * (double)sc->m2;
+ return (y);
+}
+
+/*
+ * misc utilities
+ */
+#define R2S_BUFS 8
+#define RATESTR_MAX 16
+
+char *
+rate2str(double rate)
+{
+ char *buf;
+ static char r2sbuf[R2S_BUFS][RATESTR_MAX]; /* ring bufer */
+ static int idx = 0;
+ int i;
+ static const char unit[] = " KMG";
+
+ buf = r2sbuf[idx++];
+ if (idx == R2S_BUFS)
+ idx = 0;
+
+ for (i = 0; rate >= 1000 && i <= 3; i++)
+ rate /= 1000;
+
+ if ((int)(rate * 100) % 100)
+ snprintf(buf, RATESTR_MAX, "%.2f%cb", rate, unit[i]);
+ else
+ snprintf(buf, RATESTR_MAX, "%d%cb", (int)rate, unit[i]);
+
+ return (buf);
+}
+
+#ifdef __FreeBSD__
+/*
+ * XXX
+ * FreeBSD does not have SIOCGIFDATA.
+ * To emulate this, DIOCGIFSPEED ioctl added to pf.
+ */
+u_int32_t
+getifspeed(int pfdev, char *ifname)
+{
+ struct pf_ifspeed io;
+
+ bzero(&io, sizeof io);
+ if (strlcpy(io.ifname, ifname, IFNAMSIZ) >=
+ sizeof(io.ifname))
+ errx(1, "getifspeed: strlcpy");
+ if (ioctl(pfdev, DIOCGIFSPEED, &io) == -1)
+ err(1, "DIOCGIFSPEED");
+ return ((u_int32_t)io.baudrate);
+}
+#else
+u_int32_t
+getifspeed(char *ifname)
+{
+ int s;
+ struct ifreq ifr;
+ struct if_data ifrdat;
+
+ if ((s = socket(get_socket_domain(), SOCK_DGRAM, 0)) < 0)
+ err(1, "socket");
+ bzero(&ifr, sizeof(ifr));
+ if (strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)) >=
+ sizeof(ifr.ifr_name))
+ errx(1, "getifspeed: strlcpy");
+ ifr.ifr_data = (caddr_t)&ifrdat;
+ if (ioctl(s, SIOCGIFDATA, (caddr_t)&ifr) == -1)
+ err(1, "SIOCGIFDATA");
+ if (close(s))
+ err(1, "close");
+ return ((u_int32_t)ifrdat.ifi_baudrate);
+}
+#endif
+
+u_long
+getifmtu(char *ifname)
+{
+ int s;
+ struct ifreq ifr;
+
+ if ((s = socket(get_socket_domain(), SOCK_DGRAM, 0)) < 0)
+ err(1, "socket");
+ bzero(&ifr, sizeof(ifr));
+ if (strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)) >=
+ sizeof(ifr.ifr_name))
+ errx(1, "getifmtu: strlcpy");
+ if (ioctl(s, SIOCGIFMTU, (caddr_t)&ifr) == -1)
+#ifdef __FreeBSD__
+ ifr.ifr_mtu = 1500;
+#else
+ err(1, "SIOCGIFMTU");
+#endif
+ if (close(s))
+ err(1, "close");
+ if (ifr.ifr_mtu > 0)
+ return (ifr.ifr_mtu);
+ else {
+ warnx("could not get mtu for %s, assuming 1500", ifname);
+ return (1500);
+ }
+}
+
+int
+eval_queue_opts(struct pf_altq *pa, struct node_queue_opt *opts,
+ u_int32_t ref_bw)
+{
+ int errors = 0;
+
+ switch (pa->scheduler) {
+ case ALTQT_CBQ:
+ pa->pq_u.cbq_opts = opts->data.cbq_opts;
+ break;
+ case ALTQT_PRIQ:
+ pa->pq_u.priq_opts = opts->data.priq_opts;
+ break;
+ case ALTQT_HFSC:
+ pa->pq_u.hfsc_opts.flags = opts->data.hfsc_opts.flags;
+ if (opts->data.hfsc_opts.linkshare.used) {
+ pa->pq_u.hfsc_opts.lssc_m1 =
+ eval_bwspec(&opts->data.hfsc_opts.linkshare.m1,
+ ref_bw);
+ pa->pq_u.hfsc_opts.lssc_m2 =
+ eval_bwspec(&opts->data.hfsc_opts.linkshare.m2,
+ ref_bw);
+ pa->pq_u.hfsc_opts.lssc_d =
+ opts->data.hfsc_opts.linkshare.d;
+ }
+ if (opts->data.hfsc_opts.realtime.used) {
+ pa->pq_u.hfsc_opts.rtsc_m1 =
+ eval_bwspec(&opts->data.hfsc_opts.realtime.m1,
+ ref_bw);
+ pa->pq_u.hfsc_opts.rtsc_m2 =
+ eval_bwspec(&opts->data.hfsc_opts.realtime.m2,
+ ref_bw);
+ pa->pq_u.hfsc_opts.rtsc_d =
+ opts->data.hfsc_opts.realtime.d;
+ }
+ if (opts->data.hfsc_opts.upperlimit.used) {
+ pa->pq_u.hfsc_opts.ulsc_m1 =
+ eval_bwspec(&opts->data.hfsc_opts.upperlimit.m1,
+ ref_bw);
+ pa->pq_u.hfsc_opts.ulsc_m2 =
+ eval_bwspec(&opts->data.hfsc_opts.upperlimit.m2,
+ ref_bw);
+ pa->pq_u.hfsc_opts.ulsc_d =
+ opts->data.hfsc_opts.upperlimit.d;
+ }
+ break;
+ default:
+ warnx("eval_queue_opts: unknown scheduler type %u",
+ opts->qtype);
+ errors++;
+ break;
+ }
+
+ return (errors);
+}
+
+u_int32_t
+eval_bwspec(struct node_queue_bw *bw, u_int32_t ref_bw)
+{
+ if (bw->bw_absolute > 0)
+ return (bw->bw_absolute);
+
+ if (bw->bw_percent > 0)
+ return (ref_bw / 100 * bw->bw_percent);
+
+ return (0);
+}
+
+void
+print_hfsc_sc(const char *scname, u_int m1, u_int d, u_int m2,
+ const struct node_hfsc_sc *sc)
+{
+ printf(" %s", scname);
+
+ if (d != 0) {
+ printf("(");
+ if (sc != NULL && sc->m1.bw_percent > 0)
+ printf("%u%%", sc->m1.bw_percent);
+ else
+ printf("%s", rate2str((double)m1));
+ printf(" %u", d);
+ }
+
+ if (sc != NULL && sc->m2.bw_percent > 0)
+ printf(" %u%%", sc->m2.bw_percent);
+ else
+ printf(" %s", rate2str((double)m2));
+
+ if (d != 0)
+ printf(")");
+}
diff --git a/sbin/pfctl/pfctl_optimize.c b/sbin/pfctl/pfctl_optimize.c
new file mode 100644
index 0000000..9511720
--- /dev/null
+++ b/sbin/pfctl/pfctl_optimize.c
@@ -0,0 +1,1655 @@
+/* $OpenBSD: pfctl_optimize.c,v 1.17 2008/05/06 03:45:21 mpf Exp $ */
+
+/*
+ * Copyright (c) 2004 Mike Frantzen <frantzen@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <net/if.h>
+#include <net/pfvar.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "pfctl_parser.h"
+#include "pfctl.h"
+
+/* The size at which a table becomes faster than individual rules */
+#define TABLE_THRESHOLD 6
+
+
+/* #define OPT_DEBUG 1 */
+#ifdef OPT_DEBUG
+# define DEBUG(str, v...) \
+ printf("%s: " str "\n", __FUNCTION__ , ## v)
+#else
+# define DEBUG(str, v...) ((void)0)
+#endif
+
+
+/*
+ * A container that lets us sort a superblock to optimize the skip step jumps
+ */
+struct pf_skip_step {
+ int ps_count; /* number of items */
+ TAILQ_HEAD( , pf_opt_rule) ps_rules;
+ TAILQ_ENTRY(pf_skip_step) ps_entry;
+};
+
+
+/*
+ * A superblock is a block of adjacent rules of similar action. If there
+ * are five PASS rules in a row, they all become members of a superblock.
+ * Once we have a superblock, we are free to re-order any rules within it
+ * in order to improve performance; if a packet is passed, it doesn't matter
+ * who passed it.
+ */
+struct superblock {
+ TAILQ_HEAD( , pf_opt_rule) sb_rules;
+ TAILQ_ENTRY(superblock) sb_entry;
+ struct superblock *sb_profiled_block;
+ TAILQ_HEAD(skiplist, pf_skip_step) sb_skipsteps[PF_SKIP_COUNT];
+};
+TAILQ_HEAD(superblocks, superblock);
+
+
+/*
+ * Description of the PF rule structure.
+ */
+enum {
+ BARRIER, /* the presence of the field puts the rule in it's own block */
+ BREAK, /* the field may not differ between rules in a superblock */
+ NOMERGE, /* the field may not differ between rules when combined */
+ COMBINED, /* the field may itself be combined with other rules */
+ DC, /* we just don't care about the field */
+ NEVER}; /* we should never see this field set?!? */
+struct pf_rule_field {
+ const char *prf_name;
+ int prf_type;
+ size_t prf_offset;
+ size_t prf_size;
+} pf_rule_desc[] = {
+#define PF_RULE_FIELD(field, ty) \
+ {#field, \
+ ty, \
+ offsetof(struct pf_rule, field), \
+ sizeof(((struct pf_rule *)0)->field)}
+
+
+ /*
+ * The presence of these fields in a rule put the rule in it's own
+ * superblock. Thus it will not be optimized. It also prevents the
+ * rule from being re-ordered at all.
+ */
+ PF_RULE_FIELD(label, BARRIER),
+ PF_RULE_FIELD(prob, BARRIER),
+ PF_RULE_FIELD(max_states, BARRIER),
+ PF_RULE_FIELD(max_src_nodes, BARRIER),
+ PF_RULE_FIELD(max_src_states, BARRIER),
+ PF_RULE_FIELD(max_src_conn, BARRIER),
+ PF_RULE_FIELD(max_src_conn_rate, BARRIER),
+ PF_RULE_FIELD(anchor, BARRIER), /* for now */
+
+ /*
+ * These fields must be the same between all rules in the same superblock.
+ * These rules are allowed to be re-ordered but only among like rules.
+ * For instance we can re-order all 'tag "foo"' rules because they have the
+ * same tag. But we can not re-order between a 'tag "foo"' and a
+ * 'tag "bar"' since that would change the meaning of the ruleset.
+ */
+ PF_RULE_FIELD(tagname, BREAK),
+ PF_RULE_FIELD(keep_state, BREAK),
+ PF_RULE_FIELD(qname, BREAK),
+ PF_RULE_FIELD(pqname, BREAK),
+ PF_RULE_FIELD(rt, BREAK),
+ PF_RULE_FIELD(allow_opts, BREAK),
+ PF_RULE_FIELD(rule_flag, BREAK),
+ PF_RULE_FIELD(action, BREAK),
+ PF_RULE_FIELD(log, BREAK),
+ PF_RULE_FIELD(quick, BREAK),
+ PF_RULE_FIELD(return_ttl, BREAK),
+ PF_RULE_FIELD(overload_tblname, BREAK),
+ PF_RULE_FIELD(flush, BREAK),
+ PF_RULE_FIELD(rpool, BREAK),
+ PF_RULE_FIELD(logif, BREAK),
+
+ /*
+ * Any fields not listed in this structure act as BREAK fields
+ */
+
+
+ /*
+ * These fields must not differ when we merge two rules together but
+ * their difference isn't enough to put the rules in different superblocks.
+ * There are no problems re-ordering any rules with these fields.
+ */
+ PF_RULE_FIELD(af, NOMERGE),
+ PF_RULE_FIELD(ifnot, NOMERGE),
+ PF_RULE_FIELD(ifname, NOMERGE), /* hack for IF groups */
+ PF_RULE_FIELD(match_tag_not, NOMERGE),
+ PF_RULE_FIELD(match_tagname, NOMERGE),
+ PF_RULE_FIELD(os_fingerprint, NOMERGE),
+ PF_RULE_FIELD(timeout, NOMERGE),
+ PF_RULE_FIELD(return_icmp, NOMERGE),
+ PF_RULE_FIELD(return_icmp6, NOMERGE),
+ PF_RULE_FIELD(uid, NOMERGE),
+ PF_RULE_FIELD(gid, NOMERGE),
+ PF_RULE_FIELD(direction, NOMERGE),
+ PF_RULE_FIELD(proto, NOMERGE),
+ PF_RULE_FIELD(type, NOMERGE),
+ PF_RULE_FIELD(code, NOMERGE),
+ PF_RULE_FIELD(flags, NOMERGE),
+ PF_RULE_FIELD(flagset, NOMERGE),
+ PF_RULE_FIELD(tos, NOMERGE),
+ PF_RULE_FIELD(src.port, NOMERGE),
+ PF_RULE_FIELD(dst.port, NOMERGE),
+ PF_RULE_FIELD(src.port_op, NOMERGE),
+ PF_RULE_FIELD(dst.port_op, NOMERGE),
+ PF_RULE_FIELD(src.neg, NOMERGE),
+ PF_RULE_FIELD(dst.neg, NOMERGE),
+
+ /* These fields can be merged */
+ PF_RULE_FIELD(src.addr, COMBINED),
+ PF_RULE_FIELD(dst.addr, COMBINED),
+
+ /* We just don't care about these fields. They're set by the kernel */
+ PF_RULE_FIELD(skip, DC),
+ PF_RULE_FIELD(evaluations, DC),
+ PF_RULE_FIELD(packets, DC),
+ PF_RULE_FIELD(bytes, DC),
+ PF_RULE_FIELD(kif, DC),
+ PF_RULE_FIELD(states_cur, DC),
+ PF_RULE_FIELD(states_tot, DC),
+ PF_RULE_FIELD(src_nodes, DC),
+ PF_RULE_FIELD(nr, DC),
+ PF_RULE_FIELD(entries, DC),
+ PF_RULE_FIELD(qid, DC),
+ PF_RULE_FIELD(pqid, DC),
+ PF_RULE_FIELD(anchor_relative, DC),
+ PF_RULE_FIELD(anchor_wildcard, DC),
+ PF_RULE_FIELD(tag, DC),
+ PF_RULE_FIELD(match_tag, DC),
+ PF_RULE_FIELD(overload_tbl, DC),
+
+ /* These fields should never be set in a PASS/BLOCK rule */
+ PF_RULE_FIELD(natpass, NEVER),
+ PF_RULE_FIELD(max_mss, NEVER),
+ PF_RULE_FIELD(min_ttl, NEVER),
+ PF_RULE_FIELD(set_tos, NEVER),
+};
+
+
+
+int add_opt_table(struct pfctl *, struct pf_opt_tbl **, sa_family_t,
+ struct pf_rule_addr *);
+int addrs_combineable(struct pf_rule_addr *, struct pf_rule_addr *);
+int addrs_equal(struct pf_rule_addr *, struct pf_rule_addr *);
+int block_feedback(struct pfctl *, struct superblock *);
+int combine_rules(struct pfctl *, struct superblock *);
+void comparable_rule(struct pf_rule *, const struct pf_rule *, int);
+int construct_superblocks(struct pfctl *, struct pf_opt_queue *,
+ struct superblocks *);
+void exclude_supersets(struct pf_rule *, struct pf_rule *);
+int interface_group(const char *);
+int load_feedback_profile(struct pfctl *, struct superblocks *);
+int optimize_superblock(struct pfctl *, struct superblock *);
+int pf_opt_create_table(struct pfctl *, struct pf_opt_tbl *);
+void remove_from_skipsteps(struct skiplist *, struct superblock *,
+ struct pf_opt_rule *, struct pf_skip_step *);
+int remove_identical_rules(struct pfctl *, struct superblock *);
+int reorder_rules(struct pfctl *, struct superblock *, int);
+int rules_combineable(struct pf_rule *, struct pf_rule *);
+void skip_append(struct superblock *, int, struct pf_skip_step *,
+ struct pf_opt_rule *);
+int skip_compare(int, struct pf_skip_step *, struct pf_opt_rule *);
+void skip_init(void);
+int skip_cmp_af(struct pf_rule *, struct pf_rule *);
+int skip_cmp_dir(struct pf_rule *, struct pf_rule *);
+int skip_cmp_dst_addr(struct pf_rule *, struct pf_rule *);
+int skip_cmp_dst_port(struct pf_rule *, struct pf_rule *);
+int skip_cmp_ifp(struct pf_rule *, struct pf_rule *);
+int skip_cmp_proto(struct pf_rule *, struct pf_rule *);
+int skip_cmp_src_addr(struct pf_rule *, struct pf_rule *);
+int skip_cmp_src_port(struct pf_rule *, struct pf_rule *);
+int superblock_inclusive(struct superblock *, struct pf_opt_rule *);
+void superblock_free(struct pfctl *, struct superblock *);
+
+
+int (*skip_comparitors[PF_SKIP_COUNT])(struct pf_rule *, struct pf_rule *);
+const char *skip_comparitors_names[PF_SKIP_COUNT];
+#define PF_SKIP_COMPARITORS { \
+ { "ifp", PF_SKIP_IFP, skip_cmp_ifp }, \
+ { "dir", PF_SKIP_DIR, skip_cmp_dir }, \
+ { "af", PF_SKIP_AF, skip_cmp_af }, \
+ { "proto", PF_SKIP_PROTO, skip_cmp_proto }, \
+ { "saddr", PF_SKIP_SRC_ADDR, skip_cmp_src_addr }, \
+ { "sport", PF_SKIP_SRC_PORT, skip_cmp_src_port }, \
+ { "daddr", PF_SKIP_DST_ADDR, skip_cmp_dst_addr }, \
+ { "dport", PF_SKIP_DST_PORT, skip_cmp_dst_port } \
+}
+
+struct pfr_buffer table_buffer;
+int table_identifier;
+
+
+int
+pfctl_optimize_ruleset(struct pfctl *pf, struct pf_ruleset *rs)
+{
+ struct superblocks superblocks;
+ struct pf_opt_queue opt_queue;
+ struct superblock *block;
+ struct pf_opt_rule *por;
+ struct pf_rule *r;
+ struct pf_rulequeue *old_rules;
+
+ DEBUG("optimizing ruleset");
+ memset(&table_buffer, 0, sizeof(table_buffer));
+ skip_init();
+ TAILQ_INIT(&opt_queue);
+
+ old_rules = rs->rules[PF_RULESET_FILTER].active.ptr;
+ rs->rules[PF_RULESET_FILTER].active.ptr =
+ rs->rules[PF_RULESET_FILTER].inactive.ptr;
+ rs->rules[PF_RULESET_FILTER].inactive.ptr = old_rules;
+
+ /*
+ * XXX expanding the pf_opt_rule format throughout pfctl might allow
+ * us to avoid all this copying.
+ */
+ while ((r = TAILQ_FIRST(rs->rules[PF_RULESET_FILTER].inactive.ptr))
+ != NULL) {
+ TAILQ_REMOVE(rs->rules[PF_RULESET_FILTER].inactive.ptr, r,
+ entries);
+ if ((por = calloc(1, sizeof(*por))) == NULL)
+ err(1, "calloc");
+ memcpy(&por->por_rule, r, sizeof(*r));
+ if (TAILQ_FIRST(&r->rpool.list) != NULL) {
+ TAILQ_INIT(&por->por_rule.rpool.list);
+ pfctl_move_pool(&r->rpool, &por->por_rule.rpool);
+ } else
+ bzero(&por->por_rule.rpool,
+ sizeof(por->por_rule.rpool));
+
+
+ TAILQ_INSERT_TAIL(&opt_queue, por, por_entry);
+ }
+
+ TAILQ_INIT(&superblocks);
+ if (construct_superblocks(pf, &opt_queue, &superblocks))
+ goto error;
+
+ if (pf->optimize & PF_OPTIMIZE_PROFILE) {
+ if (load_feedback_profile(pf, &superblocks))
+ goto error;
+ }
+
+ TAILQ_FOREACH(block, &superblocks, sb_entry) {
+ if (optimize_superblock(pf, block))
+ goto error;
+ }
+
+ rs->anchor->refcnt = 0;
+ while ((block = TAILQ_FIRST(&superblocks))) {
+ TAILQ_REMOVE(&superblocks, block, sb_entry);
+
+ while ((por = TAILQ_FIRST(&block->sb_rules))) {
+ TAILQ_REMOVE(&block->sb_rules, por, por_entry);
+ por->por_rule.nr = rs->anchor->refcnt++;
+ if ((r = calloc(1, sizeof(*r))) == NULL)
+ err(1, "calloc");
+ memcpy(r, &por->por_rule, sizeof(*r));
+ TAILQ_INIT(&r->rpool.list);
+ pfctl_move_pool(&por->por_rule.rpool, &r->rpool);
+ TAILQ_INSERT_TAIL(
+ rs->rules[PF_RULESET_FILTER].active.ptr,
+ r, entries);
+ free(por);
+ }
+ free(block);
+ }
+
+ return (0);
+
+error:
+ while ((por = TAILQ_FIRST(&opt_queue))) {
+ TAILQ_REMOVE(&opt_queue, por, por_entry);
+ if (por->por_src_tbl) {
+ pfr_buf_clear(por->por_src_tbl->pt_buf);
+ free(por->por_src_tbl->pt_buf);
+ free(por->por_src_tbl);
+ }
+ if (por->por_dst_tbl) {
+ pfr_buf_clear(por->por_dst_tbl->pt_buf);
+ free(por->por_dst_tbl->pt_buf);
+ free(por->por_dst_tbl);
+ }
+ free(por);
+ }
+ while ((block = TAILQ_FIRST(&superblocks))) {
+ TAILQ_REMOVE(&superblocks, block, sb_entry);
+ superblock_free(pf, block);
+ }
+ return (1);
+}
+
+
+/*
+ * Go ahead and optimize a superblock
+ */
+int
+optimize_superblock(struct pfctl *pf, struct superblock *block)
+{
+#ifdef OPT_DEBUG
+ struct pf_opt_rule *por;
+#endif /* OPT_DEBUG */
+
+ /* We have a few optimization passes:
+ * 1) remove duplicate rules or rules that are a subset of other
+ * rules
+ * 2) combine otherwise identical rules with different IP addresses
+ * into a single rule and put the addresses in a table.
+ * 3) re-order the rules to improve kernel skip steps
+ * 4) re-order the 'quick' rules based on feedback from the
+ * active ruleset statistics
+ *
+ * XXX combine_rules() doesn't combine v4 and v6 rules. would just
+ * have to keep af in the table container, make af 'COMBINE' and
+ * twiddle the af on the merged rule
+ * XXX maybe add a weighting to the metric on skipsteps when doing
+ * reordering. sometimes two sequential tables will be better
+ * that four consecutive interfaces.
+ * XXX need to adjust the skipstep count of everything after PROTO,
+ * since they aren't actually checked on a proto mismatch in
+ * pf_test_{tcp, udp, icmp}()
+ * XXX should i treat proto=0, af=0 or dir=0 special in skepstep
+ * calculation since they are a DC?
+ * XXX keep last skiplist of last superblock to influence this
+ * superblock. '5 inet6 log' should make '3 inet6' come before '4
+ * inet' in the next superblock.
+ * XXX would be useful to add tables for ports
+ * XXX we can also re-order some mutually exclusive superblocks to
+ * try merging superblocks before any of these optimization passes.
+ * for instance a single 'log in' rule in the middle of non-logging
+ * out rules.
+ */
+
+ /* shortcut. there will be a lot of 1-rule superblocks */
+ if (!TAILQ_NEXT(TAILQ_FIRST(&block->sb_rules), por_entry))
+ return (0);
+
+#ifdef OPT_DEBUG
+ printf("--- Superblock ---\n");
+ TAILQ_FOREACH(por, &block->sb_rules, por_entry) {
+ printf(" ");
+ print_rule(&por->por_rule, por->por_rule.anchor ?
+ por->por_rule.anchor->name : "", 1, 0);
+ }
+#endif /* OPT_DEBUG */
+
+
+ if (remove_identical_rules(pf, block))
+ return (1);
+ if (combine_rules(pf, block))
+ return (1);
+ if ((pf->optimize & PF_OPTIMIZE_PROFILE) &&
+ TAILQ_FIRST(&block->sb_rules)->por_rule.quick &&
+ block->sb_profiled_block) {
+ if (block_feedback(pf, block))
+ return (1);
+ } else if (reorder_rules(pf, block, 0)) {
+ return (1);
+ }
+
+ /*
+ * Don't add any optimization passes below reorder_rules(). It will
+ * have divided superblocks into smaller blocks for further refinement
+ * and doesn't put them back together again. What once was a true
+ * superblock might have been split into multiple superblocks.
+ */
+
+#ifdef OPT_DEBUG
+ printf("--- END Superblock ---\n");
+#endif /* OPT_DEBUG */
+ return (0);
+}
+
+
+/*
+ * Optimization pass #1: remove identical rules
+ */
+int
+remove_identical_rules(struct pfctl *pf, struct superblock *block)
+{
+ struct pf_opt_rule *por1, *por2, *por_next, *por2_next;
+ struct pf_rule a, a2, b, b2;
+
+ for (por1 = TAILQ_FIRST(&block->sb_rules); por1; por1 = por_next) {
+ por_next = TAILQ_NEXT(por1, por_entry);
+ for (por2 = por_next; por2; por2 = por2_next) {
+ por2_next = TAILQ_NEXT(por2, por_entry);
+ comparable_rule(&a, &por1->por_rule, DC);
+ comparable_rule(&b, &por2->por_rule, DC);
+ memcpy(&a2, &a, sizeof(a2));
+ memcpy(&b2, &b, sizeof(b2));
+
+ exclude_supersets(&a, &b);
+ exclude_supersets(&b2, &a2);
+ if (memcmp(&a, &b, sizeof(a)) == 0) {
+ DEBUG("removing identical rule nr%d = *nr%d*",
+ por1->por_rule.nr, por2->por_rule.nr);
+ TAILQ_REMOVE(&block->sb_rules, por2, por_entry);
+ if (por_next == por2)
+ por_next = TAILQ_NEXT(por1, por_entry);
+ free(por2);
+ } else if (memcmp(&a2, &b2, sizeof(a2)) == 0) {
+ DEBUG("removing identical rule *nr%d* = nr%d",
+ por1->por_rule.nr, por2->por_rule.nr);
+ TAILQ_REMOVE(&block->sb_rules, por1, por_entry);
+ free(por1);
+ break;
+ }
+ }
+ }
+
+ return (0);
+}
+
+
+/*
+ * Optimization pass #2: combine similar rules with different addresses
+ * into a single rule and a table
+ */
+int
+combine_rules(struct pfctl *pf, struct superblock *block)
+{
+ struct pf_opt_rule *p1, *p2, *por_next;
+ int src_eq, dst_eq;
+
+ if ((pf->loadopt & PFCTL_FLAG_TABLE) == 0) {
+ warnx("Must enable table loading for optimizations");
+ return (1);
+ }
+
+ /* First we make a pass to combine the rules. O(n log n) */
+ TAILQ_FOREACH(p1, &block->sb_rules, por_entry) {
+ for (p2 = TAILQ_NEXT(p1, por_entry); p2; p2 = por_next) {
+ por_next = TAILQ_NEXT(p2, por_entry);
+
+ src_eq = addrs_equal(&p1->por_rule.src,
+ &p2->por_rule.src);
+ dst_eq = addrs_equal(&p1->por_rule.dst,
+ &p2->por_rule.dst);
+
+ if (src_eq && !dst_eq && p1->por_src_tbl == NULL &&
+ p2->por_dst_tbl == NULL &&
+ p2->por_src_tbl == NULL &&
+ rules_combineable(&p1->por_rule, &p2->por_rule) &&
+ addrs_combineable(&p1->por_rule.dst,
+ &p2->por_rule.dst)) {
+ DEBUG("can combine rules nr%d = nr%d",
+ p1->por_rule.nr, p2->por_rule.nr);
+ if (p1->por_dst_tbl == NULL &&
+ add_opt_table(pf, &p1->por_dst_tbl,
+ p1->por_rule.af, &p1->por_rule.dst))
+ return (1);
+ if (add_opt_table(pf, &p1->por_dst_tbl,
+ p1->por_rule.af, &p2->por_rule.dst))
+ return (1);
+ p2->por_dst_tbl = p1->por_dst_tbl;
+ if (p1->por_dst_tbl->pt_rulecount >=
+ TABLE_THRESHOLD) {
+ TAILQ_REMOVE(&block->sb_rules, p2,
+ por_entry);
+ free(p2);
+ }
+ } else if (!src_eq && dst_eq && p1->por_dst_tbl == NULL
+ && p2->por_src_tbl == NULL &&
+ p2->por_dst_tbl == NULL &&
+ rules_combineable(&p1->por_rule, &p2->por_rule) &&
+ addrs_combineable(&p1->por_rule.src,
+ &p2->por_rule.src)) {
+ DEBUG("can combine rules nr%d = nr%d",
+ p1->por_rule.nr, p2->por_rule.nr);
+ if (p1->por_src_tbl == NULL &&
+ add_opt_table(pf, &p1->por_src_tbl,
+ p1->por_rule.af, &p1->por_rule.src))
+ return (1);
+ if (add_opt_table(pf, &p1->por_src_tbl,
+ p1->por_rule.af, &p2->por_rule.src))
+ return (1);
+ p2->por_src_tbl = p1->por_src_tbl;
+ if (p1->por_src_tbl->pt_rulecount >=
+ TABLE_THRESHOLD) {
+ TAILQ_REMOVE(&block->sb_rules, p2,
+ por_entry);
+ free(p2);
+ }
+ }
+ }
+ }
+
+
+ /*
+ * Then we make a final pass to create a valid table name and
+ * insert the name into the rules.
+ */
+ for (p1 = TAILQ_FIRST(&block->sb_rules); p1; p1 = por_next) {
+ por_next = TAILQ_NEXT(p1, por_entry);
+ assert(p1->por_src_tbl == NULL || p1->por_dst_tbl == NULL);
+
+ if (p1->por_src_tbl && p1->por_src_tbl->pt_rulecount >=
+ TABLE_THRESHOLD) {
+ if (p1->por_src_tbl->pt_generated) {
+ /* This rule is included in a table */
+ TAILQ_REMOVE(&block->sb_rules, p1, por_entry);
+ free(p1);
+ continue;
+ }
+ p1->por_src_tbl->pt_generated = 1;
+
+ if ((pf->opts & PF_OPT_NOACTION) == 0 &&
+ pf_opt_create_table(pf, p1->por_src_tbl))
+ return (1);
+
+ pf->tdirty = 1;
+
+ if (pf->opts & PF_OPT_VERBOSE)
+ print_tabledef(p1->por_src_tbl->pt_name,
+ PFR_TFLAG_CONST, 1,
+ &p1->por_src_tbl->pt_nodes);
+
+ memset(&p1->por_rule.src.addr, 0,
+ sizeof(p1->por_rule.src.addr));
+ p1->por_rule.src.addr.type = PF_ADDR_TABLE;
+ strlcpy(p1->por_rule.src.addr.v.tblname,
+ p1->por_src_tbl->pt_name,
+ sizeof(p1->por_rule.src.addr.v.tblname));
+
+ pfr_buf_clear(p1->por_src_tbl->pt_buf);
+ free(p1->por_src_tbl->pt_buf);
+ p1->por_src_tbl->pt_buf = NULL;
+ }
+ if (p1->por_dst_tbl && p1->por_dst_tbl->pt_rulecount >=
+ TABLE_THRESHOLD) {
+ if (p1->por_dst_tbl->pt_generated) {
+ /* This rule is included in a table */
+ TAILQ_REMOVE(&block->sb_rules, p1, por_entry);
+ free(p1);
+ continue;
+ }
+ p1->por_dst_tbl->pt_generated = 1;
+
+ if ((pf->opts & PF_OPT_NOACTION) == 0 &&
+ pf_opt_create_table(pf, p1->por_dst_tbl))
+ return (1);
+ pf->tdirty = 1;
+
+ if (pf->opts & PF_OPT_VERBOSE)
+ print_tabledef(p1->por_dst_tbl->pt_name,
+ PFR_TFLAG_CONST, 1,
+ &p1->por_dst_tbl->pt_nodes);
+
+ memset(&p1->por_rule.dst.addr, 0,
+ sizeof(p1->por_rule.dst.addr));
+ p1->por_rule.dst.addr.type = PF_ADDR_TABLE;
+ strlcpy(p1->por_rule.dst.addr.v.tblname,
+ p1->por_dst_tbl->pt_name,
+ sizeof(p1->por_rule.dst.addr.v.tblname));
+
+ pfr_buf_clear(p1->por_dst_tbl->pt_buf);
+ free(p1->por_dst_tbl->pt_buf);
+ p1->por_dst_tbl->pt_buf = NULL;
+ }
+ }
+
+ return (0);
+}
+
+
+/*
+ * Optimization pass #3: re-order rules to improve skip steps
+ */
+int
+reorder_rules(struct pfctl *pf, struct superblock *block, int depth)
+{
+ struct superblock *newblock;
+ struct pf_skip_step *skiplist;
+ struct pf_opt_rule *por;
+ int i, largest, largest_list, rule_count = 0;
+ TAILQ_HEAD( , pf_opt_rule) head;
+
+ /*
+ * Calculate the best-case skip steps. We put each rule in a list
+ * of other rules with common fields
+ */
+ for (i = 0; i < PF_SKIP_COUNT; i++) {
+ TAILQ_FOREACH(por, &block->sb_rules, por_entry) {
+ TAILQ_FOREACH(skiplist, &block->sb_skipsteps[i],
+ ps_entry) {
+ if (skip_compare(i, skiplist, por) == 0)
+ break;
+ }
+ if (skiplist == NULL) {
+ if ((skiplist = calloc(1, sizeof(*skiplist))) ==
+ NULL)
+ err(1, "calloc");
+ TAILQ_INIT(&skiplist->ps_rules);
+ TAILQ_INSERT_TAIL(&block->sb_skipsteps[i],
+ skiplist, ps_entry);
+ }
+ skip_append(block, i, skiplist, por);
+ }
+ }
+
+ TAILQ_FOREACH(por, &block->sb_rules, por_entry)
+ rule_count++;
+
+ /*
+ * Now we're going to ignore any fields that are identical between
+ * all of the rules in the superblock and those fields which differ
+ * between every rule in the superblock.
+ */
+ largest = 0;
+ for (i = 0; i < PF_SKIP_COUNT; i++) {
+ skiplist = TAILQ_FIRST(&block->sb_skipsteps[i]);
+ if (skiplist->ps_count == rule_count) {
+ DEBUG("(%d) original skipstep '%s' is all rules",
+ depth, skip_comparitors_names[i]);
+ skiplist->ps_count = 0;
+ } else if (skiplist->ps_count == 1) {
+ skiplist->ps_count = 0;
+ } else {
+ DEBUG("(%d) original skipstep '%s' largest jump is %d",
+ depth, skip_comparitors_names[i],
+ skiplist->ps_count);
+ if (skiplist->ps_count > largest)
+ largest = skiplist->ps_count;
+ }
+ }
+ if (largest == 0) {
+ /* Ugh. There is NO commonality in the superblock on which
+ * optimize the skipsteps optimization.
+ */
+ goto done;
+ }
+
+ /*
+ * Now we're going to empty the superblock rule list and re-create
+ * it based on a more optimal skipstep order.
+ */
+ TAILQ_INIT(&head);
+ while ((por = TAILQ_FIRST(&block->sb_rules))) {
+ TAILQ_REMOVE(&block->sb_rules, por, por_entry);
+ TAILQ_INSERT_TAIL(&head, por, por_entry);
+ }
+
+
+ while (!TAILQ_EMPTY(&head)) {
+ largest = 1;
+
+ /*
+ * Find the most useful skip steps remaining
+ */
+ for (i = 0; i < PF_SKIP_COUNT; i++) {
+ skiplist = TAILQ_FIRST(&block->sb_skipsteps[i]);
+ if (skiplist->ps_count > largest) {
+ largest = skiplist->ps_count;
+ largest_list = i;
+ }
+ }
+
+ if (largest <= 1) {
+ /*
+ * Nothing useful left. Leave remaining rules in order.
+ */
+ DEBUG("(%d) no more commonality for skip steps", depth);
+ while ((por = TAILQ_FIRST(&head))) {
+ TAILQ_REMOVE(&head, por, por_entry);
+ TAILQ_INSERT_TAIL(&block->sb_rules, por,
+ por_entry);
+ }
+ } else {
+ /*
+ * There is commonality. Extract those common rules
+ * and place them in the ruleset adjacent to each
+ * other.
+ */
+ skiplist = TAILQ_FIRST(&block->sb_skipsteps[
+ largest_list]);
+ DEBUG("(%d) skipstep '%s' largest jump is %d @ #%d",
+ depth, skip_comparitors_names[largest_list],
+ largest, TAILQ_FIRST(&TAILQ_FIRST(&block->
+ sb_skipsteps [largest_list])->ps_rules)->
+ por_rule.nr);
+ TAILQ_REMOVE(&block->sb_skipsteps[largest_list],
+ skiplist, ps_entry);
+
+
+ /*
+ * There may be further commonality inside these
+ * rules. So we'll split them off into they're own
+ * superblock and pass it back into the optimizer.
+ */
+ if (skiplist->ps_count > 2) {
+ if ((newblock = calloc(1, sizeof(*newblock)))
+ == NULL) {
+ warn("calloc");
+ return (1);
+ }
+ TAILQ_INIT(&newblock->sb_rules);
+ for (i = 0; i < PF_SKIP_COUNT; i++)
+ TAILQ_INIT(&newblock->sb_skipsteps[i]);
+ TAILQ_INSERT_BEFORE(block, newblock, sb_entry);
+ DEBUG("(%d) splitting off %d rules from superblock @ #%d",
+ depth, skiplist->ps_count,
+ TAILQ_FIRST(&skiplist->ps_rules)->
+ por_rule.nr);
+ } else {
+ newblock = block;
+ }
+
+ while ((por = TAILQ_FIRST(&skiplist->ps_rules))) {
+ TAILQ_REMOVE(&head, por, por_entry);
+ TAILQ_REMOVE(&skiplist->ps_rules, por,
+ por_skip_entry[largest_list]);
+ TAILQ_INSERT_TAIL(&newblock->sb_rules, por,
+ por_entry);
+
+ /* Remove this rule from all other skiplists */
+ remove_from_skipsteps(&block->sb_skipsteps[
+ largest_list], block, por, skiplist);
+ }
+ free(skiplist);
+ if (newblock != block)
+ if (reorder_rules(pf, newblock, depth + 1))
+ return (1);
+ }
+ }
+
+done:
+ for (i = 0; i < PF_SKIP_COUNT; i++) {
+ while ((skiplist = TAILQ_FIRST(&block->sb_skipsteps[i]))) {
+ TAILQ_REMOVE(&block->sb_skipsteps[i], skiplist,
+ ps_entry);
+ free(skiplist);
+ }
+ }
+
+ return (0);
+}
+
+
+/*
+ * Optimization pass #4: re-order 'quick' rules based on feedback from the
+ * currently running ruleset
+ */
+int
+block_feedback(struct pfctl *pf, struct superblock *block)
+{
+ TAILQ_HEAD( , pf_opt_rule) queue;
+ struct pf_opt_rule *por1, *por2;
+ u_int64_t total_count = 0;
+ struct pf_rule a, b;
+
+
+ /*
+ * Walk through all of the profiled superblock's rules and copy
+ * the counters onto our rules.
+ */
+ TAILQ_FOREACH(por1, &block->sb_profiled_block->sb_rules, por_entry) {
+ comparable_rule(&a, &por1->por_rule, DC);
+ total_count += por1->por_rule.packets[0] +
+ por1->por_rule.packets[1];
+ TAILQ_FOREACH(por2, &block->sb_rules, por_entry) {
+ if (por2->por_profile_count)
+ continue;
+ comparable_rule(&b, &por2->por_rule, DC);
+ if (memcmp(&a, &b, sizeof(a)) == 0) {
+ por2->por_profile_count =
+ por1->por_rule.packets[0] +
+ por1->por_rule.packets[1];
+ break;
+ }
+ }
+ }
+ superblock_free(pf, block->sb_profiled_block);
+ block->sb_profiled_block = NULL;
+
+ /*
+ * Now we pull all of the rules off the superblock and re-insert them
+ * in sorted order.
+ */
+
+ TAILQ_INIT(&queue);
+ while ((por1 = TAILQ_FIRST(&block->sb_rules)) != NULL) {
+ TAILQ_REMOVE(&block->sb_rules, por1, por_entry);
+ TAILQ_INSERT_TAIL(&queue, por1, por_entry);
+ }
+
+ while ((por1 = TAILQ_FIRST(&queue)) != NULL) {
+ TAILQ_REMOVE(&queue, por1, por_entry);
+/* XXX I should sort all of the unused rules based on skip steps */
+ TAILQ_FOREACH(por2, &block->sb_rules, por_entry) {
+ if (por1->por_profile_count > por2->por_profile_count) {
+ TAILQ_INSERT_BEFORE(por2, por1, por_entry);
+ break;
+ }
+ }
+#ifdef __FreeBSD__
+ if (por2 == NULL)
+#else
+ if (por2 == TAILQ_END(&block->sb_rules))
+#endif
+ TAILQ_INSERT_TAIL(&block->sb_rules, por1, por_entry);
+ }
+
+ return (0);
+}
+
+
+/*
+ * Load the current ruleset from the kernel and try to associate them with
+ * the ruleset we're optimizing.
+ */
+int
+load_feedback_profile(struct pfctl *pf, struct superblocks *superblocks)
+{
+ struct superblock *block, *blockcur;
+ struct superblocks prof_superblocks;
+ struct pf_opt_rule *por;
+ struct pf_opt_queue queue;
+ struct pfioc_rule pr;
+ struct pf_rule a, b;
+ int nr, mnr;
+
+ TAILQ_INIT(&queue);
+ TAILQ_INIT(&prof_superblocks);
+
+ memset(&pr, 0, sizeof(pr));
+ pr.rule.action = PF_PASS;
+ if (ioctl(pf->dev, DIOCGETRULES, &pr)) {
+ warn("DIOCGETRULES");
+ return (1);
+ }
+ mnr = pr.nr;
+
+ DEBUG("Loading %d active rules for a feedback profile", mnr);
+ for (nr = 0; nr < mnr; ++nr) {
+ struct pf_ruleset *rs;
+ if ((por = calloc(1, sizeof(*por))) == NULL) {
+ warn("calloc");
+ return (1);
+ }
+ pr.nr = nr;
+ if (ioctl(pf->dev, DIOCGETRULE, &pr)) {
+ warn("DIOCGETRULES");
+ return (1);
+ }
+ memcpy(&por->por_rule, &pr.rule, sizeof(por->por_rule));
+ rs = pf_find_or_create_ruleset(pr.anchor_call);
+ por->por_rule.anchor = rs->anchor;
+ if (TAILQ_EMPTY(&por->por_rule.rpool.list))
+ memset(&por->por_rule.rpool, 0,
+ sizeof(por->por_rule.rpool));
+ TAILQ_INSERT_TAIL(&queue, por, por_entry);
+
+ /* XXX pfctl_get_pool(pf->dev, &pr.rule.rpool, nr, pr.ticket,
+ * PF_PASS, pf->anchor) ???
+ * ... pfctl_clear_pool(&pr.rule.rpool)
+ */
+ }
+
+ if (construct_superblocks(pf, &queue, &prof_superblocks))
+ return (1);
+
+
+ /*
+ * Now we try to associate the active ruleset's superblocks with
+ * the superblocks we're compiling.
+ */
+ block = TAILQ_FIRST(superblocks);
+ blockcur = TAILQ_FIRST(&prof_superblocks);
+ while (block && blockcur) {
+ comparable_rule(&a, &TAILQ_FIRST(&block->sb_rules)->por_rule,
+ BREAK);
+ comparable_rule(&b, &TAILQ_FIRST(&blockcur->sb_rules)->por_rule,
+ BREAK);
+ if (memcmp(&a, &b, sizeof(a)) == 0) {
+ /* The two superblocks lined up */
+ block->sb_profiled_block = blockcur;
+ } else {
+ DEBUG("superblocks don't line up between #%d and #%d",
+ TAILQ_FIRST(&block->sb_rules)->por_rule.nr,
+ TAILQ_FIRST(&blockcur->sb_rules)->por_rule.nr);
+ break;
+ }
+ block = TAILQ_NEXT(block, sb_entry);
+ blockcur = TAILQ_NEXT(blockcur, sb_entry);
+ }
+
+
+
+ /* Free any superblocks we couldn't link */
+ while (blockcur) {
+ block = TAILQ_NEXT(blockcur, sb_entry);
+ superblock_free(pf, blockcur);
+ blockcur = block;
+ }
+ return (0);
+}
+
+
+/*
+ * Compare a rule to a skiplist to see if the rule is a member
+ */
+int
+skip_compare(int skipnum, struct pf_skip_step *skiplist,
+ struct pf_opt_rule *por)
+{
+ struct pf_rule *a, *b;
+ if (skipnum >= PF_SKIP_COUNT || skipnum < 0)
+ errx(1, "skip_compare() out of bounds");
+ a = &por->por_rule;
+ b = &TAILQ_FIRST(&skiplist->ps_rules)->por_rule;
+
+ return ((skip_comparitors[skipnum])(a, b));
+}
+
+
+/*
+ * Add a rule to a skiplist
+ */
+void
+skip_append(struct superblock *superblock, int skipnum,
+ struct pf_skip_step *skiplist, struct pf_opt_rule *por)
+{
+ struct pf_skip_step *prev;
+
+ skiplist->ps_count++;
+ TAILQ_INSERT_TAIL(&skiplist->ps_rules, por, por_skip_entry[skipnum]);
+
+ /* Keep the list of skiplists sorted by whichever is larger */
+ while ((prev = TAILQ_PREV(skiplist, skiplist, ps_entry)) &&
+ prev->ps_count < skiplist->ps_count) {
+ TAILQ_REMOVE(&superblock->sb_skipsteps[skipnum],
+ skiplist, ps_entry);
+ TAILQ_INSERT_BEFORE(prev, skiplist, ps_entry);
+ }
+}
+
+
+/*
+ * Remove a rule from the other skiplist calculations.
+ */
+void
+remove_from_skipsteps(struct skiplist *head, struct superblock *block,
+ struct pf_opt_rule *por, struct pf_skip_step *active_list)
+{
+ struct pf_skip_step *sk, *next;
+ struct pf_opt_rule *p2;
+ int i, found;
+
+ for (i = 0; i < PF_SKIP_COUNT; i++) {
+ sk = TAILQ_FIRST(&block->sb_skipsteps[i]);
+ if (sk == NULL || sk == active_list || sk->ps_count <= 1)
+ continue;
+ found = 0;
+ do {
+ TAILQ_FOREACH(p2, &sk->ps_rules, por_skip_entry[i])
+ if (p2 == por) {
+ TAILQ_REMOVE(&sk->ps_rules, p2,
+ por_skip_entry[i]);
+ found = 1;
+ sk->ps_count--;
+ break;
+ }
+ } while (!found && (sk = TAILQ_NEXT(sk, ps_entry)));
+ if (found && sk) {
+ /* Does this change the sorting order? */
+ while ((next = TAILQ_NEXT(sk, ps_entry)) &&
+ next->ps_count > sk->ps_count) {
+ TAILQ_REMOVE(head, sk, ps_entry);
+ TAILQ_INSERT_AFTER(head, next, sk, ps_entry);
+ }
+#ifdef OPT_DEBUG
+ next = TAILQ_NEXT(sk, ps_entry);
+ assert(next == NULL || next->ps_count <= sk->ps_count);
+#endif /* OPT_DEBUG */
+ }
+ }
+}
+
+
+/* Compare two rules AF field for skiplist construction */
+int
+skip_cmp_af(struct pf_rule *a, struct pf_rule *b)
+{
+ if (a->af != b->af || a->af == 0)
+ return (1);
+ return (0);
+}
+
+/* Compare two rules DIRECTION field for skiplist construction */
+int
+skip_cmp_dir(struct pf_rule *a, struct pf_rule *b)
+{
+ if (a->direction == 0 || a->direction != b->direction)
+ return (1);
+ return (0);
+}
+
+/* Compare two rules DST Address field for skiplist construction */
+int
+skip_cmp_dst_addr(struct pf_rule *a, struct pf_rule *b)
+{
+ if (a->dst.neg != b->dst.neg ||
+ a->dst.addr.type != b->dst.addr.type)
+ return (1);
+ /* XXX if (a->proto != b->proto && a->proto != 0 && b->proto != 0
+ * && (a->proto == IPPROTO_TCP || a->proto == IPPROTO_UDP ||
+ * a->proto == IPPROTO_ICMP
+ * return (1);
+ */
+ switch (a->dst.addr.type) {
+ case PF_ADDR_ADDRMASK:
+ if (memcmp(&a->dst.addr.v.a.addr, &b->dst.addr.v.a.addr,
+ sizeof(a->dst.addr.v.a.addr)) ||
+ memcmp(&a->dst.addr.v.a.mask, &b->dst.addr.v.a.mask,
+ sizeof(a->dst.addr.v.a.mask)) ||
+ (a->dst.addr.v.a.addr.addr32[0] == 0 &&
+ a->dst.addr.v.a.addr.addr32[1] == 0 &&
+ a->dst.addr.v.a.addr.addr32[2] == 0 &&
+ a->dst.addr.v.a.addr.addr32[3] == 0))
+ return (1);
+ return (0);
+ case PF_ADDR_DYNIFTL:
+ if (strcmp(a->dst.addr.v.ifname, b->dst.addr.v.ifname) != 0 ||
+ a->dst.addr.iflags != a->dst.addr.iflags ||
+ memcmp(&a->dst.addr.v.a.mask, &b->dst.addr.v.a.mask,
+ sizeof(a->dst.addr.v.a.mask)))
+ return (1);
+ return (0);
+ case PF_ADDR_NOROUTE:
+ case PF_ADDR_URPFFAILED:
+ return (0);
+ case PF_ADDR_TABLE:
+ return (strcmp(a->dst.addr.v.tblname, b->dst.addr.v.tblname));
+ }
+ return (1);
+}
+
+/* Compare two rules DST port field for skiplist construction */
+int
+skip_cmp_dst_port(struct pf_rule *a, struct pf_rule *b)
+{
+ /* XXX if (a->proto != b->proto && a->proto != 0 && b->proto != 0
+ * && (a->proto == IPPROTO_TCP || a->proto == IPPROTO_UDP ||
+ * a->proto == IPPROTO_ICMP
+ * return (1);
+ */
+ if (a->dst.port_op == PF_OP_NONE || a->dst.port_op != b->dst.port_op ||
+ a->dst.port[0] != b->dst.port[0] ||
+ a->dst.port[1] != b->dst.port[1])
+ return (1);
+ return (0);
+}
+
+/* Compare two rules IFP field for skiplist construction */
+int
+skip_cmp_ifp(struct pf_rule *a, struct pf_rule *b)
+{
+ if (strcmp(a->ifname, b->ifname) || a->ifname[0] == '\0')
+ return (1);
+ return (a->ifnot != b->ifnot);
+}
+
+/* Compare two rules PROTO field for skiplist construction */
+int
+skip_cmp_proto(struct pf_rule *a, struct pf_rule *b)
+{
+ return (a->proto != b->proto || a->proto == 0);
+}
+
+/* Compare two rules SRC addr field for skiplist construction */
+int
+skip_cmp_src_addr(struct pf_rule *a, struct pf_rule *b)
+{
+ if (a->src.neg != b->src.neg ||
+ a->src.addr.type != b->src.addr.type)
+ return (1);
+ /* XXX if (a->proto != b->proto && a->proto != 0 && b->proto != 0
+ * && (a->proto == IPPROTO_TCP || a->proto == IPPROTO_UDP ||
+ * a->proto == IPPROTO_ICMP
+ * return (1);
+ */
+ switch (a->src.addr.type) {
+ case PF_ADDR_ADDRMASK:
+ if (memcmp(&a->src.addr.v.a.addr, &b->src.addr.v.a.addr,
+ sizeof(a->src.addr.v.a.addr)) ||
+ memcmp(&a->src.addr.v.a.mask, &b->src.addr.v.a.mask,
+ sizeof(a->src.addr.v.a.mask)) ||
+ (a->src.addr.v.a.addr.addr32[0] == 0 &&
+ a->src.addr.v.a.addr.addr32[1] == 0 &&
+ a->src.addr.v.a.addr.addr32[2] == 0 &&
+ a->src.addr.v.a.addr.addr32[3] == 0))
+ return (1);
+ return (0);
+ case PF_ADDR_DYNIFTL:
+ if (strcmp(a->src.addr.v.ifname, b->src.addr.v.ifname) != 0 ||
+ a->src.addr.iflags != a->src.addr.iflags ||
+ memcmp(&a->src.addr.v.a.mask, &b->src.addr.v.a.mask,
+ sizeof(a->src.addr.v.a.mask)))
+ return (1);
+ return (0);
+ case PF_ADDR_NOROUTE:
+ case PF_ADDR_URPFFAILED:
+ return (0);
+ case PF_ADDR_TABLE:
+ return (strcmp(a->src.addr.v.tblname, b->src.addr.v.tblname));
+ }
+ return (1);
+}
+
+/* Compare two rules SRC port field for skiplist construction */
+int
+skip_cmp_src_port(struct pf_rule *a, struct pf_rule *b)
+{
+ if (a->src.port_op == PF_OP_NONE || a->src.port_op != b->src.port_op ||
+ a->src.port[0] != b->src.port[0] ||
+ a->src.port[1] != b->src.port[1])
+ return (1);
+ /* XXX if (a->proto != b->proto && a->proto != 0 && b->proto != 0
+ * && (a->proto == IPPROTO_TCP || a->proto == IPPROTO_UDP ||
+ * a->proto == IPPROTO_ICMP
+ * return (1);
+ */
+ return (0);
+}
+
+
+void
+skip_init(void)
+{
+ struct {
+ char *name;
+ int skipnum;
+ int (*func)(struct pf_rule *, struct pf_rule *);
+ } comps[] = PF_SKIP_COMPARITORS;
+ int skipnum, i;
+
+ for (skipnum = 0; skipnum < PF_SKIP_COUNT; skipnum++) {
+ for (i = 0; i < sizeof(comps)/sizeof(*comps); i++)
+ if (comps[i].skipnum == skipnum) {
+ skip_comparitors[skipnum] = comps[i].func;
+ skip_comparitors_names[skipnum] = comps[i].name;
+ }
+ }
+ for (skipnum = 0; skipnum < PF_SKIP_COUNT; skipnum++)
+ if (skip_comparitors[skipnum] == NULL)
+ errx(1, "Need to add skip step comparitor to pfctl?!");
+}
+
+/*
+ * Add a host/netmask to a table
+ */
+int
+add_opt_table(struct pfctl *pf, struct pf_opt_tbl **tbl, sa_family_t af,
+ struct pf_rule_addr *addr)
+{
+#ifdef OPT_DEBUG
+ char buf[128];
+#endif /* OPT_DEBUG */
+ static int tablenum = 0;
+ struct node_host node_host;
+
+ if (*tbl == NULL) {
+ if ((*tbl = calloc(1, sizeof(**tbl))) == NULL ||
+ ((*tbl)->pt_buf = calloc(1, sizeof(*(*tbl)->pt_buf))) ==
+ NULL)
+ err(1, "calloc");
+ (*tbl)->pt_buf->pfrb_type = PFRB_ADDRS;
+ SIMPLEQ_INIT(&(*tbl)->pt_nodes);
+
+ /* This is just a temporary table name */
+ snprintf((*tbl)->pt_name, sizeof((*tbl)->pt_name), "%s%d",
+ PF_OPT_TABLE_PREFIX, tablenum++);
+ DEBUG("creating table <%s>", (*tbl)->pt_name);
+ }
+
+ memset(&node_host, 0, sizeof(node_host));
+ node_host.af = af;
+ node_host.addr = addr->addr;
+
+#ifdef OPT_DEBUG
+ DEBUG("<%s> adding %s/%d", (*tbl)->pt_name, inet_ntop(af,
+ &node_host.addr.v.a.addr, buf, sizeof(buf)),
+ unmask(&node_host.addr.v.a.mask, af));
+#endif /* OPT_DEBUG */
+
+ if (append_addr_host((*tbl)->pt_buf, &node_host, 0, 0)) {
+ warn("failed to add host");
+ return (1);
+ }
+ if (pf->opts & PF_OPT_VERBOSE) {
+ struct node_tinit *ti;
+
+ if ((ti = calloc(1, sizeof(*ti))) == NULL)
+ err(1, "malloc");
+ if ((ti->host = malloc(sizeof(*ti->host))) == NULL)
+ err(1, "malloc");
+ memcpy(ti->host, &node_host, sizeof(*ti->host));
+ SIMPLEQ_INSERT_TAIL(&(*tbl)->pt_nodes, ti, entries);
+ }
+
+ (*tbl)->pt_rulecount++;
+ if ((*tbl)->pt_rulecount == TABLE_THRESHOLD)
+ DEBUG("table <%s> now faster than skip steps", (*tbl)->pt_name);
+
+ return (0);
+}
+
+
+/*
+ * Do the dirty work of choosing an unused table name and creating it.
+ * (be careful with the table name, it might already be used in another anchor)
+ */
+int
+pf_opt_create_table(struct pfctl *pf, struct pf_opt_tbl *tbl)
+{
+ static int tablenum;
+ struct pfr_table *t;
+
+ if (table_buffer.pfrb_type == 0) {
+ /* Initialize the list of tables */
+ table_buffer.pfrb_type = PFRB_TABLES;
+ for (;;) {
+ pfr_buf_grow(&table_buffer, table_buffer.pfrb_size);
+ table_buffer.pfrb_size = table_buffer.pfrb_msize;
+ if (pfr_get_tables(NULL, table_buffer.pfrb_caddr,
+ &table_buffer.pfrb_size, PFR_FLAG_ALLRSETS))
+ err(1, "pfr_get_tables");
+ if (table_buffer.pfrb_size <= table_buffer.pfrb_msize)
+ break;
+ }
+ table_identifier = arc4random();
+ }
+
+ /* XXX would be *really* nice to avoid duplicating identical tables */
+
+ /* Now we have to pick a table name that isn't used */
+again:
+ DEBUG("translating temporary table <%s> to <%s%x_%d>", tbl->pt_name,
+ PF_OPT_TABLE_PREFIX, table_identifier, tablenum);
+ snprintf(tbl->pt_name, sizeof(tbl->pt_name), "%s%x_%d",
+ PF_OPT_TABLE_PREFIX, table_identifier, tablenum);
+ PFRB_FOREACH(t, &table_buffer) {
+ if (strcasecmp(t->pfrt_name, tbl->pt_name) == 0) {
+ /* Collision. Try again */
+ DEBUG("wow, table <%s> in use. trying again",
+ tbl->pt_name);
+ table_identifier = arc4random();
+ goto again;
+ }
+ }
+ tablenum++;
+
+
+ if (pfctl_define_table(tbl->pt_name, PFR_TFLAG_CONST, 1,
+ pf->astack[0]->name, tbl->pt_buf, pf->astack[0]->ruleset.tticket)) {
+ warn("failed to create table %s in %s",
+ tbl->pt_name, pf->astack[0]->name);
+ return (1);
+ }
+ return (0);
+}
+
+/*
+ * Partition the flat ruleset into a list of distinct superblocks
+ */
+int
+construct_superblocks(struct pfctl *pf, struct pf_opt_queue *opt_queue,
+ struct superblocks *superblocks)
+{
+ struct superblock *block = NULL;
+ struct pf_opt_rule *por;
+ int i;
+
+ while (!TAILQ_EMPTY(opt_queue)) {
+ por = TAILQ_FIRST(opt_queue);
+ TAILQ_REMOVE(opt_queue, por, por_entry);
+ if (block == NULL || !superblock_inclusive(block, por)) {
+ if ((block = calloc(1, sizeof(*block))) == NULL) {
+ warn("calloc");
+ return (1);
+ }
+ TAILQ_INIT(&block->sb_rules);
+ for (i = 0; i < PF_SKIP_COUNT; i++)
+ TAILQ_INIT(&block->sb_skipsteps[i]);
+ TAILQ_INSERT_TAIL(superblocks, block, sb_entry);
+ }
+ TAILQ_INSERT_TAIL(&block->sb_rules, por, por_entry);
+ }
+
+ return (0);
+}
+
+
+/*
+ * Compare two rule addresses
+ */
+int
+addrs_equal(struct pf_rule_addr *a, struct pf_rule_addr *b)
+{
+ if (a->neg != b->neg)
+ return (0);
+ return (memcmp(&a->addr, &b->addr, sizeof(a->addr)) == 0);
+}
+
+
+/*
+ * The addresses are not equal, but can we combine them into one table?
+ */
+int
+addrs_combineable(struct pf_rule_addr *a, struct pf_rule_addr *b)
+{
+ if (a->addr.type != PF_ADDR_ADDRMASK ||
+ b->addr.type != PF_ADDR_ADDRMASK)
+ return (0);
+ if (a->neg != b->neg || a->port_op != b->port_op ||
+ a->port[0] != b->port[0] || a->port[1] != b->port[1])
+ return (0);
+ return (1);
+}
+
+
+/*
+ * Are we allowed to combine these two rules
+ */
+int
+rules_combineable(struct pf_rule *p1, struct pf_rule *p2)
+{
+ struct pf_rule a, b;
+
+ comparable_rule(&a, p1, COMBINED);
+ comparable_rule(&b, p2, COMBINED);
+ return (memcmp(&a, &b, sizeof(a)) == 0);
+}
+
+
+/*
+ * Can a rule be included inside a superblock
+ */
+int
+superblock_inclusive(struct superblock *block, struct pf_opt_rule *por)
+{
+ struct pf_rule a, b;
+ int i, j;
+
+ /* First check for hard breaks */
+ for (i = 0; i < sizeof(pf_rule_desc)/sizeof(*pf_rule_desc); i++) {
+ if (pf_rule_desc[i].prf_type == BARRIER) {
+ for (j = 0; j < pf_rule_desc[i].prf_size; j++)
+ if (((char *)&por->por_rule)[j +
+ pf_rule_desc[i].prf_offset] != 0)
+ return (0);
+ }
+ }
+
+ /* per-rule src-track is also a hard break */
+ if (por->por_rule.rule_flag & PFRULE_RULESRCTRACK)
+ return (0);
+
+ /*
+ * Have to handle interface groups separately. Consider the following
+ * rules:
+ * block on EXTIFS to any port 22
+ * pass on em0 to any port 22
+ * (where EXTIFS is an arbitrary interface group)
+ * The optimizer may decide to re-order the pass rule in front of the
+ * block rule. But what if EXTIFS includes em0??? Such a reordering
+ * would change the meaning of the ruleset.
+ * We can't just lookup the EXTIFS group and check if em0 is a member
+ * because the user is allowed to add interfaces to a group during
+ * runtime.
+ * Ergo interface groups become a defacto superblock break :-(
+ */
+ if (interface_group(por->por_rule.ifname) ||
+ interface_group(TAILQ_FIRST(&block->sb_rules)->por_rule.ifname)) {
+ if (strcasecmp(por->por_rule.ifname,
+ TAILQ_FIRST(&block->sb_rules)->por_rule.ifname) != 0)
+ return (0);
+ }
+
+ comparable_rule(&a, &TAILQ_FIRST(&block->sb_rules)->por_rule, NOMERGE);
+ comparable_rule(&b, &por->por_rule, NOMERGE);
+ if (memcmp(&a, &b, sizeof(a)) == 0)
+ return (1);
+
+#ifdef OPT_DEBUG
+ for (i = 0; i < sizeof(por->por_rule); i++) {
+ int closest = -1;
+ if (((u_int8_t *)&a)[i] != ((u_int8_t *)&b)[i]) {
+ for (j = 0; j < sizeof(pf_rule_desc) /
+ sizeof(*pf_rule_desc); j++) {
+ if (i >= pf_rule_desc[j].prf_offset &&
+ i < pf_rule_desc[j].prf_offset +
+ pf_rule_desc[j].prf_size) {
+ DEBUG("superblock break @ %d due to %s",
+ por->por_rule.nr,
+ pf_rule_desc[j].prf_name);
+ return (0);
+ }
+ if (i > pf_rule_desc[j].prf_offset) {
+ if (closest == -1 ||
+ i-pf_rule_desc[j].prf_offset <
+ i-pf_rule_desc[closest].prf_offset)
+ closest = j;
+ }
+ }
+
+ if (closest >= 0)
+ DEBUG("superblock break @ %d on %s+%xh",
+ por->por_rule.nr,
+ pf_rule_desc[closest].prf_name,
+ i - pf_rule_desc[closest].prf_offset -
+ pf_rule_desc[closest].prf_size);
+ else
+ DEBUG("superblock break @ %d on field @ %d",
+ por->por_rule.nr, i);
+ return (0);
+ }
+ }
+#endif /* OPT_DEBUG */
+
+ return (0);
+}
+
+
+/*
+ * Figure out if an interface name is an actual interface or actually a
+ * group of interfaces.
+ */
+int
+interface_group(const char *ifname)
+{
+ if (ifname == NULL || !ifname[0])
+ return (0);
+
+ /* Real interfaces must end in a number, interface groups do not */
+ if (isdigit(ifname[strlen(ifname) - 1]))
+ return (0);
+ else
+ return (1);
+}
+
+
+/*
+ * Make a rule that can directly compared by memcmp()
+ */
+void
+comparable_rule(struct pf_rule *dst, const struct pf_rule *src, int type)
+{
+ int i;
+ /*
+ * To simplify the comparison, we just zero out the fields that are
+ * allowed to be different and then do a simple memcmp()
+ */
+ memcpy(dst, src, sizeof(*dst));
+ for (i = 0; i < sizeof(pf_rule_desc)/sizeof(*pf_rule_desc); i++)
+ if (pf_rule_desc[i].prf_type >= type) {
+#ifdef OPT_DEBUG
+ assert(pf_rule_desc[i].prf_type != NEVER ||
+ *(((char *)dst) + pf_rule_desc[i].prf_offset) == 0);
+#endif /* OPT_DEBUG */
+ memset(((char *)dst) + pf_rule_desc[i].prf_offset, 0,
+ pf_rule_desc[i].prf_size);
+ }
+}
+
+
+/*
+ * Remove superset information from two rules so we can directly compare them
+ * with memcmp()
+ */
+void
+exclude_supersets(struct pf_rule *super, struct pf_rule *sub)
+{
+ if (super->ifname[0] == '\0')
+ memset(sub->ifname, 0, sizeof(sub->ifname));
+ if (super->direction == PF_INOUT)
+ sub->direction = PF_INOUT;
+ if ((super->proto == 0 || super->proto == sub->proto) &&
+ super->flags == 0 && super->flagset == 0 && (sub->flags ||
+ sub->flagset)) {
+ sub->flags = super->flags;
+ sub->flagset = super->flagset;
+ }
+ if (super->proto == 0)
+ sub->proto = 0;
+
+ if (super->src.port_op == 0) {
+ sub->src.port_op = 0;
+ sub->src.port[0] = 0;
+ sub->src.port[1] = 0;
+ }
+ if (super->dst.port_op == 0) {
+ sub->dst.port_op = 0;
+ sub->dst.port[0] = 0;
+ sub->dst.port[1] = 0;
+ }
+
+ if (super->src.addr.type == PF_ADDR_ADDRMASK && !super->src.neg &&
+ !sub->src.neg && super->src.addr.v.a.mask.addr32[0] == 0 &&
+ super->src.addr.v.a.mask.addr32[1] == 0 &&
+ super->src.addr.v.a.mask.addr32[2] == 0 &&
+ super->src.addr.v.a.mask.addr32[3] == 0)
+ memset(&sub->src.addr, 0, sizeof(sub->src.addr));
+ else if (super->src.addr.type == PF_ADDR_ADDRMASK &&
+ sub->src.addr.type == PF_ADDR_ADDRMASK &&
+ super->src.neg == sub->src.neg &&
+ super->af == sub->af &&
+ unmask(&super->src.addr.v.a.mask, super->af) <
+ unmask(&sub->src.addr.v.a.mask, sub->af) &&
+ super->src.addr.v.a.addr.addr32[0] ==
+ (sub->src.addr.v.a.addr.addr32[0] &
+ super->src.addr.v.a.mask.addr32[0]) &&
+ super->src.addr.v.a.addr.addr32[1] ==
+ (sub->src.addr.v.a.addr.addr32[1] &
+ super->src.addr.v.a.mask.addr32[1]) &&
+ super->src.addr.v.a.addr.addr32[2] ==
+ (sub->src.addr.v.a.addr.addr32[2] &
+ super->src.addr.v.a.mask.addr32[2]) &&
+ super->src.addr.v.a.addr.addr32[3] ==
+ (sub->src.addr.v.a.addr.addr32[3] &
+ super->src.addr.v.a.mask.addr32[3])) {
+ /* sub->src.addr is a subset of super->src.addr/mask */
+ memcpy(&sub->src.addr, &super->src.addr, sizeof(sub->src.addr));
+ }
+
+ if (super->dst.addr.type == PF_ADDR_ADDRMASK && !super->dst.neg &&
+ !sub->dst.neg && super->dst.addr.v.a.mask.addr32[0] == 0 &&
+ super->dst.addr.v.a.mask.addr32[1] == 0 &&
+ super->dst.addr.v.a.mask.addr32[2] == 0 &&
+ super->dst.addr.v.a.mask.addr32[3] == 0)
+ memset(&sub->dst.addr, 0, sizeof(sub->dst.addr));
+ else if (super->dst.addr.type == PF_ADDR_ADDRMASK &&
+ sub->dst.addr.type == PF_ADDR_ADDRMASK &&
+ super->dst.neg == sub->dst.neg &&
+ super->af == sub->af &&
+ unmask(&super->dst.addr.v.a.mask, super->af) <
+ unmask(&sub->dst.addr.v.a.mask, sub->af) &&
+ super->dst.addr.v.a.addr.addr32[0] ==
+ (sub->dst.addr.v.a.addr.addr32[0] &
+ super->dst.addr.v.a.mask.addr32[0]) &&
+ super->dst.addr.v.a.addr.addr32[1] ==
+ (sub->dst.addr.v.a.addr.addr32[1] &
+ super->dst.addr.v.a.mask.addr32[1]) &&
+ super->dst.addr.v.a.addr.addr32[2] ==
+ (sub->dst.addr.v.a.addr.addr32[2] &
+ super->dst.addr.v.a.mask.addr32[2]) &&
+ super->dst.addr.v.a.addr.addr32[3] ==
+ (sub->dst.addr.v.a.addr.addr32[3] &
+ super->dst.addr.v.a.mask.addr32[3])) {
+ /* sub->dst.addr is a subset of super->dst.addr/mask */
+ memcpy(&sub->dst.addr, &super->dst.addr, sizeof(sub->dst.addr));
+ }
+
+ if (super->af == 0)
+ sub->af = 0;
+}
+
+
+void
+superblock_free(struct pfctl *pf, struct superblock *block)
+{
+ struct pf_opt_rule *por;
+ while ((por = TAILQ_FIRST(&block->sb_rules))) {
+ TAILQ_REMOVE(&block->sb_rules, por, por_entry);
+ if (por->por_src_tbl) {
+ if (por->por_src_tbl->pt_buf) {
+ pfr_buf_clear(por->por_src_tbl->pt_buf);
+ free(por->por_src_tbl->pt_buf);
+ }
+ free(por->por_src_tbl);
+ }
+ if (por->por_dst_tbl) {
+ if (por->por_dst_tbl->pt_buf) {
+ pfr_buf_clear(por->por_dst_tbl->pt_buf);
+ free(por->por_dst_tbl->pt_buf);
+ }
+ free(por->por_dst_tbl);
+ }
+ free(por);
+ }
+ if (block->sb_profiled_block)
+ superblock_free(pf, block->sb_profiled_block);
+ free(block);
+}
+
diff --git a/sbin/pfctl/pfctl_osfp.c b/sbin/pfctl/pfctl_osfp.c
new file mode 100644
index 0000000..df78981
--- /dev/null
+++ b/sbin/pfctl/pfctl_osfp.c
@@ -0,0 +1,1108 @@
+/* $OpenBSD: pfctl_osfp.c,v 1.14 2006/04/08 02:13:14 ray Exp $ */
+
+/*
+ * Copyright (c) 2003 Mike Frantzen <frantzen@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <net/if.h>
+#include <net/pfvar.h>
+
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "pfctl_parser.h"
+#include "pfctl.h"
+
+#ifndef MIN
+# define MIN(a,b) (((a) < (b)) ? (a) : (b))
+#endif /* MIN */
+#ifndef MAX
+# define MAX(a,b) (((a) > (b)) ? (a) : (b))
+#endif /* MAX */
+
+
+#if 0
+# define DEBUG(fp, str, v...) \
+ fprintf(stderr, "%s:%s:%s " str "\n", (fp)->fp_os.fp_class_nm, \
+ (fp)->fp_os.fp_version_nm, (fp)->fp_os.fp_subtype_nm , ## v);
+#else
+# define DEBUG(fp, str, v...) ((void)0)
+#endif
+
+
+struct name_entry;
+LIST_HEAD(name_list, name_entry);
+struct name_entry {
+ LIST_ENTRY(name_entry) nm_entry;
+ int nm_num;
+ char nm_name[PF_OSFP_LEN];
+
+ struct name_list nm_sublist;
+ int nm_sublist_num;
+};
+struct name_list classes = LIST_HEAD_INITIALIZER(&classes);
+int class_count;
+int fingerprint_count;
+
+void add_fingerprint(int, int, struct pf_osfp_ioctl *);
+struct name_entry *fingerprint_name_entry(struct name_list *, char *);
+void pfctl_flush_my_fingerprints(struct name_list *);
+char *get_field(char **, size_t *, int *);
+int get_int(char **, size_t *, int *, int *, const char *,
+ int, int, const char *, int);
+int get_str(char **, size_t *, char **, const char *, int,
+ const char *, int);
+int get_tcpopts(const char *, int, const char *,
+ pf_tcpopts_t *, int *, int *, int *, int *, int *,
+ int *);
+void import_fingerprint(struct pf_osfp_ioctl *);
+const char *print_ioctl(struct pf_osfp_ioctl *);
+void print_name_list(int, struct name_list *, const char *);
+void sort_name_list(int, struct name_list *);
+struct name_entry *lookup_name_list(struct name_list *, const char *);
+
+/* Load fingerprints from a file */
+int
+pfctl_file_fingerprints(int dev, int opts, const char *fp_filename)
+{
+ FILE *in;
+ char *line;
+ size_t len;
+ int i, lineno = 0;
+ int window, w_mod, ttl, df, psize, p_mod, mss, mss_mod, wscale,
+ wscale_mod, optcnt, ts0;
+ pf_tcpopts_t packed_tcpopts;
+ char *class, *version, *subtype, *desc, *tcpopts;
+ struct pf_osfp_ioctl fp;
+
+ pfctl_flush_my_fingerprints(&classes);
+
+ if ((in = pfctl_fopen(fp_filename, "r")) == NULL) {
+ warn("%s", fp_filename);
+ return (1);
+ }
+ class = version = subtype = desc = tcpopts = NULL;
+
+ if ((opts & PF_OPT_NOACTION) == 0)
+ pfctl_clear_fingerprints(dev, opts);
+
+ while ((line = fgetln(in, &len)) != NULL) {
+ lineno++;
+ if (class)
+ free(class);
+ if (version)
+ free(version);
+ if (subtype)
+ free(subtype);
+ if (desc)
+ free(desc);
+ if (tcpopts)
+ free(tcpopts);
+ class = version = subtype = desc = tcpopts = NULL;
+ memset(&fp, 0, sizeof(fp));
+
+ /* Chop off comment */
+ for (i = 0; i < len; i++)
+ if (line[i] == '#') {
+ len = i;
+ break;
+ }
+ /* Chop off whitespace */
+ while (len > 0 && isspace(line[len - 1]))
+ len--;
+ while (len > 0 && isspace(line[0])) {
+ len--;
+ line++;
+ }
+ if (len == 0)
+ continue;
+
+#define T_DC 0x01 /* Allow don't care */
+#define T_MSS 0x02 /* Allow MSS multiple */
+#define T_MTU 0x04 /* Allow MTU multiple */
+#define T_MOD 0x08 /* Allow modulus */
+
+#define GET_INT(v, mod, n, ty, mx) \
+ get_int(&line, &len, &v, mod, n, ty, mx, fp_filename, lineno)
+#define GET_STR(v, n, mn) \
+ get_str(&line, &len, &v, n, mn, fp_filename, lineno)
+
+ if (GET_INT(window, &w_mod, "window size", T_DC|T_MSS|T_MTU|
+ T_MOD, 0xffff) ||
+ GET_INT(ttl, NULL, "ttl", 0, 0xff) ||
+ GET_INT(df, NULL, "don't fragment frag", 0, 1) ||
+ GET_INT(psize, &p_mod, "overall packet size", T_MOD|T_DC,
+ 8192) ||
+ GET_STR(tcpopts, "TCP Options", 1) ||
+ GET_STR(class, "OS class", 1) ||
+ GET_STR(version, "OS version", 0) ||
+ GET_STR(subtype, "OS subtype", 0) ||
+ GET_STR(desc, "OS description", 2))
+ continue;
+ if (get_tcpopts(fp_filename, lineno, tcpopts, &packed_tcpopts,
+ &optcnt, &mss, &mss_mod, &wscale, &wscale_mod, &ts0))
+ continue;
+ if (len != 0) {
+ fprintf(stderr, "%s:%d excess field\n", fp_filename,
+ lineno);
+ continue;
+ }
+
+ fp.fp_ttl = ttl;
+ if (df)
+ fp.fp_flags |= PF_OSFP_DF;
+ switch (w_mod) {
+ case 0:
+ break;
+ case T_DC:
+ fp.fp_flags |= PF_OSFP_WSIZE_DC;
+ break;
+ case T_MSS:
+ fp.fp_flags |= PF_OSFP_WSIZE_MSS;
+ break;
+ case T_MTU:
+ fp.fp_flags |= PF_OSFP_WSIZE_MTU;
+ break;
+ case T_MOD:
+ fp.fp_flags |= PF_OSFP_WSIZE_MOD;
+ break;
+ }
+ fp.fp_wsize = window;
+
+ switch (p_mod) {
+ case T_DC:
+ fp.fp_flags |= PF_OSFP_PSIZE_DC;
+ break;
+ case T_MOD:
+ fp.fp_flags |= PF_OSFP_PSIZE_MOD;
+ }
+ fp.fp_psize = psize;
+
+
+ switch (wscale_mod) {
+ case T_DC:
+ fp.fp_flags |= PF_OSFP_WSCALE_DC;
+ break;
+ case T_MOD:
+ fp.fp_flags |= PF_OSFP_WSCALE_MOD;
+ }
+ fp.fp_wscale = wscale;
+
+ switch (mss_mod) {
+ case T_DC:
+ fp.fp_flags |= PF_OSFP_MSS_DC;
+ break;
+ case T_MOD:
+ fp.fp_flags |= PF_OSFP_MSS_MOD;
+ break;
+ }
+ fp.fp_mss = mss;
+
+ fp.fp_tcpopts = packed_tcpopts;
+ fp.fp_optcnt = optcnt;
+ if (ts0)
+ fp.fp_flags |= PF_OSFP_TS0;
+
+ if (class[0] == '@')
+ fp.fp_os.fp_enflags |= PF_OSFP_GENERIC;
+ if (class[0] == '*')
+ fp.fp_os.fp_enflags |= PF_OSFP_NODETAIL;
+
+ if (class[0] == '@' || class[0] == '*')
+ strlcpy(fp.fp_os.fp_class_nm, class + 1,
+ sizeof(fp.fp_os.fp_class_nm));
+ else
+ strlcpy(fp.fp_os.fp_class_nm, class,
+ sizeof(fp.fp_os.fp_class_nm));
+ strlcpy(fp.fp_os.fp_version_nm, version,
+ sizeof(fp.fp_os.fp_version_nm));
+ strlcpy(fp.fp_os.fp_subtype_nm, subtype,
+ sizeof(fp.fp_os.fp_subtype_nm));
+
+ add_fingerprint(dev, opts, &fp);
+
+ fp.fp_flags |= (PF_OSFP_DF | PF_OSFP_INET6);
+ fp.fp_psize += sizeof(struct ip6_hdr) - sizeof(struct ip);
+ add_fingerprint(dev, opts, &fp);
+ }
+
+ if (class)
+ free(class);
+ if (version)
+ free(version);
+ if (subtype)
+ free(subtype);
+ if (desc)
+ free(desc);
+ if (tcpopts)
+ free(tcpopts);
+
+ fclose(in);
+
+ if (opts & PF_OPT_VERBOSE2)
+ printf("Loaded %d passive OS fingerprints\n",
+ fingerprint_count);
+ return (0);
+}
+
+/* flush the kernel's fingerprints */
+void
+pfctl_clear_fingerprints(int dev, int opts)
+{
+ if (ioctl(dev, DIOCOSFPFLUSH))
+ err(1, "DIOCOSFPFLUSH");
+}
+
+/* flush pfctl's view of the fingerprints */
+void
+pfctl_flush_my_fingerprints(struct name_list *list)
+{
+ struct name_entry *nm;
+
+ while ((nm = LIST_FIRST(list)) != NULL) {
+ LIST_REMOVE(nm, nm_entry);
+ pfctl_flush_my_fingerprints(&nm->nm_sublist);
+ free(nm);
+ }
+ fingerprint_count = 0;
+ class_count = 0;
+}
+
+/* Fetch the active fingerprints from the kernel */
+int
+pfctl_load_fingerprints(int dev, int opts)
+{
+ struct pf_osfp_ioctl io;
+ int i;
+
+ pfctl_flush_my_fingerprints(&classes);
+
+ for (i = 0; i >= 0; i++) {
+ memset(&io, 0, sizeof(io));
+ io.fp_getnum = i;
+ if (ioctl(dev, DIOCOSFPGET, &io)) {
+ if (errno == EBUSY)
+ break;
+ warn("DIOCOSFPGET");
+ return (1);
+ }
+ import_fingerprint(&io);
+ }
+ return (0);
+}
+
+/* List the fingerprints */
+void
+pfctl_show_fingerprints(int opts)
+{
+ if (LIST_FIRST(&classes) != NULL) {
+ if (opts & PF_OPT_SHOWALL) {
+ pfctl_print_title("OS FINGERPRINTS:");
+ printf("%u fingerprints loaded\n", fingerprint_count);
+ } else {
+ printf("Class\tVersion\tSubtype(subversion)\n");
+ printf("-----\t-------\t-------------------\n");
+ sort_name_list(opts, &classes);
+ print_name_list(opts, &classes, "");
+ }
+ }
+}
+
+/* Lookup a fingerprint */
+pf_osfp_t
+pfctl_get_fingerprint(const char *name)
+{
+ struct name_entry *nm, *class_nm, *version_nm, *subtype_nm;
+ pf_osfp_t ret = PF_OSFP_NOMATCH;
+ int class, version, subtype;
+ int unp_class, unp_version, unp_subtype;
+ int wr_len, version_len, subtype_len;
+ char *ptr, *wr_name;
+
+ if (strcasecmp(name, "unknown") == 0)
+ return (PF_OSFP_UNKNOWN);
+
+ /* Try most likely no version and no subtype */
+ if ((nm = lookup_name_list(&classes, name))) {
+ class = nm->nm_num;
+ version = PF_OSFP_ANY;
+ subtype = PF_OSFP_ANY;
+ goto found;
+ } else {
+
+ /* Chop it up into class/version/subtype */
+
+ if ((wr_name = strdup(name)) == NULL)
+ err(1, "malloc");
+ if ((ptr = strchr(wr_name, ' ')) == NULL) {
+ free(wr_name);
+ return (PF_OSFP_NOMATCH);
+ }
+ *ptr++ = '\0';
+
+ /* The class is easy to find since it is delimited by a space */
+ if ((class_nm = lookup_name_list(&classes, wr_name)) == NULL) {
+ free(wr_name);
+ return (PF_OSFP_NOMATCH);
+ }
+ class = class_nm->nm_num;
+
+ /* Try no subtype */
+ if ((version_nm = lookup_name_list(&class_nm->nm_sublist, ptr)))
+ {
+ version = version_nm->nm_num;
+ subtype = PF_OSFP_ANY;
+ free(wr_name);
+ goto found;
+ }
+
+
+ /*
+ * There must be a version and a subtype.
+ * We'll do some fuzzy matching to pick up things like:
+ * Linux 2.2.14 (version=2.2 subtype=14)
+ * FreeBSD 4.0-STABLE (version=4.0 subtype=STABLE)
+ * Windows 2000 SP2 (version=2000 subtype=SP2)
+ */
+#define CONNECTOR(x) ((x) == '.' || (x) == ' ' || (x) == '\t' || (x) == '-')
+ wr_len = strlen(ptr);
+ LIST_FOREACH(version_nm, &class_nm->nm_sublist, nm_entry) {
+ version_len = strlen(version_nm->nm_name);
+ if (wr_len < version_len + 2 ||
+ !CONNECTOR(ptr[version_len]))
+ continue;
+ /* first part of the string must be version */
+ if (strncasecmp(ptr, version_nm->nm_name,
+ version_len))
+ continue;
+
+ LIST_FOREACH(subtype_nm, &version_nm->nm_sublist,
+ nm_entry) {
+ subtype_len = strlen(subtype_nm->nm_name);
+ if (wr_len != version_len + subtype_len + 1)
+ continue;
+
+ /* last part of the string must be subtype */
+ if (strcasecmp(&ptr[version_len+1],
+ subtype_nm->nm_name) != 0)
+ continue;
+
+ /* Found it!! */
+ version = version_nm->nm_num;
+ subtype = subtype_nm->nm_num;
+ free(wr_name);
+ goto found;
+ }
+ }
+
+ free(wr_name);
+ return (PF_OSFP_NOMATCH);
+ }
+
+found:
+ PF_OSFP_PACK(ret, class, version, subtype);
+ if (ret != PF_OSFP_NOMATCH) {
+ PF_OSFP_UNPACK(ret, unp_class, unp_version, unp_subtype);
+ if (class != unp_class) {
+ fprintf(stderr, "warning: fingerprint table overflowed "
+ "classes\n");
+ return (PF_OSFP_NOMATCH);
+ }
+ if (version != unp_version) {
+ fprintf(stderr, "warning: fingerprint table overflowed "
+ "versions\n");
+ return (PF_OSFP_NOMATCH);
+ }
+ if (subtype != unp_subtype) {
+ fprintf(stderr, "warning: fingerprint table overflowed "
+ "subtypes\n");
+ return (PF_OSFP_NOMATCH);
+ }
+ }
+ if (ret == PF_OSFP_ANY) {
+ /* should never happen */
+ fprintf(stderr, "warning: fingerprint packed to 'any'\n");
+ return (PF_OSFP_NOMATCH);
+ }
+
+ return (ret);
+}
+
+/* Lookup a fingerprint name by ID */
+char *
+pfctl_lookup_fingerprint(pf_osfp_t fp, char *buf, size_t len)
+{
+ int class, version, subtype;
+ struct name_list *list;
+ struct name_entry *nm;
+
+ char *class_name, *version_name, *subtype_name;
+ class_name = version_name = subtype_name = NULL;
+
+ if (fp == PF_OSFP_UNKNOWN) {
+ strlcpy(buf, "unknown", len);
+ return (buf);
+ }
+ if (fp == PF_OSFP_ANY) {
+ strlcpy(buf, "any", len);
+ return (buf);
+ }
+
+ PF_OSFP_UNPACK(fp, class, version, subtype);
+ if (class >= (1 << _FP_CLASS_BITS) ||
+ version >= (1 << _FP_VERSION_BITS) ||
+ subtype >= (1 << _FP_SUBTYPE_BITS)) {
+ warnx("PF_OSFP_UNPACK(0x%x) failed!!", fp);
+ strlcpy(buf, "nomatch", len);
+ return (buf);
+ }
+
+ LIST_FOREACH(nm, &classes, nm_entry) {
+ if (nm->nm_num == class) {
+ class_name = nm->nm_name;
+ if (version == PF_OSFP_ANY)
+ goto found;
+ list = &nm->nm_sublist;
+ LIST_FOREACH(nm, list, nm_entry) {
+ if (nm->nm_num == version) {
+ version_name = nm->nm_name;
+ if (subtype == PF_OSFP_ANY)
+ goto found;
+ list = &nm->nm_sublist;
+ LIST_FOREACH(nm, list, nm_entry) {
+ if (nm->nm_num == subtype) {
+ subtype_name =
+ nm->nm_name;
+ goto found;
+ }
+ } /* foreach subtype */
+ strlcpy(buf, "nomatch", len);
+ return (buf);
+ }
+ } /* foreach version */
+ strlcpy(buf, "nomatch", len);
+ return (buf);
+ }
+ } /* foreach class */
+
+ strlcpy(buf, "nomatch", len);
+ return (buf);
+
+found:
+ snprintf(buf, len, "%s", class_name);
+ if (version_name) {
+ strlcat(buf, " ", len);
+ strlcat(buf, version_name, len);
+ if (subtype_name) {
+ if (strchr(version_name, ' '))
+ strlcat(buf, " ", len);
+ else if (strchr(version_name, '.') &&
+ isdigit(*subtype_name))
+ strlcat(buf, ".", len);
+ else
+ strlcat(buf, " ", len);
+ strlcat(buf, subtype_name, len);
+ }
+ }
+ return (buf);
+}
+
+/* lookup a name in a list */
+struct name_entry *
+lookup_name_list(struct name_list *list, const char *name)
+{
+ struct name_entry *nm;
+ LIST_FOREACH(nm, list, nm_entry)
+ if (strcasecmp(name, nm->nm_name) == 0)
+ return (nm);
+
+ return (NULL);
+}
+
+
+void
+add_fingerprint(int dev, int opts, struct pf_osfp_ioctl *fp)
+{
+ struct pf_osfp_ioctl fptmp;
+ struct name_entry *nm_class, *nm_version, *nm_subtype;
+ int class, version, subtype;
+
+/* We expand #-# or #.#-#.# version/subtypes into multiple fingerprints */
+#define EXPAND(field) do { \
+ int _dot = -1, _start = -1, _end = -1, _i = 0; \
+ /* pick major version out of #.# */ \
+ if (isdigit(fp->field[_i]) && fp->field[_i+1] == '.') { \
+ _dot = fp->field[_i] - '0'; \
+ _i += 2; \
+ } \
+ if (isdigit(fp->field[_i])) \
+ _start = fp->field[_i++] - '0'; \
+ else \
+ break; \
+ if (isdigit(fp->field[_i])) \
+ _start = (_start * 10) + fp->field[_i++] - '0'; \
+ if (fp->field[_i++] != '-') \
+ break; \
+ if (isdigit(fp->field[_i]) && fp->field[_i+1] == '.' && \
+ fp->field[_i] - '0' == _dot) \
+ _i += 2; \
+ else if (_dot != -1) \
+ break; \
+ if (isdigit(fp->field[_i])) \
+ _end = fp->field[_i++] - '0'; \
+ else \
+ break; \
+ if (isdigit(fp->field[_i])) \
+ _end = (_end * 10) + fp->field[_i++] - '0'; \
+ if (isdigit(fp->field[_i])) \
+ _end = (_end * 10) + fp->field[_i++] - '0'; \
+ if (fp->field[_i] != '\0') \
+ break; \
+ memcpy(&fptmp, fp, sizeof(fptmp)); \
+ for (;_start <= _end; _start++) { \
+ memset(fptmp.field, 0, sizeof(fptmp.field)); \
+ fptmp.fp_os.fp_enflags |= PF_OSFP_EXPANDED; \
+ if (_dot == -1) \
+ snprintf(fptmp.field, sizeof(fptmp.field), \
+ "%d", _start); \
+ else \
+ snprintf(fptmp.field, sizeof(fptmp.field), \
+ "%d.%d", _dot, _start); \
+ add_fingerprint(dev, opts, &fptmp); \
+ } \
+} while(0)
+
+ /* We allow "#-#" as a version or subtype and we'll expand it */
+ EXPAND(fp_os.fp_version_nm);
+ EXPAND(fp_os.fp_subtype_nm);
+
+ if (strcasecmp(fp->fp_os.fp_class_nm, "nomatch") == 0)
+ errx(1, "fingerprint class \"nomatch\" is reserved");
+
+ version = PF_OSFP_ANY;
+ subtype = PF_OSFP_ANY;
+
+ nm_class = fingerprint_name_entry(&classes, fp->fp_os.fp_class_nm);
+ if (nm_class->nm_num == 0)
+ nm_class->nm_num = ++class_count;
+ class = nm_class->nm_num;
+
+ nm_version = fingerprint_name_entry(&nm_class->nm_sublist,
+ fp->fp_os.fp_version_nm);
+ if (nm_version) {
+ if (nm_version->nm_num == 0)
+ nm_version->nm_num = ++nm_class->nm_sublist_num;
+ version = nm_version->nm_num;
+ nm_subtype = fingerprint_name_entry(&nm_version->nm_sublist,
+ fp->fp_os.fp_subtype_nm);
+ if (nm_subtype) {
+ if (nm_subtype->nm_num == 0)
+ nm_subtype->nm_num =
+ ++nm_version->nm_sublist_num;
+ subtype = nm_subtype->nm_num;
+ }
+ }
+
+
+ DEBUG(fp, "\tsignature %d:%d:%d %s", class, version, subtype,
+ print_ioctl(fp));
+
+ PF_OSFP_PACK(fp->fp_os.fp_os, class, version, subtype);
+ fingerprint_count++;
+
+#ifdef FAKE_PF_KERNEL
+ /* Linked to the sys/net/pf_osfp.c. Call pf_osfp_add() */
+ if ((errno = pf_osfp_add(fp)))
+#else
+ if ((opts & PF_OPT_NOACTION) == 0 && ioctl(dev, DIOCOSFPADD, fp))
+#endif /* FAKE_PF_KERNEL */
+ {
+ if (errno == EEXIST) {
+ warn("Duplicate signature for %s %s %s",
+ fp->fp_os.fp_class_nm,
+ fp->fp_os.fp_version_nm,
+ fp->fp_os.fp_subtype_nm);
+
+ } else {
+ err(1, "DIOCOSFPADD");
+ }
+ }
+}
+
+/* import a fingerprint from the kernel */
+void
+import_fingerprint(struct pf_osfp_ioctl *fp)
+{
+ struct name_entry *nm_class, *nm_version, *nm_subtype;
+ int class, version, subtype;
+
+ PF_OSFP_UNPACK(fp->fp_os.fp_os, class, version, subtype);
+
+ nm_class = fingerprint_name_entry(&classes, fp->fp_os.fp_class_nm);
+ if (nm_class->nm_num == 0) {
+ nm_class->nm_num = class;
+ class_count = MAX(class_count, class);
+ }
+
+ nm_version = fingerprint_name_entry(&nm_class->nm_sublist,
+ fp->fp_os.fp_version_nm);
+ if (nm_version) {
+ if (nm_version->nm_num == 0) {
+ nm_version->nm_num = version;
+ nm_class->nm_sublist_num = MAX(nm_class->nm_sublist_num,
+ version);
+ }
+ nm_subtype = fingerprint_name_entry(&nm_version->nm_sublist,
+ fp->fp_os.fp_subtype_nm);
+ if (nm_subtype) {
+ if (nm_subtype->nm_num == 0) {
+ nm_subtype->nm_num = subtype;
+ nm_version->nm_sublist_num =
+ MAX(nm_version->nm_sublist_num, subtype);
+ }
+ }
+ }
+
+
+ fingerprint_count++;
+ DEBUG(fp, "import signature %d:%d:%d", class, version, subtype);
+}
+
+/* Find an entry for a fingerprints class/version/subtype */
+struct name_entry *
+fingerprint_name_entry(struct name_list *list, char *name)
+{
+ struct name_entry *nm_entry;
+
+ if (name == NULL || strlen(name) == 0)
+ return (NULL);
+
+ LIST_FOREACH(nm_entry, list, nm_entry) {
+ if (strcasecmp(nm_entry->nm_name, name) == 0) {
+ /* We'll move this to the front of the list later */
+ LIST_REMOVE(nm_entry, nm_entry);
+ break;
+ }
+ }
+ if (nm_entry == NULL) {
+ nm_entry = calloc(1, sizeof(*nm_entry));
+ if (nm_entry == NULL)
+ err(1, "calloc");
+ LIST_INIT(&nm_entry->nm_sublist);
+ strlcpy(nm_entry->nm_name, name, sizeof(nm_entry->nm_name));
+ }
+ LIST_INSERT_HEAD(list, nm_entry, nm_entry);
+ return (nm_entry);
+}
+
+
+void
+print_name_list(int opts, struct name_list *nml, const char *prefix)
+{
+ char newprefix[32];
+ struct name_entry *nm;
+
+ LIST_FOREACH(nm, nml, nm_entry) {
+ snprintf(newprefix, sizeof(newprefix), "%s%s\t", prefix,
+ nm->nm_name);
+ printf("%s\n", newprefix);
+ print_name_list(opts, &nm->nm_sublist, newprefix);
+ }
+}
+
+void
+sort_name_list(int opts, struct name_list *nml)
+{
+ struct name_list new;
+ struct name_entry *nm, *nmsearch, *nmlast;
+
+ /* yes yes, it's a very slow sort. so sue me */
+
+ LIST_INIT(&new);
+
+ while ((nm = LIST_FIRST(nml)) != NULL) {
+ LIST_REMOVE(nm, nm_entry);
+ nmlast = NULL;
+ LIST_FOREACH(nmsearch, &new, nm_entry) {
+ if (strcasecmp(nmsearch->nm_name, nm->nm_name) > 0) {
+ LIST_INSERT_BEFORE(nmsearch, nm, nm_entry);
+ break;
+ }
+ nmlast = nmsearch;
+ }
+ if (nmsearch == NULL) {
+ if (nmlast)
+ LIST_INSERT_AFTER(nmlast, nm, nm_entry);
+ else
+ LIST_INSERT_HEAD(&new, nm, nm_entry);
+ }
+
+ sort_name_list(opts, &nm->nm_sublist);
+ }
+ nmlast = NULL;
+ while ((nm = LIST_FIRST(&new)) != NULL) {
+ LIST_REMOVE(nm, nm_entry);
+ if (nmlast == NULL)
+ LIST_INSERT_HEAD(nml, nm, nm_entry);
+ else
+ LIST_INSERT_AFTER(nmlast, nm, nm_entry);
+ nmlast = nm;
+ }
+}
+
+/* parse the next integer in a formatted config file line */
+int
+get_int(char **line, size_t *len, int *var, int *mod,
+ const char *name, int flags, int max, const char *filename, int lineno)
+{
+ int fieldlen, i;
+ char *field;
+ long val = 0;
+
+ if (mod)
+ *mod = 0;
+ *var = 0;
+
+ field = get_field(line, len, &fieldlen);
+ if (field == NULL)
+ return (1);
+ if (fieldlen == 0) {
+ fprintf(stderr, "%s:%d empty %s\n", filename, lineno, name);
+ return (1);
+ }
+
+ i = 0;
+ if ((*field == '%' || *field == 'S' || *field == 'T' || *field == '*')
+ && fieldlen >= 1) {
+ switch (*field) {
+ case 'S':
+ if (mod && (flags & T_MSS))
+ *mod = T_MSS;
+ if (fieldlen == 1)
+ return (0);
+ break;
+ case 'T':
+ if (mod && (flags & T_MTU))
+ *mod = T_MTU;
+ if (fieldlen == 1)
+ return (0);
+ break;
+ case '*':
+ if (fieldlen != 1) {
+ fprintf(stderr, "%s:%d long '%c' %s\n",
+ filename, lineno, *field, name);
+ return (1);
+ }
+ if (mod && (flags & T_DC)) {
+ *mod = T_DC;
+ return (0);
+ }
+ case '%':
+ if (mod && (flags & T_MOD))
+ *mod = T_MOD;
+ if (fieldlen == 1) {
+ fprintf(stderr, "%s:%d modulus %s must have a "
+ "value\n", filename, lineno, name);
+ return (1);
+ }
+ break;
+ }
+ if (mod == NULL || *mod == 0) {
+ fprintf(stderr, "%s:%d does not allow %c' %s\n",
+ filename, lineno, *field, name);
+ return (1);
+ }
+ i++;
+ }
+
+ for (; i < fieldlen; i++) {
+ if (field[i] < '0' || field[i] > '9') {
+ fprintf(stderr, "%s:%d non-digit character in %s\n",
+ filename, lineno, name);
+ return (1);
+ }
+ val = val * 10 + field[i] - '0';
+ if (val < 0) {
+ fprintf(stderr, "%s:%d %s overflowed\n", filename,
+ lineno, name);
+ return (1);
+ }
+ }
+
+ if (val > max) {
+ fprintf(stderr, "%s:%d %s value %ld > %d\n", filename, lineno,
+ name, val, max);
+ return (1);
+ }
+ *var = (int)val;
+
+ return (0);
+}
+
+/* parse the next string in a formatted config file line */
+int
+get_str(char **line, size_t *len, char **v, const char *name, int minlen,
+ const char *filename, int lineno)
+{
+ int fieldlen;
+ char *ptr;
+
+ ptr = get_field(line, len, &fieldlen);
+ if (ptr == NULL)
+ return (1);
+ if (fieldlen < minlen) {
+ fprintf(stderr, "%s:%d too short %s\n", filename, lineno, name);
+ return (1);
+ }
+ if ((*v = malloc(fieldlen + 1)) == NULL) {
+ perror("malloc()");
+ return (1);
+ }
+ memcpy(*v, ptr, fieldlen);
+ (*v)[fieldlen] = '\0';
+
+ return (0);
+}
+
+/* Parse out the TCP opts */
+int
+get_tcpopts(const char *filename, int lineno, const char *tcpopts,
+ pf_tcpopts_t *packed, int *optcnt, int *mss, int *mss_mod, int *wscale,
+ int *wscale_mod, int *ts0)
+{
+ int i, opt;
+
+ *packed = 0;
+ *optcnt = 0;
+ *wscale = 0;
+ *wscale_mod = T_DC;
+ *mss = 0;
+ *mss_mod = T_DC;
+ *ts0 = 0;
+ if (strcmp(tcpopts, ".") == 0)
+ return (0);
+
+ for (i = 0; tcpopts[i] && *optcnt < PF_OSFP_MAX_OPTS;) {
+ switch ((opt = toupper(tcpopts[i++]))) {
+ case 'N': /* FALLTHROUGH */
+ case 'S':
+ *packed = (*packed << PF_OSFP_TCPOPT_BITS) |
+ (opt == 'N' ? PF_OSFP_TCPOPT_NOP :
+ PF_OSFP_TCPOPT_SACK);
+ break;
+ case 'W': /* FALLTHROUGH */
+ case 'M': {
+ int *this_mod, *this;
+
+ if (opt == 'W') {
+ this = wscale;
+ this_mod = wscale_mod;
+ } else {
+ this = mss;
+ this_mod = mss_mod;
+ }
+ *this = 0;
+ *this_mod = 0;
+
+ *packed = (*packed << PF_OSFP_TCPOPT_BITS) |
+ (opt == 'W' ? PF_OSFP_TCPOPT_WSCALE :
+ PF_OSFP_TCPOPT_MSS);
+ if (tcpopts[i] == '*' && (tcpopts[i + 1] == '\0' ||
+ tcpopts[i + 1] == ',')) {
+ *this_mod = T_DC;
+ i++;
+ break;
+ }
+
+ if (tcpopts[i] == '%') {
+ *this_mod = T_MOD;
+ i++;
+ }
+ do {
+ if (!isdigit(tcpopts[i])) {
+ fprintf(stderr, "%s:%d unknown "
+ "character '%c' in %c TCP opt\n",
+ filename, lineno, tcpopts[i], opt);
+ return (1);
+ }
+ *this = (*this * 10) + tcpopts[i++] - '0';
+ } while(tcpopts[i] != ',' && tcpopts[i] != '\0');
+ break;
+ }
+ case 'T':
+ if (tcpopts[i] == '0') {
+ *ts0 = 1;
+ i++;
+ }
+ *packed = (*packed << PF_OSFP_TCPOPT_BITS) |
+ PF_OSFP_TCPOPT_TS;
+ break;
+ }
+ (*optcnt) ++;
+ if (tcpopts[i] == '\0')
+ break;
+ if (tcpopts[i] != ',') {
+ fprintf(stderr, "%s:%d unknown option to %c TCP opt\n",
+ filename, lineno, opt);
+ return (1);
+ }
+ i++;
+ }
+
+ return (0);
+}
+
+/* rip the next field ouf of a formatted config file line */
+char *
+get_field(char **line, size_t *len, int *fieldlen)
+{
+ char *ret, *ptr = *line;
+ size_t plen = *len;
+
+
+ while (plen && isspace(*ptr)) {
+ plen--;
+ ptr++;
+ }
+ ret = ptr;
+ *fieldlen = 0;
+
+ for (; plen > 0 && *ptr != ':'; plen--, ptr++)
+ (*fieldlen)++;
+ if (plen) {
+ *line = ptr + 1;
+ *len = plen - 1;
+ } else {
+ *len = 0;
+ }
+ while (*fieldlen && isspace(ret[*fieldlen - 1]))
+ (*fieldlen)--;
+ return (ret);
+}
+
+
+const char *
+print_ioctl(struct pf_osfp_ioctl *fp)
+{
+ static char buf[1024];
+ char tmp[32];
+ int i, opt;
+
+ *buf = '\0';
+ if (fp->fp_flags & PF_OSFP_WSIZE_DC)
+ strlcat(buf, "*", sizeof(buf));
+ else if (fp->fp_flags & PF_OSFP_WSIZE_MSS)
+ strlcat(buf, "S", sizeof(buf));
+ else if (fp->fp_flags & PF_OSFP_WSIZE_MTU)
+ strlcat(buf, "T", sizeof(buf));
+ else {
+ if (fp->fp_flags & PF_OSFP_WSIZE_MOD)
+ strlcat(buf, "%", sizeof(buf));
+ snprintf(tmp, sizeof(tmp), "%d", fp->fp_wsize);
+ strlcat(buf, tmp, sizeof(buf));
+ }
+ strlcat(buf, ":", sizeof(buf));
+
+ snprintf(tmp, sizeof(tmp), "%d", fp->fp_ttl);
+ strlcat(buf, tmp, sizeof(buf));
+ strlcat(buf, ":", sizeof(buf));
+
+ if (fp->fp_flags & PF_OSFP_DF)
+ strlcat(buf, "1", sizeof(buf));
+ else
+ strlcat(buf, "0", sizeof(buf));
+ strlcat(buf, ":", sizeof(buf));
+
+ if (fp->fp_flags & PF_OSFP_PSIZE_DC)
+ strlcat(buf, "*", sizeof(buf));
+ else {
+ if (fp->fp_flags & PF_OSFP_PSIZE_MOD)
+ strlcat(buf, "%", sizeof(buf));
+ snprintf(tmp, sizeof(tmp), "%d", fp->fp_psize);
+ strlcat(buf, tmp, sizeof(buf));
+ }
+ strlcat(buf, ":", sizeof(buf));
+
+ if (fp->fp_optcnt == 0)
+ strlcat(buf, ".", sizeof(buf));
+ for (i = fp->fp_optcnt - 1; i >= 0; i--) {
+ opt = fp->fp_tcpopts >> (i * PF_OSFP_TCPOPT_BITS);
+ opt &= (1 << PF_OSFP_TCPOPT_BITS) - 1;
+ switch (opt) {
+ case PF_OSFP_TCPOPT_NOP:
+ strlcat(buf, "N", sizeof(buf));
+ break;
+ case PF_OSFP_TCPOPT_SACK:
+ strlcat(buf, "S", sizeof(buf));
+ break;
+ case PF_OSFP_TCPOPT_TS:
+ strlcat(buf, "T", sizeof(buf));
+ if (fp->fp_flags & PF_OSFP_TS0)
+ strlcat(buf, "0", sizeof(buf));
+ break;
+ case PF_OSFP_TCPOPT_MSS:
+ strlcat(buf, "M", sizeof(buf));
+ if (fp->fp_flags & PF_OSFP_MSS_DC)
+ strlcat(buf, "*", sizeof(buf));
+ else {
+ if (fp->fp_flags & PF_OSFP_MSS_MOD)
+ strlcat(buf, "%", sizeof(buf));
+ snprintf(tmp, sizeof(tmp), "%d", fp->fp_mss);
+ strlcat(buf, tmp, sizeof(buf));
+ }
+ break;
+ case PF_OSFP_TCPOPT_WSCALE:
+ strlcat(buf, "W", sizeof(buf));
+ if (fp->fp_flags & PF_OSFP_WSCALE_DC)
+ strlcat(buf, "*", sizeof(buf));
+ else {
+ if (fp->fp_flags & PF_OSFP_WSCALE_MOD)
+ strlcat(buf, "%", sizeof(buf));
+ snprintf(tmp, sizeof(tmp), "%d", fp->fp_wscale);
+ strlcat(buf, tmp, sizeof(buf));
+ }
+ break;
+ }
+
+ if (i != 0)
+ strlcat(buf, ",", sizeof(buf));
+ }
+ strlcat(buf, ":", sizeof(buf));
+
+ strlcat(buf, fp->fp_os.fp_class_nm, sizeof(buf));
+ strlcat(buf, ":", sizeof(buf));
+ strlcat(buf, fp->fp_os.fp_version_nm, sizeof(buf));
+ strlcat(buf, ":", sizeof(buf));
+ strlcat(buf, fp->fp_os.fp_subtype_nm, sizeof(buf));
+ strlcat(buf, ":", sizeof(buf));
+
+ snprintf(tmp, sizeof(tmp), "TcpOpts %d 0x%llx", fp->fp_optcnt,
+ (long long int)fp->fp_tcpopts);
+ strlcat(buf, tmp, sizeof(buf));
+
+ return (buf);
+}
diff --git a/sbin/pfctl/pfctl_parser.c b/sbin/pfctl/pfctl_parser.c
new file mode 100644
index 0000000..1f4375a
--- /dev/null
+++ b/sbin/pfctl/pfctl_parser.c
@@ -0,0 +1,1766 @@
+/* $OpenBSD: pfctl_parser.c,v 1.240 2008/06/10 20:55:02 mcbride Exp $ */
+
+/*
+ * Copyright (c) 2001 Daniel Hartmeier
+ * Copyright (c) 2002,2003 Henning Brauer
+ * All rights reserved.
+ *
+ * Redistribution and use 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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
+ * COPYRIGHT HOLDERS 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 <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/param.h>
+#include <sys/proc.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <netinet/ip_icmp.h>
+#include <netinet/icmp6.h>
+#include <net/pfvar.h>
+#include <arpa/inet.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <netdb.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <err.h>
+#include <ifaddrs.h>
+#include <unistd.h>
+
+#include "pfctl_parser.h"
+#include "pfctl.h"
+
+void print_op (u_int8_t, const char *, const char *);
+void print_port (u_int8_t, u_int16_t, u_int16_t, const char *, int);
+void print_ugid (u_int8_t, unsigned, unsigned, const char *, unsigned);
+void print_flags (u_int8_t);
+void print_fromto(struct pf_rule_addr *, pf_osfp_t,
+ struct pf_rule_addr *, u_int8_t, u_int8_t, int, int);
+int ifa_skip_if(const char *filter, struct node_host *p);
+
+struct node_host *ifa_grouplookup(const char *, int);
+struct node_host *host_if(const char *, int);
+struct node_host *host_v4(const char *, int);
+struct node_host *host_v6(const char *, int);
+struct node_host *host_dns(const char *, int, int);
+
+const char *tcpflags = "FSRPAUEW";
+
+static const struct icmptypeent icmp_type[] = {
+ { "echoreq", ICMP_ECHO },
+ { "echorep", ICMP_ECHOREPLY },
+ { "unreach", ICMP_UNREACH },
+ { "squench", ICMP_SOURCEQUENCH },
+ { "redir", ICMP_REDIRECT },
+ { "althost", ICMP_ALTHOSTADDR },
+ { "routeradv", ICMP_ROUTERADVERT },
+ { "routersol", ICMP_ROUTERSOLICIT },
+ { "timex", ICMP_TIMXCEED },
+ { "paramprob", ICMP_PARAMPROB },
+ { "timereq", ICMP_TSTAMP },
+ { "timerep", ICMP_TSTAMPREPLY },
+ { "inforeq", ICMP_IREQ },
+ { "inforep", ICMP_IREQREPLY },
+ { "maskreq", ICMP_MASKREQ },
+ { "maskrep", ICMP_MASKREPLY },
+ { "trace", ICMP_TRACEROUTE },
+ { "dataconv", ICMP_DATACONVERR },
+ { "mobredir", ICMP_MOBILE_REDIRECT },
+ { "ipv6-where", ICMP_IPV6_WHEREAREYOU },
+ { "ipv6-here", ICMP_IPV6_IAMHERE },
+ { "mobregreq", ICMP_MOBILE_REGREQUEST },
+ { "mobregrep", ICMP_MOBILE_REGREPLY },
+ { "skip", ICMP_SKIP },
+ { "photuris", ICMP_PHOTURIS }
+};
+
+static const struct icmptypeent icmp6_type[] = {
+ { "unreach", ICMP6_DST_UNREACH },
+ { "toobig", ICMP6_PACKET_TOO_BIG },
+ { "timex", ICMP6_TIME_EXCEEDED },
+ { "paramprob", ICMP6_PARAM_PROB },
+ { "echoreq", ICMP6_ECHO_REQUEST },
+ { "echorep", ICMP6_ECHO_REPLY },
+ { "groupqry", ICMP6_MEMBERSHIP_QUERY },
+ { "listqry", MLD_LISTENER_QUERY },
+ { "grouprep", ICMP6_MEMBERSHIP_REPORT },
+ { "listenrep", MLD_LISTENER_REPORT },
+ { "groupterm", ICMP6_MEMBERSHIP_REDUCTION },
+ { "listendone", MLD_LISTENER_DONE },
+ { "routersol", ND_ROUTER_SOLICIT },
+ { "routeradv", ND_ROUTER_ADVERT },
+ { "neighbrsol", ND_NEIGHBOR_SOLICIT },
+ { "neighbradv", ND_NEIGHBOR_ADVERT },
+ { "redir", ND_REDIRECT },
+ { "routrrenum", ICMP6_ROUTER_RENUMBERING },
+ { "wrureq", ICMP6_WRUREQUEST },
+ { "wrurep", ICMP6_WRUREPLY },
+ { "fqdnreq", ICMP6_FQDN_QUERY },
+ { "fqdnrep", ICMP6_FQDN_REPLY },
+ { "niqry", ICMP6_NI_QUERY },
+ { "nirep", ICMP6_NI_REPLY },
+ { "mtraceresp", MLD_MTRACE_RESP },
+ { "mtrace", MLD_MTRACE }
+};
+
+static const struct icmpcodeent icmp_code[] = {
+ { "net-unr", ICMP_UNREACH, ICMP_UNREACH_NET },
+ { "host-unr", ICMP_UNREACH, ICMP_UNREACH_HOST },
+ { "proto-unr", ICMP_UNREACH, ICMP_UNREACH_PROTOCOL },
+ { "port-unr", ICMP_UNREACH, ICMP_UNREACH_PORT },
+ { "needfrag", ICMP_UNREACH, ICMP_UNREACH_NEEDFRAG },
+ { "srcfail", ICMP_UNREACH, ICMP_UNREACH_SRCFAIL },
+ { "net-unk", ICMP_UNREACH, ICMP_UNREACH_NET_UNKNOWN },
+ { "host-unk", ICMP_UNREACH, ICMP_UNREACH_HOST_UNKNOWN },
+ { "isolate", ICMP_UNREACH, ICMP_UNREACH_ISOLATED },
+ { "net-prohib", ICMP_UNREACH, ICMP_UNREACH_NET_PROHIB },
+ { "host-prohib", ICMP_UNREACH, ICMP_UNREACH_HOST_PROHIB },
+ { "net-tos", ICMP_UNREACH, ICMP_UNREACH_TOSNET },
+ { "host-tos", ICMP_UNREACH, ICMP_UNREACH_TOSHOST },
+ { "filter-prohib", ICMP_UNREACH, ICMP_UNREACH_FILTER_PROHIB },
+ { "host-preced", ICMP_UNREACH, ICMP_UNREACH_HOST_PRECEDENCE },
+ { "cutoff-preced", ICMP_UNREACH, ICMP_UNREACH_PRECEDENCE_CUTOFF },
+ { "redir-net", ICMP_REDIRECT, ICMP_REDIRECT_NET },
+ { "redir-host", ICMP_REDIRECT, ICMP_REDIRECT_HOST },
+ { "redir-tos-net", ICMP_REDIRECT, ICMP_REDIRECT_TOSNET },
+ { "redir-tos-host", ICMP_REDIRECT, ICMP_REDIRECT_TOSHOST },
+ { "normal-adv", ICMP_ROUTERADVERT, ICMP_ROUTERADVERT_NORMAL },
+ { "common-adv", ICMP_ROUTERADVERT, ICMP_ROUTERADVERT_NOROUTE_COMMON },
+ { "transit", ICMP_TIMXCEED, ICMP_TIMXCEED_INTRANS },
+ { "reassemb", ICMP_TIMXCEED, ICMP_TIMXCEED_REASS },
+ { "badhead", ICMP_PARAMPROB, ICMP_PARAMPROB_ERRATPTR },
+ { "optmiss", ICMP_PARAMPROB, ICMP_PARAMPROB_OPTABSENT },
+ { "badlen", ICMP_PARAMPROB, ICMP_PARAMPROB_LENGTH },
+ { "unknown-ind", ICMP_PHOTURIS, ICMP_PHOTURIS_UNKNOWN_INDEX },
+ { "auth-fail", ICMP_PHOTURIS, ICMP_PHOTURIS_AUTH_FAILED },
+ { "decrypt-fail", ICMP_PHOTURIS, ICMP_PHOTURIS_DECRYPT_FAILED }
+};
+
+static const struct icmpcodeent icmp6_code[] = {
+ { "admin-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_ADMIN },
+ { "noroute-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_NOROUTE },
+ { "notnbr-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_NOTNEIGHBOR },
+ { "beyond-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_BEYONDSCOPE },
+ { "addr-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_ADDR },
+ { "port-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_NOPORT },
+ { "transit", ICMP6_TIME_EXCEEDED, ICMP6_TIME_EXCEED_TRANSIT },
+ { "reassemb", ICMP6_TIME_EXCEEDED, ICMP6_TIME_EXCEED_REASSEMBLY },
+ { "badhead", ICMP6_PARAM_PROB, ICMP6_PARAMPROB_HEADER },
+ { "nxthdr", ICMP6_PARAM_PROB, ICMP6_PARAMPROB_NEXTHEADER },
+ { "redironlink", ND_REDIRECT, ND_REDIRECT_ONLINK },
+ { "redirrouter", ND_REDIRECT, ND_REDIRECT_ROUTER }
+};
+
+const struct pf_timeout pf_timeouts[] = {
+ { "tcp.first", PFTM_TCP_FIRST_PACKET },
+ { "tcp.opening", PFTM_TCP_OPENING },
+ { "tcp.established", PFTM_TCP_ESTABLISHED },
+ { "tcp.closing", PFTM_TCP_CLOSING },
+ { "tcp.finwait", PFTM_TCP_FIN_WAIT },
+ { "tcp.closed", PFTM_TCP_CLOSED },
+ { "tcp.tsdiff", PFTM_TS_DIFF },
+ { "udp.first", PFTM_UDP_FIRST_PACKET },
+ { "udp.single", PFTM_UDP_SINGLE },
+ { "udp.multiple", PFTM_UDP_MULTIPLE },
+ { "icmp.first", PFTM_ICMP_FIRST_PACKET },
+ { "icmp.error", PFTM_ICMP_ERROR_REPLY },
+ { "other.first", PFTM_OTHER_FIRST_PACKET },
+ { "other.single", PFTM_OTHER_SINGLE },
+ { "other.multiple", PFTM_OTHER_MULTIPLE },
+ { "frag", PFTM_FRAG },
+ { "interval", PFTM_INTERVAL },
+ { "adaptive.start", PFTM_ADAPTIVE_START },
+ { "adaptive.end", PFTM_ADAPTIVE_END },
+ { "src.track", PFTM_SRC_NODE },
+ { NULL, 0 }
+};
+
+const struct icmptypeent *
+geticmptypebynumber(u_int8_t type, sa_family_t af)
+{
+ unsigned int i;
+
+ if (af != AF_INET6) {
+ for (i=0; i < (sizeof (icmp_type) / sizeof(icmp_type[0]));
+ i++) {
+ if (type == icmp_type[i].type)
+ return (&icmp_type[i]);
+ }
+ } else {
+ for (i=0; i < (sizeof (icmp6_type) /
+ sizeof(icmp6_type[0])); i++) {
+ if (type == icmp6_type[i].type)
+ return (&icmp6_type[i]);
+ }
+ }
+ return (NULL);
+}
+
+const struct icmptypeent *
+geticmptypebyname(char *w, sa_family_t af)
+{
+ unsigned int i;
+
+ if (af != AF_INET6) {
+ for (i=0; i < (sizeof (icmp_type) / sizeof(icmp_type[0]));
+ i++) {
+ if (!strcmp(w, icmp_type[i].name))
+ return (&icmp_type[i]);
+ }
+ } else {
+ for (i=0; i < (sizeof (icmp6_type) /
+ sizeof(icmp6_type[0])); i++) {
+ if (!strcmp(w, icmp6_type[i].name))
+ return (&icmp6_type[i]);
+ }
+ }
+ return (NULL);
+}
+
+const struct icmpcodeent *
+geticmpcodebynumber(u_int8_t type, u_int8_t code, sa_family_t af)
+{
+ unsigned int i;
+
+ if (af != AF_INET6) {
+ for (i=0; i < (sizeof (icmp_code) / sizeof(icmp_code[0]));
+ i++) {
+ if (type == icmp_code[i].type &&
+ code == icmp_code[i].code)
+ return (&icmp_code[i]);
+ }
+ } else {
+ for (i=0; i < (sizeof (icmp6_code) /
+ sizeof(icmp6_code[0])); i++) {
+ if (type == icmp6_code[i].type &&
+ code == icmp6_code[i].code)
+ return (&icmp6_code[i]);
+ }
+ }
+ return (NULL);
+}
+
+const struct icmpcodeent *
+geticmpcodebyname(u_long type, char *w, sa_family_t af)
+{
+ unsigned int i;
+
+ if (af != AF_INET6) {
+ for (i=0; i < (sizeof (icmp_code) / sizeof(icmp_code[0]));
+ i++) {
+ if (type == icmp_code[i].type &&
+ !strcmp(w, icmp_code[i].name))
+ return (&icmp_code[i]);
+ }
+ } else {
+ for (i=0; i < (sizeof (icmp6_code) /
+ sizeof(icmp6_code[0])); i++) {
+ if (type == icmp6_code[i].type &&
+ !strcmp(w, icmp6_code[i].name))
+ return (&icmp6_code[i]);
+ }
+ }
+ return (NULL);
+}
+
+void
+print_op(u_int8_t op, const char *a1, const char *a2)
+{
+ if (op == PF_OP_IRG)
+ printf(" %s >< %s", a1, a2);
+ else if (op == PF_OP_XRG)
+ printf(" %s <> %s", a1, a2);
+ else if (op == PF_OP_EQ)
+ printf(" = %s", a1);
+ else if (op == PF_OP_NE)
+ printf(" != %s", a1);
+ else if (op == PF_OP_LT)
+ printf(" < %s", a1);
+ else if (op == PF_OP_LE)
+ printf(" <= %s", a1);
+ else if (op == PF_OP_GT)
+ printf(" > %s", a1);
+ else if (op == PF_OP_GE)
+ printf(" >= %s", a1);
+ else if (op == PF_OP_RRG)
+ printf(" %s:%s", a1, a2);
+}
+
+void
+print_port(u_int8_t op, u_int16_t p1, u_int16_t p2, const char *proto, int numeric)
+{
+ char a1[6], a2[6];
+ struct servent *s;
+
+ if (!numeric)
+ s = getservbyport(p1, proto);
+ else
+ s = NULL;
+ p1 = ntohs(p1);
+ p2 = ntohs(p2);
+ snprintf(a1, sizeof(a1), "%u", p1);
+ snprintf(a2, sizeof(a2), "%u", p2);
+ printf(" port");
+ if (s != NULL && (op == PF_OP_EQ || op == PF_OP_NE))
+ print_op(op, s->s_name, a2);
+ else
+ print_op(op, a1, a2);
+}
+
+void
+print_ugid(u_int8_t op, unsigned u1, unsigned u2, const char *t, unsigned umax)
+{
+ char a1[11], a2[11];
+
+ snprintf(a1, sizeof(a1), "%u", u1);
+ snprintf(a2, sizeof(a2), "%u", u2);
+ printf(" %s", t);
+ if (u1 == umax && (op == PF_OP_EQ || op == PF_OP_NE))
+ print_op(op, "unknown", a2);
+ else
+ print_op(op, a1, a2);
+}
+
+void
+print_flags(u_int8_t f)
+{
+ int i;
+
+ for (i = 0; tcpflags[i]; ++i)
+ if (f & (1 << i))
+ printf("%c", tcpflags[i]);
+}
+
+void
+print_fromto(struct pf_rule_addr *src, pf_osfp_t osfp, struct pf_rule_addr *dst,
+ sa_family_t af, u_int8_t proto, int verbose, int numeric)
+{
+ char buf[PF_OSFP_LEN*3];
+ if (src->addr.type == PF_ADDR_ADDRMASK &&
+ dst->addr.type == PF_ADDR_ADDRMASK &&
+ PF_AZERO(&src->addr.v.a.addr, AF_INET6) &&
+ PF_AZERO(&src->addr.v.a.mask, AF_INET6) &&
+ PF_AZERO(&dst->addr.v.a.addr, AF_INET6) &&
+ PF_AZERO(&dst->addr.v.a.mask, AF_INET6) &&
+ !src->neg && !dst->neg &&
+ !src->port_op && !dst->port_op &&
+ osfp == PF_OSFP_ANY)
+ printf(" all");
+ else {
+ printf(" from ");
+ if (src->neg)
+ printf("! ");
+ print_addr(&src->addr, af, verbose);
+ if (src->port_op)
+ print_port(src->port_op, src->port[0],
+ src->port[1],
+ proto == IPPROTO_TCP ? "tcp" : "udp",
+ numeric);
+ if (osfp != PF_OSFP_ANY)
+ printf(" os \"%s\"", pfctl_lookup_fingerprint(osfp, buf,
+ sizeof(buf)));
+
+ printf(" to ");
+ if (dst->neg)
+ printf("! ");
+ print_addr(&dst->addr, af, verbose);
+ if (dst->port_op)
+ print_port(dst->port_op, dst->port[0],
+ dst->port[1],
+ proto == IPPROTO_TCP ? "tcp" : "udp",
+ numeric);
+ }
+}
+
+void
+print_pool(struct pf_pool *pool, u_int16_t p1, u_int16_t p2,
+ sa_family_t af, int id)
+{
+ struct pf_pooladdr *pooladdr;
+
+ if ((TAILQ_FIRST(&pool->list) != NULL) &&
+ TAILQ_NEXT(TAILQ_FIRST(&pool->list), entries) != NULL)
+ printf("{ ");
+ TAILQ_FOREACH(pooladdr, &pool->list, entries){
+ switch (id) {
+ case PF_NAT:
+ case PF_RDR:
+ case PF_BINAT:
+ print_addr(&pooladdr->addr, af, 0);
+ break;
+ case PF_PASS:
+ if (PF_AZERO(&pooladdr->addr.v.a.addr, af))
+ printf("%s", pooladdr->ifname);
+ else {
+ printf("(%s ", pooladdr->ifname);
+ print_addr(&pooladdr->addr, af, 0);
+ printf(")");
+ }
+ break;
+ default:
+ break;
+ }
+ if (TAILQ_NEXT(pooladdr, entries) != NULL)
+ printf(", ");
+ else if (TAILQ_NEXT(TAILQ_FIRST(&pool->list), entries) != NULL)
+ printf(" }");
+ }
+ switch (id) {
+ case PF_NAT:
+ if ((p1 != PF_NAT_PROXY_PORT_LOW ||
+ p2 != PF_NAT_PROXY_PORT_HIGH) && (p1 != 0 || p2 != 0)) {
+ if (p1 == p2)
+ printf(" port %u", p1);
+ else
+ printf(" port %u:%u", p1, p2);
+ }
+ break;
+ case PF_RDR:
+ if (p1) {
+ printf(" port %u", p1);
+ if (p2 && (p2 != p1))
+ printf(":%u", p2);
+ }
+ break;
+ default:
+ break;
+ }
+ switch (pool->opts & PF_POOL_TYPEMASK) {
+ case PF_POOL_NONE:
+ break;
+ case PF_POOL_BITMASK:
+ printf(" bitmask");
+ break;
+ case PF_POOL_RANDOM:
+ printf(" random");
+ break;
+ case PF_POOL_SRCHASH:
+ printf(" source-hash 0x%08x%08x%08x%08x",
+ pool->key.key32[0], pool->key.key32[1],
+ pool->key.key32[2], pool->key.key32[3]);
+ break;
+ case PF_POOL_ROUNDROBIN:
+ printf(" round-robin");
+ break;
+ }
+ if (pool->opts & PF_POOL_STICKYADDR)
+ printf(" sticky-address");
+ if (id == PF_NAT && p1 == 0 && p2 == 0)
+ printf(" static-port");
+}
+
+const char *pf_reasons[PFRES_MAX+1] = PFRES_NAMES;
+const char *pf_lcounters[LCNT_MAX+1] = LCNT_NAMES;
+const char *pf_fcounters[FCNT_MAX+1] = FCNT_NAMES;
+const char *pf_scounters[FCNT_MAX+1] = FCNT_NAMES;
+
+void
+print_status(struct pf_status *s, int opts)
+{
+ char statline[80], *running;
+ time_t runtime;
+ int i;
+ char buf[PF_MD5_DIGEST_LENGTH * 2 + 1];
+ static const char hex[] = "0123456789abcdef";
+
+ runtime = time(NULL) - s->since;
+ running = s->running ? "Enabled" : "Disabled";
+
+ if (s->since) {
+ unsigned int sec, min, hrs, day = runtime;
+
+ sec = day % 60;
+ day /= 60;
+ min = day % 60;
+ day /= 60;
+ hrs = day % 24;
+ day /= 24;
+ snprintf(statline, sizeof(statline),
+ "Status: %s for %u days %.2u:%.2u:%.2u",
+ running, day, hrs, min, sec);
+ } else
+ snprintf(statline, sizeof(statline), "Status: %s", running);
+ printf("%-44s", statline);
+ switch (s->debug) {
+ case PF_DEBUG_NONE:
+ printf("%15s\n\n", "Debug: None");
+ break;
+ case PF_DEBUG_URGENT:
+ printf("%15s\n\n", "Debug: Urgent");
+ break;
+ case PF_DEBUG_MISC:
+ printf("%15s\n\n", "Debug: Misc");
+ break;
+ case PF_DEBUG_NOISY:
+ printf("%15s\n\n", "Debug: Loud");
+ break;
+ }
+
+ if (opts & PF_OPT_VERBOSE) {
+ printf("Hostid: 0x%08x\n", ntohl(s->hostid));
+
+ for (i = 0; i < PF_MD5_DIGEST_LENGTH; i++) {
+ buf[i + i] = hex[s->pf_chksum[i] >> 4];
+ buf[i + i + 1] = hex[s->pf_chksum[i] & 0x0f];
+ }
+ buf[i + i] = '\0';
+ printf("Checksum: 0x%s\n\n", buf);
+ }
+
+ if (s->ifname[0] != 0) {
+ printf("Interface Stats for %-16s %5s %16s\n",
+ s->ifname, "IPv4", "IPv6");
+ printf(" %-25s %14llu %16llu\n", "Bytes In",
+ (unsigned long long)s->bcounters[0][0],
+ (unsigned long long)s->bcounters[1][0]);
+ printf(" %-25s %14llu %16llu\n", "Bytes Out",
+ (unsigned long long)s->bcounters[0][1],
+ (unsigned long long)s->bcounters[1][1]);
+ printf(" Packets In\n");
+ printf(" %-23s %14llu %16llu\n", "Passed",
+ (unsigned long long)s->pcounters[0][0][PF_PASS],
+ (unsigned long long)s->pcounters[1][0][PF_PASS]);
+ printf(" %-23s %14llu %16llu\n", "Blocked",
+ (unsigned long long)s->pcounters[0][0][PF_DROP],
+ (unsigned long long)s->pcounters[1][0][PF_DROP]);
+ printf(" Packets Out\n");
+ printf(" %-23s %14llu %16llu\n", "Passed",
+ (unsigned long long)s->pcounters[0][1][PF_PASS],
+ (unsigned long long)s->pcounters[1][1][PF_PASS]);
+ printf(" %-23s %14llu %16llu\n\n", "Blocked",
+ (unsigned long long)s->pcounters[0][1][PF_DROP],
+ (unsigned long long)s->pcounters[1][1][PF_DROP]);
+ }
+ printf("%-27s %14s %16s\n", "State Table", "Total", "Rate");
+ printf(" %-25s %14u %14s\n", "current entries", s->states, "");
+ for (i = 0; i < FCNT_MAX; i++) {
+ printf(" %-25s %14llu ", pf_fcounters[i],
+ (unsigned long long)s->fcounters[i]);
+ if (runtime > 0)
+ printf("%14.1f/s\n",
+ (double)s->fcounters[i] / (double)runtime);
+ else
+ printf("%14s\n", "");
+ }
+ if (opts & PF_OPT_VERBOSE) {
+ printf("Source Tracking Table\n");
+ printf(" %-25s %14u %14s\n", "current entries",
+ s->src_nodes, "");
+ for (i = 0; i < SCNT_MAX; i++) {
+ printf(" %-25s %14lld ", pf_scounters[i],
+#ifdef __FreeBSD__
+ (long long)s->scounters[i]);
+#else
+ s->scounters[i]);
+#endif
+ if (runtime > 0)
+ printf("%14.1f/s\n",
+ (double)s->scounters[i] / (double)runtime);
+ else
+ printf("%14s\n", "");
+ }
+ }
+ printf("Counters\n");
+ for (i = 0; i < PFRES_MAX; i++) {
+ printf(" %-25s %14llu ", pf_reasons[i],
+ (unsigned long long)s->counters[i]);
+ if (runtime > 0)
+ printf("%14.1f/s\n",
+ (double)s->counters[i] / (double)runtime);
+ else
+ printf("%14s\n", "");
+ }
+ if (opts & PF_OPT_VERBOSE) {
+ printf("Limit Counters\n");
+ for (i = 0; i < LCNT_MAX; i++) {
+ printf(" %-25s %14lld ", pf_lcounters[i],
+#ifdef __FreeBSD__
+ (unsigned long long)s->lcounters[i]);
+#else
+ s->lcounters[i]);
+#endif
+ if (runtime > 0)
+ printf("%14.1f/s\n",
+ (double)s->lcounters[i] / (double)runtime);
+ else
+ printf("%14s\n", "");
+ }
+ }
+}
+
+void
+print_src_node(struct pf_src_node *sn, int opts)
+{
+ struct pf_addr_wrap aw;
+ int min, sec;
+
+ memset(&aw, 0, sizeof(aw));
+ if (sn->af == AF_INET)
+ aw.v.a.mask.addr32[0] = 0xffffffff;
+ else
+ memset(&aw.v.a.mask, 0xff, sizeof(aw.v.a.mask));
+
+ aw.v.a.addr = sn->addr;
+ print_addr(&aw, sn->af, opts & PF_OPT_VERBOSE2);
+ printf(" -> ");
+ aw.v.a.addr = sn->raddr;
+ print_addr(&aw, sn->af, opts & PF_OPT_VERBOSE2);
+ printf(" ( states %u, connections %u, rate %u.%u/%us )\n", sn->states,
+ sn->conn, sn->conn_rate.count / 1000,
+ (sn->conn_rate.count % 1000) / 100, sn->conn_rate.seconds);
+ if (opts & PF_OPT_VERBOSE) {
+ sec = sn->creation % 60;
+ sn->creation /= 60;
+ min = sn->creation % 60;
+ sn->creation /= 60;
+ printf(" age %.2u:%.2u:%.2u", sn->creation, min, sec);
+ if (sn->states == 0) {
+ sec = sn->expire % 60;
+ sn->expire /= 60;
+ min = sn->expire % 60;
+ sn->expire /= 60;
+ printf(", expires in %.2u:%.2u:%.2u",
+ sn->expire, min, sec);
+ }
+ printf(", %llu pkts, %llu bytes",
+#ifdef __FreeBSD__
+ (unsigned long long)(sn->packets[0] + sn->packets[1]),
+ (unsigned long long)(sn->bytes[0] + sn->bytes[1]));
+#else
+ sn->packets[0] + sn->packets[1],
+ sn->bytes[0] + sn->bytes[1]);
+#endif
+ switch (sn->ruletype) {
+ case PF_NAT:
+ if (sn->rule.nr != -1)
+ printf(", nat rule %u", sn->rule.nr);
+ break;
+ case PF_RDR:
+ if (sn->rule.nr != -1)
+ printf(", rdr rule %u", sn->rule.nr);
+ break;
+ case PF_PASS:
+ if (sn->rule.nr != -1)
+ printf(", filter rule %u", sn->rule.nr);
+ break;
+ }
+ printf("\n");
+ }
+}
+
+void
+print_rule(struct pf_rule *r, const char *anchor_call, int verbose, int numeric)
+{
+ static const char *actiontypes[] = { "pass", "block", "scrub",
+ "no scrub", "nat", "no nat", "binat", "no binat", "rdr", "no rdr" };
+ static const char *anchortypes[] = { "anchor", "anchor", "anchor",
+ "anchor", "nat-anchor", "nat-anchor", "binat-anchor",
+ "binat-anchor", "rdr-anchor", "rdr-anchor" };
+ int i, opts;
+
+ if (verbose)
+ printf("@%d ", r->nr);
+ if (r->action > PF_NORDR)
+ printf("action(%d)", r->action);
+ else if (anchor_call[0]) {
+ if (anchor_call[0] == '_') {
+ printf("%s", anchortypes[r->action]);
+ } else
+ printf("%s \"%s\"", anchortypes[r->action],
+ anchor_call);
+ } else {
+ printf("%s", actiontypes[r->action]);
+ if (r->natpass)
+ printf(" pass");
+ }
+ if (r->action == PF_DROP) {
+ if (r->rule_flag & PFRULE_RETURN)
+ printf(" return");
+ else if (r->rule_flag & PFRULE_RETURNRST) {
+ if (!r->return_ttl)
+ printf(" return-rst");
+ else
+ printf(" return-rst(ttl %d)", r->return_ttl);
+ } else if (r->rule_flag & PFRULE_RETURNICMP) {
+ const struct icmpcodeent *ic, *ic6;
+
+ ic = geticmpcodebynumber(r->return_icmp >> 8,
+ r->return_icmp & 255, AF_INET);
+ ic6 = geticmpcodebynumber(r->return_icmp6 >> 8,
+ r->return_icmp6 & 255, AF_INET6);
+
+ switch (r->af) {
+ case AF_INET:
+ printf(" return-icmp");
+ if (ic == NULL)
+ printf("(%u)", r->return_icmp & 255);
+ else
+ printf("(%s)", ic->name);
+ break;
+ case AF_INET6:
+ printf(" return-icmp6");
+ if (ic6 == NULL)
+ printf("(%u)", r->return_icmp6 & 255);
+ else
+ printf("(%s)", ic6->name);
+ break;
+ default:
+ printf(" return-icmp");
+ if (ic == NULL)
+ printf("(%u, ", r->return_icmp & 255);
+ else
+ printf("(%s, ", ic->name);
+ if (ic6 == NULL)
+ printf("%u)", r->return_icmp6 & 255);
+ else
+ printf("%s)", ic6->name);
+ break;
+ }
+ } else
+ printf(" drop");
+ }
+ if (r->direction == PF_IN)
+ printf(" in");
+ else if (r->direction == PF_OUT)
+ printf(" out");
+ if (r->log) {
+ printf(" log");
+ if (r->log & ~PF_LOG || r->logif) {
+ int count = 0;
+
+ printf(" (");
+ if (r->log & PF_LOG_ALL)
+ printf("%sall", count++ ? ", " : "");
+ if (r->log & PF_LOG_SOCKET_LOOKUP)
+ printf("%suser", count++ ? ", " : "");
+ if (r->logif)
+ printf("%sto pflog%u", count++ ? ", " : "",
+ r->logif);
+ printf(")");
+ }
+ }
+ if (r->quick)
+ printf(" quick");
+ if (r->ifname[0]) {
+ if (r->ifnot)
+ printf(" on ! %s", r->ifname);
+ else
+ printf(" on %s", r->ifname);
+ }
+ if (r->rt) {
+ if (r->rt == PF_ROUTETO)
+ printf(" route-to");
+ else if (r->rt == PF_REPLYTO)
+ printf(" reply-to");
+ else if (r->rt == PF_DUPTO)
+ printf(" dup-to");
+ else if (r->rt == PF_FASTROUTE)
+ printf(" fastroute");
+ if (r->rt != PF_FASTROUTE) {
+ printf(" ");
+ print_pool(&r->rpool, 0, 0, r->af, PF_PASS);
+ }
+ }
+ if (r->af) {
+ if (r->af == AF_INET)
+ printf(" inet");
+ else
+ printf(" inet6");
+ }
+ if (r->proto) {
+ struct protoent *p;
+
+ if ((p = getprotobynumber(r->proto)) != NULL)
+ printf(" proto %s", p->p_name);
+ else
+ printf(" proto %u", r->proto);
+ }
+ print_fromto(&r->src, r->os_fingerprint, &r->dst, r->af, r->proto,
+ verbose, numeric);
+ if (r->uid.op)
+ print_ugid(r->uid.op, r->uid.uid[0], r->uid.uid[1], "user",
+ UID_MAX);
+ if (r->gid.op)
+ print_ugid(r->gid.op, r->gid.gid[0], r->gid.gid[1], "group",
+ GID_MAX);
+ if (r->flags || r->flagset) {
+ printf(" flags ");
+ print_flags(r->flags);
+ printf("/");
+ print_flags(r->flagset);
+ } else if (r->action == PF_PASS &&
+ (!r->proto || r->proto == IPPROTO_TCP) &&
+ !(r->rule_flag & PFRULE_FRAGMENT) &&
+ !anchor_call[0] && r->keep_state)
+ printf(" flags any");
+ if (r->type) {
+ const struct icmptypeent *it;
+
+ it = geticmptypebynumber(r->type-1, r->af);
+ if (r->af != AF_INET6)
+ printf(" icmp-type");
+ else
+ printf(" icmp6-type");
+ if (it != NULL)
+ printf(" %s", it->name);
+ else
+ printf(" %u", r->type-1);
+ if (r->code) {
+ const struct icmpcodeent *ic;
+
+ ic = geticmpcodebynumber(r->type-1, r->code-1, r->af);
+ if (ic != NULL)
+ printf(" code %s", ic->name);
+ else
+ printf(" code %u", r->code-1);
+ }
+ }
+ if (r->tos)
+ printf(" tos 0x%2.2x", r->tos);
+ if (!r->keep_state && r->action == PF_PASS && !anchor_call[0])
+ printf(" no state");
+ else if (r->keep_state == PF_STATE_NORMAL)
+ printf(" keep state");
+ else if (r->keep_state == PF_STATE_MODULATE)
+ printf(" modulate state");
+ else if (r->keep_state == PF_STATE_SYNPROXY)
+ printf(" synproxy state");
+ if (r->prob) {
+ char buf[20];
+
+ snprintf(buf, sizeof(buf), "%f", r->prob*100.0/(UINT_MAX+1.0));
+ for (i = strlen(buf)-1; i > 0; i--) {
+ if (buf[i] == '0')
+ buf[i] = '\0';
+ else {
+ if (buf[i] == '.')
+ buf[i] = '\0';
+ break;
+ }
+ }
+ printf(" probability %s%%", buf);
+ }
+ opts = 0;
+ if (r->max_states || r->max_src_nodes || r->max_src_states)
+ opts = 1;
+ if (r->rule_flag & PFRULE_NOSYNC)
+ opts = 1;
+ if (r->rule_flag & PFRULE_SRCTRACK)
+ opts = 1;
+ if (r->rule_flag & PFRULE_IFBOUND)
+ opts = 1;
+ if (r->rule_flag & PFRULE_STATESLOPPY)
+ opts = 1;
+ for (i = 0; !opts && i < PFTM_MAX; ++i)
+ if (r->timeout[i])
+ opts = 1;
+ if (opts) {
+ printf(" (");
+ if (r->max_states) {
+ printf("max %u", r->max_states);
+ opts = 0;
+ }
+ if (r->rule_flag & PFRULE_NOSYNC) {
+ if (!opts)
+ printf(", ");
+ printf("no-sync");
+ opts = 0;
+ }
+ if (r->rule_flag & PFRULE_SRCTRACK) {
+ if (!opts)
+ printf(", ");
+ printf("source-track");
+ if (r->rule_flag & PFRULE_RULESRCTRACK)
+ printf(" rule");
+ else
+ printf(" global");
+ opts = 0;
+ }
+ if (r->max_src_states) {
+ if (!opts)
+ printf(", ");
+ printf("max-src-states %u", r->max_src_states);
+ opts = 0;
+ }
+ if (r->max_src_conn) {
+ if (!opts)
+ printf(", ");
+ printf("max-src-conn %u", r->max_src_conn);
+ opts = 0;
+ }
+ if (r->max_src_conn_rate.limit) {
+ if (!opts)
+ printf(", ");
+ printf("max-src-conn-rate %u/%u",
+ r->max_src_conn_rate.limit,
+ r->max_src_conn_rate.seconds);
+ opts = 0;
+ }
+ if (r->max_src_nodes) {
+ if (!opts)
+ printf(", ");
+ printf("max-src-nodes %u", r->max_src_nodes);
+ opts = 0;
+ }
+ if (r->overload_tblname[0]) {
+ if (!opts)
+ printf(", ");
+ printf("overload <%s>", r->overload_tblname);
+ if (r->flush)
+ printf(" flush");
+ if (r->flush & PF_FLUSH_GLOBAL)
+ printf(" global");
+ }
+ if (r->rule_flag & PFRULE_IFBOUND) {
+ if (!opts)
+ printf(", ");
+ printf("if-bound");
+ opts = 0;
+ }
+ if (r->rule_flag & PFRULE_STATESLOPPY) {
+ if (!opts)
+ printf(", ");
+ printf("sloppy");
+ opts = 0;
+ }
+ for (i = 0; i < PFTM_MAX; ++i)
+ if (r->timeout[i]) {
+ int j;
+
+ if (!opts)
+ printf(", ");
+ opts = 0;
+ for (j = 0; pf_timeouts[j].name != NULL;
+ ++j)
+ if (pf_timeouts[j].timeout == i)
+ break;
+ printf("%s %u", pf_timeouts[j].name == NULL ?
+ "inv.timeout" : pf_timeouts[j].name,
+ r->timeout[i]);
+ }
+ printf(")");
+ }
+ if (r->rule_flag & PFRULE_FRAGMENT)
+ printf(" fragment");
+ if (r->rule_flag & PFRULE_NODF)
+ printf(" no-df");
+ if (r->rule_flag & PFRULE_RANDOMID)
+ printf(" random-id");
+ if (r->min_ttl)
+ printf(" min-ttl %d", r->min_ttl);
+ if (r->max_mss)
+ printf(" max-mss %d", r->max_mss);
+ if (r->rule_flag & PFRULE_SET_TOS)
+ printf(" set-tos 0x%2.2x", r->set_tos);
+ if (r->allow_opts)
+ printf(" allow-opts");
+ if (r->action == PF_SCRUB) {
+ if (r->rule_flag & PFRULE_REASSEMBLE_TCP)
+ printf(" reassemble tcp");
+
+ if (r->rule_flag & PFRULE_FRAGDROP)
+ printf(" fragment drop-ovl");
+ else if (r->rule_flag & PFRULE_FRAGCROP)
+ printf(" fragment crop");
+ else
+ printf(" fragment reassemble");
+ }
+ if (r->label[0])
+ printf(" label \"%s\"", r->label);
+ if (r->qname[0] && r->pqname[0])
+ printf(" queue(%s, %s)", r->qname, r->pqname);
+ else if (r->qname[0])
+ printf(" queue %s", r->qname);
+ if (r->tagname[0])
+ printf(" tag %s", r->tagname);
+ if (r->match_tagname[0]) {
+ if (r->match_tag_not)
+ printf(" !");
+ printf(" tagged %s", r->match_tagname);
+ }
+ if (r->rtableid != -1)
+ printf(" rtable %u", r->rtableid);
+ if (r->divert.port) {
+#ifdef __FreeBSD__
+ printf(" divert-to %u", ntohs(r->divert.port));
+#else
+ if (PF_AZERO(&r->divert.addr, r->af)) {
+ printf(" divert-reply");
+ } else {
+ /* XXX cut&paste from print_addr */
+ char buf[48];
+
+ printf(" divert-to ");
+ if (inet_ntop(r->af, &r->divert.addr, buf,
+ sizeof(buf)) == NULL)
+ printf("?");
+ else
+ printf("%s", buf);
+ printf(" port %u", ntohs(r->divert.port));
+ }
+#endif
+ }
+ if (!anchor_call[0] && (r->action == PF_NAT ||
+ r->action == PF_BINAT || r->action == PF_RDR)) {
+ printf(" -> ");
+ print_pool(&r->rpool, r->rpool.proxy_port[0],
+ r->rpool.proxy_port[1], r->af, r->action);
+ }
+}
+
+void
+print_tabledef(const char *name, int flags, int addrs,
+ struct node_tinithead *nodes)
+{
+ struct node_tinit *ti, *nti;
+ struct node_host *h;
+
+ printf("table <%s>", name);
+ if (flags & PFR_TFLAG_CONST)
+ printf(" const");
+ if (flags & PFR_TFLAG_PERSIST)
+ printf(" persist");
+ if (flags & PFR_TFLAG_COUNTERS)
+ printf(" counters");
+ SIMPLEQ_FOREACH(ti, nodes, entries) {
+ if (ti->file) {
+ printf(" file \"%s\"", ti->file);
+ continue;
+ }
+ printf(" {");
+ for (;;) {
+ for (h = ti->host; h != NULL; h = h->next) {
+ printf(h->not ? " !" : " ");
+ print_addr(&h->addr, h->af, 0);
+ }
+ nti = SIMPLEQ_NEXT(ti, entries);
+ if (nti != NULL && nti->file == NULL)
+ ti = nti; /* merge lists */
+ else
+ break;
+ }
+ printf(" }");
+ }
+ if (addrs && SIMPLEQ_EMPTY(nodes))
+ printf(" { }");
+ printf("\n");
+}
+
+int
+parse_flags(char *s)
+{
+ char *p, *q;
+ u_int8_t f = 0;
+
+ for (p = s; *p; p++) {
+ if ((q = strchr(tcpflags, *p)) == NULL)
+ return -1;
+ else
+ f |= 1 << (q - tcpflags);
+ }
+ return (f ? f : PF_TH_ALL);
+}
+
+void
+set_ipmask(struct node_host *h, u_int8_t b)
+{
+ struct pf_addr *m, *n;
+ int i, j = 0;
+
+ m = &h->addr.v.a.mask;
+ memset(m, 0, sizeof(*m));
+
+ while (b >= 32) {
+ m->addr32[j++] = 0xffffffff;
+ b -= 32;
+ }
+ for (i = 31; i > 31-b; --i)
+ m->addr32[j] |= (1 << i);
+ if (b)
+ m->addr32[j] = htonl(m->addr32[j]);
+
+ /* Mask off bits of the address that will never be used. */
+ n = &h->addr.v.a.addr;
+ if (h->addr.type == PF_ADDR_ADDRMASK)
+ for (i = 0; i < 4; i++)
+ n->addr32[i] = n->addr32[i] & m->addr32[i];
+}
+
+int
+check_netmask(struct node_host *h, sa_family_t af)
+{
+ struct node_host *n = NULL;
+ struct pf_addr *m;
+
+ for (n = h; n != NULL; n = n->next) {
+ if (h->addr.type == PF_ADDR_TABLE)
+ continue;
+ m = &h->addr.v.a.mask;
+ /* fix up netmask for dynaddr */
+ if (af == AF_INET && h->addr.type == PF_ADDR_DYNIFTL &&
+ unmask(m, AF_INET6) > 32)
+ set_ipmask(n, 32);
+ /* netmasks > 32 bit are invalid on v4 */
+ if (af == AF_INET &&
+ (m->addr32[1] || m->addr32[2] || m->addr32[3])) {
+ fprintf(stderr, "netmask %u invalid for IPv4 address\n",
+ unmask(m, AF_INET6));
+ return (1);
+ }
+ }
+ return (0);
+}
+
+/* interface lookup routines */
+
+struct node_host *iftab;
+
+void
+ifa_load(void)
+{
+ struct ifaddrs *ifap, *ifa;
+ struct node_host *n = NULL, *h = NULL;
+
+ if (getifaddrs(&ifap) < 0)
+ err(1, "getifaddrs");
+
+ for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
+ if (!(ifa->ifa_addr->sa_family == AF_INET ||
+ ifa->ifa_addr->sa_family == AF_INET6 ||
+ ifa->ifa_addr->sa_family == AF_LINK))
+ continue;
+ n = calloc(1, sizeof(struct node_host));
+ if (n == NULL)
+ err(1, "address: calloc");
+ n->af = ifa->ifa_addr->sa_family;
+ n->ifa_flags = ifa->ifa_flags;
+#ifdef __KAME__
+ if (n->af == AF_INET6 &&
+ IN6_IS_ADDR_LINKLOCAL(&((struct sockaddr_in6 *)
+ ifa->ifa_addr)->sin6_addr) &&
+ ((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_scope_id ==
+ 0) {
+ struct sockaddr_in6 *sin6;
+
+ sin6 = (struct sockaddr_in6 *)ifa->ifa_addr;
+ sin6->sin6_scope_id = sin6->sin6_addr.s6_addr[2] << 8 |
+ sin6->sin6_addr.s6_addr[3];
+ sin6->sin6_addr.s6_addr[2] = 0;
+ sin6->sin6_addr.s6_addr[3] = 0;
+ }
+#endif
+ n->ifindex = 0;
+ if (n->af == AF_INET) {
+ memcpy(&n->addr.v.a.addr, &((struct sockaddr_in *)
+ ifa->ifa_addr)->sin_addr.s_addr,
+ sizeof(struct in_addr));
+ memcpy(&n->addr.v.a.mask, &((struct sockaddr_in *)
+ ifa->ifa_netmask)->sin_addr.s_addr,
+ sizeof(struct in_addr));
+ if (ifa->ifa_broadaddr != NULL)
+ memcpy(&n->bcast, &((struct sockaddr_in *)
+ ifa->ifa_broadaddr)->sin_addr.s_addr,
+ sizeof(struct in_addr));
+ if (ifa->ifa_dstaddr != NULL)
+ memcpy(&n->peer, &((struct sockaddr_in *)
+ ifa->ifa_dstaddr)->sin_addr.s_addr,
+ sizeof(struct in_addr));
+ } else if (n->af == AF_INET6) {
+ memcpy(&n->addr.v.a.addr, &((struct sockaddr_in6 *)
+ ifa->ifa_addr)->sin6_addr.s6_addr,
+ sizeof(struct in6_addr));
+ memcpy(&n->addr.v.a.mask, &((struct sockaddr_in6 *)
+ ifa->ifa_netmask)->sin6_addr.s6_addr,
+ sizeof(struct in6_addr));
+ if (ifa->ifa_broadaddr != NULL)
+ memcpy(&n->bcast, &((struct sockaddr_in6 *)
+ ifa->ifa_broadaddr)->sin6_addr.s6_addr,
+ sizeof(struct in6_addr));
+ if (ifa->ifa_dstaddr != NULL)
+ memcpy(&n->peer, &((struct sockaddr_in6 *)
+ ifa->ifa_dstaddr)->sin6_addr.s6_addr,
+ sizeof(struct in6_addr));
+ n->ifindex = ((struct sockaddr_in6 *)
+ ifa->ifa_addr)->sin6_scope_id;
+ }
+ if ((n->ifname = strdup(ifa->ifa_name)) == NULL)
+ err(1, "ifa_load: strdup");
+ n->next = NULL;
+ n->tail = n;
+ if (h == NULL)
+ h = n;
+ else {
+ h->tail->next = n;
+ h->tail = n;
+ }
+ }
+
+ iftab = h;
+ freeifaddrs(ifap);
+}
+
+int
+get_socket_domain(void)
+{
+ int sdom;
+
+ sdom = AF_UNSPEC;
+#ifdef WITH_INET6
+ if (sdom == AF_UNSPEC && feature_present("inet6"))
+ sdom = AF_INET6;
+#endif
+#ifdef WITH_INET
+ if (sdom == AF_UNSPEC && feature_present("inet"))
+ sdom = AF_INET;
+#endif
+ if (sdom == AF_UNSPEC)
+ sdom = AF_LINK;
+
+ return (sdom);
+}
+
+struct node_host *
+ifa_exists(const char *ifa_name)
+{
+ struct node_host *n;
+ struct ifgroupreq ifgr;
+ int s;
+
+ if (iftab == NULL)
+ ifa_load();
+
+ /* check wether this is a group */
+ if ((s = socket(get_socket_domain(), SOCK_DGRAM, 0)) == -1)
+ err(1, "socket");
+ bzero(&ifgr, sizeof(ifgr));
+ strlcpy(ifgr.ifgr_name, ifa_name, sizeof(ifgr.ifgr_name));
+ if (ioctl(s, SIOCGIFGMEMB, (caddr_t)&ifgr) == 0) {
+ /* fake a node_host */
+ if ((n = calloc(1, sizeof(*n))) == NULL)
+ err(1, "calloc");
+ if ((n->ifname = strdup(ifa_name)) == NULL)
+ err(1, "strdup");
+ close(s);
+ return (n);
+ }
+ close(s);
+
+ for (n = iftab; n; n = n->next) {
+ if (n->af == AF_LINK && !strncmp(n->ifname, ifa_name, IFNAMSIZ))
+ return (n);
+ }
+
+ return (NULL);
+}
+
+struct node_host *
+ifa_grouplookup(const char *ifa_name, int flags)
+{
+ struct ifg_req *ifg;
+ struct ifgroupreq ifgr;
+ int s, len;
+ struct node_host *n, *h = NULL;
+
+ if ((s = socket(get_socket_domain(), SOCK_DGRAM, 0)) == -1)
+ err(1, "socket");
+ bzero(&ifgr, sizeof(ifgr));
+ strlcpy(ifgr.ifgr_name, ifa_name, sizeof(ifgr.ifgr_name));
+ if (ioctl(s, SIOCGIFGMEMB, (caddr_t)&ifgr) == -1) {
+ close(s);
+ return (NULL);
+ }
+
+ len = ifgr.ifgr_len;
+ if ((ifgr.ifgr_groups = calloc(1, len)) == NULL)
+ err(1, "calloc");
+ if (ioctl(s, SIOCGIFGMEMB, (caddr_t)&ifgr) == -1)
+ err(1, "SIOCGIFGMEMB");
+
+ for (ifg = ifgr.ifgr_groups; ifg && len >= sizeof(struct ifg_req);
+ ifg++) {
+ len -= sizeof(struct ifg_req);
+ if ((n = ifa_lookup(ifg->ifgrq_member, flags)) == NULL)
+ continue;
+ if (h == NULL)
+ h = n;
+ else {
+ h->tail->next = n;
+ h->tail = n->tail;
+ }
+ }
+ free(ifgr.ifgr_groups);
+ close(s);
+
+ return (h);
+}
+
+struct node_host *
+ifa_lookup(const char *ifa_name, int flags)
+{
+ struct node_host *p = NULL, *h = NULL, *n = NULL;
+ int got4 = 0, got6 = 0;
+ const char *last_if = NULL;
+
+ if ((h = ifa_grouplookup(ifa_name, flags)) != NULL)
+ return (h);
+
+ if (!strncmp(ifa_name, "self", IFNAMSIZ))
+ ifa_name = NULL;
+
+ if (iftab == NULL)
+ ifa_load();
+
+ for (p = iftab; p; p = p->next) {
+ if (ifa_skip_if(ifa_name, p))
+ continue;
+ if ((flags & PFI_AFLAG_BROADCAST) && p->af != AF_INET)
+ continue;
+ if ((flags & PFI_AFLAG_BROADCAST) &&
+ !(p->ifa_flags & IFF_BROADCAST))
+ continue;
+ if ((flags & PFI_AFLAG_PEER) &&
+ !(p->ifa_flags & IFF_POINTOPOINT))
+ continue;
+ if ((flags & PFI_AFLAG_NETWORK) && p->ifindex > 0)
+ continue;
+ if (last_if == NULL || strcmp(last_if, p->ifname))
+ got4 = got6 = 0;
+ last_if = p->ifname;
+ if ((flags & PFI_AFLAG_NOALIAS) && p->af == AF_INET && got4)
+ continue;
+ if ((flags & PFI_AFLAG_NOALIAS) && p->af == AF_INET6 && got6)
+ continue;
+ if (p->af == AF_INET)
+ got4 = 1;
+ else
+ got6 = 1;
+ n = calloc(1, sizeof(struct node_host));
+ if (n == NULL)
+ err(1, "address: calloc");
+ n->af = p->af;
+ if (flags & PFI_AFLAG_BROADCAST)
+ memcpy(&n->addr.v.a.addr, &p->bcast,
+ sizeof(struct pf_addr));
+ else if (flags & PFI_AFLAG_PEER)
+ memcpy(&n->addr.v.a.addr, &p->peer,
+ sizeof(struct pf_addr));
+ else
+ memcpy(&n->addr.v.a.addr, &p->addr.v.a.addr,
+ sizeof(struct pf_addr));
+ if (flags & PFI_AFLAG_NETWORK)
+ set_ipmask(n, unmask(&p->addr.v.a.mask, n->af));
+ else {
+ if (n->af == AF_INET) {
+ if (p->ifa_flags & IFF_LOOPBACK &&
+ p->ifa_flags & IFF_LINK1)
+ memcpy(&n->addr.v.a.mask,
+ &p->addr.v.a.mask,
+ sizeof(struct pf_addr));
+ else
+ set_ipmask(n, 32);
+ } else
+ set_ipmask(n, 128);
+ }
+ n->ifindex = p->ifindex;
+
+ n->next = NULL;
+ n->tail = n;
+ if (h == NULL)
+ h = n;
+ else {
+ h->tail->next = n;
+ h->tail = n;
+ }
+ }
+ return (h);
+}
+
+int
+ifa_skip_if(const char *filter, struct node_host *p)
+{
+ int n;
+
+ if (p->af != AF_INET && p->af != AF_INET6)
+ return (1);
+ if (filter == NULL || !*filter)
+ return (0);
+ if (!strcmp(p->ifname, filter))
+ return (0); /* exact match */
+ n = strlen(filter);
+ if (n < 1 || n >= IFNAMSIZ)
+ return (1); /* sanity check */
+ if (filter[n-1] >= '0' && filter[n-1] <= '9')
+ return (1); /* only do exact match in that case */
+ if (strncmp(p->ifname, filter, n))
+ return (1); /* prefix doesn't match */
+ return (p->ifname[n] < '0' || p->ifname[n] > '9');
+}
+
+
+struct node_host *
+host(const char *s)
+{
+ struct node_host *h = NULL;
+ int mask, v4mask, v6mask, cont = 1;
+ char *p, *q, *ps;
+
+ if ((p = strrchr(s, '/')) != NULL) {
+ mask = strtol(p+1, &q, 0);
+ if (!q || *q || mask > 128 || q == (p+1)) {
+ fprintf(stderr, "invalid netmask '%s'\n", p);
+ return (NULL);
+ }
+ if ((ps = malloc(strlen(s) - strlen(p) + 1)) == NULL)
+ err(1, "host: malloc");
+ strlcpy(ps, s, strlen(s) - strlen(p) + 1);
+ v4mask = v6mask = mask;
+ } else {
+ if ((ps = strdup(s)) == NULL)
+ err(1, "host: strdup");
+ v4mask = 32;
+ v6mask = 128;
+ mask = -1;
+ }
+
+ /* interface with this name exists? */
+ if (cont && (h = host_if(ps, mask)) != NULL)
+ cont = 0;
+
+ /* IPv4 address? */
+ if (cont && (h = host_v4(s, mask)) != NULL)
+ cont = 0;
+
+ /* IPv6 address? */
+ if (cont && (h = host_v6(ps, v6mask)) != NULL)
+ cont = 0;
+
+ /* dns lookup */
+ if (cont && (h = host_dns(ps, v4mask, v6mask)) != NULL)
+ cont = 0;
+ free(ps);
+
+ if (h == NULL || cont == 1) {
+ fprintf(stderr, "no IP address found for %s\n", s);
+ return (NULL);
+ }
+ return (h);
+}
+
+struct node_host *
+host_if(const char *s, int mask)
+{
+ struct node_host *n, *h = NULL;
+ char *p, *ps;
+ int flags = 0;
+
+ if ((ps = strdup(s)) == NULL)
+ err(1, "host_if: strdup");
+ while ((p = strrchr(ps, ':')) != NULL) {
+ if (!strcmp(p+1, "network"))
+ flags |= PFI_AFLAG_NETWORK;
+ else if (!strcmp(p+1, "broadcast"))
+ flags |= PFI_AFLAG_BROADCAST;
+ else if (!strcmp(p+1, "peer"))
+ flags |= PFI_AFLAG_PEER;
+ else if (!strcmp(p+1, "0"))
+ flags |= PFI_AFLAG_NOALIAS;
+ else {
+ free(ps);
+ return (NULL);
+ }
+ *p = '\0';
+ }
+ if (flags & (flags - 1) & PFI_AFLAG_MODEMASK) { /* Yep! */
+ fprintf(stderr, "illegal combination of interface modifiers\n");
+ free(ps);
+ return (NULL);
+ }
+ if ((flags & (PFI_AFLAG_NETWORK|PFI_AFLAG_BROADCAST)) && mask > -1) {
+ fprintf(stderr, "network or broadcast lookup, but "
+ "extra netmask given\n");
+ free(ps);
+ return (NULL);
+ }
+ if (ifa_exists(ps) || !strncmp(ps, "self", IFNAMSIZ)) {
+ /* interface with this name exists */
+ h = ifa_lookup(ps, flags);
+ for (n = h; n != NULL && mask > -1; n = n->next)
+ set_ipmask(n, mask);
+ }
+
+ free(ps);
+ return (h);
+}
+
+struct node_host *
+host_v4(const char *s, int mask)
+{
+ struct node_host *h = NULL;
+ struct in_addr ina;
+ int bits = 32;
+
+ memset(&ina, 0, sizeof(struct in_addr));
+ if (strrchr(s, '/') != NULL) {
+ if ((bits = inet_net_pton(AF_INET, s, &ina, sizeof(ina))) == -1)
+ return (NULL);
+ } else {
+ if (inet_pton(AF_INET, s, &ina) != 1)
+ return (NULL);
+ }
+
+ h = calloc(1, sizeof(struct node_host));
+ if (h == NULL)
+ err(1, "address: calloc");
+ h->ifname = NULL;
+ h->af = AF_INET;
+ h->addr.v.a.addr.addr32[0] = ina.s_addr;
+ set_ipmask(h, bits);
+ h->next = NULL;
+ h->tail = h;
+
+ return (h);
+}
+
+struct node_host *
+host_v6(const char *s, int mask)
+{
+ struct addrinfo hints, *res;
+ struct node_host *h = NULL;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_INET6;
+ hints.ai_socktype = SOCK_DGRAM; /*dummy*/
+ hints.ai_flags = AI_NUMERICHOST;
+ if (getaddrinfo(s, "0", &hints, &res) == 0) {
+ h = calloc(1, sizeof(struct node_host));
+ if (h == NULL)
+ err(1, "address: calloc");
+ h->ifname = NULL;
+ h->af = AF_INET6;
+ memcpy(&h->addr.v.a.addr,
+ &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr,
+ sizeof(h->addr.v.a.addr));
+ h->ifindex =
+ ((struct sockaddr_in6 *)res->ai_addr)->sin6_scope_id;
+ set_ipmask(h, mask);
+ freeaddrinfo(res);
+ h->next = NULL;
+ h->tail = h;
+ }
+
+ return (h);
+}
+
+struct node_host *
+host_dns(const char *s, int v4mask, int v6mask)
+{
+ struct addrinfo hints, *res0, *res;
+ struct node_host *n, *h = NULL;
+ int error, noalias = 0;
+ int got4 = 0, got6 = 0;
+ char *p, *ps;
+
+ if ((ps = strdup(s)) == NULL)
+ err(1, "host_dns: strdup");
+ if ((p = strrchr(ps, ':')) != NULL && !strcmp(p, ":0")) {
+ noalias = 1;
+ *p = '\0';
+ }
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM; /* DUMMY */
+ error = getaddrinfo(ps, NULL, &hints, &res0);
+ if (error) {
+ free(ps);
+ return (h);
+ }
+
+ for (res = res0; res; res = res->ai_next) {
+ if (res->ai_family != AF_INET &&
+ res->ai_family != AF_INET6)
+ continue;
+ if (noalias) {
+ if (res->ai_family == AF_INET) {
+ if (got4)
+ continue;
+ got4 = 1;
+ } else {
+ if (got6)
+ continue;
+ got6 = 1;
+ }
+ }
+ n = calloc(1, sizeof(struct node_host));
+ if (n == NULL)
+ err(1, "host_dns: calloc");
+ n->ifname = NULL;
+ n->af = res->ai_family;
+ if (res->ai_family == AF_INET) {
+ memcpy(&n->addr.v.a.addr,
+ &((struct sockaddr_in *)
+ res->ai_addr)->sin_addr.s_addr,
+ sizeof(struct in_addr));
+ set_ipmask(n, v4mask);
+ } else {
+ memcpy(&n->addr.v.a.addr,
+ &((struct sockaddr_in6 *)
+ res->ai_addr)->sin6_addr.s6_addr,
+ sizeof(struct in6_addr));
+ n->ifindex =
+ ((struct sockaddr_in6 *)
+ res->ai_addr)->sin6_scope_id;
+ set_ipmask(n, v6mask);
+ }
+ n->next = NULL;
+ n->tail = n;
+ if (h == NULL)
+ h = n;
+ else {
+ h->tail->next = n;
+ h->tail = n;
+ }
+ }
+ freeaddrinfo(res0);
+ free(ps);
+
+ return (h);
+}
+
+/*
+ * convert a hostname to a list of addresses and put them in the given buffer.
+ * test:
+ * if set to 1, only simple addresses are accepted (no netblock, no "!").
+ */
+int
+append_addr(struct pfr_buffer *b, char *s, int test)
+{
+ char *r;
+ struct node_host *h, *n;
+ int rv, not = 0;
+
+ for (r = s; *r == '!'; r++)
+ not = !not;
+ if ((n = host(r)) == NULL) {
+ errno = 0;
+ return (-1);
+ }
+ rv = append_addr_host(b, n, test, not);
+ do {
+ h = n;
+ n = n->next;
+ free(h);
+ } while (n != NULL);
+ return (rv);
+}
+
+/*
+ * same as previous function, but with a pre-parsed input and the ability
+ * to "negate" the result. Does not free the node_host list.
+ * not:
+ * setting it to 1 is equivalent to adding "!" in front of parameter s.
+ */
+int
+append_addr_host(struct pfr_buffer *b, struct node_host *n, int test, int not)
+{
+ int bits;
+ struct pfr_addr addr;
+
+ do {
+ bzero(&addr, sizeof(addr));
+ addr.pfra_not = n->not ^ not;
+ addr.pfra_af = n->af;
+ addr.pfra_net = unmask(&n->addr.v.a.mask, n->af);
+ switch (n->af) {
+ case AF_INET:
+ addr.pfra_ip4addr.s_addr = n->addr.v.a.addr.addr32[0];
+ bits = 32;
+ break;
+ case AF_INET6:
+ memcpy(&addr.pfra_ip6addr, &n->addr.v.a.addr.v6,
+ sizeof(struct in6_addr));
+ bits = 128;
+ break;
+ default:
+ errno = EINVAL;
+ return (-1);
+ }
+ if ((test && (not || addr.pfra_net != bits)) ||
+ addr.pfra_net > bits) {
+ errno = EINVAL;
+ return (-1);
+ }
+ if (pfr_buf_add(b, &addr))
+ return (-1);
+ } while ((n = n->next) != NULL);
+
+ return (0);
+}
+
+int
+pfctl_add_trans(struct pfr_buffer *buf, int rs_num, const char *anchor)
+{
+ struct pfioc_trans_e trans;
+
+ bzero(&trans, sizeof(trans));
+ trans.rs_num = rs_num;
+ if (strlcpy(trans.anchor, anchor,
+ sizeof(trans.anchor)) >= sizeof(trans.anchor))
+ errx(1, "pfctl_add_trans: strlcpy");
+
+ return pfr_buf_add(buf, &trans);
+}
+
+u_int32_t
+pfctl_get_ticket(struct pfr_buffer *buf, int rs_num, const char *anchor)
+{
+ struct pfioc_trans_e *p;
+
+ PFRB_FOREACH(p, buf)
+ if (rs_num == p->rs_num && !strcmp(anchor, p->anchor))
+ return (p->ticket);
+ errx(1, "pfctl_get_ticket: assertion failed");
+}
+
+int
+pfctl_trans(int dev, struct pfr_buffer *buf, u_long cmd, int from)
+{
+ struct pfioc_trans trans;
+
+ bzero(&trans, sizeof(trans));
+ trans.size = buf->pfrb_size - from;
+ trans.esize = sizeof(struct pfioc_trans_e);
+ trans.array = ((struct pfioc_trans_e *)buf->pfrb_caddr) + from;
+ return ioctl(dev, cmd, &trans);
+}
diff --git a/sbin/pfctl/pfctl_parser.h b/sbin/pfctl/pfctl_parser.h
new file mode 100644
index 0000000..5c909e9
--- /dev/null
+++ b/sbin/pfctl/pfctl_parser.h
@@ -0,0 +1,306 @@
+/* $OpenBSD: pfctl_parser.h,v 1.86 2006/10/31 23:46:25 mcbride Exp $ */
+
+/*
+ * Copyright (c) 2001 Daniel Hartmeier
+ * All rights reserved.
+ *
+ * Redistribution and use 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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
+ * COPYRIGHT HOLDERS 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _PFCTL_PARSER_H_
+#define _PFCTL_PARSER_H_
+
+#define PF_OSFP_FILE "/etc/pf.os"
+
+#define PF_OPT_DISABLE 0x0001
+#define PF_OPT_ENABLE 0x0002
+#define PF_OPT_VERBOSE 0x0004
+#define PF_OPT_NOACTION 0x0008
+#define PF_OPT_QUIET 0x0010
+#define PF_OPT_CLRRULECTRS 0x0020
+#define PF_OPT_USEDNS 0x0040
+#define PF_OPT_VERBOSE2 0x0080
+#define PF_OPT_DUMMYACTION 0x0100
+#define PF_OPT_DEBUG 0x0200
+#define PF_OPT_SHOWALL 0x0400
+#define PF_OPT_OPTIMIZE 0x0800
+#define PF_OPT_NUMERIC 0x1000
+#define PF_OPT_MERGE 0x2000
+#define PF_OPT_RECURSE 0x4000
+
+#define PF_TH_ALL 0xFF
+
+#define PF_NAT_PROXY_PORT_LOW 50001
+#define PF_NAT_PROXY_PORT_HIGH 65535
+
+#define PF_OPTIMIZE_BASIC 0x0001
+#define PF_OPTIMIZE_PROFILE 0x0002
+
+#define FCNT_NAMES { \
+ "searches", \
+ "inserts", \
+ "removals", \
+ NULL \
+}
+
+struct pfr_buffer; /* forward definition */
+
+
+struct pfctl {
+ int dev;
+ int opts;
+ int optimize;
+ int loadopt;
+ int asd; /* anchor stack depth */
+ int bn; /* brace number */
+ int brace;
+ int tdirty; /* kernel dirty */
+#define PFCTL_ANCHOR_STACK_DEPTH 64
+ struct pf_anchor *astack[PFCTL_ANCHOR_STACK_DEPTH];
+ struct pfioc_pooladdr paddr;
+ struct pfioc_altq *paltq;
+ struct pfioc_queue *pqueue;
+ struct pfr_buffer *trans;
+ struct pf_anchor *anchor, *alast;
+ const char *ruleset;
+
+ /* 'set foo' options */
+ u_int32_t timeout[PFTM_MAX];
+ u_int32_t limit[PF_LIMIT_MAX];
+ u_int32_t debug;
+ u_int32_t hostid;
+ char *ifname;
+
+ u_int8_t timeout_set[PFTM_MAX];
+ u_int8_t limit_set[PF_LIMIT_MAX];
+ u_int8_t debug_set;
+ u_int8_t hostid_set;
+ u_int8_t ifname_set;
+};
+
+struct node_if {
+ char ifname[IFNAMSIZ];
+ u_int8_t not;
+ u_int8_t dynamic; /* antispoof */
+ u_int ifa_flags;
+ struct node_if *next;
+ struct node_if *tail;
+};
+
+struct node_host {
+ struct pf_addr_wrap addr;
+ struct pf_addr bcast;
+ struct pf_addr peer;
+ sa_family_t af;
+ u_int8_t not;
+ u_int32_t ifindex; /* link-local IPv6 addrs */
+ char *ifname;
+ u_int ifa_flags;
+ struct node_host *next;
+ struct node_host *tail;
+};
+
+struct node_os {
+ char *os;
+ pf_osfp_t fingerprint;
+ struct node_os *next;
+ struct node_os *tail;
+};
+
+struct node_queue_bw {
+ u_int32_t bw_absolute;
+ u_int16_t bw_percent;
+};
+
+struct node_hfsc_sc {
+ struct node_queue_bw m1; /* slope of 1st segment; bps */
+ u_int d; /* x-projection of m1; msec */
+ struct node_queue_bw m2; /* slope of 2nd segment; bps */
+ u_int8_t used;
+};
+
+struct node_hfsc_opts {
+ struct node_hfsc_sc realtime;
+ struct node_hfsc_sc linkshare;
+ struct node_hfsc_sc upperlimit;
+ int flags;
+};
+
+struct node_queue_opt {
+ int qtype;
+ union {
+ struct cbq_opts cbq_opts;
+ struct priq_opts priq_opts;
+ struct node_hfsc_opts hfsc_opts;
+ } data;
+};
+
+#ifdef __FreeBSD__
+/*
+ * XXX
+ * Absolutely this is not correct location to define this.
+ * Should we use an another sperate header file?
+ */
+#define SIMPLEQ_HEAD STAILQ_HEAD
+#define SIMPLEQ_HEAD_INITIALIZER STAILQ_HEAD_INITIALIZER
+#define SIMPLEQ_ENTRY STAILQ_ENTRY
+#define SIMPLEQ_FIRST STAILQ_FIRST
+#define SIMPLEQ_END(head) NULL
+#define SIMPLEQ_EMPTY STAILQ_EMPTY
+#define SIMPLEQ_NEXT STAILQ_NEXT
+/*#define SIMPLEQ_FOREACH STAILQ_FOREACH*/
+#define SIMPLEQ_FOREACH(var, head, field) \
+ for((var) = SIMPLEQ_FIRST(head); \
+ (var) != SIMPLEQ_END(head); \
+ (var) = SIMPLEQ_NEXT(var, field))
+#define SIMPLEQ_INIT STAILQ_INIT
+#define SIMPLEQ_INSERT_HEAD STAILQ_INSERT_HEAD
+#define SIMPLEQ_INSERT_TAIL STAILQ_INSERT_TAIL
+#define SIMPLEQ_INSERT_AFTER STAILQ_INSERT_AFTER
+#define SIMPLEQ_REMOVE_HEAD STAILQ_REMOVE_HEAD
+#endif
+SIMPLEQ_HEAD(node_tinithead, node_tinit);
+struct node_tinit { /* table initializer */
+ SIMPLEQ_ENTRY(node_tinit) entries;
+ struct node_host *host;
+ char *file;
+};
+
+
+/* optimizer created tables */
+struct pf_opt_tbl {
+ char pt_name[PF_TABLE_NAME_SIZE];
+ int pt_rulecount;
+ int pt_generated;
+ struct node_tinithead pt_nodes;
+ struct pfr_buffer *pt_buf;
+};
+#define PF_OPT_TABLE_PREFIX "__automatic_"
+
+/* optimizer pf_rule container */
+struct pf_opt_rule {
+ struct pf_rule por_rule;
+ struct pf_opt_tbl *por_src_tbl;
+ struct pf_opt_tbl *por_dst_tbl;
+ u_int64_t por_profile_count;
+ TAILQ_ENTRY(pf_opt_rule) por_entry;
+ TAILQ_ENTRY(pf_opt_rule) por_skip_entry[PF_SKIP_COUNT];
+};
+
+TAILQ_HEAD(pf_opt_queue, pf_opt_rule);
+
+int pfctl_rules(int, char *, int, int, char *, struct pfr_buffer *);
+int pfctl_optimize_ruleset(struct pfctl *, struct pf_ruleset *);
+
+int pfctl_add_rule(struct pfctl *, struct pf_rule *, const char *);
+int pfctl_add_altq(struct pfctl *, struct pf_altq *);
+int pfctl_add_pool(struct pfctl *, struct pf_pool *, sa_family_t);
+void pfctl_move_pool(struct pf_pool *, struct pf_pool *);
+void pfctl_clear_pool(struct pf_pool *);
+
+int pfctl_set_timeout(struct pfctl *, const char *, int, int);
+int pfctl_set_optimization(struct pfctl *, const char *);
+int pfctl_set_limit(struct pfctl *, const char *, unsigned int);
+int pfctl_set_logif(struct pfctl *, char *);
+int pfctl_set_hostid(struct pfctl *, u_int32_t);
+int pfctl_set_debug(struct pfctl *, char *);
+int pfctl_set_interface_flags(struct pfctl *, char *, int, int);
+
+int parse_config(char *, struct pfctl *);
+int parse_flags(char *);
+int pfctl_load_anchors(int, struct pfctl *, struct pfr_buffer *);
+
+void print_pool(struct pf_pool *, u_int16_t, u_int16_t, sa_family_t, int);
+void print_src_node(struct pf_src_node *, int);
+void print_rule(struct pf_rule *, const char *, int, int);
+void print_tabledef(const char *, int, int, struct node_tinithead *);
+void print_status(struct pf_status *, int);
+
+int eval_pfaltq(struct pfctl *, struct pf_altq *, struct node_queue_bw *,
+ struct node_queue_opt *);
+int eval_pfqueue(struct pfctl *, struct pf_altq *, struct node_queue_bw *,
+ struct node_queue_opt *);
+
+void print_altq(const struct pf_altq *, unsigned, struct node_queue_bw *,
+ struct node_queue_opt *);
+void print_queue(const struct pf_altq *, unsigned, struct node_queue_bw *,
+ int, struct node_queue_opt *);
+
+int pfctl_define_table(char *, int, int, const char *, struct pfr_buffer *,
+ u_int32_t);
+
+void pfctl_clear_fingerprints(int, int);
+int pfctl_file_fingerprints(int, int, const char *);
+pf_osfp_t pfctl_get_fingerprint(const char *);
+int pfctl_load_fingerprints(int, int);
+char *pfctl_lookup_fingerprint(pf_osfp_t, char *, size_t);
+void pfctl_show_fingerprints(int);
+
+
+struct icmptypeent {
+ const char *name;
+ u_int8_t type;
+};
+
+struct icmpcodeent {
+ const char *name;
+ u_int8_t type;
+ u_int8_t code;
+};
+
+const struct icmptypeent *geticmptypebynumber(u_int8_t, u_int8_t);
+const struct icmptypeent *geticmptypebyname(char *, u_int8_t);
+const struct icmpcodeent *geticmpcodebynumber(u_int8_t, u_int8_t, u_int8_t);
+const struct icmpcodeent *geticmpcodebyname(u_long, char *, u_int8_t);
+
+struct pf_timeout {
+ const char *name;
+ int timeout;
+};
+
+#define PFCTL_FLAG_FILTER 0x02
+#define PFCTL_FLAG_NAT 0x04
+#define PFCTL_FLAG_OPTION 0x08
+#define PFCTL_FLAG_ALTQ 0x10
+#define PFCTL_FLAG_TABLE 0x20
+
+extern const struct pf_timeout pf_timeouts[];
+
+void set_ipmask(struct node_host *, u_int8_t);
+int check_netmask(struct node_host *, sa_family_t);
+int unmask(struct pf_addr *, sa_family_t);
+void ifa_load(void);
+int get_socket_domain(void);
+struct node_host *ifa_exists(const char *);
+struct node_host *ifa_lookup(const char *, int);
+struct node_host *host(const char *);
+
+int append_addr(struct pfr_buffer *, char *, int);
+int append_addr_host(struct pfr_buffer *,
+ struct node_host *, int, int);
+
+#endif /* _PFCTL_PARSER_H_ */
diff --git a/sbin/pfctl/pfctl_qstats.c b/sbin/pfctl/pfctl_qstats.c
new file mode 100644
index 0000000..0921d47
--- /dev/null
+++ b/sbin/pfctl/pfctl_qstats.c
@@ -0,0 +1,449 @@
+/* $OpenBSD: pfctl_qstats.c,v 1.30 2004/04/27 21:47:32 kjc Exp $ */
+
+/*
+ * Copyright (c) Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <net/pfvar.h>
+#include <arpa/inet.h>
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <net/altq/altq.h>
+#include <net/altq/altq_cbq.h>
+#include <net/altq/altq_priq.h>
+#include <net/altq/altq_hfsc.h>
+
+#include "pfctl.h"
+#include "pfctl_parser.h"
+
+union class_stats {
+ class_stats_t cbq_stats;
+ struct priq_classstats priq_stats;
+ struct hfsc_classstats hfsc_stats;
+};
+
+#define AVGN_MAX 8
+#define STAT_INTERVAL 5
+
+struct queue_stats {
+ union class_stats data;
+ int avgn;
+ double avg_bytes;
+ double avg_packets;
+ u_int64_t prev_bytes;
+ u_int64_t prev_packets;
+};
+
+struct pf_altq_node {
+ struct pf_altq altq;
+ struct pf_altq_node *next;
+ struct pf_altq_node *children;
+ struct queue_stats qstats;
+};
+
+int pfctl_update_qstats(int, struct pf_altq_node **);
+void pfctl_insert_altq_node(struct pf_altq_node **,
+ const struct pf_altq, const struct queue_stats);
+struct pf_altq_node *pfctl_find_altq_node(struct pf_altq_node *,
+ const char *, const char *);
+void pfctl_print_altq_node(int, const struct pf_altq_node *,
+ unsigned, int);
+void print_cbqstats(struct queue_stats);
+void print_priqstats(struct queue_stats);
+void print_hfscstats(struct queue_stats);
+void pfctl_free_altq_node(struct pf_altq_node *);
+void pfctl_print_altq_nodestat(int,
+ const struct pf_altq_node *);
+
+void update_avg(struct pf_altq_node *);
+
+int
+pfctl_show_altq(int dev, const char *iface, int opts, int verbose2)
+{
+ struct pf_altq_node *root = NULL, *node;
+ int nodes, dotitle = (opts & PF_OPT_SHOWALL);
+
+#ifdef __FreeBSD__
+ if (!altqsupport)
+ return (-1);
+#endif
+
+ if ((nodes = pfctl_update_qstats(dev, &root)) < 0)
+ return (-1);
+
+ if (nodes == 0)
+ printf("No queue in use\n");
+ for (node = root; node != NULL; node = node->next) {
+ if (iface != NULL && strcmp(node->altq.ifname, iface))
+ continue;
+ if (dotitle) {
+ pfctl_print_title("ALTQ:");
+ dotitle = 0;
+ }
+ pfctl_print_altq_node(dev, node, 0, opts);
+ }
+
+ while (verbose2 && nodes > 0) {
+ printf("\n");
+ fflush(stdout);
+ sleep(STAT_INTERVAL);
+ if ((nodes = pfctl_update_qstats(dev, &root)) == -1)
+ return (-1);
+ for (node = root; node != NULL; node = node->next) {
+ if (iface != NULL && strcmp(node->altq.ifname, iface))
+ continue;
+#ifdef __FreeBSD__
+ if (node->altq.local_flags & PFALTQ_FLAG_IF_REMOVED)
+ continue;
+#endif
+ pfctl_print_altq_node(dev, node, 0, opts);
+ }
+ }
+ pfctl_free_altq_node(root);
+ return (0);
+}
+
+int
+pfctl_update_qstats(int dev, struct pf_altq_node **root)
+{
+ struct pf_altq_node *node;
+ struct pfioc_altq pa;
+ struct pfioc_qstats pq;
+ u_int32_t mnr, nr;
+ struct queue_stats qstats;
+ static u_int32_t last_ticket;
+
+ memset(&pa, 0, sizeof(pa));
+ memset(&pq, 0, sizeof(pq));
+ memset(&qstats, 0, sizeof(qstats));
+ if (ioctl(dev, DIOCGETALTQS, &pa)) {
+ warn("DIOCGETALTQS");
+ return (-1);
+ }
+
+ /* if a new set is found, start over */
+ if (pa.ticket != last_ticket && *root != NULL) {
+ pfctl_free_altq_node(*root);
+ *root = NULL;
+ }
+ last_ticket = pa.ticket;
+
+ mnr = pa.nr;
+ for (nr = 0; nr < mnr; ++nr) {
+ pa.nr = nr;
+ if (ioctl(dev, DIOCGETALTQ, &pa)) {
+ warn("DIOCGETALTQ");
+ return (-1);
+ }
+#ifdef __FreeBSD__
+ if (pa.altq.qid > 0 &&
+ !(pa.altq.local_flags & PFALTQ_FLAG_IF_REMOVED)) {
+#else
+ if (pa.altq.qid > 0) {
+#endif
+ pq.nr = nr;
+ pq.ticket = pa.ticket;
+ pq.buf = &qstats.data;
+ pq.nbytes = sizeof(qstats.data);
+ if (ioctl(dev, DIOCGETQSTATS, &pq)) {
+ warn("DIOCGETQSTATS");
+ return (-1);
+ }
+ if ((node = pfctl_find_altq_node(*root, pa.altq.qname,
+ pa.altq.ifname)) != NULL) {
+ memcpy(&node->qstats.data, &qstats.data,
+ sizeof(qstats.data));
+ update_avg(node);
+ } else {
+ pfctl_insert_altq_node(root, pa.altq, qstats);
+ }
+ }
+#ifdef __FreeBSD__
+ else if (pa.altq.local_flags & PFALTQ_FLAG_IF_REMOVED) {
+ memset(&qstats.data, 0, sizeof(qstats.data));
+ if ((node = pfctl_find_altq_node(*root, pa.altq.qname,
+ pa.altq.ifname)) != NULL) {
+ memcpy(&node->qstats.data, &qstats.data,
+ sizeof(qstats.data));
+ update_avg(node);
+ } else {
+ pfctl_insert_altq_node(root, pa.altq, qstats);
+ }
+ }
+#endif
+ }
+ return (mnr);
+}
+
+void
+pfctl_insert_altq_node(struct pf_altq_node **root,
+ const struct pf_altq altq, const struct queue_stats qstats)
+{
+ struct pf_altq_node *node;
+
+ node = calloc(1, sizeof(struct pf_altq_node));
+ if (node == NULL)
+ err(1, "pfctl_insert_altq_node: calloc");
+ memcpy(&node->altq, &altq, sizeof(struct pf_altq));
+ memcpy(&node->qstats, &qstats, sizeof(qstats));
+ node->next = node->children = NULL;
+
+ if (*root == NULL)
+ *root = node;
+ else if (!altq.parent[0]) {
+ struct pf_altq_node *prev = *root;
+
+ while (prev->next != NULL)
+ prev = prev->next;
+ prev->next = node;
+ } else {
+ struct pf_altq_node *parent;
+
+ parent = pfctl_find_altq_node(*root, altq.parent, altq.ifname);
+ if (parent == NULL)
+ errx(1, "parent %s not found", altq.parent);
+ if (parent->children == NULL)
+ parent->children = node;
+ else {
+ struct pf_altq_node *prev = parent->children;
+
+ while (prev->next != NULL)
+ prev = prev->next;
+ prev->next = node;
+ }
+ }
+ update_avg(node);
+}
+
+struct pf_altq_node *
+pfctl_find_altq_node(struct pf_altq_node *root, const char *qname,
+ const char *ifname)
+{
+ struct pf_altq_node *node, *child;
+
+ for (node = root; node != NULL; node = node->next) {
+ if (!strcmp(node->altq.qname, qname)
+ && !(strcmp(node->altq.ifname, ifname)))
+ return (node);
+ if (node->children != NULL) {
+ child = pfctl_find_altq_node(node->children, qname,
+ ifname);
+ if (child != NULL)
+ return (child);
+ }
+ }
+ return (NULL);
+}
+
+void
+pfctl_print_altq_node(int dev, const struct pf_altq_node *node,
+ unsigned int level, int opts)
+{
+ const struct pf_altq_node *child;
+
+ if (node == NULL)
+ return;
+
+ print_altq(&node->altq, level, NULL, NULL);
+
+ if (node->children != NULL) {
+ printf("{");
+ for (child = node->children; child != NULL;
+ child = child->next) {
+ printf("%s", child->altq.qname);
+ if (child->next != NULL)
+ printf(", ");
+ }
+ printf("}");
+ }
+ printf("\n");
+
+ if (opts & PF_OPT_VERBOSE)
+ pfctl_print_altq_nodestat(dev, node);
+
+ if (opts & PF_OPT_DEBUG)
+ printf(" [ qid=%u ifname=%s ifbandwidth=%s ]\n",
+ node->altq.qid, node->altq.ifname,
+ rate2str((double)(node->altq.ifbandwidth)));
+
+ for (child = node->children; child != NULL;
+ child = child->next)
+ pfctl_print_altq_node(dev, child, level + 1, opts);
+}
+
+void
+pfctl_print_altq_nodestat(int dev, const struct pf_altq_node *a)
+{
+ if (a->altq.qid == 0)
+ return;
+
+#ifdef __FreeBSD__
+ if (a->altq.local_flags & PFALTQ_FLAG_IF_REMOVED)
+ return;
+#endif
+ switch (a->altq.scheduler) {
+ case ALTQT_CBQ:
+ print_cbqstats(a->qstats);
+ break;
+ case ALTQT_PRIQ:
+ print_priqstats(a->qstats);
+ break;
+ case ALTQT_HFSC:
+ print_hfscstats(a->qstats);
+ break;
+ }
+}
+
+void
+print_cbqstats(struct queue_stats cur)
+{
+ printf(" [ pkts: %10llu bytes: %10llu "
+ "dropped pkts: %6llu bytes: %6llu ]\n",
+ (unsigned long long)cur.data.cbq_stats.xmit_cnt.packets,
+ (unsigned long long)cur.data.cbq_stats.xmit_cnt.bytes,
+ (unsigned long long)cur.data.cbq_stats.drop_cnt.packets,
+ (unsigned long long)cur.data.cbq_stats.drop_cnt.bytes);
+ printf(" [ qlength: %3d/%3d borrows: %6u suspends: %6u ]\n",
+ cur.data.cbq_stats.qcnt, cur.data.cbq_stats.qmax,
+ cur.data.cbq_stats.borrows, cur.data.cbq_stats.delays);
+
+ if (cur.avgn < 2)
+ return;
+
+ printf(" [ measured: %7.1f packets/s, %s/s ]\n",
+ cur.avg_packets / STAT_INTERVAL,
+ rate2str((8 * cur.avg_bytes) / STAT_INTERVAL));
+}
+
+void
+print_priqstats(struct queue_stats cur)
+{
+ printf(" [ pkts: %10llu bytes: %10llu "
+ "dropped pkts: %6llu bytes: %6llu ]\n",
+ (unsigned long long)cur.data.priq_stats.xmitcnt.packets,
+ (unsigned long long)cur.data.priq_stats.xmitcnt.bytes,
+ (unsigned long long)cur.data.priq_stats.dropcnt.packets,
+ (unsigned long long)cur.data.priq_stats.dropcnt.bytes);
+ printf(" [ qlength: %3d/%3d ]\n",
+ cur.data.priq_stats.qlength, cur.data.priq_stats.qlimit);
+
+ if (cur.avgn < 2)
+ return;
+
+ printf(" [ measured: %7.1f packets/s, %s/s ]\n",
+ cur.avg_packets / STAT_INTERVAL,
+ rate2str((8 * cur.avg_bytes) / STAT_INTERVAL));
+}
+
+void
+print_hfscstats(struct queue_stats cur)
+{
+ printf(" [ pkts: %10llu bytes: %10llu "
+ "dropped pkts: %6llu bytes: %6llu ]\n",
+ (unsigned long long)cur.data.hfsc_stats.xmit_cnt.packets,
+ (unsigned long long)cur.data.hfsc_stats.xmit_cnt.bytes,
+ (unsigned long long)cur.data.hfsc_stats.drop_cnt.packets,
+ (unsigned long long)cur.data.hfsc_stats.drop_cnt.bytes);
+ printf(" [ qlength: %3d/%3d ]\n",
+ cur.data.hfsc_stats.qlength, cur.data.hfsc_stats.qlimit);
+
+ if (cur.avgn < 2)
+ return;
+
+ printf(" [ measured: %7.1f packets/s, %s/s ]\n",
+ cur.avg_packets / STAT_INTERVAL,
+ rate2str((8 * cur.avg_bytes) / STAT_INTERVAL));
+}
+
+void
+pfctl_free_altq_node(struct pf_altq_node *node)
+{
+ while (node != NULL) {
+ struct pf_altq_node *prev;
+
+ if (node->children != NULL)
+ pfctl_free_altq_node(node->children);
+ prev = node;
+ node = node->next;
+ free(prev);
+ }
+}
+
+void
+update_avg(struct pf_altq_node *a)
+{
+ struct queue_stats *qs;
+ u_int64_t b, p;
+ int n;
+
+ if (a->altq.qid == 0)
+ return;
+
+ qs = &a->qstats;
+ n = qs->avgn;
+
+ switch (a->altq.scheduler) {
+ case ALTQT_CBQ:
+ b = qs->data.cbq_stats.xmit_cnt.bytes;
+ p = qs->data.cbq_stats.xmit_cnt.packets;
+ break;
+ case ALTQT_PRIQ:
+ b = qs->data.priq_stats.xmitcnt.bytes;
+ p = qs->data.priq_stats.xmitcnt.packets;
+ break;
+ case ALTQT_HFSC:
+ b = qs->data.hfsc_stats.xmit_cnt.bytes;
+ p = qs->data.hfsc_stats.xmit_cnt.packets;
+ break;
+ default:
+ b = 0;
+ p = 0;
+ break;
+ }
+
+ if (n == 0) {
+ qs->prev_bytes = b;
+ qs->prev_packets = p;
+ qs->avgn++;
+ return;
+ }
+
+ if (b >= qs->prev_bytes)
+ qs->avg_bytes = ((qs->avg_bytes * (n - 1)) +
+ (b - qs->prev_bytes)) / n;
+
+ if (p >= qs->prev_packets)
+ qs->avg_packets = ((qs->avg_packets * (n - 1)) +
+ (p - qs->prev_packets)) / n;
+
+ qs->prev_bytes = b;
+ qs->prev_packets = p;
+ if (n < AVGN_MAX)
+ qs->avgn++;
+}
diff --git a/sbin/pfctl/pfctl_radix.c b/sbin/pfctl/pfctl_radix.c
new file mode 100644
index 0000000..38f16c4
--- /dev/null
+++ b/sbin/pfctl/pfctl_radix.c
@@ -0,0 +1,585 @@
+/* $OpenBSD: pfctl_radix.c,v 1.27 2005/05/21 21:03:58 henning Exp $ */
+
+/*
+ * Copyright (c) 2002 Cedric Berger
+ * All rights reserved.
+ *
+ * Redistribution and use 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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
+ * COPYRIGHT HOLDERS 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 <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <net/if.h>
+#include <net/pfvar.h>
+
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <err.h>
+
+#include "pfctl.h"
+
+#define BUF_SIZE 256
+
+extern int dev;
+
+static int pfr_next_token(char buf[], FILE *);
+
+
+int
+pfr_clr_tables(struct pfr_table *filter, int *ndel, int flags)
+{
+ struct pfioc_table io;
+
+ bzero(&io, sizeof io);
+ io.pfrio_flags = flags;
+ if (filter != NULL)
+ io.pfrio_table = *filter;
+ if (ioctl(dev, DIOCRCLRTABLES, &io))
+ return (-1);
+ if (ndel != NULL)
+ *ndel = io.pfrio_ndel;
+ return (0);
+}
+
+int
+pfr_add_tables(struct pfr_table *tbl, int size, int *nadd, int flags)
+{
+ struct pfioc_table io;
+
+ if (size < 0 || (size && tbl == NULL)) {
+ errno = EINVAL;
+ return (-1);
+ }
+ bzero(&io, sizeof io);
+ io.pfrio_flags = flags;
+ io.pfrio_buffer = tbl;
+ io.pfrio_esize = sizeof(*tbl);
+ io.pfrio_size = size;
+ if (ioctl(dev, DIOCRADDTABLES, &io))
+ return (-1);
+ if (nadd != NULL)
+ *nadd = io.pfrio_nadd;
+ return (0);
+}
+
+int
+pfr_del_tables(struct pfr_table *tbl, int size, int *ndel, int flags)
+{
+ struct pfioc_table io;
+
+ if (size < 0 || (size && tbl == NULL)) {
+ errno = EINVAL;
+ return (-1);
+ }
+ bzero(&io, sizeof io);
+ io.pfrio_flags = flags;
+ io.pfrio_buffer = tbl;
+ io.pfrio_esize = sizeof(*tbl);
+ io.pfrio_size = size;
+ if (ioctl(dev, DIOCRDELTABLES, &io))
+ return (-1);
+ if (ndel != NULL)
+ *ndel = io.pfrio_ndel;
+ return (0);
+}
+
+int
+pfr_get_tables(struct pfr_table *filter, struct pfr_table *tbl, int *size,
+ int flags)
+{
+ struct pfioc_table io;
+
+ if (size == NULL || *size < 0 || (*size && tbl == NULL)) {
+ errno = EINVAL;
+ return (-1);
+ }
+ bzero(&io, sizeof io);
+ io.pfrio_flags = flags;
+ if (filter != NULL)
+ io.pfrio_table = *filter;
+ io.pfrio_buffer = tbl;
+ io.pfrio_esize = sizeof(*tbl);
+ io.pfrio_size = *size;
+ if (ioctl(dev, DIOCRGETTABLES, &io))
+ return (-1);
+ *size = io.pfrio_size;
+ return (0);
+}
+
+int
+pfr_get_tstats(struct pfr_table *filter, struct pfr_tstats *tbl, int *size,
+ int flags)
+{
+ struct pfioc_table io;
+
+ if (size == NULL || *size < 0 || (*size && tbl == NULL)) {
+ errno = EINVAL;
+ return (-1);
+ }
+ bzero(&io, sizeof io);
+ io.pfrio_flags = flags;
+ if (filter != NULL)
+ io.pfrio_table = *filter;
+ io.pfrio_buffer = tbl;
+ io.pfrio_esize = sizeof(*tbl);
+ io.pfrio_size = *size;
+ if (ioctl(dev, DIOCRGETTSTATS, &io))
+ return (-1);
+ *size = io.pfrio_size;
+ return (0);
+}
+
+int
+pfr_clr_addrs(struct pfr_table *tbl, int *ndel, int flags)
+{
+ struct pfioc_table io;
+
+ if (tbl == NULL) {
+ errno = EINVAL;
+ return (-1);
+ }
+ bzero(&io, sizeof io);
+ io.pfrio_flags = flags;
+ io.pfrio_table = *tbl;
+ if (ioctl(dev, DIOCRCLRADDRS, &io))
+ return (-1);
+ if (ndel != NULL)
+ *ndel = io.pfrio_ndel;
+ return (0);
+}
+
+int
+pfr_add_addrs(struct pfr_table *tbl, struct pfr_addr *addr, int size,
+ int *nadd, int flags)
+{
+ struct pfioc_table io;
+
+ if (tbl == NULL || size < 0 || (size && addr == NULL)) {
+ errno = EINVAL;
+ return (-1);
+ }
+ bzero(&io, sizeof io);
+ io.pfrio_flags = flags;
+ io.pfrio_table = *tbl;
+ io.pfrio_buffer = addr;
+ io.pfrio_esize = sizeof(*addr);
+ io.pfrio_size = size;
+ if (ioctl(dev, DIOCRADDADDRS, &io))
+ return (-1);
+ if (nadd != NULL)
+ *nadd = io.pfrio_nadd;
+ return (0);
+}
+
+int
+pfr_del_addrs(struct pfr_table *tbl, struct pfr_addr *addr, int size,
+ int *ndel, int flags)
+{
+ struct pfioc_table io;
+
+ if (tbl == NULL || size < 0 || (size && addr == NULL)) {
+ errno = EINVAL;
+ return (-1);
+ }
+ bzero(&io, sizeof io);
+ io.pfrio_flags = flags;
+ io.pfrio_table = *tbl;
+ io.pfrio_buffer = addr;
+ io.pfrio_esize = sizeof(*addr);
+ io.pfrio_size = size;
+ if (ioctl(dev, DIOCRDELADDRS, &io))
+ return (-1);
+ if (ndel != NULL)
+ *ndel = io.pfrio_ndel;
+ return (0);
+}
+
+int
+pfr_set_addrs(struct pfr_table *tbl, struct pfr_addr *addr, int size,
+ int *size2, int *nadd, int *ndel, int *nchange, int flags)
+{
+ struct pfioc_table io;
+
+ if (tbl == NULL || size < 0 || (size && addr == NULL)) {
+ errno = EINVAL;
+ return (-1);
+ }
+ bzero(&io, sizeof io);
+ io.pfrio_flags = flags;
+ io.pfrio_table = *tbl;
+ io.pfrio_buffer = addr;
+ io.pfrio_esize = sizeof(*addr);
+ io.pfrio_size = size;
+ io.pfrio_size2 = (size2 != NULL) ? *size2 : 0;
+ if (ioctl(dev, DIOCRSETADDRS, &io))
+ return (-1);
+ if (nadd != NULL)
+ *nadd = io.pfrio_nadd;
+ if (ndel != NULL)
+ *ndel = io.pfrio_ndel;
+ if (nchange != NULL)
+ *nchange = io.pfrio_nchange;
+ if (size2 != NULL)
+ *size2 = io.pfrio_size2;
+ return (0);
+}
+
+int
+pfr_get_addrs(struct pfr_table *tbl, struct pfr_addr *addr, int *size,
+ int flags)
+{
+ struct pfioc_table io;
+
+ if (tbl == NULL || size == NULL || *size < 0 ||
+ (*size && addr == NULL)) {
+ errno = EINVAL;
+ return (-1);
+ }
+ bzero(&io, sizeof io);
+ io.pfrio_flags = flags;
+ io.pfrio_table = *tbl;
+ io.pfrio_buffer = addr;
+ io.pfrio_esize = sizeof(*addr);
+ io.pfrio_size = *size;
+ if (ioctl(dev, DIOCRGETADDRS, &io))
+ return (-1);
+ *size = io.pfrio_size;
+ return (0);
+}
+
+int
+pfr_get_astats(struct pfr_table *tbl, struct pfr_astats *addr, int *size,
+ int flags)
+{
+ struct pfioc_table io;
+
+ if (tbl == NULL || size == NULL || *size < 0 ||
+ (*size && addr == NULL)) {
+ errno = EINVAL;
+ return (-1);
+ }
+ bzero(&io, sizeof io);
+ io.pfrio_flags = flags;
+ io.pfrio_table = *tbl;
+ io.pfrio_buffer = addr;
+ io.pfrio_esize = sizeof(*addr);
+ io.pfrio_size = *size;
+ if (ioctl(dev, DIOCRGETASTATS, &io))
+ return (-1);
+ *size = io.pfrio_size;
+ return (0);
+}
+
+int
+pfr_clr_tstats(struct pfr_table *tbl, int size, int *nzero, int flags)
+{
+ struct pfioc_table io;
+
+ if (size < 0 || (size && !tbl)) {
+ errno = EINVAL;
+ return (-1);
+ }
+ bzero(&io, sizeof io);
+ io.pfrio_flags = flags;
+ io.pfrio_buffer = tbl;
+ io.pfrio_esize = sizeof(*tbl);
+ io.pfrio_size = size;
+ if (ioctl(dev, DIOCRCLRTSTATS, &io))
+ return (-1);
+ if (nzero)
+ *nzero = io.pfrio_nzero;
+ return (0);
+}
+
+int
+pfr_tst_addrs(struct pfr_table *tbl, struct pfr_addr *addr, int size,
+ int *nmatch, int flags)
+{
+ struct pfioc_table io;
+
+ if (tbl == NULL || size < 0 || (size && addr == NULL)) {
+ errno = EINVAL;
+ return (-1);
+ }
+ bzero(&io, sizeof io);
+ io.pfrio_flags = flags;
+ io.pfrio_table = *tbl;
+ io.pfrio_buffer = addr;
+ io.pfrio_esize = sizeof(*addr);
+ io.pfrio_size = size;
+ if (ioctl(dev, DIOCRTSTADDRS, &io))
+ return (-1);
+ if (nmatch)
+ *nmatch = io.pfrio_nmatch;
+ return (0);
+}
+
+int
+pfr_ina_define(struct pfr_table *tbl, struct pfr_addr *addr, int size,
+ int *nadd, int *naddr, int ticket, int flags)
+{
+ struct pfioc_table io;
+
+ if (tbl == NULL || size < 0 || (size && addr == NULL)) {
+ errno = EINVAL;
+ return (-1);
+ }
+ bzero(&io, sizeof io);
+ io.pfrio_flags = flags;
+ io.pfrio_table = *tbl;
+ io.pfrio_buffer = addr;
+ io.pfrio_esize = sizeof(*addr);
+ io.pfrio_size = size;
+ io.pfrio_ticket = ticket;
+ if (ioctl(dev, DIOCRINADEFINE, &io))
+ return (-1);
+ if (nadd != NULL)
+ *nadd = io.pfrio_nadd;
+ if (naddr != NULL)
+ *naddr = io.pfrio_naddr;
+ return (0);
+}
+
+/* interface management code */
+
+int
+pfi_get_ifaces(const char *filter, struct pfi_kif *buf, int *size)
+{
+ struct pfioc_iface io;
+
+ if (size == NULL || *size < 0 || (*size && buf == NULL)) {
+ errno = EINVAL;
+ return (-1);
+ }
+ bzero(&io, sizeof io);
+ if (filter != NULL)
+ if (strlcpy(io.pfiio_name, filter, sizeof(io.pfiio_name)) >=
+ sizeof(io.pfiio_name)) {
+ errno = EINVAL;
+ return (-1);
+ }
+ io.pfiio_buffer = buf;
+ io.pfiio_esize = sizeof(*buf);
+ io.pfiio_size = *size;
+ if (ioctl(dev, DIOCIGETIFACES, &io))
+ return (-1);
+ *size = io.pfiio_size;
+ return (0);
+}
+
+/* buffer management code */
+
+size_t buf_esize[PFRB_MAX] = { 0,
+ sizeof(struct pfr_table), sizeof(struct pfr_tstats),
+ sizeof(struct pfr_addr), sizeof(struct pfr_astats),
+ sizeof(struct pfi_kif), sizeof(struct pfioc_trans_e)
+};
+
+/*
+ * add one element to the buffer
+ */
+int
+pfr_buf_add(struct pfr_buffer *b, const void *e)
+{
+ size_t bs;
+
+ if (b == NULL || b->pfrb_type <= 0 || b->pfrb_type >= PFRB_MAX ||
+ e == NULL) {
+ errno = EINVAL;
+ return (-1);
+ }
+ bs = buf_esize[b->pfrb_type];
+ if (b->pfrb_size == b->pfrb_msize)
+ if (pfr_buf_grow(b, 0))
+ return (-1);
+ memcpy(((caddr_t)b->pfrb_caddr) + bs * b->pfrb_size, e, bs);
+ b->pfrb_size++;
+ return (0);
+}
+
+/*
+ * return next element of the buffer (or first one if prev is NULL)
+ * see PFRB_FOREACH macro
+ */
+void *
+pfr_buf_next(struct pfr_buffer *b, const void *prev)
+{
+ size_t bs;
+
+ if (b == NULL || b->pfrb_type <= 0 || b->pfrb_type >= PFRB_MAX)
+ return (NULL);
+ if (b->pfrb_size == 0)
+ return (NULL);
+ if (prev == NULL)
+ return (b->pfrb_caddr);
+ bs = buf_esize[b->pfrb_type];
+ if ((((caddr_t)prev)-((caddr_t)b->pfrb_caddr)) / bs >= b->pfrb_size-1)
+ return (NULL);
+ return (((caddr_t)prev) + bs);
+}
+
+/*
+ * minsize:
+ * 0: make the buffer somewhat bigger
+ * n: make room for "n" entries in the buffer
+ */
+int
+pfr_buf_grow(struct pfr_buffer *b, int minsize)
+{
+ caddr_t p;
+ size_t bs;
+
+ if (b == NULL || b->pfrb_type <= 0 || b->pfrb_type >= PFRB_MAX) {
+ errno = EINVAL;
+ return (-1);
+ }
+ if (minsize != 0 && minsize <= b->pfrb_msize)
+ return (0);
+ bs = buf_esize[b->pfrb_type];
+ if (!b->pfrb_msize) {
+ if (minsize < 64)
+ minsize = 64;
+ b->pfrb_caddr = calloc(bs, minsize);
+ if (b->pfrb_caddr == NULL)
+ return (-1);
+ b->pfrb_msize = minsize;
+ } else {
+ if (minsize == 0)
+ minsize = b->pfrb_msize * 2;
+ if (minsize < 0 || minsize >= SIZE_T_MAX / bs) {
+ /* msize overflow */
+ errno = ENOMEM;
+ return (-1);
+ }
+ p = realloc(b->pfrb_caddr, minsize * bs);
+ if (p == NULL)
+ return (-1);
+ bzero(p + b->pfrb_msize * bs, (minsize - b->pfrb_msize) * bs);
+ b->pfrb_caddr = p;
+ b->pfrb_msize = minsize;
+ }
+ return (0);
+}
+
+/*
+ * reset buffer and free memory.
+ */
+void
+pfr_buf_clear(struct pfr_buffer *b)
+{
+ if (b == NULL)
+ return;
+ if (b->pfrb_caddr != NULL)
+ free(b->pfrb_caddr);
+ b->pfrb_caddr = NULL;
+ b->pfrb_size = b->pfrb_msize = 0;
+}
+
+int
+pfr_buf_load(struct pfr_buffer *b, char *file, int nonetwork,
+ int (*append_addr)(struct pfr_buffer *, char *, int))
+{
+ FILE *fp;
+ char buf[BUF_SIZE];
+ int rv;
+
+ if (file == NULL)
+ return (0);
+ if (!strcmp(file, "-"))
+ fp = stdin;
+ else {
+ fp = pfctl_fopen(file, "r");
+ if (fp == NULL)
+ return (-1);
+ }
+ while ((rv = pfr_next_token(buf, fp)) == 1)
+ if (append_addr(b, buf, nonetwork)) {
+ rv = -1;
+ break;
+ }
+ if (fp != stdin)
+ fclose(fp);
+ return (rv);
+}
+
+int
+pfr_next_token(char buf[BUF_SIZE], FILE *fp)
+{
+ static char next_ch = ' ';
+ int i = 0;
+
+ for (;;) {
+ /* skip spaces */
+ while (isspace(next_ch) && !feof(fp))
+ next_ch = fgetc(fp);
+ /* remove from '#' until end of line */
+ if (next_ch == '#')
+ while (!feof(fp)) {
+ next_ch = fgetc(fp);
+ if (next_ch == '\n')
+ break;
+ }
+ else
+ break;
+ }
+ if (feof(fp)) {
+ next_ch = ' ';
+ return (0);
+ }
+ do {
+ if (i < BUF_SIZE)
+ buf[i++] = next_ch;
+ next_ch = fgetc(fp);
+ } while (!feof(fp) && !isspace(next_ch));
+ if (i >= BUF_SIZE) {
+ errno = EINVAL;
+ return (-1);
+ }
+ buf[i] = '\0';
+ return (1);
+}
+
+char *
+pfr_strerror(int errnum)
+{
+ switch (errnum) {
+ case ESRCH:
+ return "Table does not exist";
+ case ENOENT:
+ return "Anchor or Ruleset does not exist";
+ default:
+ return strerror(errnum);
+ }
+}
diff --git a/sbin/pfctl/pfctl_table.c b/sbin/pfctl/pfctl_table.c
new file mode 100644
index 0000000..f3a1efd
--- /dev/null
+++ b/sbin/pfctl/pfctl_table.c
@@ -0,0 +1,634 @@
+/* $OpenBSD: pfctl_table.c,v 1.67 2008/06/10 20:55:02 mcbride Exp $ */
+
+/*
+ * Copyright (c) 2002 Cedric Berger
+ * All rights reserved.
+ *
+ * Redistribution and use 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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
+ * COPYRIGHT HOLDERS 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 <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <net/if.h>
+#include <net/pfvar.h>
+#include <arpa/inet.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <netdb.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "pfctl_parser.h"
+#include "pfctl.h"
+
+extern void usage(void);
+static int pfctl_table(int, char *[], char *, const char *, char *,
+ const char *, int);
+static void print_table(struct pfr_table *, int, int);
+static void print_tstats(struct pfr_tstats *, int);
+static int load_addr(struct pfr_buffer *, int, char *[], char *, int);
+static void print_addrx(struct pfr_addr *, struct pfr_addr *, int);
+static void print_astats(struct pfr_astats *, int);
+static void radix_perror(void);
+static void xprintf(int, const char *, ...);
+static void print_iface(struct pfi_kif *, int);
+
+static const char *stats_text[PFR_DIR_MAX][PFR_OP_TABLE_MAX] = {
+ { "In/Block:", "In/Pass:", "In/XPass:" },
+ { "Out/Block:", "Out/Pass:", "Out/XPass:" }
+};
+
+static const char *istats_text[2][2][2] = {
+ { { "In4/Pass:", "In4/Block:" }, { "Out4/Pass:", "Out4/Block:" } },
+ { { "In6/Pass:", "In6/Block:" }, { "Out6/Pass:", "Out6/Block:" } }
+};
+
+#define RVTEST(fct) do { \
+ if ((!(opts & PF_OPT_NOACTION) || \
+ (opts & PF_OPT_DUMMYACTION)) && \
+ (fct)) { \
+ radix_perror(); \
+ goto _error; \
+ } \
+ } while (0)
+
+#define CREATE_TABLE do { \
+ table.pfrt_flags |= PFR_TFLAG_PERSIST; \
+ if ((!(opts & PF_OPT_NOACTION) || \
+ (opts & PF_OPT_DUMMYACTION)) && \
+ (pfr_add_tables(&table, 1, &nadd, flags)) && \
+ (errno != EPERM)) { \
+ radix_perror(); \
+ goto _error; \
+ } \
+ if (nadd) { \
+ warn_namespace_collision(table.pfrt_name); \
+ xprintf(opts, "%d table created", nadd); \
+ if (opts & PF_OPT_NOACTION) \
+ return (0); \
+ } \
+ table.pfrt_flags &= ~PFR_TFLAG_PERSIST; \
+ } while(0)
+
+int
+pfctl_clear_tables(const char *anchor, int opts)
+{
+ return pfctl_table(0, NULL, NULL, "-F", NULL, anchor, opts);
+}
+
+int
+pfctl_show_tables(const char *anchor, int opts)
+{
+ return pfctl_table(0, NULL, NULL, "-s", NULL, anchor, opts);
+}
+
+int
+pfctl_command_tables(int argc, char *argv[], char *tname,
+ const char *command, char *file, const char *anchor, int opts)
+{
+ if (tname == NULL || command == NULL)
+ usage();
+ return pfctl_table(argc, argv, tname, command, file, anchor, opts);
+}
+
+int
+pfctl_table(int argc, char *argv[], char *tname, const char *command,
+ char *file, const char *anchor, int opts)
+{
+ struct pfr_table table;
+ struct pfr_buffer b, b2;
+ struct pfr_addr *a, *a2;
+ int nadd = 0, ndel = 0, nchange = 0, nzero = 0;
+ int rv = 0, flags = 0, nmatch = 0;
+ void *p;
+
+ if (command == NULL)
+ usage();
+ if (opts & PF_OPT_NOACTION)
+ flags |= PFR_FLAG_DUMMY;
+
+ bzero(&b, sizeof(b));
+ bzero(&b2, sizeof(b2));
+ bzero(&table, sizeof(table));
+ if (tname != NULL) {
+ if (strlen(tname) >= PF_TABLE_NAME_SIZE)
+ usage();
+ if (strlcpy(table.pfrt_name, tname,
+ sizeof(table.pfrt_name)) >= sizeof(table.pfrt_name))
+ errx(1, "pfctl_table: strlcpy");
+ }
+ if (strlcpy(table.pfrt_anchor, anchor,
+ sizeof(table.pfrt_anchor)) >= sizeof(table.pfrt_anchor))
+ errx(1, "pfctl_table: strlcpy");
+
+ if (!strcmp(command, "-F")) {
+ if (argc || file != NULL)
+ usage();
+ RVTEST(pfr_clr_tables(&table, &ndel, flags));
+ xprintf(opts, "%d tables deleted", ndel);
+ } else if (!strcmp(command, "-s")) {
+ b.pfrb_type = (opts & PF_OPT_VERBOSE2) ?
+ PFRB_TSTATS : PFRB_TABLES;
+ if (argc || file != NULL)
+ usage();
+ for (;;) {
+ pfr_buf_grow(&b, b.pfrb_size);
+ b.pfrb_size = b.pfrb_msize;
+ if (opts & PF_OPT_VERBOSE2)
+ RVTEST(pfr_get_tstats(&table,
+ b.pfrb_caddr, &b.pfrb_size, flags));
+ else
+ RVTEST(pfr_get_tables(&table,
+ b.pfrb_caddr, &b.pfrb_size, flags));
+ if (b.pfrb_size <= b.pfrb_msize)
+ break;
+ }
+
+ if ((opts & PF_OPT_SHOWALL) && b.pfrb_size > 0)
+ pfctl_print_title("TABLES:");
+
+ PFRB_FOREACH(p, &b)
+ if (opts & PF_OPT_VERBOSE2)
+ print_tstats(p, opts & PF_OPT_DEBUG);
+ else
+ print_table(p, opts & PF_OPT_VERBOSE,
+ opts & PF_OPT_DEBUG);
+ } else if (!strcmp(command, "kill")) {
+ if (argc || file != NULL)
+ usage();
+ RVTEST(pfr_del_tables(&table, 1, &ndel, flags));
+ xprintf(opts, "%d table deleted", ndel);
+ } else if (!strcmp(command, "flush")) {
+ if (argc || file != NULL)
+ usage();
+ RVTEST(pfr_clr_addrs(&table, &ndel, flags));
+ xprintf(opts, "%d addresses deleted", ndel);
+ } else if (!strcmp(command, "add")) {
+ b.pfrb_type = PFRB_ADDRS;
+ if (load_addr(&b, argc, argv, file, 0))
+ goto _error;
+ CREATE_TABLE;
+ if (opts & PF_OPT_VERBOSE)
+ flags |= PFR_FLAG_FEEDBACK;
+ RVTEST(pfr_add_addrs(&table, b.pfrb_caddr, b.pfrb_size,
+ &nadd, flags));
+ xprintf(opts, "%d/%d addresses added", nadd, b.pfrb_size);
+ if (opts & PF_OPT_VERBOSE)
+ PFRB_FOREACH(a, &b)
+ if ((opts & PF_OPT_VERBOSE2) || a->pfra_fback)
+ print_addrx(a, NULL,
+ opts & PF_OPT_USEDNS);
+ } else if (!strcmp(command, "delete")) {
+ b.pfrb_type = PFRB_ADDRS;
+ if (load_addr(&b, argc, argv, file, 0))
+ goto _error;
+ if (opts & PF_OPT_VERBOSE)
+ flags |= PFR_FLAG_FEEDBACK;
+ RVTEST(pfr_del_addrs(&table, b.pfrb_caddr, b.pfrb_size,
+ &ndel, flags));
+ xprintf(opts, "%d/%d addresses deleted", ndel, b.pfrb_size);
+ if (opts & PF_OPT_VERBOSE)
+ PFRB_FOREACH(a, &b)
+ if ((opts & PF_OPT_VERBOSE2) || a->pfra_fback)
+ print_addrx(a, NULL,
+ opts & PF_OPT_USEDNS);
+ } else if (!strcmp(command, "replace")) {
+ b.pfrb_type = PFRB_ADDRS;
+ if (load_addr(&b, argc, argv, file, 0))
+ goto _error;
+ CREATE_TABLE;
+ if (opts & PF_OPT_VERBOSE)
+ flags |= PFR_FLAG_FEEDBACK;
+ for (;;) {
+ int sz2 = b.pfrb_msize;
+
+ RVTEST(pfr_set_addrs(&table, b.pfrb_caddr, b.pfrb_size,
+ &sz2, &nadd, &ndel, &nchange, flags));
+ if (sz2 <= b.pfrb_msize) {
+ b.pfrb_size = sz2;
+ break;
+ } else
+ pfr_buf_grow(&b, sz2);
+ }
+ if (nadd)
+ xprintf(opts, "%d addresses added", nadd);
+ if (ndel)
+ xprintf(opts, "%d addresses deleted", ndel);
+ if (nchange)
+ xprintf(opts, "%d addresses changed", nchange);
+ if (!nadd && !ndel && !nchange)
+ xprintf(opts, "no changes");
+ if (opts & PF_OPT_VERBOSE)
+ PFRB_FOREACH(a, &b)
+ if ((opts & PF_OPT_VERBOSE2) || a->pfra_fback)
+ print_addrx(a, NULL,
+ opts & PF_OPT_USEDNS);
+ } else if (!strcmp(command, "expire")) {
+ const char *errstr;
+ u_int lifetime;
+
+ b.pfrb_type = PFRB_ASTATS;
+ b2.pfrb_type = PFRB_ADDRS;
+ if (argc != 1 || file != NULL)
+ usage();
+ lifetime = strtonum(*argv, 0, UINT_MAX, &errstr);
+ if (errstr)
+ errx(1, "expiry time: %s", errstr);
+ for (;;) {
+ pfr_buf_grow(&b, b.pfrb_size);
+ b.pfrb_size = b.pfrb_msize;
+ RVTEST(pfr_get_astats(&table, b.pfrb_caddr,
+ &b.pfrb_size, flags));
+ if (b.pfrb_size <= b.pfrb_msize)
+ break;
+ }
+ PFRB_FOREACH(p, &b) {
+ ((struct pfr_astats *)p)->pfras_a.pfra_fback = 0;
+ if (time(NULL) - ((struct pfr_astats *)p)->pfras_tzero >
+ lifetime)
+ if (pfr_buf_add(&b2,
+ &((struct pfr_astats *)p)->pfras_a))
+ err(1, "duplicate buffer");
+ }
+
+ if (opts & PF_OPT_VERBOSE)
+ flags |= PFR_FLAG_FEEDBACK;
+ RVTEST(pfr_del_addrs(&table, b2.pfrb_caddr, b2.pfrb_size,
+ &ndel, flags));
+ xprintf(opts, "%d/%d addresses expired", ndel, b2.pfrb_size);
+ if (opts & PF_OPT_VERBOSE)
+ PFRB_FOREACH(a, &b2)
+ if ((opts & PF_OPT_VERBOSE2) || a->pfra_fback)
+ print_addrx(a, NULL,
+ opts & PF_OPT_USEDNS);
+ } else if (!strcmp(command, "show")) {
+ b.pfrb_type = (opts & PF_OPT_VERBOSE) ?
+ PFRB_ASTATS : PFRB_ADDRS;
+ if (argc || file != NULL)
+ usage();
+ for (;;) {
+ pfr_buf_grow(&b, b.pfrb_size);
+ b.pfrb_size = b.pfrb_msize;
+ if (opts & PF_OPT_VERBOSE)
+ RVTEST(pfr_get_astats(&table, b.pfrb_caddr,
+ &b.pfrb_size, flags));
+ else
+ RVTEST(pfr_get_addrs(&table, b.pfrb_caddr,
+ &b.pfrb_size, flags));
+ if (b.pfrb_size <= b.pfrb_msize)
+ break;
+ }
+ PFRB_FOREACH(p, &b)
+ if (opts & PF_OPT_VERBOSE)
+ print_astats(p, opts & PF_OPT_USEDNS);
+ else
+ print_addrx(p, NULL, opts & PF_OPT_USEDNS);
+ } else if (!strcmp(command, "test")) {
+ b.pfrb_type = PFRB_ADDRS;
+ b2.pfrb_type = PFRB_ADDRS;
+
+ if (load_addr(&b, argc, argv, file, 1))
+ goto _error;
+ if (opts & PF_OPT_VERBOSE2) {
+ flags |= PFR_FLAG_REPLACE;
+ PFRB_FOREACH(a, &b)
+ if (pfr_buf_add(&b2, a))
+ err(1, "duplicate buffer");
+ }
+ RVTEST(pfr_tst_addrs(&table, b.pfrb_caddr, b.pfrb_size,
+ &nmatch, flags));
+ xprintf(opts, "%d/%d addresses match", nmatch, b.pfrb_size);
+ if ((opts & PF_OPT_VERBOSE) && !(opts & PF_OPT_VERBOSE2))
+ PFRB_FOREACH(a, &b)
+ if (a->pfra_fback == PFR_FB_MATCH)
+ print_addrx(a, NULL,
+ opts & PF_OPT_USEDNS);
+ if (opts & PF_OPT_VERBOSE2) {
+ a2 = NULL;
+ PFRB_FOREACH(a, &b) {
+ a2 = pfr_buf_next(&b2, a2);
+ print_addrx(a2, a, opts & PF_OPT_USEDNS);
+ }
+ }
+ if (nmatch < b.pfrb_size)
+ rv = 2;
+ } else if (!strcmp(command, "zero")) {
+ if (argc || file != NULL)
+ usage();
+ flags |= PFR_FLAG_ADDRSTOO;
+ RVTEST(pfr_clr_tstats(&table, 1, &nzero, flags));
+ xprintf(opts, "%d table/stats cleared", nzero);
+ } else
+ warnx("pfctl_table: unknown command '%s'", command);
+ goto _cleanup;
+
+_error:
+ rv = -1;
+_cleanup:
+ pfr_buf_clear(&b);
+ pfr_buf_clear(&b2);
+ return (rv);
+}
+
+void
+print_table(struct pfr_table *ta, int verbose, int debug)
+{
+ if (!debug && !(ta->pfrt_flags & PFR_TFLAG_ACTIVE))
+ return;
+ if (verbose) {
+ printf("%c%c%c%c%c%c%c\t%s",
+ (ta->pfrt_flags & PFR_TFLAG_CONST) ? 'c' : '-',
+ (ta->pfrt_flags & PFR_TFLAG_PERSIST) ? 'p' : '-',
+ (ta->pfrt_flags & PFR_TFLAG_ACTIVE) ? 'a' : '-',
+ (ta->pfrt_flags & PFR_TFLAG_INACTIVE) ? 'i' : '-',
+ (ta->pfrt_flags & PFR_TFLAG_REFERENCED) ? 'r' : '-',
+ (ta->pfrt_flags & PFR_TFLAG_REFDANCHOR) ? 'h' : '-',
+ (ta->pfrt_flags & PFR_TFLAG_COUNTERS) ? 'C' : '-',
+ ta->pfrt_name);
+ if (ta->pfrt_anchor[0])
+ printf("\t%s", ta->pfrt_anchor);
+ puts("");
+ } else
+ puts(ta->pfrt_name);
+}
+
+void
+print_tstats(struct pfr_tstats *ts, int debug)
+{
+ time_t time = ts->pfrts_tzero;
+ int dir, op;
+
+ if (!debug && !(ts->pfrts_flags & PFR_TFLAG_ACTIVE))
+ return;
+ print_table(&ts->pfrts_t, 1, debug);
+ printf("\tAddresses: %d\n", ts->pfrts_cnt);
+ printf("\tCleared: %s", ctime(&time));
+ printf("\tReferences: [ Anchors: %-18d Rules: %-18d ]\n",
+ ts->pfrts_refcnt[PFR_REFCNT_ANCHOR],
+ ts->pfrts_refcnt[PFR_REFCNT_RULE]);
+ printf("\tEvaluations: [ NoMatch: %-18llu Match: %-18llu ]\n",
+ (unsigned long long)ts->pfrts_nomatch,
+ (unsigned long long)ts->pfrts_match);
+ for (dir = 0; dir < PFR_DIR_MAX; dir++)
+ for (op = 0; op < PFR_OP_TABLE_MAX; op++)
+ printf("\t%-12s [ Packets: %-18llu Bytes: %-18llu ]\n",
+ stats_text[dir][op],
+ (unsigned long long)ts->pfrts_packets[dir][op],
+ (unsigned long long)ts->pfrts_bytes[dir][op]);
+}
+
+int
+load_addr(struct pfr_buffer *b, int argc, char *argv[], char *file,
+ int nonetwork)
+{
+ while (argc--)
+ if (append_addr(b, *argv++, nonetwork)) {
+ if (errno)
+ warn("cannot decode %s", argv[-1]);
+ return (-1);
+ }
+ if (pfr_buf_load(b, file, nonetwork, append_addr)) {
+ warn("cannot load %s", file);
+ return (-1);
+ }
+ return (0);
+}
+
+void
+print_addrx(struct pfr_addr *ad, struct pfr_addr *rad, int dns)
+{
+ char ch, buf[256] = "{error}";
+ char fb[] = { ' ', 'M', 'A', 'D', 'C', 'Z', 'X', ' ', 'Y', ' ' };
+ unsigned int fback, hostnet;
+
+ fback = (rad != NULL) ? rad->pfra_fback : ad->pfra_fback;
+ ch = (fback < sizeof(fb)/sizeof(*fb)) ? fb[fback] : '?';
+ hostnet = (ad->pfra_af == AF_INET6) ? 128 : 32;
+ inet_ntop(ad->pfra_af, &ad->pfra_u, buf, sizeof(buf));
+ printf("%c %c%s", ch, (ad->pfra_not?'!':' '), buf);
+ if (ad->pfra_net < hostnet)
+ printf("/%d", ad->pfra_net);
+ if (rad != NULL && fback != PFR_FB_NONE) {
+ if (strlcpy(buf, "{error}", sizeof(buf)) >= sizeof(buf))
+ errx(1, "print_addrx: strlcpy");
+ inet_ntop(rad->pfra_af, &rad->pfra_u, buf, sizeof(buf));
+ printf("\t%c%s", (rad->pfra_not?'!':' '), buf);
+ if (rad->pfra_net < hostnet)
+ printf("/%d", rad->pfra_net);
+ }
+ if (rad != NULL && fback == PFR_FB_NONE)
+ printf("\t nomatch");
+ if (dns && ad->pfra_net == hostnet) {
+ char host[NI_MAXHOST];
+ union sockaddr_union sa;
+
+ strlcpy(host, "?", sizeof(host));
+ bzero(&sa, sizeof(sa));
+ sa.sa.sa_family = ad->pfra_af;
+ if (sa.sa.sa_family == AF_INET) {
+ sa.sa.sa_len = sizeof(sa.sin);
+ sa.sin.sin_addr = ad->pfra_ip4addr;
+ } else {
+ sa.sa.sa_len = sizeof(sa.sin6);
+ sa.sin6.sin6_addr = ad->pfra_ip6addr;
+ }
+ if (getnameinfo(&sa.sa, sa.sa.sa_len, host, sizeof(host),
+ NULL, 0, NI_NAMEREQD) == 0)
+ printf("\t(%s)", host);
+ }
+ printf("\n");
+}
+
+void
+print_astats(struct pfr_astats *as, int dns)
+{
+ time_t time = as->pfras_tzero;
+ int dir, op;
+
+ print_addrx(&as->pfras_a, NULL, dns);
+ printf("\tCleared: %s", ctime(&time));
+ if (as->pfras_a.pfra_fback == PFR_FB_NOCOUNT)
+ return;
+ for (dir = 0; dir < PFR_DIR_MAX; dir++)
+ for (op = 0; op < PFR_OP_ADDR_MAX; op++)
+ printf("\t%-12s [ Packets: %-18llu Bytes: %-18llu ]\n",
+ stats_text[dir][op],
+ (unsigned long long)as->pfras_packets[dir][op],
+ (unsigned long long)as->pfras_bytes[dir][op]);
+}
+
+void
+radix_perror(void)
+{
+ extern char *__progname;
+ fprintf(stderr, "%s: %s.\n", __progname, pfr_strerror(errno));
+}
+
+int
+pfctl_define_table(char *name, int flags, int addrs, const char *anchor,
+ struct pfr_buffer *ab, u_int32_t ticket)
+{
+ struct pfr_table tbl;
+
+ bzero(&tbl, sizeof(tbl));
+ if (strlcpy(tbl.pfrt_name, name, sizeof(tbl.pfrt_name)) >=
+ sizeof(tbl.pfrt_name) || strlcpy(tbl.pfrt_anchor, anchor,
+ sizeof(tbl.pfrt_anchor)) >= sizeof(tbl.pfrt_anchor))
+ errx(1, "pfctl_define_table: strlcpy");
+ tbl.pfrt_flags = flags;
+
+ return pfr_ina_define(&tbl, ab->pfrb_caddr, ab->pfrb_size, NULL,
+ NULL, ticket, addrs ? PFR_FLAG_ADDRSTOO : 0);
+}
+
+void
+warn_namespace_collision(const char *filter)
+{
+ struct pfr_buffer b;
+ struct pfr_table *t;
+ const char *name = NULL, *lastcoll;
+ int coll = 0;
+
+ bzero(&b, sizeof(b));
+ b.pfrb_type = PFRB_TABLES;
+ for (;;) {
+ pfr_buf_grow(&b, b.pfrb_size);
+ b.pfrb_size = b.pfrb_msize;
+ if (pfr_get_tables(NULL, b.pfrb_caddr,
+ &b.pfrb_size, PFR_FLAG_ALLRSETS))
+ err(1, "pfr_get_tables");
+ if (b.pfrb_size <= b.pfrb_msize)
+ break;
+ }
+ PFRB_FOREACH(t, &b) {
+ if (!(t->pfrt_flags & PFR_TFLAG_ACTIVE))
+ continue;
+ if (filter != NULL && strcmp(filter, t->pfrt_name))
+ continue;
+ if (!t->pfrt_anchor[0])
+ name = t->pfrt_name;
+ else if (name != NULL && !strcmp(name, t->pfrt_name)) {
+ coll++;
+ lastcoll = name;
+ name = NULL;
+ }
+ }
+ if (coll == 1)
+ warnx("warning: namespace collision with <%s> global table.",
+ lastcoll);
+ else if (coll > 1)
+ warnx("warning: namespace collisions with %d global tables.",
+ coll);
+ pfr_buf_clear(&b);
+}
+
+void
+xprintf(int opts, const char *fmt, ...)
+{
+ va_list args;
+
+ if (opts & PF_OPT_QUIET)
+ return;
+
+ va_start(args, fmt);
+ vfprintf(stderr, fmt, args);
+ va_end(args);
+
+ if (opts & PF_OPT_DUMMYACTION)
+ fprintf(stderr, " (dummy).\n");
+ else if (opts & PF_OPT_NOACTION)
+ fprintf(stderr, " (syntax only).\n");
+ else
+ fprintf(stderr, ".\n");
+}
+
+
+/* interface stuff */
+
+int
+pfctl_show_ifaces(const char *filter, int opts)
+{
+ struct pfr_buffer b;
+ struct pfi_kif *p;
+ int i = 0;
+
+ bzero(&b, sizeof(b));
+ b.pfrb_type = PFRB_IFACES;
+ for (;;) {
+ pfr_buf_grow(&b, b.pfrb_size);
+ b.pfrb_size = b.pfrb_msize;
+ if (pfi_get_ifaces(filter, b.pfrb_caddr, &b.pfrb_size)) {
+ radix_perror();
+ return (1);
+ }
+ if (b.pfrb_size <= b.pfrb_msize)
+ break;
+ i++;
+ }
+ if (opts & PF_OPT_SHOWALL)
+ pfctl_print_title("INTERFACES:");
+ PFRB_FOREACH(p, &b)
+ print_iface(p, opts);
+ return (0);
+}
+
+void
+print_iface(struct pfi_kif *p, int opts)
+{
+ time_t tzero = p->pfik_tzero;
+ int i, af, dir, act;
+
+ printf("%s", p->pfik_name);
+ if (opts & PF_OPT_VERBOSE) {
+ if (p->pfik_flags & PFI_IFLAG_SKIP)
+ printf(" (skip)");
+ }
+ printf("\n");
+
+ if (!(opts & PF_OPT_VERBOSE2))
+ return;
+ printf("\tCleared: %s", ctime(&tzero));
+ printf("\tReferences: %-18d\n", p->pfik_rulerefs);
+ for (i = 0; i < 8; i++) {
+ af = (i>>2) & 1;
+ dir = (i>>1) &1;
+ act = i & 1;
+ printf("\t%-12s [ Packets: %-18llu Bytes: %-18llu ]\n",
+ istats_text[af][dir][act],
+ (unsigned long long)p->pfik_packets[af][dir][act],
+ (unsigned long long)p->pfik_bytes[af][dir][act]);
+ }
+}
diff --git a/sbin/pflogd/Makefile b/sbin/pflogd/Makefile
new file mode 100644
index 0000000..49a311e
--- /dev/null
+++ b/sbin/pflogd/Makefile
@@ -0,0 +1,15 @@
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../../contrib/pf/pflogd
+
+PROG= pflogd
+SRCS= pflogd.c pidfile.c privsep.c privsep_fdpass.c
+MAN= pflogd.8
+
+CFLAGS+=-include ${.CURDIR}/../../lib/libpcap/config.h
+
+LIBADD= pcap
+
+WARNS?= 2
+
+.include <bsd.prog.mk>
diff --git a/sbin/ping/Makefile b/sbin/ping/Makefile
new file mode 100644
index 0000000..533f8b8
--- /dev/null
+++ b/sbin/ping/Makefile
@@ -0,0 +1,23 @@
+# @(#)Makefile 8.1 (Berkeley) 6/5/93
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+PROG= ping
+MAN= ping.8
+BINOWN= root
+BINMODE=4555
+WARNS?= 3
+LIBADD= m
+
+.if ${MK_CASPER} != "no" && !defined(RESCUE)
+LIBADD+= capsicum
+CFLAGS+=-DHAVE_LIBCAPSICUM
+.endif
+
+.if !defined(RELEASE_CRUNCH)
+CFLAGS+=-DIPSEC
+LIBADD+= ipsec
+.endif
+
+.include <bsd.prog.mk>
diff --git a/sbin/ping/ping.8 b/sbin/ping/ping.8
new file mode 100644
index 0000000..7617585
--- /dev/null
+++ b/sbin/ping/ping.8
@@ -0,0 +1,554 @@
+.\" Copyright (c) 1985, 1991, 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.
+.\" 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.
+.\"
+.\" @(#)ping.8 8.2 (Berkeley) 12/11/93
+.\" $FreeBSD$
+.\"
+.Dd April 4, 2006
+.Dt PING 8
+.Os
+.Sh NAME
+.Nm ping
+.Nd send
+.Tn ICMP ECHO_REQUEST
+packets to network hosts
+.Sh SYNOPSIS
+.Nm
+.Op Fl AaDdfnoQqRrv
+.Op Fl c Ar count
+.Op Fl G Ar sweepmaxsize
+.Op Fl g Ar sweepminsize
+.Op Fl h Ar sweepincrsize
+.Op Fl i Ar wait
+.Op Fl l Ar preload
+.Op Fl M Cm mask | time
+.Op Fl m Ar ttl
+.Op Fl P Ar policy
+.Op Fl p Ar pattern
+.Op Fl S Ar src_addr
+.Op Fl s Ar packetsize
+.Op Fl t Ar timeout
+.Op Fl W Ar waittime
+.Op Fl z Ar tos
+.Ar host
+.Nm
+.Op Fl AaDdfLnoQqRrv
+.Op Fl c Ar count
+.Op Fl I Ar iface
+.Op Fl i Ar wait
+.Op Fl l Ar preload
+.Op Fl M Cm mask | time
+.Op Fl m Ar ttl
+.Op Fl P Ar policy
+.Op Fl p Ar pattern
+.Op Fl S Ar src_addr
+.Op Fl s Ar packetsize
+.Op Fl T Ar ttl
+.Op Fl t Ar timeout
+.Op Fl W Ar waittime
+.Op Fl z Ar tos
+.Ar mcast-group
+.Sh DESCRIPTION
+The
+.Nm
+utility uses the
+.Tn ICMP
+.No protocol Ap s mandatory
+.Tn ECHO_REQUEST
+datagram to elicit an
+.Tn ICMP ECHO_RESPONSE
+from a host or gateway.
+.Tn ECHO_REQUEST
+datagrams
+.Pq Dq pings
+have an IP and
+.Tn ICMP
+header, followed by a
+.Dq struct timeval
+and then an arbitrary number of
+.Dq pad
+bytes used to fill out the packet.
+The options are as follows:
+.Bl -tag -width indent
+.It Fl A
+Audible.
+Output a bell
+.Tn ( ASCII
+0x07)
+character when no packet is received before the next packet
+is transmitted.
+To cater for round-trip times that are longer than the interval
+between transmissions, further missing packets cause a bell only
+if the maximum number of unreceived packets has increased.
+.It Fl a
+Audible.
+Include a bell
+.Tn ( ASCII
+0x07)
+character in the output when any packet is received.
+This option is ignored
+if other format options are present.
+.It Fl c Ar count
+Stop after sending
+(and receiving)
+.Ar count
+.Tn ECHO_RESPONSE
+packets.
+If this option is not specified,
+.Nm
+will operate until interrupted.
+If this option is specified in conjunction with ping sweeps,
+each sweep will consist of
+.Ar count
+packets.
+.It Fl D
+Set the Don't Fragment bit.
+.It Fl d
+Set the
+.Dv SO_DEBUG
+option on the socket being used.
+.It Fl f
+Flood ping.
+Outputs packets as fast as they come back or one hundred times per second,
+whichever is more.
+For every
+.Tn ECHO_REQUEST
+sent a period
+.Dq .\&
+is printed, while for every
+.Tn ECHO_REPLY
+received a backspace is printed.
+This provides a rapid display of how many packets are being dropped.
+Only the super-user may use this option.
+.Bf -emphasis
+This can be very hard on a network and should be used with caution.
+.Ef
+.It Fl G Ar sweepmaxsize
+Specify the maximum size of
+.Tn ICMP
+payload when sending sweeping pings.
+This option is required for ping sweeps.
+.It Fl g Ar sweepminsize
+Specify the size of
+.Tn ICMP
+payload to start with when sending sweeping pings.
+The default value is 0.
+.It Fl h Ar sweepincrsize
+Specify the number of bytes to increment the size of
+.Tn ICMP
+payload after
+each sweep when sending sweeping pings.
+The default value is 1.
+.It Fl I Ar iface
+Source multicast packets with the given interface address.
+This flag only applies if the ping destination is a multicast address.
+.It Fl i Ar wait
+Wait
+.Ar wait
+seconds
+.Em between sending each packet .
+The default is to wait for one second between each packet.
+The wait time may be fractional, but only the super-user may specify
+values less than 1 second.
+This option is incompatible with the
+.Fl f
+option.
+.It Fl L
+Suppress loopback of multicast packets.
+This flag only applies if the ping destination is a multicast address.
+.It Fl l Ar preload
+If
+.Ar preload
+is specified,
+.Nm
+sends that many packets as fast as possible before falling into its normal
+mode of behavior.
+Only the super-user may use this option.
+.It Fl M Cm mask | time
+Use
+.Dv ICMP_MASKREQ
+or
+.Dv ICMP_TSTAMP
+instead of
+.Dv ICMP_ECHO .
+For
+.Cm mask ,
+print the netmask of the remote machine.
+Set the
+.Va net.inet.icmp.maskrepl
+MIB variable to enable
+.Dv ICMP_MASKREPLY .
+For
+.Cm time ,
+print the origination, reception and transmission timestamps.
+.It Fl m Ar ttl
+Set the IP Time To Live for outgoing packets.
+If not specified, the kernel uses the value of the
+.Va net.inet.ip.ttl
+MIB variable.
+.It Fl n
+Numeric output only.
+No attempt will be made to lookup symbolic names for host addresses.
+.It Fl o
+Exit successfully after receiving one reply packet.
+.It Fl P Ar policy
+.Ar policy
+specifies IPsec policy for the ping session.
+For details please refer to
+.Xr ipsec 4
+and
+.Xr ipsec_set_policy 3 .
+.It Fl p Ar pattern
+You may specify up to 16
+.Dq pad
+bytes to fill out the packet you send.
+This is useful for diagnosing data-dependent problems in a network.
+For example,
+.Dq Li \-p ff
+will cause the sent packet to be filled with all
+ones.
+.It Fl Q
+Somewhat quiet output.
+.No Don Ap t
+display ICMP error messages that are in response to our query messages.
+Originally, the
+.Fl v
+flag was required to display such errors, but
+.Fl v
+displays all ICMP error messages.
+On a busy machine, this output can be overbearing.
+Without the
+.Fl Q
+flag,
+.Nm
+prints out any ICMP error messages caused by its own ECHO_REQUEST
+messages.
+.It Fl q
+Quiet output.
+Nothing is displayed except the summary lines at startup time and
+when finished.
+.It Fl R
+Record route.
+Includes the
+.Tn RECORD_ROUTE
+option in the
+.Tn ECHO_REQUEST
+packet and displays
+the route buffer on returned packets.
+Note that the IP header is only large enough for nine such routes;
+the
+.Xr traceroute 8
+command is usually better at determining the route packets take to a
+particular destination.
+If more routes come back than should, such as due to an illegal spoofed
+packet, ping will print the route list and then truncate it at the correct
+spot.
+Many hosts ignore or discard the
+.Tn RECORD_ROUTE
+option.
+.It Fl r
+Bypass the normal routing tables and send directly to a host on an attached
+network.
+If the host is not on a directly-attached network, an error is returned.
+This option can be used to ping a local host through an interface
+that has no route through it
+(e.g., after the interface was dropped by
+.Xr routed 8 ) .
+.It Fl S Ar src_addr
+Use the following IP address as the source address in outgoing packets.
+On hosts with more than one IP address, this option can be used to
+force the source address to be something other than the IP address
+of the interface the probe packet is sent on.
+If the IP address
+is not one of this machine's interface addresses, an error is
+returned and nothing is sent.
+.It Fl s Ar packetsize
+Specify the number of data bytes to be sent.
+The default is 56, which translates into 64
+.Tn ICMP
+data bytes when combined
+with the 8 bytes of
+.Tn ICMP
+header data.
+Only the super-user may specify values more than default.
+This option cannot be used with ping sweeps.
+.It Fl T Ar ttl
+Set the IP Time To Live for multicasted packets.
+This flag only applies if the ping destination is a multicast address.
+.It Fl t Ar timeout
+Specify a timeout, in seconds, before ping exits regardless of how
+many packets have been received.
+.It Fl v
+Verbose output.
+.Tn ICMP
+packets other than
+.Tn ECHO_RESPONSE
+that are received are listed.
+.It Fl W Ar waittime
+Time in milliseconds to wait for a reply for each packet sent.
+If a reply arrives later, the packet is not printed as replied, but
+considered as replied when calculating statistics.
+.It Fl z Ar tos
+Use the specified type of service.
+.El
+.Pp
+When using
+.Nm
+for fault isolation, it should first be run on the local host, to verify
+that the local network interface is up and running.
+Then, hosts and gateways further and further away should be
+.Dq pinged .
+Round-trip times and packet loss statistics are computed.
+If duplicate packets are received, they are not included in the packet
+loss calculation, although the round trip time of these packets is used
+in calculating the round-trip time statistics.
+When the specified number of packets have been sent
+(and received)
+or if the program is terminated with a
+.Dv SIGINT ,
+a brief summary is displayed, showing the number of packets sent and
+received, and the minimum, mean, maximum, and standard deviation of
+the round-trip times.
+.Pp
+If
+.Nm
+receives a
+.Dv SIGINFO
+(see the
+.Cm status
+argument for
+.Xr stty 1 )
+signal, the current number of packets sent and received, and the
+minimum, mean, and maximum of the round-trip times will be written to
+the standard error output.
+.Pp
+This program is intended for use in network testing, measurement and
+management.
+Because of the load it can impose on the network, it is unwise to use
+.Nm
+during normal operations or from automated scripts.
+.Sh ICMP PACKET DETAILS
+An IP header without options is 20 bytes.
+An
+.Tn ICMP
+.Tn ECHO_REQUEST
+packet contains an additional 8 bytes worth of
+.Tn ICMP
+header followed by an arbitrary amount of data.
+When a
+.Ar packetsize
+is given, this indicated the size of this extra piece of data
+(the default is 56).
+Thus the amount of data received inside of an IP packet of type
+.Tn ICMP
+.Tn ECHO_REPLY
+will always be 8 bytes more than the requested data space
+(the
+.Tn ICMP
+header).
+.Pp
+If the data space is at least eight bytes large,
+.Nm
+uses the first eight bytes of this space to include a timestamp which
+it uses in the computation of round trip times.
+If less than eight bytes of pad are specified, no round trip times are
+given.
+.Sh DUPLICATE AND DAMAGED PACKETS
+The
+.Nm
+utility will report duplicate and damaged packets.
+Duplicate packets should never occur when pinging a unicast address,
+and seem to be caused by
+inappropriate link-level retransmissions.
+Duplicates may occur in many situations and are rarely
+(if ever)
+a good sign, although the presence of low levels of duplicates may not
+always be cause for alarm.
+Duplicates are expected when pinging a broadcast or multicast address,
+since they are not really duplicates but replies from different hosts
+to the same request.
+.Pp
+Damaged packets are obviously serious cause for alarm and often
+indicate broken hardware somewhere in the
+.Nm
+packet's path (in the network or in the hosts).
+.Sh TRYING DIFFERENT DATA PATTERNS
+The
+(inter)network
+layer should never treat packets differently depending on the data
+contained in the data portion.
+Unfortunately, data-dependent problems have been known to sneak into
+networks and remain undetected for long periods of time.
+In many cases the particular pattern that will have problems is something
+that does not have sufficient
+.Dq transitions ,
+such as all ones or all zeros, or a pattern right at the edge, such as
+almost all zeros.
+It is not
+necessarily enough to specify a data pattern of all zeros (for example)
+on the command line because the pattern that is of interest is
+at the data link level, and the relationship between what you type and
+what the controllers transmit can be complicated.
+.Pp
+This means that if you have a data-dependent problem you will probably
+have to do a lot of testing to find it.
+If you are lucky, you may manage to find a file that either
+cannot
+be sent across your network or that takes much longer to transfer than
+other similar length files.
+You can then examine this file for repeated patterns that you can test
+using the
+.Fl p
+option of
+.Nm .
+.Sh TTL DETAILS
+The
+.Tn TTL
+value of an IP packet represents the maximum number of IP routers
+that the packet can go through before being thrown away.
+In current practice you can expect each router in the Internet to decrement
+the
+.Tn TTL
+field by exactly one.
+.Pp
+The
+.Tn TCP/IP
+specification recommends setting the
+.Tn TTL
+field for
+.Tn IP
+packets to 64, but many systems use smaller values
+.No ( Bx 4.3
+uses 30,
+.Bx 4.2
+used 15).
+.Pp
+The maximum possible value of this field is 255, and most
+.Ux
+systems set
+the
+.Tn TTL
+field of
+.Tn ICMP ECHO_REQUEST
+packets to 255.
+This is why you will find you can
+.Dq ping
+some hosts, but not reach them with
+.Xr telnet 1
+or
+.Xr ftp 1 .
+.Pp
+In normal operation
+.Nm
+prints the ttl value from the packet it receives.
+When a remote system receives a ping packet, it can do one of three things
+with the
+.Tn TTL
+field in its response:
+.Bl -bullet
+.It
+Not change it; this is what
+.Bx
+systems did before the
+.Bx 4.3 tahoe
+release.
+In this case the
+.Tn TTL
+value in the received packet will be 255 minus the
+number of routers in the round-trip path.
+.It
+Set it to 255; this is what current
+.Bx
+systems do.
+In this case the
+.Tn TTL
+value in the received packet will be 255 minus the
+number of routers in the path
+.Em from
+the remote system
+.Em to
+the
+.Nm Ns Em ing
+host.
+.It
+Set it to some other value.
+Some machines use the same value for
+.Tn ICMP
+packets that they use for
+.Tn TCP
+packets, for example either 30 or 60.
+Others may use completely wild values.
+.El
+.Sh EXIT STATUS
+The
+.Nm
+utility exits with one of the following values:
+.Bl -tag -width indent
+.It 0
+At least one response was heard from the specified
+.Ar host .
+.It 2
+The transmission was successful but no responses were received.
+.It any other value
+An error occurred.
+These values are defined in
+.In sysexits.h .
+.El
+.Sh SEE ALSO
+.Xr netstat 1 ,
+.Xr ifconfig 8 ,
+.Xr routed 8 ,
+.Xr traceroute 8
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Bx 4.3 .
+.Sh AUTHORS
+The original
+.Nm
+utility was written by
+.An Mike Muuss
+while at the US Army Ballistics
+Research Laboratory.
+.Sh BUGS
+Many Hosts and Gateways ignore the
+.Tn RECORD_ROUTE
+option.
+.Pp
+The maximum IP header length is too small for options like
+.Tn RECORD_ROUTE
+to be completely useful.
+.No There Ap s
+not much that can be done about this, however.
+.Pp
+Flood pinging is not recommended in general, and flood pinging the
+broadcast address should only be done under very controlled conditions.
+.Pp
+The
+.Fl v
+option is not worth much on busy hosts.
diff --git a/sbin/ping/ping.c b/sbin/ping/ping.c
new file mode 100644
index 0000000..f24ecde
--- /dev/null
+++ b/sbin/ping/ping.c
@@ -0,0 +1,1843 @@
+/*
+ * Copyright (c) 1989, 1993
+ * 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.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if 0
+#ifndef lint
+static const char copyright[] =
+"@(#) Copyright (c) 1989, 1993\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static char sccsid[] = "@(#)ping.c 8.1 (Berkeley) 6/5/93";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*
+ * P I N G . C
+ *
+ * Using the Internet Control Message Protocol (ICMP) "ECHO" facility,
+ * measure round-trip-delays and packet loss across network paths.
+ *
+ * Author -
+ * Mike Muuss
+ * U. S. Army Ballistic Research Laboratory
+ * December, 1983
+ *
+ * Status -
+ * Public Domain. Distribution Unlimited.
+ * Bugs -
+ * More statistics could always be gathered.
+ * This program has to run SUID to ROOT to access the ICMP socket.
+ */
+
+#include <sys/param.h> /* NB: we rely on this for <sys/types.h> */
+#include <sys/capsicum.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+#include <sys/time.h>
+#include <sys/uio.h>
+
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <netinet/ip_icmp.h>
+#include <netinet/ip_var.h>
+#include <arpa/inet.h>
+#ifdef HAVE_LIBCAPSICUM
+#include <libcapsicum.h>
+#include <libcapsicum_dns.h>
+#include <libcapsicum_service.h>
+#endif
+
+#ifdef IPSEC
+#include <netipsec/ipsec.h>
+#endif /*IPSEC*/
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <math.h>
+#include <netdb.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#define INADDR_LEN ((int)sizeof(in_addr_t))
+#define TIMEVAL_LEN ((int)sizeof(struct tv32))
+#define MASK_LEN (ICMP_MASKLEN - ICMP_MINLEN)
+#define TS_LEN (ICMP_TSLEN - ICMP_MINLEN)
+#define DEFDATALEN 56 /* default data length */
+#define FLOOD_BACKOFF 20000 /* usecs to back off if F_FLOOD mode */
+ /* runs out of buffer space */
+#define MAXIPLEN (sizeof(struct ip) + MAX_IPOPTLEN)
+#define MAXICMPLEN (ICMP_ADVLENMIN + MAX_IPOPTLEN)
+#define MAXWAIT 10000 /* max ms to wait for response */
+#define MAXALARM (60 * 60) /* max seconds for alarm timeout */
+#define MAXTOS 255
+
+#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))
+
+struct tv32 {
+ int32_t tv32_sec;
+ int32_t tv32_usec;
+};
+
+/* various options */
+static int options;
+#define F_FLOOD 0x0001
+#define F_INTERVAL 0x0002
+#define F_NUMERIC 0x0004
+#define F_PINGFILLED 0x0008
+#define F_QUIET 0x0010
+#define F_RROUTE 0x0020
+#define F_SO_DEBUG 0x0040
+#define F_SO_DONTROUTE 0x0080
+#define F_VERBOSE 0x0100
+#define F_QUIET2 0x0200
+#define F_NOLOOP 0x0400
+#define F_MTTL 0x0800
+#define F_MIF 0x1000
+#define F_AUDIBLE 0x2000
+#ifdef IPSEC
+#ifdef IPSEC_POLICY_IPSEC
+#define F_POLICY 0x4000
+#endif /*IPSEC_POLICY_IPSEC*/
+#endif /*IPSEC*/
+#define F_TTL 0x8000
+#define F_MISSED 0x10000
+#define F_ONCE 0x20000
+#define F_HDRINCL 0x40000
+#define F_MASK 0x80000
+#define F_TIME 0x100000
+#define F_SWEEP 0x200000
+#define F_WAITTIME 0x400000
+
+/*
+ * MAX_DUP_CHK is the number of bits in received table, i.e. the maximum
+ * number of received sequence numbers we can keep track of. Change 128
+ * to 8192 for complete accuracy...
+ */
+#define MAX_DUP_CHK (8 * 128)
+static int mx_dup_ck = MAX_DUP_CHK;
+static char rcvd_tbl[MAX_DUP_CHK / 8];
+
+static struct sockaddr_in whereto; /* who to ping */
+static int datalen = DEFDATALEN;
+static int maxpayload;
+static int ssend; /* send socket file descriptor */
+static int srecv; /* receive socket file descriptor */
+static u_char outpackhdr[IP_MAXPACKET], *outpack;
+static char BBELL = '\a'; /* characters written for MISSED and AUDIBLE */
+static char BSPACE = '\b'; /* characters written for flood */
+static char DOT = '.';
+static char *hostname;
+static char *shostname;
+static int ident; /* process id to identify our packets */
+static int uid; /* cached uid for micro-optimization */
+static u_char icmp_type = ICMP_ECHO;
+static u_char icmp_type_rsp = ICMP_ECHOREPLY;
+static int phdr_len = 0;
+static int send_len;
+
+/* counters */
+static long nmissedmax; /* max value of ntransmitted - nreceived - 1 */
+static long npackets; /* max packets to transmit */
+static long nreceived; /* # of packets we got back */
+static long nrepeats; /* number of duplicates */
+static long ntransmitted; /* sequence # for outbound packets = #sent */
+static long snpackets; /* max packets to transmit in one sweep */
+static long sntransmitted; /* # of packets we sent in this sweep */
+static int sweepmax; /* max value of payload in sweep */
+static int sweepmin = 0; /* start value of payload in sweep */
+static int sweepincr = 1; /* payload increment in sweep */
+static int interval = 1000; /* interval between packets, ms */
+static int waittime = MAXWAIT; /* timeout for each packet */
+static long nrcvtimeout = 0; /* # of packets we got back after waittime */
+
+/* timing */
+static int timing; /* flag to do timing */
+static double tmin = 999999999.0; /* minimum round trip time */
+static double tmax = 0.0; /* maximum round trip time */
+static double tsum = 0.0; /* sum of all times, for doing average */
+static double tsumsq = 0.0; /* sum of all times squared, for std. dev. */
+
+/* nonzero if we've been told to finish up */
+static volatile sig_atomic_t finish_up;
+static volatile sig_atomic_t siginfo_p;
+
+#ifdef HAVE_LIBCAPSICUM
+static cap_channel_t *capdns;
+#endif
+
+static void fill(char *, char *);
+static u_short in_cksum(u_short *, int);
+#ifdef HAVE_LIBCAPSICUM
+static cap_channel_t *capdns_setup(void);
+#endif
+static void check_status(void);
+static void finish(void) __dead2;
+static void pinger(void);
+static char *pr_addr(struct in_addr);
+static char *pr_ntime(n_time);
+static void pr_icmph(struct icmp *);
+static void pr_iph(struct ip *);
+static void pr_pack(char *, int, struct sockaddr_in *, struct timeval *);
+static void pr_retip(struct ip *);
+static void status(int);
+static void stopit(int);
+static void tvsub(struct timeval *, const struct timeval *);
+static void usage(void) __dead2;
+
+int
+main(int argc, char *const *argv)
+{
+ struct sockaddr_in from, sock_in;
+ struct in_addr ifaddr;
+ struct timeval last, intvl;
+ struct iovec iov;
+ struct ip *ip;
+ struct msghdr msg;
+ struct sigaction si_sa;
+ size_t sz;
+ u_char *datap, packet[IP_MAXPACKET] __aligned(4);
+ char *ep, *source, *target, *payload;
+ struct hostent *hp;
+#ifdef IPSEC_POLICY_IPSEC
+ char *policy_in, *policy_out;
+#endif
+ struct sockaddr_in *to;
+ double t;
+ u_long alarmtimeout, ultmp;
+ int almost_done, ch, df, hold, i, icmp_len, mib[4], preload;
+ int ssend_errno, srecv_errno, tos, ttl;
+ char ctrl[CMSG_SPACE(sizeof(struct timeval))];
+ char hnamebuf[MAXHOSTNAMELEN], snamebuf[MAXHOSTNAMELEN];
+#ifdef IP_OPTIONS
+ char rspace[MAX_IPOPTLEN]; /* record route space */
+#endif
+ unsigned char loop, mttl;
+
+ payload = source = NULL;
+#ifdef IPSEC_POLICY_IPSEC
+ policy_in = policy_out = NULL;
+#endif
+ cap_rights_t rights;
+ bool cansandbox;
+
+ /*
+ * Do the stuff that we need root priv's for *first*, and
+ * then drop our setuid bit. Save error reporting for
+ * after arg parsing.
+ *
+ * Historicaly ping was using one socket 's' for sending and for
+ * receiving. After capsicum(4) related changes we use two
+ * sockets. It was done for special ping use case - when user
+ * issue ping on multicast or broadcast address replies come
+ * from different addresses, not from the address we
+ * connect(2)'ed to, and send socket do not receive those
+ * packets.
+ */
+ ssend = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
+ ssend_errno = errno;
+ srecv = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
+ srecv_errno = errno;
+
+ if (setuid(getuid()) != 0)
+ err(EX_NOPERM, "setuid() failed");
+ uid = getuid();
+
+ alarmtimeout = df = preload = tos = 0;
+
+ outpack = outpackhdr + sizeof(struct ip);
+ while ((ch = getopt(argc, argv,
+ "Aac:DdfG:g:h:I:i:Ll:M:m:nop:QqRrS:s:T:t:vW:z:"
+#ifdef IPSEC
+#ifdef IPSEC_POLICY_IPSEC
+ "P:"
+#endif /*IPSEC_POLICY_IPSEC*/
+#endif /*IPSEC*/
+ )) != -1)
+ {
+ switch(ch) {
+ case 'A':
+ options |= F_MISSED;
+ break;
+ case 'a':
+ options |= F_AUDIBLE;
+ break;
+ case 'c':
+ ultmp = strtoul(optarg, &ep, 0);
+ if (*ep || ep == optarg || ultmp > LONG_MAX || !ultmp)
+ errx(EX_USAGE,
+ "invalid count of packets to transmit: `%s'",
+ optarg);
+ npackets = ultmp;
+ break;
+ case 'D':
+ options |= F_HDRINCL;
+ df = 1;
+ break;
+ case 'd':
+ options |= F_SO_DEBUG;
+ break;
+ case 'f':
+ if (uid) {
+ errno = EPERM;
+ err(EX_NOPERM, "-f flag");
+ }
+ options |= F_FLOOD;
+ setbuf(stdout, (char *)NULL);
+ break;
+ case 'G': /* Maximum packet size for ping sweep */
+ ultmp = strtoul(optarg, &ep, 0);
+ if (*ep || ep == optarg)
+ errx(EX_USAGE, "invalid packet size: `%s'",
+ optarg);
+ if (uid != 0 && ultmp > DEFDATALEN) {
+ errno = EPERM;
+ err(EX_NOPERM,
+ "packet size too large: %lu > %u",
+ ultmp, DEFDATALEN);
+ }
+ options |= F_SWEEP;
+ sweepmax = ultmp;
+ break;
+ case 'g': /* Minimum packet size for ping sweep */
+ ultmp = strtoul(optarg, &ep, 0);
+ if (*ep || ep == optarg)
+ errx(EX_USAGE, "invalid packet size: `%s'",
+ optarg);
+ if (uid != 0 && ultmp > DEFDATALEN) {
+ errno = EPERM;
+ err(EX_NOPERM,
+ "packet size too large: %lu > %u",
+ ultmp, DEFDATALEN);
+ }
+ options |= F_SWEEP;
+ sweepmin = ultmp;
+ break;
+ case 'h': /* Packet size increment for ping sweep */
+ ultmp = strtoul(optarg, &ep, 0);
+ if (*ep || ep == optarg || ultmp < 1)
+ errx(EX_USAGE, "invalid increment size: `%s'",
+ optarg);
+ if (uid != 0 && ultmp > DEFDATALEN) {
+ errno = EPERM;
+ err(EX_NOPERM,
+ "packet size too large: %lu > %u",
+ ultmp, DEFDATALEN);
+ }
+ options |= F_SWEEP;
+ sweepincr = ultmp;
+ break;
+ case 'I': /* multicast interface */
+ if (inet_aton(optarg, &ifaddr) == 0)
+ errx(EX_USAGE,
+ "invalid multicast interface: `%s'",
+ optarg);
+ options |= F_MIF;
+ break;
+ case 'i': /* wait between sending packets */
+ t = strtod(optarg, &ep) * 1000.0;
+ if (*ep || ep == optarg || t > (double)INT_MAX)
+ errx(EX_USAGE, "invalid timing interval: `%s'",
+ optarg);
+ options |= F_INTERVAL;
+ interval = (int)t;
+ if (uid && interval < 1000) {
+ errno = EPERM;
+ err(EX_NOPERM, "-i interval too short");
+ }
+ break;
+ case 'L':
+ options |= F_NOLOOP;
+ loop = 0;
+ break;
+ case 'l':
+ ultmp = strtoul(optarg, &ep, 0);
+ if (*ep || ep == optarg || ultmp > INT_MAX)
+ errx(EX_USAGE,
+ "invalid preload value: `%s'", optarg);
+ if (uid) {
+ errno = EPERM;
+ err(EX_NOPERM, "-l flag");
+ }
+ preload = ultmp;
+ break;
+ case 'M':
+ switch(optarg[0]) {
+ case 'M':
+ case 'm':
+ options |= F_MASK;
+ break;
+ case 'T':
+ case 't':
+ options |= F_TIME;
+ break;
+ default:
+ errx(EX_USAGE, "invalid message: `%c'", optarg[0]);
+ break;
+ }
+ break;
+ case 'm': /* TTL */
+ ultmp = strtoul(optarg, &ep, 0);
+ if (*ep || ep == optarg || ultmp > MAXTTL)
+ errx(EX_USAGE, "invalid TTL: `%s'", optarg);
+ ttl = ultmp;
+ options |= F_TTL;
+ break;
+ case 'n':
+ options |= F_NUMERIC;
+ break;
+ case 'o':
+ options |= F_ONCE;
+ break;
+#ifdef IPSEC
+#ifdef IPSEC_POLICY_IPSEC
+ case 'P':
+ options |= F_POLICY;
+ if (!strncmp("in", optarg, 2))
+ policy_in = strdup(optarg);
+ else if (!strncmp("out", optarg, 3))
+ policy_out = strdup(optarg);
+ else
+ errx(1, "invalid security policy");
+ break;
+#endif /*IPSEC_POLICY_IPSEC*/
+#endif /*IPSEC*/
+ case 'p': /* fill buffer with user pattern */
+ options |= F_PINGFILLED;
+ payload = optarg;
+ break;
+ case 'Q':
+ options |= F_QUIET2;
+ break;
+ case 'q':
+ options |= F_QUIET;
+ break;
+ case 'R':
+ options |= F_RROUTE;
+ break;
+ case 'r':
+ options |= F_SO_DONTROUTE;
+ break;
+ case 'S':
+ source = optarg;
+ break;
+ case 's': /* size of packet to send */
+ ultmp = strtoul(optarg, &ep, 0);
+ if (*ep || ep == optarg)
+ errx(EX_USAGE, "invalid packet size: `%s'",
+ optarg);
+ if (uid != 0 && ultmp > DEFDATALEN) {
+ errno = EPERM;
+ err(EX_NOPERM,
+ "packet size too large: %lu > %u",
+ ultmp, DEFDATALEN);
+ }
+ datalen = ultmp;
+ break;
+ case 'T': /* multicast TTL */
+ ultmp = strtoul(optarg, &ep, 0);
+ if (*ep || ep == optarg || ultmp > MAXTTL)
+ errx(EX_USAGE, "invalid multicast TTL: `%s'",
+ optarg);
+ mttl = ultmp;
+ options |= F_MTTL;
+ break;
+ case 't':
+ alarmtimeout = strtoul(optarg, &ep, 0);
+ if ((alarmtimeout < 1) || (alarmtimeout == ULONG_MAX))
+ errx(EX_USAGE, "invalid timeout: `%s'",
+ optarg);
+ if (alarmtimeout > MAXALARM)
+ errx(EX_USAGE, "invalid timeout: `%s' > %d",
+ optarg, MAXALARM);
+ alarm((int)alarmtimeout);
+ break;
+ case 'v':
+ options |= F_VERBOSE;
+ break;
+ case 'W': /* wait ms for answer */
+ t = strtod(optarg, &ep);
+ if (*ep || ep == optarg || t > (double)INT_MAX)
+ errx(EX_USAGE, "invalid timing interval: `%s'",
+ optarg);
+ options |= F_WAITTIME;
+ waittime = (int)t;
+ break;
+ case 'z':
+ options |= F_HDRINCL;
+ ultmp = strtoul(optarg, &ep, 0);
+ if (*ep || ep == optarg || ultmp > MAXTOS)
+ errx(EX_USAGE, "invalid TOS: `%s'", optarg);
+ tos = ultmp;
+ break;
+ default:
+ usage();
+ }
+ }
+
+ if (argc - optind != 1)
+ usage();
+ target = argv[optind];
+
+ switch (options & (F_MASK|F_TIME)) {
+ case 0: break;
+ case F_MASK:
+ icmp_type = ICMP_MASKREQ;
+ icmp_type_rsp = ICMP_MASKREPLY;
+ phdr_len = MASK_LEN;
+ if (!(options & F_QUIET))
+ (void)printf("ICMP_MASKREQ\n");
+ break;
+ case F_TIME:
+ icmp_type = ICMP_TSTAMP;
+ icmp_type_rsp = ICMP_TSTAMPREPLY;
+ phdr_len = TS_LEN;
+ if (!(options & F_QUIET))
+ (void)printf("ICMP_TSTAMP\n");
+ break;
+ default:
+ errx(EX_USAGE, "ICMP_TSTAMP and ICMP_MASKREQ are exclusive.");
+ break;
+ }
+ icmp_len = sizeof(struct ip) + ICMP_MINLEN + phdr_len;
+ if (options & F_RROUTE)
+ icmp_len += MAX_IPOPTLEN;
+ maxpayload = IP_MAXPACKET - icmp_len;
+ if (datalen > maxpayload)
+ errx(EX_USAGE, "packet size too large: %d > %d", datalen,
+ maxpayload);
+ send_len = icmp_len + datalen;
+ datap = &outpack[ICMP_MINLEN + phdr_len + TIMEVAL_LEN];
+ if (options & F_PINGFILLED) {
+ fill((char *)datap, payload);
+ }
+#ifdef HAVE_LIBCAPSICUM
+ capdns = capdns_setup();
+#endif
+ if (source) {
+ bzero((char *)&sock_in, sizeof(sock_in));
+ sock_in.sin_family = AF_INET;
+ if (inet_aton(source, &sock_in.sin_addr) != 0) {
+ shostname = source;
+ } else {
+#ifdef HAVE_LIBCAPSICUM
+ if (capdns != NULL)
+ hp = cap_gethostbyname2(capdns, source,
+ AF_INET);
+ else
+#endif
+ hp = gethostbyname2(source, AF_INET);
+ if (!hp)
+ errx(EX_NOHOST, "cannot resolve %s: %s",
+ source, hstrerror(h_errno));
+
+ sock_in.sin_len = sizeof sock_in;
+ if ((unsigned)hp->h_length > sizeof(sock_in.sin_addr) ||
+ hp->h_length < 0)
+ errx(1, "gethostbyname2: illegal address");
+ memcpy(&sock_in.sin_addr, hp->h_addr_list[0],
+ sizeof(sock_in.sin_addr));
+ (void)strncpy(snamebuf, hp->h_name,
+ sizeof(snamebuf) - 1);
+ snamebuf[sizeof(snamebuf) - 1] = '\0';
+ shostname = snamebuf;
+ }
+ if (bind(ssend, (struct sockaddr *)&sock_in, sizeof sock_in) ==
+ -1)
+ err(1, "bind");
+ }
+
+ bzero(&whereto, sizeof(whereto));
+ to = &whereto;
+ to->sin_family = AF_INET;
+ to->sin_len = sizeof *to;
+ if (inet_aton(target, &to->sin_addr) != 0) {
+ hostname = target;
+ } else {
+#ifdef HAVE_LIBCAPSICUM
+ if (capdns != NULL)
+ hp = cap_gethostbyname2(capdns, target, AF_INET);
+ else
+#endif
+ hp = gethostbyname2(target, AF_INET);
+ if (!hp)
+ errx(EX_NOHOST, "cannot resolve %s: %s",
+ target, hstrerror(h_errno));
+
+ if ((unsigned)hp->h_length > sizeof(to->sin_addr))
+ errx(1, "gethostbyname2 returned an illegal address");
+ memcpy(&to->sin_addr, hp->h_addr_list[0], sizeof to->sin_addr);
+ (void)strncpy(hnamebuf, hp->h_name, sizeof(hnamebuf) - 1);
+ hnamebuf[sizeof(hnamebuf) - 1] = '\0';
+ hostname = hnamebuf;
+ }
+
+#ifdef HAVE_LIBCAPSICUM
+ /* From now on we will use only reverse DNS lookups. */
+ if (capdns != NULL) {
+ const char *types[1];
+
+ types[0] = "ADDR";
+ if (cap_dns_type_limit(capdns, types, 1) < 0)
+ err(1, "unable to limit access to system.dns service");
+ }
+#endif
+
+ if (ssend < 0) {
+ errno = ssend_errno;
+ err(EX_OSERR, "ssend socket");
+ }
+
+ if (srecv < 0) {
+ errno = srecv_errno;
+ err(EX_OSERR, "srecv socket");
+ }
+
+ if (connect(ssend, (struct sockaddr *)&whereto, sizeof(whereto)) != 0)
+ err(1, "connect");
+
+ if (options & F_FLOOD && options & F_INTERVAL)
+ errx(EX_USAGE, "-f and -i: incompatible options");
+
+ if (options & F_FLOOD && IN_MULTICAST(ntohl(to->sin_addr.s_addr)))
+ errx(EX_USAGE,
+ "-f flag cannot be used with multicast destination");
+ if (options & (F_MIF | F_NOLOOP | F_MTTL)
+ && !IN_MULTICAST(ntohl(to->sin_addr.s_addr)))
+ errx(EX_USAGE,
+ "-I, -L, -T flags cannot be used with unicast destination");
+
+ if (datalen >= TIMEVAL_LEN) /* can we time transfer */
+ timing = 1;
+
+ if (!(options & F_PINGFILLED))
+ for (i = TIMEVAL_LEN; i < datalen; ++i)
+ *datap++ = i;
+
+ ident = getpid() & 0xFFFF;
+
+ hold = 1;
+ if (options & F_SO_DEBUG) {
+ (void)setsockopt(ssend, SOL_SOCKET, SO_DEBUG, (char *)&hold,
+ sizeof(hold));
+ (void)setsockopt(srecv, SOL_SOCKET, SO_DEBUG, (char *)&hold,
+ sizeof(hold));
+ }
+ if (options & F_SO_DONTROUTE)
+ (void)setsockopt(ssend, SOL_SOCKET, SO_DONTROUTE, (char *)&hold,
+ sizeof(hold));
+#ifdef IPSEC
+#ifdef IPSEC_POLICY_IPSEC
+ if (options & F_POLICY) {
+ char *buf;
+ if (policy_in != NULL) {
+ buf = ipsec_set_policy(policy_in, strlen(policy_in));
+ if (buf == NULL)
+ errx(EX_CONFIG, "%s", ipsec_strerror());
+ if (setsockopt(srecv, IPPROTO_IP, IP_IPSEC_POLICY,
+ buf, ipsec_get_policylen(buf)) < 0)
+ err(EX_CONFIG,
+ "ipsec policy cannot be configured");
+ free(buf);
+ }
+
+ if (policy_out != NULL) {
+ buf = ipsec_set_policy(policy_out, strlen(policy_out));
+ if (buf == NULL)
+ errx(EX_CONFIG, "%s", ipsec_strerror());
+ if (setsockopt(ssend, IPPROTO_IP, IP_IPSEC_POLICY,
+ buf, ipsec_get_policylen(buf)) < 0)
+ err(EX_CONFIG,
+ "ipsec policy cannot be configured");
+ free(buf);
+ }
+ }
+#endif /*IPSEC_POLICY_IPSEC*/
+#endif /*IPSEC*/
+
+ if (options & F_HDRINCL) {
+ ip = (struct ip*)outpackhdr;
+ if (!(options & (F_TTL | F_MTTL))) {
+ mib[0] = CTL_NET;
+ mib[1] = PF_INET;
+ mib[2] = IPPROTO_IP;
+ mib[3] = IPCTL_DEFTTL;
+ sz = sizeof(ttl);
+ if (sysctl(mib, 4, &ttl, &sz, NULL, 0) == -1)
+ err(1, "sysctl(net.inet.ip.ttl)");
+ }
+ setsockopt(ssend, IPPROTO_IP, IP_HDRINCL, &hold, sizeof(hold));
+ ip->ip_v = IPVERSION;
+ ip->ip_hl = sizeof(struct ip) >> 2;
+ ip->ip_tos = tos;
+ ip->ip_id = 0;
+ ip->ip_off = htons(df ? IP_DF : 0);
+ ip->ip_ttl = ttl;
+ ip->ip_p = IPPROTO_ICMP;
+ ip->ip_src.s_addr = source ? sock_in.sin_addr.s_addr : INADDR_ANY;
+ ip->ip_dst = to->sin_addr;
+ }
+
+ if (options & F_NUMERIC)
+ cansandbox = true;
+#ifdef HAVE_LIBCAPSICUM
+ else if (capdns != NULL)
+ cansandbox = true;
+#endif
+ else
+ cansandbox = false;
+
+ /*
+ * Here we enter capability mode. Further down access to global
+ * namespaces (e.g filesystem) is restricted (see capsicum(4)).
+ * We must connect(2) our socket before this point.
+ */
+ if (cansandbox && cap_enter() < 0 && errno != ENOSYS)
+ err(1, "cap_enter");
+
+ if (cap_sandboxed())
+ fprintf(stderr, "capability mode sandbox enabled\n");
+
+ cap_rights_init(&rights, CAP_RECV, CAP_EVENT, CAP_SETSOCKOPT);
+ if (cap_rights_limit(srecv, &rights) < 0 && errno != ENOSYS)
+ err(1, "cap_rights_limit srecv");
+
+ cap_rights_init(&rights, CAP_SEND, CAP_SETSOCKOPT);
+ if (cap_rights_limit(ssend, &rights) < 0 && errno != ENOSYS)
+ err(1, "cap_rights_limit ssend");
+
+ /* record route option */
+ if (options & F_RROUTE) {
+#ifdef IP_OPTIONS
+ bzero(rspace, sizeof(rspace));
+ rspace[IPOPT_OPTVAL] = IPOPT_RR;
+ rspace[IPOPT_OLEN] = sizeof(rspace) - 1;
+ rspace[IPOPT_OFFSET] = IPOPT_MINOFF;
+ rspace[sizeof(rspace) - 1] = IPOPT_EOL;
+ if (setsockopt(ssend, IPPROTO_IP, IP_OPTIONS, rspace,
+ sizeof(rspace)) < 0)
+ err(EX_OSERR, "setsockopt IP_OPTIONS");
+#else
+ errx(EX_UNAVAILABLE,
+ "record route not available in this implementation");
+#endif /* IP_OPTIONS */
+ }
+
+ if (options & F_TTL) {
+ if (setsockopt(ssend, IPPROTO_IP, IP_TTL, &ttl,
+ sizeof(ttl)) < 0) {
+ err(EX_OSERR, "setsockopt IP_TTL");
+ }
+ }
+ if (options & F_NOLOOP) {
+ if (setsockopt(ssend, IPPROTO_IP, IP_MULTICAST_LOOP, &loop,
+ sizeof(loop)) < 0) {
+ err(EX_OSERR, "setsockopt IP_MULTICAST_LOOP");
+ }
+ }
+ if (options & F_MTTL) {
+ if (setsockopt(ssend, IPPROTO_IP, IP_MULTICAST_TTL, &mttl,
+ sizeof(mttl)) < 0) {
+ err(EX_OSERR, "setsockopt IP_MULTICAST_TTL");
+ }
+ }
+ if (options & F_MIF) {
+ if (setsockopt(ssend, IPPROTO_IP, IP_MULTICAST_IF, &ifaddr,
+ sizeof(ifaddr)) < 0) {
+ err(EX_OSERR, "setsockopt IP_MULTICAST_IF");
+ }
+ }
+#ifdef SO_TIMESTAMP
+ { int on = 1;
+ if (setsockopt(srecv, SOL_SOCKET, SO_TIMESTAMP, &on, sizeof(on)) < 0)
+ err(EX_OSERR, "setsockopt SO_TIMESTAMP");
+ }
+#endif
+ if (sweepmax) {
+ if (sweepmin >= sweepmax)
+ errx(EX_USAGE, "Maximum packet size must be greater than the minimum packet size");
+
+ if (datalen != DEFDATALEN)
+ errx(EX_USAGE, "Packet size and ping sweep are mutually exclusive");
+
+ if (npackets > 0) {
+ snpackets = npackets;
+ npackets = 0;
+ } else
+ snpackets = 1;
+ datalen = sweepmin;
+ send_len = icmp_len + sweepmin;
+ }
+ if (options & F_SWEEP && !sweepmax)
+ errx(EX_USAGE, "Maximum sweep size must be specified");
+
+ /*
+ * When pinging the broadcast address, you can get a lot of answers.
+ * Doing something so evil is useful if you are trying to stress the
+ * ethernet, or just want to fill the arp cache to get some stuff for
+ * /etc/ethers. But beware: RFC 1122 allows hosts to ignore broadcast
+ * or multicast pings if they wish.
+ */
+
+ /*
+ * XXX receive buffer needs undetermined space for mbuf overhead
+ * as well.
+ */
+ hold = IP_MAXPACKET + 128;
+ (void)setsockopt(srecv, SOL_SOCKET, SO_RCVBUF, (char *)&hold,
+ sizeof(hold));
+ /* CAP_SETSOCKOPT removed */
+ cap_rights_init(&rights, CAP_RECV, CAP_EVENT);
+ if (cap_rights_limit(srecv, &rights) < 0 && errno != ENOSYS)
+ err(1, "cap_rights_limit srecv setsockopt");
+ if (uid == 0)
+ (void)setsockopt(ssend, SOL_SOCKET, SO_SNDBUF, (char *)&hold,
+ sizeof(hold));
+ /* CAP_SETSOCKOPT removed */
+ cap_rights_init(&rights, CAP_SEND);
+ if (cap_rights_limit(ssend, &rights) < 0 && errno != ENOSYS)
+ err(1, "cap_rights_limit ssend setsockopt");
+
+ if (to->sin_family == AF_INET) {
+ (void)printf("PING %s (%s)", hostname,
+ inet_ntoa(to->sin_addr));
+ if (source)
+ (void)printf(" from %s", shostname);
+ if (sweepmax)
+ (void)printf(": (%d ... %d) data bytes\n",
+ sweepmin, sweepmax);
+ else
+ (void)printf(": %d data bytes\n", datalen);
+
+ } else {
+ if (sweepmax)
+ (void)printf("PING %s: (%d ... %d) data bytes\n",
+ hostname, sweepmin, sweepmax);
+ else
+ (void)printf("PING %s: %d data bytes\n", hostname, datalen);
+ }
+
+ /*
+ * Use sigaction() instead of signal() to get unambiguous semantics,
+ * in particular with SA_RESTART not set.
+ */
+
+ sigemptyset(&si_sa.sa_mask);
+ si_sa.sa_flags = 0;
+
+ si_sa.sa_handler = stopit;
+ if (sigaction(SIGINT, &si_sa, 0) == -1) {
+ err(EX_OSERR, "sigaction SIGINT");
+ }
+
+ si_sa.sa_handler = status;
+ if (sigaction(SIGINFO, &si_sa, 0) == -1) {
+ err(EX_OSERR, "sigaction");
+ }
+
+ if (alarmtimeout > 0) {
+ si_sa.sa_handler = stopit;
+ if (sigaction(SIGALRM, &si_sa, 0) == -1)
+ err(EX_OSERR, "sigaction SIGALRM");
+ }
+
+ bzero(&msg, sizeof(msg));
+ msg.msg_name = (caddr_t)&from;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+#ifdef SO_TIMESTAMP
+ msg.msg_control = (caddr_t)ctrl;
+#endif
+ iov.iov_base = packet;
+ iov.iov_len = IP_MAXPACKET;
+
+ if (preload == 0)
+ pinger(); /* send the first ping */
+ else {
+ if (npackets != 0 && preload > npackets)
+ preload = npackets;
+ while (preload--) /* fire off them quickies */
+ pinger();
+ }
+ (void)gettimeofday(&last, NULL);
+
+ if (options & F_FLOOD) {
+ intvl.tv_sec = 0;
+ intvl.tv_usec = 10000;
+ } else {
+ intvl.tv_sec = interval / 1000;
+ intvl.tv_usec = interval % 1000 * 1000;
+ }
+
+ almost_done = 0;
+ while (!finish_up) {
+ struct timeval now, timeout;
+ fd_set rfds;
+ int cc, n;
+
+ check_status();
+ if ((unsigned)srecv >= FD_SETSIZE)
+ errx(EX_OSERR, "descriptor too large");
+ FD_ZERO(&rfds);
+ FD_SET(srecv, &rfds);
+ (void)gettimeofday(&now, NULL);
+ timeout.tv_sec = last.tv_sec + intvl.tv_sec - now.tv_sec;
+ timeout.tv_usec = last.tv_usec + intvl.tv_usec - now.tv_usec;
+ while (timeout.tv_usec < 0) {
+ timeout.tv_usec += 1000000;
+ timeout.tv_sec--;
+ }
+ while (timeout.tv_usec >= 1000000) {
+ timeout.tv_usec -= 1000000;
+ timeout.tv_sec++;
+ }
+ if (timeout.tv_sec < 0)
+ timerclear(&timeout);
+ n = select(srecv + 1, &rfds, NULL, NULL, &timeout);
+ if (n < 0)
+ continue; /* Must be EINTR. */
+ if (n == 1) {
+ struct timeval *tv = NULL;
+#ifdef SO_TIMESTAMP
+ struct cmsghdr *cmsg = (struct cmsghdr *)&ctrl;
+
+ msg.msg_controllen = sizeof(ctrl);
+#endif
+ msg.msg_namelen = sizeof(from);
+ if ((cc = recvmsg(srecv, &msg, 0)) < 0) {
+ if (errno == EINTR)
+ continue;
+ warn("recvmsg");
+ continue;
+ }
+#ifdef SO_TIMESTAMP
+ if (cmsg->cmsg_level == SOL_SOCKET &&
+ cmsg->cmsg_type == SCM_TIMESTAMP &&
+ cmsg->cmsg_len == CMSG_LEN(sizeof *tv)) {
+ /* Copy to avoid alignment problems: */
+ memcpy(&now, CMSG_DATA(cmsg), sizeof(now));
+ tv = &now;
+ }
+#endif
+ if (tv == NULL) {
+ (void)gettimeofday(&now, NULL);
+ tv = &now;
+ }
+ pr_pack((char *)packet, cc, &from, tv);
+ if ((options & F_ONCE && nreceived) ||
+ (npackets && nreceived >= npackets))
+ break;
+ }
+ if (n == 0 || options & F_FLOOD) {
+ if (sweepmax && sntransmitted == snpackets) {
+ for (i = 0; i < sweepincr ; ++i)
+ *datap++ = i;
+ datalen += sweepincr;
+ if (datalen > sweepmax)
+ break;
+ send_len = icmp_len + datalen;
+ sntransmitted = 0;
+ }
+ if (!npackets || ntransmitted < npackets)
+ pinger();
+ else {
+ if (almost_done)
+ break;
+ almost_done = 1;
+ intvl.tv_usec = 0;
+ if (nreceived) {
+ intvl.tv_sec = 2 * tmax / 1000;
+ if (!intvl.tv_sec)
+ intvl.tv_sec = 1;
+ } else {
+ intvl.tv_sec = waittime / 1000;
+ intvl.tv_usec = waittime % 1000 * 1000;
+ }
+ }
+ (void)gettimeofday(&last, NULL);
+ if (ntransmitted - nreceived - 1 > nmissedmax) {
+ nmissedmax = ntransmitted - nreceived - 1;
+ if (options & F_MISSED)
+ (void)write(STDOUT_FILENO, &BBELL, 1);
+ }
+ }
+ }
+ finish();
+ /* NOTREACHED */
+ exit(0); /* Make the compiler happy */
+}
+
+/*
+ * stopit --
+ * Set the global bit that causes the main loop to quit.
+ * Do NOT call finish() from here, since finish() does far too much
+ * to be called from a signal handler.
+ */
+void
+stopit(int sig __unused)
+{
+
+ /*
+ * When doing reverse DNS lookups, the finish_up flag might not
+ * be noticed for a while. Just exit if we get a second SIGINT.
+ */
+ if (!(options & F_NUMERIC) && finish_up)
+ _exit(nreceived ? 0 : 2);
+ finish_up = 1;
+}
+
+/*
+ * pinger --
+ * Compose and transmit an ICMP ECHO REQUEST packet. The IP packet
+ * will be added on by the kernel. The ID field is our UNIX process ID,
+ * and the sequence number is an ascending integer. The first TIMEVAL_LEN
+ * bytes of the data portion are used to hold a UNIX "timeval" struct in
+ * host byte-order, to compute the round-trip time.
+ */
+static void
+pinger(void)
+{
+ struct timeval now;
+ struct tv32 tv32;
+ struct ip *ip;
+ struct icmp *icp;
+ int cc, i;
+ u_char *packet;
+
+ packet = outpack;
+ icp = (struct icmp *)outpack;
+ icp->icmp_type = icmp_type;
+ icp->icmp_code = 0;
+ icp->icmp_cksum = 0;
+ icp->icmp_seq = htons(ntransmitted);
+ icp->icmp_id = ident; /* ID */
+
+ CLR(ntransmitted % mx_dup_ck);
+
+ if ((options & F_TIME) || timing) {
+ (void)gettimeofday(&now, NULL);
+
+ tv32.tv32_sec = htonl(now.tv_sec);
+ tv32.tv32_usec = htonl(now.tv_usec);
+ if (options & F_TIME)
+ icp->icmp_otime = htonl((now.tv_sec % (24*60*60))
+ * 1000 + now.tv_usec / 1000);
+ if (timing)
+ bcopy((void *)&tv32,
+ (void *)&outpack[ICMP_MINLEN + phdr_len],
+ sizeof(tv32));
+ }
+
+ cc = ICMP_MINLEN + phdr_len + datalen;
+
+ /* compute ICMP checksum here */
+ icp->icmp_cksum = in_cksum((u_short *)icp, cc);
+
+ if (options & F_HDRINCL) {
+ cc += sizeof(struct ip);
+ ip = (struct ip *)outpackhdr;
+ ip->ip_len = htons(cc);
+ ip->ip_sum = in_cksum((u_short *)outpackhdr, cc);
+ packet = outpackhdr;
+ }
+ i = send(ssend, (char *)packet, cc, 0);
+ if (i < 0 || i != cc) {
+ if (i < 0) {
+ if (options & F_FLOOD && errno == ENOBUFS) {
+ usleep(FLOOD_BACKOFF);
+ return;
+ }
+ warn("sendto");
+ } else {
+ warn("%s: partial write: %d of %d bytes",
+ hostname, i, cc);
+ }
+ }
+ ntransmitted++;
+ sntransmitted++;
+ if (!(options & F_QUIET) && options & F_FLOOD)
+ (void)write(STDOUT_FILENO, &DOT, 1);
+}
+
+/*
+ * pr_pack --
+ * Print out the packet, if it came from us. This logic is necessary
+ * because ALL readers of the ICMP socket get a copy of ALL ICMP packets
+ * which arrive ('tis only fair). This permits multiple copies of this
+ * program to be run without having intermingled output (or statistics!).
+ */
+static void
+pr_pack(char *buf, int cc, struct sockaddr_in *from, struct timeval *tv)
+{
+ struct in_addr ina;
+ u_char *cp, *dp;
+ struct icmp *icp;
+ struct ip *ip;
+ const void *tp;
+ double triptime;
+ int dupflag, hlen, i, j, recv_len, seq;
+ static int old_rrlen;
+ static char old_rr[MAX_IPOPTLEN];
+
+ /* Check the IP header */
+ ip = (struct ip *)buf;
+ hlen = ip->ip_hl << 2;
+ recv_len = cc;
+ if (cc < hlen + ICMP_MINLEN) {
+ if (options & F_VERBOSE)
+ warn("packet too short (%d bytes) from %s", cc,
+ inet_ntoa(from->sin_addr));
+ return;
+ }
+
+ /* Now the ICMP part */
+ cc -= hlen;
+ icp = (struct icmp *)(buf + hlen);
+ if (icp->icmp_type == icmp_type_rsp) {
+ if (icp->icmp_id != ident)
+ return; /* 'Twas not our ECHO */
+ ++nreceived;
+ triptime = 0.0;
+ if (timing) {
+ struct timeval tv1;
+ struct tv32 tv32;
+#ifndef icmp_data
+ tp = &icp->icmp_ip;
+#else
+ tp = icp->icmp_data;
+#endif
+ tp = (const char *)tp + phdr_len;
+
+ if ((size_t)(cc - ICMP_MINLEN - phdr_len) >=
+ sizeof(tv1)) {
+ /* Copy to avoid alignment problems: */
+ memcpy(&tv32, tp, sizeof(tv32));
+ tv1.tv_sec = ntohl(tv32.tv32_sec);
+ tv1.tv_usec = ntohl(tv32.tv32_usec);
+ tvsub(tv, &tv1);
+ triptime = ((double)tv->tv_sec) * 1000.0 +
+ ((double)tv->tv_usec) / 1000.0;
+ tsum += triptime;
+ tsumsq += triptime * triptime;
+ if (triptime < tmin)
+ tmin = triptime;
+ if (triptime > tmax)
+ tmax = triptime;
+ } else
+ timing = 0;
+ }
+
+ seq = ntohs(icp->icmp_seq);
+
+ if (TST(seq % mx_dup_ck)) {
+ ++nrepeats;
+ --nreceived;
+ dupflag = 1;
+ } else {
+ SET(seq % mx_dup_ck);
+ dupflag = 0;
+ }
+
+ if (options & F_QUIET)
+ return;
+
+ if (options & F_WAITTIME && triptime > waittime) {
+ ++nrcvtimeout;
+ return;
+ }
+
+ if (options & F_FLOOD)
+ (void)write(STDOUT_FILENO, &BSPACE, 1);
+ else {
+ (void)printf("%d bytes from %s: icmp_seq=%u", cc,
+ inet_ntoa(*(struct in_addr *)&from->sin_addr.s_addr),
+ seq);
+ (void)printf(" ttl=%d", ip->ip_ttl);
+ if (timing)
+ (void)printf(" time=%.3f ms", triptime);
+ if (dupflag)
+ (void)printf(" (DUP!)");
+ if (options & F_AUDIBLE)
+ (void)write(STDOUT_FILENO, &BBELL, 1);
+ if (options & F_MASK) {
+ /* Just prentend this cast isn't ugly */
+ (void)printf(" mask=%s",
+ pr_addr(*(struct in_addr *)&(icp->icmp_mask)));
+ }
+ if (options & F_TIME) {
+ (void)printf(" tso=%s", pr_ntime(icp->icmp_otime));
+ (void)printf(" tsr=%s", pr_ntime(icp->icmp_rtime));
+ (void)printf(" tst=%s", pr_ntime(icp->icmp_ttime));
+ }
+ if (recv_len != send_len) {
+ (void)printf(
+ "\nwrong total length %d instead of %d",
+ recv_len, send_len);
+ }
+ /* check the data */
+ cp = (u_char*)&icp->icmp_data[phdr_len];
+ dp = &outpack[ICMP_MINLEN + phdr_len];
+ cc -= ICMP_MINLEN + phdr_len;
+ i = 0;
+ if (timing) { /* don't check variable timestamp */
+ cp += TIMEVAL_LEN;
+ dp += TIMEVAL_LEN;
+ cc -= TIMEVAL_LEN;
+ i += TIMEVAL_LEN;
+ }
+ for (; i < datalen && cc > 0; ++i, ++cp, ++dp, --cc) {
+ if (*cp != *dp) {
+ (void)printf("\nwrong data byte #%d should be 0x%x but was 0x%x",
+ i, *dp, *cp);
+ (void)printf("\ncp:");
+ cp = (u_char*)&icp->icmp_data[0];
+ for (i = 0; i < datalen; ++i, ++cp) {
+ if ((i % 16) == 8)
+ (void)printf("\n\t");
+ (void)printf("%2x ", *cp);
+ }
+ (void)printf("\ndp:");
+ cp = &outpack[ICMP_MINLEN];
+ for (i = 0; i < datalen; ++i, ++cp) {
+ if ((i % 16) == 8)
+ (void)printf("\n\t");
+ (void)printf("%2x ", *cp);
+ }
+ break;
+ }
+ }
+ }
+ } else {
+ /*
+ * We've got something other than an ECHOREPLY.
+ * See if it's a reply to something that we sent.
+ * We can compare IP destination, protocol,
+ * and ICMP type and ID.
+ *
+ * Only print all the error messages if we are running
+ * as root to avoid leaking information not normally
+ * available to those not running as root.
+ */
+#ifndef icmp_data
+ struct ip *oip = &icp->icmp_ip;
+#else
+ struct ip *oip = (struct ip *)icp->icmp_data;
+#endif
+ struct icmp *oicmp = (struct icmp *)(oip + 1);
+
+ if (((options & F_VERBOSE) && uid == 0) ||
+ (!(options & F_QUIET2) &&
+ (oip->ip_dst.s_addr == whereto.sin_addr.s_addr) &&
+ (oip->ip_p == IPPROTO_ICMP) &&
+ (oicmp->icmp_type == ICMP_ECHO) &&
+ (oicmp->icmp_id == ident))) {
+ (void)printf("%d bytes from %s: ", cc,
+ pr_addr(from->sin_addr));
+ pr_icmph(icp);
+ } else
+ return;
+ }
+
+ /* Display any IP options */
+ cp = (u_char *)buf + sizeof(struct ip);
+
+ for (; hlen > (int)sizeof(struct ip); --hlen, ++cp)
+ switch (*cp) {
+ case IPOPT_EOL:
+ hlen = 0;
+ break;
+ case IPOPT_LSRR:
+ case IPOPT_SSRR:
+ (void)printf(*cp == IPOPT_LSRR ?
+ "\nLSRR: " : "\nSSRR: ");
+ j = cp[IPOPT_OLEN] - IPOPT_MINOFF + 1;
+ hlen -= 2;
+ cp += 2;
+ if (j >= INADDR_LEN &&
+ j <= hlen - (int)sizeof(struct ip)) {
+ for (;;) {
+ bcopy(++cp, &ina.s_addr, INADDR_LEN);
+ if (ina.s_addr == 0)
+ (void)printf("\t0.0.0.0");
+ else
+ (void)printf("\t%s",
+ pr_addr(ina));
+ hlen -= INADDR_LEN;
+ cp += INADDR_LEN - 1;
+ j -= INADDR_LEN;
+ if (j < INADDR_LEN)
+ break;
+ (void)putchar('\n');
+ }
+ } else
+ (void)printf("\t(truncated route)\n");
+ break;
+ case IPOPT_RR:
+ j = cp[IPOPT_OLEN]; /* get length */
+ i = cp[IPOPT_OFFSET]; /* and pointer */
+ hlen -= 2;
+ cp += 2;
+ if (i > j)
+ i = j;
+ i = i - IPOPT_MINOFF + 1;
+ if (i < 0 || i > (hlen - (int)sizeof(struct ip))) {
+ old_rrlen = 0;
+ continue;
+ }
+ if (i == old_rrlen
+ && !bcmp((char *)cp, old_rr, i)
+ && !(options & F_FLOOD)) {
+ (void)printf("\t(same route)");
+ hlen -= i;
+ cp += i;
+ break;
+ }
+ old_rrlen = i;
+ bcopy((char *)cp, old_rr, i);
+ (void)printf("\nRR: ");
+ if (i >= INADDR_LEN &&
+ i <= hlen - (int)sizeof(struct ip)) {
+ for (;;) {
+ bcopy(++cp, &ina.s_addr, INADDR_LEN);
+ if (ina.s_addr == 0)
+ (void)printf("\t0.0.0.0");
+ else
+ (void)printf("\t%s",
+ pr_addr(ina));
+ hlen -= INADDR_LEN;
+ cp += INADDR_LEN - 1;
+ i -= INADDR_LEN;
+ if (i < INADDR_LEN)
+ break;
+ (void)putchar('\n');
+ }
+ } else
+ (void)printf("\t(truncated route)");
+ break;
+ case IPOPT_NOP:
+ (void)printf("\nNOP");
+ break;
+ default:
+ (void)printf("\nunknown option %x", *cp);
+ break;
+ }
+ if (!(options & F_FLOOD)) {
+ (void)putchar('\n');
+ (void)fflush(stdout);
+ }
+}
+
+/*
+ * in_cksum --
+ * Checksum routine for Internet Protocol family headers (C Version)
+ */
+u_short
+in_cksum(u_short *addr, int len)
+{
+ int nleft, sum;
+ u_short *w;
+ union {
+ u_short us;
+ u_char uc[2];
+ } last;
+ u_short answer;
+
+ nleft = len;
+ sum = 0;
+ w = addr;
+
+ /*
+ * 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) {
+ last.uc[0] = *(u_char *)w;
+ last.uc[1] = 0;
+ sum += last.us;
+ }
+
+ /* 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);
+}
+
+/*
+ * tvsub --
+ * Subtract 2 timeval structs: out = out - in. Out is assumed to
+ * be >= in.
+ */
+static void
+tvsub(struct timeval *out, const struct timeval *in)
+{
+
+ if ((out->tv_usec -= in->tv_usec) < 0) {
+ --out->tv_sec;
+ out->tv_usec += 1000000;
+ }
+ out->tv_sec -= in->tv_sec;
+}
+
+/*
+ * status --
+ * Print out statistics when SIGINFO is received.
+ */
+
+static void
+status(int sig __unused)
+{
+
+ siginfo_p = 1;
+}
+
+static void
+check_status(void)
+{
+
+ if (siginfo_p) {
+ siginfo_p = 0;
+ (void)fprintf(stderr, "\r%ld/%ld packets received (%.1f%%)",
+ nreceived, ntransmitted,
+ ntransmitted ? nreceived * 100.0 / ntransmitted : 0.0);
+ if (nreceived && timing)
+ (void)fprintf(stderr, " %.3f min / %.3f avg / %.3f max",
+ tmin, tsum / (nreceived + nrepeats), tmax);
+ (void)fprintf(stderr, "\n");
+ }
+}
+
+/*
+ * finish --
+ * Print out statistics, and give up.
+ */
+static void
+finish(void)
+{
+
+ (void)signal(SIGINT, SIG_IGN);
+ (void)signal(SIGALRM, SIG_IGN);
+ (void)putchar('\n');
+ (void)fflush(stdout);
+ (void)printf("--- %s ping statistics ---\n", hostname);
+ (void)printf("%ld packets transmitted, ", ntransmitted);
+ (void)printf("%ld packets received, ", nreceived);
+ if (nrepeats)
+ (void)printf("+%ld duplicates, ", nrepeats);
+ if (ntransmitted) {
+ if (nreceived > ntransmitted)
+ (void)printf("-- somebody's printing up packets!");
+ else
+ (void)printf("%.1f%% packet loss",
+ ((ntransmitted - nreceived) * 100.0) /
+ ntransmitted);
+ }
+ if (nrcvtimeout)
+ (void)printf(", %ld packets out of wait time", nrcvtimeout);
+ (void)putchar('\n');
+ if (nreceived && timing) {
+ double n = nreceived + nrepeats;
+ double avg = tsum / n;
+ double vari = tsumsq / n - avg * avg;
+ (void)printf(
+ "round-trip min/avg/max/stddev = %.3f/%.3f/%.3f/%.3f ms\n",
+ tmin, avg, tmax, sqrt(vari));
+ }
+
+ if (nreceived)
+ exit(0);
+ else
+ exit(2);
+}
+
+#ifdef notdef
+static char *ttab[] = {
+ "Echo Reply", /* ip + seq + udata */
+ "Dest Unreachable", /* net, host, proto, port, frag, sr + IP */
+ "Source Quench", /* IP */
+ "Redirect", /* redirect type, gateway, + IP */
+ "Echo",
+ "Time Exceeded", /* transit, frag reassem + IP */
+ "Parameter Problem", /* pointer + IP */
+ "Timestamp", /* id + seq + three timestamps */
+ "Timestamp Reply", /* " */
+ "Info Request", /* id + sq */
+ "Info Reply" /* " */
+};
+#endif
+
+/*
+ * pr_icmph --
+ * Print a descriptive string about an ICMP header.
+ */
+static void
+pr_icmph(struct icmp *icp)
+{
+
+ switch(icp->icmp_type) {
+ case ICMP_ECHOREPLY:
+ (void)printf("Echo Reply\n");
+ /* XXX ID + Seq + Data */
+ break;
+ case ICMP_UNREACH:
+ switch(icp->icmp_code) {
+ case ICMP_UNREACH_NET:
+ (void)printf("Destination Net Unreachable\n");
+ break;
+ case ICMP_UNREACH_HOST:
+ (void)printf("Destination Host Unreachable\n");
+ break;
+ case ICMP_UNREACH_PROTOCOL:
+ (void)printf("Destination Protocol Unreachable\n");
+ break;
+ case ICMP_UNREACH_PORT:
+ (void)printf("Destination Port Unreachable\n");
+ break;
+ case ICMP_UNREACH_NEEDFRAG:
+ (void)printf("frag needed and DF set (MTU %d)\n",
+ ntohs(icp->icmp_nextmtu));
+ break;
+ case ICMP_UNREACH_SRCFAIL:
+ (void)printf("Source Route Failed\n");
+ break;
+ case ICMP_UNREACH_FILTER_PROHIB:
+ (void)printf("Communication prohibited by filter\n");
+ break;
+ default:
+ (void)printf("Dest Unreachable, Bad Code: %d\n",
+ icp->icmp_code);
+ break;
+ }
+ /* Print returned IP header information */
+#ifndef icmp_data
+ pr_retip(&icp->icmp_ip);
+#else
+ pr_retip((struct ip *)icp->icmp_data);
+#endif
+ break;
+ case ICMP_SOURCEQUENCH:
+ (void)printf("Source Quench\n");
+#ifndef icmp_data
+ pr_retip(&icp->icmp_ip);
+#else
+ pr_retip((struct ip *)icp->icmp_data);
+#endif
+ break;
+ case ICMP_REDIRECT:
+ switch(icp->icmp_code) {
+ case ICMP_REDIRECT_NET:
+ (void)printf("Redirect Network");
+ break;
+ case ICMP_REDIRECT_HOST:
+ (void)printf("Redirect Host");
+ break;
+ case ICMP_REDIRECT_TOSNET:
+ (void)printf("Redirect Type of Service and Network");
+ break;
+ case ICMP_REDIRECT_TOSHOST:
+ (void)printf("Redirect Type of Service and Host");
+ break;
+ default:
+ (void)printf("Redirect, Bad Code: %d", icp->icmp_code);
+ break;
+ }
+ (void)printf("(New addr: %s)\n", inet_ntoa(icp->icmp_gwaddr));
+#ifndef icmp_data
+ pr_retip(&icp->icmp_ip);
+#else
+ pr_retip((struct ip *)icp->icmp_data);
+#endif
+ break;
+ case ICMP_ECHO:
+ (void)printf("Echo Request\n");
+ /* XXX ID + Seq + Data */
+ break;
+ case ICMP_TIMXCEED:
+ switch(icp->icmp_code) {
+ case ICMP_TIMXCEED_INTRANS:
+ (void)printf("Time to live exceeded\n");
+ break;
+ case ICMP_TIMXCEED_REASS:
+ (void)printf("Frag reassembly time exceeded\n");
+ break;
+ default:
+ (void)printf("Time exceeded, Bad Code: %d\n",
+ icp->icmp_code);
+ break;
+ }
+#ifndef icmp_data
+ pr_retip(&icp->icmp_ip);
+#else
+ pr_retip((struct ip *)icp->icmp_data);
+#endif
+ break;
+ case ICMP_PARAMPROB:
+ (void)printf("Parameter problem: pointer = 0x%02x\n",
+ icp->icmp_hun.ih_pptr);
+#ifndef icmp_data
+ pr_retip(&icp->icmp_ip);
+#else
+ pr_retip((struct ip *)icp->icmp_data);
+#endif
+ break;
+ case ICMP_TSTAMP:
+ (void)printf("Timestamp\n");
+ /* XXX ID + Seq + 3 timestamps */
+ break;
+ case ICMP_TSTAMPREPLY:
+ (void)printf("Timestamp Reply\n");
+ /* XXX ID + Seq + 3 timestamps */
+ break;
+ case ICMP_IREQ:
+ (void)printf("Information Request\n");
+ /* XXX ID + Seq */
+ break;
+ case ICMP_IREQREPLY:
+ (void)printf("Information Reply\n");
+ /* XXX ID + Seq */
+ break;
+ case ICMP_MASKREQ:
+ (void)printf("Address Mask Request\n");
+ break;
+ case ICMP_MASKREPLY:
+ (void)printf("Address Mask Reply\n");
+ break;
+ case ICMP_ROUTERADVERT:
+ (void)printf("Router Advertisement\n");
+ break;
+ case ICMP_ROUTERSOLICIT:
+ (void)printf("Router Solicitation\n");
+ break;
+ default:
+ (void)printf("Bad ICMP type: %d\n", icp->icmp_type);
+ }
+}
+
+/*
+ * pr_iph --
+ * Print an IP header with options.
+ */
+static void
+pr_iph(struct ip *ip)
+{
+ u_char *cp;
+ int hlen;
+
+ hlen = ip->ip_hl << 2;
+ cp = (u_char *)ip + 20; /* point to options */
+
+ (void)printf("Vr HL TOS Len ID Flg off TTL Pro cks Src Dst\n");
+ (void)printf(" %1x %1x %02x %04x %04x",
+ ip->ip_v, ip->ip_hl, ip->ip_tos, ntohs(ip->ip_len),
+ ntohs(ip->ip_id));
+ (void)printf(" %1lx %04lx",
+ (u_long) (ntohl(ip->ip_off) & 0xe000) >> 13,
+ (u_long) ntohl(ip->ip_off) & 0x1fff);
+ (void)printf(" %02x %02x %04x", ip->ip_ttl, ip->ip_p,
+ ntohs(ip->ip_sum));
+ (void)printf(" %s ", inet_ntoa(*(struct in_addr *)&ip->ip_src.s_addr));
+ (void)printf(" %s ", inet_ntoa(*(struct in_addr *)&ip->ip_dst.s_addr));
+ /* dump any option bytes */
+ while (hlen-- > 20) {
+ (void)printf("%02x", *cp++);
+ }
+ (void)putchar('\n');
+}
+
+/*
+ * pr_addr --
+ * Return an ascii host address as a dotted quad and optionally with
+ * a hostname.
+ */
+static char *
+pr_addr(struct in_addr ina)
+{
+ struct hostent *hp;
+ static char buf[16 + 3 + MAXHOSTNAMELEN];
+
+ if (options & F_NUMERIC)
+ return inet_ntoa(ina);
+
+#ifdef HAVE_LIBCAPSICUM
+ if (capdns != NULL)
+ hp = cap_gethostbyaddr(capdns, (char *)&ina, 4, AF_INET);
+ else
+#endif
+ hp = gethostbyaddr((char *)&ina, 4, AF_INET);
+
+ if (hp == NULL)
+ return inet_ntoa(ina);
+
+ (void)snprintf(buf, sizeof(buf), "%s (%s)", hp->h_name,
+ inet_ntoa(ina));
+ return(buf);
+}
+
+/*
+ * pr_retip --
+ * Dump some info on a returned (via ICMP) IP packet.
+ */
+static void
+pr_retip(struct ip *ip)
+{
+ u_char *cp;
+ int hlen;
+
+ pr_iph(ip);
+ hlen = ip->ip_hl << 2;
+ cp = (u_char *)ip + hlen;
+
+ if (ip->ip_p == 6)
+ (void)printf("TCP: from port %u, to port %u (decimal)\n",
+ (*cp * 256 + *(cp + 1)), (*(cp + 2) * 256 + *(cp + 3)));
+ else if (ip->ip_p == 17)
+ (void)printf("UDP: from port %u, to port %u (decimal)\n",
+ (*cp * 256 + *(cp + 1)), (*(cp + 2) * 256 + *(cp + 3)));
+}
+
+static char *
+pr_ntime(n_time timestamp)
+{
+ static char buf[10];
+ int hour, min, sec;
+
+ sec = ntohl(timestamp) / 1000;
+ hour = sec / 60 / 60;
+ min = (sec % (60 * 60)) / 60;
+ sec = (sec % (60 * 60)) % 60;
+
+ (void)snprintf(buf, sizeof(buf), "%02d:%02d:%02d", hour, min, sec);
+
+ return (buf);
+}
+
+static void
+fill(char *bp, char *patp)
+{
+ char *cp;
+ int pat[16];
+ u_int ii, jj, kk;
+
+ for (cp = patp; *cp; cp++) {
+ if (!isxdigit(*cp))
+ errx(EX_USAGE,
+ "patterns must be specified as hex digits");
+
+ }
+ ii = sscanf(patp,
+ "%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x",
+ &pat[0], &pat[1], &pat[2], &pat[3], &pat[4], &pat[5], &pat[6],
+ &pat[7], &pat[8], &pat[9], &pat[10], &pat[11], &pat[12],
+ &pat[13], &pat[14], &pat[15]);
+
+ if (ii > 0)
+ for (kk = 0; kk <= maxpayload - (TIMEVAL_LEN + ii); kk += ii)
+ for (jj = 0; jj < ii; ++jj)
+ bp[jj + kk] = pat[jj];
+ if (!(options & F_QUIET)) {
+ (void)printf("PATTERN: 0x");
+ for (jj = 0; jj < ii; ++jj)
+ (void)printf("%02x", bp[jj] & 0xFF);
+ (void)printf("\n");
+ }
+}
+
+#ifdef HAVE_LIBCAPSICUM
+static cap_channel_t *
+capdns_setup(void)
+{
+ cap_channel_t *capcas, *capdnsloc;
+ const char *types[2];
+ int families[1];
+
+ capcas = cap_init();
+ if (capcas == NULL) {
+ warn("unable to contact casperd");
+ return (NULL);
+ }
+ capdnsloc = cap_service_open(capcas, "system.dns");
+ /* Casper capability no longer needed. */
+ cap_close(capcas);
+ if (capdnsloc == NULL)
+ err(1, "unable to open system.dns service");
+ types[0] = "NAME";
+ types[1] = "ADDR";
+ if (cap_dns_type_limit(capdnsloc, types, 2) < 0)
+ err(1, "unable to limit access to system.dns service");
+ families[0] = AF_INET;
+ if (cap_dns_family_limit(capdnsloc, families, 1) < 0)
+ err(1, "unable to limit access to system.dns service");
+
+ return (capdnsloc);
+}
+#endif /* HAVE_LIBCAPSICUM */
+
+#if defined(IPSEC) && defined(IPSEC_POLICY_IPSEC)
+#define SECOPT " [-P policy]"
+#else
+#define SECOPT ""
+#endif
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr, "%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n",
+"usage: ping [-AaDdfnoQqRrv] [-c count] [-G sweepmaxsize] [-g sweepminsize]",
+" [-h sweepincrsize] [-i wait] [-l preload] [-M mask | time] [-m ttl]",
+" " SECOPT " [-p pattern] [-S src_addr] [-s packetsize] [-t timeout]",
+" [-W waittime] [-z tos] host",
+" ping [-AaDdfLnoQqRrv] [-c count] [-I iface] [-i wait] [-l preload]",
+" [-M mask | time] [-m ttl]" SECOPT " [-p pattern] [-S src_addr]",
+" [-s packetsize] [-T ttl] [-t timeout] [-W waittime]",
+" [-z tos] mcast-group");
+ exit(EX_USAGE);
+}
diff --git a/sbin/ping6/Makefile b/sbin/ping6/Makefile
new file mode 100644
index 0000000..35a76e4
--- /dev/null
+++ b/sbin/ping6/Makefile
@@ -0,0 +1,15 @@
+# $FreeBSD$
+
+PROG= ping6
+MAN= ping6.8
+
+CFLAGS+=-DIPSEC -DKAME_SCOPEID -DUSE_RFC2292BIS \
+ -DHAVE_ARC4RANDOM
+WARNS?= 3
+
+BINOWN= root
+BINMODE=4555
+
+LIBADD= ipsec m md
+
+.include <bsd.prog.mk>
diff --git a/sbin/ping6/ping6.8 b/sbin/ping6/ping6.8
new file mode 100644
index 0000000..99111e2
--- /dev/null
+++ b/sbin/ping6/ping6.8
@@ -0,0 +1,556 @@
+.\" $KAME: ping6.8,v 1.58 2003/06/20 12:00:22 itojun Exp $
+.\"
+.\" Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
+.\" 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 project 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 PROJECT 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 PROJECT 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd September 22, 2014
+.Dt PING6 8
+.Os
+.Sh NAME
+.Nm ping6
+.Nd send
+.Tn ICMPv6 ECHO_REQUEST
+packets to network hosts
+.Sh SYNOPSIS
+.Nm
+.\" without ipsec, or new ipsec
+.Op Fl DdfHmnNoqrRtvwW
+.\" old ipsec
+.\" .Op Fl ADdEfmnNqRtvwW
+.Bk -words
+.Op Fl a Ar addrtype
+.Ek
+.Bk -words
+.Op Fl b Ar bufsiz
+.Ek
+.Bk -words
+.Op Fl c Ar count
+.Ek
+.Bk -words
+.Op Fl g Ar gateway
+.Ek
+.Bk -words
+.Op Fl h Ar hoplimit
+.Ek
+.Bk -words
+.Op Fl I Ar interface
+.Ek
+.Bk -words
+.Op Fl i Ar wait
+.Ek
+.Bk -words
+.Op Fl x Ar waittime
+.Ek
+.Bk -words
+.Op Fl X Ar timeout
+.Ek
+.Bk -words
+.Op Fl l Ar preload
+.Ek
+.Bk -words
+.\" new ipsec
+.Op Fl P Ar policy
+.Ek
+.Bk -words
+.Op Fl p Ar pattern
+.Ek
+.Bk -words
+.Op Fl S Ar sourceaddr
+.Ek
+.Bk -words
+.Op Fl s Ar packetsize
+.Ek
+.Bk -words
+.Op Ar hops ...
+.Ek
+.Bk -words
+.Ar host
+.Ek
+.Sh DESCRIPTION
+The
+.Nm
+utility uses the
+.Tn ICMPv6
+protocol's mandatory
+.Tn ICMP6_ECHO_REQUEST
+datagram to elicit an
+.Tn ICMP6_ECHO_REPLY
+from a host or gateway.
+.Tn ICMP6_ECHO_REQUEST
+datagrams (``pings'') have an IPv6 header,
+and
+.Tn ICMPv6
+header formatted as documented in RFC2463.
+The options are as follows:
+.Bl -tag -width Ds
+.\" old ipsec
+.\" .It Fl A
+.\" Enables transport-mode IPsec authentication header
+.\" (experimental).
+.It Fl a Ar addrtype
+Generate ICMPv6 Node Information Node Addresses query, rather than echo-request.
+.Ar addrtype
+must be a string constructed of the following characters.
+.Bl -tag -width Ds -compact
+.It Ic a
+requests unicast addresses from all of the responder's interfaces.
+If the character is omitted,
+only those addresses which belong to the interface which has the
+responder's address are requests.
+.It Ic c
+requests responder's IPv4-compatible and IPv4-mapped addresses.
+.It Ic g
+requests responder's global-scope addresses.
+.It Ic s
+requests responder's site-local addresses.
+.It Ic l
+requests responder's link-local addresses.
+.It Ic A
+requests responder's anycast addresses.
+Without this character, the responder will return unicast addresses only.
+With this character, the responder will return anycast addresses only.
+Note that the specification does not specify how to get responder's
+anycast addresses.
+This is an experimental option.
+.El
+.It Fl b Ar bufsiz
+Set socket buffer size.
+.It Fl c Ar count
+Stop after sending
+(and receiving)
+.Ar count
+.Tn ECHO_RESPONSE
+packets.
+.It Fl D
+Disable IPv6 fragmentation.
+.It Fl d
+Set the
+.Dv SO_DEBUG
+option on the socket being used.
+.\" .It Fl E
+.\" Enables transport-mode IPsec encapsulated security payload
+.\" (experimental).
+.It Fl f
+Flood ping.
+Outputs packets as fast as they come back or one hundred times per second,
+whichever is more.
+For every
+.Tn ECHO_REQUEST
+sent a period
+.Dq \&.
+is printed, while for every
+.Tn ECHO_REPLY
+received a backspace is printed.
+This provides a rapid display of how many packets are being dropped.
+Only the super-user may use this option.
+.Bf -emphasis
+This can be very hard on a network and should be used with caution.
+.Ef
+.It Fl g Ar gateway
+Specifies to use
+.Ar gateway
+as the next hop to the destination.
+The gateway must be a neighbor of the sending node.
+.It Fl H
+Specifies to try reverse-lookup of IPv6 addresses.
+The
+.Nm
+utility does not try reverse-lookup unless the option is specified.
+.It Fl h Ar hoplimit
+Set the IPv6 hoplimit.
+.It Fl I Ar interface
+Source packets with the given interface address.
+This flag applies if the ping destination is a multicast address,
+or link-local/site-local unicast address.
+.It Fl i Ar wait
+Wait
+.Ar wait
+seconds
+.Em between sending each packet .
+The default is to wait for one second between each packet.
+This option is incompatible with the
+.Fl f
+option.
+.It Fl x Ar waittime
+Time in milliseconds to wait for a reply for each packet sent.
+If a reply arrives later,
+the packet is not printed as replied,
+but considered as replied when calculating statistics.
+.It Fl X Ar timeout
+Specify a timeout,
+in seconds,
+before ping exits regardless of how many packets have been received.
+.It Fl l Ar preload
+If
+.Ar preload
+is specified,
+.Nm
+sends that many packets as fast as possible before falling into its normal
+mode of behavior.
+Only the super-user may use this option.
+.It Fl m
+By default,
+.Nm
+asks the kernel to fragment packets to fit into the minimum IPv6 MTU.
+The
+.Fl m
+option
+will suppress the behavior in the following two levels:
+when the option is specified once, the behavior will be disabled for
+unicast packets.
+When the option is more than once, it will be disabled for both
+unicast and multicast packets.
+.It Fl n
+Numeric output only.
+No attempt will be made to lookup symbolic names from addresses in the reply.
+.It Fl N
+Probe node information multicast group address
+.Pq Li ff02::2:ffxx:xxxx .
+.Ar host
+must be string hostname of the target
+(must not be a numeric IPv6 address).
+Node information multicast group will be computed based on given
+.Ar host ,
+and will be used as the final destination.
+Since node information multicast group is a link-local multicast group,
+outgoing interface needs to be specified by
+.Fl I
+option.
+.Pp
+When specified twice, the address
+.Pq Li ff02::2:xxxx:xxxx
+is used instead.
+The former is in RFC 4620, the latter is in an old Internet Draft
+draft-ietf-ipngwg-icmp-name-lookup.
+Note that KAME-derived implementations including
+.Fx
+use the latter.
+.It Fl o
+Exit successfully after receiving one reply packet.
+.It Fl p Ar pattern
+You may specify up to 16
+.Dq pad
+bytes to fill out the packet you send.
+This is useful for diagnosing data-dependent problems in a network.
+For example,
+.Dq Li \-p ff
+will cause the sent packet to be filled with all
+ones.
+.\" new ipsec
+.It Fl P Ar policy
+.Ar policy
+specifies IPsec policy to be used for the probe.
+.It Fl q
+Quiet output.
+Nothing is displayed except the summary lines at startup time and
+when finished.
+.It Fl r
+Audible.
+Include a bell
+.Tn ( ASCII
+0x07)
+character in the output when any packet is received.
+.It Fl R
+Audible.
+Output a bell
+.Tn ( ASCII
+0x07)
+character when no packet is received before the next packet
+is transmitted.
+To cater for round-trip times that are longer than the interval
+between transmissions, further missing packets cause a bell only
+if the maximum number of unreceived packets has increased.
+.It Fl S Ar sourceaddr
+Specifies the source address of request packets.
+The source address must be one of the unicast addresses of the sending node,
+and must be numeric.
+.It Fl s Ar packetsize
+Specifies the number of data bytes to be sent.
+The default is 56, which translates into 64
+.Tn ICMP
+data bytes when combined
+with the 8 bytes of
+.Tn ICMP
+header data.
+You may need to specify
+.Fl b
+as well to extend socket buffer size.
+.It Fl t
+Generate ICMPv6 Node Information supported query types query,
+rather than echo-request.
+.Fl s
+has no effect if
+.Fl t
+is specified.
+.It Fl v
+Verbose output.
+.Tn ICMP
+packets other than
+.Tn ECHO_RESPONSE
+that are received are listed.
+.It Fl w
+Generate ICMPv6 Node Information DNS Name query, rather than echo-request.
+.Fl s
+has no effect if
+.Fl w
+is specified.
+.It Fl W
+Same as
+.Fl w ,
+but with old packet format based on 03 draft.
+This option is present for backward compatibility.
+.Fl s
+has no effect if
+.Fl w
+is specified.
+.It Ar hops
+IPv6 addresses for intermediate nodes,
+which will be put into type 0 routing header.
+.It Ar host
+IPv6 address of the final destination node.
+.El
+.Pp
+When using
+.Nm
+for fault isolation, it should first be run on the local host, to verify
+that the local network interface is up and running.
+Then, hosts and gateways further and further away should be
+.Dq pinged .
+Round-trip times and packet loss statistics are computed.
+If duplicate packets are received, they are not included in the packet
+loss calculation, although the round trip time of these packets is used
+in calculating the round-trip time statistics.
+When the specified number of packets have been sent
+(and received)
+or if the program is terminated with a
+.Dv SIGINT ,
+a brief summary is displayed, showing the number of packets sent and
+received, and the minimum, mean, maximum, and standard deviation of
+the round-trip times.
+.Pp
+If
+.Nm
+receives a
+.Dv SIGINFO
+(see the
+.Cm status
+argument for
+.Xr stty 1 )
+signal, the current number of packets sent and received, and the
+minimum, mean, maximum, and standard deviation of the round-trip times
+will be written to the standard output in the same format as the
+standard completion message.
+.Pp
+This program is intended for use in network testing, measurement and
+management.
+Because of the load it can impose on the network, it is unwise to use
+.Nm
+during normal operations or from automated scripts.
+.\" .Sh ICMP PACKET DETAILS
+.\" An IP header without options is 20 bytes.
+.\" An
+.\" .Tn ICMP
+.\" .Tn ECHO_REQUEST
+.\" packet contains an additional 8 bytes worth of
+.\" .Tn ICMP
+.\" header followed by an arbitrary amount of data.
+.\" When a
+.\" .Ar packetsize
+.\" is given, this indicated the size of this extra piece of data
+.\" (the default is 56).
+.\" Thus the amount of data received inside of an IP packet of type
+.\" .Tn ICMP
+.\" .Tn ECHO_REPLY
+.\" will always be 8 bytes more than the requested data space
+.\" (the
+.\" .Tn ICMP
+.\" header).
+.\" .Pp
+.\" If the data space is at least eight bytes large,
+.\" .Nm
+.\" uses the first eight bytes of this space to include a timestamp which
+.\" it uses in the computation of round trip times.
+.\" If less than eight bytes of pad are specified, no round trip times are
+.\" given.
+.Sh DUPLICATE AND DAMAGED PACKETS
+The
+.Nm
+utility will report duplicate and damaged packets.
+Duplicate packets should never occur when pinging a unicast address,
+and seem to be caused by
+inappropriate link-level retransmissions.
+Duplicates may occur in many situations and are rarely
+(if ever)
+a good sign, although the presence of low levels of duplicates may not
+always be cause for alarm.
+Duplicates are expected when pinging a broadcast or multicast address,
+since they are not really duplicates but replies from different hosts
+to the same request.
+.Pp
+Damaged packets are obviously serious cause for alarm and often
+indicate broken hardware somewhere in the
+.Nm
+packet's path
+(in the network or in the hosts).
+.Sh TRYING DIFFERENT DATA PATTERNS
+The
+(inter)network
+layer should never treat packets differently depending on the data
+contained in the data portion.
+Unfortunately, data-dependent problems have been known to sneak into
+networks and remain undetected for long periods of time.
+In many cases the particular pattern that will have problems is something
+that does not have sufficient
+.Dq transitions ,
+such as all ones or all zeros, or a pattern right at the edge, such as
+almost all zeros.
+It is not
+necessarily enough to specify a data pattern of all zeros (for example)
+on the command line because the pattern that is of interest is
+at the data link level, and the relationship between what you type and
+what the controllers transmit can be complicated.
+.Pp
+This means that if you have a data-dependent problem you will probably
+have to do a lot of testing to find it.
+If you are lucky, you may manage to find a file that either
+cannot
+be sent across your network or that takes much longer to transfer than
+other similar length files.
+You can then examine this file for repeated patterns that you can test
+using the
+.Fl p
+option of
+.Nm .
+.Sh EXIT STATUS
+The
+.Nm
+utility returns 0 on success (the host is alive),
+2 if the transmission was successful but no responses were received,
+any other non-zero value if the arguments are incorrect or
+another error has occurred.
+.Sh EXAMPLES
+Normally,
+.Nm
+works just like
+.Xr ping 8
+would work; the following will send ICMPv6 echo request to
+.Li dst.foo.com .
+.Bd -literal -offset indent
+ping6 -n dst.foo.com
+.Ed
+.Pp
+The following will probe hostnames for all nodes on the network link attached to
+.Li wi0
+interface.
+The address
+.Li ff02::1
+is named the link-local all-node multicast address, and the packet would
+reach every node on the network link.
+.Bd -literal -offset indent
+ping6 -w ff02::1%wi0
+.Ed
+.Pp
+The following will probe addresses assigned to the destination node,
+.Li dst.foo.com .
+.Bd -literal -offset indent
+ping6 -a agl dst.foo.com
+.Ed
+.Sh SEE ALSO
+.Xr netstat 1 ,
+.Xr icmp6 4 ,
+.Xr inet6 4 ,
+.Xr ip6 4 ,
+.Xr ifconfig 8 ,
+.Xr ping 8 ,
+.Xr routed 8 ,
+.Xr traceroute 8 ,
+.Xr traceroute6 8
+.Rs
+.%A A. Conta
+.%A S. Deering
+.%T "Internet Control Message Protocol (ICMPv6) for the Internet Protocol Version 6 (IPv6) Specification"
+.%N RFC2463
+.%D December 1998
+.Re
+.Rs
+.%A Matt Crawford
+.%T "IPv6 Node Information Queries"
+.%N draft-ietf-ipngwg-icmp-name-lookups-09.txt
+.%D May 2002
+.%O work in progress material
+.Re
+.Sh HISTORY
+The
+.Xr ping 8
+utility appeared in
+.Bx 4.3 .
+The
+.Nm
+utility with IPv6 support first appeared in the WIDE Hydrangea IPv6
+protocol stack kit.
+.Pp
+IPv6 and IPsec support based on the KAME Project
+.Pq Pa http://www.kame.net/
+stack was initially integrated into
+.Fx 4.0 .
+.Sh BUGS
+The
+.Nm
+utility
+is intentionally separate from
+.Xr ping 8 .
+.Pp
+There have been many discussions on why we separate
+.Nm
+and
+.Xr ping 8 .
+Some people argued that it would be more convenient to uniform the
+ping command for both IPv4 and IPv6.
+The followings are an answer to the request.
+.Pp
+From a developer's point of view:
+since the underling raw sockets API is totally different between IPv4
+and IPv6, we would end up having two types of code base.
+There would actually be less benefit to uniform the two commands
+into a single command from the developer's standpoint.
+.Pp
+From an operator's point of view: unlike ordinary network applications
+like remote login tools, we are usually aware of address family when using
+network management tools.
+We do not just want to know the reachability to the host, but want to know the
+reachability to the host via a particular network protocol such as
+IPv6.
+Thus, even if we had a unified
+.Xr ping 8
+command for both IPv4 and IPv6, we would usually type a
+.Fl 6
+or
+.Fl 4
+option (or something like those) to specify the particular address family.
+This essentially means that we have two different commands.
diff --git a/sbin/ping6/ping6.c b/sbin/ping6/ping6.c
new file mode 100644
index 0000000..d71c021
--- /dev/null
+++ b/sbin/ping6/ping6.c
@@ -0,0 +1,2793 @@
+/* $KAME: ping6.c,v 1.169 2003/07/25 06:01:47 itojun Exp $ */
+
+/*
+ * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
+ * 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 project 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 PROJECT 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 PROJECT 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.
+ */
+
+/* BSDI ping.c,v 2.3 1996/01/21 17:56:50 jch Exp */
+
+/*
+ * Copyright (c) 1989, 1993
+ * 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.
+ * 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 lint
+static const char copyright[] =
+"@(#) Copyright (c) 1989, 1993\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)ping.c 8.1 (Berkeley) 6/5/93";
+#endif
+#endif /* not lint */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*
+ * Using the InterNet Control Message Protocol (ICMP) "ECHO" facility,
+ * measure round-trip-delays and packet loss across network paths.
+ *
+ * Author -
+ * Mike Muuss
+ * U. S. Army Ballistic Research Laboratory
+ * December, 1983
+ *
+ * Status -
+ * Public Domain. Distribution Unlimited.
+ * Bugs -
+ * More statistics could always be gathered.
+ * This program has to run SUID to ROOT to access the ICMP socket.
+ */
+/*
+ * NOTE:
+ * USE_SIN6_SCOPE_ID assumes that sin6_scope_id has the same semantics
+ * as IPV6_PKTINFO. Some people object it (sin6_scope_id specifies *link*
+ * while IPV6_PKTINFO specifies *interface*. Link is defined as collection of
+ * network attached to 1 or more interfaces)
+ */
+
+#include <sys/param.h>
+#include <sys/uio.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+
+#include <net/if.h>
+#include <net/route.h>
+
+#include <netinet/in.h>
+#include <netinet/ip6.h>
+#include <netinet/icmp6.h>
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+#include <netdb.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <math.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#ifdef IPSEC
+#include <netipsec/ah.h>
+#include <netipsec/ipsec.h>
+#endif
+
+#include <md5.h>
+
+struct tv32 {
+ u_int32_t tv32_sec;
+ u_int32_t tv32_usec;
+};
+
+#define MAXPACKETLEN 131072
+#define IP6LEN 40
+#define ICMP6ECHOLEN 8 /* icmp echo header len excluding time */
+#define ICMP6ECHOTMLEN sizeof(struct tv32)
+#define ICMP6_NIQLEN (ICMP6ECHOLEN + 8)
+# define CONTROLLEN 10240 /* ancillary data buffer size RFC3542 20.1 */
+/* FQDN case, 64 bits of nonce + 32 bits ttl */
+#define ICMP6_NIRLEN (ICMP6ECHOLEN + 12)
+#define EXTRA 256 /* for AH and various other headers. weird. */
+#define DEFDATALEN ICMP6ECHOTMLEN
+#define MAXDATALEN MAXPACKETLEN - IP6LEN - ICMP6ECHOLEN
+#define NROUTES 9 /* number of record route slots */
+#define MAXWAIT 10000 /* max ms to wait for response */
+#define MAXALARM (60 * 60) /* max seconds for alarm timeout */
+
+#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))
+
+#define F_FLOOD 0x0001
+#define F_INTERVAL 0x0002
+#define F_PINGFILLED 0x0008
+#define F_QUIET 0x0010
+#define F_RROUTE 0x0020
+#define F_SO_DEBUG 0x0040
+#define F_VERBOSE 0x0100
+#ifdef IPSEC
+#ifdef IPSEC_POLICY_IPSEC
+#define F_POLICY 0x0400
+#else
+#define F_AUTHHDR 0x0200
+#define F_ENCRYPT 0x0400
+#endif /*IPSEC_POLICY_IPSEC*/
+#endif /*IPSEC*/
+#define F_NODEADDR 0x0800
+#define F_FQDN 0x1000
+#define F_INTERFACE 0x2000
+#define F_SRCADDR 0x4000
+#define F_HOSTNAME 0x10000
+#define F_FQDNOLD 0x20000
+#define F_NIGROUP 0x40000
+#define F_SUPTYPES 0x80000
+#define F_NOMINMTU 0x100000
+#define F_ONCE 0x200000
+#define F_AUDIBLE 0x400000
+#define F_MISSED 0x800000
+#define F_DONTFRAG 0x1000000
+#define F_NOUSERDATA (F_NODEADDR | F_FQDN | F_FQDNOLD | F_SUPTYPES)
+#define F_WAITTIME 0x2000000
+u_int options;
+
+#define IN6LEN sizeof(struct in6_addr)
+#define SA6LEN sizeof(struct sockaddr_in6)
+#define DUMMY_PORT 10101
+
+#define SIN6(s) ((struct sockaddr_in6 *)(s))
+
+/*
+ * MAX_DUP_CHK is the number of bits in received table, i.e. the maximum
+ * number of received sequence numbers we can keep track of. Change 128
+ * to 8192 for complete accuracy...
+ */
+#define MAX_DUP_CHK (8 * 8192)
+static int mx_dup_ck = MAX_DUP_CHK;
+static char rcvd_tbl[MAX_DUP_CHK / 8];
+
+static struct sockaddr_in6 dst; /* who to ping6 */
+static struct sockaddr_in6 src; /* src addr of this packet */
+static socklen_t srclen;
+static size_t datalen = DEFDATALEN;
+static int s; /* socket file descriptor */
+static u_char outpack[MAXPACKETLEN];
+static char BSPACE = '\b'; /* characters written for flood */
+static char BBELL = '\a'; /* characters written for AUDIBLE */
+static char DOT = '.';
+static char *hostname;
+static int ident; /* process id to identify our packets */
+static u_int8_t nonce[8]; /* nonce field for node information */
+static int hoplimit = -1; /* hoplimit */
+static u_char *packet = NULL;
+
+/* counters */
+static long nmissedmax; /* max value of ntransmitted - nreceived - 1 */
+static long npackets; /* max packets to transmit */
+static long nreceived; /* # of packets we got back */
+static long nrepeats; /* number of duplicates */
+static long ntransmitted; /* sequence # for outbound packets = #sent */
+static int interval = 1000; /* interval between packets in ms */
+static int waittime = MAXWAIT; /* timeout for each packet */
+static long nrcvtimeout = 0; /* # of packets we got back after waittime */
+
+/* timing */
+static int timing; /* flag to do timing */
+static double tmin = 999999999.0; /* minimum round trip time */
+static double tmax = 0.0; /* maximum round trip time */
+static double tsum = 0.0; /* sum of all times, for doing average */
+static double tsumsq = 0.0; /* sum of all times squared, for std. dev. */
+
+/* for node addresses */
+static u_short naflags;
+
+/* for ancillary data(advanced API) */
+static struct msghdr smsghdr;
+static struct iovec smsgiov;
+static char *scmsg = 0;
+
+static volatile sig_atomic_t seenint;
+#ifdef SIGINFO
+static volatile sig_atomic_t seeninfo;
+#endif
+
+int main(int, char *[]);
+static void fill(char *, char *);
+static int get_hoplim(struct msghdr *);
+static int get_pathmtu(struct msghdr *);
+static struct in6_pktinfo *get_rcvpktinfo(struct msghdr *);
+static void onsignal(int);
+static void onint(int);
+static size_t pingerlen(void);
+static int pinger(void);
+static const char *pr_addr(struct sockaddr *, int);
+static void pr_icmph(struct icmp6_hdr *, u_char *);
+static void pr_iph(struct ip6_hdr *);
+static void pr_suptypes(struct icmp6_nodeinfo *, size_t);
+static void pr_nodeaddr(struct icmp6_nodeinfo *, int);
+static int myechoreply(const struct icmp6_hdr *);
+static int mynireply(const struct icmp6_nodeinfo *);
+static char *dnsdecode(const u_char **, const u_char *, const u_char *,
+ char *, size_t);
+static void pr_pack(u_char *, int, struct msghdr *);
+static void pr_exthdrs(struct msghdr *);
+static void pr_ip6opt(void *, size_t);
+static void pr_rthdr(void *, size_t);
+static int pr_bitrange(u_int32_t, int, int);
+static void pr_retip(struct ip6_hdr *, u_char *);
+static void summary(void);
+static void tvsub(struct timeval *, struct timeval *);
+static int setpolicy(int, char *);
+static char *nigroup(char *, int);
+static void usage(void);
+
+int
+main(int argc, char *argv[])
+{
+ struct timeval last, intvl;
+ struct sockaddr_in6 from, *sin6;
+#ifndef HAVE_ARC4RANDOM
+ struct timeval seed;
+#endif
+ struct addrinfo hints, *res;
+ struct sigaction si_sa;
+ int cc, i;
+ int almost_done, ch, hold, packlen, preload, optval, error;
+ int nig_oldmcprefix = -1;
+ u_char *datap;
+ char *e, *target, *ifname = NULL, *gateway = NULL;
+ int ip6optlen = 0;
+ struct cmsghdr *scmsgp = NULL;
+ /* For control (ancillary) data received from recvmsg() */
+ struct cmsghdr cm[CONTROLLEN];
+#if defined(SO_SNDBUF) && defined(SO_RCVBUF)
+ u_long lsockbufsize;
+ int sockbufsize = 0;
+#endif
+ int usepktinfo = 0;
+ struct in6_pktinfo *pktinfo = NULL;
+#ifdef USE_RFC2292BIS
+ struct ip6_rthdr *rthdr = NULL;
+#endif
+#ifdef IPSEC_POLICY_IPSEC
+ char *policy_in = NULL;
+ char *policy_out = NULL;
+#endif
+ double t;
+ u_long alarmtimeout;
+ size_t rthlen;
+#ifdef IPV6_USE_MIN_MTU
+ int mflag = 0;
+#endif
+
+ /* just to be sure */
+ memset(&smsghdr, 0, sizeof(smsghdr));
+ memset(&smsgiov, 0, sizeof(smsgiov));
+
+ alarmtimeout = preload = 0;
+ datap = &outpack[ICMP6ECHOLEN + ICMP6ECHOTMLEN];
+#ifndef IPSEC
+#define ADDOPTS
+#else
+#ifdef IPSEC_POLICY_IPSEC
+#define ADDOPTS "P:"
+#else
+#define ADDOPTS "AE"
+#endif /*IPSEC_POLICY_IPSEC*/
+#endif
+ while ((ch = getopt(argc, argv,
+ "a:b:c:DdfHg:h:I:i:l:mnNop:qrRS:s:tvwWx:X:" ADDOPTS)) != -1) {
+#undef ADDOPTS
+ switch (ch) {
+ case 'a':
+ {
+ char *cp;
+
+ options &= ~F_NOUSERDATA;
+ options |= F_NODEADDR;
+ for (cp = optarg; *cp != '\0'; cp++) {
+ switch (*cp) {
+ case 'a':
+ naflags |= NI_NODEADDR_FLAG_ALL;
+ break;
+ case 'c':
+ case 'C':
+ naflags |= NI_NODEADDR_FLAG_COMPAT;
+ break;
+ case 'l':
+ case 'L':
+ naflags |= NI_NODEADDR_FLAG_LINKLOCAL;
+ break;
+ case 's':
+ case 'S':
+ naflags |= NI_NODEADDR_FLAG_SITELOCAL;
+ break;
+ case 'g':
+ case 'G':
+ naflags |= NI_NODEADDR_FLAG_GLOBAL;
+ break;
+ case 'A': /* experimental. not in the spec */
+#ifdef NI_NODEADDR_FLAG_ANYCAST
+ naflags |= NI_NODEADDR_FLAG_ANYCAST;
+ break;
+#else
+ errx(1,
+"-a A is not supported on the platform");
+ /*NOTREACHED*/
+#endif
+ default:
+ usage();
+ /*NOTREACHED*/
+ }
+ }
+ break;
+ }
+ case 'b':
+#if defined(SO_SNDBUF) && defined(SO_RCVBUF)
+ errno = 0;
+ e = NULL;
+ lsockbufsize = strtoul(optarg, &e, 10);
+ sockbufsize = (int)lsockbufsize;
+ if (errno || !*optarg || *e ||
+ lsockbufsize > INT_MAX)
+ errx(1, "invalid socket buffer size");
+#else
+ errx(1,
+"-b option ignored: SO_SNDBUF/SO_RCVBUF socket options not supported");
+#endif
+ break;
+ case 'c':
+ npackets = strtol(optarg, &e, 10);
+ if (npackets <= 0 || *optarg == '\0' || *e != '\0')
+ errx(1,
+ "illegal number of packets -- %s", optarg);
+ break;
+ case 'D':
+ options |= F_DONTFRAG;
+ break;
+ case 'd':
+ options |= F_SO_DEBUG;
+ break;
+ case 'f':
+ if (getuid()) {
+ errno = EPERM;
+ errx(1, "Must be superuser to flood ping");
+ }
+ options |= F_FLOOD;
+ setbuf(stdout, (char *)NULL);
+ break;
+ case 'g':
+ gateway = optarg;
+ break;
+ case 'H':
+ options |= F_HOSTNAME;
+ break;
+ case 'h': /* hoplimit */
+ hoplimit = strtol(optarg, &e, 10);
+ if (*optarg == '\0' || *e != '\0')
+ errx(1, "illegal hoplimit %s", optarg);
+ if (255 < hoplimit || hoplimit < -1)
+ errx(1,
+ "illegal hoplimit -- %s", optarg);
+ break;
+ case 'I':
+ ifname = optarg;
+ options |= F_INTERFACE;
+#ifndef USE_SIN6_SCOPE_ID
+ usepktinfo++;
+#endif
+ break;
+ case 'i': /* wait between sending packets */
+ t = strtod(optarg, &e);
+ if (*optarg == '\0' || *e != '\0')
+ errx(1, "illegal timing interval %s", optarg);
+ if (t < 1 && getuid()) {
+ errx(1, "%s: only root may use interval < 1s",
+ strerror(EPERM));
+ }
+ intvl.tv_sec = (long)t;
+ intvl.tv_usec =
+ (long)((t - intvl.tv_sec) * 1000000);
+ if (intvl.tv_sec < 0)
+ errx(1, "illegal timing interval %s", optarg);
+ /* less than 1/hz does not make sense */
+ if (intvl.tv_sec == 0 && intvl.tv_usec < 1) {
+ warnx("too small interval, raised to .000001");
+ intvl.tv_usec = 1;
+ }
+ options |= F_INTERVAL;
+ break;
+ case 'l':
+ if (getuid()) {
+ errno = EPERM;
+ errx(1, "Must be superuser to preload");
+ }
+ preload = strtol(optarg, &e, 10);
+ if (preload < 0 || *optarg == '\0' || *e != '\0')
+ errx(1, "illegal preload value -- %s", optarg);
+ break;
+ case 'm':
+#ifdef IPV6_USE_MIN_MTU
+ mflag++;
+ break;
+#else
+ errx(1, "-%c is not supported on this platform", ch);
+ /*NOTREACHED*/
+#endif
+ case 'n':
+ options &= ~F_HOSTNAME;
+ break;
+ case 'N':
+ options |= F_NIGROUP;
+ nig_oldmcprefix++;
+ break;
+ case 'o':
+ options |= F_ONCE;
+ break;
+ case 'p': /* fill buffer with user pattern */
+ options |= F_PINGFILLED;
+ fill((char *)datap, optarg);
+ break;
+ case 'q':
+ options |= F_QUIET;
+ break;
+ case 'r':
+ options |= F_AUDIBLE;
+ break;
+ case 'R':
+ options |= F_MISSED;
+ break;
+ case 'S':
+ memset(&hints, 0, sizeof(struct addrinfo));
+ hints.ai_flags = AI_NUMERICHOST; /* allow hostname? */
+ hints.ai_family = AF_INET6;
+ hints.ai_socktype = SOCK_RAW;
+ hints.ai_protocol = IPPROTO_ICMPV6;
+
+ error = getaddrinfo(optarg, NULL, &hints, &res);
+ if (error) {
+ errx(1, "invalid source address: %s",
+ gai_strerror(error));
+ }
+ /*
+ * res->ai_family must be AF_INET6 and res->ai_addrlen
+ * must be sizeof(src).
+ */
+ memcpy(&src, res->ai_addr, res->ai_addrlen);
+ srclen = res->ai_addrlen;
+ freeaddrinfo(res);
+ res = NULL;
+ options |= F_SRCADDR;
+ break;
+ case 's': /* size of packet to send */
+ datalen = strtol(optarg, &e, 10);
+ if (datalen <= 0 || *optarg == '\0' || *e != '\0')
+ errx(1, "illegal datalen value -- %s", optarg);
+ if (datalen > MAXDATALEN) {
+ errx(1,
+ "datalen value too large, maximum is %d",
+ MAXDATALEN);
+ }
+ break;
+ case 't':
+ options &= ~F_NOUSERDATA;
+ options |= F_SUPTYPES;
+ break;
+ case 'v':
+ options |= F_VERBOSE;
+ break;
+ case 'w':
+ options &= ~F_NOUSERDATA;
+ options |= F_FQDN;
+ break;
+ case 'W':
+ options &= ~F_NOUSERDATA;
+ options |= F_FQDNOLD;
+ break;
+ case 'x':
+ t = strtod(optarg, &e);
+ if (*e || e == optarg || t > (double)INT_MAX)
+ err(EX_USAGE, "invalid timing interval: `%s'",
+ optarg);
+ options |= F_WAITTIME;
+ waittime = (int)t;
+ break;
+ case 'X':
+ alarmtimeout = strtoul(optarg, &e, 0);
+ if ((alarmtimeout < 1) || (alarmtimeout == ULONG_MAX))
+ errx(EX_USAGE, "invalid timeout: `%s'",
+ optarg);
+ if (alarmtimeout > MAXALARM)
+ errx(EX_USAGE, "invalid timeout: `%s' > %d",
+ optarg, MAXALARM);
+ alarm((int)alarmtimeout);
+ break;
+#ifdef IPSEC
+#ifdef IPSEC_POLICY_IPSEC
+ case 'P':
+ options |= F_POLICY;
+ if (!strncmp("in", optarg, 2)) {
+ if ((policy_in = strdup(optarg)) == NULL)
+ errx(1, "strdup");
+ } else if (!strncmp("out", optarg, 3)) {
+ if ((policy_out = strdup(optarg)) == NULL)
+ errx(1, "strdup");
+ } else
+ errx(1, "invalid security policy");
+ break;
+#else
+ case 'A':
+ options |= F_AUTHHDR;
+ break;
+ case 'E':
+ options |= F_ENCRYPT;
+ break;
+#endif /*IPSEC_POLICY_IPSEC*/
+#endif /*IPSEC*/
+ default:
+ usage();
+ /*NOTREACHED*/
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 1) {
+ usage();
+ /*NOTREACHED*/
+ }
+
+ if (argc > 1) {
+#ifdef IPV6_RECVRTHDR /* 2292bis */
+ rthlen = CMSG_SPACE(inet6_rth_space(IPV6_RTHDR_TYPE_0,
+ argc - 1));
+#else /* RFC2292 */
+ rthlen = inet6_rthdr_space(IPV6_RTHDR_TYPE_0, argc - 1);
+#endif
+ if (rthlen == 0) {
+ errx(1, "too many intermediate hops");
+ /*NOTREACHED*/
+ }
+ ip6optlen += rthlen;
+ }
+
+ if (options & F_NIGROUP) {
+ target = nigroup(argv[argc - 1], nig_oldmcprefix);
+ if (target == NULL) {
+ usage();
+ /*NOTREACHED*/
+ }
+ } else
+ target = argv[argc - 1];
+
+ /* getaddrinfo */
+ memset(&hints, 0, sizeof(struct addrinfo));
+ hints.ai_flags = AI_CANONNAME;
+ hints.ai_family = AF_INET6;
+ hints.ai_socktype = SOCK_RAW;
+ hints.ai_protocol = IPPROTO_ICMPV6;
+
+ error = getaddrinfo(target, NULL, &hints, &res);
+ if (error)
+ errx(1, "%s", gai_strerror(error));
+ if (res->ai_canonname)
+ hostname = res->ai_canonname;
+ else
+ hostname = target;
+
+ if (!res->ai_addr)
+ errx(1, "getaddrinfo failed");
+
+ (void)memcpy(&dst, res->ai_addr, res->ai_addrlen);
+
+ if ((s = socket(res->ai_family, res->ai_socktype,
+ res->ai_protocol)) < 0)
+ err(1, "socket");
+
+ /* set the source address if specified. */
+ if ((options & F_SRCADDR) != 0) {
+ /* properly fill sin6_scope_id */
+ if (IN6_IS_ADDR_LINKLOCAL(&src.sin6_addr) && (
+ IN6_IS_ADDR_LINKLOCAL(&dst.sin6_addr) ||
+ IN6_IS_ADDR_MC_LINKLOCAL(&dst.sin6_addr) ||
+ IN6_IS_ADDR_MC_NODELOCAL(&dst.sin6_addr))) {
+ if (src.sin6_scope_id == 0)
+ src.sin6_scope_id = dst.sin6_scope_id;
+ if (dst.sin6_scope_id == 0)
+ dst.sin6_scope_id = src.sin6_scope_id;
+ }
+ if (bind(s, (struct sockaddr *)&src, srclen) != 0)
+ err(1, "bind");
+ }
+ /* set the gateway (next hop) if specified */
+ if (gateway) {
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_INET6;
+ hints.ai_socktype = SOCK_RAW;
+ hints.ai_protocol = IPPROTO_ICMPV6;
+
+ error = getaddrinfo(gateway, NULL, &hints, &res);
+ if (error) {
+ errx(1, "getaddrinfo for the gateway %s: %s",
+ gateway, gai_strerror(error));
+ }
+ if (res->ai_next && (options & F_VERBOSE))
+ warnx("gateway resolves to multiple addresses");
+
+ if (setsockopt(s, IPPROTO_IPV6, IPV6_NEXTHOP,
+ res->ai_addr, res->ai_addrlen)) {
+ err(1, "setsockopt(IPV6_NEXTHOP)");
+ }
+
+ freeaddrinfo(res);
+ }
+
+ /*
+ * let the kerel pass extension headers of incoming packets,
+ * for privileged socket options
+ */
+ if ((options & F_VERBOSE) != 0) {
+ int opton = 1;
+
+#ifdef IPV6_RECVHOPOPTS
+ if (setsockopt(s, IPPROTO_IPV6, IPV6_RECVHOPOPTS, &opton,
+ sizeof(opton)))
+ err(1, "setsockopt(IPV6_RECVHOPOPTS)");
+#else /* old adv. API */
+ if (setsockopt(s, IPPROTO_IPV6, IPV6_HOPOPTS, &opton,
+ sizeof(opton)))
+ err(1, "setsockopt(IPV6_HOPOPTS)");
+#endif
+#ifdef IPV6_RECVDSTOPTS
+ if (setsockopt(s, IPPROTO_IPV6, IPV6_RECVDSTOPTS, &opton,
+ sizeof(opton)))
+ err(1, "setsockopt(IPV6_RECVDSTOPTS)");
+#else /* old adv. API */
+ if (setsockopt(s, IPPROTO_IPV6, IPV6_DSTOPTS, &opton,
+ sizeof(opton)))
+ err(1, "setsockopt(IPV6_DSTOPTS)");
+#endif
+#ifdef IPV6_RECVRTHDRDSTOPTS
+ if (setsockopt(s, IPPROTO_IPV6, IPV6_RECVRTHDRDSTOPTS, &opton,
+ sizeof(opton)))
+ err(1, "setsockopt(IPV6_RECVRTHDRDSTOPTS)");
+#endif
+ }
+
+ /* revoke root privilege */
+ if (seteuid(getuid()) != 0)
+ err(1, "seteuid() failed");
+ if (setuid(getuid()) != 0)
+ err(1, "setuid() failed");
+
+ if ((options & F_FLOOD) && (options & F_INTERVAL))
+ errx(1, "-f and -i incompatible options");
+
+ if ((options & F_NOUSERDATA) == 0) {
+ if (datalen >= sizeof(struct tv32)) {
+ /* we can time transfer */
+ timing = 1;
+ } else
+ timing = 0;
+ /* in F_VERBOSE case, we may get non-echoreply packets*/
+ if (options & F_VERBOSE)
+ packlen = 2048 + IP6LEN + ICMP6ECHOLEN + EXTRA;
+ else
+ packlen = datalen + IP6LEN + ICMP6ECHOLEN + EXTRA;
+ } else {
+ /* suppress timing for node information query */
+ timing = 0;
+ datalen = 2048;
+ packlen = 2048 + IP6LEN + ICMP6ECHOLEN + EXTRA;
+ }
+
+ if (!(packet = (u_char *)malloc((u_int)packlen)))
+ err(1, "Unable to allocate packet");
+ if (!(options & F_PINGFILLED))
+ for (i = ICMP6ECHOLEN; i < packlen; ++i)
+ *datap++ = i;
+
+ ident = getpid() & 0xFFFF;
+#ifndef HAVE_ARC4RANDOM
+ gettimeofday(&seed, NULL);
+ srand((unsigned int)(seed.tv_sec ^ seed.tv_usec ^ (long)ident));
+ memset(nonce, 0, sizeof(nonce));
+ for (i = 0; i < sizeof(nonce); i += sizeof(int))
+ *((int *)&nonce[i]) = rand();
+#else
+ memset(nonce, 0, sizeof(nonce));
+ for (i = 0; i < (int)sizeof(nonce); i += sizeof(u_int32_t))
+ *((u_int32_t *)&nonce[i]) = arc4random();
+#endif
+ optval = 1;
+ if (options & F_DONTFRAG)
+ if (setsockopt(s, IPPROTO_IPV6, IPV6_DONTFRAG,
+ &optval, sizeof(optval)) == -1)
+ err(1, "IPV6_DONTFRAG");
+ hold = 1;
+
+ if (options & F_SO_DEBUG)
+ (void)setsockopt(s, SOL_SOCKET, SO_DEBUG, (char *)&hold,
+ sizeof(hold));
+ optval = IPV6_DEFHLIM;
+ if (IN6_IS_ADDR_MULTICAST(&dst.sin6_addr))
+ if (setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_HOPS,
+ &optval, sizeof(optval)) == -1)
+ err(1, "IPV6_MULTICAST_HOPS");
+#ifdef IPV6_USE_MIN_MTU
+ if (mflag != 1) {
+ optval = mflag > 1 ? 0 : 1;
+
+ if (setsockopt(s, IPPROTO_IPV6, IPV6_USE_MIN_MTU,
+ &optval, sizeof(optval)) == -1)
+ err(1, "setsockopt(IPV6_USE_MIN_MTU)");
+ }
+#ifdef IPV6_RECVPATHMTU
+ else {
+ optval = 1;
+ if (setsockopt(s, IPPROTO_IPV6, IPV6_RECVPATHMTU,
+ &optval, sizeof(optval)) == -1)
+ err(1, "setsockopt(IPV6_RECVPATHMTU)");
+ }
+#endif /* IPV6_RECVPATHMTU */
+#endif /* IPV6_USE_MIN_MTU */
+
+#ifdef IPSEC
+#ifdef IPSEC_POLICY_IPSEC
+ if (options & F_POLICY) {
+ if (setpolicy(s, policy_in) < 0)
+ errx(1, "%s", ipsec_strerror());
+ if (setpolicy(s, policy_out) < 0)
+ errx(1, "%s", ipsec_strerror());
+ }
+#else
+ if (options & F_AUTHHDR) {
+ optval = IPSEC_LEVEL_REQUIRE;
+#ifdef IPV6_AUTH_TRANS_LEVEL
+ if (setsockopt(s, IPPROTO_IPV6, IPV6_AUTH_TRANS_LEVEL,
+ &optval, sizeof(optval)) == -1)
+ err(1, "setsockopt(IPV6_AUTH_TRANS_LEVEL)");
+#else /* old def */
+ if (setsockopt(s, IPPROTO_IPV6, IPV6_AUTH_LEVEL,
+ &optval, sizeof(optval)) == -1)
+ err(1, "setsockopt(IPV6_AUTH_LEVEL)");
+#endif
+ }
+ if (options & F_ENCRYPT) {
+ optval = IPSEC_LEVEL_REQUIRE;
+ if (setsockopt(s, IPPROTO_IPV6, IPV6_ESP_TRANS_LEVEL,
+ &optval, sizeof(optval)) == -1)
+ err(1, "setsockopt(IPV6_ESP_TRANS_LEVEL)");
+ }
+#endif /*IPSEC_POLICY_IPSEC*/
+#endif
+
+#ifdef ICMP6_FILTER
+ {
+ struct icmp6_filter filt;
+ if (!(options & F_VERBOSE)) {
+ ICMP6_FILTER_SETBLOCKALL(&filt);
+ if ((options & F_FQDN) || (options & F_FQDNOLD) ||
+ (options & F_NODEADDR) || (options & F_SUPTYPES))
+ ICMP6_FILTER_SETPASS(ICMP6_NI_REPLY, &filt);
+ else
+ ICMP6_FILTER_SETPASS(ICMP6_ECHO_REPLY, &filt);
+ } else {
+ ICMP6_FILTER_SETPASSALL(&filt);
+ }
+ if (setsockopt(s, IPPROTO_ICMPV6, ICMP6_FILTER, &filt,
+ sizeof(filt)) < 0)
+ err(1, "setsockopt(ICMP6_FILTER)");
+ }
+#endif /*ICMP6_FILTER*/
+
+ /* let the kerel pass extension headers of incoming packets */
+ if ((options & F_VERBOSE) != 0) {
+ int opton = 1;
+
+#ifdef IPV6_RECVRTHDR
+ if (setsockopt(s, IPPROTO_IPV6, IPV6_RECVRTHDR, &opton,
+ sizeof(opton)))
+ err(1, "setsockopt(IPV6_RECVRTHDR)");
+#else /* old adv. API */
+ if (setsockopt(s, IPPROTO_IPV6, IPV6_RTHDR, &opton,
+ sizeof(opton)))
+ err(1, "setsockopt(IPV6_RTHDR)");
+#endif
+ }
+
+/*
+ optval = 1;
+ if (IN6_IS_ADDR_MULTICAST(&dst.sin6_addr))
+ if (setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_LOOP,
+ &optval, sizeof(optval)) == -1)
+ err(1, "IPV6_MULTICAST_LOOP");
+*/
+
+ /* Specify the outgoing interface and/or the source address */
+ if (usepktinfo)
+ ip6optlen += CMSG_SPACE(sizeof(struct in6_pktinfo));
+
+ if (hoplimit != -1)
+ ip6optlen += CMSG_SPACE(sizeof(int));
+
+ /* set IP6 packet options */
+ if (ip6optlen) {
+ if ((scmsg = (char *)malloc(ip6optlen)) == 0)
+ errx(1, "can't allocate enough memory");
+ smsghdr.msg_control = (caddr_t)scmsg;
+ smsghdr.msg_controllen = ip6optlen;
+ scmsgp = (struct cmsghdr *)scmsg;
+ }
+ if (usepktinfo) {
+ pktinfo = (struct in6_pktinfo *)(CMSG_DATA(scmsgp));
+ memset(pktinfo, 0, sizeof(*pktinfo));
+ scmsgp->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
+ scmsgp->cmsg_level = IPPROTO_IPV6;
+ scmsgp->cmsg_type = IPV6_PKTINFO;
+ scmsgp = CMSG_NXTHDR(&smsghdr, scmsgp);
+ }
+
+ /* set the outgoing interface */
+ if (ifname) {
+#ifndef USE_SIN6_SCOPE_ID
+ /* pktinfo must have already been allocated */
+ if ((pktinfo->ipi6_ifindex = if_nametoindex(ifname)) == 0)
+ errx(1, "%s: invalid interface name", ifname);
+#else
+ if ((dst.sin6_scope_id = if_nametoindex(ifname)) == 0)
+ errx(1, "%s: invalid interface name", ifname);
+#endif
+ }
+ if (hoplimit != -1) {
+ scmsgp->cmsg_len = CMSG_LEN(sizeof(int));
+ scmsgp->cmsg_level = IPPROTO_IPV6;
+ scmsgp->cmsg_type = IPV6_HOPLIMIT;
+ *(int *)(CMSG_DATA(scmsgp)) = hoplimit;
+
+ scmsgp = CMSG_NXTHDR(&smsghdr, scmsgp);
+ }
+
+ if (argc > 1) { /* some intermediate addrs are specified */
+ int hops;
+#ifdef USE_RFC2292BIS
+ int rthdrlen;
+#endif
+
+#ifdef USE_RFC2292BIS
+ rthdrlen = inet6_rth_space(IPV6_RTHDR_TYPE_0, argc - 1);
+ scmsgp->cmsg_len = CMSG_LEN(rthdrlen);
+ scmsgp->cmsg_level = IPPROTO_IPV6;
+ scmsgp->cmsg_type = IPV6_RTHDR;
+ rthdr = (struct ip6_rthdr *)CMSG_DATA(scmsgp);
+ rthdr = inet6_rth_init((void *)rthdr, rthdrlen,
+ IPV6_RTHDR_TYPE_0, argc - 1);
+ if (rthdr == NULL)
+ errx(1, "can't initialize rthdr");
+#else /* old advanced API */
+ if ((scmsgp = (struct cmsghdr *)inet6_rthdr_init(scmsgp,
+ IPV6_RTHDR_TYPE_0)) == 0)
+ errx(1, "can't initialize rthdr");
+#endif /* USE_RFC2292BIS */
+
+ for (hops = 0; hops < argc - 1; hops++) {
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_INET6;
+
+ if ((error = getaddrinfo(argv[hops], NULL, &hints,
+ &res)))
+ errx(1, "%s", gai_strerror(error));
+ if (res->ai_addr->sa_family != AF_INET6)
+ errx(1,
+ "bad addr family of an intermediate addr");
+ sin6 = (struct sockaddr_in6 *)(void *)res->ai_addr;
+#ifdef USE_RFC2292BIS
+ if (inet6_rth_add(rthdr, &sin6->sin6_addr))
+ errx(1, "can't add an intermediate node");
+#else /* old advanced API */
+ if (inet6_rthdr_add(scmsg, &sin6->sin6_addr,
+ IPV6_RTHDR_LOOSE))
+ errx(1, "can't add an intermediate node");
+#endif /* USE_RFC2292BIS */
+ freeaddrinfo(res);
+ }
+
+#ifndef USE_RFC2292BIS
+ if (inet6_rthdr_lasthop(scmsgp, IPV6_RTHDR_LOOSE))
+ errx(1, "can't set the last flag");
+#endif
+
+ scmsgp = CMSG_NXTHDR(&smsghdr, scmsgp);
+ }
+
+ if (!(options & F_SRCADDR)) {
+ /*
+ * get the source address. XXX since we revoked the root
+ * privilege, we cannot use a raw socket for this.
+ */
+ int dummy;
+ socklen_t len = sizeof(src);
+
+ if ((dummy = socket(AF_INET6, SOCK_DGRAM, 0)) < 0)
+ err(1, "UDP socket");
+
+ src.sin6_family = AF_INET6;
+ src.sin6_addr = dst.sin6_addr;
+ src.sin6_port = ntohs(DUMMY_PORT);
+ src.sin6_scope_id = dst.sin6_scope_id;
+
+#ifdef USE_RFC2292BIS
+ if (pktinfo &&
+ setsockopt(dummy, IPPROTO_IPV6, IPV6_PKTINFO,
+ (void *)pktinfo, sizeof(*pktinfo)))
+ err(1, "UDP setsockopt(IPV6_PKTINFO)");
+
+ if (hoplimit != -1 &&
+ setsockopt(dummy, IPPROTO_IPV6, IPV6_UNICAST_HOPS,
+ (void *)&hoplimit, sizeof(hoplimit)))
+ err(1, "UDP setsockopt(IPV6_UNICAST_HOPS)");
+
+ if (hoplimit != -1 &&
+ setsockopt(dummy, IPPROTO_IPV6, IPV6_MULTICAST_HOPS,
+ (void *)&hoplimit, sizeof(hoplimit)))
+ err(1, "UDP setsockopt(IPV6_MULTICAST_HOPS)");
+
+ if (rthdr &&
+ setsockopt(dummy, IPPROTO_IPV6, IPV6_RTHDR,
+ (void *)rthdr, (rthdr->ip6r_len + 1) << 3))
+ err(1, "UDP setsockopt(IPV6_RTHDR)");
+#else /* old advanced API */
+ if (smsghdr.msg_control &&
+ setsockopt(dummy, IPPROTO_IPV6, IPV6_PKTOPTIONS,
+ (void *)smsghdr.msg_control, smsghdr.msg_controllen))
+ err(1, "UDP setsockopt(IPV6_PKTOPTIONS)");
+#endif
+
+ if (connect(dummy, (struct sockaddr *)&src, len) < 0)
+ err(1, "UDP connect");
+
+ if (getsockname(dummy, (struct sockaddr *)&src, &len) < 0)
+ err(1, "getsockname");
+
+ close(dummy);
+ }
+
+#if defined(SO_SNDBUF) && defined(SO_RCVBUF)
+ if (sockbufsize) {
+ if (datalen > (size_t)sockbufsize)
+ warnx("you need -b to increase socket buffer size");
+ if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, &sockbufsize,
+ sizeof(sockbufsize)) < 0)
+ err(1, "setsockopt(SO_SNDBUF)");
+ if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, &sockbufsize,
+ sizeof(sockbufsize)) < 0)
+ err(1, "setsockopt(SO_RCVBUF)");
+ }
+ else {
+ if (datalen > 8 * 1024) /*XXX*/
+ warnx("you need -b to increase socket buffer size");
+ /*
+ * When pinging the broadcast address, you can get a lot of
+ * answers. Doing something so evil is useful if you are trying
+ * to stress the ethernet, or just want to fill the arp cache
+ * to get some stuff for /etc/ethers.
+ */
+ hold = 48 * 1024;
+ setsockopt(s, SOL_SOCKET, SO_RCVBUF, (char *)&hold,
+ sizeof(hold));
+ }
+#endif
+
+ optval = 1;
+#ifndef USE_SIN6_SCOPE_ID
+#ifdef IPV6_RECVPKTINFO
+ if (setsockopt(s, IPPROTO_IPV6, IPV6_RECVPKTINFO, &optval,
+ sizeof(optval)) < 0)
+ warn("setsockopt(IPV6_RECVPKTINFO)"); /* XXX err? */
+#else /* old adv. API */
+ if (setsockopt(s, IPPROTO_IPV6, IPV6_PKTINFO, &optval,
+ sizeof(optval)) < 0)
+ warn("setsockopt(IPV6_PKTINFO)"); /* XXX err? */
+#endif
+#endif /* USE_SIN6_SCOPE_ID */
+#ifdef IPV6_RECVHOPLIMIT
+ if (setsockopt(s, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &optval,
+ sizeof(optval)) < 0)
+ warn("setsockopt(IPV6_RECVHOPLIMIT)"); /* XXX err? */
+#else /* old adv. API */
+ if (setsockopt(s, IPPROTO_IPV6, IPV6_HOPLIMIT, &optval,
+ sizeof(optval)) < 0)
+ warn("setsockopt(IPV6_HOPLIMIT)"); /* XXX err? */
+#endif
+
+ printf("PING6(%lu=40+8+%lu bytes) ", (unsigned long)(40 + pingerlen()),
+ (unsigned long)(pingerlen() - 8));
+ printf("%s --> ", pr_addr((struct sockaddr *)&src, sizeof(src)));
+ printf("%s\n", pr_addr((struct sockaddr *)&dst, sizeof(dst)));
+
+ if (preload == 0)
+ pinger();
+ else {
+ if (npackets != 0 && preload > npackets)
+ preload = npackets;
+ while (preload--)
+ pinger();
+ }
+ gettimeofday(&last, NULL);
+
+ sigemptyset(&si_sa.sa_mask);
+ si_sa.sa_flags = 0;
+ si_sa.sa_handler = onsignal;
+ if (sigaction(SIGINT, &si_sa, 0) == -1)
+ err(EX_OSERR, "sigaction SIGINT");
+ seenint = 0;
+#ifdef SIGINFO
+ if (sigaction(SIGINFO, &si_sa, 0) == -1)
+ err(EX_OSERR, "sigaction SIGINFO");
+ seeninfo = 0;
+#endif
+ if (alarmtimeout > 0) {
+ if (sigaction(SIGALRM, &si_sa, 0) == -1)
+ err(EX_OSERR, "sigaction SIGALRM");
+ }
+ if (options & F_FLOOD) {
+ intvl.tv_sec = 0;
+ intvl.tv_usec = 10000;
+ } else if ((options & F_INTERVAL) == 0) {
+ intvl.tv_sec = interval / 1000;
+ intvl.tv_usec = interval % 1000 * 1000;
+ }
+
+ almost_done = 0;
+ while (seenint == 0) {
+ struct timeval now, timeout;
+ struct msghdr m;
+ struct iovec iov[2];
+ fd_set rfds;
+ int n;
+
+ /* signal handling */
+ if (seenint)
+ onint(SIGINT);
+#ifdef SIGINFO
+ if (seeninfo) {
+ summary();
+ seeninfo = 0;
+ continue;
+ }
+#endif
+ FD_ZERO(&rfds);
+ FD_SET(s, &rfds);
+ gettimeofday(&now, NULL);
+ timeout.tv_sec = last.tv_sec + intvl.tv_sec - now.tv_sec;
+ timeout.tv_usec = last.tv_usec + intvl.tv_usec - now.tv_usec;
+ while (timeout.tv_usec < 0) {
+ timeout.tv_usec += 1000000;
+ timeout.tv_sec--;
+ }
+ while (timeout.tv_usec > 1000000) {
+ timeout.tv_usec -= 1000000;
+ timeout.tv_sec++;
+ }
+ if (timeout.tv_sec < 0)
+ timeout.tv_sec = timeout.tv_usec = 0;
+
+ n = select(s + 1, &rfds, NULL, NULL, &timeout);
+ if (n < 0)
+ continue; /* EINTR */
+ if (n == 1) {
+ m.msg_name = (caddr_t)&from;
+ m.msg_namelen = sizeof(from);
+ memset(&iov, 0, sizeof(iov));
+ iov[0].iov_base = (caddr_t)packet;
+ iov[0].iov_len = packlen;
+ m.msg_iov = iov;
+ m.msg_iovlen = 1;
+ memset(cm, 0, CONTROLLEN);
+ m.msg_control = (void *)cm;
+ m.msg_controllen = CONTROLLEN;
+
+ cc = recvmsg(s, &m, 0);
+ if (cc < 0) {
+ if (errno != EINTR) {
+ warn("recvmsg");
+ sleep(1);
+ }
+ continue;
+ } else if (cc == 0) {
+ int mtu;
+
+ /*
+ * receive control messages only. Process the
+ * exceptions (currently the only possibility is
+ * a path MTU notification.)
+ */
+ if ((mtu = get_pathmtu(&m)) > 0) {
+ if ((options & F_VERBOSE) != 0) {
+ printf("new path MTU (%d) is "
+ "notified\n", mtu);
+ }
+ }
+ continue;
+ } else {
+ /*
+ * an ICMPv6 message (probably an echoreply)
+ * arrived.
+ */
+ pr_pack(packet, cc, &m);
+ }
+ if (((options & F_ONCE) != 0 && nreceived > 0) ||
+ (npackets > 0 && nreceived >= npackets))
+ break;
+ }
+ if (n == 0 || (options & F_FLOOD)) {
+ if (npackets == 0 || ntransmitted < npackets)
+ pinger();
+ else {
+ if (almost_done)
+ break;
+ almost_done = 1;
+ /*
+ * If we're not transmitting any more packets,
+ * change the timer to wait two round-trip times
+ * if we've received any packets or (waittime)
+ * milliseconds if we haven't.
+ */
+ intvl.tv_usec = 0;
+ if (nreceived) {
+ intvl.tv_sec = 2 * tmax / 1000;
+ if (intvl.tv_sec == 0)
+ intvl.tv_sec = 1;
+ } else {
+ intvl.tv_sec = waittime / 1000;
+ intvl.tv_usec = waittime % 1000 * 1000;
+ }
+ }
+ gettimeofday(&last, NULL);
+ if (ntransmitted - nreceived - 1 > nmissedmax) {
+ nmissedmax = ntransmitted - nreceived - 1;
+ if (options & F_MISSED)
+ (void)write(STDOUT_FILENO, &BBELL, 1);
+ }
+ }
+ }
+ sigemptyset(&si_sa.sa_mask);
+ si_sa.sa_flags = 0;
+ si_sa.sa_handler = SIG_IGN;
+ sigaction(SIGINT, &si_sa, 0);
+ sigaction(SIGALRM, &si_sa, 0);
+ summary();
+
+ if (res != NULL)
+ freeaddrinfo(res);
+
+ if(packet != NULL)
+ free(packet);
+
+ exit(nreceived == 0 ? 2 : 0);
+}
+
+static void
+onsignal(int sig)
+{
+
+ switch (sig) {
+ case SIGINT:
+ case SIGALRM:
+ seenint++;
+ break;
+#ifdef SIGINFO
+ case SIGINFO:
+ seeninfo++;
+ break;
+#endif
+ }
+}
+
+/*
+ * pinger --
+ * Compose and transmit an ICMP ECHO REQUEST packet. The IP packet
+ * will be added on by the kernel. The ID field is our UNIX process ID,
+ * and the sequence number is an ascending integer. The first 8 bytes
+ * of the data portion are used to hold a UNIX "timeval" struct in VAX
+ * byte-order, to compute the round-trip time.
+ */
+static size_t
+pingerlen(void)
+{
+ size_t l;
+
+ if (options & F_FQDN)
+ l = ICMP6_NIQLEN + sizeof(dst.sin6_addr);
+ else if (options & F_FQDNOLD)
+ l = ICMP6_NIQLEN;
+ else if (options & F_NODEADDR)
+ l = ICMP6_NIQLEN + sizeof(dst.sin6_addr);
+ else if (options & F_SUPTYPES)
+ l = ICMP6_NIQLEN;
+ else
+ l = ICMP6ECHOLEN + datalen;
+
+ return l;
+}
+
+static int
+pinger(void)
+{
+ struct icmp6_hdr *icp;
+ struct iovec iov[2];
+ int i, cc;
+ struct icmp6_nodeinfo *nip;
+ int seq;
+
+ if (npackets && ntransmitted >= npackets)
+ return(-1); /* no more transmission */
+
+ icp = (struct icmp6_hdr *)outpack;
+ nip = (struct icmp6_nodeinfo *)outpack;
+ memset(icp, 0, sizeof(*icp));
+ icp->icmp6_cksum = 0;
+ seq = ntransmitted++;
+ CLR(seq % mx_dup_ck);
+
+ if (options & F_FQDN) {
+ icp->icmp6_type = ICMP6_NI_QUERY;
+ icp->icmp6_code = ICMP6_NI_SUBJ_IPV6;
+ nip->ni_qtype = htons(NI_QTYPE_FQDN);
+ nip->ni_flags = htons(0);
+
+ memcpy(nip->icmp6_ni_nonce, nonce,
+ sizeof(nip->icmp6_ni_nonce));
+ *(u_int16_t *)nip->icmp6_ni_nonce = ntohs(seq);
+
+ memcpy(&outpack[ICMP6_NIQLEN], &dst.sin6_addr,
+ sizeof(dst.sin6_addr));
+ cc = ICMP6_NIQLEN + sizeof(dst.sin6_addr);
+ datalen = 0;
+ } else if (options & F_FQDNOLD) {
+ /* packet format in 03 draft - no Subject data on queries */
+ icp->icmp6_type = ICMP6_NI_QUERY;
+ icp->icmp6_code = 0; /* code field is always 0 */
+ nip->ni_qtype = htons(NI_QTYPE_FQDN);
+ nip->ni_flags = htons(0);
+
+ memcpy(nip->icmp6_ni_nonce, nonce,
+ sizeof(nip->icmp6_ni_nonce));
+ *(u_int16_t *)nip->icmp6_ni_nonce = ntohs(seq);
+
+ cc = ICMP6_NIQLEN;
+ datalen = 0;
+ } else if (options & F_NODEADDR) {
+ icp->icmp6_type = ICMP6_NI_QUERY;
+ icp->icmp6_code = ICMP6_NI_SUBJ_IPV6;
+ nip->ni_qtype = htons(NI_QTYPE_NODEADDR);
+ nip->ni_flags = naflags;
+
+ memcpy(nip->icmp6_ni_nonce, nonce,
+ sizeof(nip->icmp6_ni_nonce));
+ *(u_int16_t *)nip->icmp6_ni_nonce = ntohs(seq);
+
+ memcpy(&outpack[ICMP6_NIQLEN], &dst.sin6_addr,
+ sizeof(dst.sin6_addr));
+ cc = ICMP6_NIQLEN + sizeof(dst.sin6_addr);
+ datalen = 0;
+ } else if (options & F_SUPTYPES) {
+ icp->icmp6_type = ICMP6_NI_QUERY;
+ icp->icmp6_code = ICMP6_NI_SUBJ_FQDN; /*empty*/
+ nip->ni_qtype = htons(NI_QTYPE_SUPTYPES);
+ /* we support compressed bitmap */
+ nip->ni_flags = NI_SUPTYPE_FLAG_COMPRESS;
+
+ memcpy(nip->icmp6_ni_nonce, nonce,
+ sizeof(nip->icmp6_ni_nonce));
+ *(u_int16_t *)nip->icmp6_ni_nonce = ntohs(seq);
+ cc = ICMP6_NIQLEN;
+ datalen = 0;
+ } else {
+ icp->icmp6_type = ICMP6_ECHO_REQUEST;
+ icp->icmp6_code = 0;
+ icp->icmp6_id = htons(ident);
+ icp->icmp6_seq = ntohs(seq);
+ if (timing) {
+ struct timeval tv;
+ struct tv32 *tv32;
+ (void)gettimeofday(&tv, NULL);
+ tv32 = (struct tv32 *)&outpack[ICMP6ECHOLEN];
+ tv32->tv32_sec = htonl(tv.tv_sec);
+ tv32->tv32_usec = htonl(tv.tv_usec);
+ }
+ cc = ICMP6ECHOLEN + datalen;
+ }
+
+#ifdef DIAGNOSTIC
+ if (pingerlen() != cc)
+ errx(1, "internal error; length mismatch");
+#endif
+
+ smsghdr.msg_name = (caddr_t)&dst;
+ smsghdr.msg_namelen = sizeof(dst);
+ memset(&iov, 0, sizeof(iov));
+ iov[0].iov_base = (caddr_t)outpack;
+ iov[0].iov_len = cc;
+ smsghdr.msg_iov = iov;
+ smsghdr.msg_iovlen = 1;
+
+ i = sendmsg(s, &smsghdr, 0);
+
+ if (i < 0 || i != cc) {
+ if (i < 0)
+ warn("sendmsg");
+ (void)printf("ping6: wrote %s %d chars, ret=%d\n",
+ hostname, cc, i);
+ }
+ if (!(options & F_QUIET) && options & F_FLOOD)
+ (void)write(STDOUT_FILENO, &DOT, 1);
+
+ return(0);
+}
+
+static int
+myechoreply(const struct icmp6_hdr *icp)
+{
+ if (ntohs(icp->icmp6_id) == ident)
+ return 1;
+ else
+ return 0;
+}
+
+static int
+mynireply(const struct icmp6_nodeinfo *nip)
+{
+ if (memcmp(nip->icmp6_ni_nonce + sizeof(u_int16_t),
+ nonce + sizeof(u_int16_t),
+ sizeof(nonce) - sizeof(u_int16_t)) == 0)
+ return 1;
+ else
+ return 0;
+}
+
+static char *
+dnsdecode(const u_char **sp, const u_char *ep, const u_char *base, char *buf,
+ size_t bufsiz)
+ /*base for compressed name*/
+{
+ int i;
+ const u_char *cp;
+ char cresult[MAXDNAME + 1];
+ const u_char *comp;
+ int l;
+
+ cp = *sp;
+ *buf = '\0';
+
+ if (cp >= ep)
+ return NULL;
+ while (cp < ep) {
+ i = *cp;
+ if (i == 0 || cp != *sp) {
+ if (strlcat((char *)buf, ".", bufsiz) >= bufsiz)
+ return NULL; /*result overrun*/
+ }
+ if (i == 0)
+ break;
+ cp++;
+
+ if ((i & 0xc0) == 0xc0 && cp - base > (i & 0x3f)) {
+ /* DNS compression */
+ if (!base)
+ return NULL;
+
+ comp = base + (i & 0x3f);
+ if (dnsdecode(&comp, cp, base, cresult,
+ sizeof(cresult)) == NULL)
+ return NULL;
+ if (strlcat(buf, cresult, bufsiz) >= bufsiz)
+ return NULL; /*result overrun*/
+ break;
+ } else if ((i & 0x3f) == i) {
+ if (i > ep - cp)
+ return NULL; /*source overrun*/
+ while (i-- > 0 && cp < ep) {
+ l = snprintf(cresult, sizeof(cresult),
+ isprint(*cp) ? "%c" : "\\%03o", *cp & 0xff);
+ if ((size_t)l >= sizeof(cresult) || l < 0)
+ return NULL;
+ if (strlcat(buf, cresult, bufsiz) >= bufsiz)
+ return NULL; /*result overrun*/
+ cp++;
+ }
+ } else
+ return NULL; /*invalid label*/
+ }
+ if (i != 0)
+ return NULL; /*not terminated*/
+ cp++;
+ *sp = cp;
+ return buf;
+}
+
+/*
+ * pr_pack --
+ * Print out the packet, if it came from us. This logic is necessary
+ * because ALL readers of the ICMP socket get a copy of ALL ICMP packets
+ * which arrive ('tis only fair). This permits multiple copies of this
+ * program to be run without having intermingled output (or statistics!).
+ */
+static void
+pr_pack(u_char *buf, int cc, struct msghdr *mhdr)
+{
+#define safeputc(c) printf((isprint((c)) ? "%c" : "\\%03o"), c)
+ struct icmp6_hdr *icp;
+ struct icmp6_nodeinfo *ni;
+ int i;
+ int hoplim;
+ struct sockaddr *from;
+ int fromlen;
+ u_char *cp = NULL, *dp, *end = buf + cc;
+ struct in6_pktinfo *pktinfo = NULL;
+ struct timeval tv, tp;
+ struct tv32 *tpp;
+ double triptime = 0;
+ int dupflag;
+ size_t off;
+ int oldfqdn;
+ u_int16_t seq;
+ char dnsname[MAXDNAME + 1];
+
+ (void)gettimeofday(&tv, NULL);
+
+ if (!mhdr || !mhdr->msg_name ||
+ mhdr->msg_namelen != sizeof(struct sockaddr_in6) ||
+ ((struct sockaddr *)mhdr->msg_name)->sa_family != AF_INET6) {
+ if (options & F_VERBOSE)
+ warnx("invalid peername");
+ return;
+ }
+ from = (struct sockaddr *)mhdr->msg_name;
+ fromlen = mhdr->msg_namelen;
+ if (cc < (int)sizeof(struct icmp6_hdr)) {
+ if (options & F_VERBOSE)
+ warnx("packet too short (%d bytes) from %s", cc,
+ pr_addr(from, fromlen));
+ return;
+ }
+ if (((mhdr->msg_flags & MSG_CTRUNC) != 0) &&
+ (options & F_VERBOSE) != 0)
+ warnx("some control data discarded, insufficient buffer size");
+ icp = (struct icmp6_hdr *)buf;
+ ni = (struct icmp6_nodeinfo *)buf;
+ off = 0;
+
+ if ((hoplim = get_hoplim(mhdr)) == -1) {
+ warnx("failed to get receiving hop limit");
+ return;
+ }
+ if ((pktinfo = get_rcvpktinfo(mhdr)) == NULL) {
+ warnx("failed to get receiving packet information");
+ return;
+ }
+
+ if (icp->icmp6_type == ICMP6_ECHO_REPLY && myechoreply(icp)) {
+ seq = ntohs(icp->icmp6_seq);
+ ++nreceived;
+ if (timing) {
+ tpp = (struct tv32 *)(icp + 1);
+ tp.tv_sec = ntohl(tpp->tv32_sec);
+ tp.tv_usec = ntohl(tpp->tv32_usec);
+ tvsub(&tv, &tp);
+ triptime = ((double)tv.tv_sec) * 1000.0 +
+ ((double)tv.tv_usec) / 1000.0;
+ tsum += triptime;
+ tsumsq += triptime * triptime;
+ if (triptime < tmin)
+ tmin = triptime;
+ if (triptime > tmax)
+ tmax = triptime;
+ }
+
+ if (TST(seq % mx_dup_ck)) {
+ ++nrepeats;
+ --nreceived;
+ dupflag = 1;
+ } else {
+ SET(seq % mx_dup_ck);
+ dupflag = 0;
+ }
+
+ if (options & F_QUIET)
+ return;
+
+ if (options & F_WAITTIME && triptime > waittime) {
+ ++nrcvtimeout;
+ return;
+ }
+
+ if (options & F_FLOOD)
+ (void)write(STDOUT_FILENO, &BSPACE, 1);
+ else {
+ if (options & F_AUDIBLE)
+ (void)write(STDOUT_FILENO, &BBELL, 1);
+ (void)printf("%d bytes from %s, icmp_seq=%u", cc,
+ pr_addr(from, fromlen), seq);
+ (void)printf(" hlim=%d", hoplim);
+ if ((options & F_VERBOSE) != 0) {
+ struct sockaddr_in6 dstsa;
+
+ memset(&dstsa, 0, sizeof(dstsa));
+ dstsa.sin6_family = AF_INET6;
+ dstsa.sin6_len = sizeof(dstsa);
+ dstsa.sin6_scope_id = pktinfo->ipi6_ifindex;
+ dstsa.sin6_addr = pktinfo->ipi6_addr;
+ (void)printf(" dst=%s",
+ pr_addr((struct sockaddr *)&dstsa,
+ sizeof(dstsa)));
+ }
+ if (timing)
+ (void)printf(" time=%.3f ms", triptime);
+ if (dupflag)
+ (void)printf("(DUP!)");
+ /* check the data */
+ cp = buf + off + ICMP6ECHOLEN + ICMP6ECHOTMLEN;
+ dp = outpack + ICMP6ECHOLEN + ICMP6ECHOTMLEN;
+ for (i = 8; cp < end; ++i, ++cp, ++dp) {
+ if (*cp != *dp) {
+ (void)printf("\nwrong data byte #%d should be 0x%x but was 0x%x", i, *dp, *cp);
+ break;
+ }
+ }
+ }
+ } else if (icp->icmp6_type == ICMP6_NI_REPLY && mynireply(ni)) {
+ seq = ntohs(*(u_int16_t *)ni->icmp6_ni_nonce);
+ ++nreceived;
+ if (TST(seq % mx_dup_ck)) {
+ ++nrepeats;
+ --nreceived;
+ dupflag = 1;
+ } else {
+ SET(seq % mx_dup_ck);
+ dupflag = 0;
+ }
+
+ if (options & F_QUIET)
+ return;
+
+ (void)printf("%d bytes from %s: ", cc, pr_addr(from, fromlen));
+
+ switch (ntohs(ni->ni_code)) {
+ case ICMP6_NI_SUCCESS:
+ break;
+ case ICMP6_NI_REFUSED:
+ printf("refused, type 0x%x", ntohs(ni->ni_type));
+ goto fqdnend;
+ case ICMP6_NI_UNKNOWN:
+ printf("unknown, type 0x%x", ntohs(ni->ni_type));
+ goto fqdnend;
+ default:
+ printf("unknown code 0x%x, type 0x%x",
+ ntohs(ni->ni_code), ntohs(ni->ni_type));
+ goto fqdnend;
+ }
+
+ switch (ntohs(ni->ni_qtype)) {
+ case NI_QTYPE_NOOP:
+ printf("NodeInfo NOOP");
+ break;
+ case NI_QTYPE_SUPTYPES:
+ pr_suptypes(ni, end - (u_char *)ni);
+ break;
+ case NI_QTYPE_NODEADDR:
+ pr_nodeaddr(ni, end - (u_char *)ni);
+ break;
+ case NI_QTYPE_FQDN:
+ default: /* XXX: for backward compatibility */
+ cp = (u_char *)ni + ICMP6_NIRLEN;
+ if (buf[off + ICMP6_NIRLEN] ==
+ cc - off - ICMP6_NIRLEN - 1)
+ oldfqdn = 1;
+ else
+ oldfqdn = 0;
+ if (oldfqdn) {
+ cp++; /* skip length */
+ while (cp < end) {
+ safeputc(*cp & 0xff);
+ cp++;
+ }
+ } else {
+ i = 0;
+ while (cp < end) {
+ if (dnsdecode((const u_char **)&cp, end,
+ (const u_char *)(ni + 1), dnsname,
+ sizeof(dnsname)) == NULL) {
+ printf("???");
+ break;
+ }
+ /*
+ * name-lookup special handling for
+ * truncated name
+ */
+ if (cp + 1 <= end && !*cp &&
+ strlen(dnsname) > 0) {
+ dnsname[strlen(dnsname) - 1] = '\0';
+ cp++;
+ }
+ printf("%s%s", i > 0 ? "," : "",
+ dnsname);
+ }
+ }
+ if (options & F_VERBOSE) {
+ int32_t ttl;
+ int comma = 0;
+
+ (void)printf(" ("); /*)*/
+
+ switch (ni->ni_code) {
+ case ICMP6_NI_REFUSED:
+ (void)printf("refused");
+ comma++;
+ break;
+ case ICMP6_NI_UNKNOWN:
+ (void)printf("unknown qtype");
+ comma++;
+ break;
+ }
+
+ if ((end - (u_char *)ni) < ICMP6_NIRLEN) {
+ /* case of refusion, unknown */
+ /*(*/
+ putchar(')');
+ goto fqdnend;
+ }
+ ttl = (int32_t)ntohl(*(u_long *)&buf[off+ICMP6ECHOLEN+8]);
+ if (comma)
+ printf(",");
+ if (!(ni->ni_flags & NI_FQDN_FLAG_VALIDTTL)) {
+ (void)printf("TTL=%d:meaningless",
+ (int)ttl);
+ } else {
+ if (ttl < 0) {
+ (void)printf("TTL=%d:invalid",
+ ttl);
+ } else
+ (void)printf("TTL=%d", ttl);
+ }
+ comma++;
+
+ if (oldfqdn) {
+ if (comma)
+ printf(",");
+ printf("03 draft");
+ comma++;
+ } else {
+ cp = (u_char *)ni + ICMP6_NIRLEN;
+ if (cp == end) {
+ if (comma)
+ printf(",");
+ printf("no name");
+ comma++;
+ }
+ }
+
+ if (buf[off + ICMP6_NIRLEN] !=
+ cc - off - ICMP6_NIRLEN - 1 && oldfqdn) {
+ if (comma)
+ printf(",");
+ (void)printf("invalid namelen:%d/%lu",
+ buf[off + ICMP6_NIRLEN],
+ (u_long)cc - off - ICMP6_NIRLEN - 1);
+ comma++;
+ }
+ /*(*/
+ putchar(')');
+ }
+ fqdnend:
+ ;
+ }
+ } else {
+ /* We've got something other than an ECHOREPLY */
+ if (!(options & F_VERBOSE))
+ return;
+ (void)printf("%d bytes from %s: ", cc, pr_addr(from, fromlen));
+ pr_icmph(icp, end);
+ }
+
+ if (!(options & F_FLOOD)) {
+ (void)putchar('\n');
+ if (options & F_VERBOSE)
+ pr_exthdrs(mhdr);
+ (void)fflush(stdout);
+ }
+#undef safeputc
+}
+
+static void
+pr_exthdrs(struct msghdr *mhdr)
+{
+ ssize_t bufsize;
+ void *bufp;
+ struct cmsghdr *cm;
+
+ bufsize = 0;
+ bufp = mhdr->msg_control;
+ for (cm = (struct cmsghdr *)CMSG_FIRSTHDR(mhdr); cm;
+ cm = (struct cmsghdr *)CMSG_NXTHDR(mhdr, cm)) {
+ if (cm->cmsg_level != IPPROTO_IPV6)
+ continue;
+
+ bufsize = CONTROLLEN - ((caddr_t)CMSG_DATA(cm) - (caddr_t)bufp);
+ if (bufsize <= 0)
+ continue;
+ switch (cm->cmsg_type) {
+ case IPV6_HOPOPTS:
+ printf(" HbH Options: ");
+ pr_ip6opt(CMSG_DATA(cm), (size_t)bufsize);
+ break;
+ case IPV6_DSTOPTS:
+#ifdef IPV6_RTHDRDSTOPTS
+ case IPV6_RTHDRDSTOPTS:
+#endif
+ printf(" Dst Options: ");
+ pr_ip6opt(CMSG_DATA(cm), (size_t)bufsize);
+ break;
+ case IPV6_RTHDR:
+ printf(" Routing: ");
+ pr_rthdr(CMSG_DATA(cm), (size_t)bufsize);
+ break;
+ }
+ }
+}
+
+#ifdef USE_RFC2292BIS
+static void
+pr_ip6opt(void *extbuf, size_t bufsize)
+{
+ struct ip6_hbh *ext;
+ int currentlen;
+ u_int8_t type;
+ socklen_t extlen, len;
+ void *databuf;
+ size_t offset;
+ u_int16_t value2;
+ u_int32_t value4;
+
+ ext = (struct ip6_hbh *)extbuf;
+ extlen = (ext->ip6h_len + 1) * 8;
+ printf("nxt %u, len %u (%lu bytes)\n", ext->ip6h_nxt,
+ (unsigned int)ext->ip6h_len, (unsigned long)extlen);
+
+ /*
+ * Bounds checking on the ancillary data buffer:
+ * subtract the size of a cmsg structure from the buffer size.
+ */
+ if (bufsize < (extlen + CMSG_SPACE(0))) {
+ extlen = bufsize - CMSG_SPACE(0);
+ warnx("options truncated, showing only %u (total=%u)",
+ (unsigned int)(extlen / 8 - 1),
+ (unsigned int)(ext->ip6h_len));
+ }
+
+ currentlen = 0;
+ while (1) {
+ currentlen = inet6_opt_next(extbuf, extlen, currentlen,
+ &type, &len, &databuf);
+ if (currentlen == -1)
+ break;
+ switch (type) {
+ /*
+ * Note that inet6_opt_next automatically skips any padding
+ * optins.
+ */
+ case IP6OPT_JUMBO:
+ offset = 0;
+ offset = inet6_opt_get_val(databuf, offset,
+ &value4, sizeof(value4));
+ printf(" Jumbo Payload Opt: Length %u\n",
+ (u_int32_t)ntohl(value4));
+ break;
+ case IP6OPT_ROUTER_ALERT:
+ offset = 0;
+ offset = inet6_opt_get_val(databuf, offset,
+ &value2, sizeof(value2));
+ printf(" Router Alert Opt: Type %u\n",
+ ntohs(value2));
+ break;
+ default:
+ printf(" Received Opt %u len %lu\n",
+ type, (unsigned long)len);
+ break;
+ }
+ }
+ return;
+}
+#else /* !USE_RFC2292BIS */
+/* ARGSUSED */
+static void
+pr_ip6opt(void *extbuf, size_t bufsize __unused)
+{
+ putchar('\n');
+ return;
+}
+#endif /* USE_RFC2292BIS */
+
+#ifdef USE_RFC2292BIS
+static void
+pr_rthdr(void *extbuf, size_t bufsize)
+{
+ struct in6_addr *in6;
+ char ntopbuf[INET6_ADDRSTRLEN];
+ struct ip6_rthdr *rh = (struct ip6_rthdr *)extbuf;
+ int i, segments, origsegs, rthsize, size0, size1;
+
+ /* print fixed part of the header */
+ printf("nxt %u, len %u (%d bytes), type %u, ", rh->ip6r_nxt,
+ rh->ip6r_len, (rh->ip6r_len + 1) << 3, rh->ip6r_type);
+ if ((segments = inet6_rth_segments(extbuf)) >= 0) {
+ printf("%d segments, ", segments);
+ printf("%d left\n", rh->ip6r_segleft);
+ } else {
+ printf("segments unknown, ");
+ printf("%d left\n", rh->ip6r_segleft);
+ return;
+ }
+
+ /*
+ * Bounds checking on the ancillary data buffer. When calculating
+ * the number of items to show keep in mind:
+ * - The size of the cmsg structure
+ * - The size of one segment (the size of a Type 0 routing header)
+ * - When dividing add a fudge factor of one in case the
+ * dividend is not evenly divisible by the divisor
+ */
+ rthsize = (rh->ip6r_len + 1) * 8;
+ if (bufsize < (rthsize + CMSG_SPACE(0))) {
+ origsegs = segments;
+ size0 = inet6_rth_space(IPV6_RTHDR_TYPE_0, 0);
+ size1 = inet6_rth_space(IPV6_RTHDR_TYPE_0, 1);
+ segments -= (rthsize - (bufsize - CMSG_SPACE(0))) /
+ (size1 - size0) + 1;
+ warnx("segments truncated, showing only %d (total=%d)",
+ segments, origsegs);
+ }
+
+ for (i = 0; i < segments; i++) {
+ in6 = inet6_rth_getaddr(extbuf, i);
+ if (in6 == NULL)
+ printf(" [%d]<NULL>\n", i);
+ else {
+ if (!inet_ntop(AF_INET6, in6, ntopbuf,
+ sizeof(ntopbuf)))
+ strlcpy(ntopbuf, "?", sizeof(ntopbuf));
+ printf(" [%d]%s\n", i, ntopbuf);
+ }
+ }
+
+ return;
+
+}
+
+#else /* !USE_RFC2292BIS */
+/* ARGSUSED */
+static void
+pr_rthdr(void *extbuf, size_t bufsize __unused)
+{
+ putchar('\n');
+ return;
+}
+#endif /* USE_RFC2292BIS */
+
+static int
+pr_bitrange(u_int32_t v, int soff, int ii)
+{
+ int off;
+ int i;
+
+ off = 0;
+ while (off < 32) {
+ /* shift till we have 0x01 */
+ if ((v & 0x01) == 0) {
+ if (ii > 1)
+ printf("-%u", soff + off - 1);
+ ii = 0;
+ switch (v & 0x0f) {
+ case 0x00:
+ v >>= 4;
+ off += 4;
+ continue;
+ case 0x08:
+ v >>= 3;
+ off += 3;
+ continue;
+ case 0x04: case 0x0c:
+ v >>= 2;
+ off += 2;
+ continue;
+ default:
+ v >>= 1;
+ off += 1;
+ continue;
+ }
+ }
+
+ /* we have 0x01 with us */
+ for (i = 0; i < 32 - off; i++) {
+ if ((v & (0x01 << i)) == 0)
+ break;
+ }
+ if (!ii)
+ printf(" %u", soff + off);
+ ii += i;
+ v >>= i; off += i;
+ }
+ return ii;
+}
+
+static void
+pr_suptypes(struct icmp6_nodeinfo *ni, size_t nilen)
+ /* ni->qtype must be SUPTYPES */
+{
+ size_t clen;
+ u_int32_t v;
+ const u_char *cp, *end;
+ u_int16_t cur;
+ struct cbit {
+ u_int16_t words; /*32bit count*/
+ u_int16_t skip;
+ } cbit;
+#define MAXQTYPES (1 << 16)
+ size_t off;
+ int b;
+
+ cp = (u_char *)(ni + 1);
+ end = ((u_char *)ni) + nilen;
+ cur = 0;
+ b = 0;
+
+ printf("NodeInfo Supported Qtypes");
+ if (options & F_VERBOSE) {
+ if (ni->ni_flags & NI_SUPTYPE_FLAG_COMPRESS)
+ printf(", compressed bitmap");
+ else
+ printf(", raw bitmap");
+ }
+
+ while (cp < end) {
+ clen = (size_t)(end - cp);
+ if ((ni->ni_flags & NI_SUPTYPE_FLAG_COMPRESS) == 0) {
+ if (clen == 0 || clen > MAXQTYPES / 8 ||
+ clen % sizeof(v)) {
+ printf("???");
+ return;
+ }
+ } else {
+ if (clen < sizeof(cbit) || clen % sizeof(v))
+ return;
+ memcpy(&cbit, cp, sizeof(cbit));
+ if (sizeof(cbit) + ntohs(cbit.words) * sizeof(v) >
+ clen)
+ return;
+ cp += sizeof(cbit);
+ clen = ntohs(cbit.words) * sizeof(v);
+ if (cur + clen * 8 + (u_long)ntohs(cbit.skip) * 32 >
+ MAXQTYPES)
+ return;
+ }
+
+ for (off = 0; off < clen; off += sizeof(v)) {
+ memcpy(&v, cp + off, sizeof(v));
+ v = (u_int32_t)ntohl(v);
+ b = pr_bitrange(v, (int)(cur + off * 8), b);
+ }
+ /* flush the remaining bits */
+ b = pr_bitrange(0, (int)(cur + off * 8), b);
+
+ cp += clen;
+ cur += clen * 8;
+ if ((ni->ni_flags & NI_SUPTYPE_FLAG_COMPRESS) != 0)
+ cur += ntohs(cbit.skip) * 32;
+ }
+}
+
+static void
+pr_nodeaddr(struct icmp6_nodeinfo *ni, int nilen)
+ /* ni->qtype must be NODEADDR */
+{
+ u_char *cp = (u_char *)(ni + 1);
+ char ntop_buf[INET6_ADDRSTRLEN];
+ int withttl = 0;
+
+ nilen -= sizeof(struct icmp6_nodeinfo);
+
+ if (options & F_VERBOSE) {
+ switch (ni->ni_code) {
+ case ICMP6_NI_REFUSED:
+ (void)printf("refused");
+ break;
+ case ICMP6_NI_UNKNOWN:
+ (void)printf("unknown qtype");
+ break;
+ }
+ if (ni->ni_flags & NI_NODEADDR_FLAG_TRUNCATE)
+ (void)printf(" truncated");
+ }
+ putchar('\n');
+ if (nilen <= 0)
+ printf(" no address\n");
+
+ /*
+ * In icmp-name-lookups 05 and later, TTL of each returned address
+ * is contained in the resposne. We try to detect the version
+ * by the length of the data, but note that the detection algorithm
+ * is incomplete. We assume the latest draft by default.
+ */
+ if (nilen % (sizeof(u_int32_t) + sizeof(struct in6_addr)) == 0)
+ withttl = 1;
+ while (nilen > 0) {
+ u_int32_t ttl;
+
+ if (withttl) {
+ /* XXX: alignment? */
+ ttl = (u_int32_t)ntohl(*(u_int32_t *)cp);
+ cp += sizeof(u_int32_t);
+ nilen -= sizeof(u_int32_t);
+ }
+
+ if (inet_ntop(AF_INET6, cp, ntop_buf, sizeof(ntop_buf)) ==
+ NULL)
+ strlcpy(ntop_buf, "?", sizeof(ntop_buf));
+ printf(" %s", ntop_buf);
+ if (withttl) {
+ if (ttl == 0xffffffff) {
+ /*
+ * XXX: can this convention be applied to all
+ * type of TTL (i.e. non-ND TTL)?
+ */
+ printf("(TTL=infty)");
+ }
+ else
+ printf("(TTL=%u)", ttl);
+ }
+ putchar('\n');
+
+ nilen -= sizeof(struct in6_addr);
+ cp += sizeof(struct in6_addr);
+ }
+}
+
+static int
+get_hoplim(struct msghdr *mhdr)
+{
+ struct cmsghdr *cm;
+
+ for (cm = (struct cmsghdr *)CMSG_FIRSTHDR(mhdr); cm;
+ cm = (struct cmsghdr *)CMSG_NXTHDR(mhdr, cm)) {
+ if (cm->cmsg_len == 0)
+ return(-1);
+
+ if (cm->cmsg_level == IPPROTO_IPV6 &&
+ cm->cmsg_type == IPV6_HOPLIMIT &&
+ cm->cmsg_len == CMSG_LEN(sizeof(int)))
+ return(*(int *)CMSG_DATA(cm));
+ }
+
+ return(-1);
+}
+
+static struct in6_pktinfo *
+get_rcvpktinfo(struct msghdr *mhdr)
+{
+ struct cmsghdr *cm;
+
+ for (cm = (struct cmsghdr *)CMSG_FIRSTHDR(mhdr); cm;
+ cm = (struct cmsghdr *)CMSG_NXTHDR(mhdr, cm)) {
+ if (cm->cmsg_len == 0)
+ return(NULL);
+
+ if (cm->cmsg_level == IPPROTO_IPV6 &&
+ cm->cmsg_type == IPV6_PKTINFO &&
+ cm->cmsg_len == CMSG_LEN(sizeof(struct in6_pktinfo)))
+ return((struct in6_pktinfo *)CMSG_DATA(cm));
+ }
+
+ return(NULL);
+}
+
+static int
+get_pathmtu(struct msghdr *mhdr)
+{
+#ifdef IPV6_RECVPATHMTU
+ struct cmsghdr *cm;
+ struct ip6_mtuinfo *mtuctl = NULL;
+
+ for (cm = (struct cmsghdr *)CMSG_FIRSTHDR(mhdr); cm;
+ cm = (struct cmsghdr *)CMSG_NXTHDR(mhdr, cm)) {
+ if (cm->cmsg_len == 0)
+ return(0);
+
+ if (cm->cmsg_level == IPPROTO_IPV6 &&
+ cm->cmsg_type == IPV6_PATHMTU &&
+ cm->cmsg_len == CMSG_LEN(sizeof(struct ip6_mtuinfo))) {
+ mtuctl = (struct ip6_mtuinfo *)CMSG_DATA(cm);
+
+ /*
+ * If the notified destination is different from
+ * the one we are pinging, just ignore the info.
+ * We check the scope ID only when both notified value
+ * and our own value have non-0 values, because we may
+ * have used the default scope zone ID for sending,
+ * in which case the scope ID value is 0.
+ */
+ if (!IN6_ARE_ADDR_EQUAL(&mtuctl->ip6m_addr.sin6_addr,
+ &dst.sin6_addr) ||
+ (mtuctl->ip6m_addr.sin6_scope_id &&
+ dst.sin6_scope_id &&
+ mtuctl->ip6m_addr.sin6_scope_id !=
+ dst.sin6_scope_id)) {
+ if ((options & F_VERBOSE) != 0) {
+ printf("path MTU for %s is notified. "
+ "(ignored)\n",
+ pr_addr((struct sockaddr *)&mtuctl->ip6m_addr,
+ sizeof(mtuctl->ip6m_addr)));
+ }
+ return(0);
+ }
+
+ /*
+ * Ignore an invalid MTU. XXX: can we just believe
+ * the kernel check?
+ */
+ if (mtuctl->ip6m_mtu < IPV6_MMTU)
+ return(0);
+
+ /* notification for our destination. return the MTU. */
+ return((int)mtuctl->ip6m_mtu);
+ }
+ }
+#endif
+ return(0);
+}
+
+/*
+ * tvsub --
+ * Subtract 2 timeval structs: out = out - in. Out is assumed to
+ * be >= in.
+ */
+static 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;
+}
+
+/*
+ * onint --
+ * SIGINT handler.
+ */
+/* ARGSUSED */
+static void
+onint(int notused __unused)
+{
+ /*
+ * When doing reverse DNS lookups, the seenint flag might not
+ * be noticed for a while. Just exit if we get a second SIGINT.
+ */
+ if ((options & F_HOSTNAME) && seenint != 0)
+ _exit(nreceived ? 0 : 2);
+}
+
+/*
+ * summary --
+ * Print out statistics.
+ */
+static void
+summary(void)
+{
+
+ (void)printf("\n--- %s ping6 statistics ---\n", hostname);
+ (void)printf("%ld packets transmitted, ", ntransmitted);
+ (void)printf("%ld packets received, ", nreceived);
+ if (nrepeats)
+ (void)printf("+%ld duplicates, ", nrepeats);
+ if (ntransmitted) {
+ if (nreceived > ntransmitted)
+ (void)printf("-- somebody's duplicating packets!");
+ else
+ (void)printf("%.1f%% packet loss",
+ ((((double)ntransmitted - nreceived) * 100.0) /
+ ntransmitted));
+ }
+ if (nrcvtimeout)
+ printf(", %ld packets out of wait time", nrcvtimeout);
+ (void)putchar('\n');
+ if (nreceived && timing) {
+ /* Only display average to microseconds */
+ double num = nreceived + nrepeats;
+ double avg = tsum / num;
+ double dev = sqrt(tsumsq / num - avg * avg);
+ (void)printf(
+ "round-trip min/avg/max/std-dev = %.3f/%.3f/%.3f/%.3f ms\n",
+ tmin, avg, tmax, dev);
+ (void)fflush(stdout);
+ }
+ (void)fflush(stdout);
+}
+
+/*subject type*/
+static const char *niqcode[] = {
+ "IPv6 address",
+ "DNS label", /*or empty*/
+ "IPv4 address",
+};
+
+/*result code*/
+static const char *nircode[] = {
+ "Success", "Refused", "Unknown",
+};
+
+
+/*
+ * pr_icmph --
+ * Print a descriptive string about an ICMP header.
+ */
+static void
+pr_icmph(struct icmp6_hdr *icp, u_char *end)
+{
+ char ntop_buf[INET6_ADDRSTRLEN];
+ struct nd_redirect *red;
+ struct icmp6_nodeinfo *ni;
+ char dnsname[MAXDNAME + 1];
+ const u_char *cp;
+ size_t l;
+
+ switch (icp->icmp6_type) {
+ case ICMP6_DST_UNREACH:
+ switch (icp->icmp6_code) {
+ case ICMP6_DST_UNREACH_NOROUTE:
+ (void)printf("No Route to Destination\n");
+ break;
+ case ICMP6_DST_UNREACH_ADMIN:
+ (void)printf("Destination Administratively "
+ "Unreachable\n");
+ break;
+ case ICMP6_DST_UNREACH_BEYONDSCOPE:
+ (void)printf("Destination Unreachable Beyond Scope\n");
+ break;
+ case ICMP6_DST_UNREACH_ADDR:
+ (void)printf("Destination Host Unreachable\n");
+ break;
+ case ICMP6_DST_UNREACH_NOPORT:
+ (void)printf("Destination Port Unreachable\n");
+ break;
+ default:
+ (void)printf("Destination Unreachable, Bad Code: %d\n",
+ icp->icmp6_code);
+ break;
+ }
+ /* Print returned IP header information */
+ pr_retip((struct ip6_hdr *)(icp + 1), end);
+ break;
+ case ICMP6_PACKET_TOO_BIG:
+ (void)printf("Packet too big mtu = %d\n",
+ (int)ntohl(icp->icmp6_mtu));
+ pr_retip((struct ip6_hdr *)(icp + 1), end);
+ break;
+ case ICMP6_TIME_EXCEEDED:
+ switch (icp->icmp6_code) {
+ case ICMP6_TIME_EXCEED_TRANSIT:
+ (void)printf("Time to live exceeded\n");
+ break;
+ case ICMP6_TIME_EXCEED_REASSEMBLY:
+ (void)printf("Frag reassembly time exceeded\n");
+ break;
+ default:
+ (void)printf("Time exceeded, Bad Code: %d\n",
+ icp->icmp6_code);
+ break;
+ }
+ pr_retip((struct ip6_hdr *)(icp + 1), end);
+ break;
+ case ICMP6_PARAM_PROB:
+ (void)printf("Parameter problem: ");
+ switch (icp->icmp6_code) {
+ case ICMP6_PARAMPROB_HEADER:
+ (void)printf("Erroneous Header ");
+ break;
+ case ICMP6_PARAMPROB_NEXTHEADER:
+ (void)printf("Unknown Nextheader ");
+ break;
+ case ICMP6_PARAMPROB_OPTION:
+ (void)printf("Unrecognized Option ");
+ break;
+ default:
+ (void)printf("Bad code(%d) ", icp->icmp6_code);
+ break;
+ }
+ (void)printf("pointer = 0x%02x\n",
+ (u_int32_t)ntohl(icp->icmp6_pptr));
+ pr_retip((struct ip6_hdr *)(icp + 1), end);
+ break;
+ case ICMP6_ECHO_REQUEST:
+ (void)printf("Echo Request");
+ /* XXX ID + Seq + Data */
+ break;
+ case ICMP6_ECHO_REPLY:
+ (void)printf("Echo Reply");
+ /* XXX ID + Seq + Data */
+ break;
+ case ICMP6_MEMBERSHIP_QUERY:
+ (void)printf("Listener Query");
+ break;
+ case ICMP6_MEMBERSHIP_REPORT:
+ (void)printf("Listener Report");
+ break;
+ case ICMP6_MEMBERSHIP_REDUCTION:
+ (void)printf("Listener Done");
+ break;
+ case ND_ROUTER_SOLICIT:
+ (void)printf("Router Solicitation");
+ break;
+ case ND_ROUTER_ADVERT:
+ (void)printf("Router Advertisement");
+ break;
+ case ND_NEIGHBOR_SOLICIT:
+ (void)printf("Neighbor Solicitation");
+ break;
+ case ND_NEIGHBOR_ADVERT:
+ (void)printf("Neighbor Advertisement");
+ break;
+ case ND_REDIRECT:
+ red = (struct nd_redirect *)icp;
+ (void)printf("Redirect\n");
+ if (!inet_ntop(AF_INET6, &red->nd_rd_dst, ntop_buf,
+ sizeof(ntop_buf)))
+ strlcpy(ntop_buf, "?", sizeof(ntop_buf));
+ (void)printf("Destination: %s", ntop_buf);
+ if (!inet_ntop(AF_INET6, &red->nd_rd_target, ntop_buf,
+ sizeof(ntop_buf)))
+ strlcpy(ntop_buf, "?", sizeof(ntop_buf));
+ (void)printf(" New Target: %s", ntop_buf);
+ break;
+ case ICMP6_NI_QUERY:
+ (void)printf("Node Information Query");
+ /* XXX ID + Seq + Data */
+ ni = (struct icmp6_nodeinfo *)icp;
+ l = end - (u_char *)(ni + 1);
+ printf(", ");
+ switch (ntohs(ni->ni_qtype)) {
+ case NI_QTYPE_NOOP:
+ (void)printf("NOOP");
+ break;
+ case NI_QTYPE_SUPTYPES:
+ (void)printf("Supported qtypes");
+ break;
+ case NI_QTYPE_FQDN:
+ (void)printf("DNS name");
+ break;
+ case NI_QTYPE_NODEADDR:
+ (void)printf("nodeaddr");
+ break;
+ case NI_QTYPE_IPV4ADDR:
+ (void)printf("IPv4 nodeaddr");
+ break;
+ default:
+ (void)printf("unknown qtype");
+ break;
+ }
+ if (options & F_VERBOSE) {
+ switch (ni->ni_code) {
+ case ICMP6_NI_SUBJ_IPV6:
+ if (l == sizeof(struct in6_addr) &&
+ inet_ntop(AF_INET6, ni + 1, ntop_buf,
+ sizeof(ntop_buf)) != NULL) {
+ (void)printf(", subject=%s(%s)",
+ niqcode[ni->ni_code], ntop_buf);
+ } else {
+#if 1
+ /* backward compat to -W */
+ (void)printf(", oldfqdn");
+#else
+ (void)printf(", invalid");
+#endif
+ }
+ break;
+ case ICMP6_NI_SUBJ_FQDN:
+ if (end == (u_char *)(ni + 1)) {
+ (void)printf(", no subject");
+ break;
+ }
+ printf(", subject=%s", niqcode[ni->ni_code]);
+ cp = (const u_char *)(ni + 1);
+ if (dnsdecode(&cp, end, NULL, dnsname,
+ sizeof(dnsname)) != NULL)
+ printf("(%s)", dnsname);
+ else
+ printf("(invalid)");
+ break;
+ case ICMP6_NI_SUBJ_IPV4:
+ if (l == sizeof(struct in_addr) &&
+ inet_ntop(AF_INET, ni + 1, ntop_buf,
+ sizeof(ntop_buf)) != NULL) {
+ (void)printf(", subject=%s(%s)",
+ niqcode[ni->ni_code], ntop_buf);
+ } else
+ (void)printf(", invalid");
+ break;
+ default:
+ (void)printf(", invalid");
+ break;
+ }
+ }
+ break;
+ case ICMP6_NI_REPLY:
+ (void)printf("Node Information Reply");
+ /* XXX ID + Seq + Data */
+ ni = (struct icmp6_nodeinfo *)icp;
+ printf(", ");
+ switch (ntohs(ni->ni_qtype)) {
+ case NI_QTYPE_NOOP:
+ (void)printf("NOOP");
+ break;
+ case NI_QTYPE_SUPTYPES:
+ (void)printf("Supported qtypes");
+ break;
+ case NI_QTYPE_FQDN:
+ (void)printf("DNS name");
+ break;
+ case NI_QTYPE_NODEADDR:
+ (void)printf("nodeaddr");
+ break;
+ case NI_QTYPE_IPV4ADDR:
+ (void)printf("IPv4 nodeaddr");
+ break;
+ default:
+ (void)printf("unknown qtype");
+ break;
+ }
+ if (options & F_VERBOSE) {
+ if (ni->ni_code > sizeof(nircode) / sizeof(nircode[0]))
+ printf(", invalid");
+ else
+ printf(", %s", nircode[ni->ni_code]);
+ }
+ break;
+ default:
+ (void)printf("Bad ICMP type: %d", icp->icmp6_type);
+ }
+}
+
+/*
+ * pr_iph --
+ * Print an IP6 header.
+ */
+static void
+pr_iph(struct ip6_hdr *ip6)
+{
+ u_int32_t flow = ip6->ip6_flow & IPV6_FLOWLABEL_MASK;
+ u_int8_t tc;
+ char ntop_buf[INET6_ADDRSTRLEN];
+
+ tc = *(&ip6->ip6_vfc + 1); /* XXX */
+ tc = (tc >> 4) & 0x0f;
+ tc |= (ip6->ip6_vfc << 4);
+
+ printf("Vr TC Flow Plen Nxt Hlim\n");
+ printf(" %1x %02x %05x %04x %02x %02x\n",
+ (ip6->ip6_vfc & IPV6_VERSION_MASK) >> 4, tc, (u_int32_t)ntohl(flow),
+ ntohs(ip6->ip6_plen), ip6->ip6_nxt, ip6->ip6_hlim);
+ if (!inet_ntop(AF_INET6, &ip6->ip6_src, ntop_buf, sizeof(ntop_buf)))
+ strlcpy(ntop_buf, "?", sizeof(ntop_buf));
+ printf("%s->", ntop_buf);
+ if (!inet_ntop(AF_INET6, &ip6->ip6_dst, ntop_buf, sizeof(ntop_buf)))
+ strlcpy(ntop_buf, "?", sizeof(ntop_buf));
+ printf("%s\n", ntop_buf);
+}
+
+/*
+ * pr_addr --
+ * Return an ascii host address as a dotted quad and optionally with
+ * a hostname.
+ */
+static const char *
+pr_addr(struct sockaddr *addr, int addrlen)
+{
+ static char buf[NI_MAXHOST];
+ int flag = 0;
+
+ if ((options & F_HOSTNAME) == 0)
+ flag |= NI_NUMERICHOST;
+
+ if (getnameinfo(addr, addrlen, buf, sizeof(buf), NULL, 0, flag) == 0)
+ return (buf);
+ else
+ return "?";
+}
+
+/*
+ * pr_retip --
+ * Dump some info on a returned (via ICMPv6) IPv6 packet.
+ */
+static void
+pr_retip(struct ip6_hdr *ip6, u_char *end)
+{
+ u_char *cp = (u_char *)ip6, nh;
+ int hlen;
+
+ if ((size_t)(end - (u_char *)ip6) < sizeof(*ip6)) {
+ printf("IP6");
+ goto trunc;
+ }
+ pr_iph(ip6);
+ hlen = sizeof(*ip6);
+
+ nh = ip6->ip6_nxt;
+ cp += hlen;
+ while (end - cp >= 8) {
+ switch (nh) {
+ case IPPROTO_HOPOPTS:
+ printf("HBH ");
+ hlen = (((struct ip6_hbh *)cp)->ip6h_len+1) << 3;
+ nh = ((struct ip6_hbh *)cp)->ip6h_nxt;
+ break;
+ case IPPROTO_DSTOPTS:
+ printf("DSTOPT ");
+ hlen = (((struct ip6_dest *)cp)->ip6d_len+1) << 3;
+ nh = ((struct ip6_dest *)cp)->ip6d_nxt;
+ break;
+ case IPPROTO_FRAGMENT:
+ printf("FRAG ");
+ hlen = sizeof(struct ip6_frag);
+ nh = ((struct ip6_frag *)cp)->ip6f_nxt;
+ break;
+ case IPPROTO_ROUTING:
+ printf("RTHDR ");
+ hlen = (((struct ip6_rthdr *)cp)->ip6r_len+1) << 3;
+ nh = ((struct ip6_rthdr *)cp)->ip6r_nxt;
+ break;
+#ifdef IPSEC
+ case IPPROTO_AH:
+ printf("AH ");
+ hlen = (((struct ah *)cp)->ah_len+2) << 2;
+ nh = ((struct ah *)cp)->ah_nxt;
+ break;
+#endif
+ case IPPROTO_ICMPV6:
+ printf("ICMP6: type = %d, code = %d\n",
+ *cp, *(cp + 1));
+ return;
+ case IPPROTO_ESP:
+ printf("ESP\n");
+ return;
+ case IPPROTO_TCP:
+ printf("TCP: from port %u, to port %u (decimal)\n",
+ (*cp * 256 + *(cp + 1)),
+ (*(cp + 2) * 256 + *(cp + 3)));
+ return;
+ case IPPROTO_UDP:
+ printf("UDP: from port %u, to port %u (decimal)\n",
+ (*cp * 256 + *(cp + 1)),
+ (*(cp + 2) * 256 + *(cp + 3)));
+ return;
+ default:
+ printf("Unknown Header(%d)\n", nh);
+ return;
+ }
+
+ if ((cp += hlen) >= end)
+ goto trunc;
+ }
+ if (end - cp < 8)
+ goto trunc;
+
+ putchar('\n');
+ return;
+
+ trunc:
+ printf("...\n");
+ return;
+}
+
+static void
+fill(char *bp, char *patp)
+{
+ int ii, jj, kk;
+ int pat[16];
+ char *cp;
+
+ for (cp = patp; *cp; cp++)
+ if (!isxdigit(*cp))
+ errx(1, "patterns must be specified as hex digits");
+ ii = sscanf(patp,
+ "%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x",
+ &pat[0], &pat[1], &pat[2], &pat[3], &pat[4], &pat[5], &pat[6],
+ &pat[7], &pat[8], &pat[9], &pat[10], &pat[11], &pat[12],
+ &pat[13], &pat[14], &pat[15]);
+
+/* xxx */
+ if (ii > 0)
+ for (kk = 0;
+ (size_t)kk <= MAXDATALEN - 8 + sizeof(struct tv32) + ii;
+ kk += ii)
+ for (jj = 0; jj < ii; ++jj)
+ bp[jj + kk] = pat[jj];
+ if (!(options & F_QUIET)) {
+ (void)printf("PATTERN: 0x");
+ for (jj = 0; jj < ii; ++jj)
+ (void)printf("%02x", bp[jj] & 0xFF);
+ (void)printf("\n");
+ }
+}
+
+#ifdef IPSEC
+#ifdef IPSEC_POLICY_IPSEC
+static int
+setpolicy(int so __unused, char *policy)
+{
+ char *buf;
+
+ if (policy == NULL)
+ return 0; /* ignore */
+
+ buf = ipsec_set_policy(policy, strlen(policy));
+ if (buf == NULL)
+ errx(1, "%s", ipsec_strerror());
+ if (setsockopt(s, IPPROTO_IPV6, IPV6_IPSEC_POLICY, buf,
+ ipsec_get_policylen(buf)) < 0)
+ warnx("Unable to set IPsec policy");
+ free(buf);
+
+ return 0;
+}
+#endif
+#endif
+
+static char *
+nigroup(char *name, int nig_oldmcprefix)
+{
+ char *p;
+ char *q;
+ MD5_CTX ctxt;
+ u_int8_t digest[16];
+ u_int8_t c;
+ size_t l;
+ char hbuf[NI_MAXHOST];
+ struct in6_addr in6;
+ int valid;
+
+ p = strchr(name, '.');
+ if (!p)
+ p = name + strlen(name);
+ l = p - name;
+ if (l > 63 || l > sizeof(hbuf) - 1)
+ return NULL; /*label too long*/
+ strncpy(hbuf, name, l);
+ hbuf[(int)l] = '\0';
+
+ for (q = name; *q; q++) {
+ if (isupper(*(unsigned char *)q))
+ *q = tolower(*(unsigned char *)q);
+ }
+
+ /* generate 16 bytes of pseudo-random value. */
+ memset(&ctxt, 0, sizeof(ctxt));
+ MD5Init(&ctxt);
+ c = l & 0xff;
+ MD5Update(&ctxt, &c, sizeof(c));
+ MD5Update(&ctxt, (unsigned char *)name, l);
+ MD5Final(digest, &ctxt);
+
+ if (nig_oldmcprefix) {
+ /* draft-ietf-ipngwg-icmp-name-lookup */
+ valid = inet_pton(AF_INET6, "ff02::2:0000:0000", &in6);
+ } else {
+ /* RFC 4620 */
+ valid = inet_pton(AF_INET6, "ff02::2:ff00:0000", &in6);
+ }
+ if (valid != 1)
+ return NULL; /*XXX*/
+
+ if (nig_oldmcprefix) {
+ /* draft-ietf-ipngwg-icmp-name-lookup */
+ bcopy(digest, &in6.s6_addr[12], 4);
+ } else {
+ /* RFC 4620 */
+ bcopy(digest, &in6.s6_addr[13], 3);
+ }
+
+ if (inet_ntop(AF_INET6, &in6, hbuf, sizeof(hbuf)) == NULL)
+ return NULL;
+
+ return strdup(hbuf);
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr,
+#if defined(IPSEC) && !defined(IPSEC_POLICY_IPSEC)
+ "A"
+#endif
+ "usage: ping6 [-"
+ "Dd"
+#if defined(IPSEC) && !defined(IPSEC_POLICY_IPSEC)
+ "E"
+#endif
+ "fH"
+#ifdef IPV6_USE_MIN_MTU
+ "m"
+#endif
+ "nNoqrRtvwW] "
+ "[-a addrtype] [-b bufsiz] [-c count] [-g gateway]\n"
+ " [-h hoplimit] [-I interface] [-i wait] [-l preload]"
+#if defined(IPSEC) && defined(IPSEC_POLICY_IPSEC)
+ " [-P policy]"
+#endif
+ "\n"
+ " [-p pattern] [-S sourceaddr] [-s packetsize] "
+ "[-x waittime]\n"
+ " [-X timeout] [hops ...] host\n");
+ exit(1);
+}
diff --git a/sbin/quotacheck/Makefile b/sbin/quotacheck/Makefile
new file mode 100644
index 0000000..7be3e35
--- /dev/null
+++ b/sbin/quotacheck/Makefile
@@ -0,0 +1,12 @@
+# $FreeBSD$
+# @(#)Makefile 8.1 (Berkeley) 6/5/93
+
+PROG= quotacheck
+SRCS= quotacheck.c preen.c fsutil.c utilities.c
+WARNS?= 2
+MAN= quotacheck.8
+LIBADD= util
+
+.PATH: ${.CURDIR}/../fsck ${.CURDIR}/../fsck_ffs
+
+.include <bsd.prog.mk>
diff --git a/sbin/quotacheck/preen.c b/sbin/quotacheck/preen.c
new file mode 100644
index 0000000..1c285cc
--- /dev/null
+++ b/sbin/quotacheck/preen.c
@@ -0,0 +1,289 @@
+/* $NetBSD: preen.c,v 1.18 1998/07/26 20:02:36 mycroft Exp $ */
+
+/*
+ * Copyright (c) 1990, 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.
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)preen.c 8.5 (Berkeley) 4/28/95";
+#else
+__RCSID("$NetBSD: preen.c,v 1.18 1998/07/26 20:02:36 mycroft Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/queue.h>
+
+#include <ufs/ufs/quota.h>
+
+#include <err.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <fstab.h>
+#include <libutil.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "quotacheck.h"
+
+struct partentry {
+ TAILQ_ENTRY(partentry) p_entries;
+ char *p_devname; /* device name */
+ const char *p_mntpt; /* mount point */
+ struct quotafile *p_qfu; /* user quota file info ptr */
+ struct quotafile *p_qfg; /* group quota file info */
+};
+
+TAILQ_HEAD(part, partentry) badh;
+
+struct diskentry {
+ TAILQ_ENTRY(diskentry) d_entries;
+ char *d_name; /* disk base name */
+ TAILQ_HEAD(prt, partentry) d_part; /* list of partitions on disk */
+ int d_pid; /* 0 or pid of fsck proc */
+};
+
+TAILQ_HEAD(disk, diskentry) diskh;
+
+static struct diskentry *finddisk(const char *);
+static void addpart(struct fstab *, struct quotafile *, struct quotafile *);
+static int startdisk(struct diskentry *);
+extern void *emalloc(size_t);
+extern char *estrdup(const char *);
+
+int
+checkfstab(int uflag, int gflag)
+{
+ struct fstab *fs;
+ struct diskentry *d, *nextdisk;
+ struct partentry *p;
+ int ret, pid, retcode, passno, sumstatus, status, nextpass;
+ struct quotafile *qfu, *qfg;
+
+ TAILQ_INIT(&badh);
+ TAILQ_INIT(&diskh);
+
+ sumstatus = 0;
+
+ nextpass = 0;
+ for (passno = 1; nextpass != INT_MAX; passno = nextpass) {
+ nextpass = INT_MAX;
+ if (setfsent() == 0) {
+ warnx("Can't open checklist file: %s\n", _PATH_FSTAB);
+ return (8);
+ }
+ while ((fs = getfsent()) != 0) {
+ if (fs->fs_passno > passno && fs->fs_passno < nextpass)
+ nextpass = fs->fs_passno;
+
+ if (passno != fs->fs_passno)
+ continue;
+
+ qfu = NULL;
+ if (uflag)
+ qfu = quota_open(fs, USRQUOTA, O_CREAT|O_RDWR);
+ qfg = NULL;
+ if (gflag)
+ qfg = quota_open(fs, GRPQUOTA, O_CREAT|O_RDWR);
+ if (qfu == NULL && qfg == NULL)
+ continue;
+
+ if (passno == 1) {
+ sumstatus = chkquota(fs->fs_spec, qfu, qfg);
+ if (qfu)
+ quota_close(qfu);
+ if (qfg)
+ quota_close(qfg);
+ if (sumstatus)
+ return (sumstatus);
+ continue;
+ }
+ addpart(fs, qfu, qfg);
+ }
+
+ if (passno == 1)
+ continue;
+
+ TAILQ_FOREACH(nextdisk, &diskh, d_entries) {
+ if ((ret = startdisk(nextdisk)) != 0)
+ return ret;
+ }
+
+ while ((pid = wait(&status)) != -1) {
+ TAILQ_FOREACH(d, &diskh, d_entries)
+ if (d->d_pid == pid)
+ break;
+
+ if (d == NULL) {
+ warnx("Unknown pid %d\n", pid);
+ continue;
+ }
+
+ if (WIFEXITED(status))
+ retcode = WEXITSTATUS(status);
+ else
+ retcode = 0;
+
+ p = TAILQ_FIRST(&d->d_part);
+
+ if (WIFSIGNALED(status)) {
+ (void) fprintf(stderr,
+ "%s: (%s): EXITED WITH SIGNAL %d\n",
+ p->p_devname, p->p_mntpt,
+ WTERMSIG(status));
+ retcode = 8;
+ }
+
+ TAILQ_REMOVE(&d->d_part, p, p_entries);
+
+ if (retcode != 0) {
+ TAILQ_INSERT_TAIL(&badh, p, p_entries);
+ sumstatus |= retcode;
+ } else {
+ free(p->p_devname);
+ if (p->p_qfu)
+ quota_close(p->p_qfu);
+ if (p->p_qfg)
+ quota_close(p->p_qfg);
+ free(p);
+ }
+ d->d_pid = 0;
+
+ if (TAILQ_EMPTY(&d->d_part)) {
+ TAILQ_REMOVE(&diskh, d, d_entries);
+ } else {
+ if ((ret = startdisk(d)) != 0)
+ return ret;
+ }
+ }
+ }
+
+ if (sumstatus) {
+ p = TAILQ_FIRST(&badh);
+ if (p == NULL)
+ return (sumstatus);
+
+ (void) fprintf(stderr,
+ "THE FOLLOWING FILE SYSTEM%s HAD AN %s\n\t",
+ TAILQ_NEXT(p, p_entries) ? "S" : "",
+ "UNEXPECTED INCONSISTENCY:");
+
+ for (; p; p = TAILQ_NEXT(p, p_entries))
+ (void) fprintf(stderr,
+ "%s: (%s)%s", p->p_devname, p->p_mntpt,
+ TAILQ_NEXT(p, p_entries) ? ", " : "\n");
+
+ return sumstatus;
+ }
+ (void) endfsent();
+ return (0);
+}
+
+
+static struct diskentry *
+finddisk(const char *name)
+{
+ const char *p;
+ size_t len = 0;
+ struct diskentry *d;
+
+ p = strrchr(name, '/');
+ if (p == NULL)
+ p = name;
+ else
+ p++;
+ for (; *p && !isdigit(*p); p++)
+ continue;
+ for (; *p && isdigit(*p); p++)
+ continue;
+ len = p - name;
+ if (len == 0)
+ len = strlen(name);
+
+ TAILQ_FOREACH(d, &diskh, d_entries)
+ if (strncmp(d->d_name, name, len) == 0 && d->d_name[len] == 0)
+ return d;
+
+ d = emalloc(sizeof(*d));
+ d->d_name = estrdup(name);
+ d->d_name[len] = '\0';
+ TAILQ_INIT(&d->d_part);
+ d->d_pid = 0;
+
+ TAILQ_INSERT_TAIL(&diskh, d, d_entries);
+
+ return d;
+}
+
+static void
+addpart(struct fstab *fs, struct quotafile *qfu, struct quotafile *qfg)
+{
+ struct diskentry *d = finddisk(fs->fs_spec);
+ struct partentry *p;
+
+ TAILQ_FOREACH(p, &d->d_part, p_entries)
+ if (strcmp(p->p_devname, fs->fs_spec) == 0) {
+ warnx("%s in fstab more than once!\n", fs->fs_spec);
+ return;
+ }
+
+ p = emalloc(sizeof(*p));
+ p->p_devname = estrdup(blockcheck(fs->fs_spec));
+ if (qfu != NULL)
+ p->p_mntpt = quota_fsname(qfu);
+ else
+ p->p_mntpt = quota_fsname(qfg);
+ p->p_qfu = qfu;
+ p->p_qfg = qfg;
+
+ TAILQ_INSERT_TAIL(&d->d_part, p, p_entries);
+}
+
+
+static int
+startdisk(struct diskentry *d)
+{
+ struct partentry *p = TAILQ_FIRST(&d->d_part);
+
+ d->d_pid = fork();
+ if (d->d_pid < 0) {
+ perror("fork");
+ return (8);
+ }
+ if (d->d_pid == 0)
+ exit(chkquota(p->p_devname, p->p_qfu, p->p_qfg));
+ return (0);
+}
diff --git a/sbin/quotacheck/quotacheck.8 b/sbin/quotacheck/quotacheck.8
new file mode 100644
index 0000000..0ca59ac
--- /dev/null
+++ b/sbin/quotacheck/quotacheck.8
@@ -0,0 +1,203 @@
+.\" Copyright (c) 1983, 1990, 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" Robert Elz at The University of Melbourne.
+.\"
+.\" 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.
+.\" 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.
+.\"
+.\" @(#)quotacheck.8 8.1 (Berkeley) 6/5/93
+.\" $FreeBSD$
+.\"
+.Dd January 25, 2007
+.Dt QUOTACHECK 8
+.Os
+.Sh NAME
+.Nm quotacheck
+.Nd file system quota consistency checker
+.Sh SYNOPSIS
+.Nm
+.Op Fl guv
+.Op Fl c Ar 32 | 64
+.Op Fl l Ar maxrun
+.Fl a
+.Nm
+.Op Fl guv
+.Op Fl c Ar 32 | 64
+.Ar filesystem ...
+.Sh DESCRIPTION
+The
+.Nm
+utility examines each file system,
+builds a table of current disk usage,
+and compares this table against that recorded
+in the disk quota file for the file system.
+If any inconsistencies are detected, both the
+quota file and the current system copy of the
+incorrect quotas are updated (the latter only
+occurs if an active file system is checked).
+By default both user and group quotas are checked.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl a
+If supplied in place of any file system names,
+.Nm
+will check all the file systems indicated in
+.Pa /etc/fstab
+to be read-write with disk quotas.
+By default only the types of quotas listed in
+.Pa /etc/fstab
+are checked.
+.It Fl c Ar 32 | 64
+Before performing its checks,
+.Nm
+will convert the quota file to the specified word size.
+A conversion size of 64 is given to request conversion to
+the new 64-bit quota file format.
+A conversion size of 32 is given to request conversion back to
+the old 32-bit quota file format.
+The original quota file is left unchanged and moved aside with an
+underscore and its format size plus a
+.Pa .orig
+extension added to its name.
+Thus, the original 32-bit
+.Pa quota.user
+quota file converted to the 64-bit format quota file will be renamed to
+.Pa quota.user_32.orig .
+.It Fl g
+Only group quotas listed in
+.Pa /etc/fstab
+are to be checked.
+.It Fl l Ar maxrun
+Specifies the maximum number of concurrent file systems
+to check in parallel.
+If this option is omitted, or if
+.Ar maxrun
+is zero, parallel passes are run as per
+.Xr fsck 8 .
+This option is deprecated and parallel passes are always run
+as per
+.Xr fsck 8 .
+.It Fl u
+Only user quotas listed in
+.Pa /etc/fstab
+are to be checked.
+.It Fl v
+Report discrepancies between the
+calculated and recorded disk quotas and other additional diagnostic messages.
+.El
+.Pp
+Specifying both
+.Fl g
+and
+.Fl u
+is equivalent to the default.
+Parallel passes are run on the file systems required,
+using the pass numbers in
+.Pa /etc/fstab
+in an identical fashion to
+.Xr fsck 8 .
+.Pp
+Normally,
+.Nm
+operates silently.
+.Pp
+The
+.Nm
+utility expects each file system to be checked to have a
+quota files named
+.Pa quota.user
+and
+.Pa quota.group
+which are located at the root of the associated file system.
+These defaults may be overridden in
+.Pa /etc/fstab .
+If a file is not present,
+.Nm
+will create it.
+These files should be edited with the
+.Xr edquota 8
+utility.
+.Pp
+The
+.Nm
+utility is normally run at boot time from the
+.Pa /etc/rc
+file.
+The rc startup procedure is controlled by the
+.Pa /etc/rc.conf
+variable
+.Ar check_quotas .
+Note that to enable this functionality in
+.Pa /etc/rc
+you also need to enable startup quota procedures
+with the variable
+.Ar enable_quotas
+in
+.Pa /etc/rc.conf .
+The kernel must also be built with
+.Cd "options QUOTA" .
+.Pp
+The
+.Nm
+utility accesses the raw device in calculating the actual
+disk usage for each user.
+Thus, the file systems
+checked should be quiescent while
+.Nm
+is running.
+.Sh FILES
+.Bl -tag -width quota.group -compact
+.It Pa quota.user
+at the file system root with user quotas
+.It Pa quota.group
+at the file system root with group quotas
+.It Pa /etc/fstab
+default file systems
+.El
+.Sh SEE ALSO
+.Xr quota 1 ,
+.Xr quotactl 2 ,
+.Xr fstab 5 ,
+.Xr rc.conf 5 ,
+.Xr edquota 8 ,
+.Xr fsck 8 ,
+.Xr quotaon 8 ,
+.Xr repquota 8
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Bx 4.2 .
+.Sh BUGS
+The quota system will ignore UIDs or GIDs that would be negative
+when evaluated as a signed value.
+Typically those types of ids can appear in the file system from NFS
+mounts or archive files from other operating systems.
+Extremely large UIDs or GIDs will cause
+.Nm
+to run for an unreasonable amount of time and also produce extremely
+large quota data files.
diff --git a/sbin/quotacheck/quotacheck.c b/sbin/quotacheck/quotacheck.c
new file mode 100644
index 0000000..a32ac84
--- /dev/null
+++ b/sbin/quotacheck/quotacheck.c
@@ -0,0 +1,725 @@
+/*
+ * Copyright (c) 1980, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Robert Elz at The University of Melbourne.
+ *
+ * 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.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if 0
+#ifndef lint
+static const char copyright[] =
+"@(#) Copyright (c) 1980, 1990, 1993\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static char sccsid[] = "@(#)quotacheck.c 8.3 (Berkeley) 1/29/94";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*
+ * Fix up / report on disk quotas & usage
+ */
+#include <sys/param.h>
+#include <sys/disklabel.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+
+#include <ufs/ufs/dinode.h>
+#include <ufs/ufs/quota.h>
+#include <ufs/ffs/fs.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <fstab.h>
+#include <grp.h>
+#include <libutil.h>
+#include <pwd.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "quotacheck.h"
+
+char *qfname = QUOTAFILENAME;
+char *qfextension[] = INITQFNAMES;
+char *quotagroup = QUOTAGROUP;
+
+union {
+ struct fs sblk;
+ char dummy[MAXBSIZE];
+} sb_un;
+#define sblock sb_un.sblk
+union {
+ struct cg cgblk;
+ char dummy[MAXBSIZE];
+} cg_un;
+#define cgblk cg_un.cgblk
+long dev_bsize = 1;
+ino_t maxino;
+
+union dinode {
+ struct ufs1_dinode dp1;
+ struct ufs2_dinode dp2;
+};
+#define DIP(dp, field) \
+ ((sblock.fs_magic == FS_UFS1_MAGIC) ? \
+ (dp)->dp1.field : (dp)->dp2.field)
+
+#define HASUSR 1
+#define HASGRP 2
+
+struct fileusage {
+ struct fileusage *fu_next;
+ u_long fu_curinodes;
+ u_long fu_curblocks;
+ u_long fu_id;
+ char fu_name[1];
+ /* actually bigger */
+};
+#define FUHASH 1024 /* must be power of two */
+struct fileusage *fuhead[MAXQUOTAS][FUHASH];
+
+int aflag; /* all file systems */
+int cflag; /* convert format to 32 or 64 bit size */
+int gflag; /* check group quotas */
+int uflag; /* check user quotas */
+int vflag; /* verbose */
+int fi; /* open disk file descriptor */
+
+struct fileusage *
+ addid(u_long, int, char *, const char *);
+void bread(ufs2_daddr_t, char *, long);
+void freeinodebuf(void);
+union dinode *
+ getnextinode(ino_t);
+int getquotagid(void);
+struct fileusage *
+ lookup(u_long, int);
+int oneof(char *, char*[], int);
+void printchanges(const char *, int, struct dqblk *, struct fileusage *,
+ u_long);
+void setinodebuf(ino_t);
+int update(const char *, struct quotafile *, int);
+void usage(void);
+
+int
+main(int argc, char *argv[])
+{
+ struct fstab *fs;
+ struct passwd *pw;
+ struct group *gr;
+ struct quotafile *qfu, *qfg;
+ int i, argnum, maxrun, errs, ch;
+ long done = 0;
+ char *name;
+
+ errs = maxrun = 0;
+ while ((ch = getopt(argc, argv, "ac:guvl:")) != -1) {
+ switch(ch) {
+ case 'a':
+ aflag++;
+ break;
+ case 'c':
+ if (cflag)
+ usage();
+ cflag = atoi(optarg);
+ break;
+ case 'g':
+ gflag++;
+ break;
+ case 'u':
+ uflag++;
+ break;
+ case 'v':
+ vflag++;
+ break;
+ case 'l':
+ maxrun = atoi(optarg);
+ break;
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if ((argc == 0 && !aflag) || (argc > 0 && aflag))
+ usage();
+ if (cflag && cflag != 32 && cflag != 64)
+ usage();
+ if (!gflag && !uflag) {
+ gflag++;
+ uflag++;
+ }
+ if (gflag) {
+ setgrent();
+ while ((gr = getgrent()) != NULL)
+ (void) addid((u_long)gr->gr_gid, GRPQUOTA, gr->gr_name,
+ NULL);
+ endgrent();
+ }
+ if (uflag) {
+ setpwent();
+ while ((pw = getpwent()) != NULL)
+ (void) addid((u_long)pw->pw_uid, USRQUOTA, pw->pw_name,
+ NULL);
+ endpwent();
+ }
+ /*
+ * The maxrun (-l) option is now deprecated.
+ */
+ if (maxrun > 0)
+ warnx("the -l option is now deprecated");
+ if (aflag)
+ exit(checkfstab(uflag, gflag));
+ if (setfsent() == 0)
+ errx(1, "%s: can't open", FSTAB);
+ while ((fs = getfsent()) != NULL) {
+ if (((argnum = oneof(fs->fs_file, argv, argc)) >= 0 ||
+ (argnum = oneof(fs->fs_spec, argv, argc)) >= 0) &&
+ (name = blockcheck(fs->fs_spec))) {
+ done |= 1 << argnum;
+ qfu = NULL;
+ if (uflag)
+ qfu = quota_open(fs, USRQUOTA, O_CREAT|O_RDWR);
+ qfg = NULL;
+ if (gflag)
+ qfg = quota_open(fs, GRPQUOTA, O_CREAT|O_RDWR);
+ if (qfu == NULL && qfg == NULL)
+ continue;
+ errs += chkquota(name, qfu, qfg);
+ if (qfu)
+ quota_close(qfu);
+ if (qfg)
+ quota_close(qfg);
+ }
+ }
+ endfsent();
+ for (i = 0; i < argc; i++)
+ if ((done & (1 << i)) == 0)
+ fprintf(stderr, "%s not found in %s\n",
+ argv[i], FSTAB);
+ exit(errs);
+}
+
+void
+usage(void)
+{
+ (void)fprintf(stderr, "%s\n%s\n",
+ "usage: quotacheck [-guv] [-c 32 | 64] [-l maxrun] -a",
+ " quotacheck [-guv] [-c 32 | 64] filesystem ...");
+ exit(1);
+}
+
+/*
+ * Possible superblock locations ordered from most to least likely.
+ */
+static int sblock_try[] = SBLOCKSEARCH;
+
+/*
+ * Scan the specified file system to check quota(s) present on it.
+ */
+int
+chkquota(char *specname, struct quotafile *qfu, struct quotafile *qfg)
+{
+ struct fileusage *fup;
+ union dinode *dp;
+ int cg, i, mode, errs = 0;
+ ino_t ino, inosused, userino = 0, groupino = 0;
+ dev_t dev, userdev = 0, groupdev = 0;
+ struct stat sb;
+ const char *mntpt;
+ char *cp;
+
+ if (qfu != NULL)
+ mntpt = quota_fsname(qfu);
+ else if (qfg != NULL)
+ mntpt = quota_fsname(qfg);
+ else
+ errx(1, "null quotafile information passed to chkquota()\n");
+ if (cflag) {
+ if (vflag && qfu != NULL)
+ printf("%s: convert user quota to %d bits\n",
+ mntpt, cflag);
+ if (qfu != NULL && quota_convert(qfu, cflag) < 0) {
+ if (errno == EBADF)
+ errx(1,
+ "%s: cannot convert an active quota file",
+ mntpt);
+ err(1, "user quota conversion to size %d failed",
+ cflag);
+ }
+ if (vflag && qfg != NULL)
+ printf("%s: convert group quota to %d bits\n",
+ mntpt, cflag);
+ if (qfg != NULL && quota_convert(qfg, cflag) < 0) {
+ if (errno == EBADF)
+ errx(1,
+ "%s: cannot convert an active quota file",
+ mntpt);
+ err(1, "group quota conversion to size %d failed",
+ cflag);
+ }
+ }
+ if ((fi = open(specname, O_RDONLY, 0)) < 0) {
+ warn("%s", specname);
+ return (1);
+ }
+ if ((stat(mntpt, &sb)) < 0) {
+ warn("%s", mntpt);
+ return (1);
+ }
+ dev = sb.st_dev;
+ if (vflag) {
+ (void)printf("*** Checking ");
+ if (qfu)
+ (void)printf("user%s", qfg ? " and " : "");
+ if (qfg)
+ (void)printf("group");
+ (void)printf(" quotas for %s (%s)\n", specname, mntpt);
+ }
+ if (qfu) {
+ if (stat(quota_qfname(qfu), &sb) == 0) {
+ userino = sb.st_ino;
+ userdev = sb.st_dev;
+ }
+ }
+ if (qfg) {
+ if (stat(quota_qfname(qfg), &sb) == 0) {
+ groupino = sb.st_ino;
+ groupdev = sb.st_dev;
+ }
+ }
+ sync();
+ dev_bsize = 1;
+ for (i = 0; sblock_try[i] != -1; i++) {
+ bread(sblock_try[i], (char *)&sblock, (long)SBLOCKSIZE);
+ if ((sblock.fs_magic == FS_UFS1_MAGIC ||
+ (sblock.fs_magic == FS_UFS2_MAGIC &&
+ sblock.fs_sblockloc == sblock_try[i])) &&
+ sblock.fs_bsize <= MAXBSIZE &&
+ sblock.fs_bsize >= sizeof(struct fs))
+ break;
+ }
+ if (sblock_try[i] == -1) {
+ warn("Cannot find file system superblock");
+ return (1);
+ }
+ dev_bsize = sblock.fs_fsize / fsbtodb(&sblock, 1);
+ maxino = sblock.fs_ncg * sblock.fs_ipg;
+ for (cg = 0; cg < sblock.fs_ncg; cg++) {
+ ino = cg * sblock.fs_ipg;
+ setinodebuf(ino);
+ bread(fsbtodb(&sblock, cgtod(&sblock, cg)), (char *)(&cgblk),
+ sblock.fs_cgsize);
+ if (sblock.fs_magic == FS_UFS2_MAGIC)
+ inosused = cgblk.cg_initediblk;
+ else
+ inosused = sblock.fs_ipg;
+ /*
+ * If we are using soft updates, then we can trust the
+ * cylinder group inode allocation maps to tell us which
+ * inodes are allocated. We will scan the used inode map
+ * to find the inodes that are really in use, and then
+ * read only those inodes in from disk.
+ */
+ if (sblock.fs_flags & FS_DOSOFTDEP) {
+ if (!cg_chkmagic(&cgblk))
+ errx(1, "CG %d: BAD MAGIC NUMBER\n", cg);
+ cp = &cg_inosused(&cgblk)[(inosused - 1) / CHAR_BIT];
+ for ( ; inosused > 0; inosused -= CHAR_BIT, cp--) {
+ if (*cp == 0)
+ continue;
+ for (i = 1 << (CHAR_BIT - 1); i > 0; i >>= 1) {
+ if (*cp & i)
+ break;
+ inosused--;
+ }
+ break;
+ }
+ if (inosused <= 0)
+ continue;
+ }
+ for (i = 0; i < inosused; i++, ino++) {
+ if ((dp = getnextinode(ino)) == NULL || ino < ROOTINO ||
+ (mode = DIP(dp, di_mode) & IFMT) == 0)
+ continue;
+ /*
+ * XXX: Do not account for UIDs or GIDs that appear
+ * to be negative to prevent generating 100GB+
+ * quota files.
+ */
+ if ((int)DIP(dp, di_uid) < 0 ||
+ (int)DIP(dp, di_gid) < 0) {
+ if (vflag) {
+ if (aflag)
+ (void)printf("%s: ", mntpt);
+ (void)printf("out of range UID/GID (%u/%u) ino=%ju\n",
+ DIP(dp, di_uid), DIP(dp,di_gid),
+ (uintmax_t)ino);
+ }
+ continue;
+ }
+
+ /*
+ * Do not account for file system snapshot files
+ * or the actual quota data files to be consistent
+ * with how they are handled inside the kernel.
+ */
+#ifdef SF_SNAPSHOT
+ if (DIP(dp, di_flags) & SF_SNAPSHOT)
+ continue;
+#endif
+ if ((ino == userino && dev == userdev) ||
+ (ino == groupino && dev == groupdev))
+ continue;
+ if (qfg) {
+ fup = addid((u_long)DIP(dp, di_gid), GRPQUOTA,
+ (char *)0, mntpt);
+ fup->fu_curinodes++;
+ if (mode == IFREG || mode == IFDIR ||
+ mode == IFLNK)
+ fup->fu_curblocks += DIP(dp, di_blocks);
+ }
+ if (qfu) {
+ fup = addid((u_long)DIP(dp, di_uid), USRQUOTA,
+ (char *)0, mntpt);
+ fup->fu_curinodes++;
+ if (mode == IFREG || mode == IFDIR ||
+ mode == IFLNK)
+ fup->fu_curblocks += DIP(dp, di_blocks);
+ }
+ }
+ }
+ freeinodebuf();
+ if (qfu)
+ errs += update(mntpt, qfu, USRQUOTA);
+ if (qfg)
+ errs += update(mntpt, qfg, GRPQUOTA);
+ close(fi);
+ (void)fflush(stdout);
+ return (errs);
+}
+
+/*
+ * Update a specified quota file.
+ */
+int
+update(const char *fsname, struct quotafile *qf, int type)
+{
+ struct fileusage *fup;
+ u_long id, lastid, highid = 0;
+ struct dqblk dqbuf;
+ struct stat sb;
+ static struct dqblk zerodqbuf;
+ static struct fileusage zerofileusage;
+
+ /*
+ * Scan the on-disk quota file and record any usage changes.
+ */
+ lastid = quota_maxid(qf);
+ for (id = 0; id <= lastid; id++) {
+ if (quota_read(qf, &dqbuf, id) < 0)
+ dqbuf = zerodqbuf;
+ if ((fup = lookup(id, type)) == NULL)
+ fup = &zerofileusage;
+ if (fup->fu_curinodes || fup->fu_curblocks ||
+ dqbuf.dqb_bsoftlimit || dqbuf.dqb_bhardlimit ||
+ dqbuf.dqb_isoftlimit || dqbuf.dqb_ihardlimit)
+ highid = id;
+ if (dqbuf.dqb_curinodes == fup->fu_curinodes &&
+ dqbuf.dqb_curblocks == fup->fu_curblocks) {
+ fup->fu_curinodes = 0;
+ fup->fu_curblocks = 0;
+ continue;
+ }
+ printchanges(fsname, type, &dqbuf, fup, id);
+ dqbuf.dqb_curinodes = fup->fu_curinodes;
+ dqbuf.dqb_curblocks = fup->fu_curblocks;
+ (void) quota_write_usage(qf, &dqbuf, id);
+ fup->fu_curinodes = 0;
+ fup->fu_curblocks = 0;
+ }
+
+ /*
+ * Walk the hash table looking for ids with non-zero usage
+ * that are not currently recorded in the quota file. E.g.
+ * ids that are past the end of the current file.
+ */
+ for (id = 0; id < FUHASH; id++) {
+ for (fup = fuhead[type][id]; fup != NULL; fup = fup->fu_next) {
+ if (fup->fu_id <= lastid)
+ continue;
+ if (fup->fu_curinodes == 0 && fup->fu_curblocks == 0)
+ continue;
+ bzero(&dqbuf, sizeof(struct dqblk));
+ if (fup->fu_id > highid)
+ highid = fup->fu_id;
+ printchanges(fsname, type, &dqbuf, fup, fup->fu_id);
+ dqbuf.dqb_curinodes = fup->fu_curinodes;
+ dqbuf.dqb_curblocks = fup->fu_curblocks;
+ (void) quota_write_usage(qf, &dqbuf, fup->fu_id);
+ fup->fu_curinodes = 0;
+ fup->fu_curblocks = 0;
+ }
+ }
+ /*
+ * If this is old format file, then size may be smaller,
+ * so ensure that we only truncate when it will make things
+ * smaller, and not if it will grow an old format file.
+ */
+ if (highid < lastid &&
+ stat(quota_qfname(qf), &sb) == 0 &&
+ sb.st_size > (((off_t)highid + 2) * sizeof(struct dqblk)))
+ truncate(quota_qfname(qf),
+ (((off_t)highid + 2) * sizeof(struct dqblk)));
+ return (0);
+}
+
+/*
+ * Check to see if target appears in list of size cnt.
+ */
+int
+oneof(char *target, char *list[], int cnt)
+{
+ int i;
+
+ for (i = 0; i < cnt; i++)
+ if (strcmp(target, list[i]) == 0)
+ return (i);
+ return (-1);
+}
+
+/*
+ * Determine the group identifier for quota files.
+ */
+int
+getquotagid(void)
+{
+ struct group *gr;
+
+ if ((gr = getgrnam(quotagroup)) != NULL)
+ return (gr->gr_gid);
+ return (-1);
+}
+
+/*
+ * Routines to manage the file usage table.
+ *
+ * Lookup an id of a specific type.
+ */
+struct fileusage *
+lookup(u_long id, int type)
+{
+ struct fileusage *fup;
+
+ for (fup = fuhead[type][id & (FUHASH-1)]; fup != 0; fup = fup->fu_next)
+ if (fup->fu_id == id)
+ return (fup);
+ return (NULL);
+}
+
+/*
+ * Add a new file usage id if it does not already exist.
+ */
+struct fileusage *
+addid(u_long id, int type, char *name, const char *fsname)
+{
+ struct fileusage *fup, **fhp;
+ int len;
+
+ if ((fup = lookup(id, type)) != NULL)
+ return (fup);
+ if (name)
+ len = strlen(name);
+ else
+ len = 0;
+ if ((fup = calloc(1, sizeof(*fup) + len)) == NULL)
+ errx(1, "calloc failed");
+ fhp = &fuhead[type][id & (FUHASH - 1)];
+ fup->fu_next = *fhp;
+ *fhp = fup;
+ fup->fu_id = id;
+ if (name)
+ bcopy(name, fup->fu_name, len + 1);
+ else {
+ (void)sprintf(fup->fu_name, "%lu", id);
+ if (vflag) {
+ if (aflag && fsname != NULL)
+ (void)printf("%s: ", fsname);
+ printf("unknown %cid: %lu\n",
+ type == USRQUOTA ? 'u' : 'g', id);
+ }
+ }
+ return (fup);
+}
+
+/*
+ * Special purpose version of ginode used to optimize pass
+ * over all the inodes in numerical order.
+ */
+static ino_t nextino, lastinum, lastvalidinum;
+static long readcnt, readpercg, fullcnt, inobufsize, partialcnt, partialsize;
+static caddr_t inodebuf;
+#define INOBUFSIZE 56*1024 /* size of buffer to read inodes */
+
+union dinode *
+getnextinode(ino_t inumber)
+{
+ long size;
+ ufs2_daddr_t dblk;
+ union dinode *dp;
+ static caddr_t nextinop;
+
+ if (inumber != nextino++ || inumber > lastvalidinum)
+ errx(1, "bad inode number %ju to nextinode",
+ (uintmax_t)inumber);
+ if (inumber >= lastinum) {
+ readcnt++;
+ dblk = fsbtodb(&sblock, ino_to_fsba(&sblock, lastinum));
+ if (readcnt % readpercg == 0) {
+ size = partialsize;
+ lastinum += partialcnt;
+ } else {
+ size = inobufsize;
+ lastinum += fullcnt;
+ }
+ /*
+ * If bread returns an error, it will already have zeroed
+ * out the buffer, so we do not need to do so here.
+ */
+ bread(dblk, inodebuf, size);
+ nextinop = inodebuf;
+ }
+ dp = (union dinode *)nextinop;
+ if (sblock.fs_magic == FS_UFS1_MAGIC)
+ nextinop += sizeof(struct ufs1_dinode);
+ else
+ nextinop += sizeof(struct ufs2_dinode);
+ return (dp);
+}
+
+/*
+ * Prepare to scan a set of inodes.
+ */
+void
+setinodebuf(ino_t inum)
+{
+
+ if (inum % sblock.fs_ipg != 0)
+ errx(1, "bad inode number %ju to setinodebuf", (uintmax_t)inum);
+ lastvalidinum = inum + sblock.fs_ipg - 1;
+ nextino = inum;
+ lastinum = inum;
+ readcnt = 0;
+ if (inodebuf != NULL)
+ return;
+ inobufsize = blkroundup(&sblock, INOBUFSIZE);
+ fullcnt = inobufsize / ((sblock.fs_magic == FS_UFS1_MAGIC) ?
+ sizeof(struct ufs1_dinode) : sizeof(struct ufs2_dinode));
+ readpercg = sblock.fs_ipg / fullcnt;
+ partialcnt = sblock.fs_ipg % fullcnt;
+ partialsize = partialcnt * ((sblock.fs_magic == FS_UFS1_MAGIC) ?
+ sizeof(struct ufs1_dinode) : sizeof(struct ufs2_dinode));
+ if (partialcnt != 0) {
+ readpercg++;
+ } else {
+ partialcnt = fullcnt;
+ partialsize = inobufsize;
+ }
+ if ((inodebuf = malloc((unsigned)inobufsize)) == NULL)
+ errx(1, "cannot allocate space for inode buffer");
+}
+
+/*
+ * Free up data structures used to scan inodes.
+ */
+void
+freeinodebuf(void)
+{
+
+ if (inodebuf != NULL)
+ free(inodebuf);
+ inodebuf = NULL;
+}
+
+/*
+ * Read specified disk blocks.
+ */
+void
+bread(ufs2_daddr_t bno, char *buf, long cnt)
+{
+
+ if (lseek(fi, (off_t)bno * dev_bsize, SEEK_SET) < 0 ||
+ read(fi, buf, cnt) != cnt)
+ errx(1, "bread failed on block %ld", (long)bno);
+}
+
+/*
+ * Display updated block and i-node counts.
+ */
+void
+printchanges(const char *fsname, int type, struct dqblk *dp,
+ struct fileusage *fup, u_long id)
+{
+ if (!vflag)
+ return;
+ if (aflag)
+ (void)printf("%s: ", fsname);
+ if (fup->fu_name[0] == '\0')
+ (void)printf("%-8lu fixed ", id);
+ else
+ (void)printf("%-8s fixed ", fup->fu_name);
+ switch (type) {
+
+ case GRPQUOTA:
+ (void)printf("(group):");
+ break;
+
+ case USRQUOTA:
+ (void)printf("(user): ");
+ break;
+
+ default:
+ (void)printf("(unknown quota type %d)", type);
+ break;
+ }
+ if (dp->dqb_curinodes != fup->fu_curinodes)
+ (void)printf("\tinodes %lu -> %lu", (u_long)dp->dqb_curinodes,
+ (u_long)fup->fu_curinodes);
+ if (dp->dqb_curblocks != fup->fu_curblocks)
+ (void)printf("\tblocks %lu -> %lu",
+ (u_long)dp->dqb_curblocks,
+ (u_long)fup->fu_curblocks);
+ (void)printf("\n");
+}
diff --git a/sbin/quotacheck/quotacheck.h b/sbin/quotacheck/quotacheck.h
new file mode 100644
index 0000000..dcff75b
--- /dev/null
+++ b/sbin/quotacheck/quotacheck.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 1980, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Robert Elz at The University of Melbourne.
+ *
+ * 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.
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+extern char *blockcheck(char *);
+extern int checkfstab(int, int);
+extern int chkquota(char *, struct quotafile *, struct quotafile *);
diff --git a/sbin/rcorder/Makefile b/sbin/rcorder/Makefile
new file mode 100644
index 0000000..2f1bbac
--- /dev/null
+++ b/sbin/rcorder/Makefile
@@ -0,0 +1,14 @@
+# $NetBSD: Makefile,v 1.1 1999/11/23 05:28:20 mrg Exp $
+# $FreeBSD$
+
+PROG= rcorder
+SRCS= ealloc.c hash.c rcorder.c
+MAN= rcorder.8
+
+LIBADD= util
+
+CFLAGS+= -DORDER
+
+#CFLAGS+= -DDEBUG
+
+.include <bsd.prog.mk>
diff --git a/sbin/rcorder/ealloc.c b/sbin/rcorder/ealloc.c
new file mode 100644
index 0000000..9971193
--- /dev/null
+++ b/sbin/rcorder/ealloc.c
@@ -0,0 +1,118 @@
+/* $FreeBSD$ */
+/* $NetBSD: ealloc.c,v 1.1.1.1 1999/11/19 04:30:56 mrg Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * 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.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: ealloc.c,v 1.1.1.1 1999/11/19 04:30:56 mrg Exp $");
+#endif /* not lint */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <err.h>
+
+#include "ealloc.h"
+
+static void enomem(void);
+
+/*
+ * enomem --
+ * die when out of memory.
+ */
+static void
+enomem(void)
+{
+ errx(2, "Cannot allocate memory.");
+}
+
+/*
+ * emalloc --
+ * malloc, but die on error.
+ */
+void *
+emalloc(size_t len)
+{
+ void *p;
+
+ if ((p = malloc(len)) == NULL)
+ enomem();
+ return(p);
+}
+
+/*
+ * estrdup --
+ * strdup, but die on error.
+ */
+char *
+estrdup(const char *str)
+{
+ char *p;
+
+ if ((p = strdup(str)) == NULL)
+ enomem();
+ return(p);
+}
+
+/*
+ * erealloc --
+ * realloc, but die on error.
+ */
+void *
+erealloc(void *ptr, size_t size)
+{
+ if ((ptr = realloc(ptr, size)) == NULL)
+ enomem();
+ return(ptr);
+}
+
+/*
+ * ecalloc --
+ * calloc, but die on error.
+ */
+void *
+ecalloc(size_t nmemb, size_t size)
+{
+ void *ptr;
+
+ if ((ptr = calloc(nmemb, size)) == NULL)
+ enomem();
+ return(ptr);
+}
diff --git a/sbin/rcorder/ealloc.h b/sbin/rcorder/ealloc.h
new file mode 100644
index 0000000..093ebc3
--- /dev/null
+++ b/sbin/rcorder/ealloc.h
@@ -0,0 +1,7 @@
+/* $FreeBSD$ */
+/* $NetBSD: ealloc.h,v 1.1.1.1 1999/11/19 04:30:56 mrg Exp $ */
+
+void *emalloc(size_t len);
+char *estrdup(const char *str);
+void *erealloc(void *ptr, size_t size);
+void *ecalloc(size_t nmemb, size_t size);
diff --git a/sbin/rcorder/hash.c b/sbin/rcorder/hash.c
new file mode 100644
index 0000000..34b95ce
--- /dev/null
+++ b/sbin/rcorder/hash.c
@@ -0,0 +1,435 @@
+/* $FreeBSD$ */
+/* $NetBSD: hash.c,v 1.1.1.1 1999/11/19 04:30:56 mrg Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
+ * Copyright (c) 1988, 1989 by Adam de Boor
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * 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.
+ */
+
+#ifdef MAKE_BOOTSTRAP
+static char rcsid[] = "$NetBSD: hash.c,v 1.1.1.1 1999/11/19 04:30:56 mrg Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)hash.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: hash.c,v 1.1.1.1 1999/11/19 04:30:56 mrg Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+#include <sys/types.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/* hash.c --
+ *
+ * This module contains routines to manipulate a hash table.
+ * See hash.h for a definition of the structure of the hash
+ * table. Hash tables grow automatically as the amount of
+ * information increases.
+ */
+#include "sprite.h"
+#ifndef ORDER
+#include "make.h"
+#endif /* ORDER */
+#include "hash.h"
+#include "ealloc.h"
+
+/*
+ * Forward references to local procedures that are used before they're
+ * defined:
+ */
+
+static void RebuildTable(Hash_Table *);
+
+/*
+ * The following defines the ratio of # entries to # buckets
+ * at which we rebuild the table to make it larger.
+ */
+
+#define rebuildLimit 8
+
+/*
+ *---------------------------------------------------------
+ *
+ * Hash_InitTable --
+ *
+ * This routine just sets up the hash table.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Memory is allocated for the initial bucket area.
+ *
+ *---------------------------------------------------------
+ */
+
+void
+Hash_InitTable(
+ register Hash_Table *t, /* Structure to use to hold table. */
+ int numBuckets) /* How many buckets to create for starters.
+ * This number is rounded up to a power of
+ * two. If <= 0, a reasonable default is
+ * chosen. The table will grow in size later
+ * as needed. */
+{
+ register int i;
+ register struct Hash_Entry **hp;
+
+ /*
+ * Round up the size to a power of two.
+ */
+ if (numBuckets <= 0)
+ i = 16;
+ else {
+ for (i = 2; i < numBuckets; i <<= 1)
+ continue;
+ }
+ t->numEntries = 0;
+ t->size = i;
+ t->mask = i - 1;
+ t->bucketPtr = hp = (struct Hash_Entry **)emalloc(sizeof(*hp) * i);
+ while (--i >= 0)
+ *hp++ = NULL;
+}
+
+/*
+ *---------------------------------------------------------
+ *
+ * Hash_DeleteTable --
+ *
+ * This routine removes everything from a hash table
+ * and frees up the memory space it occupied (except for
+ * the space in the Hash_Table structure).
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Lots of memory is freed up.
+ *
+ *---------------------------------------------------------
+ */
+
+void
+Hash_DeleteTable(Hash_Table *t)
+{
+ register struct Hash_Entry **hp, *h, *nexth = NULL;
+ register int i;
+
+ for (hp = t->bucketPtr, i = t->size; --i >= 0;) {
+ for (h = *hp++; h != NULL; h = nexth) {
+ nexth = h->next;
+ free((char *)h);
+ }
+ }
+ free((char *)t->bucketPtr);
+
+ /*
+ * Set up the hash table to cause memory faults on any future access
+ * attempts until re-initialization.
+ */
+ t->bucketPtr = NULL;
+}
+
+/*
+ *---------------------------------------------------------
+ *
+ * Hash_FindEntry --
+ *
+ * Searches a hash table for an entry corresponding to key.
+ *
+ * Results:
+ * The return value is a pointer to the entry for key,
+ * if key was present in the table. If key was not
+ * present, NULL is returned.
+ *
+ * Side Effects:
+ * None.
+ *
+ *---------------------------------------------------------
+ */
+
+Hash_Entry *
+Hash_FindEntry(
+ Hash_Table *t, /* Hash table to search. */
+ char *key) /* A hash key. */
+{
+ register Hash_Entry *e;
+ register unsigned h;
+ register char *p;
+
+ for (h = 0, p = key; *p;)
+ h = (h << 5) - h + *p++;
+ p = key;
+ for (e = t->bucketPtr[h & t->mask]; e != NULL; e = e->next)
+ if (e->namehash == h && strcmp(e->name, p) == 0)
+ return (e);
+ return (NULL);
+}
+
+/*
+ *---------------------------------------------------------
+ *
+ * Hash_CreateEntry --
+ *
+ * Searches a hash table for an entry corresponding to
+ * key. If no entry is found, then one is created.
+ *
+ * Results:
+ * The return value is a pointer to the entry. If *newPtr
+ * isn't NULL, then *newPtr is filled in with TRUE if a
+ * new entry was created, and FALSE if an entry already existed
+ * with the given key.
+ *
+ * Side Effects:
+ * Memory may be allocated, and the hash buckets may be modified.
+ *---------------------------------------------------------
+ */
+
+Hash_Entry *
+Hash_CreateEntry(
+ register Hash_Table *t, /* Hash table to search. */
+ char *key, /* A hash key. */
+ Boolean *newPtr) /* Filled in with TRUE if new entry created,
+ * FALSE otherwise. */
+{
+ register Hash_Entry *e;
+ register unsigned h;
+ register char *p;
+ int keylen;
+ struct Hash_Entry **hp;
+
+ /*
+ * Hash the key. As a side effect, save the length (strlen) of the
+ * key in case we need to create the entry.
+ */
+ for (h = 0, p = key; *p;)
+ h = (h << 5) - h + *p++;
+ keylen = p - key;
+ p = key;
+ for (e = t->bucketPtr[h & t->mask]; e != NULL; e = e->next) {
+ if (e->namehash == h && strcmp(e->name, p) == 0) {
+ if (newPtr != NULL)
+ *newPtr = FALSE;
+ return (e);
+ }
+ }
+
+ /*
+ * The desired entry isn't there. Before allocating a new entry,
+ * expand the table if necessary (and this changes the resulting
+ * bucket chain).
+ */
+ if (t->numEntries >= rebuildLimit * t->size)
+ RebuildTable(t);
+ e = (Hash_Entry *) emalloc(sizeof(*e) + keylen);
+ hp = &t->bucketPtr[h & t->mask];
+ e->next = *hp;
+ *hp = e;
+ e->clientData = NULL;
+ e->namehash = h;
+ (void) strcpy(e->name, p);
+ t->numEntries++;
+
+ if (newPtr != NULL)
+ *newPtr = TRUE;
+ return (e);
+}
+
+/*
+ *---------------------------------------------------------
+ *
+ * Hash_DeleteEntry --
+ *
+ * Delete the given hash table entry and free memory associated with
+ * it.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Hash chain that entry lives in is modified and memory is freed.
+ *
+ *---------------------------------------------------------
+ */
+
+void
+Hash_DeleteEntry(Hash_Table *t, Hash_Entry *e)
+{
+ register Hash_Entry **hp, *p;
+
+ if (e == NULL)
+ return;
+ for (hp = &t->bucketPtr[e->namehash & t->mask];
+ (p = *hp) != NULL; hp = &p->next) {
+ if (p == e) {
+ *hp = p->next;
+ free((char *)p);
+ t->numEntries--;
+ return;
+ }
+ }
+ (void)write(2, "bad call to Hash_DeleteEntry\n", 29);
+ abort();
+}
+
+/*
+ *---------------------------------------------------------
+ *
+ * Hash_EnumFirst --
+ * This procedure sets things up for a complete search
+ * of all entries recorded in the hash table.
+ *
+ * Results:
+ * The return value is the address of the first entry in
+ * the hash table, or NULL if the table is empty.
+ *
+ * Side Effects:
+ * The information in searchPtr is initialized so that successive
+ * calls to Hash_Next will return successive HashEntry's
+ * from the table.
+ *
+ *---------------------------------------------------------
+ */
+
+Hash_Entry *
+Hash_EnumFirst(
+ Hash_Table *t, /* Table to be searched. */
+ register Hash_Search *searchPtr)/* Area in which to keep state
+ * about search.*/
+{
+ searchPtr->tablePtr = t;
+ searchPtr->nextIndex = 0;
+ searchPtr->hashEntryPtr = NULL;
+ return Hash_EnumNext(searchPtr);
+}
+
+/*
+ *---------------------------------------------------------
+ *
+ * Hash_EnumNext --
+ * This procedure returns successive entries in the hash table.
+ *
+ * Results:
+ * The return value is a pointer to the next HashEntry
+ * in the table, or NULL when the end of the table is
+ * reached.
+ *
+ * Side Effects:
+ * The information in searchPtr is modified to advance to the
+ * next entry.
+ *
+ *---------------------------------------------------------
+ */
+
+Hash_Entry *
+Hash_EnumNext(
+ register Hash_Search *searchPtr) /* Area used to keep state about
+ search. */
+{
+ register Hash_Entry *e;
+ Hash_Table *t = searchPtr->tablePtr;
+
+ /*
+ * The hashEntryPtr field points to the most recently returned
+ * entry, or is nil if we are starting up. If not nil, we have
+ * to start at the next one in the chain.
+ */
+ e = searchPtr->hashEntryPtr;
+ if (e != NULL)
+ e = e->next;
+ /*
+ * If the chain ran out, or if we are starting up, we need to
+ * find the next nonempty chain.
+ */
+ while (e == NULL) {
+ if (searchPtr->nextIndex >= t->size)
+ return (NULL);
+ e = t->bucketPtr[searchPtr->nextIndex++];
+ }
+ searchPtr->hashEntryPtr = e;
+ return (e);
+}
+
+/*
+ *---------------------------------------------------------
+ *
+ * RebuildTable --
+ * This local routine makes a new hash table that
+ * is larger than the old one.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * The entire hash table is moved, so any bucket numbers
+ * from the old table are invalid.
+ *
+ *---------------------------------------------------------
+ */
+
+static void
+RebuildTable(register Hash_Table *t)
+{
+ register Hash_Entry *e, *next = NULL, **hp, **xp;
+ register int i, mask;
+ register Hash_Entry **oldhp;
+ int oldsize;
+
+ oldhp = t->bucketPtr;
+ oldsize = i = t->size;
+ i <<= 1;
+ t->size = i;
+ t->mask = mask = i - 1;
+ t->bucketPtr = hp = (struct Hash_Entry **) emalloc(sizeof(*hp) * i);
+ while (--i >= 0)
+ *hp++ = NULL;
+ for (hp = oldhp, i = oldsize; --i >= 0;) {
+ for (e = *hp++; e != NULL; e = next) {
+ next = e->next;
+ xp = &t->bucketPtr[e->namehash & mask];
+ e->next = *xp;
+ *xp = e;
+ }
+ }
+ free((char *)oldhp);
+}
diff --git a/sbin/rcorder/hash.h b/sbin/rcorder/hash.h
new file mode 100644
index 0000000..fd2f978
--- /dev/null
+++ b/sbin/rcorder/hash.h
@@ -0,0 +1,131 @@
+/* $FreeBSD$ */
+/* $NetBSD: hash.h,v 1.1.1.1 1999/11/19 04:30:56 mrg Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
+ * Copyright (c) 1988, 1989 by Adam de Boor
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * 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.
+ *
+ * from: @(#)hash.h 8.1 (Berkeley) 6/6/93
+ */
+
+/* hash.h --
+ *
+ * This file contains definitions used by the hash module,
+ * which maintains hash tables.
+ */
+
+#ifndef _HASH
+#define _HASH
+
+/*
+ * The following defines one entry in the hash table.
+ */
+
+typedef struct Hash_Entry {
+ struct Hash_Entry *next; /* Used to link together all the
+ * entries associated with the same
+ * bucket. */
+ ClientData clientData; /* Arbitrary piece of data associated
+ * with key. */
+ unsigned namehash; /* hash value of key */
+ char name[1]; /* key string */
+} Hash_Entry;
+
+typedef struct Hash_Table {
+ struct Hash_Entry **bucketPtr;
+ /* Pointers to Hash_Entry, one
+ * for each bucket in the table. */
+ int size; /* Actual size of array. */
+ int numEntries; /* Number of entries in the table. */
+ int mask; /* Used to select bits for hashing. */
+} Hash_Table;
+
+/*
+ * The following structure is used by the searching routines
+ * to record where we are in the search.
+ */
+
+typedef struct Hash_Search {
+ Hash_Table *tablePtr; /* Table being searched. */
+ int nextIndex; /* Next bucket to check (after
+ * current). */
+ Hash_Entry *hashEntryPtr; /* Next entry to check in current
+ * bucket. */
+} Hash_Search;
+
+/*
+ * Macros.
+ */
+
+/*
+ * ClientData Hash_GetValue(h)
+ * Hash_Entry *h;
+ */
+
+#define Hash_GetValue(h) ((h)->clientData)
+
+/*
+ * Hash_SetValue(h, val);
+ * Hash_Entry *h;
+ * char *val;
+ */
+
+#define Hash_SetValue(h, val) ((h)->clientData = (ClientData) (val))
+
+#ifdef ORDER
+/*
+ * Hash_GetKey(h);
+ * Hash_Entry *h;
+ */
+
+#define Hash_GetKey(h) ((h)->name)
+#endif /* ORDER */
+
+/*
+ * Hash_Size(n) returns the number of words in an object of n bytes
+ */
+
+#define Hash_Size(n) (((n) + sizeof (int) - 1) / sizeof (int))
+
+void Hash_InitTable(Hash_Table *, int);
+void Hash_DeleteTable(Hash_Table *);
+Hash_Entry *Hash_FindEntry(Hash_Table *, char *);
+Hash_Entry *Hash_CreateEntry(Hash_Table *, char *, Boolean *);
+void Hash_DeleteEntry(Hash_Table *, Hash_Entry *);
+Hash_Entry *Hash_EnumFirst(Hash_Table *, Hash_Search *);
+Hash_Entry *Hash_EnumNext(Hash_Search *);
+
+#endif /* _HASH */
diff --git a/sbin/rcorder/rcorder.8 b/sbin/rcorder/rcorder.8
new file mode 100644
index 0000000..995ef68
--- /dev/null
+++ b/sbin/rcorder/rcorder.8
@@ -0,0 +1,189 @@
+.\" $NetBSD: rcorder.8,v 1.3 2000/07/17 14:16:22 mrg Exp $
+.\"
+.\" Copyright (c) 1998
+.\" Perry E. Metzger. 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 for the NetBSD Project
+.\" by Perry E. Metzger.
+.\" 4. The name of the author may not be used to endorse or promote products
+.\" derived from this software without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+.\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+.\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd August 5, 2011
+.Dt RCORDER 8
+.Os
+.Sh NAME
+.Nm rcorder
+.Nd print a dependency ordering of interdependent files
+.Sh SYNOPSIS
+.Nm
+.Op Fl k Ar keep
+.Op Fl s Ar skip
+.Ar
+.Sh DESCRIPTION
+The
+.Nm
+utility is designed to print out a dependency ordering of a set of
+interdependent files.
+Typically it is used to find an execution
+sequence for a set of shell scripts in which certain files must be
+executed before others.
+.Pp
+Each file passed to
+.Nm
+must be annotated with special lines (which look like comments to the
+shell) which indicate the dependencies the files have upon certain
+points in the sequence, known as
+.Dq conditions ,
+and which indicate, for each file, which
+.Dq conditions
+may be expected to be filled by that file.
+.Pp
+Within each file, a block containing a series of
+.Dq Li REQUIRE ,
+.Dq Li PROVIDE ,
+.Dq Li BEFORE
+and
+.Dq Li KEYWORD
+lines must appear.
+The format of the lines is rigid.
+Each line must begin with a single
+.Ql # ,
+followed by a single space, followed by
+.Dq Li PROVIDE: ,
+.Dq Li REQUIRE: ,
+.Dq Li BEFORE: ,
+or
+.Dq Li KEYWORD: .
+No deviation is permitted.
+Each dependency line is then followed by a series of conditions,
+separated by whitespace.
+Multiple
+.Dq Li PROVIDE ,
+.Dq Li REQUIRE ,
+.Dq Li BEFORE
+and
+.Dq Li KEYWORD
+lines may appear, but all such lines must appear in a sequence without
+any intervening lines, as once a line that does not follow the format
+is reached, parsing stops.
+.\" Note that for historical reasons REQUIRES, PROVIDES, and KEYWORDS
+.\" are also accepted in addition to the above, but not documented so
+.\" that they can be deprecated at some point in the future.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl k
+Add the specified keyword to the
+.Dq "keep list" .
+If any
+.Fl k
+option is given, only those files containing the matching keyword are listed.
+.It Fl s
+Add the specified keyword to the
+.Dq "skip list" .
+If any
+.Fl s
+option is given, files containing the matching keyword are not listed.
+.El
+.Pp
+An example block follows:
+.Bd -literal -offset indent
+# REQUIRE: networking syslog
+# REQUIRE: usr
+# PROVIDE: dns nscd
+.Ed
+.Pp
+This block states that the file in which it appears depends upon the
+.Dq Li networking ,
+.Dq Li syslog ,
+and
+.Dq Li usr
+conditions, and provides the
+.Dq Li dns
+and
+.Dq Li nscd
+conditions.
+.Pp
+A file may contain zero
+.Dq Li PROVIDE
+lines, in which case it provides no conditions, and may contain zero
+.Dq Li REQUIRE
+lines, in which case it has no dependencies.
+There must be at least one file with no dependencies in the set of
+arguments passed to
+.Nm
+in order for it to find a starting place in the dependency ordering.
+.Sh DIAGNOSTICS
+The
+.Nm
+utility may print one of the following error messages and exit with a non-zero
+status if it encounters an error while processing the file list.
+.Bl -diag
+.It "Requirement %s has no providers, aborting."
+No file has a
+.Dq Li PROVIDE
+line corresponding to a condition present in a
+.Dq Li REQUIRE
+line in another file.
+.It "Circular dependency on provision %s, aborting."
+A set of files has a circular dependency which was detected while
+processing the stated condition.
+.It "Circular dependency on file %s, aborting."
+A set of files has a circular dependency which was detected while
+processing the stated file.
+.El
+.Sh SEE ALSO
+.Xr rc 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Nx 1.5 .
+.Sh AUTHORS
+.An -nosplit
+Written by
+.An Perry E. Metzger Aq Mt perry@piermont.com
+and
+.An Matthew R. Green Aq Mt mrg@eterna.com.au .
+.Sh BUGS
+The
+.Dq Li REQUIRE
+keyword is misleading:
+It doesn't describe which daemons have to be running before a script
+will be started.
+It describes which scripts must be placed before it in
+the dependency ordering.
+For example,
+if your script has a
+.Dq Li REQUIRE
+on
+.Dq Li named ,
+it means the script must be placed after the
+.Dq Li named
+script in the dependency ordering,
+not necessarily that it requires
+.Xr named 8
+to be started or enabled.
diff --git a/sbin/rcorder/rcorder.c b/sbin/rcorder/rcorder.c
new file mode 100644
index 0000000..8c46b4f
--- /dev/null
+++ b/sbin/rcorder/rcorder.c
@@ -0,0 +1,806 @@
+# if 0
+/* $NetBSD: rcorder.c,v 1.7 2000/08/04 07:33:55 enami Exp $ */
+#endif
+
+/*
+ * Copyright (c) 1998, 1999 Matthew R. Green
+ * All rights reserved.
+ * Copyright (c) 1998
+ * Perry E. Metzger. 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 for the NetBSD Project
+ * by Perry E. Metzger.
+ * 4. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/stat.h>
+
+#include <err.h>
+#include <stdio.h>
+#include <libutil.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "ealloc.h"
+#include "sprite.h"
+#include "hash.h"
+
+#ifdef DEBUG
+static int debug = 0;
+# define DPRINTF(args) if (debug) { fflush(stdout); fprintf args; }
+#else
+# define DPRINTF(args)
+#endif
+
+#define REQUIRE_STR "# REQUIRE:"
+#define REQUIRE_LEN (sizeof(REQUIRE_STR) - 1)
+#define REQUIRES_STR "# REQUIRES:"
+#define REQUIRES_LEN (sizeof(REQUIRES_STR) - 1)
+#define PROVIDE_STR "# PROVIDE:"
+#define PROVIDE_LEN (sizeof(PROVIDE_STR) - 1)
+#define PROVIDES_STR "# PROVIDES:"
+#define PROVIDES_LEN (sizeof(PROVIDES_STR) - 1)
+#define BEFORE_STR "# BEFORE:"
+#define BEFORE_LEN (sizeof(BEFORE_STR) - 1)
+#define KEYWORD_STR "# KEYWORD:"
+#define KEYWORD_LEN (sizeof(KEYWORD_STR) - 1)
+#define KEYWORDS_STR "# KEYWORDS:"
+#define KEYWORDS_LEN (sizeof(KEYWORDS_STR) - 1)
+
+static int exit_code;
+static int file_count;
+static char **file_list;
+
+typedef int bool;
+#define TRUE 1
+#define FALSE 0
+typedef bool flag;
+#define SET TRUE
+#define RESET FALSE
+
+static Hash_Table provide_hash_s, *provide_hash;
+
+typedef struct provnode provnode;
+typedef struct filenode filenode;
+typedef struct f_provnode f_provnode;
+typedef struct f_reqnode f_reqnode;
+typedef struct strnodelist strnodelist;
+
+struct provnode {
+ flag head;
+ flag in_progress;
+ filenode *fnode;
+ provnode *next, *last;
+};
+
+struct f_provnode {
+ provnode *pnode;
+ f_provnode *next;
+};
+
+struct f_reqnode {
+ Hash_Entry *entry;
+ f_reqnode *next;
+};
+
+struct strnodelist {
+ filenode *node;
+ strnodelist *next;
+ char s[1];
+};
+
+struct filenode {
+ char *filename;
+ flag in_progress;
+ filenode *next, *last;
+ f_reqnode *req_list;
+ f_provnode *prov_list;
+ strnodelist *keyword_list;
+};
+
+static filenode fn_head_s, *fn_head;
+
+static strnodelist *bl_list;
+static strnodelist *keep_list;
+static strnodelist *skip_list;
+
+static void do_file(filenode *fnode);
+static void strnode_add(strnodelist **, char *, filenode *);
+static int skip_ok(filenode *fnode);
+static int keep_ok(filenode *fnode);
+static void satisfy_req(f_reqnode *rnode, char *filename);
+static void crunch_file(char *);
+static void parse_require(filenode *, char *);
+static void parse_provide(filenode *, char *);
+static void parse_before(filenode *, char *);
+static void parse_keywords(filenode *, char *);
+static filenode *filenode_new(char *);
+static void add_require(filenode *, char *);
+static void add_provide(filenode *, char *);
+static void add_before(filenode *, char *);
+static void add_keyword(filenode *, char *);
+static void insert_before(void);
+static Hash_Entry *make_fake_provision(filenode *);
+static void crunch_all_files(void);
+static void initialize(void);
+static void generate_ordering(void);
+
+int
+main(int argc, char *argv[])
+{
+ int ch;
+
+ while ((ch = getopt(argc, argv, "dk:s:")) != -1)
+ switch (ch) {
+ case 'd':
+#ifdef DEBUG
+ debug = 1;
+#else
+ warnx("debugging not compiled in, -d ignored");
+#endif
+ break;
+ case 'k':
+ strnode_add(&keep_list, optarg, 0);
+ break;
+ case 's':
+ strnode_add(&skip_list, optarg, 0);
+ break;
+ default:
+ /* XXX should crunch it? */
+ break;
+ }
+ argc -= optind;
+ argv += optind;
+
+ file_count = argc;
+ file_list = argv;
+
+ DPRINTF((stderr, "parse_args\n"));
+ initialize();
+ DPRINTF((stderr, "initialize\n"));
+ crunch_all_files();
+ DPRINTF((stderr, "crunch_all_files\n"));
+ generate_ordering();
+ DPRINTF((stderr, "generate_ordering\n"));
+
+ exit(exit_code);
+}
+
+/*
+ * initialise various variables.
+ */
+static void
+initialize(void)
+{
+
+ fn_head = &fn_head_s;
+
+ provide_hash = &provide_hash_s;
+ Hash_InitTable(provide_hash, file_count);
+}
+
+/* generic function to insert a new strnodelist element */
+static void
+strnode_add(strnodelist **listp, char *s, filenode *fnode)
+{
+ strnodelist *ent;
+
+ ent = emalloc(sizeof *ent + strlen(s));
+ ent->node = fnode;
+ strcpy(ent->s, s);
+ ent->next = *listp;
+ *listp = ent;
+}
+
+/*
+ * below are the functions that deal with creating the lists
+ * from the filename's given and the dependancies and provisions
+ * in each of these files. no ordering or checking is done here.
+ */
+
+/*
+ * we have a new filename, create a new filenode structure.
+ * fill in the bits, and put it in the filenode linked list
+ */
+static filenode *
+filenode_new(char *filename)
+{
+ filenode *temp;
+
+ temp = emalloc(sizeof(*temp));
+ memset(temp, 0, sizeof(*temp));
+ temp->filename = estrdup(filename);
+ temp->req_list = NULL;
+ temp->prov_list = NULL;
+ temp->keyword_list = NULL;
+ temp->in_progress = RESET;
+ /*
+ * link the filenode into the list of filenodes.
+ * note that the double linking means we can delete a
+ * filenode without searching for where it belongs.
+ */
+ temp->next = fn_head->next;
+ if (temp->next != NULL)
+ temp->next->last = temp;
+ temp->last = fn_head;
+ fn_head->next = temp;
+ return (temp);
+}
+
+/*
+ * add a requirement to a filenode.
+ */
+static void
+add_require(filenode *fnode, char *s)
+{
+ Hash_Entry *entry;
+ f_reqnode *rnode;
+ int new;
+
+ entry = Hash_CreateEntry(provide_hash, s, &new);
+ if (new)
+ Hash_SetValue(entry, NULL);
+ rnode = emalloc(sizeof(*rnode));
+ rnode->entry = entry;
+ rnode->next = fnode->req_list;
+ fnode->req_list = rnode;
+}
+
+/*
+ * add a provision to a filenode. if this provision doesn't
+ * have a head node, create one here.
+ */
+static void
+add_provide(filenode *fnode, char *s)
+{
+ Hash_Entry *entry;
+ f_provnode *f_pnode;
+ provnode *pnode, *head;
+ int new;
+
+ entry = Hash_CreateEntry(provide_hash, s, &new);
+ head = Hash_GetValue(entry);
+
+ /* create a head node if necessary. */
+ if (head == NULL) {
+ head = emalloc(sizeof(*head));
+ head->head = SET;
+ head->in_progress = RESET;
+ head->fnode = NULL;
+ head->last = head->next = NULL;
+ Hash_SetValue(entry, head);
+ }
+#if 0
+ /*
+ * Don't warn about this. We want to be able to support
+ * scripts that do two complex things:
+ *
+ * - Two independent scripts which both provide the
+ * same thing. Both scripts must be executed in
+ * any order to meet the barrier. An example:
+ *
+ * Script 1:
+ *
+ * PROVIDE: mail
+ * REQUIRE: LOGIN
+ *
+ * Script 2:
+ *
+ * PROVIDE: mail
+ * REQUIRE: LOGIN
+ *
+ * - Two interdependent scripts which both provide the
+ * same thing. Both scripts must be executed in
+ * graph order to meet the barrier. An example:
+ *
+ * Script 1:
+ *
+ * PROVIDE: nameservice dnscache
+ * REQUIRE: SERVERS
+ *
+ * Script 2:
+ *
+ * PROVIDE: nameservice nscd
+ * REQUIRE: dnscache
+ */
+ else if (new == 0) {
+ warnx("file `%s' provides `%s'.", fnode->filename, s);
+ warnx("\tpreviously seen in `%s'.",
+ head->next->fnode->filename);
+ }
+#endif
+
+ pnode = emalloc(sizeof(*pnode));
+ pnode->head = RESET;
+ pnode->in_progress = RESET;
+ pnode->fnode = fnode;
+ pnode->next = head->next;
+ pnode->last = head;
+ head->next = pnode;
+ if (pnode->next != NULL)
+ pnode->next->last = pnode;
+
+ f_pnode = emalloc(sizeof(*f_pnode));
+ f_pnode->pnode = pnode;
+ f_pnode->next = fnode->prov_list;
+ fnode->prov_list = f_pnode;
+}
+
+/*
+ * put the BEFORE: lines to a list and handle them later.
+ */
+static void
+add_before(filenode *fnode, char *s)
+{
+ strnodelist *bf_ent;
+
+ bf_ent = emalloc(sizeof *bf_ent + strlen(s));
+ bf_ent->node = fnode;
+ strcpy(bf_ent->s, s);
+ bf_ent->next = bl_list;
+ bl_list = bf_ent;
+}
+
+/*
+ * add a key to a filenode.
+ */
+static void
+add_keyword(filenode *fnode, char *s)
+{
+
+ strnode_add(&fnode->keyword_list, s, fnode);
+}
+
+/*
+ * loop over the rest of a REQUIRE line, giving each word to
+ * add_require() to do the real work.
+ */
+static void
+parse_require(filenode *node, char *buffer)
+{
+ char *s;
+
+ while ((s = strsep(&buffer, " \t\n")) != NULL)
+ if (*s != '\0')
+ add_require(node, s);
+}
+
+/*
+ * loop over the rest of a PROVIDE line, giving each word to
+ * add_provide() to do the real work.
+ */
+static void
+parse_provide(filenode *node, char *buffer)
+{
+ char *s;
+
+ while ((s = strsep(&buffer, " \t\n")) != NULL)
+ if (*s != '\0')
+ add_provide(node, s);
+}
+
+/*
+ * loop over the rest of a BEFORE line, giving each word to
+ * add_before() to do the real work.
+ */
+static void
+parse_before(filenode *node, char *buffer)
+{
+ char *s;
+
+ while ((s = strsep(&buffer, " \t\n")) != NULL)
+ if (*s != '\0')
+ add_before(node, s);
+}
+
+/*
+ * loop over the rest of a KEYWORD line, giving each word to
+ * add_keyword() to do the real work.
+ */
+static void
+parse_keywords(filenode *node, char *buffer)
+{
+ char *s;
+
+ while ((s = strsep(&buffer, " \t\n")) != NULL)
+ if (*s != '\0')
+ add_keyword(node, s);
+}
+
+/*
+ * given a file name, create a filenode for it, read in lines looking
+ * for provision and requirement lines, building the graphs as needed.
+ */
+static void
+crunch_file(char *filename)
+{
+ FILE *fp;
+ char *buf;
+ int require_flag, provide_flag, before_flag, keywords_flag;
+ enum { BEFORE_PARSING, PARSING, PARSING_DONE } state;
+ filenode *node;
+ char delims[3] = { '\\', '\\', '\0' };
+ struct stat st;
+
+ if ((fp = fopen(filename, "r")) == NULL) {
+ warn("could not open %s", filename);
+ return;
+ }
+
+ if (fstat(fileno(fp), &st) == -1) {
+ warn("could not stat %s", filename);
+ fclose(fp);
+ return;
+ }
+
+ if (!S_ISREG(st.st_mode)) {
+#if 0
+ warnx("%s is not a file", filename);
+#endif
+ fclose(fp);
+ return;
+ }
+
+ node = filenode_new(filename);
+
+ /*
+ * we don't care about length, line number, don't want # for comments,
+ * and have no flags.
+ */
+ for (state = BEFORE_PARSING; state != PARSING_DONE &&
+ (buf = fparseln(fp, NULL, NULL, delims, 0)) != NULL; free(buf)) {
+ require_flag = provide_flag = before_flag = keywords_flag = 0;
+ if (strncmp(REQUIRE_STR, buf, REQUIRE_LEN) == 0)
+ require_flag = REQUIRE_LEN;
+ else if (strncmp(REQUIRES_STR, buf, REQUIRES_LEN) == 0)
+ require_flag = REQUIRES_LEN;
+ else if (strncmp(PROVIDE_STR, buf, PROVIDE_LEN) == 0)
+ provide_flag = PROVIDE_LEN;
+ else if (strncmp(PROVIDES_STR, buf, PROVIDES_LEN) == 0)
+ provide_flag = PROVIDES_LEN;
+ else if (strncmp(BEFORE_STR, buf, BEFORE_LEN) == 0)
+ before_flag = BEFORE_LEN;
+ else if (strncmp(KEYWORD_STR, buf, KEYWORD_LEN) == 0)
+ keywords_flag = KEYWORD_LEN;
+ else if (strncmp(KEYWORDS_STR, buf, KEYWORDS_LEN) == 0)
+ keywords_flag = KEYWORDS_LEN;
+ else {
+ if (state == PARSING)
+ state = PARSING_DONE;
+ continue;
+ }
+
+ state = PARSING;
+ if (require_flag)
+ parse_require(node, buf + require_flag);
+ else if (provide_flag)
+ parse_provide(node, buf + provide_flag);
+ else if (before_flag)
+ parse_before(node, buf + before_flag);
+ else if (keywords_flag)
+ parse_keywords(node, buf + keywords_flag);
+ }
+ fclose(fp);
+}
+
+static Hash_Entry *
+make_fake_provision(filenode *node)
+{
+ Hash_Entry *entry;
+ f_provnode *f_pnode;
+ provnode *head, *pnode;
+ static int i = 0;
+ int new;
+ char buffer[30];
+
+ do {
+ snprintf(buffer, sizeof buffer, "fake_prov_%08d", i++);
+ entry = Hash_CreateEntry(provide_hash, buffer, &new);
+ } while (new == 0);
+ head = emalloc(sizeof(*head));
+ head->head = SET;
+ head->in_progress = RESET;
+ head->fnode = NULL;
+ head->last = head->next = NULL;
+ Hash_SetValue(entry, head);
+
+ pnode = emalloc(sizeof(*pnode));
+ pnode->head = RESET;
+ pnode->in_progress = RESET;
+ pnode->fnode = node;
+ pnode->next = head->next;
+ pnode->last = head;
+ head->next = pnode;
+ if (pnode->next != NULL)
+ pnode->next->last = pnode;
+
+ f_pnode = emalloc(sizeof(*f_pnode));
+ f_pnode->pnode = pnode;
+ f_pnode->next = node->prov_list;
+ node->prov_list = f_pnode;
+
+ return (entry);
+}
+
+/*
+ * go through the BEFORE list, inserting requirements into the graph(s)
+ * as required. in the before list, for each entry B, we have a file F
+ * and a string S. we create a "fake" provision (P) that F provides.
+ * for each entry in the provision list for S, add a requirement to
+ * that provisions filenode for P.
+ */
+static void
+insert_before(void)
+{
+ Hash_Entry *entry, *fake_prov_entry;
+ provnode *pnode;
+ f_reqnode *rnode;
+ strnodelist *bl;
+ int new;
+
+ while (bl_list != NULL) {
+ bl = bl_list->next;
+
+ fake_prov_entry = make_fake_provision(bl_list->node);
+
+ entry = Hash_CreateEntry(provide_hash, bl_list->s, &new);
+ if (new == 1)
+ warnx("file `%s' is before unknown provision `%s'", bl_list->node->filename, bl_list->s);
+
+ for (pnode = Hash_GetValue(entry); pnode; pnode = pnode->next) {
+ if (pnode->head)
+ continue;
+
+ rnode = emalloc(sizeof(*rnode));
+ rnode->entry = fake_prov_entry;
+ rnode->next = pnode->fnode->req_list;
+ pnode->fnode->req_list = rnode;
+ }
+
+ free(bl_list);
+ bl_list = bl;
+ }
+}
+
+/*
+ * loop over all the files calling crunch_file() on them to do the
+ * real work. after we have built all the nodes, insert the BEFORE:
+ * lines into graph(s).
+ */
+static void
+crunch_all_files(void)
+{
+ int i;
+
+ for (i = 0; i < file_count; i++)
+ crunch_file(file_list[i]);
+ insert_before();
+}
+
+/*
+ * below are the functions that traverse the graphs we have built
+ * finding out the desired ordering, printing each file in turn.
+ * if missing requirements, or cyclic graphs are detected, a
+ * warning will be issued, and we will continue on..
+ */
+
+/*
+ * given a requirement node (in a filename) we attempt to satisfy it.
+ * we do some sanity checking first, to ensure that we have providers,
+ * aren't already satisfied and aren't already being satisfied (ie,
+ * cyclic). if we pass all this, we loop over the provision list
+ * calling do_file() (enter recursion) for each filenode in this
+ * provision.
+ */
+static void
+satisfy_req(f_reqnode *rnode, char *filename)
+{
+ Hash_Entry *entry;
+ provnode *head;
+
+ entry = rnode->entry;
+ head = Hash_GetValue(entry);
+
+ if (head == NULL) {
+ warnx("requirement `%s' in file `%s' has no providers.",
+ Hash_GetKey(entry), filename);
+ exit_code = 1;
+ return;
+ }
+
+ /* return if the requirement is already satisfied. */
+ if (head->next == NULL)
+ return;
+
+ /*
+ * if list is marked as in progress,
+ * print that there is a circular dependency on it and abort
+ */
+ if (head->in_progress == SET) {
+ warnx("Circular dependency on provision `%s' in file `%s'.",
+ Hash_GetKey(entry), filename);
+ exit_code = 1;
+ return;
+ }
+
+ head->in_progress = SET;
+
+ /*
+ * while provision_list is not empty
+ * do_file(first_member_of(provision_list));
+ */
+ while (head->next != NULL)
+ do_file(head->next->fnode);
+}
+
+static int
+skip_ok(filenode *fnode)
+{
+ strnodelist *s;
+ strnodelist *k;
+
+ for (s = skip_list; s; s = s->next)
+ for (k = fnode->keyword_list; k; k = k->next)
+ if (strcmp(k->s, s->s) == 0)
+ return (0);
+
+ return (1);
+}
+
+static int
+keep_ok(filenode *fnode)
+{
+ strnodelist *s;
+ strnodelist *k;
+
+ for (s = keep_list; s; s = s->next)
+ for (k = fnode->keyword_list; k; k = k->next)
+ if (strcmp(k->s, s->s) == 0)
+ return (1);
+
+ /* an empty keep_list means every one */
+ return (!keep_list);
+}
+
+/*
+ * given a filenode, we ensure we are not a cyclic graph. if this
+ * is ok, we loop over the filenodes requirements, calling satisfy_req()
+ * for each of them.. once we have done this, remove this filenode
+ * from each provision table, as we are now done.
+ *
+ * NOTE: do_file() is called recursively from several places and cannot
+ * safely free() anything related to items that may be recursed on.
+ * Circular dependancies will cause problems if we do.
+ */
+static void
+do_file(filenode *fnode)
+{
+ f_reqnode *r, *r_tmp;
+ f_provnode *p, *p_tmp;
+ provnode *pnode;
+ int was_set;
+
+ DPRINTF((stderr, "do_file on %s.\n", fnode->filename));
+
+ /*
+ * if fnode is marked as in progress,
+ * print that fnode; is circularly depended upon and abort.
+ */
+ if (fnode->in_progress == SET) {
+ warnx("Circular dependency on file `%s'.",
+ fnode->filename);
+ was_set = exit_code = 1;
+ } else
+ was_set = 0;
+
+ /* mark fnode */
+ fnode->in_progress = SET;
+
+ /*
+ * for each requirement of fnode -> r
+ * satisfy_req(r, filename)
+ */
+ r = fnode->req_list;
+ while (r != NULL) {
+ r_tmp = r;
+ satisfy_req(r, fnode->filename);
+ r = r->next;
+#if 0
+ if (was_set == 0)
+ free(r_tmp);
+#endif
+ }
+ fnode->req_list = NULL;
+
+ /*
+ * for each provision of fnode -> p
+ * remove fnode from provision list for p in hash table
+ */
+ p = fnode->prov_list;
+ while (p != NULL) {
+ p_tmp = p;
+ pnode = p->pnode;
+ if (pnode->next != NULL) {
+ pnode->next->last = pnode->last;
+ }
+ if (pnode->last != NULL) {
+ pnode->last->next = pnode->next;
+ }
+ free(pnode);
+ p = p->next;
+ free(p_tmp);
+ }
+ fnode->prov_list = NULL;
+
+ /* do_it(fnode) */
+ DPRINTF((stderr, "next do: "));
+
+ /* if we were already in progress, don't print again */
+ if (was_set == 0 && skip_ok(fnode) && keep_ok(fnode))
+ printf("%s\n", fnode->filename);
+
+ if (fnode->next != NULL) {
+ fnode->next->last = fnode->last;
+ }
+ if (fnode->last != NULL) {
+ fnode->last->next = fnode->next;
+ }
+
+ DPRINTF((stderr, "nuking %s\n", fnode->filename));
+#if 0
+ if (was_set == 0) {
+ free(fnode->filename);
+ free(fnode);
+ }
+#endif
+}
+
+static void
+generate_ordering(void)
+{
+
+ /*
+ * while there remain undone files{f},
+ * pick an arbitrary f, and do_file(f)
+ * Note that the first file in the file list is perfectly
+ * arbitrary, and easy to find, so we use that.
+ */
+
+ /*
+ * N.B.: the file nodes "self delete" after they execute, so
+ * after each iteration of the loop, the head will be pointing
+ * to something totally different. The loop ends up being
+ * executed only once for every strongly connected set of
+ * nodes.
+ */
+ while (fn_head->next != NULL) {
+ DPRINTF((stderr, "generate on %s\n", fn_head->next->filename));
+ do_file(fn_head->next);
+ }
+}
diff --git a/sbin/rcorder/sprite.h b/sbin/rcorder/sprite.h
new file mode 100644
index 0000000..5e5d7f3
--- /dev/null
+++ b/sbin/rcorder/sprite.h
@@ -0,0 +1,113 @@
+/* $NetBSD: sprite.h,v 1.1 1999/11/23 05:28:22 mrg Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * 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.
+ *
+ * from: @(#)sprite.h 8.1 (Berkeley) 6/6/93
+ */
+
+/*
+ * sprite.h --
+ *
+ * Common constants and type declarations for Sprite.
+ */
+
+#ifndef _SPRITE
+#define _SPRITE
+
+
+/*
+ * A boolean type is defined as an integer, not an enum. This allows a
+ * boolean argument to be an expression that isn't strictly 0 or 1 valued.
+ */
+
+typedef int Boolean;
+#ifndef TRUE
+#define TRUE 1
+#endif /* TRUE */
+#ifndef FALSE
+#define FALSE 0
+#endif /* FALSE */
+
+/*
+ * Functions that must return a status can return a ReturnStatus to
+ * indicate success or type of failure.
+ */
+
+typedef int ReturnStatus;
+
+/*
+ * The following statuses overlap with the first 2 generic statuses
+ * defined in status.h:
+ *
+ * SUCCESS There was no error.
+ * FAILURE There was a general error.
+ */
+
+#define SUCCESS 0x00000000
+#define FAILURE 0x00000001
+
+
+/*
+ * A nil pointer must be something that will cause an exception if
+ * referenced. There are two nils: the kernels nil and the nil used
+ * by user processes.
+ */
+
+#define NIL ~0
+#define USER_NIL 0
+#ifndef NULL
+#define NULL 0
+#endif /* NULL */
+
+/*
+ * An address is just a pointer in C. It is defined as a character pointer
+ * so that address arithmetic will work properly, a byte at a time.
+ */
+
+typedef char *Address;
+
+/*
+ * ClientData is an uninterpreted word. It is defined as an int so that
+ * kdbx will not interpret client data as a string. Unlike an "Address",
+ * client data will generally not be used in arithmetic.
+ * But we don't have kdbx anymore so we define it as void (christos)
+ */
+
+typedef void *ClientData;
+
+#endif /* _SPRITE */
diff --git a/sbin/reboot/Makefile b/sbin/reboot/Makefile
new file mode 100644
index 0000000..aa41e30
--- /dev/null
+++ b/sbin/reboot/Makefile
@@ -0,0 +1,22 @@
+# @(#)Makefile 8.1 (Berkeley) 6/5/93
+# $FreeBSD$
+
+PROG= reboot
+MAN= reboot.8 nextboot.8
+MLINKS= reboot.8 halt.8 reboot.8 fastboot.8 reboot.8 fasthalt.8
+
+.if exists(${.CURDIR}/boot_${MACHINE}.8)
+MAN+= boot_${MACHINE}.8
+MLINKS+= boot_${MACHINE}.8 boot.8
+.endif
+.if ${MACHINE} == "amd64"
+MAN+= boot_i386.8
+MLINKS+= boot_i386.8 boot.8
+.endif
+
+LINKS= ${BINDIR}/reboot ${BINDIR}/halt ${BINDIR}/reboot ${BINDIR}/fastboot \
+ ${BINDIR}/reboot ${BINDIR}/fasthalt
+
+SCRIPTS= nextboot.sh
+
+.include <bsd.prog.mk>
diff --git a/sbin/reboot/boot_i386.8 b/sbin/reboot/boot_i386.8
new file mode 100644
index 0000000..690dfcd
--- /dev/null
+++ b/sbin/reboot/boot_i386.8
@@ -0,0 +1,379 @@
+.\" Copyright (c) 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software written and contributed
+.\" to Berkeley by William Jolitz.
+.\"
+.\" Almost completely rewritten for FreeBSD 2.1 by Joerg Wunsch.
+.\"
+.\" Substantially revised for FreeBSD 3.1 by Robert Nordier.
+.\"
+.\" 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.
+.\" 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.
+.\"
+.\" @(#)boot_i386.8 8.2 (Berkeley) 4/19/94
+.\"
+.\" $FreeBSD$
+.\"
+.Dd November 14, 2014
+.Dt BOOT 8 i386
+.Os
+.Sh NAME
+.Nm boot
+.Nd system bootstrapping procedures
+.Sh DESCRIPTION
+.Sy Power fail and crash recovery .
+Normally, the system will reboot itself at power-up or after crashes.
+An automatic consistency check of the file systems will be performed,
+and unless this fails, the system will resume multi-user operations.
+.Pp
+.Sy Cold starts .
+Most i386 PCs attempt to boot first from floppy disk drive 0 (sometimes
+known as drive A:) and, failing that, from hard disk drive 0 (sometimes
+known as drive C:, or as drive 0x80 to the BIOS).
+Some BIOSes allow
+you to change this default sequence, and may also include a CD-ROM
+drive as a boot device.
+.Pp
+Some newer PCs boot using UEFI firmware, not BIOS.
+That process is described
+in
+.Xr uefi 8 .
+.Pp
+By default, a three-stage bootstrap is employed, and control is
+automatically passed from the boot blocks (bootstrap stages one and
+two) to a separate third-stage bootstrap program,
+.Xr loader 8 .
+This third stage provides more sophisticated control over the booting
+process than it is possible to achieve in the boot blocks, which are
+constrained by occupying limited fixed space on a given disk or slice.
+.Pp
+However, it is possible to dispense with the third stage altogether,
+either by specifying a kernel name in the boot block parameter
+file,
+.Pa /boot.config ,
+or, unless option
+.Fl n
+is set, by hitting a key during a brief pause (while one of the characters
+.Sy - ,
+.Sy \e ,
+.Sy \&| ,
+or
+.Sy /
+is displayed) before
+.Xr loader 8
+is invoked.
+Booting will also be attempted at stage two, if the
+third stage cannot be loaded.
+.Pp
+The remainder of this subsection deals only with the boot blocks.
+The
+.Xr loader 8
+program is documented separately.
+.Pp
+After the boot blocks have been loaded,
+you should see a prompt similar to the following:
+.Bd -literal
+>> FreeBSD/i386 BOOT
+Default: 0:ad(0,a)/boot/loader
+boot:
+.Ed
+.Pp
+The automatic boot will attempt to load
+.Pa /boot/loader
+from partition
+.Ql a
+of either the floppy or the hard disk.
+This boot may be aborted by typing any character on the keyboard
+at the
+.Ql boot:
+prompt.
+At this time, the following input will be accepted:
+.Bl -tag -width indent
+.It Ic \&?
+Give a short listing of the files in the root directory of the default
+boot device, as a hint about available boot files.
+(A
+.Ic ?\&
+may also be specified as the last segment of a path, in which case
+the listing will be of the relevant subdirectory.)
+.It Xo
+.Sm off
+.Ar bios_drive : interface ( unit , Oo Ar slice , Oc Ar part )
+.Ar filename
+.Sm on
+.Op Fl aCcDdghmnPprsv
+.Op Fl S Ns Ar speed
+.Xc
+Specify boot file and flags.
+.Bl -tag -width indent
+.It Ar bios_drive
+The drive number as recognized by the BIOS.
+0 for the first drive, 1 for the second drive, etc.
+.It Ar interface
+The type of controller to boot from.
+Note that the controller is required
+to have BIOS support since the BIOS services are used to load the
+boot file image.
+.Pp
+The supported interfaces are:
+.Pp
+.Bl -tag -width "adXX" -compact
+.It ad
+ST506, IDE, ESDI, RLL disks on a WD100[2367] or lookalike
+controller
+.It fd
+5 1/4" or 3 1/2" High density floppies
+.It da
+SCSI disk on any supported SCSI controller
+.\".It cd
+.\"boot from CDROM
+.El
+.It Ar unit
+The unit number of the drive on the interface being used.
+0 for the first drive, 1 for the second drive, etc.
+.It Oo Ar slice , Oc Ns Ar part
+The partition letter inside the
+.Bx
+portion of the disk.
+See
+.Xr bsdlabel 8 .
+By convention, only partition
+.Ql a
+contains a bootable image.
+If sliced disks are used
+.Pq Dq fdisk partitions ,
+any
+.Ar slice
+(1 for the first slice, 2 for the second slice, etc.\&)
+can be booted from, with the default (if not specified) being the active slice
+or, otherwise, the first
+.Fx
+slice.
+If
+.Ar slice
+is specified as 0, the first
+.Fx
+slice (also known as
+.Dq compatibility
+slice) is booted from.
+.It Ar filename
+The pathname of the file to boot (relative to the root directory
+on the specified partition).
+Defaults to
+.Pa /boot/kernel/kernel .
+Symbolic links are not supported (hard links are).
+.It Xo Op Fl aCcDdghmnPpqrsv
+.Op Fl S Ns Ar speed
+.Xc
+Boot flags:
+.Pp
+.Bl -tag -width "-CXX" -compact
+.It Fl a
+during kernel initialization,
+ask for the device to mount as the root file system.
+.It Fl C
+try to mount root file system from a CD-ROM.
+.It Fl c
+this flag is currently a no-op.
+.It Fl D
+boot with the dual console configuration.
+In the single
+configuration, the console will be either the internal display
+or the serial port, depending on the state of the
+.Fl h
+option below.
+In the dual console configuration,
+both the internal display and the serial port will become the console
+at the same time, regardless of the state of the
+.Fl h
+option.
+.It Fl d
+enter the DDB kernel debugger
+(see
+.Xr ddb 4 )
+as early as possible in kernel initialization.
+.It Fl g
+use the GDB remote debugging protocol.
+.It Fl h
+force the serial console.
+For instance, if you boot from the internal console,
+you can use the
+.Fl h
+option to force the kernel to use the serial port as its
+console device.
+The serial port driver
+.Xr sio 4
+(but not
+.Xr uart 4 )
+has a flag (0x20) to override this option.
+If that flag is set, the serial port will always be used as the console,
+regardless of the
+.Fl h
+option described here.
+.It Fl m
+mute the console to suppress all console input and output during the
+boot.
+.It Fl n
+ignore key press to interrupt boot before
+.Xr loader 8
+is invoked.
+.It Fl P
+probe the keyboard.
+If no keyboard is found, the
+.Fl D
+and
+.Fl h
+options are automatically set.
+.It Fl p
+pause after each attached device during the device probing phase.
+.It Fl q
+be quiet,
+do not write anything to the console unless automatic boot fails or
+is disabled.
+This option only affects second-stage bootstrap,
+to prevent next stages from writing to the console use in
+combination with the
+.Fl m
+option.
+.It Fl r
+use the statically configured default for the device containing the
+root file system
+(see
+.Xr config 8 ) .
+Normally, the root file system is on the device
+that the kernel was loaded from.
+.It Fl s
+boot into single-user mode; if the console is marked as
+.Dq insecure
+(see
+.Xr ttys 5 ) ,
+the root password must be entered.
+.It Fl S Ns Ar speed
+set the speed of the serial console to
+.Ar speed .
+The default is 9600 unless it has been overridden by setting
+.Va BOOT_COMCONSOLE_SPEED
+in
+.Xr make.conf 5
+and recompiling and reinstalling the boot blocks.
+.It Fl v
+be verbose during device probing (and later).
+.El
+.El
+.El
+.Pp
+Use the
+.Pa /boot.config
+file to set the default configuration options for the boot block code.
+See
+.Xr boot.config 5
+for more information about the
+.Pa /boot.config
+file.
+.Sh FILES
+.Bl -tag -width /boot/loader -compact
+.It Pa /boot.config
+parameters for the boot blocks (optional)
+.It Pa /boot/boot1
+first stage bootstrap file
+.It Pa /boot/boot2
+second stage bootstrap file
+.It Pa /boot/loader
+third stage bootstrap
+.It Pa /boot/kernel/kernel
+default kernel
+.It Pa /boot/kernel.old/kernel
+typical non-default kernel (optional)
+.El
+.Sh DIAGNOSTICS
+When disk-related errors occur, these are reported by the second-stage
+bootstrap using the same error codes returned by the BIOS, for example
+.Dq Disk error 0x1 (lba=0x12345678) .
+Here is a partial list of these error codes:
+.Pp
+.Bl -tag -width "0x80" -compact
+.It 0x1
+Invalid argument
+.It 0x2
+Address mark not found
+.It 0x4
+Sector not found
+.It 0x8
+DMA overrun
+.It 0x9
+DMA attempt across 64K boundary
+.It 0xc
+Invalid media
+.It 0x10
+Uncorrectable CRC/ECC error
+.It 0x20
+Controller failure
+.It 0x40
+Seek failed
+.It 0x80
+Timeout
+.El
+.Pp
+.Sy "NOTE" :
+On older machines, or otherwise where EDD support (disk packet
+interface support) is not available, all boot-related files and
+structures (including the kernel) that need to be accessed during the
+boot phase must reside on the disk at or below cylinder 1023 (as the
+BIOS understands the geometry).
+When a
+.Dq Disk error 0x1
+is reported by the second-stage bootstrap, it generally means that this
+requirement has not been adhered to.
+.Sh SEE ALSO
+.Xr ddb 4 ,
+.Xr boot.config 5 ,
+.Xr make.conf 5 ,
+.Xr ttys 5 ,
+.Xr boot0cfg 8 ,
+.Xr bsdlabel 8 ,
+.Xr btxld 8 ,
+.Xr config 8 ,
+.Xr gptboot 8 ,
+.Xr halt 8 ,
+.Xr loader 8 ,
+.Xr nextboot 8 ,
+.Xr reboot 8 ,
+.Xr shutdown 8 ,
+.Xr uefi 8
+.Sh BUGS
+The bsdlabel format used by this version of
+.Bx
+is quite
+different from that of other architectures.
+.Pp
+Due to space constraints, the keyboard probe initiated by the
+.Fl P
+option is simply a test that the BIOS has detected an
+.Dq extended
+keyboard.
+If an
+.Dq XT/AT
+keyboard (with no F11 and F12 keys, etc.) is attached, the probe will
+fail.
diff --git a/sbin/reboot/nextboot.8 b/sbin/reboot/nextboot.8
new file mode 100644
index 0000000..14573e2
--- /dev/null
+++ b/sbin/reboot/nextboot.8
@@ -0,0 +1,138 @@
+.\" Copyright (c) 2002 Gordon Tetlow
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd March 17, 2015
+.Dt NEXTBOOT 8
+.Os
+.Sh NAME
+.Nm nextboot
+.Nd "specify an alternate kernel and boot flags for the next reboot"
+.Sh SYNOPSIS
+.Nm
+.Op Fl e Ar variable=value
+.Op Fl f
+.Op Fl k Ar kernel
+.Op Fl o Ar options
+.Nm
+.Fl D
+.Sh DESCRIPTION
+The
+.Nm
+utility allows specifying some combination of an alternate kernel, boot flags
+and kernel environment for the
+next time the machine is booted.
+Once the
+.Xr loader 8
+loads in the new kernel
+information, it is deleted so in case the new kernel hangs the machine,
+once it is rebooted, the machine will automatically revert to its previous
+configuration.
+.Pp
+The options are as follows:
+.Bl -tag -width ".Fl o Ar options"
+.It Fl D
+Invoking
+.Nm
+with this
+option removes an existing
+.Nm
+configuration.
+.It Fl e Ar variable=value
+This option adds the provided variable and value to the kernel environment.
+The value is quoted when written to the
+.Nm
+configuration.
+.It Fl f
+This
+option disables the sanity checking which checks if the kernel really exists
+before writing the
+.Nm
+configuration.
+.It Fl k Ar kernel
+This option specifies a kernel directory relative to
+.Pa /boot
+to load the kernel and any modules from.
+.It Fl o Ar options
+This option
+allows the passing of kernel flags for the next boot.
+.El
+.Sh FILES
+.Bl -tag -width ".Pa /boot/nextboot.conf" -compact
+.It Pa /boot/nextboot.conf
+The configuration file that the
+.Nm
+configuration is written into.
+.El
+.Sh EXAMPLES
+To boot the
+.Pa GENERIC
+kernel with the
+.Nm
+command:
+.Pp
+.Dl "nextboot -k GENERIC"
+.Pp
+To enable into single user mode with the normal kernel:
+.Pp
+.Dl "nextboot -o ""-s"" -k kernel"
+.Pp
+To remove an existing nextboot configuration:
+.Pp
+.Dl "nextboot -D"
+.Sh SEE ALSO
+.Xr boot 8 ,
+.Xr loader 8
+.Sh HISTORY
+The original
+.Nm
+manual page first appeared in
+.Fx 2.2 .
+It used a very different interface to achieve similar results.
+.Pp
+The current incarnation of
+.Nm
+appeared in
+.Fx 5.0 .
+.Sh AUTHORS
+This manual page was written by
+.An Gordon Tetlow Aq Mt gordon@FreeBSD.org .
+.Sh BUGS
+The
+.Nm
+code is implemented in the
+.Xr loader 8 .
+It is not the most thoroughly tested code.
+It is also my first attempt to write in Forth.
+.Pp
+Finally, it does some evil things like writing to the file system before it
+has been checked.
+If it scrambles your file system, do not blame me.
+.Pp
+.Xr loader 8
+is only able to read ZFS, not write to it.
+.Pa nextboot.conf
+will NOT be reset in case of a kernel boot failure.
+
diff --git a/sbin/reboot/nextboot.sh b/sbin/reboot/nextboot.sh
new file mode 100644
index 0000000..655e533
--- /dev/null
+++ b/sbin/reboot/nextboot.sh
@@ -0,0 +1,112 @@
+#! /bin/sh
+#
+# Copyright (c) 2002 Gordon Tetlow. All rights reserved.
+# Copyright (c) 2012 Sandvine Incorporated. 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.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# $FreeBSD$
+
+delete="NO"
+kenv=
+force="NO"
+nextboot_file="/boot/nextboot.conf"
+
+add_kenv()
+{
+ local var value
+
+ var=$1
+ # strip literal quotes if passed in
+ value=${2%\"*}
+ value=${value#*\"}
+
+ if [ -n "${kenv}" ]; then
+ kenv="${kenv}
+"
+ fi
+ kenv="${kenv}${var}=\"${value}\""
+}
+
+display_usage() {
+ echo "Usage: nextboot [-e variable=value] [-f] [-k kernel] [-o options]"
+ echo " nextboot -D"
+}
+
+while getopts "De:fk:o:" argument ; do
+ case "${argument}" in
+ D)
+ delete="YES"
+ ;;
+ e)
+ var=${OPTARG%%=*}
+ value=${OPTARG#*=}
+ if [ -z "$var" -o -z "$value" ]; then
+ display_usage
+ exit 1
+ fi
+ add_kenv "$var" "$value"
+ ;;
+ f)
+ force="YES"
+ ;;
+ k)
+ kernel="${OPTARG}"
+ add_kenv kernel "$kernel"
+ ;;
+ o)
+ add_kenv kernel_options "${OPTARG}"
+ ;;
+ *)
+ display_usage
+ exit 1
+ ;;
+ esac
+done
+
+if [ ${delete} = "YES" ]; then
+ rm -f ${nextboot_file}
+ exit 0
+fi
+
+if [ -z "${kenv}" ]; then
+ display_usage
+ exit 1
+fi
+
+if [ -n "${kernel}" -a ${force} = "NO" -a ! -d /boot/${kernel} ]; then
+ echo "Error: /boot/${kernel} doesn't exist. Use -f to override."
+ exit 1
+fi
+
+df -Tn "/boot/" 2>/dev/null | while read _fs _type _other ; do
+ [ "zfs" = "${_type}" ] || continue
+ cat 1>&2 <<-EOF
+ WARNING: loader(8) has only R/O support for ZFS
+ nextboot.conf will NOT be reset in case of kernel boot failure
+ EOF
+done
+
+cat > ${nextboot_file} << EOF
+nextboot_enable="YES"
+$kenv
+EOF
diff --git a/sbin/reboot/reboot.8 b/sbin/reboot/reboot.8
new file mode 100644
index 0000000..13d7098
--- /dev/null
+++ b/sbin/reboot/reboot.8
@@ -0,0 +1,143 @@
+.\" Copyright (c) 1990, 1991, 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.
+.\" 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.
+.\"
+.\" @(#)reboot.8 8.1 (Berkeley) 6/9/93
+.\" $FreeBSD$
+.\"
+.Dd October 11, 2010
+.Dt REBOOT 8
+.Os
+.Sh NAME
+.Nm reboot ,
+.Nm halt ,
+.Nm fastboot ,
+.Nm fasthalt
+.Nd stopping and restarting the system
+.Sh SYNOPSIS
+.Nm halt
+.Op Fl lnpq
+.Op Fl k Ar kernel
+.Nm
+.Op Fl dlnpq
+.Op Fl k Ar kernel
+.Nm fasthalt
+.Op Fl lnpq
+.Op Fl k Ar kernel
+.Nm fastboot
+.Op Fl dlnpq
+.Op Fl k Ar kernel
+.Sh DESCRIPTION
+The
+.Nm halt
+and
+.Nm
+utilities flush the file system cache to disk, send all running processes
+a
+.Dv SIGTERM
+(and subsequently a
+.Dv SIGKILL )
+and, respectively, halt or restart the system.
+The action is logged, including entering a shutdown record into the user
+accounting database.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl d
+The system is requested to create a crash dump.
+This option is
+supported only when rebooting, and it has no effect unless a dump
+device has previously been specified with
+.Xr dumpon 8 .
+.It Fl k Ar kernel
+Boot the specified
+.Ar kernel
+on the next system boot.
+If the kernel boots successfully, the
+.Em default
+kernel will be booted on successive boots, this is a one-shot option.
+If the boot fails, the system will continue attempting to boot
+.Ar kernel
+until the boot process is interrupted and a valid kernel booted.
+This may change in the future.
+.It Fl l
+The halt or reboot is
+.Em not
+logged to the system log.
+This option is intended for applications such as
+.Xr shutdown 8 ,
+that call
+.Nm
+or
+.Nm halt
+and log this themselves.
+.It Fl n
+The file system cache is not flushed.
+This option should probably not be used.
+.It Fl p
+The system will turn off the power if it can.
+If the power down action fails, the system
+will halt or reboot normally, depending on whether
+.Nm halt
+or
+.Nm
+was called.
+.It Fl q
+The system is halted or restarted quickly and ungracefully, and only
+the flushing of the file system cache is performed (if the
+.Fl n
+option is not specified).
+This option should probably not be used.
+.El
+.Pp
+The
+.Nm fasthalt
+and
+.Nm fastboot
+utilities are nothing more than aliases for the
+.Nm halt
+and
+.Nm
+utilities.
+.Pp
+Normally, the
+.Xr shutdown 8
+utility is used when the system needs to be halted or restarted, giving
+users advance warning of their impending doom and cleanly terminating
+specific programs.
+.Sh SEE ALSO
+.Xr getutxent 3 ,
+.Xr boot 8 ,
+.Xr dumpon 8 ,
+.Xr nextboot 8 ,
+.Xr savecore 8 ,
+.Xr shutdown 8 ,
+.Xr sync 8
+.Sh HISTORY
+A
+.Nm
+utility appeared in
+.At v6 .
diff --git a/sbin/reboot/reboot.c b/sbin/reboot/reboot.c
new file mode 100644
index 0000000..d927db0
--- /dev/null
+++ b/sbin/reboot/reboot.c
@@ -0,0 +1,245 @@
+/*
+ * Copyright (c) 1980, 1986, 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.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if 0
+#ifndef lint
+static const char copyright[] =
+"@(#) Copyright (c) 1980, 1986, 1993\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static char sccsid[] = "@(#)reboot.c 8.1 (Berkeley) 6/5/93";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/reboot.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/sysctl.h>
+#include <signal.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <syslog.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <utmpx.h>
+
+static void usage(void);
+static u_int get_pageins(void);
+
+static int dohalt;
+
+int
+main(int argc, char *argv[])
+{
+ struct utmpx utx;
+ const struct passwd *pw;
+ int ch, howto, i, fd, lflag, nflag, qflag, sverrno;
+ u_int pageins;
+ const char *user, *kernel = NULL;
+
+ if (strcmp(getprogname(), "halt") == 0) {
+ dohalt = 1;
+ howto = RB_HALT;
+ } else
+ howto = 0;
+ lflag = nflag = qflag = 0;
+ while ((ch = getopt(argc, argv, "dk:lnpq")) != -1)
+ switch(ch) {
+ case 'd':
+ howto |= RB_DUMP;
+ break;
+ case 'k':
+ kernel = optarg;
+ break;
+ case 'l':
+ lflag = 1;
+ break;
+ case 'n':
+ nflag = 1;
+ howto |= RB_NOSYNC;
+ break;
+ case 'p':
+ howto |= RB_POWEROFF;
+ break;
+ case 'q':
+ qflag = 1;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ if ((howto & (RB_DUMP | RB_HALT)) == (RB_DUMP | RB_HALT))
+ errx(1, "cannot dump (-d) when halting; must reboot instead");
+ if (geteuid()) {
+ errno = EPERM;
+ err(1, NULL);
+ }
+
+ if (qflag) {
+ reboot(howto);
+ err(1, NULL);
+ }
+
+ if (kernel != NULL) {
+ fd = open("/boot/nextboot.conf", O_WRONLY | O_CREAT | O_TRUNC,
+ 0444);
+ if (fd > -1) {
+ (void)write(fd, "nextboot_enable=\"YES\"\n", 22);
+ (void)write(fd, "kernel=\"", 8L);
+ (void)write(fd, kernel, strlen(kernel));
+ (void)write(fd, "\"\n", 2);
+ close(fd);
+ }
+ }
+
+ /* Log the reboot. */
+ if (!lflag) {
+ if ((user = getlogin()) == NULL)
+ user = (pw = getpwuid(getuid())) ?
+ pw->pw_name : "???";
+ if (dohalt) {
+ openlog("halt", 0, LOG_AUTH | LOG_CONS);
+ syslog(LOG_CRIT, "halted by %s", user);
+ } else {
+ openlog("reboot", 0, LOG_AUTH | LOG_CONS);
+ syslog(LOG_CRIT, "rebooted by %s", user);
+ }
+ }
+ utx.ut_type = SHUTDOWN_TIME;
+ gettimeofday(&utx.ut_tv, NULL);
+ pututxline(&utx);
+
+ /*
+ * Do a sync early on, so disks start transfers while we're off
+ * killing processes. Don't worry about writes done before the
+ * processes die, the reboot system call syncs the disks.
+ */
+ if (!nflag)
+ sync();
+
+ /*
+ * Ignore signals that we can get as a result of killing
+ * parents, group leaders, etc.
+ */
+ (void)signal(SIGHUP, SIG_IGN);
+ (void)signal(SIGINT, SIG_IGN);
+ (void)signal(SIGQUIT, SIG_IGN);
+ (void)signal(SIGTERM, SIG_IGN);
+ (void)signal(SIGTSTP, SIG_IGN);
+
+ /*
+ * If we're running in a pipeline, we don't want to die
+ * after killing whatever we're writing to.
+ */
+ (void)signal(SIGPIPE, SIG_IGN);
+
+ /* Just stop init -- if we fail, we'll restart it. */
+ if (kill(1, SIGTSTP) == -1)
+ err(1, "SIGTSTP init");
+
+ /* Send a SIGTERM first, a chance to save the buffers. */
+ if (kill(-1, SIGTERM) == -1 && errno != ESRCH)
+ err(1, "SIGTERM processes");
+
+ /*
+ * After the processes receive the signal, start the rest of the
+ * buffers on their way. Wait 5 seconds between the SIGTERM and
+ * the SIGKILL to give everybody a chance. If there is a lot of
+ * paging activity then wait longer, up to a maximum of approx
+ * 60 seconds.
+ */
+ sleep(2);
+ for (i = 0; i < 20; i++) {
+ pageins = get_pageins();
+ if (!nflag)
+ sync();
+ sleep(3);
+ if (get_pageins() == pageins)
+ break;
+ }
+
+ for (i = 1;; ++i) {
+ if (kill(-1, SIGKILL) == -1) {
+ if (errno == ESRCH)
+ break;
+ goto restart;
+ }
+ if (i > 5) {
+ (void)fprintf(stderr,
+ "WARNING: some process(es) wouldn't die\n");
+ break;
+ }
+ (void)sleep(2 * i);
+ }
+
+ reboot(howto);
+ /* FALLTHROUGH */
+
+restart:
+ sverrno = errno;
+ errx(1, "%s%s", kill(1, SIGHUP) == -1 ? "(can't restart init): " : "",
+ strerror(sverrno));
+ /* NOTREACHED */
+}
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr, dohalt ?
+ "usage: halt [-lnpq] [-k kernel]\n" :
+ "usage: reboot [-dlnpq] [-k kernel]\n");
+ exit(1);
+}
+
+static u_int
+get_pageins(void)
+{
+ u_int pageins;
+ size_t len;
+
+ len = sizeof(pageins);
+ if (sysctlbyname("vm.stats.vm.v_swappgsin", &pageins, &len, NULL, 0)
+ != 0) {
+ warnx("v_swappgsin");
+ return (0);
+ }
+ return pageins;
+}
diff --git a/sbin/recoverdisk/Makefile b/sbin/recoverdisk/Makefile
new file mode 100644
index 0000000..136e570
--- /dev/null
+++ b/sbin/recoverdisk/Makefile
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+PROG= recoverdisk
+
+.include <bsd.prog.mk>
+
+test: ${PROG}
+ ./${PROG} /dev/ad0
diff --git a/sbin/recoverdisk/recoverdisk.1 b/sbin/recoverdisk/recoverdisk.1
new file mode 100644
index 0000000..fd42f65
--- /dev/null
+++ b/sbin/recoverdisk/recoverdisk.1
@@ -0,0 +1,174 @@
+.\" Copyright (c) 2006 Ulrich Spoerlein <uspoerlein@gmail.com>
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd October 1, 2013
+.Dt RECOVERDISK 1
+.Os
+.Sh NAME
+.Nm recoverdisk
+.Nd recover data from hard disk or optical media
+.Sh SYNOPSIS
+.Nm
+.Op Fl b Ar bigsize
+.Op Fl r Ar readlist
+.Op Fl s Ar interval
+.Op Fl w Ar writelist
+.Ar source
+.Op Ar destination
+.Sh DESCRIPTION
+The
+.Nm
+utility reads data from the
+.Ar source
+file until all blocks could be successfully read.
+If
+.Ar destination
+was specified all data is being written to that file.
+It starts reading in multiples of the sector size.
+Whenever a block fails, it is put to the end of the working queue and will be
+read again, possibly with a smaller read size.
+.Pp
+By default it uses block sizes of roughly 1 MB, 32kB, and the native
+sector size (usually 512 bytes).
+These figures are adjusted slightly, for devices whose sectorsize is not a
+power of 2, e.g., audio CDs with a sector size of 2352 bytes.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl b Ar bigsize
+The size of reads attempted first.
+The middle pass is roughly the logarithmic average of the bigsize and
+the sectorsize.
+.It Fl r Ar readlist
+Read the list of blocks and block sizes to read from the specified file.
+.It Fl s Ar interval
+How often we should update the writelist file while things go OK.
+The default is 60 and the unit is "progress messages" so if things
+go well, this is the same as once per minute.
+.It Fl w Ar writelist
+Write the list of remaining blocks to read to the specified file if
+.Nm
+is aborted via
+.Dv SIGINT .
+.El
+.Pp
+The
+.Fl r
+and
+.Fl w
+options can be specified together.
+Especially, they can point to the same file, which will be updated on abort.
+.Sh OUTPUT
+The
+.Nm
+utility
+prints several columns, detailing the progress
+.Bl -tag -width remaining
+.It Va start
+Starting offset of the current block.
+.It Va size
+Read size of the current block.
+.It Va len
+Length of the current block.
+.It Va state
+Is increased for every failed read.
+.It Va done
+Number of bytes already read.
+.It Va remaining
+Number of bytes remaining.
+.It Va "% done"
+Percent complete.
+.El
+.Sh EXAMPLES
+.Bd -literal
+# recover data from failing hard drive ada3
+recoverdisk /dev/ada3 /data/disk.img
+
+# clone a hard disk
+recoverdisk /dev/ada3 /dev/ada4
+
+# read an ISO image from a CD-ROM
+recoverdisk /dev/cd0 /data/cd.iso
+
+# continue reading from a broken CD and update the existing worklist
+recoverdisk -r worklist -w worklist /dev/cd0 /data/cd.iso
+
+# recover a single file from the unreadable media
+recoverdisk /cdrom/file.avi file.avi
+
+# If the disk hangs the system on read-errors try:
+recoverdisk -b 0 /dev/ada3 /somewhere
+
+.Ed
+.Sh SEE ALSO
+.Xr dd 1 ,
+.Xr ada 4 ,
+.Xr cam 4 ,
+.Xr cd 4 ,
+.Xr da 4
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 7.0 .
+.Sh AUTHORS
+.An -nosplit
+The original implementation was done by
+.An Poul-Henning Kamp Aq Mt phk@FreeBSD.org
+with minor improvements from
+.An Ulrich Sp\(:orlein Aq Mt uqs@FreeBSD.org .
+.Pp
+This manual page was written by
+.An Ulrich Sp\(:orlein .
+.Sh BUGS
+Reading from media where the sectorsize is not a power of 2 will make all
+1 MB reads fail.
+This is due to the DMA reads being split up into blocks of at most 128kB.
+These reads then fail if the sectorsize is not a divisor of 128kB.
+When reading a full raw audio CD, this leads to roughly 700 error messages
+flying by.
+This is harmless and can be avoided by setting
+.Fl b
+to no more than 128kB.
+.Pp
+.Nm
+needs to know about read errors as fast as possible, i.e. retries by lower
+layers will usually slow down the operation.
+When using
+.Xr cam 4
+attached drives, you may want to set kern.cam.XX.retry_count to zero, e.g.:
+.Bd -literal
+# sysctl kern.cam.ada.retry_count=0
+# sysctl kern.cam.cd.retry_count=0
+# sysctl kern.cam.da.retry_count=0
+.Ed
+.\".Pp
+.\"When reading from optical media, a bug in the GEOM framework will
+.\"prevent it from seeing that the media has been removed.
+.\"The device can still be opened, but all reads will fail.
+.\"This is usually harmless, but will send
+.\".Nm
+.\"into an infinite loop.
diff --git a/sbin/recoverdisk/recoverdisk.c b/sbin/recoverdisk/recoverdisk.c
new file mode 100644
index 0000000..af0d88e
--- /dev/null
+++ b/sbin/recoverdisk/recoverdisk.c
@@ -0,0 +1,322 @@
+/*-
+ * ----------------------------------------------------------------------------
+ * "THE BEER-WARE LICENSE" (Revision 42):
+ * <phk@FreeBSD.ORG> wrote this file. As long as you retain this notice you
+ * can do whatever you want with this stuff. If we meet some day, and you think
+ * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp
+ * ----------------------------------------------------------------------------
+ *
+ * $FreeBSD$
+ */
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/disk.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+static volatile sig_atomic_t aborting = 0;
+static size_t bigsize = 1024 * 1024;
+static size_t medsize;
+static size_t minsize = 512;
+
+struct lump {
+ off_t start;
+ off_t len;
+ int state;
+ TAILQ_ENTRY(lump) list;
+};
+
+static TAILQ_HEAD(, lump) lumps = TAILQ_HEAD_INITIALIZER(lumps);
+
+static void
+new_lump(off_t start, off_t len, int state)
+{
+ struct lump *lp;
+
+ lp = malloc(sizeof *lp);
+ if (lp == NULL)
+ err(1, "Malloc failed");
+ lp->start = start;
+ lp->len = len;
+ lp->state = state;
+ TAILQ_INSERT_TAIL(&lumps, lp, list);
+}
+
+static struct lump *lp;
+static char *wworklist = NULL;
+static char *rworklist = NULL;
+
+
+#define PRINT_HEADER \
+ printf("%13s %7s %13s %5s %13s %13s %9s\n", \
+ "start", "size", "block-len", "state", "done", "remaining", "% done")
+
+#define PRINT_STATUS(start, i, len, state, d, t) \
+ printf("\r%13jd %7zu %13jd %5d %13jd %13jd %9.5f", \
+ (intmax_t)start, \
+ i, \
+ (intmax_t)len, \
+ state, \
+ (intmax_t)d, \
+ (intmax_t)(t - d), \
+ 100*(double)d/(double)t)
+
+/* Save the worklist if -w was given */
+static void
+save_worklist(void)
+{
+ FILE *file;
+ struct lump *llp;
+
+ if (wworklist != NULL) {
+ (void)fprintf(stderr, "\nSaving worklist ...");
+ fflush(stderr);
+
+ file = fopen(wworklist, "w");
+ if (file == NULL)
+ err(1, "Error opening file %s", wworklist);
+
+ TAILQ_FOREACH(llp, &lumps, list)
+ fprintf(file, "%jd %jd %d\n",
+ (intmax_t)llp->start, (intmax_t)llp->len,
+ llp->state);
+ fclose(file);
+ (void)fprintf(stderr, " done.\n");
+ }
+}
+
+/* Read the worklist if -r was given */
+static off_t
+read_worklist(off_t t)
+{
+ off_t s, l, d;
+ int state, lines;
+ FILE *file;
+
+ (void)fprintf(stderr, "Reading worklist ...");
+ fflush(stderr);
+ file = fopen(rworklist, "r");
+ if (file == NULL)
+ err(1, "Error opening file %s", rworklist);
+
+ lines = 0;
+ d = t;
+ for (;;) {
+ ++lines;
+ if (3 != fscanf(file, "%jd %jd %d\n", &s, &l, &state)) {
+ if (!feof(file))
+ err(1, "Error parsing file %s at line %d",
+ rworklist, lines);
+ else
+ break;
+ }
+ new_lump(s, l, state);
+ d -= l;
+ }
+ (void)fprintf(stderr, " done.\n");
+ /*
+ * Return the number of bytes already read
+ * (at least not in worklist).
+ */
+ return (d);
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr, "usage: recoverdisk [-b bigsize] [-r readlist] "
+ "[-s interval] [-w writelist] source [destination]\n");
+ exit(1);
+}
+
+static void
+sighandler(__unused int sig)
+{
+
+ aborting = 1;
+}
+
+int
+main(int argc, char * const argv[])
+{
+ int ch;
+ int fdr, fdw;
+ off_t t, d, start, len;
+ size_t i, j;
+ int error, state;
+ u_char *buf;
+ u_int sectorsize;
+ off_t stripesize;
+ time_t t1, t2;
+ struct stat sb;
+ u_int n, snapshot = 60;
+
+ while ((ch = getopt(argc, argv, "b:r:w:s:")) != -1) {
+ switch (ch) {
+ case 'b':
+ bigsize = strtoul(optarg, NULL, 0);
+ break;
+ case 'r':
+ rworklist = strdup(optarg);
+ if (rworklist == NULL)
+ err(1, "Cannot allocate enough memory");
+ break;
+ case 's':
+ snapshot = strtoul(optarg, NULL, 0);
+ break;
+ case 'w':
+ wworklist = strdup(optarg);
+ if (wworklist == NULL)
+ err(1, "Cannot allocate enough memory");
+ break;
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 1 || argc > 2)
+ usage();
+
+ fdr = open(argv[0], O_RDONLY);
+ if (fdr < 0)
+ err(1, "Cannot open read descriptor %s", argv[0]);
+
+ error = fstat(fdr, &sb);
+ if (error < 0)
+ err(1, "fstat failed");
+ if (S_ISBLK(sb.st_mode) || S_ISCHR(sb.st_mode)) {
+ error = ioctl(fdr, DIOCGSECTORSIZE, &sectorsize);
+ if (error < 0)
+ err(1, "DIOCGSECTORSIZE failed");
+
+ error = ioctl(fdr, DIOCGSTRIPESIZE, &stripesize);
+ if (error == 0 && stripesize > sectorsize)
+ sectorsize = stripesize;
+
+ minsize = sectorsize;
+ bigsize = (bigsize / sectorsize) * sectorsize;
+
+ error = ioctl(fdr, DIOCGMEDIASIZE, &t);
+ if (error < 0)
+ err(1, "DIOCGMEDIASIZE failed");
+ } else {
+ t = sb.st_size;
+ }
+
+ if (bigsize < minsize)
+ bigsize = minsize;
+
+ for (ch = 0; (bigsize >> ch) > minsize; ch++)
+ continue;
+ medsize = bigsize >> (ch / 2);
+ medsize = (medsize / minsize) * minsize;
+
+ fprintf(stderr, "Bigsize = %zu, medsize = %zu, minsize = %zu\n",
+ bigsize, medsize, minsize);
+
+ buf = malloc(bigsize);
+ if (buf == NULL)
+ err(1, "Cannot allocate %zu bytes buffer", bigsize);
+
+ if (argc > 1) {
+ fdw = open(argv[1], O_WRONLY | O_CREAT, DEFFILEMODE);
+ if (fdw < 0)
+ err(1, "Cannot open write descriptor %s", argv[1]);
+ if (ftruncate(fdw, t) < 0)
+ err(1, "Cannot truncate output %s to %jd bytes",
+ argv[1], (intmax_t)t);
+ } else
+ fdw = -1;
+
+ if (rworklist != NULL) {
+ d = read_worklist(t);
+ } else {
+ new_lump(0, t, 0);
+ d = 0;
+ }
+ if (wworklist != NULL)
+ signal(SIGINT, sighandler);
+
+ t1 = 0;
+ start = len = i = state = 0;
+ PRINT_HEADER;
+ n = 0;
+ for (;;) {
+ lp = TAILQ_FIRST(&lumps);
+ if (lp == NULL)
+ break;
+ while (lp->len > 0 && !aborting) {
+ /* These are only copied for printing stats */
+ start = lp->start;
+ len = lp->len;
+ state = lp->state;
+
+ i = MIN(lp->len, (off_t)bigsize);
+ if (lp->state == 1)
+ i = MIN(lp->len, (off_t)medsize);
+ if (lp->state > 1)
+ i = MIN(lp->len, (off_t)minsize);
+ time(&t2);
+ if (t1 != t2 || lp->len < (off_t)bigsize) {
+ PRINT_STATUS(start, i, len, state, d, t);
+ t1 = t2;
+ if (++n == snapshot) {
+ save_worklist();
+ n = 0;
+ }
+ }
+ if (i == 0) {
+ errx(1, "BOGUS i %10jd", (intmax_t)i);
+ }
+ fflush(stdout);
+ j = pread(fdr, buf, i, lp->start);
+ if (j == i) {
+ d += i;
+ if (fdw >= 0)
+ j = pwrite(fdw, buf, i, lp->start);
+ else
+ j = i;
+ if (j != i)
+ printf("\nWrite error at %jd/%zu\n",
+ lp->start, i);
+ lp->start += i;
+ lp->len -= i;
+ continue;
+ }
+ printf("\n%jd %zu failed (%s)\n",
+ lp->start, i, strerror(errno));
+ if (errno == EINVAL) {
+ printf("read() size too big? Try with -b 131072");
+ aborting = 1;
+ }
+ if (errno == ENXIO)
+ aborting = 1;
+ new_lump(lp->start, i, lp->state + 1);
+ lp->start += i;
+ lp->len -= i;
+ }
+ if (aborting) {
+ save_worklist();
+ return (0);
+ }
+ TAILQ_REMOVE(&lumps, lp, list);
+ free(lp);
+ }
+ PRINT_STATUS(start, i, len, state, d, t);
+ save_worklist();
+ printf("\nCompleted\n");
+ return (0);
+}
diff --git a/sbin/resolvconf/Makefile b/sbin/resolvconf/Makefile
new file mode 100644
index 0000000..2d4701e
--- /dev/null
+++ b/sbin/resolvconf/Makefile
@@ -0,0 +1,37 @@
+# $FreeBSD$
+
+DIST= ${.CURDIR}/../../contrib/openresolv
+.PATH: ${DIST}
+
+SCRIPTS= resolvconf
+
+FILES= libc dnsmasq named pdnsd pdns_recursor unbound
+FILESDIR= /libexec/resolvconf
+
+MAN= resolvconf.conf.5 resolvconf.8
+
+CLEANFILES= ${SCRIPTS} ${FILES} ${MAN}
+
+SYSCONFDIR= /etc
+RCDIR= ${SYSCONFDIR}/rc.d
+VARDIR= /var/run/resolvconf
+
+# We don't assume to restart the services in /sbin. So, though
+# our service(8) is in /usr/sbin, we can use it, here.
+CMD1= \1 onestatus >/dev/null 2>\&1
+CMD2= \1 restart
+RESTARTCMD= /usr/sbin/service ${CMD1} \&\& /usr/sbin/service ${CMD2}
+
+.for f in ${SCRIPTS} ${FILES} ${MAN}
+${f}: ${f}.in
+ sed -e 's:@PREFIX@::g' \
+ -e 's:@SYSCONFDIR@:${SYSCONFDIR}:g' \
+ -e 's:@LIBEXECDIR@:${FILESDIR}:g' \
+ -e 's:@VARDIR@:${VARDIR}:g' \
+ -e 's:@RESTARTCMD \(.*\)@:${RESTARTCMD}:g' \
+ -e 's:@RCDIR@:${RCDIR}:g' \
+ -e 's: vpn : ng[0-9]*&:g' \
+ ${DIST}/$@.in > $@
+.endfor
+
+.include <bsd.prog.mk>
diff --git a/sbin/restore/Makefile b/sbin/restore/Makefile
new file mode 100644
index 0000000..c462f51
--- /dev/null
+++ b/sbin/restore/Makefile
@@ -0,0 +1,15 @@
+# @(#)Makefile 8.1 (Berkeley) 6/5/93
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../dump
+
+PROG= restore
+LINKS= ${BINDIR}/restore ${BINDIR}/rrestore
+MAN= restore.8
+MLINKS= restore.8 rrestore.8
+SRCS= main.c interactive.c restore.c dirs.c symtab.c tape.c utilities.c \
+ dumprmt.c
+WARNS?= 2
+CFLAGS+= -DRRESTORE -D_ACL_PRIVATE
+
+.include <bsd.prog.mk>
diff --git a/sbin/restore/dirs.c b/sbin/restore/dirs.c
new file mode 100644
index 0000000..7b308cd
--- /dev/null
+++ b/sbin/restore/dirs.c
@@ -0,0 +1,817 @@
+/*
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ * (c) UNIX System Laboratories, Inc.
+ * All or some portions of this file are derived from material licensed
+ * to the University of California by American Telephone and Telegraph
+ * Co. or Unix System Laboratories, Inc. and are reproduced herein with
+ * the permission of UNIX System Laboratories, Inc.
+ *
+ * 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.
+ * 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 lint
+#if 0
+static char sccsid[] = "@(#)dirs.c 8.7 (Berkeley) 5/1/95";
+#endif
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#include <ufs/ufs/dinode.h>
+#include <ufs/ufs/dir.h>
+#include <protocols/dumprestore.h>
+
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <paths.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "restore.h"
+#include "extern.h"
+
+/*
+ * Symbol table of directories read from tape.
+ */
+#define HASHSIZE 1000
+#define INOHASH(val) (val % HASHSIZE)
+struct inotab {
+ struct inotab *t_next;
+ ino_t t_ino;
+ int32_t t_seekpt;
+ int32_t t_size;
+};
+static struct inotab *inotab[HASHSIZE];
+
+/*
+ * Information retained about directories.
+ */
+struct modeinfo {
+ ino_t ino;
+ struct timespec ctimep[2];
+ struct timespec mtimep[2];
+ mode_t mode;
+ uid_t uid;
+ gid_t gid;
+ int flags;
+ int extsize;
+};
+
+/*
+ * Definitions for library routines operating on directories.
+ */
+#undef DIRBLKSIZ
+#define DIRBLKSIZ 1024
+struct rstdirdesc {
+ int dd_fd;
+ int32_t dd_loc;
+ int32_t dd_size;
+ char dd_buf[DIRBLKSIZ];
+};
+
+/*
+ * Global variables for this file.
+ */
+static long seekpt;
+static FILE *df, *mf;
+static RST_DIR *dirp;
+static char dirfile[MAXPATHLEN] = "#"; /* No file */
+static char modefile[MAXPATHLEN] = "#"; /* No file */
+static char dot[2] = "."; /* So it can be modified */
+
+static struct inotab *allocinotab(struct context *, long);
+static void flushent(void);
+static struct inotab *inotablookup(ino_t);
+static RST_DIR *opendirfile(const char *);
+static void putdir(char *, long);
+static void putdirattrs(char *, long);
+static void putent(struct direct *);
+static void rst_seekdir(RST_DIR *, long, long);
+static long rst_telldir(RST_DIR *);
+static struct direct *searchdir(ino_t, char *);
+static void fail_dirtmp(char *);
+
+/*
+ * Extract directory contents, building up a directory structure
+ * on disk for extraction by name.
+ * If genmode is requested, save mode, owner, and times for all
+ * directories on the tape.
+ */
+void
+extractdirs(int genmode)
+{
+ struct inotab *itp;
+ struct direct nulldir;
+ int i, fd;
+ const char *tmpdir;
+
+ vprintf(stdout, "Extract directories from tape\n");
+ if ((tmpdir = getenv("TMPDIR")) == NULL || tmpdir[0] == '\0')
+ tmpdir = _PATH_TMP;
+ (void) sprintf(dirfile, "%s/rstdir%jd", tmpdir, (intmax_t)dumpdate);
+ if (command != 'r' && command != 'R') {
+ (void) strcat(dirfile, "-XXXXXX");
+ fd = mkstemp(dirfile);
+ } else
+ fd = open(dirfile, O_RDWR|O_CREAT|O_EXCL, 0666);
+ if (fd == -1 || (df = fdopen(fd, "w")) == NULL) {
+ if (fd != -1)
+ close(fd);
+ warn("%s: cannot create directory database", dirfile);
+ done(1);
+ }
+ if (genmode != 0) {
+ (void) sprintf(modefile, "%s/rstmode%jd", tmpdir,
+ (intmax_t)dumpdate);
+ if (command != 'r' && command != 'R') {
+ (void) strcat(modefile, "-XXXXXX");
+ fd = mkstemp(modefile);
+ } else
+ fd = open(modefile, O_RDWR|O_CREAT|O_EXCL, 0666);
+ if (fd == -1 || (mf = fdopen(fd, "w")) == NULL) {
+ if (fd != -1)
+ close(fd);
+ warn("%s: cannot create modefile", modefile);
+ done(1);
+ }
+ }
+ nulldir.d_ino = 0;
+ nulldir.d_type = DT_DIR;
+ nulldir.d_namlen = 1;
+ (void) strcpy(nulldir.d_name, "/");
+ nulldir.d_reclen = DIRSIZ(0, &nulldir);
+ for (;;) {
+ curfile.name = "<directory file - name unknown>";
+ curfile.action = USING;
+ if (curfile.mode == 0 || (curfile.mode & IFMT) != IFDIR)
+ break;
+ itp = allocinotab(&curfile, seekpt);
+ getfile(putdir, putdirattrs, xtrnull);
+ putent(&nulldir);
+ flushent();
+ itp->t_size = seekpt - itp->t_seekpt;
+ }
+ if (fclose(df) != 0)
+ fail_dirtmp(dirfile);
+ dirp = opendirfile(dirfile);
+ if (dirp == NULL)
+ fprintf(stderr, "opendirfile: %s\n", strerror(errno));
+ if (mf != NULL && fclose(mf) != 0)
+ fail_dirtmp(modefile);
+ i = dirlookup(dot);
+ if (i == 0)
+ panic("Root directory is not on tape\n");
+}
+
+/*
+ * skip over all the directories on the tape
+ */
+void
+skipdirs(void)
+{
+
+ while (curfile.ino && (curfile.mode & IFMT) == IFDIR) {
+ skipfile();
+ }
+}
+
+/*
+ * Recursively find names and inumbers of all files in subtree
+ * pname and pass them off to be processed.
+ */
+void
+treescan(char *pname, ino_t ino, long (*todo)(char *, ino_t, int))
+{
+ struct inotab *itp;
+ struct direct *dp;
+ int namelen;
+ long bpt;
+ char locname[MAXPATHLEN];
+
+ itp = inotablookup(ino);
+ if (itp == NULL) {
+ /*
+ * Pname is name of a simple file or an unchanged directory.
+ */
+ (void) (*todo)(pname, ino, LEAF);
+ return;
+ }
+ /*
+ * Pname is a dumped directory name.
+ */
+ if ((*todo)(pname, ino, NODE) == FAIL)
+ return;
+ /*
+ * begin search through the directory
+ * skipping over "." and ".."
+ */
+ (void) strlcpy(locname, pname, sizeof(locname));
+ (void) strlcat(locname, "/", sizeof(locname));
+ namelen = strlen(locname);
+ rst_seekdir(dirp, itp->t_seekpt, itp->t_seekpt);
+ dp = rst_readdir(dirp); /* "." */
+ if (dp != NULL && strcmp(dp->d_name, ".") == 0)
+ dp = rst_readdir(dirp); /* ".." */
+ else
+ fprintf(stderr, "Warning: `.' missing from directory %s\n",
+ pname);
+ if (dp != NULL && strcmp(dp->d_name, "..") == 0)
+ dp = rst_readdir(dirp); /* first real entry */
+ else
+ fprintf(stderr, "Warning: `..' missing from directory %s\n",
+ pname);
+ bpt = rst_telldir(dirp);
+ /*
+ * a zero inode signals end of directory
+ */
+ while (dp != NULL) {
+ locname[namelen] = '\0';
+ if (namelen + dp->d_namlen >= sizeof(locname)) {
+ fprintf(stderr, "%s%s: name exceeds %zu char\n",
+ locname, dp->d_name, sizeof(locname) - 1);
+ } else {
+ (void)strlcat(locname, dp->d_name, sizeof(locname));
+ treescan(locname, dp->d_ino, todo);
+ rst_seekdir(dirp, bpt, itp->t_seekpt);
+ }
+ dp = rst_readdir(dirp);
+ bpt = rst_telldir(dirp);
+ }
+}
+
+/*
+ * Lookup a pathname which is always assumed to start from the ROOTINO.
+ */
+struct direct *
+pathsearch(const char *pathname)
+{
+ ino_t ino;
+ struct direct *dp;
+ char *path, *name, buffer[MAXPATHLEN];
+
+ strcpy(buffer, pathname);
+ path = buffer;
+ ino = ROOTINO;
+ while (*path == '/')
+ path++;
+ dp = NULL;
+ while ((name = strsep(&path, "/")) != NULL && *name != '\0') {
+ if ((dp = searchdir(ino, name)) == NULL)
+ return (NULL);
+ ino = dp->d_ino;
+ }
+ return (dp);
+}
+
+/*
+ * Lookup the requested name in directory inum.
+ * Return its inode number if found, zero if it does not exist.
+ */
+static struct direct *
+searchdir(ino_t inum, char *name)
+{
+ struct direct *dp;
+ struct inotab *itp;
+ int len;
+
+ itp = inotablookup(inum);
+ if (itp == NULL)
+ return (NULL);
+ rst_seekdir(dirp, itp->t_seekpt, itp->t_seekpt);
+ len = strlen(name);
+ do {
+ dp = rst_readdir(dirp);
+ if (dp == NULL)
+ return (NULL);
+ } while (dp->d_namlen != len || strncmp(dp->d_name, name, len) != 0);
+ return (dp);
+}
+
+/*
+ * Put the directory entries in the directory file
+ */
+static void
+putdir(char *buf, long size)
+{
+ struct direct *dp;
+ long loc, i;
+
+ for (loc = 0; loc < size; ) {
+ dp = (struct direct *)(buf + loc);
+ if (Bcvt)
+ swabst((u_char *)"ls", (u_char *) dp);
+ if (oldinofmt && dp->d_ino != 0) {
+#if BYTE_ORDER == BIG_ENDIAN
+ if (Bcvt)
+ dp->d_namlen = dp->d_type;
+#else
+ if (!Bcvt && dp->d_namlen == 0)
+ dp->d_namlen = dp->d_type;
+#endif
+ dp->d_type = DT_UNKNOWN;
+ }
+ i = DIRBLKSIZ - (loc & (DIRBLKSIZ - 1));
+ if ((dp->d_reclen & 0x3) != 0 ||
+ dp->d_reclen > i ||
+ dp->d_reclen < DIRSIZ(0, dp)
+#if NAME_MAX < 255
+ || dp->d_namlen > NAME_MAX
+#endif
+ ) {
+ vprintf(stdout, "Mangled directory: ");
+ if ((dp->d_reclen & 0x3) != 0)
+ vprintf(stdout,
+ "reclen not multiple of 4 ");
+ if (dp->d_reclen < DIRSIZ(0, dp))
+ vprintf(stdout,
+ "reclen less than DIRSIZ (%d < %zu) ",
+ dp->d_reclen, DIRSIZ(0, dp));
+#if NAME_MAX < 255
+ if (dp->d_namlen > NAME_MAX)
+ vprintf(stdout,
+ "reclen name too big (%d > %d) ",
+ dp->d_namlen, NAME_MAX);
+#endif
+ vprintf(stdout, "\n");
+ loc += i;
+ continue;
+ }
+ loc += dp->d_reclen;
+ if (dp->d_ino != 0) {
+ putent(dp);
+ }
+ }
+}
+
+/*
+ * These variables are "local" to the following two functions.
+ */
+char dirbuf[DIRBLKSIZ];
+long dirloc = 0;
+long prev = 0;
+
+/*
+ * add a new directory entry to a file.
+ */
+static void
+putent(struct direct *dp)
+{
+ dp->d_reclen = DIRSIZ(0, dp);
+ if (dirloc + dp->d_reclen > DIRBLKSIZ) {
+ ((struct direct *)(dirbuf + prev))->d_reclen =
+ DIRBLKSIZ - prev;
+ if (fwrite(dirbuf, DIRBLKSIZ, 1, df) != 1)
+ fail_dirtmp(dirfile);
+ dirloc = 0;
+ }
+ memmove(dirbuf + dirloc, dp, (long)dp->d_reclen);
+ prev = dirloc;
+ dirloc += dp->d_reclen;
+}
+
+/*
+ * flush out a directory that is finished.
+ */
+static void
+flushent(void)
+{
+ ((struct direct *)(dirbuf + prev))->d_reclen = DIRBLKSIZ - prev;
+ if (fwrite(dirbuf, (int)dirloc, 1, df) != 1)
+ fail_dirtmp(dirfile);
+ seekpt = ftell(df);
+ dirloc = 0;
+}
+
+/*
+ * Save extended attributes for a directory entry to a file.
+ */
+static void
+putdirattrs(char *buf, long size)
+{
+
+ if (mf != NULL && fwrite(buf, size, 1, mf) != 1)
+ fail_dirtmp(modefile);
+}
+
+/*
+ * Seek to an entry in a directory.
+ * Only values returned by rst_telldir should be passed to rst_seekdir.
+ * This routine handles many directories in a single file.
+ * It takes the base of the directory in the file, plus
+ * the desired seek offset into it.
+ */
+static void
+rst_seekdir(RST_DIR *dirp, long loc, long base)
+{
+
+ if (loc == rst_telldir(dirp))
+ return;
+ loc -= base;
+ if (loc < 0)
+ fprintf(stderr, "bad seek pointer to rst_seekdir %ld\n", loc);
+ (void) lseek(dirp->dd_fd, base + (loc & ~(DIRBLKSIZ - 1)), SEEK_SET);
+ dirp->dd_loc = loc & (DIRBLKSIZ - 1);
+ if (dirp->dd_loc != 0)
+ dirp->dd_size = read(dirp->dd_fd, dirp->dd_buf, DIRBLKSIZ);
+}
+
+/*
+ * get next entry in a directory.
+ */
+struct direct *
+rst_readdir(RST_DIR *dirp)
+{
+ struct direct *dp;
+
+ for (;;) {
+ if (dirp->dd_loc == 0) {
+ dirp->dd_size = read(dirp->dd_fd, dirp->dd_buf,
+ DIRBLKSIZ);
+ if (dirp->dd_size <= 0) {
+ dprintf(stderr, "error reading directory\n");
+ return (NULL);
+ }
+ }
+ if (dirp->dd_loc >= dirp->dd_size) {
+ dirp->dd_loc = 0;
+ continue;
+ }
+ dp = (struct direct *)(dirp->dd_buf + dirp->dd_loc);
+ if (dp->d_reclen == 0 ||
+ dp->d_reclen > DIRBLKSIZ + 1 - dirp->dd_loc) {
+ dprintf(stderr, "corrupted directory: bad reclen %d\n",
+ dp->d_reclen);
+ return (NULL);
+ }
+ dirp->dd_loc += dp->d_reclen;
+ if (dp->d_ino == 0 && strcmp(dp->d_name, "/") == 0)
+ return (NULL);
+ if (dp->d_ino >= maxino) {
+ dprintf(stderr, "corrupted directory: bad inum %d\n",
+ dp->d_ino);
+ continue;
+ }
+ return (dp);
+ }
+}
+
+/*
+ * Simulate the opening of a directory
+ */
+void *
+rst_opendir(const char *name)
+{
+ struct inotab *itp;
+ RST_DIR *dirp;
+ ino_t ino;
+
+ if ((ino = dirlookup(name)) > 0 &&
+ (itp = inotablookup(ino)) != NULL) {
+ dirp = opendirfile(dirfile);
+ rst_seekdir(dirp, itp->t_seekpt, itp->t_seekpt);
+ return (dirp);
+ }
+ return (NULL);
+}
+
+/*
+ * In our case, there is nothing to do when closing a directory.
+ */
+void
+rst_closedir(void *arg)
+{
+ RST_DIR *dirp;
+
+ dirp = arg;
+ (void)close(dirp->dd_fd);
+ free(dirp);
+ return;
+}
+
+/*
+ * Simulate finding the current offset in the directory.
+ */
+static long
+rst_telldir(RST_DIR *dirp)
+{
+ return ((long)lseek(dirp->dd_fd,
+ (off_t)0, SEEK_CUR) - dirp->dd_size + dirp->dd_loc);
+}
+
+/*
+ * Open a directory file.
+ */
+static RST_DIR *
+opendirfile(const char *name)
+{
+ RST_DIR *dirp;
+ int fd;
+
+ if ((fd = open(name, O_RDONLY)) == -1)
+ return (NULL);
+ if ((dirp = malloc(sizeof(RST_DIR))) == NULL) {
+ (void)close(fd);
+ return (NULL);
+ }
+ dirp->dd_fd = fd;
+ dirp->dd_loc = 0;
+ return (dirp);
+}
+
+/*
+ * Set the mode, owner, and times for all new or changed directories
+ */
+void
+setdirmodes(int flags)
+{
+ FILE *mf;
+ struct modeinfo node;
+ struct entry *ep;
+ char *cp, *buf;
+ const char *tmpdir;
+ int bufsize;
+ uid_t myuid;
+
+ vprintf(stdout, "Set directory mode, owner, and times.\n");
+ if ((tmpdir = getenv("TMPDIR")) == NULL || tmpdir[0] == '\0')
+ tmpdir = _PATH_TMP;
+ if (command == 'r' || command == 'R')
+ (void) sprintf(modefile, "%s/rstmode%jd", tmpdir,
+ (intmax_t)dumpdate);
+ if (modefile[0] == '#') {
+ panic("modefile not defined\n");
+ fprintf(stderr, "directory mode, owner, and times not set\n");
+ return;
+ }
+ mf = fopen(modefile, "r");
+ if (mf == NULL) {
+ fprintf(stderr, "fopen: %s\n", strerror(errno));
+ fprintf(stderr, "cannot open mode file %s\n", modefile);
+ fprintf(stderr, "directory mode, owner, and times not set\n");
+ return;
+ }
+ clearerr(mf);
+ bufsize = 0;
+ myuid = getuid();
+ for (;;) {
+ (void) fread((char *)&node, 1, sizeof(struct modeinfo), mf);
+ if (ferror(mf)) {
+ warn("%s: cannot read modefile.", modefile);
+ fprintf(stderr, "Mode, owner, and times not set.\n");
+ break;
+ }
+ if (feof(mf))
+ break;
+ if (node.extsize > 0) {
+ if (bufsize < node.extsize) {
+ if (bufsize > 0)
+ free(buf);
+ if ((buf = malloc(node.extsize)) != 0) {
+ bufsize = node.extsize;
+ } else {
+ bufsize = 0;
+ }
+ }
+ if (bufsize >= node.extsize) {
+ (void) fread(buf, 1, node.extsize, mf);
+ if (ferror(mf)) {
+ warn("%s: cannot read modefile.",
+ modefile);
+ fprintf(stderr, "Not all external ");
+ fprintf(stderr, "attributes set.\n");
+ break;
+ }
+ } else {
+ (void) fseek(mf, node.extsize, SEEK_CUR);
+ if (ferror(mf)) {
+ warn("%s: cannot seek in modefile.",
+ modefile);
+ fprintf(stderr, "Not all directory ");
+ fprintf(stderr, "attributes set.\n");
+ break;
+ }
+ }
+ }
+ ep = lookupino(node.ino);
+ if (command == 'i' || command == 'x') {
+ if (ep == NULL)
+ continue;
+ if ((flags & FORCE) == 0 && ep->e_flags & EXISTED) {
+ ep->e_flags &= ~NEW;
+ continue;
+ }
+ if (node.ino == ROOTINO &&
+ reply("set owner/mode for '.'") == FAIL)
+ continue;
+ }
+ if (ep == NULL) {
+ panic("cannot find directory inode %ju\n",
+ (uintmax_t)node.ino);
+ continue;
+ }
+ cp = myname(ep);
+ if (!Nflag) {
+ if (node.extsize > 0) {
+ if (bufsize >= node.extsize) {
+ set_extattr_file(cp, buf, node.extsize);
+ } else {
+ fprintf(stderr, "Cannot restore %s%s\n",
+ "extended attributes for ", cp);
+ }
+ }
+ if (myuid != 0)
+ (void) chown(cp, myuid, node.gid);
+ else
+ (void) chown(cp, node.uid, node.gid);
+ (void) chmod(cp, node.mode);
+ utimensat(AT_FDCWD, cp, node.ctimep, 0);
+ utimensat(AT_FDCWD, cp, node.mtimep, 0);
+ (void) chflags(cp, node.flags);
+ }
+ ep->e_flags &= ~NEW;
+ }
+ if (bufsize > 0)
+ free(buf);
+ (void) fclose(mf);
+}
+
+/*
+ * Generate a literal copy of a directory.
+ */
+int
+genliteraldir(char *name, ino_t ino)
+{
+ struct inotab *itp;
+ int ofile, dp, i, size;
+ char buf[BUFSIZ];
+
+ itp = inotablookup(ino);
+ if (itp == NULL)
+ panic("Cannot find directory inode %ju named %s\n",
+ (uintmax_t)ino, name);
+ if ((ofile = open(name, O_WRONLY | O_CREAT | O_TRUNC, 0666)) < 0) {
+ fprintf(stderr, "%s: ", name);
+ (void) fflush(stderr);
+ fprintf(stderr, "cannot create file: %s\n", strerror(errno));
+ return (FAIL);
+ }
+ rst_seekdir(dirp, itp->t_seekpt, itp->t_seekpt);
+ dp = dup(dirp->dd_fd);
+ for (i = itp->t_size; i > 0; i -= BUFSIZ) {
+ size = i < BUFSIZ ? i : BUFSIZ;
+ if (read(dp, buf, (int) size) == -1) {
+ fprintf(stderr,
+ "write error extracting inode %ju, name %s\n",
+ (uintmax_t)curfile.ino, curfile.name);
+ fprintf(stderr, "read: %s\n", strerror(errno));
+ done(1);
+ }
+ if (!Nflag && write(ofile, buf, (int) size) == -1) {
+ fprintf(stderr,
+ "write error extracting inode %ju, name %s\n",
+ (uintmax_t)curfile.ino, curfile.name);
+ fprintf(stderr, "write: %s\n", strerror(errno));
+ done(1);
+ }
+ }
+ (void) close(dp);
+ (void) close(ofile);
+ return (GOOD);
+}
+
+/*
+ * Determine the type of an inode
+ */
+int
+inodetype(ino_t ino)
+{
+ struct inotab *itp;
+
+ itp = inotablookup(ino);
+ if (itp == NULL)
+ return (LEAF);
+ return (NODE);
+}
+
+/*
+ * Allocate and initialize a directory inode entry.
+ * If requested, save its pertinent mode, owner, and time info.
+ */
+static struct inotab *
+allocinotab(struct context *ctxp, long seekpt)
+{
+ struct inotab *itp;
+ struct modeinfo node;
+
+ itp = calloc(1, sizeof(struct inotab));
+ if (itp == NULL)
+ panic("no memory for directory table\n");
+ itp->t_next = inotab[INOHASH(ctxp->ino)];
+ inotab[INOHASH(ctxp->ino)] = itp;
+ itp->t_ino = ctxp->ino;
+ itp->t_seekpt = seekpt;
+ if (mf == NULL)
+ return (itp);
+ node.ino = ctxp->ino;
+ node.mtimep[0].tv_sec = ctxp->atime_sec;
+ node.mtimep[0].tv_nsec = ctxp->atime_nsec;
+ node.mtimep[1].tv_sec = ctxp->mtime_sec;
+ node.mtimep[1].tv_nsec = ctxp->mtime_nsec;
+ node.ctimep[0].tv_sec = ctxp->atime_sec;
+ node.ctimep[0].tv_nsec = ctxp->atime_nsec;
+ node.ctimep[1].tv_sec = ctxp->birthtime_sec;
+ node.ctimep[1].tv_nsec = ctxp->birthtime_nsec;
+ node.extsize = ctxp->extsize;
+ node.mode = ctxp->mode;
+ node.flags = ctxp->file_flags;
+ node.uid = ctxp->uid;
+ node.gid = ctxp->gid;
+ if (fwrite((char *)&node, sizeof(struct modeinfo), 1, mf) != 1)
+ fail_dirtmp(modefile);
+ return (itp);
+}
+
+/*
+ * Look up an inode in the table of directories
+ */
+static struct inotab *
+inotablookup(ino_t ino)
+{
+ struct inotab *itp;
+
+ for (itp = inotab[INOHASH(ino)]; itp != NULL; itp = itp->t_next)
+ if (itp->t_ino == ino)
+ return (itp);
+ return (NULL);
+}
+
+/*
+ * Clean up and exit
+ */
+void
+done(int exitcode)
+{
+
+ closemt();
+ if (modefile[0] != '#') {
+ (void) truncate(modefile, 0);
+ (void) unlink(modefile);
+ }
+ if (dirfile[0] != '#') {
+ (void) truncate(dirfile, 0);
+ (void) unlink(dirfile);
+ }
+ exit(exitcode);
+}
+
+/*
+ * Print out information about the failure to save directory,
+ * extended attribute, and mode information.
+ */
+static void
+fail_dirtmp(char *filename)
+{
+ const char *tmpdir;
+
+ warn("%s: cannot write directory database", filename);
+ if (errno == ENOSPC) {
+ if ((tmpdir = getenv("TMPDIR")) == NULL || tmpdir[0] == '\0')
+ tmpdir = _PATH_TMP;
+ fprintf(stderr, "Try making space in %s, %s\n%s\n", tmpdir,
+ "or set environment variable TMPDIR",
+ "to an alternate location with more disk space.");
+ }
+ done(1);
+}
diff --git a/sbin/restore/extern.h b/sbin/restore/extern.h
new file mode 100644
index 0000000..7b744e2
--- /dev/null
+++ b/sbin/restore/extern.h
@@ -0,0 +1,109 @@
+/*-
+ * 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.
+ * 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.
+ *
+ * @(#)extern.h 8.2 (Berkeley) 1/7/94
+ * $FreeBSD$
+ */
+
+struct entry *addentry(char *, ino_t, int);
+long addfile(char *, ino_t, int);
+int addwhiteout(char *);
+void badentry(struct entry *, char *);
+void canon(char *, char *, size_t);
+void checkrestore(void);
+void closemt(void);
+void createfiles(void);
+void createleaves(char *);
+void createlinks(void);
+long deletefile(char *, ino_t, int);
+void deleteino(ino_t);
+void delwhiteout(struct entry *);
+ino_t dirlookup(const char *);
+void done(int) __dead2;
+void dumpsymtable(char *, long);
+void extractdirs(int);
+int extractfile(char *);
+void findunreflinks(void);
+char *flagvalues(struct entry *);
+void freeentry(struct entry *);
+void freename(char *);
+int genliteraldir(char *, ino_t);
+char *gentempname(struct entry *);
+void getfile(void (*)(char *, long), void (*)(char *, long),
+ void (*)(char *, long));
+void getvol(long);
+void initsymtable(char *);
+int inodetype(ino_t);
+int linkit(char *, char *, int);
+struct entry *lookupino(ino_t);
+struct entry *lookupname(char *);
+long listfile(char *, ino_t, int);
+ino_t lowerbnd(ino_t);
+void mktempname(struct entry *);
+void moveentry(struct entry *, char *);
+void msg(const char *, ...) __printflike(1, 2);
+char *myname(struct entry *);
+void newnode(struct entry *);
+void newtapebuf(long);
+long nodeupdates(char *, ino_t, int);
+void onintr(int);
+void panic(const char *, ...) __printflike(1, 2);
+void pathcheck(char *);
+struct direct *pathsearch(const char *);
+void printdumpinfo(void);
+void removeleaf(struct entry *);
+void removenode(struct entry *);
+void removeoldleaves(void);
+void removeoldnodes(void);
+void renameit(char *, char *);
+int reply(char *);
+void *rst_opendir(const char *);
+struct direct *rst_readdir(RST_DIR *);
+void rst_closedir(void *);
+void runcmdshell(void);
+char *savename(char *);
+void set_extattr_file(char *, void *, int);
+void setdirmodes(int);
+void setinput(char *, int);
+void setup(void);
+void skipdirs(void);
+void skipfile(void);
+void skipmaps(void);
+void swabst(u_char *, u_char *);
+void treescan(char *, ino_t, long (*)(char *, ino_t, int));
+ino_t upperbnd(ino_t);
+long verifyfile(char *, ino_t, int);
+void xtrnull(char *, long);
+
+/* From ../dump/dumprmt.c */
+void rmtclose(void);
+int rmthost(char *);
+int rmtioctl(int, int);
+int rmtopen(char *, int);
+int rmtread(char *, int);
+int rmtseek(int, int);
diff --git a/sbin/restore/interactive.c b/sbin/restore/interactive.c
new file mode 100644
index 0000000..05d1a58
--- /dev/null
+++ b/sbin/restore/interactive.c
@@ -0,0 +1,770 @@
+/*
+ * Copyright (c) 1985, 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.
+ * 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 lint
+#if 0
+static char sccsid[] = "@(#)interactive.c 8.5 (Berkeley) 5/1/95";
+#endif
+#endif /* not lint */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include <ufs/ufs/dinode.h>
+#include <ufs/ufs/dir.h>
+#include <protocols/dumprestore.h>
+
+#include <ctype.h>
+#include <glob.h>
+#include <limits.h>
+#include <setjmp.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "restore.h"
+#include "extern.h"
+
+#define round(a, b) (((a) + (b) - 1) / (b) * (b))
+
+/*
+ * Things to handle interruptions.
+ */
+static int runshell;
+static jmp_buf reset;
+static char *nextarg = NULL;
+
+/*
+ * Structure and routines associated with listing directories.
+ */
+struct afile {
+ ino_t fnum; /* inode number of file */
+ char *fname; /* file name */
+ short len; /* name length */
+ char prefix; /* prefix character */
+ char postfix; /* postfix character */
+};
+struct arglist {
+ int freeglob; /* glob structure needs to be freed */
+ int argcnt; /* next globbed argument to return */
+ glob_t glob; /* globbing information */
+ char *cmd; /* the current command */
+};
+
+static char *copynext(char *, char *);
+static int fcmp(const void *, const void *);
+static void formatf(struct afile *, int);
+static void getcmd(char *, char *, char *, size_t, struct arglist *);
+struct dirent *glob_readdir(void *);
+static int glob_stat(const char *, struct stat *);
+static void mkentry(char *, struct direct *, struct afile *);
+static void printlist(char *, char *);
+
+/*
+ * Read and execute commands from the terminal.
+ */
+void
+runcmdshell(void)
+{
+ struct entry *np;
+ ino_t ino;
+ struct arglist arglist;
+ char curdir[MAXPATHLEN];
+ char name[MAXPATHLEN];
+ char cmd[BUFSIZ];
+
+ arglist.freeglob = 0;
+ arglist.argcnt = 0;
+ arglist.glob.gl_flags = GLOB_ALTDIRFUNC;
+ arglist.glob.gl_opendir = rst_opendir;
+ arglist.glob.gl_readdir = glob_readdir;
+ arglist.glob.gl_closedir = rst_closedir;
+ arglist.glob.gl_lstat = glob_stat;
+ arglist.glob.gl_stat = glob_stat;
+ canon("/", curdir, sizeof(curdir));
+loop:
+ if (setjmp(reset) != 0) {
+ if (arglist.freeglob != 0) {
+ arglist.freeglob = 0;
+ arglist.argcnt = 0;
+ globfree(&arglist.glob);
+ }
+ nextarg = NULL;
+ volno = 0;
+ }
+ runshell = 1;
+ getcmd(curdir, cmd, name, sizeof(name), &arglist);
+ switch (cmd[0]) {
+ /*
+ * Add elements to the extraction list.
+ */
+ case 'a':
+ if (strncmp(cmd, "add", strlen(cmd)) != 0)
+ goto bad;
+ ino = dirlookup(name);
+ if (ino == 0)
+ break;
+ if (mflag)
+ pathcheck(name);
+ treescan(name, ino, addfile);
+ break;
+ /*
+ * Change working directory.
+ */
+ case 'c':
+ if (strncmp(cmd, "cd", strlen(cmd)) != 0)
+ goto bad;
+ ino = dirlookup(name);
+ if (ino == 0)
+ break;
+ if (inodetype(ino) == LEAF) {
+ fprintf(stderr, "%s: not a directory\n", name);
+ break;
+ }
+ (void) strcpy(curdir, name);
+ break;
+ /*
+ * Delete elements from the extraction list.
+ */
+ case 'd':
+ if (strncmp(cmd, "delete", strlen(cmd)) != 0)
+ goto bad;
+ np = lookupname(name);
+ if (np == NULL || (np->e_flags & NEW) == 0) {
+ fprintf(stderr, "%s: not on extraction list\n", name);
+ break;
+ }
+ treescan(name, np->e_ino, deletefile);
+ break;
+ /*
+ * Extract the requested list.
+ */
+ case 'e':
+ if (strncmp(cmd, "extract", strlen(cmd)) != 0)
+ goto bad;
+ createfiles();
+ createlinks();
+ setdirmodes(0);
+ if (dflag)
+ checkrestore();
+ volno = 0;
+ break;
+ /*
+ * List available commands.
+ */
+ case 'h':
+ if (strncmp(cmd, "help", strlen(cmd)) != 0)
+ goto bad;
+ case '?':
+ fprintf(stderr, "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s",
+ "Available commands are:\n",
+ "\tls [arg] - list directory\n",
+ "\tcd arg - change directory\n",
+ "\tpwd - print current directory\n",
+ "\tadd [arg] - add `arg' to list of",
+ " files to be extracted\n",
+ "\tdelete [arg] - delete `arg' from",
+ " list of files to be extracted\n",
+ "\textract - extract requested files\n",
+ "\tsetmodes - set modes of requested directories\n",
+ "\tquit - immediately exit program\n",
+ "\twhat - list dump header information\n",
+ "\tverbose - toggle verbose flag",
+ " (useful with ``ls'')\n",
+ "\thelp or `?' - print this list\n",
+ "If no `arg' is supplied, the current",
+ " directory is used\n");
+ break;
+ /*
+ * List a directory.
+ */
+ case 'l':
+ if (strncmp(cmd, "ls", strlen(cmd)) != 0)
+ goto bad;
+ printlist(name, curdir);
+ break;
+ /*
+ * Print current directory.
+ */
+ case 'p':
+ if (strncmp(cmd, "pwd", strlen(cmd)) != 0)
+ goto bad;
+ if (curdir[1] == '\0')
+ fprintf(stderr, "/\n");
+ else
+ fprintf(stderr, "%s\n", &curdir[1]);
+ break;
+ /*
+ * Quit.
+ */
+ case 'q':
+ if (strncmp(cmd, "quit", strlen(cmd)) != 0)
+ goto bad;
+ return;
+ case 'x':
+ if (strncmp(cmd, "xit", strlen(cmd)) != 0)
+ goto bad;
+ return;
+ /*
+ * Toggle verbose mode.
+ */
+ case 'v':
+ if (strncmp(cmd, "verbose", strlen(cmd)) != 0)
+ goto bad;
+ if (vflag) {
+ fprintf(stderr, "verbose mode off\n");
+ vflag = 0;
+ break;
+ }
+ fprintf(stderr, "verbose mode on\n");
+ vflag++;
+ break;
+ /*
+ * Just restore requested directory modes.
+ */
+ case 's':
+ if (strncmp(cmd, "setmodes", strlen(cmd)) != 0)
+ goto bad;
+ setdirmodes(FORCE);
+ break;
+ /*
+ * Print out dump header information.
+ */
+ case 'w':
+ if (strncmp(cmd, "what", strlen(cmd)) != 0)
+ goto bad;
+ printdumpinfo();
+ break;
+ /*
+ * Turn on debugging.
+ */
+ case 'D':
+ if (strncmp(cmd, "Debug", strlen(cmd)) != 0)
+ goto bad;
+ if (dflag) {
+ fprintf(stderr, "debugging mode off\n");
+ dflag = 0;
+ break;
+ }
+ fprintf(stderr, "debugging mode on\n");
+ dflag++;
+ break;
+ /*
+ * Unknown command.
+ */
+ default:
+ bad:
+ fprintf(stderr, "%s: unknown command; type ? for help\n", cmd);
+ break;
+ }
+ goto loop;
+}
+
+/*
+ * Read and parse an interactive command.
+ * The first word on the line is assigned to "cmd". If
+ * there are no arguments on the command line, then "curdir"
+ * is returned as the argument. If there are arguments
+ * on the line they are returned one at a time on each
+ * successive call to getcmd. Each argument is first assigned
+ * to "name". If it does not start with "/" the pathname in
+ * "curdir" is prepended to it. Finally "canon" is called to
+ * eliminate any embedded ".." components.
+ */
+static void
+getcmd(char *curdir, char *cmd, char *name, size_t size, struct arglist *ap)
+{
+ char *cp;
+ static char input[BUFSIZ];
+ char output[BUFSIZ];
+# define rawname input /* save space by reusing input buffer */
+
+ /*
+ * Check to see if still processing arguments.
+ */
+ if (ap->argcnt > 0)
+ goto retnext;
+ if (nextarg != NULL)
+ goto getnext;
+ /*
+ * Read a command line and trim off trailing white space.
+ */
+ do {
+ fprintf(stderr, "restore > ");
+ (void) fflush(stderr);
+ if (fgets(input, BUFSIZ, terminal) == NULL) {
+ strcpy(cmd, "quit");
+ return;
+ }
+ } while (input[0] == '\n');
+ for (cp = &input[strlen(input) - 2]; *cp == ' ' || *cp == '\t'; cp--)
+ /* trim off trailing white space and newline */;
+ *++cp = '\0';
+ /*
+ * Copy the command into "cmd".
+ */
+ cp = copynext(input, cmd);
+ ap->cmd = cmd;
+ /*
+ * If no argument, use curdir as the default.
+ */
+ if (*cp == '\0') {
+ (void) strncpy(name, curdir, size);
+ name[size - 1] = '\0';
+ return;
+ }
+ nextarg = cp;
+ /*
+ * Find the next argument.
+ */
+getnext:
+ cp = copynext(nextarg, rawname);
+ if (*cp == '\0')
+ nextarg = NULL;
+ else
+ nextarg = cp;
+ /*
+ * If it is an absolute pathname, canonicalize it and return it.
+ */
+ if (rawname[0] == '/') {
+ canon(rawname, name, size);
+ } else {
+ /*
+ * For relative pathnames, prepend the current directory to
+ * it then canonicalize and return it.
+ */
+ snprintf(output, sizeof(output), "%s/%s", curdir, rawname);
+ canon(output, name, size);
+ }
+ switch (glob(name, GLOB_ALTDIRFUNC, NULL, &ap->glob)) {
+ case GLOB_NOSPACE:
+ fprintf(stderr, "%s: out of memory\n", ap->cmd);
+ break;
+ case GLOB_NOMATCH:
+ fprintf(stderr, "%s %s: no such file or directory\n", ap->cmd, name);
+ break;
+ }
+ if (ap->glob.gl_pathc == 0)
+ return;
+ ap->freeglob = 1;
+ ap->argcnt = ap->glob.gl_pathc;
+
+retnext:
+ strncpy(name, ap->glob.gl_pathv[ap->glob.gl_pathc - ap->argcnt], size);
+ name[size - 1] = '\0';
+ if (--ap->argcnt == 0) {
+ ap->freeglob = 0;
+ globfree(&ap->glob);
+ }
+# undef rawname
+}
+
+/*
+ * Strip off the next token of the input.
+ */
+static char *
+copynext(char *input, char *output)
+{
+ char *cp, *bp;
+ char quote;
+
+ for (cp = input; *cp == ' ' || *cp == '\t'; cp++)
+ /* skip to argument */;
+ bp = output;
+ while (*cp != ' ' && *cp != '\t' && *cp != '\0') {
+ /*
+ * Handle back slashes.
+ */
+ if (*cp == '\\') {
+ if (*++cp == '\0') {
+ fprintf(stderr,
+ "command lines cannot be continued\n");
+ continue;
+ }
+ *bp++ = *cp++;
+ continue;
+ }
+ /*
+ * The usual unquoted case.
+ */
+ if (*cp != '\'' && *cp != '"') {
+ *bp++ = *cp++;
+ continue;
+ }
+ /*
+ * Handle single and double quotes.
+ */
+ quote = *cp++;
+ while (*cp != quote && *cp != '\0')
+ *bp++ = *cp++;
+ if (*cp++ == '\0') {
+ fprintf(stderr, "missing %c\n", quote);
+ cp--;
+ continue;
+ }
+ }
+ *bp = '\0';
+ return (cp);
+}
+
+/*
+ * Canonicalize file names to always start with ``./'' and
+ * remove any embedded "." and ".." components.
+ */
+void
+canon(char *rawname, char *canonname, size_t len)
+{
+ char *cp, *np;
+
+ if (strcmp(rawname, ".") == 0 || strncmp(rawname, "./", 2) == 0)
+ (void) strcpy(canonname, "");
+ else if (rawname[0] == '/')
+ (void) strcpy(canonname, ".");
+ else
+ (void) strcpy(canonname, "./");
+ if (strlen(canonname) + strlen(rawname) >= len) {
+ fprintf(stderr, "canonname: not enough buffer space\n");
+ done(1);
+ }
+
+ (void) strcat(canonname, rawname);
+ /*
+ * Eliminate multiple and trailing '/'s
+ */
+ for (cp = np = canonname; *np != '\0'; cp++) {
+ *cp = *np++;
+ while (*cp == '/' && *np == '/')
+ np++;
+ }
+ *cp = '\0';
+ if (*--cp == '/')
+ *cp = '\0';
+ /*
+ * Eliminate extraneous "." and ".." from pathnames.
+ */
+ for (np = canonname; *np != '\0'; ) {
+ np++;
+ cp = np;
+ while (*np != '/' && *np != '\0')
+ np++;
+ if (np - cp == 1 && *cp == '.') {
+ cp--;
+ (void) strcpy(cp, np);
+ np = cp;
+ }
+ if (np - cp == 2 && strncmp(cp, "..", 2) == 0) {
+ cp--;
+ while (cp > &canonname[1] && *--cp != '/')
+ /* find beginning of name */;
+ (void) strcpy(cp, np);
+ np = cp;
+ }
+ }
+}
+
+/*
+ * Do an "ls" style listing of a directory
+ */
+static void
+printlist(char *name, char *basename)
+{
+ struct afile *fp, *list, *listp;
+ struct direct *dp;
+ struct afile single;
+ RST_DIR *dirp;
+ int entries, len, namelen;
+ char locname[MAXPATHLEN];
+
+ dp = pathsearch(name);
+ if (dp == NULL || (!dflag && TSTINO(dp->d_ino, dumpmap) == 0) ||
+ (!vflag && dp->d_ino == WINO))
+ return;
+ if ((dirp = rst_opendir(name)) == NULL) {
+ entries = 1;
+ list = &single;
+ mkentry(name, dp, list);
+ len = strlen(basename) + 1;
+ if (strlen(name) - len > single.len) {
+ freename(single.fname);
+ single.fname = savename(&name[len]);
+ single.len = strlen(single.fname);
+ }
+ } else {
+ entries = 0;
+ while ((dp = rst_readdir(dirp)))
+ entries++;
+ rst_closedir(dirp);
+ list = (struct afile *)malloc(entries * sizeof(struct afile));
+ if (list == NULL) {
+ fprintf(stderr, "ls: out of memory\n");
+ return;
+ }
+ if ((dirp = rst_opendir(name)) == NULL)
+ panic("directory reopen failed\n");
+ fprintf(stderr, "%s:\n", name);
+ entries = 0;
+ listp = list;
+ (void)strlcpy(locname, name, MAXPATHLEN);
+ (void)strlcat(locname, "/", MAXPATHLEN);
+ namelen = strlen(locname);
+ while ((dp = rst_readdir(dirp))) {
+ if (dp == NULL)
+ break;
+ if (!dflag && TSTINO(dp->d_ino, dumpmap) == 0)
+ continue;
+ if (!vflag && (dp->d_ino == WINO ||
+ strcmp(dp->d_name, ".") == 0 ||
+ strcmp(dp->d_name, "..") == 0))
+ continue;
+ locname[namelen] = '\0';
+ if (namelen + dp->d_namlen >= MAXPATHLEN) {
+ fprintf(stderr, "%s%s: name exceeds %d char\n",
+ locname, dp->d_name, MAXPATHLEN);
+ } else {
+ (void)strlcat(locname, dp->d_name, MAXPATHLEN);
+ mkentry(locname, dp, listp++);
+ entries++;
+ }
+ }
+ rst_closedir(dirp);
+ if (entries == 0) {
+ fprintf(stderr, "\n");
+ free(list);
+ return;
+ }
+ qsort((char *)list, entries, sizeof(struct afile), fcmp);
+ }
+ formatf(list, entries);
+ if (dirp != NULL) {
+ for (fp = listp - 1; fp >= list; fp--)
+ freename(fp->fname);
+ fprintf(stderr, "\n");
+ free(list);
+ }
+}
+
+/*
+ * Read the contents of a directory.
+ */
+static void
+mkentry(char *name, struct direct *dp, struct afile *fp)
+{
+ char *cp;
+ struct entry *np;
+
+ fp->fnum = dp->d_ino;
+ fp->fname = savename(dp->d_name);
+ for (cp = fp->fname; *cp; cp++)
+ if (!vflag && !isprint((unsigned char)*cp))
+ *cp = '?';
+ fp->len = cp - fp->fname;
+ if (dflag && TSTINO(fp->fnum, dumpmap) == 0)
+ fp->prefix = '^';
+ else if ((np = lookupname(name)) != NULL && (np->e_flags & NEW))
+ fp->prefix = '*';
+ else
+ fp->prefix = ' ';
+ switch(dp->d_type) {
+
+ default:
+ fprintf(stderr, "Warning: undefined file type %d\n",
+ dp->d_type);
+ /* FALLTHROUGH */
+ case DT_REG:
+ fp->postfix = ' ';
+ break;
+
+ case DT_LNK:
+ fp->postfix = '@';
+ break;
+
+ case DT_FIFO:
+ case DT_SOCK:
+ fp->postfix = '=';
+ break;
+
+ case DT_CHR:
+ case DT_BLK:
+ fp->postfix = '#';
+ break;
+
+ case DT_WHT:
+ fp->postfix = '%';
+ break;
+
+ case DT_UNKNOWN:
+ case DT_DIR:
+ if (inodetype(dp->d_ino) == NODE)
+ fp->postfix = '/';
+ else
+ fp->postfix = ' ';
+ break;
+ }
+ return;
+}
+
+/*
+ * Print out a pretty listing of a directory
+ */
+static void
+formatf(struct afile *list, int nentry)
+{
+ struct afile *fp, *endlist;
+ int width, bigino, haveprefix, havepostfix;
+ int i, j, w, precision, columns, lines;
+
+ width = 0;
+ haveprefix = 0;
+ havepostfix = 0;
+ bigino = ROOTINO;
+ endlist = &list[nentry];
+ for (fp = &list[0]; fp < endlist; fp++) {
+ if (bigino < fp->fnum)
+ bigino = fp->fnum;
+ if (width < fp->len)
+ width = fp->len;
+ if (fp->prefix != ' ')
+ haveprefix = 1;
+ if (fp->postfix != ' ')
+ havepostfix = 1;
+ }
+ if (haveprefix)
+ width++;
+ if (havepostfix)
+ width++;
+ if (vflag) {
+ for (precision = 0, i = bigino; i > 0; i /= 10)
+ precision++;
+ width += precision + 1;
+ }
+ width++;
+ columns = 81 / width;
+ if (columns == 0)
+ columns = 1;
+ lines = (nentry + columns - 1) / columns;
+ for (i = 0; i < lines; i++) {
+ for (j = 0; j < columns; j++) {
+ fp = &list[j * lines + i];
+ if (vflag) {
+ fprintf(stderr, "%*ju ",
+ precision, (uintmax_t)fp->fnum);
+ fp->len += precision + 1;
+ }
+ if (haveprefix) {
+ putc(fp->prefix, stderr);
+ fp->len++;
+ }
+ fprintf(stderr, "%s", fp->fname);
+ if (havepostfix) {
+ putc(fp->postfix, stderr);
+ fp->len++;
+ }
+ if (fp + lines >= endlist) {
+ fprintf(stderr, "\n");
+ break;
+ }
+ for (w = fp->len; w < width; w++)
+ putc(' ', stderr);
+ }
+ }
+}
+
+/*
+ * Skip over directory entries that are not on the tape
+ *
+ * First have to get definition of a dirent.
+ */
+#undef DIRBLKSIZ
+#include <dirent.h>
+#undef d_ino
+
+struct dirent *
+glob_readdir(void *dirp)
+{
+ struct direct *dp;
+ static struct dirent adirent;
+
+ while ((dp = rst_readdir(dirp)) != NULL) {
+ if (!vflag && dp->d_ino == WINO)
+ continue;
+ if (dflag || TSTINO(dp->d_ino, dumpmap))
+ break;
+ }
+ if (dp == NULL)
+ return (NULL);
+ adirent.d_fileno = dp->d_ino;
+ adirent.d_namlen = dp->d_namlen;
+ memmove(adirent.d_name, dp->d_name, dp->d_namlen + 1);
+ return (&adirent);
+}
+
+/*
+ * Return st_mode information in response to stat or lstat calls
+ */
+static int
+glob_stat(const char *name, struct stat *stp)
+{
+ struct direct *dp;
+
+ dp = pathsearch(name);
+ if (dp == NULL || (!dflag && TSTINO(dp->d_ino, dumpmap) == 0) ||
+ (!vflag && dp->d_ino == WINO))
+ return (-1);
+ if (inodetype(dp->d_ino) == NODE)
+ stp->st_mode = IFDIR;
+ else
+ stp->st_mode = IFREG;
+ return (0);
+}
+
+/*
+ * Comparison routine for qsort.
+ */
+static int
+fcmp(const void *f1, const void *f2)
+{
+ return (strcoll(((struct afile *)f1)->fname,
+ ((struct afile *)f2)->fname));
+}
+
+/*
+ * respond to interrupts
+ */
+void
+onintr(int signo __unused)
+{
+ if (command == 'i' && runshell)
+ longjmp(reset, 1);
+ if (reply("restore interrupted, continue") == FAIL)
+ done(1);
+}
diff --git a/sbin/restore/main.c b/sbin/restore/main.c
new file mode 100644
index 0000000..91f4a83
--- /dev/null
+++ b/sbin/restore/main.c
@@ -0,0 +1,376 @@
+/*
+ * Copyright (c) 1983, 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.
+ * 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 lint
+static const char copyright[] =
+"@(#) Copyright (c) 1983, 1993\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)main.c 8.6 (Berkeley) 5/4/95";
+#endif
+#endif /* not lint */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include <ufs/ufs/dinode.h>
+#include <protocols/dumprestore.h>
+
+#include <err.h>
+#include <limits.h>
+#include <locale.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "restore.h"
+#include "extern.h"
+
+int bflag = 0, cvtflag = 0, dflag = 0, Dflag = 0, vflag = 0, yflag = 0;
+int hflag = 1, mflag = 1, Nflag = 0;
+int uflag = 0;
+int pipecmd = 0;
+char command = '\0';
+long dumpnum = 1;
+long volno = 0;
+long ntrec;
+char *dumpmap;
+char *usedinomap;
+ino_t maxino;
+time_t dumptime;
+time_t dumpdate;
+FILE *terminal;
+
+static void obsolete(int *, char **[]);
+static void usage(void) __dead2;
+
+int
+main(int argc, char *argv[])
+{
+ int ch;
+ ino_t ino;
+ char *inputdev;
+ char *symtbl = "./restoresymtable";
+ char *p, name[MAXPATHLEN];
+
+ /* Temp files should *not* be readable. We set permissions later. */
+ (void) umask(077);
+
+ if (argc < 2)
+ usage();
+
+ (void)setlocale(LC_ALL, "");
+
+ inputdev = NULL;
+ obsolete(&argc, &argv);
+ while ((ch = getopt(argc, argv, "b:dDf:himNP:Rrs:tuvxy")) != -1)
+ switch(ch) {
+ case 'b':
+ /* Change default tape blocksize. */
+ bflag = 1;
+ ntrec = strtol(optarg, &p, 10);
+ if (*p)
+ errx(1, "illegal blocksize -- %s", optarg);
+ if (ntrec <= 0)
+ errx(1, "block size must be greater than 0");
+ break;
+ case 'd':
+ dflag = 1;
+ break;
+ case 'D':
+ Dflag = 1;
+ break;
+ case 'f':
+ if (pipecmd)
+ errx(1,
+ "-P and -f options are mutually exclusive");
+ inputdev = optarg;
+ break;
+ case 'P':
+ if (!pipecmd && inputdev)
+ errx(1,
+ "-P and -f options are mutually exclusive");
+ inputdev = optarg;
+ pipecmd = 1;
+ break;
+ case 'h':
+ hflag = 0;
+ break;
+ case 'i':
+ case 'R':
+ case 'r':
+ case 't':
+ case 'x':
+ if (command != '\0')
+ errx(1,
+ "%c and %c options are mutually exclusive",
+ ch, command);
+ command = ch;
+ break;
+ case 'm':
+ mflag = 0;
+ break;
+ case 'N':
+ Nflag = 1;
+ break;
+ case 's':
+ /* Dumpnum (skip to) for multifile dump tapes. */
+ dumpnum = strtol(optarg, &p, 10);
+ if (*p)
+ errx(1, "illegal dump number -- %s", optarg);
+ if (dumpnum <= 0)
+ errx(1, "dump number must be greater than 0");
+ break;
+ case 'u':
+ uflag = 1;
+ break;
+ case 'v':
+ vflag = 1;
+ break;
+ case 'y':
+ yflag = 1;
+ break;
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (command == '\0')
+ errx(1, "none of i, R, r, t or x options specified");
+
+ if (signal(SIGINT, onintr) == SIG_IGN)
+ (void) signal(SIGINT, SIG_IGN);
+ if (signal(SIGTERM, onintr) == SIG_IGN)
+ (void) signal(SIGTERM, SIG_IGN);
+ setlinebuf(stderr);
+
+ if (inputdev == NULL && (inputdev = getenv("TAPE")) == NULL)
+ inputdev = _PATH_DEFTAPE;
+ setinput(inputdev, pipecmd);
+
+ if (argc == 0) {
+ argc = 1;
+ *--argv = ".";
+ }
+
+ switch (command) {
+ /*
+ * Interactive mode.
+ */
+ case 'i':
+ setup();
+ extractdirs(1);
+ initsymtable(NULL);
+ runcmdshell();
+ break;
+ /*
+ * Incremental restoration of a file system.
+ */
+ case 'r':
+ setup();
+ if (dumptime > 0) {
+ /*
+ * This is an incremental dump tape.
+ */
+ vprintf(stdout, "Begin incremental restore\n");
+ initsymtable(symtbl);
+ extractdirs(1);
+ removeoldleaves();
+ vprintf(stdout, "Calculate node updates.\n");
+ treescan(".", ROOTINO, nodeupdates);
+ findunreflinks();
+ removeoldnodes();
+ } else {
+ /*
+ * This is a level zero dump tape.
+ */
+ vprintf(stdout, "Begin level 0 restore\n");
+ initsymtable((char *)0);
+ extractdirs(1);
+ vprintf(stdout, "Calculate extraction list.\n");
+ treescan(".", ROOTINO, nodeupdates);
+ }
+ createleaves(symtbl);
+ createlinks();
+ setdirmodes(FORCE);
+ checkrestore();
+ if (dflag) {
+ vprintf(stdout, "Verify the directory structure\n");
+ treescan(".", ROOTINO, verifyfile);
+ }
+ dumpsymtable(symtbl, (long)1);
+ break;
+ /*
+ * Resume an incremental file system restoration.
+ */
+ case 'R':
+ initsymtable(symtbl);
+ skipmaps();
+ skipdirs();
+ createleaves(symtbl);
+ createlinks();
+ setdirmodes(FORCE);
+ checkrestore();
+ dumpsymtable(symtbl, (long)1);
+ break;
+ /*
+ * List contents of tape.
+ */
+ case 't':
+ setup();
+ extractdirs(0);
+ initsymtable((char *)0);
+ while (argc--) {
+ canon(*argv++, name, sizeof(name));
+ ino = dirlookup(name);
+ if (ino == 0)
+ continue;
+ treescan(name, ino, listfile);
+ }
+ break;
+ /*
+ * Batch extraction of tape contents.
+ */
+ case 'x':
+ setup();
+ extractdirs(1);
+ initsymtable((char *)0);
+ while (argc--) {
+ canon(*argv++, name, sizeof(name));
+ ino = dirlookup(name);
+ if (ino == 0)
+ continue;
+ if (mflag)
+ pathcheck(name);
+ treescan(name, ino, addfile);
+ }
+ createfiles();
+ createlinks();
+ setdirmodes(0);
+ if (dflag)
+ checkrestore();
+ break;
+ }
+ done(0);
+ /* NOTREACHED */
+}
+
+static void
+usage()
+{
+ const char *const common =
+ "[-b blocksize] [-f file | -P pipecommand] [-s fileno]";
+ const char *const fileell = "[file ...]";
+
+ (void)fprintf(stderr, "usage:\t%s %s\n\t%s %s\n\t%s %s\n"
+ "\t%s %s %s\n\t%s %s %s\n",
+ "restore -i [-dhmNuvy]", common,
+ "restore -R [-dNuvy]", common,
+ "restore -r [-dNuvy]", common,
+ "restore -t [-dhNuvy]", common, fileell,
+ "restore -x [-dhmNuvy]", common, fileell);
+ done(1);
+}
+
+/*
+ * obsolete --
+ * Change set of key letters and ordered arguments into something
+ * getopt(3) will like.
+ */
+static void
+obsolete(int *argcp, char **argvp[])
+{
+ int argc, flags;
+ char *ap, **argv, *flagsp, **nargv, *p;
+
+ /* Setup. */
+ argv = *argvp;
+ argc = *argcp;
+
+ /* Return if no arguments or first argument has leading dash. */
+ ap = argv[1];
+ if (argc == 1 || *ap == '-')
+ return;
+
+ /* Allocate space for new arguments. */
+ if ((*argvp = nargv = malloc((argc + 1) * sizeof(char *))) == NULL ||
+ (p = flagsp = malloc(strlen(ap) + 2)) == NULL)
+ err(1, NULL);
+
+ *nargv++ = *argv;
+ argv += 2, argc -= 2;
+
+ for (flags = 0; *ap; ++ap) {
+ switch (*ap) {
+ case 'b':
+ case 'f':
+ case 's':
+ if (*argv == NULL) {
+ warnx("option requires an argument -- %c", *ap);
+ usage();
+ }
+ if ((nargv[0] = malloc(strlen(*argv) + 2 + 1)) == NULL)
+ err(1, NULL);
+ nargv[0][0] = '-';
+ nargv[0][1] = *ap;
+ (void)strcpy(&nargv[0][2], *argv);
+ ++argv;
+ ++nargv;
+ break;
+ default:
+ if (!flags) {
+ *p++ = '-';
+ flags = 1;
+ }
+ *p++ = *ap;
+ break;
+ }
+ }
+
+ /* Terminate flags. */
+ if (flags) {
+ *p = '\0';
+ *nargv++ = flagsp;
+ }
+
+ /* Copy remaining arguments. */
+ while ((*nargv++ = *argv++));
+
+ /* Update argument count. */
+ *argcp = nargv - *argvp - 1;
+}
diff --git a/sbin/restore/restore.8 b/sbin/restore/restore.8
new file mode 100644
index 0000000..d17496c
--- /dev/null
+++ b/sbin/restore/restore.8
@@ -0,0 +1,503 @@
+.\" Copyright (c) 1985, 1991, 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.
+.\" 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.
+.\"
+.\" @(#)restore.8 8.4 (Berkeley) 5/1/95
+.\" $FreeBSD$
+.\"
+.Dd October 12, 2006
+.Dt RESTORE 8
+.Os
+.Sh NAME
+.Nm restore ,
+.Nm rrestore
+.Nd "restore files or file systems from backups made with dump"
+.Sh SYNOPSIS
+.Nm
+.Fl i
+.Op Fl dDhmNuvy
+.Op Fl b Ar blocksize
+.Op Fl f Ar file | Fl P Ar pipecommand
+.Op Fl s Ar fileno
+.Nm
+.Fl R
+.Op Fl dDNuvy
+.Op Fl b Ar blocksize
+.Op Fl f Ar file | Fl P Ar pipecommand
+.Op Fl s Ar fileno
+.Nm
+.Fl r
+.Op Fl dDNuvy
+.Op Fl b Ar blocksize
+.Op Fl f Ar file | Fl P Ar pipecommand
+.Op Fl s Ar fileno
+.Nm
+.Fl t
+.Op Fl dDhNuvy
+.Op Fl b Ar blocksize
+.Op Fl f Ar file | Fl P Ar pipecommand
+.Op Fl s Ar fileno
+.Op Ar
+.Nm
+.Fl x
+.Op Fl dDhmNuvy
+.Op Fl b Ar blocksize
+.Op Fl f Ar file | Fl P Ar pipecommand
+.Op Fl s Ar fileno
+.Op Ar
+.Sh DESCRIPTION
+The
+.Nm
+utility performs the inverse function of
+.Xr dump 8 .
+A full backup of a file system may be restored and
+subsequent incremental backups layered on top of it.
+Single files and
+directory subtrees may be restored from full or partial
+backups.
+The
+.Nm
+utility works across a network;
+to do this see the
+.Fl f
+and
+.Fl P
+flags described below.
+Other arguments to the command are file or directory
+names specifying the files that are to be restored.
+Unless the
+.Fl h
+flag is specified (see below),
+the appearance of a directory name refers to
+the files and (recursively) subdirectories of that directory.
+.Pp
+.Nm
+may also be invoked as
+.Nm rrestore .
+The
+.Bx 4.3
+option syntax is implemented for backward compatibility, but
+is not documented here.
+.Pp
+Exactly one of the following flags is required:
+.Bl -tag -width Ds
+.It Fl i
+This mode allows interactive restoration of files from a dump.
+After reading in the directory information from the dump,
+.Nm
+provides a shell like interface that allows the user to move
+around the directory tree selecting files to be extracted.
+The available commands are given below;
+for those commands that require an argument,
+the default is the current directory.
+.Bl -tag -width Fl
+.It Ic add Op Ar arg
+The current directory or specified argument is added to the list of
+files to be extracted.
+If a directory is specified, then it and all its descendents are
+added to the extraction list
+(unless the
+.Fl h
+flag is specified on the command line).
+Files that are on the extraction list are prepended with a ``*''
+when they are listed by
+.Ic ls .
+.It Ic \&cd Ar arg
+Change the current working directory to the specified argument.
+.It Ic delete Op Ar arg
+The current directory or specified argument is deleted from the list of
+files to be extracted.
+If a directory is specified, then it and all its descendents are
+deleted from the extraction list
+(unless the
+.Fl h
+flag is specified on the command line).
+The most expedient way to extract most of the files from a directory
+is to add the directory to the extraction list and then delete
+those files that are not needed.
+.It Ic extract
+All the files that are on the extraction list are extracted
+from the dump.
+The
+.Nm
+utility will ask which volume the user wishes to mount.
+The fastest way to extract a few files is to
+start with the last volume, and work towards the first volume.
+.It Ic help
+List a summary of the available commands.
+.It Ic \&ls Op Ar arg
+List the current or specified directory.
+Entries that are directories are appended with a ``/''.
+Entries that have been marked for extraction are prepended with a ``*''.
+If the verbose
+flag is set the inode number of each entry is also listed.
+.It Ic pwd
+Print the full pathname of the current working directory.
+.It Ic quit
+Exit immediately,
+even if the extraction list is not empty.
+.It Ic setmodes
+All the directories that have been added to the extraction list
+have their owner, modes, and times set;
+nothing is extracted from the dump.
+This is useful for cleaning up after a restore has been prematurely aborted.
+.It Ic verbose
+The sense of the
+.Fl v
+flag is toggled.
+When set, the verbose flag causes the
+.Ic ls
+command to list the inode numbers of all entries.
+It also causes
+.Nm
+to print out information about each file as it is extracted.
+.It Ic what
+Display dump header information, which includes: date,
+level, label, and the file system and host dump was made
+from.
+.El
+.It Fl R
+Request a particular tape of a multi volume set on which to restart
+a full restore
+(see the
+.Fl r
+flag below).
+This is useful if the restore has been interrupted.
+.It Fl r
+Restore (rebuild a file system).
+The target file system should be made pristine with
+.Xr newfs 8 ,
+mounted and the user
+.Xr cd 1 Ns 'd
+into the pristine file system
+before starting the restoration of the initial level 0 backup.
+If the
+level 0 restores successfully, the
+.Fl r
+flag may be used to restore
+any necessary incremental backups on top of the level 0.
+The
+.Fl r
+flag precludes an interactive file extraction and can be
+detrimental to one's health if not used carefully (not to mention
+the disk).
+An example:
+.Bd -literal -offset indent
+newfs /dev/da0s1a
+mount /dev/da0s1a /mnt
+cd /mnt
+
+restore rf /dev/sa0
+.Ed
+.Pp
+Note that
+.Nm
+leaves a file
+.Pa restoresymtable
+in the root directory to pass information between incremental
+restore passes.
+This file should be removed when the last incremental has been
+restored.
+.Pp
+The
+.Nm
+utility ,
+in conjunction with
+.Xr newfs 8
+and
+.Xr dump 8 ,
+may be used to modify file system parameters
+such as size or block size.
+.It Fl t
+The names of the specified files are listed if they occur
+on the backup.
+If no file argument is given,
+then the root directory is listed,
+which results in the entire content of the
+backup being listed,
+unless the
+.Fl h
+flag has been specified.
+Note that the
+.Fl t
+flag replaces the function of the old
+.Xr dumpdir 8
+program.
+.It Fl x
+The named files are read from the given media.
+If a named file matches a directory whose contents
+are on the backup
+and the
+.Fl h
+flag is not specified,
+the directory is recursively extracted.
+The owner, modification time,
+and mode are restored (if possible).
+If no file argument is given,
+then the root directory is extracted,
+which results in the entire content of the
+backup being extracted,
+unless the
+.Fl h
+flag has been specified.
+.El
+.Pp
+The following additional options may be specified:
+.Bl -tag -width Ds
+.It Fl b Ar blocksize
+The number of kilobytes per dump record.
+If the
+.Fl b
+option is not specified,
+.Nm
+tries to determine the media block size dynamically.
+.It Fl d
+Sends verbose debugging output to the standard error.
+.It Fl D
+This puts
+.Nm
+into degraded mode,
+causing restore to operate less efficiently
+but to try harder to read corrupted backups.
+.It Fl f Ar file
+Read the backup from
+.Ar file ;
+.Ar file
+may be a special device file
+like
+.Pa /dev/sa0
+(a tape drive),
+.Pa /dev/da1c
+(a disk drive),
+an ordinary file,
+or
+.Sq Fl
+(the standard input).
+If the name of the file is of the form
+.Dq host:file ,
+or
+.Dq user@host:file ,
+.Nm
+reads from the named file on the remote host using
+.Xr rmt 8 .
+.It Fl P Ar pipecommand
+Use
+.Xr popen 3
+to execute the
+.Xr sh 1
+script string defined by
+.Ar pipecommand
+as the input for every volume in the backup.
+This child pipeline's
+.Dv stdout
+.Pq Pa /dev/fd/1
+is redirected to the
+.Nm
+input stream, and the environment variable
+.Ev RESTORE_VOLUME
+is set to the current volume number being read.
+The
+.Ar pipecommand
+script is started each time a volume is loaded, as if it were a tape drive.
+.It Fl h
+Extract the actual directory,
+rather than the files that it references.
+This prevents hierarchical restoration of complete subtrees
+from the dump.
+.It Fl m
+Extract by inode numbers rather than by file name.
+This is useful if only a few files are being extracted,
+and one wants to avoid regenerating the complete pathname
+to the file.
+.It Fl N
+Do the extraction normally, but do not actually write any changes
+to disk.
+This can be used to check the integrity of dump media
+or other test purposes.
+.It Fl s Ar fileno
+Read from the specified
+.Ar fileno
+on a multi-file tape.
+File numbering starts at 1.
+.It Fl u
+When creating certain types of files, restore may generate a warning
+diagnostic if they already exist in the target directory.
+To prevent this, the
+.Fl u
+(unlink) flag causes restore to remove old entries before attempting
+to create new ones.
+.It Fl v
+Normally
+.Nm
+does its work silently.
+The
+.Fl v
+(verbose)
+flag causes it to type the name of each file it treats
+preceded by its file type.
+.It Fl y
+Do not ask the user whether to abort the restore in the event of an error.
+Always try to skip over the bad block(s) and continue.
+.El
+.Sh ENVIRONMENT
+.Bl -tag -width ".Ev TMPDIR"
+.It Ev TAPE
+Device from which to read backup.
+.It Ev TMPDIR
+Name of directory where temporary files are to be created.
+.El
+.Sh FILES
+.Bl -tag -width "./restoresymtable" -compact
+.It Pa /dev/sa0
+the default tape drive
+.It Pa /tmp/rstdir*
+file containing directories on the tape.
+.It Pa /tmp/rstmode*
+owner, mode, and time stamps for directories.
+.It Pa \&./restoresymtable
+information passed between incremental restores.
+.El
+.Sh DIAGNOSTICS
+The
+.Nm
+utility complains if it gets a read error.
+If
+.Fl y
+has been specified, or the user responds
+.Ql y ,
+.Nm
+will attempt to continue the restore.
+.Pp
+If a backup was made using more than one tape volume,
+.Nm
+will notify the user when it is time to mount the next volume.
+If the
+.Fl x
+or
+.Fl i
+flag has been specified,
+.Nm
+will also ask which volume the user wishes to mount.
+The fastest way to extract a few files is to
+start with the last volume, and work towards the first volume.
+.Pp
+There are numerous consistency checks that can be listed by
+.Nm .
+Most checks are self-explanatory or can ``never happen''.
+Common errors are given below.
+.Pp
+.Bl -tag -width Ds -compact
+.It <filename>: not found on tape
+The specified file name was listed in the tape directory,
+but was not found on the tape.
+This is caused by tape read errors while looking for the file,
+and from using a dump tape created on an active file system.
+.Pp
+.It expected next file <inumber>, got <inumber>
+A file that was not listed in the directory showed up.
+This can occur when using a dump created on an active file system.
+.Pp
+.It Incremental dump too low
+When doing incremental restore,
+a dump that was written before the previous incremental dump,
+or that has too low an incremental level has been loaded.
+.Pp
+.It Incremental dump too high
+When doing incremental restore,
+a dump that does not begin its coverage where the previous incremental
+dump left off,
+or that has too high an incremental level has been loaded.
+.Pp
+.It Tape read error while restoring <filename>
+.It Tape read error while skipping over inode <inumber>
+.It Tape read error while trying to resynchronize
+A tape (or other media) read error has occurred.
+If a file name is specified,
+then its contents are probably partially wrong.
+If an inode is being skipped or the tape is trying to resynchronize,
+then no extracted files have been corrupted,
+though files may not be found on the tape.
+.Pp
+.It resync restore, skipped <num> blocks
+After a dump read error,
+.Nm
+may have to resynchronize itself.
+This message lists the number of blocks that were skipped over.
+.El
+.Sh SEE ALSO
+.Xr dump 8 ,
+.Xr mount 8 ,
+.Xr newfs 8 ,
+.Xr rmt 8
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Bx 4.2 .
+.Sh BUGS
+The
+.Nm
+utility can get confused when doing incremental restores from
+dumps that were made on active file systems without the
+.Fl L
+option (see
+.Xr dump 8 ) .
+.Pp
+A level zero dump must be done after a full restore.
+Because restore runs in user code,
+it has no control over inode allocation;
+thus a full dump must be done to get a new set of directories
+reflecting the new inode numbering,
+even though the contents of the files is unchanged.
+.Pp
+To do a network restore, you have to run restore as root.
+This is due
+to the previous security history of dump and restore.
+(restore is
+written to be setuid root, but we are not certain all bugs are gone
+from the restore code - run setuid at your own risk.)
+.Pp
+The temporary files
+.Pa /tmp/rstdir*
+and
+.Pa /tmp/rstmode*
+are generated with a unique name based on the date of the dump
+and the process ID (see
+.Xr mktemp 3 ) ,
+except for when
+.Fl r
+or
+.Fl R
+is used.
+Because
+.Fl R
+allows you to restart a
+.Fl r
+operation that may have been interrupted, the temporary files should
+be the same across different processes.
+In all other cases, the files are unique because it is possible to
+have two different dumps started at the same time, and separate
+operations should not conflict with each other.
diff --git a/sbin/restore/restore.c b/sbin/restore/restore.c
new file mode 100644
index 0000000..fbc24ec
--- /dev/null
+++ b/sbin/restore/restore.c
@@ -0,0 +1,860 @@
+/*
+ * Copyright (c) 1983, 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.
+ * 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 lint
+#if 0
+static char sccsid[] = "@(#)restore.c 8.3 (Berkeley) 9/13/94";
+#endif
+#endif /* not lint */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+
+#include <limits.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <ufs/ufs/dinode.h>
+
+#include "restore.h"
+#include "extern.h"
+
+static char *keyval(int);
+
+/*
+ * This implements the 't' option.
+ * List entries on the tape.
+ */
+long
+listfile(char *name, ino_t ino, int type)
+{
+ long descend = hflag ? GOOD : FAIL;
+
+ if (TSTINO(ino, dumpmap) == 0)
+ return (descend);
+ vprintf(stdout, "%s", type == LEAF ? "leaf" : "dir ");
+ fprintf(stdout, "%10ju\t%s\n", (uintmax_t)ino, name);
+ return (descend);
+}
+
+/*
+ * This implements the 'x' option.
+ * Request that new entries be extracted.
+ */
+long
+addfile(char *name, ino_t ino, int type)
+{
+ struct entry *ep;
+ long descend = hflag ? GOOD : FAIL;
+ char buf[100];
+
+ if (TSTINO(ino, dumpmap) == 0) {
+ dprintf(stdout, "%s: not on the tape\n", name);
+ return (descend);
+ }
+ if (ino == WINO && command == 'i' && !vflag)
+ return (descend);
+ if (!mflag) {
+ (void) sprintf(buf, "./%ju", (uintmax_t)ino);
+ name = buf;
+ if (type == NODE) {
+ (void) genliteraldir(name, ino);
+ return (descend);
+ }
+ }
+ ep = lookupino(ino);
+ if (ep != NULL) {
+ if (strcmp(name, myname(ep)) == 0) {
+ ep->e_flags |= NEW;
+ return (descend);
+ }
+ type |= LINK;
+ }
+ ep = addentry(name, ino, type);
+ if (type == NODE)
+ newnode(ep);
+ ep->e_flags |= NEW;
+ return (descend);
+}
+
+/*
+ * This is used by the 'i' option to undo previous requests made by addfile.
+ * Delete entries from the request queue.
+ */
+/* ARGSUSED */
+long
+deletefile(char *name, ino_t ino, int type)
+{
+ long descend = hflag ? GOOD : FAIL;
+ struct entry *ep;
+
+ if (TSTINO(ino, dumpmap) == 0)
+ return (descend);
+ ep = lookupname(name);
+ if (ep != NULL) {
+ ep->e_flags &= ~NEW;
+ ep->e_flags |= REMOVED;
+ if (ep->e_type != NODE)
+ freeentry(ep);
+ }
+ return (descend);
+}
+
+/*
+ * The following four routines implement the incremental
+ * restore algorithm. The first removes old entries, the second
+ * does renames and calculates the extraction list, the third
+ * cleans up link names missed by the first two, and the final
+ * one deletes old directories.
+ *
+ * Directories cannot be immediately deleted, as they may have
+ * other files in them which need to be moved out first. As
+ * directories to be deleted are found, they are put on the
+ * following deletion list. After all deletions and renames
+ * are done, this list is actually deleted.
+ */
+static struct entry *removelist;
+
+/*
+ * Remove invalid whiteouts from the old tree.
+ * Remove unneeded leaves from the old tree.
+ * Remove directories from the lookup chains.
+ */
+void
+removeoldleaves(void)
+{
+ struct entry *ep, *nextep;
+ ino_t i, mydirino;
+
+ vprintf(stdout, "Mark entries to be removed.\n");
+ if ((ep = lookupino(WINO))) {
+ vprintf(stdout, "Delete whiteouts\n");
+ for ( ; ep != NULL; ep = nextep) {
+ nextep = ep->e_links;
+ mydirino = ep->e_parent->e_ino;
+ /*
+ * We remove all whiteouts that are in directories
+ * that have been removed or that have been dumped.
+ */
+ if (TSTINO(mydirino, usedinomap) &&
+ !TSTINO(mydirino, dumpmap))
+ continue;
+ delwhiteout(ep);
+ freeentry(ep);
+ }
+ }
+ for (i = ROOTINO + 1; i < maxino; i++) {
+ ep = lookupino(i);
+ if (ep == NULL)
+ continue;
+ if (TSTINO(i, usedinomap))
+ continue;
+ for ( ; ep != NULL; ep = ep->e_links) {
+ dprintf(stdout, "%s: REMOVE\n", myname(ep));
+ if (ep->e_type == LEAF) {
+ removeleaf(ep);
+ freeentry(ep);
+ } else {
+ mktempname(ep);
+ deleteino(ep->e_ino);
+ ep->e_next = removelist;
+ removelist = ep;
+ }
+ }
+ }
+}
+
+/*
+ * For each directory entry on the incremental tape, determine which
+ * category it falls into as follows:
+ * KEEP - entries that are to be left alone.
+ * NEW - new entries to be added.
+ * EXTRACT - files that must be updated with new contents.
+ * LINK - new links to be added.
+ * Renames are done at the same time.
+ */
+long
+nodeupdates(char *name, ino_t ino, int type)
+{
+ struct entry *ep, *np, *ip;
+ long descend = GOOD;
+ int lookuptype = 0;
+ int key = 0;
+ /* key values */
+# define ONTAPE 0x1 /* inode is on the tape */
+# define INOFND 0x2 /* inode already exists */
+# define NAMEFND 0x4 /* name already exists */
+# define MODECHG 0x8 /* mode of inode changed */
+
+ /*
+ * This routine is called once for each element in the
+ * directory hierarchy, with a full path name.
+ * The "type" value is incorrectly specified as LEAF for
+ * directories that are not on the dump tape.
+ *
+ * Check to see if the file is on the tape.
+ */
+ if (TSTINO(ino, dumpmap))
+ key |= ONTAPE;
+ /*
+ * Check to see if the name exists, and if the name is a link.
+ */
+ np = lookupname(name);
+ if (np != NULL) {
+ key |= NAMEFND;
+ ip = lookupino(np->e_ino);
+ if (ip == NULL)
+ panic("corrupted symbol table\n");
+ if (ip != np)
+ lookuptype = LINK;
+ }
+ /*
+ * Check to see if the inode exists, and if one of its links
+ * corresponds to the name (if one was found).
+ */
+ ip = lookupino(ino);
+ if (ip != NULL) {
+ key |= INOFND;
+ for (ep = ip->e_links; ep != NULL; ep = ep->e_links) {
+ if (ep == np) {
+ ip = ep;
+ break;
+ }
+ }
+ }
+ /*
+ * If both a name and an inode are found, but they do not
+ * correspond to the same file, then both the inode that has
+ * been found and the inode corresponding to the name that
+ * has been found need to be renamed. The current pathname
+ * is the new name for the inode that has been found. Since
+ * all files to be deleted have already been removed, the
+ * named file is either a now unneeded link, or it must live
+ * under a new name in this dump level. If it is a link, it
+ * can be removed. If it is not a link, it is given a
+ * temporary name in anticipation that it will be renamed
+ * when it is later found by inode number.
+ */
+ if (((key & (INOFND|NAMEFND)) == (INOFND|NAMEFND)) && ip != np) {
+ if (lookuptype == LINK) {
+ removeleaf(np);
+ freeentry(np);
+ } else {
+ dprintf(stdout, "name/inode conflict, mktempname %s\n",
+ myname(np));
+ mktempname(np);
+ }
+ np = NULL;
+ key &= ~NAMEFND;
+ }
+ if ((key & ONTAPE) &&
+ (((key & INOFND) && ip->e_type != type) ||
+ ((key & NAMEFND) && np->e_type != type)))
+ key |= MODECHG;
+
+ /*
+ * Decide on the disposition of the file based on its flags.
+ * Note that we have already handled the case in which
+ * a name and inode are found that correspond to different files.
+ * Thus if both NAMEFND and INOFND are set then ip == np.
+ */
+ switch (key) {
+
+ /*
+ * A previously existing file has been found.
+ * Mark it as KEEP so that other links to the inode can be
+ * detected, and so that it will not be reclaimed by the search
+ * for unreferenced names.
+ */
+ case INOFND|NAMEFND:
+ ip->e_flags |= KEEP;
+ dprintf(stdout, "[%s] %s: %s\n", keyval(key), name,
+ flagvalues(ip));
+ break;
+
+ /*
+ * A file on the tape has a name which is the same as a name
+ * corresponding to a different file in the previous dump.
+ * Since all files to be deleted have already been removed,
+ * this file is either a now unneeded link, or it must live
+ * under a new name in this dump level. If it is a link, it
+ * can simply be removed. If it is not a link, it is given a
+ * temporary name in anticipation that it will be renamed
+ * when it is later found by inode number (see INOFND case
+ * below). The entry is then treated as a new file.
+ */
+ case ONTAPE|NAMEFND:
+ case ONTAPE|NAMEFND|MODECHG:
+ if (lookuptype == LINK) {
+ removeleaf(np);
+ freeentry(np);
+ } else {
+ mktempname(np);
+ }
+ /* FALLTHROUGH */
+
+ /*
+ * A previously non-existent file.
+ * Add it to the file system, and request its extraction.
+ * If it is a directory, create it immediately.
+ * (Since the name is unused there can be no conflict)
+ */
+ case ONTAPE:
+ ep = addentry(name, ino, type);
+ if (type == NODE)
+ newnode(ep);
+ ep->e_flags |= NEW|KEEP;
+ dprintf(stdout, "[%s] %s: %s\n", keyval(key), name,
+ flagvalues(ep));
+ break;
+
+ /*
+ * A file with the same inode number, but a different
+ * name has been found. If the other name has not already
+ * been found (indicated by the KEEP flag, see above) then
+ * this must be a new name for the file, and it is renamed.
+ * If the other name has been found then this must be a
+ * link to the file. Hard links to directories are not
+ * permitted, and are either deleted or converted to
+ * symbolic links. Finally, if the file is on the tape,
+ * a request is made to extract it.
+ */
+ case ONTAPE|INOFND:
+ if (type == LEAF && (ip->e_flags & KEEP) == 0)
+ ip->e_flags |= EXTRACT;
+ /* FALLTHROUGH */
+ case INOFND:
+ if ((ip->e_flags & KEEP) == 0) {
+ renameit(myname(ip), name);
+ moveentry(ip, name);
+ ip->e_flags |= KEEP;
+ dprintf(stdout, "[%s] %s: %s\n", keyval(key), name,
+ flagvalues(ip));
+ break;
+ }
+ if (ip->e_type == NODE) {
+ descend = FAIL;
+ fprintf(stderr,
+ "deleted hard link %s to directory %s\n",
+ name, myname(ip));
+ break;
+ }
+ ep = addentry(name, ino, type|LINK);
+ ep->e_flags |= NEW;
+ dprintf(stdout, "[%s] %s: %s|LINK\n", keyval(key), name,
+ flagvalues(ep));
+ break;
+
+ /*
+ * A previously known file which is to be updated. If it is a link,
+ * then all names referring to the previous file must be removed
+ * so that the subset of them that remain can be recreated.
+ */
+ case ONTAPE|INOFND|NAMEFND:
+ if (lookuptype == LINK) {
+ removeleaf(np);
+ freeentry(np);
+ ep = addentry(name, ino, type|LINK);
+ if (type == NODE)
+ newnode(ep);
+ ep->e_flags |= NEW|KEEP;
+ dprintf(stdout, "[%s] %s: %s|LINK\n", keyval(key), name,
+ flagvalues(ep));
+ break;
+ }
+ if (type == LEAF && lookuptype != LINK)
+ np->e_flags |= EXTRACT;
+ np->e_flags |= KEEP;
+ dprintf(stdout, "[%s] %s: %s\n", keyval(key), name,
+ flagvalues(np));
+ break;
+
+ /*
+ * An inode is being reused in a completely different way.
+ * Normally an extract can simply do an "unlink" followed
+ * by a "creat". Here we must do effectively the same
+ * thing. The complications arise because we cannot really
+ * delete a directory since it may still contain files
+ * that we need to rename, so we delete it from the symbol
+ * table, and put it on the list to be deleted eventually.
+ * Conversely if a directory is to be created, it must be
+ * done immediately, rather than waiting until the
+ * extraction phase.
+ */
+ case ONTAPE|INOFND|MODECHG:
+ case ONTAPE|INOFND|NAMEFND|MODECHG:
+ if (ip->e_flags & KEEP) {
+ badentry(ip, "cannot KEEP and change modes");
+ break;
+ }
+ if (ip->e_type == LEAF) {
+ /* changing from leaf to node */
+ for (ip = lookupino(ino); ip != NULL; ip = ip->e_links) {
+ if (ip->e_type != LEAF)
+ badentry(ip, "NODE and LEAF links to same inode");
+ removeleaf(ip);
+ freeentry(ip);
+ }
+ ip = addentry(name, ino, type);
+ newnode(ip);
+ } else {
+ /* changing from node to leaf */
+ if ((ip->e_flags & TMPNAME) == 0)
+ mktempname(ip);
+ deleteino(ip->e_ino);
+ ip->e_next = removelist;
+ removelist = ip;
+ ip = addentry(name, ino, type);
+ }
+ ip->e_flags |= NEW|KEEP;
+ dprintf(stdout, "[%s] %s: %s\n", keyval(key), name,
+ flagvalues(ip));
+ break;
+
+ /*
+ * A hard link to a directory that has been removed.
+ * Ignore it.
+ */
+ case NAMEFND:
+ dprintf(stdout, "[%s] %s: Extraneous name\n", keyval(key),
+ name);
+ descend = FAIL;
+ break;
+
+ /*
+ * If we find a directory entry for a file that is not on
+ * the tape, then we must have found a file that was created
+ * while the dump was in progress. Since we have no contents
+ * for it, we discard the name knowing that it will be on the
+ * next incremental tape.
+ */
+ case 0:
+ fprintf(stderr, "%s: (inode %ju) not found on tape\n",
+ name, (uintmax_t)ino);
+ break;
+
+ /*
+ * If any of these arise, something is grievously wrong with
+ * the current state of the symbol table.
+ */
+ case INOFND|NAMEFND|MODECHG:
+ case NAMEFND|MODECHG:
+ case INOFND|MODECHG:
+ fprintf(stderr, "[%s] %s: inconsistent state\n", keyval(key),
+ name);
+ break;
+
+ /*
+ * These states "cannot" arise for any state of the symbol table.
+ */
+ case ONTAPE|MODECHG:
+ case MODECHG:
+ default:
+ panic("[%s] %s: impossible state\n", keyval(key), name);
+ break;
+ }
+ return (descend);
+}
+
+/*
+ * Calculate the active flags in a key.
+ */
+static char *
+keyval(int key)
+{
+ static char keybuf[32];
+
+ (void) strcpy(keybuf, "|NIL");
+ keybuf[0] = '\0';
+ if (key & ONTAPE)
+ (void) strcat(keybuf, "|ONTAPE");
+ if (key & INOFND)
+ (void) strcat(keybuf, "|INOFND");
+ if (key & NAMEFND)
+ (void) strcat(keybuf, "|NAMEFND");
+ if (key & MODECHG)
+ (void) strcat(keybuf, "|MODECHG");
+ return (&keybuf[1]);
+}
+
+/*
+ * Find unreferenced link names.
+ */
+void
+findunreflinks(void)
+{
+ struct entry *ep, *np;
+ ino_t i;
+
+ vprintf(stdout, "Find unreferenced names.\n");
+ for (i = ROOTINO; i < maxino; i++) {
+ ep = lookupino(i);
+ if (ep == NULL || ep->e_type == LEAF || TSTINO(i, dumpmap) == 0)
+ continue;
+ for (np = ep->e_entries; np != NULL; np = np->e_sibling) {
+ if (np->e_flags == 0) {
+ dprintf(stdout,
+ "%s: remove unreferenced name\n",
+ myname(np));
+ removeleaf(np);
+ freeentry(np);
+ }
+ }
+ }
+ /*
+ * Any leaves remaining in removed directories is unreferenced.
+ */
+ for (ep = removelist; ep != NULL; ep = ep->e_next) {
+ for (np = ep->e_entries; np != NULL; np = np->e_sibling) {
+ if (np->e_type == LEAF) {
+ if (np->e_flags != 0)
+ badentry(np, "unreferenced with flags");
+ dprintf(stdout,
+ "%s: remove unreferenced name\n",
+ myname(np));
+ removeleaf(np);
+ freeentry(np);
+ }
+ }
+ }
+}
+
+/*
+ * Remove old nodes (directories).
+ * Note that this routine runs in O(N*D) where:
+ * N is the number of directory entries to be removed.
+ * D is the maximum depth of the tree.
+ * If N == D this can be quite slow. If the list were
+ * topologically sorted, the deletion could be done in
+ * time O(N).
+ */
+void
+removeoldnodes(void)
+{
+ struct entry *ep, **prev;
+ long change;
+
+ vprintf(stdout, "Remove old nodes (directories).\n");
+ do {
+ change = 0;
+ prev = &removelist;
+ for (ep = removelist; ep != NULL; ep = *prev) {
+ if (ep->e_entries != NULL) {
+ prev = &ep->e_next;
+ continue;
+ }
+ *prev = ep->e_next;
+ removenode(ep);
+ freeentry(ep);
+ change++;
+ }
+ } while (change);
+ for (ep = removelist; ep != NULL; ep = ep->e_next)
+ badentry(ep, "cannot remove, non-empty");
+}
+
+/*
+ * This is the routine used to extract files for the 'r' command.
+ * Extract new leaves.
+ */
+void
+createleaves(char *symtabfile)
+{
+ struct entry *ep;
+ ino_t first;
+ long curvol;
+
+ if (command == 'R') {
+ vprintf(stdout, "Continue extraction of new leaves\n");
+ } else {
+ vprintf(stdout, "Extract new leaves.\n");
+ dumpsymtable(symtabfile, volno);
+ }
+ first = lowerbnd(ROOTINO);
+ curvol = volno;
+ while (curfile.ino < maxino) {
+ first = lowerbnd(first);
+ /*
+ * If the next available file is not the one which we
+ * expect then we have missed one or more files. Since
+ * we do not request files that were not on the tape,
+ * the lost files must have been due to a tape read error,
+ * or a file that was removed while the dump was in progress.
+ */
+ while (first < curfile.ino) {
+ ep = lookupino(first);
+ if (ep == NULL)
+ panic("%ju: bad first\n", (uintmax_t)first);
+ fprintf(stderr, "%s: not found on tape\n", myname(ep));
+ ep->e_flags &= ~(NEW|EXTRACT);
+ first = lowerbnd(first);
+ }
+ /*
+ * If we find files on the tape that have no corresponding
+ * directory entries, then we must have found a file that
+ * was created while the dump was in progress. Since we have
+ * no name for it, we discard it knowing that it will be
+ * on the next incremental tape.
+ */
+ if (first != curfile.ino) {
+ fprintf(stderr, "expected next file %ju, got %ju\n",
+ (uintmax_t)first, (uintmax_t)curfile.ino);
+ skipfile();
+ goto next;
+ }
+ ep = lookupino(curfile.ino);
+ if (ep == NULL)
+ panic("unknown file on tape\n");
+ if ((ep->e_flags & (NEW|EXTRACT)) == 0)
+ badentry(ep, "unexpected file on tape");
+ /*
+ * If the file is to be extracted, then the old file must
+ * be removed since its type may change from one leaf type
+ * to another (e.g. "file" to "character special").
+ */
+ if ((ep->e_flags & EXTRACT) != 0) {
+ removeleaf(ep);
+ ep->e_flags &= ~REMOVED;
+ }
+ (void) extractfile(myname(ep));
+ ep->e_flags &= ~(NEW|EXTRACT);
+ /*
+ * We checkpoint the restore after every tape reel, so
+ * as to simplify the amount of work required by the
+ * 'R' command.
+ */
+ next:
+ if (curvol != volno) {
+ dumpsymtable(symtabfile, volno);
+ skipmaps();
+ curvol = volno;
+ }
+ }
+}
+
+/*
+ * This is the routine used to extract files for the 'x' and 'i' commands.
+ * Efficiently extract a subset of the files on a tape.
+ */
+void
+createfiles(void)
+{
+ ino_t first, next, last;
+ struct entry *ep;
+ long curvol;
+
+ vprintf(stdout, "Extract requested files\n");
+ curfile.action = SKIP;
+ getvol((long)1);
+ skipmaps();
+ skipdirs();
+ first = lowerbnd(ROOTINO);
+ last = upperbnd(maxino - 1);
+ for (;;) {
+ curvol = volno;
+ first = lowerbnd(first);
+ last = upperbnd(last);
+ /*
+ * Check to see if any files remain to be extracted
+ */
+ if (first > last)
+ return;
+ if (Dflag) {
+ if (curfile.ino == maxino)
+ return;
+ if((ep = lookupino(curfile.ino)) != NULL &&
+ (ep->e_flags & (NEW|EXTRACT))) {
+ goto justgetit;
+ } else {
+ skipfile();
+ continue;
+ }
+ }
+ /*
+ * Reject any volumes with inodes greater than the last
+ * one needed, so that we can quickly skip backwards to
+ * a volume containing useful inodes. We can't do this
+ * if there are no further volumes available (curfile.ino
+ * >= maxino) or if we are already at the first tape.
+ */
+ if (curfile.ino > last && curfile.ino < maxino && volno > 1) {
+ curfile.action = SKIP;
+ getvol((long)0);
+ skipmaps();
+ skipdirs();
+ continue;
+ }
+ /*
+ * Decide on the next inode needed.
+ * Skip across the inodes until it is found
+ * or a volume change is encountered
+ */
+ if (curfile.ino < maxino) {
+ next = lowerbnd(curfile.ino);
+ while (next > curfile.ino && volno == curvol)
+ skipfile();
+ if (volno != curvol) {
+ skipmaps();
+ skipdirs();
+ continue;
+ }
+ } else {
+ /*
+ * No further volumes or inodes available. Set
+ * `next' to the first inode, so that a warning
+ * is emitted below for each missing file.
+ */
+ next = first;
+ }
+ /*
+ * If the current inode is greater than the one we were
+ * looking for then we missed the one we were looking for.
+ * Since we only attempt to extract files listed in the
+ * dump map, the lost files must have been due to a tape
+ * read error, or a file that was removed while the dump
+ * was in progress. Thus we report all requested files
+ * between the one we were looking for, and the one we
+ * found as missing, and delete their request flags.
+ */
+ while (next < curfile.ino) {
+ ep = lookupino(next);
+ if (ep == NULL)
+ panic("corrupted symbol table\n");
+ fprintf(stderr, "%s: not found on tape\n", myname(ep));
+ ep->e_flags &= ~NEW;
+ next = lowerbnd(next);
+ }
+ /*
+ * The current inode is the one that we are looking for,
+ * so extract it per its requested name.
+ */
+ if (next == curfile.ino && next <= last) {
+ ep = lookupino(next);
+ if (ep == NULL)
+ panic("corrupted symbol table\n");
+justgetit:
+ (void) extractfile(myname(ep));
+ ep->e_flags &= ~NEW;
+ if (volno != curvol)
+ skipmaps();
+ }
+ }
+}
+
+/*
+ * Add links.
+ */
+void
+createlinks(void)
+{
+ struct entry *np, *ep;
+ ino_t i;
+ char name[BUFSIZ];
+
+ if ((ep = lookupino(WINO))) {
+ vprintf(stdout, "Add whiteouts\n");
+ for ( ; ep != NULL; ep = ep->e_links) {
+ if ((ep->e_flags & NEW) == 0)
+ continue;
+ (void) addwhiteout(myname(ep));
+ ep->e_flags &= ~NEW;
+ }
+ }
+ vprintf(stdout, "Add links\n");
+ for (i = ROOTINO; i < maxino; i++) {
+ ep = lookupino(i);
+ if (ep == NULL)
+ continue;
+ for (np = ep->e_links; np != NULL; np = np->e_links) {
+ if ((np->e_flags & NEW) == 0)
+ continue;
+ (void) strcpy(name, myname(ep));
+ if (ep->e_type == NODE) {
+ (void) linkit(name, myname(np), SYMLINK);
+ } else {
+ (void) linkit(name, myname(np), HARDLINK);
+ }
+ np->e_flags &= ~NEW;
+ }
+ }
+}
+
+/*
+ * Check the symbol table.
+ * We do this to insure that all the requested work was done, and
+ * that no temporary names remain.
+ */
+void
+checkrestore(void)
+{
+ struct entry *ep;
+ ino_t i;
+
+ vprintf(stdout, "Check the symbol table.\n");
+ for (i = WINO; i < maxino; i++) {
+ for (ep = lookupino(i); ep != NULL; ep = ep->e_links) {
+ ep->e_flags &= ~KEEP;
+ if (ep->e_type == NODE)
+ ep->e_flags &= ~(NEW|EXISTED);
+ if (ep->e_flags != 0)
+ badentry(ep, "incomplete operations");
+ }
+ }
+}
+
+/*
+ * Compare with the directory structure on the tape
+ * A paranoid check that things are as they should be.
+ */
+long
+verifyfile(char *name, ino_t ino, int type)
+{
+ struct entry *np, *ep;
+ long descend = GOOD;
+
+ ep = lookupname(name);
+ if (ep == NULL) {
+ fprintf(stderr, "Warning: missing name %s\n", name);
+ return (FAIL);
+ }
+ np = lookupino(ino);
+ if (np != ep)
+ descend = FAIL;
+ for ( ; np != NULL; np = np->e_links)
+ if (np == ep)
+ break;
+ if (np == NULL)
+ panic("missing inumber %ju\n", (uintmax_t)ino);
+ if (ep->e_type == LEAF && type != LEAF)
+ badentry(ep, "type should be LEAF");
+ return (descend);
+}
diff --git a/sbin/restore/restore.h b/sbin/restore/restore.h
new file mode 100644
index 0000000..5d02bdf
--- /dev/null
+++ b/sbin/restore/restore.h
@@ -0,0 +1,153 @@
+/*-
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ * (c) UNIX System Laboratories, Inc.
+ * All or some portions of this file are derived from material licensed
+ * to the University of California by American Telephone and Telegraph
+ * Co. or Unix System Laboratories, Inc. and are reproduced herein with
+ * the permission of UNIX System Laboratories, Inc.
+ *
+ * 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.
+ * 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.
+ *
+ * @(#)restore.h 8.3 (Berkeley) 9/13/94
+ * $FreeBSD$
+ */
+
+/*
+ * Flags
+ */
+extern int bflag; /* set input block size */
+extern int dflag; /* print out debugging info */
+extern int Dflag; /* degraded mode - try hard to get stuff back */
+extern int hflag; /* restore hierarchies */
+extern int mflag; /* restore by name instead of inode number */
+extern int Nflag; /* do not write the disk */
+extern int uflag; /* unlink symlink targets */
+extern int vflag; /* print out actions taken */
+extern int yflag; /* always try to recover from tape errors */
+/*
+ * Global variables
+ */
+extern char *dumpmap; /* map of inodes on this dump tape */
+extern char *usedinomap; /* map of inodes that are in use on this fs */
+extern ino_t maxino; /* highest numbered inode in this file system */
+extern long dumpnum; /* location of the dump on this tape */
+extern long volno; /* current volume being read */
+extern long ntrec; /* number of TP_BSIZE records per tape block */
+extern time_t dumptime; /* time that this dump begins */
+extern time_t dumpdate; /* time that this dump was made */
+extern char command; /* opration being performed */
+extern FILE *terminal; /* file descriptor for the terminal input */
+extern int Bcvt; /* need byte swapping on inodes and dirs */
+extern int oldinofmt; /* reading tape with FreeBSD 1 format inodes */
+
+/*
+ * Each file in the file system is described by one of these entries
+ */
+struct entry {
+ char *e_name; /* the current name of this entry */
+ u_char e_namlen; /* length of this name */
+ char e_type; /* type of this entry, see below */
+ short e_flags; /* status flags, see below */
+ ino_t e_ino; /* inode number in previous file sys */
+ long e_index; /* unique index (for dumpped table) */
+ struct entry *e_parent; /* pointer to parent directory (..) */
+ struct entry *e_sibling; /* next element in this directory (.) */
+ struct entry *e_links; /* hard links to this inode */
+ struct entry *e_entries; /* for directories, their entries */
+ struct entry *e_next; /* hash chain list */
+};
+/* types */
+#define LEAF 1 /* non-directory entry */
+#define NODE 2 /* directory entry */
+#define LINK 4 /* synthesized type, stripped by addentry */
+/* flags */
+#define EXTRACT 0x0001 /* entry is to be replaced from the tape */
+#define NEW 0x0002 /* a new entry to be extracted */
+#define KEEP 0x0004 /* entry is not to change */
+#define REMOVED 0x0010 /* entry has been removed */
+#define TMPNAME 0x0020 /* entry has been given a temporary name */
+#define EXISTED 0x0040 /* directory already existed during extract */
+
+/*
+ * Constants associated with entry structs
+ */
+#define HARDLINK 1
+#define SYMLINK 2
+#define TMPHDR "RSTTMP"
+
+/*
+ * The entry describes the next file available on the tape
+ */
+struct context {
+ short action; /* action being taken on this file */
+ mode_t mode; /* mode of file */
+ ino_t ino; /* inumber of file */
+ uid_t uid; /* file owner */
+ gid_t gid; /* file group */
+ int file_flags; /* status flags (chflags) */
+ int rdev; /* device number of file */
+ time_t atime_sec; /* access time seconds */
+ time_t mtime_sec; /* modified time seconds */
+ time_t birthtime_sec; /* creation time seconds */
+ int atime_nsec; /* access time nanoseconds */
+ int mtime_nsec; /* modified time nanoseconds */
+ int birthtime_nsec; /* creation time nanoseconds */
+ int extsize; /* size of extended attribute data */
+ off_t size; /* size of file */
+ char *name; /* name of file */
+} curfile;
+/* actions */
+#define USING 1 /* extracting from the tape */
+#define SKIP 2 /* skipping */
+#define UNKNOWN 3 /* disposition or starting point is unknown */
+
+/*
+ * Definitions for library routines operating on directories.
+ */
+typedef struct rstdirdesc RST_DIR;
+
+/*
+ * Flags to setdirmodes.
+ */
+#define FORCE 0x0001
+
+/*
+ * Useful macros
+ */
+#define TSTINO(ino, map) \
+ (map[(u_int)((ino) - 1) / CHAR_BIT] & \
+ (1 << ((u_int)((ino) - 1) % CHAR_BIT)))
+#define SETINO(ino, map) \
+ map[(u_int)((ino) - 1) / CHAR_BIT] |= \
+ 1 << ((u_int)((ino) - 1) % CHAR_BIT)
+
+#define dprintf if (dflag) fprintf
+#define vprintf if (vflag) fprintf
+
+#define GOOD 1
+#define FAIL 0
+
+#define NFS_DR_NEWINODEFMT 0x2 /* Tape uses 4.4 BSD inode format */
diff --git a/sbin/restore/symtab.c b/sbin/restore/symtab.c
new file mode 100644
index 0000000..9d0313d
--- /dev/null
+++ b/sbin/restore/symtab.c
@@ -0,0 +1,615 @@
+/*
+ * Copyright (c) 1983, 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.
+ * 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 lint
+#if 0
+static char sccsid[] = "@(#)symtab.c 8.3 (Berkeley) 4/28/95";
+#endif
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+/*
+ * These routines maintain the symbol table which tracks the state
+ * of the file system being restored. They provide lookup by either
+ * name or inode number. They also provide for creation, deletion,
+ * and renaming of entries. Because of the dynamic nature of pathnames,
+ * names should not be saved, but always constructed just before they
+ * are needed, by calling "myname".
+ */
+
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include <ufs/ufs/dinode.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "restore.h"
+#include "extern.h"
+
+/*
+ * The following variables define the inode symbol table.
+ * The primary hash table is dynamically allocated based on
+ * the number of inodes in the file system (maxino), scaled by
+ * HASHFACTOR. The variable "entry" points to the hash table;
+ * the variable "entrytblsize" indicates its size (in entries).
+ */
+#define HASHFACTOR 5
+static struct entry **entry;
+static long entrytblsize;
+
+static void addino(ino_t, struct entry *);
+static struct entry *lookupparent(char *);
+static void removeentry(struct entry *);
+
+/*
+ * Look up an entry by inode number
+ */
+struct entry *
+lookupino(ino_t inum)
+{
+ struct entry *ep;
+
+ if (inum < WINO || inum >= maxino)
+ return (NULL);
+ for (ep = entry[inum % entrytblsize]; ep != NULL; ep = ep->e_next)
+ if (ep->e_ino == inum)
+ return (ep);
+ return (NULL);
+}
+
+/*
+ * Add an entry into the entry table
+ */
+static void
+addino(ino_t inum, struct entry *np)
+{
+ struct entry **epp;
+
+ if (inum < WINO || inum >= maxino)
+ panic("addino: out of range %ju\n", (uintmax_t)inum);
+ epp = &entry[inum % entrytblsize];
+ np->e_ino = inum;
+ np->e_next = *epp;
+ *epp = np;
+ if (dflag)
+ for (np = np->e_next; np != NULL; np = np->e_next)
+ if (np->e_ino == inum)
+ badentry(np, "duplicate inum");
+}
+
+/*
+ * Delete an entry from the entry table
+ */
+void
+deleteino(ino_t inum)
+{
+ struct entry *next;
+ struct entry **prev;
+
+ if (inum < WINO || inum >= maxino)
+ panic("deleteino: out of range %ju\n", (uintmax_t)inum);
+ prev = &entry[inum % entrytblsize];
+ for (next = *prev; next != NULL; next = next->e_next) {
+ if (next->e_ino == inum) {
+ next->e_ino = 0;
+ *prev = next->e_next;
+ return;
+ }
+ prev = &next->e_next;
+ }
+ panic("deleteino: %ju not found\n", (uintmax_t)inum);
+}
+
+/*
+ * Look up an entry by name
+ */
+struct entry *
+lookupname(char *name)
+{
+ struct entry *ep;
+ char *np, *cp;
+ char buf[MAXPATHLEN];
+
+ cp = name;
+ for (ep = lookupino(ROOTINO); ep != NULL; ep = ep->e_entries) {
+ for (np = buf; *cp != '/' && *cp != '\0' &&
+ np < &buf[sizeof(buf)]; )
+ *np++ = *cp++;
+ if (np == &buf[sizeof(buf)])
+ break;
+ *np = '\0';
+ for ( ; ep != NULL; ep = ep->e_sibling)
+ if (strcmp(ep->e_name, buf) == 0)
+ break;
+ if (ep == NULL)
+ break;
+ if (*cp++ == '\0')
+ return (ep);
+ }
+ return (NULL);
+}
+
+/*
+ * Look up the parent of a pathname
+ */
+static struct entry *
+lookupparent(char *name)
+{
+ struct entry *ep;
+ char *tailindex;
+
+ tailindex = strrchr(name, '/');
+ if (tailindex == NULL)
+ return (NULL);
+ *tailindex = '\0';
+ ep = lookupname(name);
+ *tailindex = '/';
+ if (ep == NULL)
+ return (NULL);
+ if (ep->e_type != NODE)
+ panic("%s is not a directory\n", name);
+ return (ep);
+}
+
+/*
+ * Determine the current pathname of a node or leaf
+ */
+char *
+myname(struct entry *ep)
+{
+ char *cp;
+ static char namebuf[MAXPATHLEN];
+
+ for (cp = &namebuf[MAXPATHLEN - 2]; cp > &namebuf[ep->e_namlen]; ) {
+ cp -= ep->e_namlen;
+ memmove(cp, ep->e_name, (long)ep->e_namlen);
+ if (ep == lookupino(ROOTINO))
+ return (cp);
+ *(--cp) = '/';
+ ep = ep->e_parent;
+ }
+ panic("%s: pathname too long\n", cp);
+ return(cp);
+}
+
+/*
+ * Unused symbol table entries are linked together on a free list
+ * headed by the following pointer.
+ */
+static struct entry *freelist = NULL;
+
+/*
+ * add an entry to the symbol table
+ */
+struct entry *
+addentry(char *name, ino_t inum, int type)
+{
+ struct entry *np, *ep;
+
+ if (freelist != NULL) {
+ np = freelist;
+ freelist = np->e_next;
+ memset(np, 0, (long)sizeof(struct entry));
+ } else {
+ np = (struct entry *)calloc(1, sizeof(struct entry));
+ if (np == NULL)
+ panic("no memory to extend symbol table\n");
+ }
+ np->e_type = type & ~LINK;
+ ep = lookupparent(name);
+ if (ep == NULL) {
+ if (inum != ROOTINO || lookupino(ROOTINO) != NULL)
+ panic("bad name to addentry %s\n", name);
+ np->e_name = savename(name);
+ np->e_namlen = strlen(name);
+ np->e_parent = np;
+ addino(ROOTINO, np);
+ return (np);
+ }
+ np->e_name = savename(strrchr(name, '/') + 1);
+ np->e_namlen = strlen(np->e_name);
+ np->e_parent = ep;
+ np->e_sibling = ep->e_entries;
+ ep->e_entries = np;
+ if (type & LINK) {
+ ep = lookupino(inum);
+ if (ep == NULL)
+ panic("link to non-existent name\n");
+ np->e_ino = inum;
+ np->e_links = ep->e_links;
+ ep->e_links = np;
+ } else if (inum != 0) {
+ if (lookupino(inum) != NULL)
+ panic("duplicate entry\n");
+ addino(inum, np);
+ }
+ return (np);
+}
+
+/*
+ * delete an entry from the symbol table
+ */
+void
+freeentry(struct entry *ep)
+{
+ struct entry *np;
+ ino_t inum;
+
+ if (ep->e_flags != REMOVED)
+ badentry(ep, "not marked REMOVED");
+ if (ep->e_type == NODE) {
+ if (ep->e_links != NULL)
+ badentry(ep, "freeing referenced directory");
+ if (ep->e_entries != NULL)
+ badentry(ep, "freeing non-empty directory");
+ }
+ if (ep->e_ino != 0) {
+ np = lookupino(ep->e_ino);
+ if (np == NULL)
+ badentry(ep, "lookupino failed");
+ if (np == ep) {
+ inum = ep->e_ino;
+ deleteino(inum);
+ if (ep->e_links != NULL)
+ addino(inum, ep->e_links);
+ } else {
+ for (; np != NULL; np = np->e_links) {
+ if (np->e_links == ep) {
+ np->e_links = ep->e_links;
+ break;
+ }
+ }
+ if (np == NULL)
+ badentry(ep, "link not found");
+ }
+ }
+ removeentry(ep);
+ freename(ep->e_name);
+ ep->e_next = freelist;
+ freelist = ep;
+}
+
+/*
+ * Relocate an entry in the tree structure
+ */
+void
+moveentry(struct entry *ep, char *newname)
+{
+ struct entry *np;
+ char *cp;
+
+ np = lookupparent(newname);
+ if (np == NULL)
+ badentry(ep, "cannot move ROOT");
+ if (np != ep->e_parent) {
+ removeentry(ep);
+ ep->e_parent = np;
+ ep->e_sibling = np->e_entries;
+ np->e_entries = ep;
+ }
+ cp = strrchr(newname, '/') + 1;
+ freename(ep->e_name);
+ ep->e_name = savename(cp);
+ ep->e_namlen = strlen(cp);
+ if (strcmp(gentempname(ep), ep->e_name) == 0)
+ ep->e_flags |= TMPNAME;
+ else
+ ep->e_flags &= ~TMPNAME;
+}
+
+/*
+ * Remove an entry in the tree structure
+ */
+static void
+removeentry(struct entry *ep)
+{
+ struct entry *np;
+
+ np = ep->e_parent;
+ if (np->e_entries == ep) {
+ np->e_entries = ep->e_sibling;
+ } else {
+ for (np = np->e_entries; np != NULL; np = np->e_sibling) {
+ if (np->e_sibling == ep) {
+ np->e_sibling = ep->e_sibling;
+ break;
+ }
+ }
+ if (np == NULL)
+ badentry(ep, "cannot find entry in parent list");
+ }
+}
+
+/*
+ * Table of unused string entries, sorted by length.
+ *
+ * Entries are allocated in STRTBLINCR sized pieces so that names
+ * of similar lengths can use the same entry. The value of STRTBLINCR
+ * is chosen so that every entry has at least enough space to hold
+ * a "struct strtbl" header. Thus every entry can be linked onto an
+ * appropriate free list.
+ *
+ * NB. The macro "allocsize" below assumes that "struct strhdr"
+ * has a size that is a power of two.
+ */
+struct strhdr {
+ struct strhdr *next;
+};
+
+#define STRTBLINCR (sizeof(struct strhdr))
+#define allocsize(size) (((size) + 1 + STRTBLINCR - 1) & ~(STRTBLINCR - 1))
+
+static struct strhdr strtblhdr[allocsize(NAME_MAX) / STRTBLINCR];
+
+/*
+ * Allocate space for a name. It first looks to see if it already
+ * has an appropriate sized entry, and if not allocates a new one.
+ */
+char *
+savename(char *name)
+{
+ struct strhdr *np;
+ long len;
+ char *cp;
+
+ if (name == NULL)
+ panic("bad name\n");
+ len = strlen(name);
+ np = strtblhdr[len / STRTBLINCR].next;
+ if (np != NULL) {
+ strtblhdr[len / STRTBLINCR].next = np->next;
+ cp = (char *)np;
+ } else {
+ cp = malloc((unsigned)allocsize(len));
+ if (cp == NULL)
+ panic("no space for string table\n");
+ }
+ (void) strcpy(cp, name);
+ return (cp);
+}
+
+/*
+ * Free space for a name. The resulting entry is linked onto the
+ * appropriate free list.
+ */
+void
+freename(char *name)
+{
+ struct strhdr *tp, *np;
+
+ tp = &strtblhdr[strlen(name) / STRTBLINCR];
+ np = (struct strhdr *)name;
+ np->next = tp->next;
+ tp->next = np;
+}
+
+/*
+ * Useful quantities placed at the end of a dumped symbol table.
+ */
+struct symtableheader {
+ int32_t volno;
+ int32_t stringsize;
+ int32_t entrytblsize;
+ time_t dumptime;
+ time_t dumpdate;
+ ino_t maxino;
+ int32_t ntrec;
+};
+
+/*
+ * dump a snapshot of the symbol table
+ */
+void
+dumpsymtable(char *filename, long checkpt)
+{
+ struct entry *ep, *tep;
+ ino_t i;
+ struct entry temp, *tentry;
+ long mynum = 1, stroff = 0;
+ FILE *fd;
+ struct symtableheader hdr;
+
+ vprintf(stdout, "Checkpointing the restore\n");
+ if (Nflag)
+ return;
+ if ((fd = fopen(filename, "w")) == NULL) {
+ fprintf(stderr, "fopen: %s\n", strerror(errno));
+ panic("cannot create save file %s for symbol table\n",
+ filename);
+ done(1);
+ }
+ clearerr(fd);
+ /*
+ * Assign indices to each entry
+ * Write out the string entries
+ */
+ for (i = WINO; i <= maxino; i++) {
+ for (ep = lookupino(i); ep != NULL; ep = ep->e_links) {
+ ep->e_index = mynum++;
+ (void) fwrite(ep->e_name, sizeof(char),
+ (int)allocsize(ep->e_namlen), fd);
+ }
+ }
+ /*
+ * Convert pointers to indexes, and output
+ */
+ tep = &temp;
+ stroff = 0;
+ for (i = WINO; i <= maxino; i++) {
+ for (ep = lookupino(i); ep != NULL; ep = ep->e_links) {
+ memmove(tep, ep, (long)sizeof(struct entry));
+ tep->e_name = (char *)stroff;
+ stroff += allocsize(ep->e_namlen);
+ tep->e_parent = (struct entry *)ep->e_parent->e_index;
+ if (ep->e_links != NULL)
+ tep->e_links =
+ (struct entry *)ep->e_links->e_index;
+ if (ep->e_sibling != NULL)
+ tep->e_sibling =
+ (struct entry *)ep->e_sibling->e_index;
+ if (ep->e_entries != NULL)
+ tep->e_entries =
+ (struct entry *)ep->e_entries->e_index;
+ if (ep->e_next != NULL)
+ tep->e_next =
+ (struct entry *)ep->e_next->e_index;
+ (void) fwrite((char *)tep, sizeof(struct entry), 1, fd);
+ }
+ }
+ /*
+ * Convert entry pointers to indexes, and output
+ */
+ for (i = 0; i < entrytblsize; i++) {
+ if (entry[i] == NULL)
+ tentry = NULL;
+ else
+ tentry = (struct entry *)entry[i]->e_index;
+ (void) fwrite((char *)&tentry, sizeof(struct entry *), 1, fd);
+ }
+ hdr.volno = checkpt;
+ hdr.maxino = maxino;
+ hdr.entrytblsize = entrytblsize;
+ hdr.stringsize = stroff;
+ hdr.dumptime = dumptime;
+ hdr.dumpdate = dumpdate;
+ hdr.ntrec = ntrec;
+ (void) fwrite((char *)&hdr, sizeof(struct symtableheader), 1, fd);
+ if (ferror(fd)) {
+ fprintf(stderr, "fwrite: %s\n", strerror(errno));
+ panic("output error to file %s writing symbol table\n",
+ filename);
+ }
+ (void) fclose(fd);
+}
+
+/*
+ * Initialize a symbol table from a file
+ */
+void
+initsymtable(char *filename)
+{
+ char *base;
+ long tblsize;
+ struct entry *ep;
+ struct entry *baseep, *lep;
+ struct symtableheader hdr;
+ struct stat stbuf;
+ long i;
+ int fd;
+
+ vprintf(stdout, "Initialize symbol table.\n");
+ if (filename == NULL) {
+ entrytblsize = maxino / HASHFACTOR;
+ entry = (struct entry **)
+ calloc((unsigned)entrytblsize, sizeof(struct entry *));
+ if (entry == (struct entry **)NULL)
+ panic("no memory for entry table\n");
+ ep = addentry(".", ROOTINO, NODE);
+ ep->e_flags |= NEW;
+ return;
+ }
+ if ((fd = open(filename, O_RDONLY, 0)) < 0) {
+ fprintf(stderr, "open: %s\n", strerror(errno));
+ panic("cannot open symbol table file %s\n", filename);
+ }
+ if (fstat(fd, &stbuf) < 0) {
+ fprintf(stderr, "stat: %s\n", strerror(errno));
+ panic("cannot stat symbol table file %s\n", filename);
+ }
+ tblsize = stbuf.st_size - sizeof(struct symtableheader);
+ base = calloc(sizeof(char), (unsigned)tblsize);
+ if (base == NULL)
+ panic("cannot allocate space for symbol table\n");
+ if (read(fd, base, (int)tblsize) < 0 ||
+ read(fd, (char *)&hdr, sizeof(struct symtableheader)) < 0) {
+ fprintf(stderr, "read: %s\n", strerror(errno));
+ panic("cannot read symbol table file %s\n", filename);
+ }
+ switch (command) {
+ case 'r':
+ /*
+ * For normal continuation, insure that we are using
+ * the next incremental tape
+ */
+ if (hdr.dumpdate != dumptime) {
+ if (hdr.dumpdate < dumptime)
+ fprintf(stderr, "Incremental tape too low\n");
+ else
+ fprintf(stderr, "Incremental tape too high\n");
+ done(1);
+ }
+ break;
+ case 'R':
+ /*
+ * For restart, insure that we are using the same tape
+ */
+ curfile.action = SKIP;
+ dumptime = hdr.dumptime;
+ dumpdate = hdr.dumpdate;
+ if (!bflag)
+ newtapebuf(hdr.ntrec);
+ getvol(hdr.volno);
+ break;
+ default:
+ panic("initsymtable called from command %c\n", command);
+ break;
+ }
+ maxino = hdr.maxino;
+ entrytblsize = hdr.entrytblsize;
+ entry = (struct entry **)
+ (base + tblsize - (entrytblsize * sizeof(struct entry *)));
+ baseep = (struct entry *)(base + hdr.stringsize - sizeof(struct entry));
+ lep = (struct entry *)entry;
+ for (i = 0; i < entrytblsize; i++) {
+ if (entry[i] == NULL)
+ continue;
+ entry[i] = &baseep[(long)entry[i]];
+ }
+ for (ep = &baseep[1]; ep < lep; ep++) {
+ ep->e_name = base + (long)ep->e_name;
+ ep->e_parent = &baseep[(long)ep->e_parent];
+ if (ep->e_sibling != NULL)
+ ep->e_sibling = &baseep[(long)ep->e_sibling];
+ if (ep->e_links != NULL)
+ ep->e_links = &baseep[(long)ep->e_links];
+ if (ep->e_entries != NULL)
+ ep->e_entries = &baseep[(long)ep->e_entries];
+ if (ep->e_next != NULL)
+ ep->e_next = &baseep[(long)ep->e_next];
+ }
+}
diff --git a/sbin/restore/tape.c b/sbin/restore/tape.c
new file mode 100644
index 0000000..9c9890f
--- /dev/null
+++ b/sbin/restore/tape.c
@@ -0,0 +1,1756 @@
+/*
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ * (c) UNIX System Laboratories, Inc.
+ * All or some portions of this file are derived from material licensed
+ * to the University of California by American Telephone and Telegraph
+ * Co. or Unix System Laboratories, Inc. and are reproduced herein with
+ * the permission of UNIX System Laboratories, Inc.
+ *
+ * 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.
+ * 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 lint
+#if 0
+static char sccsid[] = "@(#)tape.c 8.9 (Berkeley) 5/1/95";
+#endif
+#endif /* not lint */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/file.h>
+#include <sys/mtio.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/extattr.h>
+#include <sys/acl.h>
+
+#include <ufs/ufs/extattr.h>
+#include <ufs/ufs/dinode.h>
+#include <protocols/dumprestore.h>
+
+#include <errno.h>
+#include <limits.h>
+#include <paths.h>
+#include <setjmp.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <timeconv.h>
+#include <unistd.h>
+
+#include "restore.h"
+#include "extern.h"
+
+static long fssize = MAXBSIZE;
+static int mt = -1;
+static int pipein = 0;
+static int pipecmdin = 0;
+static FILE *popenfp = NULL;
+static char *magtape;
+static int blkcnt;
+static int numtrec;
+static char *tapebuf;
+static union u_spcl endoftapemark;
+static long byteslide = 0;
+static long blksread; /* blocks read since last header */
+static int64_t tapeaddr = 0; /* current TP_BSIZE tape record */
+static long tapesread;
+static jmp_buf restart;
+static int gettingfile = 0; /* restart has a valid frame */
+static char *host = NULL;
+static int readmapflag;
+
+static int ofile;
+static char *map;
+static char lnkbuf[MAXPATHLEN + 1];
+static int pathlen;
+
+int Bcvt; /* Swap Bytes */
+int oldinofmt; /* FreeBSD 1 inode format needs cvt */
+
+#define FLUSHTAPEBUF() blkcnt = ntrec + 1
+
+char *namespace_names[] = EXTATTR_NAMESPACE_NAMES;
+
+static void accthdr(struct s_spcl *);
+static int checksum(int *);
+static void findinode(struct s_spcl *);
+static void findtapeblksize(void);
+static char *setupextattr(int);
+static void xtrattr(char *, long);
+static void set_extattr_link(char *, void *, int);
+static void set_extattr_fd(int, char *, void *, int);
+static int gethead(struct s_spcl *);
+static void readtape(char *);
+static void setdumpnum(void);
+static u_long swabl(u_long);
+static u_char *swablong(u_char *, int);
+static u_char *swabshort(u_char *, int);
+static void terminateinput(void);
+static void xtrfile(char *, long);
+static void xtrlnkfile(char *, long);
+static void xtrlnkskip(char *, long);
+static void xtrmap(char *, long);
+static void xtrmapskip(char *, long);
+static void xtrskip(char *, long);
+
+/*
+ * Set up an input source
+ */
+void
+setinput(char *source, int ispipecommand)
+{
+ FLUSHTAPEBUF();
+ if (bflag)
+ newtapebuf(ntrec);
+ else
+ newtapebuf(NTREC > HIGHDENSITYTREC ? NTREC : HIGHDENSITYTREC);
+ terminal = stdin;
+
+ if (ispipecommand)
+ pipecmdin++;
+ else
+#ifdef RRESTORE
+ if (strchr(source, ':')) {
+ host = source;
+ source = strchr(host, ':');
+ *source++ = '\0';
+ if (rmthost(host) == 0)
+ done(1);
+ } else
+#endif
+ if (strcmp(source, "-") == 0) {
+ /*
+ * Since input is coming from a pipe we must establish
+ * our own connection to the terminal.
+ */
+ terminal = fopen(_PATH_TTY, "r");
+ if (terminal == NULL) {
+ (void)fprintf(stderr, "cannot open %s: %s\n",
+ _PATH_TTY, strerror(errno));
+ terminal = fopen(_PATH_DEVNULL, "r");
+ if (terminal == NULL) {
+ (void)fprintf(stderr, "cannot open %s: %s\n",
+ _PATH_DEVNULL, strerror(errno));
+ done(1);
+ }
+ }
+ pipein++;
+ }
+ /* no longer need or want root privileges */
+ if (setuid(getuid()) != 0) {
+ fprintf(stderr, "setuid failed\n");
+ done(1);
+ }
+ magtape = strdup(source);
+ if (magtape == NULL) {
+ fprintf(stderr, "Cannot allocate space for magtape buffer\n");
+ done(1);
+ }
+}
+
+void
+newtapebuf(long size)
+{
+ static int tapebufsize = -1;
+
+ ntrec = size;
+ if (size <= tapebufsize)
+ return;
+ if (tapebuf != NULL)
+ free(tapebuf - TP_BSIZE);
+ tapebuf = malloc((size+1) * TP_BSIZE);
+ if (tapebuf == NULL) {
+ fprintf(stderr, "Cannot allocate space for tape buffer\n");
+ done(1);
+ }
+ tapebuf += TP_BSIZE;
+ tapebufsize = size;
+}
+
+/*
+ * Verify that the tape drive can be accessed and
+ * that it actually is a dump tape.
+ */
+void
+setup(void)
+{
+ int i, j, *ip;
+ struct stat stbuf;
+
+ vprintf(stdout, "Verify tape and initialize maps\n");
+ if (pipecmdin) {
+ if (setenv("RESTORE_VOLUME", "1", 1) == -1) {
+ fprintf(stderr, "Cannot set $RESTORE_VOLUME: %s\n",
+ strerror(errno));
+ done(1);
+ }
+ popenfp = popen(magtape, "r");
+ mt = popenfp ? fileno(popenfp) : -1;
+ } else
+#ifdef RRESTORE
+ if (host)
+ mt = rmtopen(magtape, 0);
+ else
+#endif
+ if (pipein)
+ mt = 0;
+ else
+ mt = open(magtape, O_RDONLY, 0);
+ if (mt < 0) {
+ fprintf(stderr, "%s: %s\n", magtape, strerror(errno));
+ done(1);
+ }
+ volno = 1;
+ setdumpnum();
+ FLUSHTAPEBUF();
+ if (!pipein && !pipecmdin && !bflag)
+ findtapeblksize();
+ if (gethead(&spcl) == FAIL) {
+ fprintf(stderr, "Tape is not a dump tape\n");
+ done(1);
+ }
+ if (pipein) {
+ endoftapemark.s_spcl.c_magic = FS_UFS2_MAGIC;
+ endoftapemark.s_spcl.c_type = TS_END;
+ ip = (int *)&endoftapemark;
+ j = sizeof(union u_spcl) / sizeof(int);
+ i = 0;
+ do
+ i += *ip++;
+ while (--j);
+ endoftapemark.s_spcl.c_checksum = CHECKSUM - i;
+ }
+ if (vflag || command == 't')
+ printdumpinfo();
+ dumptime = _time64_to_time(spcl.c_ddate);
+ dumpdate = _time64_to_time(spcl.c_date);
+ if (stat(".", &stbuf) < 0) {
+ fprintf(stderr, "cannot stat .: %s\n", strerror(errno));
+ done(1);
+ }
+ if (stbuf.st_blksize > 0 && stbuf.st_blksize < TP_BSIZE )
+ fssize = TP_BSIZE;
+ if (stbuf.st_blksize >= TP_BSIZE && stbuf.st_blksize <= MAXBSIZE)
+ fssize = stbuf.st_blksize;
+ if (((TP_BSIZE - 1) & stbuf.st_blksize) != 0) {
+ fprintf(stderr, "Warning: filesystem with non-multiple-of-%d "
+ "blocksize (%d);\n", TP_BSIZE, stbuf.st_blksize);
+ fssize = roundup(fssize, TP_BSIZE);
+ fprintf(stderr, "\twriting using blocksize %ld\n", fssize);
+ }
+ if (spcl.c_volume != 1) {
+ fprintf(stderr, "Tape is not volume 1 of the dump\n");
+ done(1);
+ }
+ if (gethead(&spcl) == FAIL) {
+ dprintf(stdout, "header read failed at %ld blocks\n", blksread);
+ panic("no header after volume mark!\n");
+ }
+ findinode(&spcl);
+ if (spcl.c_type != TS_CLRI) {
+ fprintf(stderr, "Cannot find file removal list\n");
+ done(1);
+ }
+ maxino = (spcl.c_count * TP_BSIZE * NBBY) + 1;
+ dprintf(stdout, "maxino = %ju\n", (uintmax_t)maxino);
+ map = calloc((unsigned)1, (unsigned)howmany(maxino, NBBY));
+ if (map == NULL)
+ panic("no memory for active inode map\n");
+ usedinomap = map;
+ curfile.action = USING;
+ getfile(xtrmap, xtrmapskip, xtrmapskip);
+ if (spcl.c_type != TS_BITS) {
+ fprintf(stderr, "Cannot find file dump list\n");
+ done(1);
+ }
+ map = calloc((unsigned)1, (unsigned)howmany(maxino, NBBY));
+ if (map == (char *)NULL)
+ panic("no memory for file dump list\n");
+ dumpmap = map;
+ curfile.action = USING;
+ getfile(xtrmap, xtrmapskip, xtrmapskip);
+ /*
+ * If there may be whiteout entries on the tape, pretend that the
+ * whiteout inode exists, so that the whiteout entries can be
+ * extracted.
+ */
+ SETINO(WINO, dumpmap);
+ /* 'r' restores don't call getvol() for tape 1, so mark it as read. */
+ if (command == 'r')
+ tapesread = 1;
+}
+
+/*
+ * Prompt user to load a new dump volume.
+ * "Nextvol" is the next suggested volume to use.
+ * This suggested volume is enforced when doing full
+ * or incremental restores, but can be overridden by
+ * the user when only extracting a subset of the files.
+ */
+void
+getvol(long nextvol)
+{
+ int64_t prevtapea;
+ long i, newvol, savecnt;
+ union u_spcl tmpspcl;
+# define tmpbuf tmpspcl.s_spcl
+ char buf[TP_BSIZE];
+
+ if (nextvol == 1) {
+ tapesread = 0;
+ gettingfile = 0;
+ }
+ prevtapea = tapeaddr;
+ savecnt = blksread;
+ if (pipein) {
+ if (nextvol != 1) {
+ panic("Changing volumes on pipe input?\n");
+ /* Avoid looping if we couldn't ask the user. */
+ if (yflag || ferror(terminal) || feof(terminal))
+ done(1);
+ }
+ if (volno == 1)
+ return;
+ goto gethdr;
+ }
+again:
+ if (pipein)
+ done(1); /* pipes do not get a second chance */
+ if (command == 'R' || command == 'r' || curfile.action != SKIP)
+ newvol = nextvol;
+ else
+ newvol = 0;
+ while (newvol <= 0) {
+ if (tapesread == 0) {
+ fprintf(stderr, "%s%s%s%s%s%s%s",
+ "You have not read any tapes yet.\n",
+ "If you are extracting just a few files,",
+ " start with the last volume\n",
+ "and work towards the first; restore",
+ " can quickly skip tapes that\n",
+ "have no further files to extract.",
+ " Otherwise, begin with volume 1.\n");
+ } else {
+ fprintf(stderr, "You have read volumes");
+ strcpy(buf, ": ");
+ for (i = 0; i < 32; i++)
+ if (tapesread & (1 << i)) {
+ fprintf(stderr, "%s%ld", buf, i + 1);
+ strcpy(buf, ", ");
+ }
+ fprintf(stderr, "\n");
+ }
+ do {
+ fprintf(stderr, "Specify next volume #: ");
+ (void) fflush(stderr);
+ if (fgets(buf, BUFSIZ, terminal) == NULL)
+ done(1);
+ } while (buf[0] == '\n');
+ newvol = atoi(buf);
+ if (newvol <= 0) {
+ fprintf(stderr,
+ "Volume numbers are positive numerics\n");
+ }
+ }
+ if (newvol == volno) {
+ tapesread |= 1 << (volno - 1);
+ return;
+ }
+ closemt();
+ fprintf(stderr, "Mount tape volume %ld\n", newvol);
+ fprintf(stderr, "Enter ``none'' if there are no more tapes\n");
+ fprintf(stderr, "otherwise enter tape name (default: %s) ", magtape);
+ (void) fflush(stderr);
+ if (fgets(buf, BUFSIZ, terminal) == NULL)
+ done(1);
+ if (!strcmp(buf, "none\n")) {
+ terminateinput();
+ return;
+ }
+ if (buf[0] != '\n') {
+ (void) strcpy(magtape, buf);
+ magtape[strlen(magtape) - 1] = '\0';
+ }
+ if (pipecmdin) {
+ char volno[sizeof("2147483647")];
+
+ (void)sprintf(volno, "%ld", newvol);
+ if (setenv("RESTORE_VOLUME", volno, 1) == -1) {
+ fprintf(stderr, "Cannot set $RESTORE_VOLUME: %s\n",
+ strerror(errno));
+ done(1);
+ }
+ popenfp = popen(magtape, "r");
+ mt = popenfp ? fileno(popenfp) : -1;
+ } else
+#ifdef RRESTORE
+ if (host)
+ mt = rmtopen(magtape, 0);
+ else
+#endif
+ mt = open(magtape, O_RDONLY, 0);
+
+ if (mt == -1) {
+ fprintf(stderr, "Cannot open %s\n", magtape);
+ volno = -1;
+ goto again;
+ }
+gethdr:
+ volno = newvol;
+ setdumpnum();
+ FLUSHTAPEBUF();
+ if (gethead(&tmpbuf) == FAIL) {
+ dprintf(stdout, "header read failed at %ld blocks\n", blksread);
+ fprintf(stderr, "tape is not dump tape\n");
+ volno = 0;
+ goto again;
+ }
+ if (tmpbuf.c_volume != volno) {
+ fprintf(stderr, "Wrong volume (%jd)\n",
+ (intmax_t)tmpbuf.c_volume);
+ volno = 0;
+ goto again;
+ }
+ if (_time64_to_time(tmpbuf.c_date) != dumpdate ||
+ _time64_to_time(tmpbuf.c_ddate) != dumptime) {
+ time_t t = _time64_to_time(tmpbuf.c_date);
+ fprintf(stderr, "Wrong dump date\n\tgot: %s", ctime(&t));
+ fprintf(stderr, "\twanted: %s", ctime(&dumpdate));
+ volno = 0;
+ goto again;
+ }
+ tapesread |= 1 << (volno - 1);
+ blksread = savecnt;
+ /*
+ * If continuing from the previous volume, skip over any
+ * blocks read already at the end of the previous volume.
+ *
+ * If coming to this volume at random, skip to the beginning
+ * of the next record.
+ */
+ dprintf(stdout, "last rec %jd, tape starts with %jd\n",
+ (intmax_t)prevtapea, (intmax_t)tmpbuf.c_tapea);
+ if (tmpbuf.c_type == TS_TAPE) {
+ if (curfile.action != USING) {
+ /*
+ * XXX Dump incorrectly sets c_count to 1 in the
+ * volume header of the first tape, so ignore
+ * c_count when volno == 1.
+ */
+ if (volno != 1)
+ for (i = tmpbuf.c_count; i > 0; i--)
+ readtape(buf);
+ } else if (tmpbuf.c_tapea <= prevtapea) {
+ /*
+ * Normally the value of c_tapea in the volume
+ * header is the record number of the header itself.
+ * However in the volume header following an EOT-
+ * terminated tape, it is the record number of the
+ * first continuation data block (dump bug?).
+ *
+ * The next record we want is `prevtapea + 1'.
+ */
+ i = prevtapea + 1 - tmpbuf.c_tapea;
+ dprintf(stderr, "Skipping %ld duplicate record%s.\n",
+ i, i > 1 ? "s" : "");
+ while (--i >= 0)
+ readtape(buf);
+ }
+ }
+ if (curfile.action == USING) {
+ if (volno == 1)
+ panic("active file into volume 1\n");
+ return;
+ }
+ (void) gethead(&spcl);
+ findinode(&spcl);
+ if (gettingfile) {
+ gettingfile = 0;
+ longjmp(restart, 1);
+ }
+}
+
+/*
+ * Handle unexpected EOF.
+ */
+static void
+terminateinput(void)
+{
+
+ if (gettingfile && curfile.action == USING) {
+ printf("Warning: %s %s\n",
+ "End-of-input encountered while extracting", curfile.name);
+ }
+ curfile.name = "<name unknown>";
+ curfile.action = UNKNOWN;
+ curfile.mode = 0;
+ curfile.ino = maxino;
+ if (gettingfile) {
+ gettingfile = 0;
+ longjmp(restart, 1);
+ }
+}
+
+/*
+ * handle multiple dumps per tape by skipping forward to the
+ * appropriate one.
+ */
+static void
+setdumpnum(void)
+{
+ struct mtop tcom;
+
+ if (dumpnum == 1 || volno != 1)
+ return;
+ if (pipein) {
+ fprintf(stderr, "Cannot have multiple dumps on pipe input\n");
+ done(1);
+ }
+ tcom.mt_op = MTFSF;
+ tcom.mt_count = dumpnum - 1;
+#ifdef RRESTORE
+ if (host)
+ rmtioctl(MTFSF, dumpnum - 1);
+ else
+#endif
+ if (!pipecmdin && ioctl(mt, MTIOCTOP, (char *)&tcom) < 0)
+ fprintf(stderr, "ioctl MTFSF: %s\n", strerror(errno));
+}
+
+void
+printdumpinfo(void)
+{
+ time_t t;
+ t = _time64_to_time(spcl.c_date);
+ fprintf(stdout, "Dump date: %s", ctime(&t));
+ t = _time64_to_time(spcl.c_ddate);
+ fprintf(stdout, "Dumped from: %s",
+ (spcl.c_ddate == 0) ? "the epoch\n" : ctime(&t));
+ if (spcl.c_host[0] == '\0')
+ return;
+ fprintf(stderr, "Level %jd dump of %s on %s:%s\n",
+ (intmax_t)spcl.c_level, spcl.c_filesys, spcl.c_host, spcl.c_dev);
+ fprintf(stderr, "Label: %s\n", spcl.c_label);
+}
+
+int
+extractfile(char *name)
+{
+ int flags;
+ uid_t uid;
+ gid_t gid;
+ mode_t mode;
+ int extsize;
+ struct timespec mtimep[2], ctimep[2];
+ struct entry *ep;
+ char *buf;
+
+ curfile.name = name;
+ curfile.action = USING;
+ mtimep[0].tv_sec = curfile.atime_sec;
+ mtimep[0].tv_nsec = curfile.atime_nsec;
+ mtimep[1].tv_sec = curfile.mtime_sec;
+ mtimep[1].tv_nsec = curfile.mtime_nsec;
+ ctimep[0].tv_sec = curfile.atime_sec;
+ ctimep[0].tv_nsec = curfile.atime_nsec;
+ ctimep[1].tv_sec = curfile.birthtime_sec;
+ ctimep[1].tv_nsec = curfile.birthtime_nsec;
+ extsize = curfile.extsize;
+ uid = getuid();
+ if (uid == 0)
+ uid = curfile.uid;
+ gid = curfile.gid;
+ mode = curfile.mode;
+ flags = curfile.file_flags;
+ switch (mode & IFMT) {
+
+ default:
+ fprintf(stderr, "%s: unknown file mode 0%o\n", name, mode);
+ skipfile();
+ return (FAIL);
+
+ case IFSOCK:
+ vprintf(stdout, "skipped socket %s\n", name);
+ skipfile();
+ return (GOOD);
+
+ case IFDIR:
+ if (mflag) {
+ ep = lookupname(name);
+ if (ep == NULL || ep->e_flags & EXTRACT)
+ panic("unextracted directory %s\n", name);
+ skipfile();
+ return (GOOD);
+ }
+ vprintf(stdout, "extract file %s\n", name);
+ return (genliteraldir(name, curfile.ino));
+
+ case IFLNK:
+ lnkbuf[0] = '\0';
+ pathlen = 0;
+ buf = setupextattr(extsize);
+ getfile(xtrlnkfile, xtrattr, xtrlnkskip);
+ if (pathlen == 0) {
+ vprintf(stdout,
+ "%s: zero length symbolic link (ignored)\n", name);
+ return (GOOD);
+ }
+ if (linkit(lnkbuf, name, SYMLINK) == GOOD) {
+ if (extsize > 0)
+ set_extattr_link(name, buf, extsize);
+ (void) lchown(name, uid, gid);
+ (void) lchmod(name, mode);
+ (void) utimensat(AT_FDCWD, name, ctimep,
+ AT_SYMLINK_NOFOLLOW);
+ (void) utimensat(AT_FDCWD, name, mtimep,
+ AT_SYMLINK_NOFOLLOW);
+ (void) lchflags(name, flags);
+ return (GOOD);
+ }
+ return (FAIL);
+
+ case IFIFO:
+ vprintf(stdout, "extract fifo %s\n", name);
+ if (Nflag) {
+ skipfile();
+ return (GOOD);
+ }
+ if (uflag)
+ (void) unlink(name);
+ if (mkfifo(name, 0600) < 0) {
+ fprintf(stderr, "%s: cannot create fifo: %s\n",
+ name, strerror(errno));
+ skipfile();
+ return (FAIL);
+ }
+ if (extsize == 0) {
+ skipfile();
+ } else {
+ buf = setupextattr(extsize);
+ getfile(xtrnull, xtrattr, xtrnull);
+ set_extattr_file(name, buf, extsize);
+ }
+ (void) chown(name, uid, gid);
+ (void) chmod(name, mode);
+ (void) utimensat(AT_FDCWD, name, ctimep, 0);
+ (void) utimensat(AT_FDCWD, name, mtimep, 0);
+ (void) chflags(name, flags);
+ return (GOOD);
+
+ case IFCHR:
+ case IFBLK:
+ vprintf(stdout, "extract special file %s\n", name);
+ if (Nflag) {
+ skipfile();
+ return (GOOD);
+ }
+ if (uflag)
+ (void) unlink(name);
+ if (mknod(name, (mode & (IFCHR | IFBLK)) | 0600,
+ (int)curfile.rdev) < 0) {
+ fprintf(stderr, "%s: cannot create special file: %s\n",
+ name, strerror(errno));
+ skipfile();
+ return (FAIL);
+ }
+ if (extsize == 0) {
+ skipfile();
+ } else {
+ buf = setupextattr(extsize);
+ getfile(xtrnull, xtrattr, xtrnull);
+ set_extattr_file(name, buf, extsize);
+ }
+ (void) chown(name, uid, gid);
+ (void) chmod(name, mode);
+ (void) utimensat(AT_FDCWD, name, ctimep, 0);
+ (void) utimensat(AT_FDCWD, name, mtimep, 0);
+ (void) chflags(name, flags);
+ return (GOOD);
+
+ case IFREG:
+ vprintf(stdout, "extract file %s\n", name);
+ if (Nflag) {
+ skipfile();
+ return (GOOD);
+ }
+ if (uflag)
+ (void) unlink(name);
+ if ((ofile = open(name, O_WRONLY | O_CREAT | O_TRUNC,
+ 0600)) < 0) {
+ fprintf(stderr, "%s: cannot create file: %s\n",
+ name, strerror(errno));
+ skipfile();
+ return (FAIL);
+ }
+ buf = setupextattr(extsize);
+ getfile(xtrfile, xtrattr, xtrskip);
+ if (extsize > 0)
+ set_extattr_fd(ofile, name, buf, extsize);
+ (void) fchown(ofile, uid, gid);
+ (void) fchmod(ofile, mode);
+ (void) futimens(ofile, ctimep);
+ (void) futimens(ofile, mtimep);
+ (void) fchflags(ofile, flags);
+ (void) close(ofile);
+ return (GOOD);
+ }
+ /* NOTREACHED */
+}
+
+/*
+ * Set attributes for a file.
+ */
+void
+set_extattr_file(char *name, void *buf, int size)
+{
+ struct extattr *eap, *eaend;
+
+ vprintf(stdout, "Set attributes for %s:", name);
+ eaend = buf + size;
+ for (eap = buf; eap < eaend; eap = EXTATTR_NEXT(eap)) {
+ /*
+ * Make sure this entry is complete.
+ */
+ if (EXTATTR_NEXT(eap) > eaend || eap->ea_length <= 0) {
+ dprintf(stdout, "\n\t%scorrupted",
+ eap == buf ? "" : "remainder ");
+ break;
+ }
+ if (eap->ea_namespace == EXTATTR_NAMESPACE_EMPTY)
+ continue;
+ vprintf(stdout, "\n\t%s, (%d bytes), %*s",
+ namespace_names[eap->ea_namespace], eap->ea_length,
+ eap->ea_namelength, eap->ea_name);
+ /*
+ * First we try the general attribute setting interface.
+ * However, some attributes can only be set by root or
+ * by using special interfaces (for example, ACLs).
+ */
+ if (extattr_set_file(name, eap->ea_namespace, eap->ea_name,
+ EXTATTR_CONTENT(eap), EXTATTR_CONTENT_SIZE(eap)) != -1) {
+ dprintf(stdout, " (set using extattr_set_file)");
+ continue;
+ }
+ /*
+ * If the general interface refuses to set the attribute,
+ * then we try all the specialized interfaces that we
+ * know about.
+ */
+ if (eap->ea_namespace == EXTATTR_NAMESPACE_SYSTEM &&
+ !strcmp(eap->ea_name, POSIX1E_ACL_ACCESS_EXTATTR_NAME)) {
+ if (acl_set_file(name, ACL_TYPE_ACCESS,
+ EXTATTR_CONTENT(eap)) != -1) {
+ dprintf(stdout, " (set using acl_set_file)");
+ continue;
+ }
+ }
+ if (eap->ea_namespace == EXTATTR_NAMESPACE_SYSTEM &&
+ !strcmp(eap->ea_name, POSIX1E_ACL_DEFAULT_EXTATTR_NAME)) {
+ if (acl_set_file(name, ACL_TYPE_DEFAULT,
+ EXTATTR_CONTENT(eap)) != -1) {
+ dprintf(stdout, " (set using acl_set_file)");
+ continue;
+ }
+ }
+ vprintf(stdout, " (unable to set)");
+ }
+ vprintf(stdout, "\n");
+}
+
+/*
+ * Set attributes for a symbolic link.
+ */
+static void
+set_extattr_link(char *name, void *buf, int size)
+{
+ struct extattr *eap, *eaend;
+
+ vprintf(stdout, "Set attributes for %s:", name);
+ eaend = buf + size;
+ for (eap = buf; eap < eaend; eap = EXTATTR_NEXT(eap)) {
+ /*
+ * Make sure this entry is complete.
+ */
+ if (EXTATTR_NEXT(eap) > eaend || eap->ea_length <= 0) {
+ dprintf(stdout, "\n\t%scorrupted",
+ eap == buf ? "" : "remainder ");
+ break;
+ }
+ if (eap->ea_namespace == EXTATTR_NAMESPACE_EMPTY)
+ continue;
+ vprintf(stdout, "\n\t%s, (%d bytes), %*s",
+ namespace_names[eap->ea_namespace], eap->ea_length,
+ eap->ea_namelength, eap->ea_name);
+ /*
+ * First we try the general attribute setting interface.
+ * However, some attributes can only be set by root or
+ * by using special interfaces (for example, ACLs).
+ */
+ if (extattr_set_link(name, eap->ea_namespace, eap->ea_name,
+ EXTATTR_CONTENT(eap), EXTATTR_CONTENT_SIZE(eap)) != -1) {
+ dprintf(stdout, " (set using extattr_set_link)");
+ continue;
+ }
+ /*
+ * If the general interface refuses to set the attribute,
+ * then we try all the specialized interfaces that we
+ * know about.
+ */
+ if (eap->ea_namespace == EXTATTR_NAMESPACE_SYSTEM &&
+ !strcmp(eap->ea_name, POSIX1E_ACL_ACCESS_EXTATTR_NAME)) {
+ if (acl_set_link_np(name, ACL_TYPE_ACCESS,
+ EXTATTR_CONTENT(eap)) != -1) {
+ dprintf(stdout, " (set using acl_set_link_np)");
+ continue;
+ }
+ }
+ if (eap->ea_namespace == EXTATTR_NAMESPACE_SYSTEM &&
+ !strcmp(eap->ea_name, POSIX1E_ACL_DEFAULT_EXTATTR_NAME)) {
+ if (acl_set_link_np(name, ACL_TYPE_DEFAULT,
+ EXTATTR_CONTENT(eap)) != -1) {
+ dprintf(stdout, " (set using acl_set_link_np)");
+ continue;
+ }
+ }
+ vprintf(stdout, " (unable to set)");
+ }
+ vprintf(stdout, "\n");
+}
+
+/*
+ * Set attributes on a file descriptor.
+ */
+static void
+set_extattr_fd(int fd, char *name, void *buf, int size)
+{
+ struct extattr *eap, *eaend;
+
+ vprintf(stdout, "Set attributes for %s:", name);
+ eaend = buf + size;
+ for (eap = buf; eap < eaend; eap = EXTATTR_NEXT(eap)) {
+ /*
+ * Make sure this entry is complete.
+ */
+ if (EXTATTR_NEXT(eap) > eaend || eap->ea_length <= 0) {
+ dprintf(stdout, "\n\t%scorrupted",
+ eap == buf ? "" : "remainder ");
+ break;
+ }
+ if (eap->ea_namespace == EXTATTR_NAMESPACE_EMPTY)
+ continue;
+ vprintf(stdout, "\n\t%s, (%d bytes), %*s",
+ namespace_names[eap->ea_namespace], eap->ea_length,
+ eap->ea_namelength, eap->ea_name);
+ /*
+ * First we try the general attribute setting interface.
+ * However, some attributes can only be set by root or
+ * by using special interfaces (for example, ACLs).
+ */
+ if (extattr_set_fd(fd, eap->ea_namespace, eap->ea_name,
+ EXTATTR_CONTENT(eap), EXTATTR_CONTENT_SIZE(eap)) != -1) {
+ dprintf(stdout, " (set using extattr_set_fd)");
+ continue;
+ }
+ /*
+ * If the general interface refuses to set the attribute,
+ * then we try all the specialized interfaces that we
+ * know about.
+ */
+ if (eap->ea_namespace == EXTATTR_NAMESPACE_SYSTEM &&
+ !strcmp(eap->ea_name, POSIX1E_ACL_ACCESS_EXTATTR_NAME)) {
+ if (acl_set_fd(fd, EXTATTR_CONTENT(eap)) != -1) {
+ dprintf(stdout, " (set using acl_set_fd)");
+ continue;
+ }
+ }
+ if (eap->ea_namespace == EXTATTR_NAMESPACE_SYSTEM &&
+ !strcmp(eap->ea_name, POSIX1E_ACL_DEFAULT_EXTATTR_NAME)) {
+ if (acl_set_file(name, ACL_TYPE_DEFAULT,
+ EXTATTR_CONTENT(eap)) != -1) {
+ dprintf(stdout, " (set using acl_set_file)");
+ continue;
+ }
+ }
+ vprintf(stdout, " (unable to set)");
+ }
+ vprintf(stdout, "\n");
+}
+
+/*
+ * skip over bit maps on the tape
+ */
+void
+skipmaps(void)
+{
+
+ while (spcl.c_type == TS_BITS || spcl.c_type == TS_CLRI)
+ skipfile();
+}
+
+/*
+ * skip over a file on the tape
+ */
+void
+skipfile(void)
+{
+
+ curfile.action = SKIP;
+ getfile(xtrnull, xtrnull, xtrnull);
+}
+
+/*
+ * Extract a file from the tape.
+ * When an allocated block is found it is passed to the fill function;
+ * when an unallocated block (hole) is found, a zeroed buffer is passed
+ * to the skip function.
+ */
+void
+getfile(void (*datafill)(char *, long), void (*attrfill)(char *, long),
+ void (*skip)(char *, long))
+{
+ int i;
+ off_t size;
+ int curblk, attrsize;
+ void (*fillit)(char *, long);
+ static char clearedbuf[MAXBSIZE];
+ char buf[MAXBSIZE / TP_BSIZE][TP_BSIZE];
+ char junk[TP_BSIZE];
+
+ curblk = 0;
+ size = spcl.c_size;
+ attrsize = spcl.c_extsize;
+ if (spcl.c_type == TS_END)
+ panic("ran off end of tape\n");
+ if (spcl.c_magic != FS_UFS2_MAGIC)
+ panic("not at beginning of a file\n");
+ if (!gettingfile && setjmp(restart) != 0)
+ return;
+ gettingfile++;
+ fillit = datafill;
+ if (size == 0 && attrsize > 0) {
+ fillit = attrfill;
+ size = attrsize;
+ attrsize = 0;
+ }
+loop:
+ for (i = 0; i < spcl.c_count; i++) {
+ if (!readmapflag && i > TP_NINDIR) {
+ if (Dflag) {
+ fprintf(stderr, "spcl.c_count = %jd\n",
+ (intmax_t)spcl.c_count);
+ break;
+ } else
+ panic("spcl.c_count = %jd\n",
+ (intmax_t)spcl.c_count);
+ }
+ if (readmapflag || spcl.c_addr[i]) {
+ readtape(&buf[curblk++][0]);
+ if (curblk == fssize / TP_BSIZE) {
+ (*fillit)((char *)buf, (long)(size > TP_BSIZE ?
+ fssize : (curblk - 1) * TP_BSIZE + size));
+ curblk = 0;
+ }
+ } else {
+ if (curblk > 0) {
+ (*fillit)((char *)buf, (long)(size > TP_BSIZE ?
+ curblk * TP_BSIZE :
+ (curblk - 1) * TP_BSIZE + size));
+ curblk = 0;
+ }
+ (*skip)(clearedbuf, (long)(size > TP_BSIZE ?
+ TP_BSIZE : size));
+ }
+ if ((size -= TP_BSIZE) <= 0) {
+ if (size > -TP_BSIZE && curblk > 0) {
+ (*fillit)((char *)buf,
+ (long)((curblk * TP_BSIZE) + size));
+ curblk = 0;
+ }
+ if (attrsize > 0) {
+ fillit = attrfill;
+ size = attrsize;
+ attrsize = 0;
+ continue;
+ }
+ if (spcl.c_count - i > 1)
+ dprintf(stdout, "skipping %d junk block(s)\n",
+ spcl.c_count - i - 1);
+ for (i++; i < spcl.c_count; i++) {
+ if (!readmapflag && i > TP_NINDIR) {
+ if (Dflag) {
+ fprintf(stderr,
+ "spcl.c_count = %jd\n",
+ (intmax_t)spcl.c_count);
+ break;
+ } else
+ panic("spcl.c_count = %jd\n",
+ (intmax_t)spcl.c_count);
+ }
+ if (readmapflag || spcl.c_addr[i])
+ readtape(junk);
+ }
+ break;
+ }
+ }
+ if (gethead(&spcl) == GOOD && size > 0) {
+ if (spcl.c_type == TS_ADDR)
+ goto loop;
+ dprintf(stdout,
+ "Missing address (header) block for %s at %ld blocks\n",
+ curfile.name, blksread);
+ }
+ if (curblk > 0)
+ panic("getfile: lost data\n");
+ findinode(&spcl);
+ gettingfile = 0;
+}
+
+/*
+ * These variables are shared between the next two functions.
+ */
+static int extbufsize = 0;
+static char *extbuf;
+static int extloc;
+
+/*
+ * Allocate a buffer into which to extract extended attributes.
+ */
+static char *
+setupextattr(int extsize)
+{
+
+ extloc = 0;
+ if (extsize <= extbufsize)
+ return (extbuf);
+ if (extbufsize > 0)
+ free(extbuf);
+ if ((extbuf = malloc(extsize)) != NULL) {
+ extbufsize = extsize;
+ return (extbuf);
+ }
+ extbufsize = 0;
+ extbuf = NULL;
+ fprintf(stderr, "Cannot extract %d bytes %s for inode %ju, name %s\n",
+ extsize, "of extended attributes", (uintmax_t)curfile.ino,
+ curfile.name);
+ return (NULL);
+}
+
+/*
+ * Extract the next block of extended attributes.
+ */
+static void
+xtrattr(char *buf, long size)
+{
+
+ if (extloc + size > extbufsize)
+ panic("overrun attribute buffer\n");
+ memmove(&extbuf[extloc], buf, size);
+ extloc += size;
+}
+
+/*
+ * Write out the next block of a file.
+ */
+static void
+xtrfile(char *buf, long size)
+{
+
+ if (Nflag)
+ return;
+ if (write(ofile, buf, (int) size) == -1) {
+ fprintf(stderr,
+ "write error extracting inode %ju, name %s\nwrite: %s\n",
+ (uintmax_t)curfile.ino, curfile.name, strerror(errno));
+ }
+}
+
+/*
+ * Skip over a hole in a file.
+ */
+/* ARGSUSED */
+static void
+xtrskip(char *buf, long size)
+{
+
+ if (lseek(ofile, size, SEEK_CUR) == -1) {
+ fprintf(stderr,
+ "seek error extracting inode %ju, name %s\nlseek: %s\n",
+ (uintmax_t)curfile.ino, curfile.name, strerror(errno));
+ done(1);
+ }
+}
+
+/*
+ * Collect the next block of a symbolic link.
+ */
+static void
+xtrlnkfile(char *buf, long size)
+{
+
+ pathlen += size;
+ if (pathlen > MAXPATHLEN) {
+ fprintf(stderr, "symbolic link name: %s->%s%s; too long %d\n",
+ curfile.name, lnkbuf, buf, pathlen);
+ done(1);
+ }
+ (void) strcat(lnkbuf, buf);
+}
+
+/*
+ * Skip over a hole in a symbolic link (should never happen).
+ */
+/* ARGSUSED */
+static void
+xtrlnkskip(char *buf, long size)
+{
+
+ fprintf(stderr, "unallocated block in symbolic link %s\n",
+ curfile.name);
+ done(1);
+}
+
+/*
+ * Collect the next block of a bit map.
+ */
+static void
+xtrmap(char *buf, long size)
+{
+
+ memmove(map, buf, size);
+ map += size;
+}
+
+/*
+ * Skip over a hole in a bit map (should never happen).
+ */
+/* ARGSUSED */
+static void
+xtrmapskip(char *buf, long size)
+{
+
+ panic("hole in map\n");
+ map += size;
+}
+
+/*
+ * Noop, when an extraction function is not needed.
+ */
+/* ARGSUSED */
+void
+xtrnull(char *buf, long size)
+{
+
+ return;
+}
+
+/*
+ * Read TP_BSIZE blocks from the input.
+ * Handle read errors, and end of media.
+ */
+static void
+readtape(char *buf)
+{
+ long rd, newvol, i, oldnumtrec;
+ int cnt, seek_failed;
+
+ if (blkcnt + (byteslide > 0) < numtrec) {
+ memmove(buf, &tapebuf[(blkcnt++ * TP_BSIZE) + byteslide], (long)TP_BSIZE);
+ blksread++;
+ tapeaddr++;
+ return;
+ }
+ if (numtrec > 0)
+ memmove(&tapebuf[-TP_BSIZE],
+ &tapebuf[(numtrec-1) * TP_BSIZE], (long)TP_BSIZE);
+ oldnumtrec = numtrec;
+ for (i = 0; i < ntrec; i++)
+ ((struct s_spcl *)&tapebuf[i * TP_BSIZE])->c_magic = 0;
+ if (numtrec == 0)
+ numtrec = ntrec;
+ cnt = ntrec * TP_BSIZE;
+ rd = 0;
+getmore:
+#ifdef RRESTORE
+ if (host)
+ i = rmtread(&tapebuf[rd], cnt);
+ else
+#endif
+ i = read(mt, &tapebuf[rd], cnt);
+ /*
+ * Check for mid-tape short read error.
+ * If found, skip rest of buffer and start with the next.
+ */
+ if (!pipein && !pipecmdin && numtrec < ntrec && i > 0) {
+ dprintf(stdout, "mid-media short read error.\n");
+ numtrec = ntrec;
+ }
+ /*
+ * Handle partial block read.
+ */
+ if ((pipein || pipecmdin) && i == 0 && rd > 0)
+ i = rd;
+ else if (i > 0 && i != ntrec * TP_BSIZE) {
+ if (pipein || pipecmdin) {
+ rd += i;
+ cnt -= i;
+ if (cnt > 0)
+ goto getmore;
+ i = rd;
+ } else {
+ /*
+ * Short read. Process the blocks read.
+ */
+ if (i % TP_BSIZE != 0)
+ vprintf(stdout,
+ "partial block read: %ld should be %ld\n",
+ i, ntrec * TP_BSIZE);
+ numtrec = i / TP_BSIZE;
+ }
+ }
+ /*
+ * Handle read error.
+ */
+ if (i < 0) {
+ fprintf(stderr, "Tape read error while ");
+ switch (curfile.action) {
+ default:
+ fprintf(stderr, "trying to set up tape\n");
+ break;
+ case UNKNOWN:
+ fprintf(stderr, "trying to resynchronize\n");
+ break;
+ case USING:
+ fprintf(stderr, "restoring %s\n", curfile.name);
+ break;
+ case SKIP:
+ fprintf(stderr, "skipping over inode %ju\n",
+ (uintmax_t)curfile.ino);
+ break;
+ }
+ if (!yflag && !reply("continue"))
+ done(1);
+ i = ntrec * TP_BSIZE;
+ memset(tapebuf, 0, i);
+#ifdef RRESTORE
+ if (host)
+ seek_failed = (rmtseek(i, 1) < 0);
+ else
+#endif
+ seek_failed = (lseek(mt, i, SEEK_CUR) == (off_t)-1);
+
+ if (seek_failed) {
+ fprintf(stderr,
+ "continuation failed: %s\n", strerror(errno));
+ done(1);
+ }
+ }
+ /*
+ * Handle end of tape.
+ */
+ if (i == 0) {
+ vprintf(stdout, "End-of-tape encountered\n");
+ if (!pipein) {
+ newvol = volno + 1;
+ volno = 0;
+ numtrec = 0;
+ getvol(newvol);
+ readtape(buf);
+ return;
+ }
+ if (rd % TP_BSIZE != 0)
+ panic("partial block read: %ld should be %ld\n",
+ rd, ntrec * TP_BSIZE);
+ terminateinput();
+ memmove(&tapebuf[rd], &endoftapemark, (long)TP_BSIZE);
+ }
+ if (oldnumtrec == 0)
+ blkcnt = 0;
+ else
+ blkcnt -= oldnumtrec;
+ memmove(buf,
+ &tapebuf[(blkcnt++ * TP_BSIZE) + byteslide], (long)TP_BSIZE);
+ blksread++;
+ tapeaddr++;
+}
+
+static void
+findtapeblksize(void)
+{
+ long i;
+
+ for (i = 0; i < ntrec; i++)
+ ((struct s_spcl *)&tapebuf[i * TP_BSIZE])->c_magic = 0;
+ blkcnt = 0;
+#ifdef RRESTORE
+ if (host)
+ i = rmtread(tapebuf, ntrec * TP_BSIZE);
+ else
+#endif
+ i = read(mt, tapebuf, ntrec * TP_BSIZE);
+
+ if (i <= 0) {
+ fprintf(stderr, "tape read error: %s\n", strerror(errno));
+ done(1);
+ }
+ if (i % TP_BSIZE != 0) {
+ fprintf(stderr, "Tape block size (%ld) %s (%d)\n",
+ i, "is not a multiple of dump block size", TP_BSIZE);
+ done(1);
+ }
+ ntrec = i / TP_BSIZE;
+ numtrec = ntrec;
+ vprintf(stdout, "Tape block size is %ld\n", ntrec);
+}
+
+void
+closemt(void)
+{
+
+ if (mt < 0)
+ return;
+ if (pipecmdin) {
+ pclose(popenfp);
+ popenfp = NULL;
+ } else
+#ifdef RRESTORE
+ if (host)
+ rmtclose();
+ else
+#endif
+ (void) close(mt);
+}
+
+/*
+ * Read the next block from the tape.
+ * If it is not any valid header, return an error.
+ */
+static int
+gethead(struct s_spcl *buf)
+{
+ long i;
+
+ readtape((char *)buf);
+ if (buf->c_magic != FS_UFS2_MAGIC && buf->c_magic != NFS_MAGIC) {
+ if (buf->c_magic == OFS_MAGIC) {
+ fprintf(stderr,
+ "Format of dump tape is too old. Must use\n");
+ fprintf(stderr,
+ "a version of restore from before 2002.\n");
+ return (FAIL);
+ }
+ if (swabl(buf->c_magic) != FS_UFS2_MAGIC &&
+ buf->c_magic != NFS_MAGIC) {
+ if (buf->c_magic == OFS_MAGIC) {
+ fprintf(stderr,
+ "Format of dump tape is too old. Must use\n");
+ fprintf(stderr,
+ "a version of restore from before 2002.\n");
+ }
+ return (FAIL);
+ }
+ if (!Bcvt) {
+ vprintf(stdout, "Note: Doing Byte swapping\n");
+ Bcvt = 1;
+ }
+ }
+ if (checksum((int *)buf) == FAIL)
+ return (FAIL);
+ if (Bcvt) {
+ swabst((u_char *)"8l4s1q8l2q17l", (u_char *)buf);
+ swabst((u_char *)"l",(u_char *) &buf->c_level);
+ swabst((u_char *)"2l4q",(u_char *) &buf->c_flags);
+ }
+ readmapflag = 0;
+
+ switch (buf->c_type) {
+
+ case TS_CLRI:
+ case TS_BITS:
+ /*
+ * Have to patch up missing information in bit map headers
+ */
+ buf->c_size = buf->c_count * TP_BSIZE;
+ if (buf->c_count > TP_NINDIR)
+ readmapflag = 1;
+ else
+ for (i = 0; i < buf->c_count; i++)
+ buf->c_addr[i]++;
+ /* FALL THROUGH */
+
+ case TS_TAPE:
+ if (buf->c_magic == NFS_MAGIC &&
+ (buf->c_flags & NFS_DR_NEWINODEFMT) == 0)
+ oldinofmt = 1;
+ /* FALL THROUGH */
+
+ case TS_END:
+ buf->c_inumber = 0;
+ /* FALL THROUGH */
+
+ case TS_ADDR:
+ case TS_INODE:
+ /*
+ * For old dump tapes, have to copy up old fields to
+ * new locations.
+ */
+ if (buf->c_magic == NFS_MAGIC) {
+ buf->c_tapea = buf->c_old_tapea;
+ buf->c_firstrec = buf->c_old_firstrec;
+ buf->c_date = _time32_to_time(buf->c_old_date);
+ buf->c_ddate = _time32_to_time(buf->c_old_ddate);
+ buf->c_atime = _time32_to_time(buf->c_old_atime);
+ buf->c_mtime = _time32_to_time(buf->c_old_mtime);
+ buf->c_birthtime = 0;
+ buf->c_birthtimensec = 0;
+ buf->c_extsize = 0;
+ }
+ break;
+
+ default:
+ panic("gethead: unknown inode type %d\n", buf->c_type);
+ break;
+ }
+ if (dumpdate != 0 && _time64_to_time(buf->c_date) != dumpdate)
+ fprintf(stderr, "Header with wrong dumpdate.\n");
+ /*
+ * If we're restoring a filesystem with the old (FreeBSD 1)
+ * format inodes, copy the uid/gid to the new location
+ */
+ if (oldinofmt) {
+ buf->c_uid = buf->c_spare1[1];
+ buf->c_gid = buf->c_spare1[2];
+ }
+ buf->c_magic = FS_UFS2_MAGIC;
+ tapeaddr = buf->c_tapea;
+ if (dflag)
+ accthdr(buf);
+ return(GOOD);
+}
+
+/*
+ * Check that a header is where it belongs and predict the next header
+ */
+static void
+accthdr(struct s_spcl *header)
+{
+ static ino_t previno = 0x7fffffff;
+ static int prevtype;
+ static long predict;
+ long blks, i;
+
+ if (header->c_type == TS_TAPE) {
+ fprintf(stderr, "Volume header ");
+ if (header->c_firstrec)
+ fprintf(stderr, "begins with record %jd",
+ (intmax_t)header->c_firstrec);
+ fprintf(stderr, "\n");
+ previno = 0x7fffffff;
+ return;
+ }
+ if (previno == 0x7fffffff)
+ goto newcalc;
+ switch (prevtype) {
+ case TS_BITS:
+ fprintf(stderr, "Dumped inodes map header");
+ break;
+ case TS_CLRI:
+ fprintf(stderr, "Used inodes map header");
+ break;
+ case TS_INODE:
+ fprintf(stderr, "File header, ino %ju", (uintmax_t)previno);
+ break;
+ case TS_ADDR:
+ fprintf(stderr, "File continuation header, ino %ju",
+ (uintmax_t)previno);
+ break;
+ case TS_END:
+ fprintf(stderr, "End of tape header");
+ break;
+ }
+ if (predict != blksread - 1)
+ fprintf(stderr, "; predicted %ld blocks, got %ld blocks",
+ predict, blksread - 1);
+ fprintf(stderr, "\n");
+newcalc:
+ blks = 0;
+ if (header->c_type != TS_END)
+ for (i = 0; i < header->c_count; i++)
+ if (readmapflag || header->c_addr[i] != 0)
+ blks++;
+ predict = blks;
+ blksread = 0;
+ prevtype = header->c_type;
+ previno = header->c_inumber;
+}
+
+/*
+ * Find an inode header.
+ * Complain if had to skip.
+ */
+static void
+findinode(struct s_spcl *header)
+{
+ static long skipcnt = 0;
+ long i;
+ char buf[TP_BSIZE];
+ int htype;
+
+ curfile.name = "<name unknown>";
+ curfile.action = UNKNOWN;
+ curfile.mode = 0;
+ curfile.ino = 0;
+ do {
+ htype = header->c_type;
+ switch (htype) {
+
+ case TS_ADDR:
+ /*
+ * Skip up to the beginning of the next record
+ */
+ for (i = 0; i < header->c_count; i++)
+ if (header->c_addr[i])
+ readtape(buf);
+ while (gethead(header) == FAIL ||
+ _time64_to_time(header->c_date) != dumpdate) {
+ skipcnt++;
+ if (Dflag) {
+ byteslide++;
+ if (byteslide < TP_BSIZE) {
+ blkcnt--;
+ blksread--;
+ } else
+ byteslide = 0;
+ }
+ }
+ break;
+
+ case TS_INODE:
+ curfile.mode = header->c_mode;
+ curfile.uid = header->c_uid;
+ curfile.gid = header->c_gid;
+ curfile.file_flags = header->c_file_flags;
+ curfile.rdev = header->c_rdev;
+ curfile.atime_sec = header->c_atime;
+ curfile.atime_nsec = header->c_atimensec;
+ curfile.mtime_sec = header->c_mtime;
+ curfile.mtime_nsec = header->c_mtimensec;
+ curfile.birthtime_sec = header->c_birthtime;
+ curfile.birthtime_nsec = header->c_birthtimensec;
+ curfile.extsize = header->c_extsize;
+ curfile.size = header->c_size;
+ curfile.ino = header->c_inumber;
+ break;
+
+ case TS_END:
+ /* If we missed some tapes, get another volume. */
+ if (tapesread & (tapesread + 1)) {
+ getvol(0);
+ continue;
+ }
+ curfile.ino = maxino;
+ break;
+
+ case TS_CLRI:
+ curfile.name = "<file removal list>";
+ break;
+
+ case TS_BITS:
+ curfile.name = "<file dump list>";
+ break;
+
+ case TS_TAPE:
+ if (Dflag)
+ fprintf(stderr, "unexpected tape header\n");
+ else
+ panic("unexpected tape header\n");
+
+ default:
+ if (Dflag)
+ fprintf(stderr, "unknown tape header type %d\n",
+ spcl.c_type);
+ else
+ panic("unknown tape header type %d\n",
+ spcl.c_type);
+ while (gethead(header) == FAIL ||
+ _time64_to_time(header->c_date) != dumpdate) {
+ skipcnt++;
+ if (Dflag) {
+ byteslide++;
+ if (byteslide < TP_BSIZE) {
+ blkcnt--;
+ blksread--;
+ } else
+ byteslide = 0;
+ }
+ }
+
+ }
+ } while (htype == TS_ADDR);
+ if (skipcnt > 0)
+ fprintf(stderr, "resync restore, skipped %ld %s\n",
+ skipcnt, Dflag ? "bytes" : "blocks");
+ skipcnt = 0;
+}
+
+static int
+checksum(int *buf)
+{
+ int i, j;
+
+ j = sizeof(union u_spcl) / sizeof(int);
+ i = 0;
+ if (!Bcvt) {
+ do
+ i += *buf++;
+ while (--j);
+ } else {
+ /* What happens if we want to read restore tapes
+ for a 16bit int machine??? */
+ do
+ i += swabl(*buf++);
+ while (--j);
+ }
+
+ if (i != CHECKSUM) {
+ fprintf(stderr, "Checksum error %o, inode %ju file %s\n", i,
+ (uintmax_t)curfile.ino, curfile.name);
+ return(FAIL);
+ }
+ return(GOOD);
+}
+
+#ifdef RRESTORE
+#include <stdarg.h>
+
+void
+msg(const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ (void)vfprintf(stderr, fmt, ap);
+ va_end(ap);
+}
+#endif /* RRESTORE */
+
+static u_char *
+swabshort(u_char *sp, int n)
+{
+ char c;
+
+ while (--n >= 0) {
+ c = sp[0]; sp[0] = sp[1]; sp[1] = c;
+ sp += 2;
+ }
+ return (sp);
+}
+
+static u_char *
+swablong(u_char *sp, int n)
+{
+ char c;
+
+ while (--n >= 0) {
+ c = sp[0]; sp[0] = sp[3]; sp[3] = c;
+ c = sp[2]; sp[2] = sp[1]; sp[1] = c;
+ sp += 4;
+ }
+ return (sp);
+}
+
+static u_char *
+swabquad(u_char *sp, int n)
+{
+ char c;
+
+ while (--n >= 0) {
+ c = sp[0]; sp[0] = sp[7]; sp[7] = c;
+ c = sp[1]; sp[1] = sp[6]; sp[6] = c;
+ c = sp[2]; sp[2] = sp[5]; sp[5] = c;
+ c = sp[3]; sp[3] = sp[4]; sp[4] = c;
+ sp += 8;
+ }
+ return (sp);
+}
+
+void
+swabst(u_char *cp, u_char *sp)
+{
+ int n = 0;
+
+ while (*cp) {
+ switch (*cp) {
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ n = (n * 10) + (*cp++ - '0');
+ continue;
+
+ case 's': case 'w': case 'h':
+ if (n == 0)
+ n = 1;
+ sp = swabshort(sp, n);
+ break;
+
+ case 'l':
+ if (n == 0)
+ n = 1;
+ sp = swablong(sp, n);
+ break;
+
+ case 'q':
+ if (n == 0)
+ n = 1;
+ sp = swabquad(sp, n);
+ break;
+
+ case 'b':
+ if (n == 0)
+ n = 1;
+ sp += n;
+ break;
+
+ default:
+ fprintf(stderr, "Unknown conversion character: %c\n",
+ *cp);
+ done(0);
+ break;
+ }
+ cp++;
+ n = 0;
+ }
+}
+
+static u_long
+swabl(u_long x)
+{
+ swabst((u_char *)"l", (u_char *)&x);
+ return (x);
+}
diff --git a/sbin/restore/utilities.c b/sbin/restore/utilities.c
new file mode 100644
index 0000000..89c2b09
--- /dev/null
+++ b/sbin/restore/utilities.c
@@ -0,0 +1,422 @@
+/*
+ * Copyright (c) 1983, 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.
+ * 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 lint
+#if 0
+static char sccsid[] = "@(#)utilities.c 8.5 (Berkeley) 4/28/95";
+#endif
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include <ufs/ufs/dinode.h>
+#include <ufs/ufs/dir.h>
+
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "restore.h"
+#include "extern.h"
+
+/*
+ * Insure that all the components of a pathname exist.
+ */
+void
+pathcheck(char *name)
+{
+ char *cp;
+ struct entry *ep;
+ char *start;
+
+ start = strchr(name, '/');
+ if (start == 0)
+ return;
+ for (cp = start; *cp != '\0'; cp++) {
+ if (*cp != '/')
+ continue;
+ *cp = '\0';
+ ep = lookupname(name);
+ if (ep == NULL) {
+ /* Safe; we know the pathname exists in the dump. */
+ ep = addentry(name, pathsearch(name)->d_ino, NODE);
+ newnode(ep);
+ }
+ ep->e_flags |= NEW|KEEP;
+ *cp = '/';
+ }
+}
+
+/*
+ * Change a name to a unique temporary name.
+ */
+void
+mktempname(struct entry *ep)
+{
+ char oldname[MAXPATHLEN];
+
+ if (ep->e_flags & TMPNAME)
+ badentry(ep, "mktempname: called with TMPNAME");
+ ep->e_flags |= TMPNAME;
+ (void) strcpy(oldname, myname(ep));
+ freename(ep->e_name);
+ ep->e_name = savename(gentempname(ep));
+ ep->e_namlen = strlen(ep->e_name);
+ renameit(oldname, myname(ep));
+}
+
+/*
+ * Generate a temporary name for an entry.
+ */
+char *
+gentempname(struct entry *ep)
+{
+ static char name[MAXPATHLEN];
+ struct entry *np;
+ long i = 0;
+
+ for (np = lookupino(ep->e_ino);
+ np != NULL && np != ep; np = np->e_links)
+ i++;
+ if (np == NULL)
+ badentry(ep, "not on ino list");
+ (void) sprintf(name, "%s%ld%lu", TMPHDR, i, (u_long)ep->e_ino);
+ return (name);
+}
+
+/*
+ * Rename a file or directory.
+ */
+void
+renameit(char *from, char *to)
+{
+ if (!Nflag && rename(from, to) < 0) {
+ fprintf(stderr, "warning: cannot rename %s to %s: %s\n",
+ from, to, strerror(errno));
+ return;
+ }
+ vprintf(stdout, "rename %s to %s\n", from, to);
+}
+
+/*
+ * Create a new node (directory).
+ */
+void
+newnode(struct entry *np)
+{
+ char *cp;
+
+ if (np->e_type != NODE)
+ badentry(np, "newnode: not a node");
+ cp = myname(np);
+ if (!Nflag && mkdir(cp, 0777) < 0 && !uflag) {
+ np->e_flags |= EXISTED;
+ fprintf(stderr, "warning: %s: %s\n", cp, strerror(errno));
+ return;
+ }
+ vprintf(stdout, "Make node %s\n", cp);
+}
+
+/*
+ * Remove an old node (directory).
+ */
+void
+removenode(struct entry *ep)
+{
+ char *cp;
+
+ if (ep->e_type != NODE)
+ badentry(ep, "removenode: not a node");
+ if (ep->e_entries != NULL)
+ badentry(ep, "removenode: non-empty directory");
+ ep->e_flags |= REMOVED;
+ ep->e_flags &= ~TMPNAME;
+ cp = myname(ep);
+ if (!Nflag && rmdir(cp) < 0) {
+ fprintf(stderr, "warning: %s: %s\n", cp, strerror(errno));
+ return;
+ }
+ vprintf(stdout, "Remove node %s\n", cp);
+}
+
+/*
+ * Remove a leaf.
+ */
+void
+removeleaf(struct entry *ep)
+{
+ char *cp;
+
+ if (ep->e_type != LEAF)
+ badentry(ep, "removeleaf: not a leaf");
+ ep->e_flags |= REMOVED;
+ ep->e_flags &= ~TMPNAME;
+ cp = myname(ep);
+ if (!Nflag && unlink(cp) < 0) {
+ fprintf(stderr, "warning: %s: %s\n", cp, strerror(errno));
+ return;
+ }
+ vprintf(stdout, "Remove leaf %s\n", cp);
+}
+
+/*
+ * Create a link.
+ */
+int
+linkit(char *existing, char *new, int type)
+{
+
+ /* if we want to unlink first, do it now so *link() won't fail */
+ if (uflag && !Nflag)
+ (void)unlink(new);
+
+ if (type == SYMLINK) {
+ if (!Nflag && symlink(existing, new) < 0) {
+ fprintf(stderr,
+ "warning: cannot create symbolic link %s->%s: %s\n",
+ new, existing, strerror(errno));
+ return (FAIL);
+ }
+ } else if (type == HARDLINK) {
+ int ret;
+
+ if (!Nflag && (ret = link(existing, new)) < 0) {
+ struct stat s;
+
+ /*
+ * Most likely, the schg flag is set. Clear the
+ * flags and try again.
+ */
+ if (stat(existing, &s) == 0 && s.st_flags != 0 &&
+ chflags(existing, 0) == 0) {
+ ret = link(existing, new);
+ chflags(existing, s.st_flags);
+ }
+ if (ret < 0) {
+ fprintf(stderr, "warning: cannot create "
+ "hard link %s->%s: %s\n",
+ new, existing, strerror(errno));
+ return (FAIL);
+ }
+ }
+ } else {
+ panic("linkit: unknown type %d\n", type);
+ return (FAIL);
+ }
+ vprintf(stdout, "Create %s link %s->%s\n",
+ type == SYMLINK ? "symbolic" : "hard", new, existing);
+ return (GOOD);
+}
+
+/*
+ * Create a whiteout.
+ */
+int
+addwhiteout(char *name)
+{
+
+ if (!Nflag && mknod(name, S_IFWHT, 0) < 0) {
+ fprintf(stderr, "warning: cannot create whiteout %s: %s\n",
+ name, strerror(errno));
+ return (FAIL);
+ }
+ vprintf(stdout, "Create whiteout %s\n", name);
+ return (GOOD);
+}
+
+/*
+ * Delete a whiteout.
+ */
+void
+delwhiteout(struct entry *ep)
+{
+ char *name;
+
+ if (ep->e_type != LEAF)
+ badentry(ep, "delwhiteout: not a leaf");
+ ep->e_flags |= REMOVED;
+ ep->e_flags &= ~TMPNAME;
+ name = myname(ep);
+ if (!Nflag && undelete(name) < 0) {
+ fprintf(stderr, "warning: cannot delete whiteout %s: %s\n",
+ name, strerror(errno));
+ return;
+ }
+ vprintf(stdout, "Delete whiteout %s\n", name);
+}
+
+/*
+ * find lowest number file (above "start") that needs to be extracted
+ */
+ino_t
+lowerbnd(ino_t start)
+{
+ struct entry *ep;
+
+ for ( ; start < maxino; start++) {
+ ep = lookupino(start);
+ if (ep == NULL || ep->e_type == NODE)
+ continue;
+ if (ep->e_flags & (NEW|EXTRACT))
+ return (start);
+ }
+ return (start);
+}
+
+/*
+ * find highest number file (below "start") that needs to be extracted
+ */
+ino_t
+upperbnd(ino_t start)
+{
+ struct entry *ep;
+
+ for ( ; start > ROOTINO; start--) {
+ ep = lookupino(start);
+ if (ep == NULL || ep->e_type == NODE)
+ continue;
+ if (ep->e_flags & (NEW|EXTRACT))
+ return (start);
+ }
+ return (start);
+}
+
+/*
+ * report on a badly formed entry
+ */
+void
+badentry(struct entry *ep, char *msg)
+{
+
+ fprintf(stderr, "bad entry: %s\n", msg);
+ fprintf(stderr, "name: %s\n", myname(ep));
+ fprintf(stderr, "parent name %s\n", myname(ep->e_parent));
+ if (ep->e_sibling != NULL)
+ fprintf(stderr, "sibling name: %s\n", myname(ep->e_sibling));
+ if (ep->e_entries != NULL)
+ fprintf(stderr, "next entry name: %s\n", myname(ep->e_entries));
+ if (ep->e_links != NULL)
+ fprintf(stderr, "next link name: %s\n", myname(ep->e_links));
+ if (ep->e_next != NULL)
+ fprintf(stderr,
+ "next hashchain name: %s\n", myname(ep->e_next));
+ fprintf(stderr, "entry type: %s\n",
+ ep->e_type == NODE ? "NODE" : "LEAF");
+ fprintf(stderr, "inode number: %lu\n", (u_long)ep->e_ino);
+ panic("flags: %s\n", flagvalues(ep));
+}
+
+/*
+ * Construct a string indicating the active flag bits of an entry.
+ */
+char *
+flagvalues(struct entry *ep)
+{
+ static char flagbuf[BUFSIZ];
+
+ (void) strcpy(flagbuf, "|NIL");
+ flagbuf[0] = '\0';
+ if (ep->e_flags & REMOVED)
+ (void) strcat(flagbuf, "|REMOVED");
+ if (ep->e_flags & TMPNAME)
+ (void) strcat(flagbuf, "|TMPNAME");
+ if (ep->e_flags & EXTRACT)
+ (void) strcat(flagbuf, "|EXTRACT");
+ if (ep->e_flags & NEW)
+ (void) strcat(flagbuf, "|NEW");
+ if (ep->e_flags & KEEP)
+ (void) strcat(flagbuf, "|KEEP");
+ if (ep->e_flags & EXISTED)
+ (void) strcat(flagbuf, "|EXISTED");
+ return (&flagbuf[1]);
+}
+
+/*
+ * Check to see if a name is on a dump tape.
+ */
+ino_t
+dirlookup(const char *name)
+{
+ struct direct *dp;
+ ino_t ino;
+
+ ino = ((dp = pathsearch(name)) == NULL) ? 0 : dp->d_ino;
+
+ if (ino == 0 || TSTINO(ino, dumpmap) == 0)
+ fprintf(stderr, "%s is not on the tape\n", name);
+ return (ino);
+}
+
+/*
+ * Elicit a reply.
+ */
+int
+reply(char *question)
+{
+ int c;
+
+ do {
+ fprintf(stderr, "%s? [yn] ", question);
+ (void) fflush(stderr);
+ c = getc(terminal);
+ while (c != '\n' && getc(terminal) != '\n')
+ if (c == EOF)
+ return (FAIL);
+ } while (c != 'y' && c != 'n');
+ if (c == 'y')
+ return (GOOD);
+ return (FAIL);
+}
+
+/*
+ * handle unexpected inconsistencies
+ */
+#include <stdarg.h>
+
+void
+panic(const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ if (yflag)
+ return;
+ if (reply("abort") == GOOD) {
+ if (reply("dump core") == GOOD)
+ abort();
+ done(1);
+ }
+}
diff --git a/sbin/route/Makefile b/sbin/route/Makefile
new file mode 100644
index 0000000..25134e4
--- /dev/null
+++ b/sbin/route/Makefile
@@ -0,0 +1,27 @@
+# @(#)Makefile 8.1 (Berkeley) 6/5/93
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+PROG= route
+MAN= route.8
+SRCS= route.c keywords.h
+WARNS?= 3
+CLEANFILES+=keywords.h
+
+CFLAGS+= -DNS
+.if ${MK_INET_SUPPORT} != "no"
+CFLAGS+= -DINET
+.endif
+.if ${MK_INET6_SUPPORT} != "no"
+CFLAGS+= -DINET6
+.endif
+CFLAGS+= -I.
+
+keywords.h: keywords
+ LC_ALL=C awk '!/^#|^$$/ { \
+ printf "#define\tK_%s\t%d\n\t{\"%s\", K_%s},\n", \
+ toupper($$1), ++L, $$1, toupper($$1); \
+ }' < ${.CURDIR}/keywords > ${.TARGET} || (rm -f ${.TARGET}; false)
+
+.include <bsd.prog.mk>
diff --git a/sbin/route/keywords b/sbin/route/keywords
new file mode 100644
index 0000000..82edc46
--- /dev/null
+++ b/sbin/route/keywords
@@ -0,0 +1,57 @@
+# @(#)keywords 8.2 (Berkeley) 3/19/94
+# $FreeBSD$
+
+4
+6
+add
+blackhole
+change
+cloning
+del
+delete
+dst
+expire
+fib
+flush
+gateway
+genmask
+get
+host
+hopcount
+iface
+interface
+ifa
+ifp
+inet
+inet6
+iso
+link
+llinfo
+lock
+lockrest
+mask
+monitor
+mtu
+net
+netmask
+nostatic
+nostick
+osi
+prefixlen
+proto1
+proto2
+proxy
+recvpipe
+reject
+rtt
+rttvar
+sa
+sendpipe
+show
+ssthresh
+static
+sticky
+weight
+x25
+xns
+xresolve
diff --git a/sbin/route/route.8 b/sbin/route/route.8
new file mode 100644
index 0000000..5e6f78b
--- /dev/null
+++ b/sbin/route/route.8
@@ -0,0 +1,506 @@
+.\" Copyright (c) 1983, 1991, 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.
+.\" 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.
+.\"
+.\" @(#)route.8 8.3 (Berkeley) 3/19/94
+.\" $FreeBSD$
+.\"
+.Dd November 11, 2014
+.Dt ROUTE 8
+.Os
+.Sh NAME
+.Nm route
+.Nd manually manipulate the routing tables
+.Sh SYNOPSIS
+.Nm
+.Op Fl dnqtv
+.Ar command
+.Oo
+.Op Ar modifiers
+.Ar args
+.Oc
+.Sh DESCRIPTION
+The
+.Nm
+utility is used to manually manipulate the network
+routing tables.
+It normally is not needed, as a
+system routing table management daemon, such as
+.Xr routed 8 ,
+should tend to this task.
+.Pp
+The
+.Nm
+utility supports a limited number of general options,
+but a rich command language, enabling the user to specify
+any arbitrary request that could be delivered via the
+programmatic interface discussed in
+.Xr route 4 .
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl 4
+Specify
+.Cm inet
+address family as family hint for subcommands.
+.It Fl 6
+Specify
+.Cm inet
+address family as family hint for subcommands.
+.It Fl d
+Run in debug-only mode, i.e., do not actually modify the routing table.
+.It Fl n
+Bypass attempts to print host and network names symbolically
+when reporting actions.
+(The process of translating between symbolic
+names and numerical equivalents can be quite time consuming, and
+may require correct operation of the network; thus it may be expedient
+to forget this, especially when attempting to repair networking operations).
+.It Fl t
+Run in test-only mode.
+.Pa /dev/null
+is used instead of a socket.
+.It Fl v
+(verbose) Print additional details.
+.It Fl q
+Suppress all output from the
+.Cm add , change , delete ,
+and
+.Cm flush
+commands.
+.El
+.Pp
+The
+.Nm
+utility provides the following commands:
+.Pp
+.Bl -tag -width Fl -compact
+.It Cm add
+Add a route.
+.It Cm flush
+Remove all routes.
+.It Cm delete
+Delete a specific route.
+.It Cm del
+Another name for the
+.Cm delete
+command.
+.It Cm change
+Change aspects of a route (such as its gateway).
+.It Cm get
+Lookup and display the route for a destination.
+.It Cm monitor
+Continuously report any changes to the routing information base,
+routing lookup misses, or suspected network partitionings.
+.It Cm show
+Another name for the
+.Cm get
+command.
+.El
+.Pp
+The monitor command has the syntax:
+.Pp
+.Bd -ragged -offset indent -compact
+.Nm
+.Op Fl n
+.Cm monitor Op Fl fib Ar number
+.Ed
+.Pp
+The flush command has the syntax:
+.Pp
+.Bd -ragged -offset indent -compact
+.Nm
+.Oo Fl n Cm flush Oc Oo Ar family Oc Op Fl fib Ar number
+.Ed
+.Pp
+If the
+.Cm flush
+command is specified,
+.Nm
+will ``flush'' the routing tables of all gateway entries.
+When the address family may is specified by any of the
+.Fl osi ,
+.Fl xns ,
+.Fl inet6 ,
+or
+.Fl inet
+modifiers, only routes having destinations with addresses in the
+delineated family will be deleted.
+Additionally,
+.Fl 4
+or
+.Fl 6
+can be used as aliases for
+.Fl inet
+and
+.Fl inet6
+modifiers.
+When a
+.Fl fib
+option is specified, the operation will be applied to
+the specified FIB
+.Pq routing table .
+.Pp
+The other commands have the following syntax:
+.Pp
+.Bd -ragged -offset indent -compact
+.Nm
+.Op Fl n
+.Ar command
+.Op Fl net No \&| Fl host
+.Ar destination gateway
+.Op Ar netmask
+.Op Fl fib Ar number
+.Ed
+.Pp
+where
+.Ar destination
+is the destination host or network,
+.Ar gateway
+is the next-hop intermediary via which packets should be routed.
+Routes to a particular host may be distinguished from those to
+a network by interpreting the Internet address specified as the
+.Ar destination
+argument.
+The optional modifiers
+.Fl net
+and
+.Fl host
+force the destination to be interpreted as a network or a host, respectively.
+Otherwise, if the
+.Ar destination
+has a
+.Dq local address part
+of
+INADDR_ANY
+.Pq Li 0.0.0.0 ,
+or if the
+.Ar destination
+is the symbolic name of a network, then the route is
+assumed to be to a network; otherwise, it is presumed to be a
+route to a host.
+Optionally, the
+.Ar destination
+could also be specified in the
+.Ar net Ns / Ns Ar bits
+format.
+.Pp
+For example,
+.Li 128.32
+is interpreted as
+.Fl host Li 128.0.0.32 ;
+.Li 128.32.130
+is interpreted as
+.Fl host Li 128.32.0.130 ;
+.Fl net Li 128.32
+is interpreted as
+.Li 128.32.0.0;
+.Fl net Li 128.32.130
+is interpreted as
+.Li 128.32.130.0;
+and
+.Li 192.168.64/20
+is interpreted as
+.Fl net Li 192.168.64 Fl netmask Li 255.255.240.0 .
+.Pp
+A
+.Ar destination
+of
+.Ar default
+is a synonym for the default route.
+For
+.Li IPv4
+it is
+.Fl net Fl inet Li 0.0.0.0 ,
+and for
+.Li IPv6
+it is
+.Fl net Fl inet6 Li :: .
+.Pp
+If the destination is directly reachable
+via an interface requiring
+no intermediary system to act as a gateway, the
+.Fl interface
+modifier should be specified;
+the gateway given is the address of this host on the common network,
+indicating the interface to be used for transmission.
+Alternately, if the interface is point to point the name of the interface
+itself may be given, in which case the route remains valid even
+if the local or remote addresses change.
+.Pp
+The optional modifiers
+.Fl xns ,
+.Fl osi ,
+and
+.Fl link
+specify that all subsequent addresses are in the
+.Tn XNS
+or
+.Tn OSI
+address families,
+or are specified as link-level addresses,
+and the names must be numeric specifications rather than
+symbolic names.
+.Pp
+The optional
+.Fl netmask
+modifier is intended
+to achieve the effect of an
+.Tn OSI
+.Tn ESIS
+redirect with the netmask option,
+or to manually add subnet routes with
+netmasks different from that of the implied network interface
+(as would otherwise be communicated using the OSPF or ISIS routing protocols).
+One specifies an additional ensuing address parameter
+(to be interpreted as a network mask).
+The implicit network mask generated in the AF_INET case
+can be overridden by making sure this option follows the destination parameter.
+.Pp
+For
+.Dv AF_INET6 ,
+the
+.Fl prefixlen
+qualifier
+is available instead of the
+.Fl mask
+qualifier because non-continuous masks are not allowed in IPv6.
+For example,
+.Fl prefixlen Li 32
+specifies network mask of
+.Li ffff:ffff:0000:0000:0000:0000:0000:0000
+to be used.
+The default value of prefixlen is 64 to get along with
+the aggregatable address.
+But 0 is assumed if
+.Cm default
+is specified.
+Note that the qualifier works only for
+.Dv AF_INET6
+address family.
+.Pp
+Routes have associated flags which influence operation of the protocols
+when sending to destinations matched by the routes.
+These flags may be set (or sometimes cleared)
+by indicating the following corresponding modifiers:
+.Bd -literal
+-xresolve RTF_XRESOLVE - emit mesg on use (for external lookup)
+-iface ~RTF_GATEWAY - destination is directly reachable
+-static RTF_STATIC - manually added route
+-nostatic ~RTF_STATIC - pretend route added by kernel or daemon
+-reject RTF_REJECT - emit an ICMP unreachable when matched
+-blackhole RTF_BLACKHOLE - silently discard pkts (during updates)
+-proto1 RTF_PROTO1 - set protocol specific routing flag #1
+-proto2 RTF_PROTO2 - set protocol specific routing flag #2
+.Ed
+.Pp
+The optional modifiers
+.Fl rtt ,
+.Fl rttvar ,
+.Fl sendpipe ,
+.Fl recvpipe ,
+.Fl mtu ,
+.Fl hopcount ,
+.Fl expire ,
+and
+.Fl ssthresh
+provide initial values to quantities maintained in the routing entry
+by transport level protocols, such as TCP or TP4.
+These may be individually locked by preceding each such modifier to
+be locked by
+the
+.Fl lock
+meta-modifier, or one can
+specify that all ensuing metrics may be locked by the
+.Fl lockrest
+meta-modifier.
+.Pp
+Note that
+.Fl expire
+accepts expiration time of the route as the number of seconds since the
+Epoch
+.Pq see Xr time 3 .
+When the first character of the number is
+.Dq +
+or
+.Dq - ,
+it is interpreted as a value relative to the current time.
+.Pp
+The optional modifier
+.Fl fib Ar number
+specifies that the command will be applied to a non-default FIB.
+The
+.Ar number
+must be smaller than the
+.Va net.fibs
+.Xr sysctl 8
+MIB.
+When this modifier is not specified,
+or a negative number is specified,
+the default FIB shown in the
+.Va net.my_fibnum
+.Xr sysctl 8
+MIB will be used.
+.Pp
+The
+.Ar number
+allows multiple FIBs by a comma-separeted list and/or range
+specification.
+The
+.Qq Fl fib Li 2,4,6
+means the FIB number 2, 4, and 6.
+The
+.Qq Fl fib Li 1,3-5,6
+means the 1, 3, 4, 5, and 6.
+.Pp
+In a
+.Cm change
+or
+.Cm add
+command where the destination and gateway are not sufficient to specify
+the route (as in the
+.Tn ISO
+case where several interfaces may have the
+same address), the
+.Fl ifp
+or
+.Fl ifa
+modifiers may be used to determine the interface or interface address.
+.Pp
+All symbolic names specified for a
+.Ar destination
+or
+.Ar gateway
+are looked up first as a host name using
+.Xr gethostbyname 3 .
+If this lookup fails,
+.Xr getnetbyname 3
+is then used to interpret the name as that of a network.
+.Pp
+The
+.Nm
+utility uses a routing socket and the new message types
+.Dv RTM_ADD , RTM_DELETE , RTM_GET ,
+and
+.Dv RTM_CHANGE .
+As such, only the super-user may modify
+the routing tables.
+.Sh EXIT STATUS
+.Ex -std
+.Sh EXAMPLES
+Add a default route to the network routing table.
+This will send all packets for destinations not available in the routing table
+to the default gateway at 192.168.1.1:
+.Pp
+.Dl route add -net 0.0.0.0/0 192.168.1.1
+.Pp
+A shorter version of adding a default route can also be written as:
+.Pp
+.Dl route add default 192.168.1.1
+.Pp
+Add a static route to the 172.16.10.0/24 network via the 172.16.1.1 gateway:
+.Pp
+.Dl route add -net 172.16.10.0/24 172.16.1.1
+.Pp
+Change the gateway of an already established static route in the routing table:
+.Pp
+.Dl route change -net 172.16.10.0/24 172.16.1.2
+.Pp
+Display the route for a destination network:
+.Pp
+.Dl route show 172.16.10.0
+.Pp
+Delete a static route from the routing table:
+.Pp
+.Dl route delete -net 172.16.10.0/24 172.16.1.2
+.Pp
+Remove all routes from the routing table:
+.Pp
+.Dl route flush
+.Sh DIAGNOSTICS
+.Bl -diag
+.It "add [host \&| network ] %s: gateway %s flags %x"
+The specified route is being added to the tables.
+The
+values printed are from the routing table entry supplied
+in the
+.Xr ioctl 2
+call.
+If the gateway address used was not the primary address of the gateway
+(the first one returned by
+.Xr gethostbyname 3 ) ,
+the gateway address is printed numerically as well as symbolically.
+.It "delete [ host \&| network ] %s: gateway %s flags %x"
+As above, but when deleting an entry.
+.It "%s %s done"
+When the
+.Cm flush
+command is specified, each routing table entry deleted
+is indicated with a message of this form.
+.It "Network is unreachable"
+An attempt to add a route failed because the gateway listed was not
+on a directly-connected network.
+The next-hop gateway must be given.
+.It "not in table"
+A delete operation was attempted for an entry which
+was not present in the tables.
+.It "routing table overflow"
+An add operation was attempted, but the system was
+low on resources and was unable to allocate memory
+to create the new entry.
+.It "gateway uses the same route"
+A
+.Cm change
+operation resulted in a route whose gateway uses the
+same route as the one being changed.
+The next-hop gateway should be reachable through a different route.
+.El
+.Sh SEE ALSO
+.\".Xr esis 4 ,
+.Xr netintro 4 ,
+.Xr route 4 ,
+.Xr arp 8 ,
+.Xr routed 8
+.\".Xr XNSrouted 8
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Bx 4.2 .
+.Sh BUGS
+The first paragraph may have slightly exaggerated
+.Xr routed 8 Ns 's
+abilities.
+.Pp
+Currently, routes with the
+.Dv RTF_BLACKHOLE
+flag set need to have the gateway set to an instance of the
+.Xr lo 4
+driver, using the
+.Fl iface
+option, for the flag to have any effect; unless IP fast forwarding
+is enabled, in which case the meaning of the flag will always
+be honored.
diff --git a/sbin/route/route.c b/sbin/route/route.c
new file mode 100644
index 0000000..1bce41e
--- /dev/null
+++ b/sbin/route/route.c
@@ -0,0 +1,1929 @@
+/*
+ * Copyright (c) 1983, 1989, 1991, 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.
+ * 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 lint
+static const char copyright[] =
+"@(#) Copyright (c) 1983, 1989, 1991, 1993\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)route.c 8.6 (Berkeley) 4/28/95";
+#endif
+#endif /* not lint */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/file.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <sys/sysctl.h>
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include <net/if.h>
+#include <net/route.h>
+#include <net/if_dl.h>
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <paths.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <time.h>
+#include <unistd.h>
+#include <ifaddrs.h>
+
+struct fibl {
+ TAILQ_ENTRY(fibl) fl_next;
+
+ int fl_num;
+ int fl_error;
+ int fl_errno;
+};
+
+static struct keytab {
+ const char *kt_cp;
+ int kt_i;
+} const keywords[] = {
+#include "keywords.h"
+ {0, 0}
+};
+
+static struct sockaddr_storage so[RTAX_MAX];
+static int pid, rtm_addrs;
+static int s;
+static int nflag, af, qflag, tflag;
+static int verbose, aflen;
+static int locking, lockrest, debugonly;
+static struct rt_metrics rt_metrics;
+static u_long rtm_inits;
+static uid_t uid;
+static int defaultfib;
+static int numfibs;
+static char domain[MAXHOSTNAMELEN + 1];
+static bool domain_initialized;
+static int rtm_seq;
+static char rt_line[NI_MAXHOST];
+static char net_line[MAXHOSTNAMELEN + 1];
+
+static struct {
+ struct rt_msghdr m_rtm;
+ char m_space[512];
+} m_rtmsg;
+
+static TAILQ_HEAD(fibl_head_t, fibl) fibl_head;
+
+static void printb(int, const char *);
+static void flushroutes(int argc, char *argv[]);
+static int flushroutes_fib(int);
+static int getaddr(int, char *, struct hostent **, int);
+static int keyword(const char *);
+#ifdef INET
+static void inet_makenetandmask(u_long, struct sockaddr_in *,
+ struct sockaddr_in *, u_long);
+#endif
+#ifdef INET6
+static int inet6_makenetandmask(struct sockaddr_in6 *, const char *);
+#endif
+static void interfaces(void);
+static void monitor(int, char*[]);
+static const char *netname(struct sockaddr *);
+static void newroute(int, char **);
+static int newroute_fib(int, char *, int);
+static void pmsg_addrs(char *, int, size_t);
+static void pmsg_common(struct rt_msghdr *, size_t);
+static int prefixlen(const char *);
+static void print_getmsg(struct rt_msghdr *, int, int);
+static void print_rtmsg(struct rt_msghdr *, size_t);
+static const char *routename(struct sockaddr *);
+static int rtmsg(int, int, int);
+static void set_metric(char *, int);
+static int set_sofib(int);
+static void sockaddr(char *, struct sockaddr *, size_t);
+static void sodump(struct sockaddr *, const char *);
+static int fiboptlist_csv(const char *, struct fibl_head_t *);
+static int fiboptlist_range(const char *, struct fibl_head_t *);
+
+static void usage(const char *) __dead2;
+
+static void
+usage(const char *cp)
+{
+ if (cp != NULL)
+ warnx("bad keyword: %s", cp);
+ errx(EX_USAGE, "usage: route [-46dnqtv] command [[modifiers] args]");
+ /* NOTREACHED */
+}
+
+int
+main(int argc, char **argv)
+{
+ int ch;
+ size_t len;
+
+ if (argc < 2)
+ usage(NULL);
+
+ while ((ch = getopt(argc, argv, "46nqdtv")) != -1)
+ switch(ch) {
+ case '4':
+#ifdef INET
+ af = AF_INET;
+ aflen = sizeof(struct sockaddr_in);
+#else
+ errx(1, "IPv4 support is not compiled in");
+#endif
+ break;
+ case '6':
+#ifdef INET6
+ af = AF_INET6;
+ aflen = sizeof(struct sockaddr_in6);
+#else
+ errx(1, "IPv6 support is not compiled in");
+#endif
+ break;
+ case 'n':
+ nflag = 1;
+ break;
+ case 'q':
+ qflag = 1;
+ break;
+ case 'v':
+ verbose = 1;
+ break;
+ case 't':
+ tflag = 1;
+ break;
+ case 'd':
+ debugonly = 1;
+ break;
+ case '?':
+ default:
+ usage(NULL);
+ }
+ argc -= optind;
+ argv += optind;
+
+ pid = getpid();
+ uid = geteuid();
+ if (tflag)
+ s = open(_PATH_DEVNULL, O_WRONLY, 0);
+ else
+ s = socket(PF_ROUTE, SOCK_RAW, 0);
+ if (s < 0)
+ err(EX_OSERR, "socket");
+
+ len = sizeof(numfibs);
+ if (sysctlbyname("net.fibs", (void *)&numfibs, &len, NULL, 0) == -1)
+ numfibs = -1;
+
+ len = sizeof(defaultfib);
+ if (numfibs != -1 &&
+ sysctlbyname("net.my_fibnum", (void *)&defaultfib, &len, NULL,
+ 0) == -1)
+ defaultfib = -1;
+
+ if (*argv != NULL)
+ switch (keyword(*argv)) {
+ case K_GET:
+ case K_SHOW:
+ uid = 0;
+ /* FALLTHROUGH */
+
+ case K_CHANGE:
+ case K_ADD:
+ case K_DEL:
+ case K_DELETE:
+ newroute(argc, argv);
+ /* NOTREACHED */
+
+ case K_MONITOR:
+ monitor(argc, argv);
+ /* NOTREACHED */
+
+ case K_FLUSH:
+ flushroutes(argc, argv);
+ exit(0);
+ /* NOTREACHED */
+ }
+ usage(*argv);
+ /* NOTREACHED */
+}
+
+static int
+set_sofib(int fib)
+{
+
+ if (fib < 0)
+ return (0);
+ return (setsockopt(s, SOL_SOCKET, SO_SETFIB, (void *)&fib,
+ sizeof(fib)));
+}
+
+static int
+fiboptlist_range(const char *arg, struct fibl_head_t *flh)
+{
+ struct fibl *fl;
+ char *str0, *str, *token, *endptr;
+ int fib[2], i, error;
+
+ str0 = str = strdup(arg);
+ error = 0;
+ i = 0;
+ while ((token = strsep(&str, "-")) != NULL) {
+ switch (i) {
+ case 0:
+ case 1:
+ errno = 0;
+ fib[i] = strtol(token, &endptr, 0);
+ if (errno == 0) {
+ if (*endptr != '\0' ||
+ fib[i] < 0 ||
+ (numfibs != -1 && fib[i] > numfibs - 1))
+ errno = EINVAL;
+ }
+ if (errno)
+ error = 1;
+ break;
+ default:
+ error = 1;
+ }
+ if (error)
+ goto fiboptlist_range_ret;
+ i++;
+ }
+ if (fib[0] >= fib[1]) {
+ error = 1;
+ goto fiboptlist_range_ret;
+ }
+ for (i = fib[0]; i <= fib[1]; i++) {
+ fl = calloc(1, sizeof(*fl));
+ if (fl == NULL) {
+ error = 1;
+ goto fiboptlist_range_ret;
+ }
+ fl->fl_num = i;
+ TAILQ_INSERT_TAIL(flh, fl, fl_next);
+ }
+fiboptlist_range_ret:
+ free(str0);
+ return (error);
+}
+
+#define ALLSTRLEN 64
+static int
+fiboptlist_csv(const char *arg, struct fibl_head_t *flh)
+{
+ struct fibl *fl;
+ char *str0, *str, *token, *endptr;
+ int fib, error;
+
+ str0 = str = NULL;
+ if (strcmp("all", arg) == 0) {
+ str = calloc(1, ALLSTRLEN);
+ if (str == NULL) {
+ error = 1;
+ goto fiboptlist_csv_ret;
+ }
+ if (numfibs > 1)
+ snprintf(str, ALLSTRLEN - 1, "%d-%d", 0, numfibs - 1);
+ else
+ snprintf(str, ALLSTRLEN - 1, "%d", 0);
+ } else if (strcmp("default", arg) == 0) {
+ str0 = str = calloc(1, ALLSTRLEN);
+ if (str == NULL) {
+ error = 1;
+ goto fiboptlist_csv_ret;
+ }
+ snprintf(str, ALLSTRLEN - 1, "%d", defaultfib);
+ } else
+ str0 = str = strdup(arg);
+
+ error = 0;
+ while ((token = strsep(&str, ",")) != NULL) {
+ if (*token != '-' && strchr(token, '-') != NULL) {
+ error = fiboptlist_range(token, flh);
+ if (error)
+ goto fiboptlist_csv_ret;
+ } else {
+ errno = 0;
+ fib = strtol(token, &endptr, 0);
+ if (errno == 0) {
+ if (*endptr != '\0' ||
+ fib < 0 ||
+ (numfibs != -1 && fib > numfibs - 1))
+ errno = EINVAL;
+ }
+ if (errno) {
+ error = 1;
+ goto fiboptlist_csv_ret;
+ }
+ fl = calloc(1, sizeof(*fl));
+ if (fl == NULL) {
+ error = 1;
+ goto fiboptlist_csv_ret;
+ }
+ fl->fl_num = fib;
+ TAILQ_INSERT_TAIL(flh, fl, fl_next);
+ }
+ }
+fiboptlist_csv_ret:
+ if (str0 != NULL)
+ free(str0);
+ return (error);
+}
+
+/*
+ * Purge all entries in the routing tables not
+ * associated with network interfaces.
+ */
+static void
+flushroutes(int argc, char *argv[])
+{
+ struct fibl *fl;
+ int error;
+
+ if (uid != 0 && !debugonly && !tflag)
+ errx(EX_NOPERM, "must be root to alter routing table");
+ shutdown(s, SHUT_RD); /* Don't want to read back our messages */
+
+ TAILQ_INIT(&fibl_head);
+ while (argc > 1) {
+ argc--;
+ argv++;
+ if (**argv != '-')
+ usage(*argv);
+ switch (keyword(*argv + 1)) {
+#ifdef INET
+ case K_4:
+ case K_INET:
+ af = AF_INET;
+ break;
+#endif
+#ifdef INET6
+ case K_6:
+ case K_INET6:
+ af = AF_INET6;
+ break;
+#endif
+ case K_LINK:
+ af = AF_LINK;
+ break;
+ case K_FIB:
+ if (!--argc)
+ usage(*argv);
+ error = fiboptlist_csv(*++argv, &fibl_head);
+ if (error)
+ errx(EX_USAGE, "invalid fib number: %s", *argv);
+ break;
+ default:
+ usage(*argv);
+ }
+ }
+ if (TAILQ_EMPTY(&fibl_head)) {
+ error = fiboptlist_csv("default", &fibl_head);
+ if (error)
+ errx(EX_OSERR, "fiboptlist_csv failed.");
+ }
+ TAILQ_FOREACH(fl, &fibl_head, fl_next)
+ flushroutes_fib(fl->fl_num);
+}
+
+static int
+flushroutes_fib(int fib)
+{
+ struct rt_msghdr *rtm;
+ size_t needed;
+ char *buf, *next, *lim;
+ int mib[7], rlen, seqno, count = 0;
+ int error;
+
+ error = set_sofib(fib);
+ if (error) {
+ warn("fib number %d is ignored", fib);
+ return (error);
+ }
+
+retry:
+ mib[0] = CTL_NET;
+ mib[1] = PF_ROUTE;
+ mib[2] = 0; /* protocol */
+ mib[3] = AF_UNSPEC;
+ mib[4] = NET_RT_DUMP;
+ mib[5] = 0; /* no flags */
+ mib[6] = fib;
+ if (sysctl(mib, nitems(mib), NULL, &needed, NULL, 0) < 0)
+ err(EX_OSERR, "route-sysctl-estimate");
+ if ((buf = malloc(needed)) == NULL)
+ errx(EX_OSERR, "malloc failed");
+ if (sysctl(mib, nitems(mib), buf, &needed, NULL, 0) < 0) {
+ if (errno == ENOMEM && count++ < 10) {
+ warnx("Routing table grew, retrying");
+ sleep(1);
+ free(buf);
+ goto retry;
+ }
+ err(EX_OSERR, "route-sysctl-get");
+ }
+ lim = buf + needed;
+ if (verbose)
+ (void)printf("Examining routing table from sysctl\n");
+ seqno = 0; /* ??? */
+ for (next = buf; next < lim; next += rtm->rtm_msglen) {
+ rtm = (struct rt_msghdr *)(void *)next;
+ if (verbose)
+ print_rtmsg(rtm, rtm->rtm_msglen);
+ if ((rtm->rtm_flags & RTF_GATEWAY) == 0)
+ continue;
+ if (af != 0) {
+ struct sockaddr *sa = (struct sockaddr *)(rtm + 1);
+
+ if (sa->sa_family != af)
+ continue;
+ }
+ if (debugonly)
+ continue;
+ rtm->rtm_type = RTM_DELETE;
+ rtm->rtm_seq = seqno;
+ rlen = write(s, next, rtm->rtm_msglen);
+ if (rlen < 0 && errno == EPERM)
+ err(1, "write to routing socket");
+ if (rlen < (int)rtm->rtm_msglen) {
+ warn("write to routing socket");
+ (void)printf("got only %d for rlen\n", rlen);
+ free(buf);
+ goto retry;
+ break;
+ }
+ seqno++;
+ if (qflag)
+ continue;
+ if (verbose)
+ print_rtmsg(rtm, rlen);
+ else {
+ struct sockaddr *sa = (struct sockaddr *)(rtm + 1);
+
+ printf("%-20.20s ", rtm->rtm_flags & RTF_HOST ?
+ routename(sa) : netname(sa));
+ sa = (struct sockaddr *)(SA_SIZE(sa) + (char *)sa);
+ printf("%-20.20s ", routename(sa));
+ if (fib >= 0)
+ printf("-fib %-3d ", fib);
+ printf("done\n");
+ }
+ }
+ return (error);
+}
+
+static const char *
+routename(struct sockaddr *sa)
+{
+ struct sockaddr_dl *sdl;
+ const char *cp;
+ int n;
+
+ if (!domain_initialized) {
+ domain_initialized = true;
+ if (gethostname(domain, MAXHOSTNAMELEN) == 0 &&
+ (cp = strchr(domain, '.'))) {
+ domain[MAXHOSTNAMELEN] = '\0';
+ (void)strcpy(domain, cp + 1);
+ } else
+ domain[0] = '\0';
+ }
+
+ /* If the address is zero-filled, use "default". */
+ if (sa->sa_len == 0 && nflag == 0)
+ return ("default");
+#if defined(INET) || defined(INET6)
+ switch (sa->sa_family) {
+#ifdef INET
+ case AF_INET:
+ /* If the address is zero-filled, use "default". */
+ if (nflag == 0 &&
+ ((struct sockaddr_in *)(void *)sa)->sin_addr.s_addr ==
+ INADDR_ANY)
+ return("default");
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ /* If the address is zero-filled, use "default". */
+ if (nflag == 0 &&
+ IN6_IS_ADDR_UNSPECIFIED(&((struct sockaddr_in6 *)(void *)sa)->sin6_addr))
+ return("default");
+ break;
+#endif
+ }
+#endif
+
+ switch (sa->sa_family) {
+#if defined(INET) || defined(INET6)
+#ifdef INET
+ case AF_INET:
+#endif
+#ifdef INET6
+ case AF_INET6:
+#endif
+ {
+ struct sockaddr_storage ss;
+ int error;
+ char *p;
+
+ memset(&ss, 0, sizeof(ss));
+ if (sa->sa_len == 0)
+ ss.ss_family = sa->sa_family;
+ else
+ memcpy(&ss, sa, sa->sa_len);
+ /* Expand sa->sa_len because it could be shortened. */
+ if (sa->sa_family == AF_INET)
+ ss.ss_len = sizeof(struct sockaddr_in);
+ else if (sa->sa_family == AF_INET6)
+ ss.ss_len = sizeof(struct sockaddr_in6);
+ error = getnameinfo((struct sockaddr *)&ss, ss.ss_len,
+ rt_line, sizeof(rt_line), NULL, 0,
+ (nflag == 0) ? 0 : NI_NUMERICHOST);
+ if (error) {
+ warnx("getnameinfo(): %s", gai_strerror(error));
+ strncpy(rt_line, "invalid", sizeof(rt_line));
+ }
+
+ /* Remove the domain part if any. */
+ p = strchr(rt_line, '.');
+ if (p != NULL && strcmp(p + 1, domain) == 0)
+ *p = '\0';
+
+ return (rt_line);
+ break;
+ }
+#endif
+ case AF_LINK:
+ sdl = (struct sockaddr_dl *)(void *)sa;
+
+ if (sdl->sdl_nlen == 0 &&
+ sdl->sdl_alen == 0 &&
+ sdl->sdl_slen == 0) {
+ n = snprintf(rt_line, sizeof(rt_line), "link#%d",
+ sdl->sdl_index);
+ if (n > (int)sizeof(rt_line))
+ rt_line[0] = '\0';
+ return (rt_line);
+ } else
+ return (link_ntoa(sdl));
+ break;
+
+ default:
+ {
+ u_short *sp = (u_short *)(void *)sa;
+ u_short *splim = sp + ((sa->sa_len + 1) >> 1);
+ char *cps = rt_line + sprintf(rt_line, "(%d)", sa->sa_family);
+ char *cpe = rt_line + sizeof(rt_line);
+
+ while (++sp < splim && cps < cpe) /* start with sa->sa_data */
+ if ((n = snprintf(cps, cpe - cps, " %x", *sp)) > 0)
+ cps += n;
+ else
+ *cps = '\0';
+ break;
+ }
+ }
+ return (rt_line);
+}
+
+/*
+ * Return the name of the network whose address is given.
+ * The address is assumed to be that of a net, not a host.
+ */
+static const char *
+netname(struct sockaddr *sa)
+{
+ struct sockaddr_dl *sdl;
+ int n;
+#ifdef INET
+ struct netent *np = NULL;
+ const char *cp = NULL;
+ u_long i;
+#endif
+
+ switch (sa->sa_family) {
+#ifdef INET
+ case AF_INET:
+ {
+ struct in_addr in;
+
+ in = ((struct sockaddr_in *)(void *)sa)->sin_addr;
+ i = in.s_addr = ntohl(in.s_addr);
+ if (in.s_addr == 0)
+ cp = "default";
+ else if (!nflag) {
+ np = getnetbyaddr(i, AF_INET);
+ if (np != NULL)
+ cp = np->n_name;
+ }
+#define C(x) (unsigned)((x) & 0xff)
+ if (cp != NULL)
+ strncpy(net_line, cp, sizeof(net_line));
+ else if ((in.s_addr & 0xffffff) == 0)
+ (void)sprintf(net_line, "%u", C(in.s_addr >> 24));
+ else if ((in.s_addr & 0xffff) == 0)
+ (void)sprintf(net_line, "%u.%u", C(in.s_addr >> 24),
+ C(in.s_addr >> 16));
+ else if ((in.s_addr & 0xff) == 0)
+ (void)sprintf(net_line, "%u.%u.%u", C(in.s_addr >> 24),
+ C(in.s_addr >> 16), C(in.s_addr >> 8));
+ else
+ (void)sprintf(net_line, "%u.%u.%u.%u", C(in.s_addr >> 24),
+ C(in.s_addr >> 16), C(in.s_addr >> 8),
+ C(in.s_addr));
+#undef C
+ break;
+ }
+#endif
+#ifdef INET6
+ case AF_INET6:
+ {
+ struct sockaddr_in6 sin6;
+ int niflags = 0;
+
+ memset(&sin6, 0, sizeof(sin6));
+ memcpy(&sin6, sa, sa->sa_len);
+ sin6.sin6_len = sizeof(sin6);
+ sin6.sin6_family = AF_INET6;
+ if (nflag)
+ niflags |= NI_NUMERICHOST;
+ if (getnameinfo((struct sockaddr *)&sin6, sin6.sin6_len,
+ net_line, sizeof(net_line), NULL, 0, niflags) != 0)
+ strncpy(net_line, "invalid", sizeof(net_line));
+
+ return(net_line);
+ }
+#endif
+ case AF_LINK:
+ sdl = (struct sockaddr_dl *)(void *)sa;
+
+ if (sdl->sdl_nlen == 0 &&
+ sdl->sdl_alen == 0 &&
+ sdl->sdl_slen == 0) {
+ n = snprintf(net_line, sizeof(net_line), "link#%d",
+ sdl->sdl_index);
+ if (n > (int)sizeof(net_line))
+ net_line[0] = '\0';
+ return (net_line);
+ } else
+ return (link_ntoa(sdl));
+ break;
+
+ default:
+ {
+ u_short *sp = (u_short *)(void *)sa->sa_data;
+ u_short *splim = sp + ((sa->sa_len + 1)>>1);
+ char *cps = net_line + sprintf(net_line, "af %d:", sa->sa_family);
+ char *cpe = net_line + sizeof(net_line);
+
+ while (sp < splim && cps < cpe)
+ if ((n = snprintf(cps, cpe - cps, " %x", *sp++)) > 0)
+ cps += n;
+ else
+ *cps = '\0';
+ break;
+ }
+ }
+ return (net_line);
+}
+
+static void
+set_metric(char *value, int key)
+{
+ int flag = 0;
+ char *endptr;
+ u_long noval, *valp = &noval;
+
+ switch (key) {
+#define caseof(x, y, z) case x: valp = &rt_metrics.z; flag = y; break
+ caseof(K_MTU, RTV_MTU, rmx_mtu);
+ caseof(K_HOPCOUNT, RTV_HOPCOUNT, rmx_hopcount);
+ caseof(K_EXPIRE, RTV_EXPIRE, rmx_expire);
+ caseof(K_RECVPIPE, RTV_RPIPE, rmx_recvpipe);
+ caseof(K_SENDPIPE, RTV_SPIPE, rmx_sendpipe);
+ caseof(K_SSTHRESH, RTV_SSTHRESH, rmx_ssthresh);
+ caseof(K_RTT, RTV_RTT, rmx_rtt);
+ caseof(K_RTTVAR, RTV_RTTVAR, rmx_rttvar);
+ caseof(K_WEIGHT, RTV_WEIGHT, rmx_weight);
+ }
+ rtm_inits |= flag;
+ if (lockrest || locking)
+ rt_metrics.rmx_locks |= flag;
+ if (locking)
+ locking = 0;
+ errno = 0;
+ *valp = strtol(value, &endptr, 0);
+ if (errno == 0 && *endptr != '\0')
+ errno = EINVAL;
+ if (errno)
+ err(EX_USAGE, "%s", value);
+ if (flag & RTV_EXPIRE && (value[0] == '+' || value[0] == '-')) {
+ struct timespec ts;
+
+ clock_gettime(CLOCK_REALTIME_FAST, &ts);
+ *valp += ts.tv_sec;
+ }
+}
+
+#define F_ISHOST 0x01
+#define F_FORCENET 0x02
+#define F_FORCEHOST 0x04
+#define F_PROXY 0x08
+#define F_INTERFACE 0x10
+
+static void
+newroute(int argc, char **argv)
+{
+ struct hostent *hp;
+ struct fibl *fl;
+ char *cmd;
+ const char *dest, *gateway, *errmsg;
+ int key, error, flags, nrflags, fibnum;
+
+ if (uid != 0 && !debugonly && !tflag)
+ errx(EX_NOPERM, "must be root to alter routing table");
+ dest = NULL;
+ gateway = NULL;
+ flags = RTF_STATIC;
+ nrflags = 0;
+ hp = NULL;
+ TAILQ_INIT(&fibl_head);
+
+ cmd = argv[0];
+ if (*cmd != 'g' && *cmd != 's')
+ shutdown(s, SHUT_RD); /* Don't want to read back our messages */
+ while (--argc > 0) {
+ if (**(++argv)== '-') {
+ switch (key = keyword(1 + *argv)) {
+ case K_LINK:
+ af = AF_LINK;
+ aflen = sizeof(struct sockaddr_dl);
+ break;
+#ifdef INET
+ case K_4:
+ case K_INET:
+ af = AF_INET;
+ aflen = sizeof(struct sockaddr_in);
+ break;
+#endif
+#ifdef INET6
+ case K_6:
+ case K_INET6:
+ af = AF_INET6;
+ aflen = sizeof(struct sockaddr_in6);
+ break;
+#endif
+ case K_SA:
+ af = PF_ROUTE;
+ aflen = sizeof(struct sockaddr_storage);
+ break;
+ case K_IFACE:
+ case K_INTERFACE:
+ nrflags |= F_INTERFACE;
+ break;
+ case K_NOSTATIC:
+ flags &= ~RTF_STATIC;
+ break;
+ case K_LOCK:
+ locking = 1;
+ break;
+ case K_LOCKREST:
+ lockrest = 1;
+ break;
+ case K_HOST:
+ nrflags |= F_FORCEHOST;
+ break;
+ case K_REJECT:
+ flags |= RTF_REJECT;
+ break;
+ case K_BLACKHOLE:
+ flags |= RTF_BLACKHOLE;
+ break;
+ case K_PROTO1:
+ flags |= RTF_PROTO1;
+ break;
+ case K_PROTO2:
+ flags |= RTF_PROTO2;
+ break;
+ case K_PROXY:
+ nrflags |= F_PROXY;
+ break;
+ case K_XRESOLVE:
+ flags |= RTF_XRESOLVE;
+ break;
+ case K_STATIC:
+ flags |= RTF_STATIC;
+ break;
+ case K_STICKY:
+ flags |= RTF_STICKY;
+ break;
+ case K_NOSTICK:
+ flags &= ~RTF_STICKY;
+ break;
+ case K_FIB:
+ if (!--argc)
+ usage(NULL);
+ error = fiboptlist_csv(*++argv, &fibl_head);
+ if (error)
+ errx(EX_USAGE,
+ "invalid fib number: %s", *argv);
+ break;
+ case K_IFA:
+ if (!--argc)
+ usage(NULL);
+ getaddr(RTAX_IFA, *++argv, 0, nrflags);
+ break;
+ case K_IFP:
+ if (!--argc)
+ usage(NULL);
+ getaddr(RTAX_IFP, *++argv, 0, nrflags);
+ break;
+ case K_GENMASK:
+ if (!--argc)
+ usage(NULL);
+ getaddr(RTAX_GENMASK, *++argv, 0, nrflags);
+ break;
+ case K_GATEWAY:
+ if (!--argc)
+ usage(NULL);
+ getaddr(RTAX_GATEWAY, *++argv, 0, nrflags);
+ gateway = *argv;
+ break;
+ case K_DST:
+ if (!--argc)
+ usage(NULL);
+ if (getaddr(RTAX_DST, *++argv, &hp, nrflags))
+ nrflags |= F_ISHOST;
+ dest = *argv;
+ break;
+ case K_NETMASK:
+ if (!--argc)
+ usage(NULL);
+ getaddr(RTAX_NETMASK, *++argv, 0, nrflags);
+ /* FALLTHROUGH */
+ case K_NET:
+ nrflags |= F_FORCENET;
+ break;
+ case K_PREFIXLEN:
+ if (!--argc)
+ usage(NULL);
+ if (prefixlen(*++argv) == -1) {
+ nrflags &= ~F_FORCENET;
+ nrflags |= F_ISHOST;
+ } else {
+ nrflags |= F_FORCENET;
+ nrflags &= ~F_ISHOST;
+ }
+ break;
+ case K_MTU:
+ case K_HOPCOUNT:
+ case K_EXPIRE:
+ case K_RECVPIPE:
+ case K_SENDPIPE:
+ case K_SSTHRESH:
+ case K_RTT:
+ case K_RTTVAR:
+ case K_WEIGHT:
+ if (!--argc)
+ usage(NULL);
+ set_metric(*++argv, key);
+ break;
+ default:
+ usage(1+*argv);
+ }
+ } else {
+ if ((rtm_addrs & RTA_DST) == 0) {
+ dest = *argv;
+ if (getaddr(RTAX_DST, *argv, &hp, nrflags))
+ nrflags |= F_ISHOST;
+ } else if ((rtm_addrs & RTA_GATEWAY) == 0) {
+ gateway = *argv;
+ getaddr(RTAX_GATEWAY, *argv, &hp, nrflags);
+ } else {
+ getaddr(RTAX_NETMASK, *argv, 0, nrflags);
+ nrflags |= F_FORCENET;
+ }
+ }
+ }
+
+ /* Do some sanity checks on resulting request */
+ if (so[RTAX_DST].ss_len == 0) {
+ warnx("destination parameter required");
+ usage(NULL);
+ }
+
+ if (so[RTAX_NETMASK].ss_len != 0 &&
+ so[RTAX_DST].ss_family != so[RTAX_NETMASK].ss_family) {
+ warnx("destination and netmask family need to be the same");
+ usage(NULL);
+ }
+
+ if (nrflags & F_FORCEHOST) {
+ nrflags |= F_ISHOST;
+#ifdef INET6
+ if (af == AF_INET6) {
+ rtm_addrs &= ~RTA_NETMASK;
+ memset(&so[RTAX_NETMASK], 0, sizeof(so[RTAX_NETMASK]));
+ }
+#endif
+ }
+ if (nrflags & F_FORCENET)
+ nrflags &= ~F_ISHOST;
+ flags |= RTF_UP;
+ if (nrflags & F_ISHOST)
+ flags |= RTF_HOST;
+ if ((nrflags & F_INTERFACE) == 0)
+ flags |= RTF_GATEWAY;
+ if (nrflags & F_PROXY)
+ flags |= RTF_ANNOUNCE;
+ if (dest == NULL)
+ dest = "";
+ if (gateway == NULL)
+ gateway = "";
+
+ if (TAILQ_EMPTY(&fibl_head)) {
+ error = fiboptlist_csv("default", &fibl_head);
+ if (error)
+ errx(EX_OSERR, "fiboptlist_csv failed.");
+ }
+ error = 0;
+ TAILQ_FOREACH(fl, &fibl_head, fl_next) {
+ fl->fl_error = newroute_fib(fl->fl_num, cmd, flags);
+ if (fl->fl_error)
+ fl->fl_errno = errno;
+ error += fl->fl_error;
+ }
+ if (*cmd == 'g' || *cmd == 's')
+ exit(error);
+
+ error = 0;
+ if (!qflag) {
+ fibnum = 0;
+ TAILQ_FOREACH(fl, &fibl_head, fl_next) {
+ if (fl->fl_error == 0)
+ fibnum++;
+ }
+ if (fibnum > 0) {
+ int firstfib = 1;
+
+ printf("%s %s %s", cmd,
+ (nrflags & F_ISHOST) ? "host" : "net", dest);
+ if (*gateway)
+ printf(": gateway %s", gateway);
+
+ if (numfibs > 1) {
+ TAILQ_FOREACH(fl, &fibl_head, fl_next) {
+ if (fl->fl_error == 0
+ && fl->fl_num >= 0) {
+ if (firstfib) {
+ printf(" fib ");
+ firstfib = 0;
+ }
+ printf("%d", fl->fl_num);
+ if (fibnum-- > 1)
+ printf(",");
+ }
+ }
+ }
+ printf("\n");
+ }
+
+ fibnum = 0;
+ TAILQ_FOREACH(fl, &fibl_head, fl_next) {
+ if (fl->fl_error != 0) {
+ printf("%s %s %s", cmd, (nrflags & F_ISHOST)
+ ? "host" : "net", dest);
+ if (*gateway)
+ printf(": gateway %s", gateway);
+
+ if (fl->fl_num >= 0)
+ printf(" fib %d", fl->fl_num);
+
+ switch (fl->fl_errno) {
+ case ESRCH:
+ errmsg = "not in table";
+ break;
+ case EBUSY:
+ errmsg = "entry in use";
+ break;
+ case ENOBUFS:
+ errmsg = "not enough memory";
+ break;
+ case EADDRINUSE:
+ /*
+ * handle recursion avoidance
+ * in rt_setgate()
+ */
+ errmsg = "gateway uses the same route";
+ break;
+ case EEXIST:
+ errmsg = "route already in table";
+ break;
+ default:
+ errmsg = strerror(fl->fl_errno);
+ break;
+ }
+ printf(": %s\n", errmsg);
+ error = 1;
+ }
+ }
+ }
+ exit(error);
+}
+
+static int
+newroute_fib(int fib, char *cmd, int flags)
+{
+ int error;
+
+ error = set_sofib(fib);
+ if (error) {
+ warn("fib number %d is ignored", fib);
+ return (error);
+ }
+
+ error = rtmsg(*cmd, flags, fib);
+ return (error);
+}
+
+#ifdef INET
+static void
+inet_makenetandmask(u_long net, struct sockaddr_in *sin,
+ struct sockaddr_in *sin_mask, u_long bits)
+{
+ u_long mask = 0;
+
+ rtm_addrs |= RTA_NETMASK;
+
+ /*
+ * MSB of net should be meaningful. 0/0 is exception.
+ */
+ if (net > 0)
+ while ((net & 0xff000000) == 0)
+ net <<= 8;
+
+ /*
+ * If no /xx was specified we must calculate the
+ * CIDR address.
+ */
+ if ((bits == 0) && (net != 0)) {
+ u_long i, j;
+
+ for(i = 0, j = 0xff; i < 4; i++) {
+ if (net & j) {
+ break;
+ }
+ j <<= 8;
+ }
+ /* i holds the first non zero bit */
+ bits = 32 - (i*8);
+ }
+ if (bits != 0)
+ mask = 0xffffffff << (32 - bits);
+
+ sin->sin_addr.s_addr = htonl(net);
+ sin_mask->sin_addr.s_addr = htonl(mask);
+ sin_mask->sin_len = sizeof(struct sockaddr_in);
+ sin_mask->sin_family = AF_INET;
+}
+#endif
+
+#ifdef INET6
+/*
+ * XXX the function may need more improvement...
+ */
+static int
+inet6_makenetandmask(struct sockaddr_in6 *sin6, const char *plen)
+{
+
+ if (plen == NULL) {
+ if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr) &&
+ sin6->sin6_scope_id == 0)
+ plen = "0";
+ }
+
+ if (plen == NULL || strcmp(plen, "128") == 0)
+ return (1);
+ rtm_addrs |= RTA_NETMASK;
+ prefixlen(plen);
+ return (0);
+}
+#endif
+
+/*
+ * Interpret an argument as a network address of some kind,
+ * returning 1 if a host address, 0 if a network address.
+ */
+static int
+getaddr(int idx, char *str, struct hostent **hpp, int nrflags)
+{
+ struct sockaddr *sa;
+#if defined(INET)
+ struct sockaddr_in *sin;
+ struct hostent *hp;
+ struct netent *np;
+ u_long val;
+ char *q;
+#elif defined(INET6)
+ char *q;
+#endif
+
+ if (idx < 0 || idx >= RTAX_MAX)
+ usage("internal error");
+ if (af == 0) {
+#if defined(INET)
+ af = AF_INET;
+ aflen = sizeof(struct sockaddr_in);
+#elif defined(INET6)
+ af = AF_INET6;
+ aflen = sizeof(struct sockaddr_in6);
+#else
+ af = AF_LINK;
+ aflen = sizeof(struct sockaddr_dl);
+#endif
+ }
+#ifndef INET
+ hpp = NULL;
+#endif
+ rtm_addrs |= (1 << idx);
+ sa = (struct sockaddr *)&so[idx];
+ sa->sa_family = af;
+ sa->sa_len = aflen;
+
+ switch (idx) {
+ case RTAX_GATEWAY:
+ if (nrflags & F_INTERFACE) {
+ struct ifaddrs *ifap, *ifa;
+ struct sockaddr_dl *sdl0 = (struct sockaddr_dl *)(void *)sa;
+ struct sockaddr_dl *sdl = NULL;
+
+ if (getifaddrs(&ifap))
+ err(EX_OSERR, "getifaddrs");
+
+ for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) {
+ if (ifa->ifa_addr->sa_family != AF_LINK)
+ continue;
+
+ if (strcmp(str, ifa->ifa_name) != 0)
+ continue;
+
+ sdl = (struct sockaddr_dl *)(void *)ifa->ifa_addr;
+ }
+ /* If we found it, then use it */
+ if (sdl != NULL) {
+ /*
+ * Note that we need to copy before calling
+ * freeifaddrs().
+ */
+ memcpy(sdl0, sdl, sdl->sdl_len);
+ }
+ freeifaddrs(ifap);
+ if (sdl != NULL)
+ return(1);
+ }
+ break;
+ case RTAX_IFP:
+ sa->sa_family = AF_LINK;
+ break;
+ }
+ if (strcmp(str, "default") == 0) {
+ /*
+ * Default is net 0.0.0.0/0
+ */
+ switch (idx) {
+ case RTAX_DST:
+ nrflags |= F_FORCENET;
+ getaddr(RTAX_NETMASK, str, 0, nrflags);
+ break;
+ }
+ return (0);
+ }
+ switch (sa->sa_family) {
+#ifdef INET6
+ case AF_INET6:
+ {
+ struct addrinfo hints, *res;
+ int ecode;
+
+ q = NULL;
+ if (idx == RTAX_DST && (q = strchr(str, '/')) != NULL)
+ *q = '\0';
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = sa->sa_family;
+ hints.ai_socktype = SOCK_DGRAM;
+ ecode = getaddrinfo(str, NULL, &hints, &res);
+ if (ecode != 0 || res->ai_family != AF_INET6 ||
+ res->ai_addrlen != sizeof(struct sockaddr_in6))
+ errx(EX_OSERR, "%s: %s", str, gai_strerror(ecode));
+ memcpy(sa, res->ai_addr, res->ai_addrlen);
+ freeaddrinfo(res);
+ if (q != NULL)
+ *q++ = '/';
+ if (idx == RTAX_DST)
+ return (inet6_makenetandmask((struct sockaddr_in6 *)(void *)sa, q));
+ return (0);
+ }
+#endif /* INET6 */
+ case AF_LINK:
+ link_addr(str, (struct sockaddr_dl *)(void *)sa);
+ return (1);
+
+ case PF_ROUTE:
+ sockaddr(str, sa, sizeof(struct sockaddr_storage));
+ return (1);
+#ifdef INET
+ case AF_INET:
+#endif
+ default:
+ break;
+ }
+
+#ifdef INET
+ sin = (struct sockaddr_in *)(void *)sa;
+ if (hpp == NULL)
+ hpp = &hp;
+ *hpp = NULL;
+
+ q = strchr(str,'/');
+ if (q != NULL && idx == RTAX_DST) {
+ *q = '\0';
+ if ((val = inet_network(str)) != INADDR_NONE) {
+ inet_makenetandmask(val, sin,
+ (struct sockaddr_in *)&so[RTAX_NETMASK],
+ strtoul(q+1, 0, 0));
+ return (0);
+ }
+ *q = '/';
+ }
+ if ((idx != RTAX_DST || (nrflags & F_FORCENET) == 0) &&
+ inet_aton(str, &sin->sin_addr)) {
+ val = sin->sin_addr.s_addr;
+ if (idx != RTAX_DST || nrflags & F_FORCEHOST ||
+ inet_lnaof(sin->sin_addr) != INADDR_ANY)
+ return (1);
+ else {
+ val = ntohl(val);
+ goto netdone;
+ }
+ }
+ if (idx == RTAX_DST && (nrflags & F_FORCEHOST) == 0 &&
+ ((val = inet_network(str)) != INADDR_NONE ||
+ ((np = getnetbyname(str)) != NULL && (val = np->n_net) != 0))) {
+netdone:
+ inet_makenetandmask(val, sin,
+ (struct sockaddr_in *)&so[RTAX_NETMASK], 0);
+ return (0);
+ }
+ hp = gethostbyname(str);
+ if (hp != NULL) {
+ *hpp = hp;
+ sin->sin_family = hp->h_addrtype;
+ memmove((char *)&sin->sin_addr, hp->h_addr,
+ MIN((size_t)hp->h_length, sizeof(sin->sin_addr)));
+ return (1);
+ }
+#endif
+ errx(EX_NOHOST, "bad address: %s", str);
+}
+
+static int
+prefixlen(const char *str)
+{
+ int len = atoi(str), q, r;
+ int max;
+ char *p;
+
+ rtm_addrs |= RTA_NETMASK;
+ switch (af) {
+#ifdef INET6
+ case AF_INET6:
+ {
+ struct sockaddr_in6 *sin6 =
+ (struct sockaddr_in6 *)&so[RTAX_NETMASK];
+
+ max = 128;
+ p = (char *)&sin6->sin6_addr;
+ sin6->sin6_family = AF_INET6;
+ sin6->sin6_len = sizeof(*sin6);
+ break;
+ }
+#endif
+#ifdef INET
+ case AF_INET:
+ {
+ struct sockaddr_in *sin =
+ (struct sockaddr_in *)&so[RTAX_NETMASK];
+
+ max = 32;
+ p = (char *)&sin->sin_addr;
+ sin->sin_family = AF_INET;
+ sin->sin_len = sizeof(*sin);
+ break;
+ }
+#endif
+ default:
+ errx(EX_OSERR, "prefixlen not supported in this af");
+ }
+
+ if (len < 0 || max < len)
+ errx(EX_USAGE, "%s: invalid prefixlen", str);
+
+ q = len >> 3;
+ r = len & 7;
+ memset((void *)p, 0, max / 8);
+ if (q > 0)
+ memset((void *)p, 0xff, q);
+ if (r > 0)
+ *((u_char *)p + q) = (0xff00 >> r) & 0xff;
+ if (len == max)
+ return (-1);
+ else
+ return (len);
+}
+
+static void
+interfaces(void)
+{
+ size_t needed;
+ int mib[6];
+ char *buf, *lim, *next, count = 0;
+ struct rt_msghdr *rtm;
+
+retry2:
+ mib[0] = CTL_NET;
+ mib[1] = PF_ROUTE;
+ mib[2] = 0; /* protocol */
+ mib[3] = AF_UNSPEC;
+ mib[4] = NET_RT_IFLIST;
+ mib[5] = 0; /* no flags */
+ if (sysctl(mib, nitems(mib), NULL, &needed, NULL, 0) < 0)
+ err(EX_OSERR, "route-sysctl-estimate");
+ if ((buf = malloc(needed)) == NULL)
+ errx(EX_OSERR, "malloc failed");
+ if (sysctl(mib, nitems(mib), buf, &needed, NULL, 0) < 0) {
+ if (errno == ENOMEM && count++ < 10) {
+ warnx("Routing table grew, retrying");
+ sleep(1);
+ free(buf);
+ goto retry2;
+ }
+ err(EX_OSERR, "actual retrieval of interface table");
+ }
+ lim = buf + needed;
+ for (next = buf; next < lim; next += rtm->rtm_msglen) {
+ rtm = (struct rt_msghdr *)(void *)next;
+ print_rtmsg(rtm, rtm->rtm_msglen);
+ }
+}
+
+static void
+monitor(int argc, char *argv[])
+{
+ int n, fib, error;
+ char msg[2048], *endptr;
+
+ fib = defaultfib;
+ while (argc > 1) {
+ argc--;
+ argv++;
+ if (**argv != '-')
+ usage(*argv);
+ switch (keyword(*argv + 1)) {
+ case K_FIB:
+ if (!--argc)
+ usage(*argv);
+ errno = 0;
+ fib = strtol(*++argv, &endptr, 0);
+ if (errno == 0) {
+ if (*endptr != '\0' ||
+ fib < 0 ||
+ (numfibs != -1 && fib > numfibs - 1))
+ errno = EINVAL;
+ }
+ if (errno)
+ errx(EX_USAGE, "invalid fib number: %s", *argv);
+ break;
+ default:
+ usage(*argv);
+ }
+ }
+ error = set_sofib(fib);
+ if (error)
+ errx(EX_USAGE, "invalid fib number: %d", fib);
+
+ verbose = 1;
+ if (debugonly) {
+ interfaces();
+ exit(0);
+ }
+ for (;;) {
+ time_t now;
+ n = read(s, msg, 2048);
+ now = time(NULL);
+ (void)printf("\ngot message of size %d on %s", n, ctime(&now));
+ print_rtmsg((struct rt_msghdr *)(void *)msg, n);
+ }
+}
+
+static int
+rtmsg(int cmd, int flags, int fib)
+{
+ int rlen;
+ char *cp = m_rtmsg.m_space;
+ int l;
+
+#define NEXTADDR(w, u) \
+ if (rtm_addrs & (w)) { \
+ l = (((struct sockaddr *)&(u))->sa_len == 0) ? \
+ sizeof(long) : \
+ 1 + ((((struct sockaddr *)&(u))->sa_len - 1) \
+ | (sizeof(long) - 1)); \
+ memmove(cp, (char *)&(u), l); \
+ cp += l; \
+ if (verbose) \
+ sodump((struct sockaddr *)&(u), #w); \
+ }
+
+ errno = 0;
+ memset(&m_rtmsg, 0, sizeof(m_rtmsg));
+ if (cmd == 'a')
+ cmd = RTM_ADD;
+ else if (cmd == 'c')
+ cmd = RTM_CHANGE;
+ else if (cmd == 'g' || cmd == 's') {
+ cmd = RTM_GET;
+ if (so[RTAX_IFP].ss_family == 0) {
+ so[RTAX_IFP].ss_family = AF_LINK;
+ so[RTAX_IFP].ss_len = sizeof(struct sockaddr_dl);
+ rtm_addrs |= RTA_IFP;
+ }
+ } else
+ cmd = RTM_DELETE;
+#define rtm m_rtmsg.m_rtm
+ rtm.rtm_type = cmd;
+ rtm.rtm_flags = flags;
+ rtm.rtm_version = RTM_VERSION;
+ rtm.rtm_seq = ++rtm_seq;
+ rtm.rtm_addrs = rtm_addrs;
+ rtm.rtm_rmx = rt_metrics;
+ rtm.rtm_inits = rtm_inits;
+
+ NEXTADDR(RTA_DST, so[RTAX_DST]);
+ NEXTADDR(RTA_GATEWAY, so[RTAX_GATEWAY]);
+ NEXTADDR(RTA_NETMASK, so[RTAX_NETMASK]);
+ NEXTADDR(RTA_GENMASK, so[RTAX_GENMASK]);
+ NEXTADDR(RTA_IFP, so[RTAX_IFP]);
+ NEXTADDR(RTA_IFA, so[RTAX_IFA]);
+ rtm.rtm_msglen = l = cp - (char *)&m_rtmsg;
+ if (verbose)
+ print_rtmsg(&rtm, l);
+ if (debugonly)
+ return (0);
+ if ((rlen = write(s, (char *)&m_rtmsg, l)) < 0) {
+ switch (errno) {
+ case EPERM:
+ err(1, "writing to routing socket");
+ break;
+ case ESRCH:
+ warnx("route has not been found");
+ break;
+ case EEXIST:
+ /* Handled by newroute() */
+ break;
+ default:
+ warn("writing to routing socket");
+ }
+ return (-1);
+ }
+ if (cmd == RTM_GET) {
+ do {
+ l = read(s, (char *)&m_rtmsg, sizeof(m_rtmsg));
+ } while (l > 0 && (rtm.rtm_seq != rtm_seq || rtm.rtm_pid != pid));
+ if (l < 0)
+ warn("read from routing socket");
+ else
+ print_getmsg(&rtm, l, fib);
+ }
+#undef rtm
+ return (0);
+}
+
+static const char *const msgtypes[] = {
+ "",
+ "RTM_ADD: Add Route",
+ "RTM_DELETE: Delete Route",
+ "RTM_CHANGE: Change Metrics or flags",
+ "RTM_GET: Report Metrics",
+ "RTM_LOSING: Kernel Suspects Partitioning",
+ "RTM_REDIRECT: Told to use different route",
+ "RTM_MISS: Lookup failed on this address",
+ "RTM_LOCK: fix specified metrics",
+ "RTM_OLDADD: caused by SIOCADDRT",
+ "RTM_OLDDEL: caused by SIOCDELRT",
+ "RTM_RESOLVE: Route created by cloning",
+ "RTM_NEWADDR: address being added to iface",
+ "RTM_DELADDR: address being removed from iface",
+ "RTM_IFINFO: iface status change",
+ "RTM_NEWMADDR: new multicast group membership on iface",
+ "RTM_DELMADDR: multicast group membership removed from iface",
+ "RTM_IFANNOUNCE: interface arrival/departure",
+ "RTM_IEEE80211: IEEE 802.11 wireless event",
+};
+
+static const char metricnames[] =
+ "\011weight\010rttvar\7rtt\6ssthresh\5sendpipe\4recvpipe\3expire"
+ "\1mtu";
+static const char routeflags[] =
+ "\1UP\2GATEWAY\3HOST\4REJECT\5DYNAMIC\6MODIFIED\7DONE"
+ "\012XRESOLVE\013LLINFO\014STATIC\015BLACKHOLE"
+ "\017PROTO2\020PROTO1\021PRCLONING\022WASCLONED\023PROTO3"
+ "\024FIXEDMTU\025PINNED\026LOCAL\027BROADCAST\030MULTICAST\035STICKY";
+static const char ifnetflags[] =
+ "\1UP\2BROADCAST\3DEBUG\4LOOPBACK\5PTP\6b6\7RUNNING\010NOARP"
+ "\011PPROMISC\012ALLMULTI\013OACTIVE\014SIMPLEX\015LINK0\016LINK1"
+ "\017LINK2\020MULTICAST";
+static const char addrnames[] =
+ "\1DST\2GATEWAY\3NETMASK\4GENMASK\5IFP\6IFA\7AUTHOR\010BRD";
+
+static const char errfmt[] =
+ "\n%s: truncated route message, only %zu bytes left\n";
+
+static void
+print_rtmsg(struct rt_msghdr *rtm, size_t msglen)
+{
+ struct if_msghdr *ifm;
+ struct ifa_msghdr *ifam;
+#ifdef RTM_NEWMADDR
+ struct ifma_msghdr *ifmam;
+#endif
+ struct if_announcemsghdr *ifan;
+ const char *state;
+
+ if (verbose == 0)
+ return;
+ if (rtm->rtm_version != RTM_VERSION) {
+ (void)printf("routing message version %d not understood\n",
+ rtm->rtm_version);
+ return;
+ }
+ if (rtm->rtm_type < nitems(msgtypes))
+ (void)printf("%s: ", msgtypes[rtm->rtm_type]);
+ else
+ (void)printf("unknown type %d: ", rtm->rtm_type);
+ (void)printf("len %d, ", rtm->rtm_msglen);
+
+#define REQUIRE(x) do { \
+ if (msglen < sizeof(x)) \
+ goto badlen; \
+ else \
+ msglen -= sizeof(x); \
+ } while (0)
+
+ switch (rtm->rtm_type) {
+ case RTM_IFINFO:
+ REQUIRE(struct if_msghdr);
+ ifm = (struct if_msghdr *)rtm;
+ (void)printf("if# %d, ", ifm->ifm_index);
+ switch (ifm->ifm_data.ifi_link_state) {
+ case LINK_STATE_DOWN:
+ state = "down";
+ break;
+ case LINK_STATE_UP:
+ state = "up";
+ break;
+ default:
+ state = "unknown";
+ break;
+ }
+ (void)printf("link: %s, flags:", state);
+ printb(ifm->ifm_flags, ifnetflags);
+ pmsg_addrs((char *)(ifm + 1), ifm->ifm_addrs, msglen);
+ break;
+ case RTM_NEWADDR:
+ case RTM_DELADDR:
+ REQUIRE(struct ifa_msghdr);
+ ifam = (struct ifa_msghdr *)rtm;
+ (void)printf("metric %d, flags:", ifam->ifam_metric);
+ printb(ifam->ifam_flags, routeflags);
+ pmsg_addrs((char *)(ifam + 1), ifam->ifam_addrs, msglen);
+ break;
+#ifdef RTM_NEWMADDR
+ case RTM_NEWMADDR:
+ case RTM_DELMADDR:
+ REQUIRE(struct ifma_msghdr);
+ ifmam = (struct ifma_msghdr *)rtm;
+ pmsg_addrs((char *)(ifmam + 1), ifmam->ifmam_addrs, msglen);
+ break;
+#endif
+ case RTM_IFANNOUNCE:
+ REQUIRE(struct if_announcemsghdr);
+ ifan = (struct if_announcemsghdr *)rtm;
+ (void)printf("if# %d, what: ", ifan->ifan_index);
+ switch (ifan->ifan_what) {
+ case IFAN_ARRIVAL:
+ (void)printf("arrival");
+ break;
+ case IFAN_DEPARTURE:
+ printf("departure");
+ break;
+ default:
+ printf("#%d", ifan->ifan_what);
+ break;
+ }
+ printf("\n");
+ fflush(stdout);
+ break;
+
+ default:
+ printf("pid: %ld, seq %d, errno %d, flags:",
+ (long)rtm->rtm_pid, rtm->rtm_seq, rtm->rtm_errno);
+ printb(rtm->rtm_flags, routeflags);
+ pmsg_common(rtm, msglen);
+ }
+
+ return;
+
+badlen:
+ (void)printf(errfmt, __func__, msglen);
+#undef REQUIRE
+}
+
+static void
+print_getmsg(struct rt_msghdr *rtm, int msglen, int fib)
+{
+ struct sockaddr *sp[RTAX_MAX];
+ struct timespec ts;
+ char *cp;
+ int i;
+
+ memset(sp, 0, sizeof(sp));
+ (void)printf(" route to: %s\n",
+ routename((struct sockaddr *)&so[RTAX_DST]));
+ if (rtm->rtm_version != RTM_VERSION) {
+ warnx("routing message version %d not understood",
+ rtm->rtm_version);
+ return;
+ }
+ if (rtm->rtm_msglen > msglen) {
+ warnx("message length mismatch, in packet %d, returned %d",
+ rtm->rtm_msglen, msglen);
+ return;
+ }
+ if (rtm->rtm_errno) {
+ errno = rtm->rtm_errno;
+ warn("message indicates error %d", errno);
+ return;
+ }
+ cp = ((char *)(rtm + 1));
+ for (i = 0; i < RTAX_MAX; i++)
+ if (rtm->rtm_addrs & (1 << i)) {
+ sp[i] = (struct sockaddr *)cp;
+ cp += SA_SIZE((struct sockaddr *)cp);
+ }
+ if ((rtm->rtm_addrs & RTA_IFP) &&
+ (sp[RTAX_IFP]->sa_family != AF_LINK ||
+ ((struct sockaddr_dl *)(void *)sp[RTAX_IFP])->sdl_nlen == 0))
+ sp[RTAX_IFP] = NULL;
+ if (sp[RTAX_DST])
+ (void)printf("destination: %s\n", routename(sp[RTAX_DST]));
+ if (sp[RTAX_NETMASK])
+ (void)printf(" mask: %s\n", routename(sp[RTAX_NETMASK]));
+ if (sp[RTAX_GATEWAY] && (rtm->rtm_flags & RTF_GATEWAY))
+ (void)printf(" gateway: %s\n", routename(sp[RTAX_GATEWAY]));
+ if (fib >= 0)
+ (void)printf(" fib: %u\n", (unsigned int)fib);
+ if (sp[RTAX_IFP])
+ (void)printf(" interface: %.*s\n",
+ ((struct sockaddr_dl *)(void *)sp[RTAX_IFP])->sdl_nlen,
+ ((struct sockaddr_dl *)(void *)sp[RTAX_IFP])->sdl_data);
+ (void)printf(" flags: ");
+ printb(rtm->rtm_flags, routeflags);
+
+#define lock(f) ((rtm->rtm_rmx.rmx_locks & __CONCAT(RTV_,f)) ? 'L' : ' ')
+#define msec(u) (((u) + 500) / 1000) /* usec to msec */
+ printf("\n%9s %9s %9s %9s %9s %10s %9s\n", "recvpipe",
+ "sendpipe", "ssthresh", "rtt,msec", "mtu ", "weight", "expire");
+ printf("%8lu%c ", rtm->rtm_rmx.rmx_recvpipe, lock(RPIPE));
+ printf("%8lu%c ", rtm->rtm_rmx.rmx_sendpipe, lock(SPIPE));
+ printf("%8lu%c ", rtm->rtm_rmx.rmx_ssthresh, lock(SSTHRESH));
+ printf("%8lu%c ", msec(rtm->rtm_rmx.rmx_rtt), lock(RTT));
+ printf("%8lu%c ", rtm->rtm_rmx.rmx_mtu, lock(MTU));
+ printf("%8lu%c ", rtm->rtm_rmx.rmx_weight, lock(WEIGHT));
+ if (rtm->rtm_rmx.rmx_expire > 0)
+ clock_gettime(CLOCK_REALTIME_FAST, &ts);
+ else
+ ts.tv_sec = 0;
+ printf("%8ld%c\n", (long)(rtm->rtm_rmx.rmx_expire - ts.tv_sec),
+ lock(EXPIRE));
+#undef lock
+#undef msec
+#define RTA_IGN (RTA_DST|RTA_GATEWAY|RTA_NETMASK|RTA_IFP|RTA_IFA|RTA_BRD)
+ if (verbose)
+ pmsg_common(rtm, msglen);
+ else if (rtm->rtm_addrs &~ RTA_IGN) {
+ (void)printf("sockaddrs: ");
+ printb(rtm->rtm_addrs, addrnames);
+ putchar('\n');
+ }
+#undef RTA_IGN
+}
+
+static void
+pmsg_common(struct rt_msghdr *rtm, size_t msglen)
+{
+
+ (void)printf("\nlocks: ");
+ printb(rtm->rtm_rmx.rmx_locks, metricnames);
+ (void)printf(" inits: ");
+ printb(rtm->rtm_inits, metricnames);
+ if (msglen > sizeof(struct rt_msghdr))
+ pmsg_addrs(((char *)(rtm + 1)), rtm->rtm_addrs,
+ msglen - sizeof(struct rt_msghdr));
+ else
+ (void)fflush(stdout);
+}
+
+static void
+pmsg_addrs(char *cp, int addrs, size_t len)
+{
+ struct sockaddr *sa;
+ int i;
+
+ if (addrs == 0) {
+ (void)putchar('\n');
+ return;
+ }
+ (void)printf("\nsockaddrs: ");
+ printb(addrs, addrnames);
+ putchar('\n');
+ for (i = 0; i < RTAX_MAX; i++)
+ if (addrs & (1 << i)) {
+ sa = (struct sockaddr *)cp;
+ if (len == 0 || len < SA_SIZE(sa)) {
+ (void)printf(errfmt, __func__, len);
+ break;
+ }
+ (void)printf(" %s", routename(sa));
+ len -= SA_SIZE(sa);
+ cp += SA_SIZE(sa);
+ }
+ (void)putchar('\n');
+ (void)fflush(stdout);
+}
+
+static void
+printb(int b, const char *str)
+{
+ int i;
+ int gotsome = 0;
+
+ if (b == 0)
+ return;
+ while ((i = *str++) != 0) {
+ if (b & (1 << (i-1))) {
+ if (gotsome == 0)
+ i = '<';
+ else
+ i = ',';
+ putchar(i);
+ gotsome = 1;
+ for (; (i = *str) > 32; str++)
+ putchar(i);
+ } else
+ while (*str > 32)
+ str++;
+ }
+ if (gotsome)
+ putchar('>');
+}
+
+int
+keyword(const char *cp)
+{
+ const struct keytab *kt = keywords;
+
+ while (kt->kt_cp != NULL && strcmp(kt->kt_cp, cp) != 0)
+ kt++;
+ return (kt->kt_i);
+}
+
+static void
+sodump(struct sockaddr *sa, const char *which)
+{
+#ifdef INET6
+ char nbuf[INET6_ADDRSTRLEN];
+#endif
+
+ switch (sa->sa_family) {
+ case AF_LINK:
+ (void)printf("%s: link %s; ", which,
+ link_ntoa((struct sockaddr_dl *)(void *)sa));
+ break;
+#ifdef INET
+ case AF_INET:
+ (void)printf("%s: inet %s; ", which,
+ inet_ntoa(((struct sockaddr_in *)(void *)sa)->sin_addr));
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ (void)printf("%s: inet6 %s; ", which, inet_ntop(sa->sa_family,
+ &((struct sockaddr_in6 *)(void *)sa)->sin6_addr, nbuf,
+ sizeof(nbuf)));
+ break;
+#endif
+ }
+ (void)fflush(stdout);
+}
+
+/* States*/
+#define VIRGIN 0
+#define GOTONE 1
+#define GOTTWO 2
+/* Inputs */
+#define DIGIT (4*0)
+#define END (4*1)
+#define DELIM (4*2)
+
+static void
+sockaddr(char *addr, struct sockaddr *sa, size_t size)
+{
+ char *cp = (char *)sa;
+ char *cplim = cp + size;
+ int byte = 0, state = VIRGIN, new = 0 /* foil gcc */;
+
+ memset(cp, 0, size);
+ cp++;
+ do {
+ if ((*addr >= '0') && (*addr <= '9')) {
+ new = *addr - '0';
+ } else if ((*addr >= 'a') && (*addr <= 'f')) {
+ new = *addr - 'a' + 10;
+ } else if ((*addr >= 'A') && (*addr <= 'F')) {
+ new = *addr - 'A' + 10;
+ } else if (*addr == '\0')
+ state |= END;
+ else
+ state |= DELIM;
+ addr++;
+ switch (state /* | INPUT */) {
+ case GOTTWO | DIGIT:
+ *cp++ = byte; /*FALLTHROUGH*/
+ case VIRGIN | DIGIT:
+ state = GOTONE; byte = new; continue;
+ case GOTONE | DIGIT:
+ state = GOTTWO; byte = new + (byte << 4); continue;
+ default: /* | DELIM */
+ state = VIRGIN; *cp++ = byte; byte = 0; continue;
+ case GOTONE | END:
+ case GOTTWO | END:
+ *cp++ = byte; /* FALLTHROUGH */
+ case VIRGIN | END:
+ break;
+ }
+ break;
+ } while (cp < cplim);
+ sa->sa_len = cp - (char *)sa;
+}
diff --git a/sbin/routed/Makefile b/sbin/routed/Makefile
new file mode 100644
index 0000000..2308722
--- /dev/null
+++ b/sbin/routed/Makefile
@@ -0,0 +1,12 @@
+# Make `routed` for FreeBSD
+# $FreeBSD$
+
+PROG= routed
+MAN= routed.8
+SRCS= if.c input.c main.c output.c parms.c radix.c rdisc.c table.c trace.c
+WARNS?= 3
+LIBADD= md
+
+SUBDIR= rtquery
+
+.include <bsd.prog.mk>
diff --git a/sbin/routed/Makefile.inc b/sbin/routed/Makefile.inc
new file mode 100644
index 0000000..265f86d
--- /dev/null
+++ b/sbin/routed/Makefile.inc
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+.include "../Makefile.inc"
diff --git a/sbin/routed/defs.h b/sbin/routed/defs.h
new file mode 100644
index 0000000..a31d6f5
--- /dev/null
+++ b/sbin/routed/defs.h
@@ -0,0 +1,648 @@
+/*
+ * Copyright (c) 1983, 1988, 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.
+ * 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.
+ *
+ * @(#)defs.h 8.1 (Berkeley) 6/5/93
+ *
+ * $FreeBSD$
+ */
+
+#ifdef sgi
+#ident "$FreeBSD$"
+#endif
+
+/* Definitions for RIPv2 routing process.
+ *
+ * This code is based on the 4.4BSD `routed` daemon, with extensions to
+ * support:
+ * RIPv2, including variable length subnet masks.
+ * Router Discovery
+ * aggregate routes in the kernel tables.
+ * aggregate advertised routes.
+ * maintain spare routes for faster selection of another gateway
+ * when the current gateway dies.
+ * timers on routes with second granularity so that selection
+ * of a new route does not wait 30-60 seconds.
+ * tolerance of static routes.
+ * tell the kernel hop counts
+ * do not advertise if ipforwarding=0
+ *
+ * The vestigial support for other protocols has been removed. There
+ * is no likelihood that IETF RIPv1 or RIPv2 will ever be used with
+ * other protocols. The result is far smaller, faster, cleaner, and
+ * perhaps understandable.
+ *
+ * The accumulation of special flags and kludges added over the many
+ * years have been simplified and integrated.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <netdb.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#ifdef sgi
+#include <strings.h>
+#include <bstring.h>
+#endif
+#include <stdarg.h>
+#include <syslog.h>
+#include <time.h>
+#include <sys/cdefs.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/sysctl.h>
+#include <sys/socket.h>
+#include <sys/queue.h>
+#ifdef sgi
+#define _USER_ROUTE_TREE
+#include <net/radix.h>
+#else
+#include "radix.h"
+#define UNUSED __attribute__((unused))
+#define PATTRIB(f,l) __attribute__((format (printf,f,l)))
+#endif
+#include <net/if.h>
+#include <net/route.h>
+#include <net/if_dl.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#define RIPVERSION RIPv2
+#include <protocols/routed.h>
+
+#ifndef __RCSID
+#define __RCSID(_s) static const char rcsid[] UNUSED = _s
+#endif
+#ifndef __COPYRIGHT
+#define __COPYRIGHT(_s) static const char copyright[] UNUSED = _s
+#endif
+
+/* Type of an IP address.
+ * Some systems do not like to pass structures, so do not use in_addr.
+ * Some systems think a long has 64 bits, which would be a gross waste.
+ * So define it here so it can be changed for the target system.
+ * It should be defined somewhere netinet/in.h, but it is not.
+ */
+#ifdef sgi
+#define naddr u_int32_t
+#elif defined (__NetBSD__)
+#define naddr u_int32_t
+#define _HAVE_SA_LEN
+#define _HAVE_SIN_LEN
+#else
+#define naddr u_long
+#define _HAVE_SA_LEN
+#define _HAVE_SIN_LEN
+#endif
+
+#define DAY (24*60*60)
+#define NEVER DAY /* a long time */
+#define EPOCH NEVER /* bias time by this to avoid <0 */
+
+/* Scan the kernel regularly to see if any interfaces have appeared or been
+ * turned off. These must be less than STALE_TIME.
+ */
+#define CHECK_BAD_INTERVAL 5 /* when an interface is known bad */
+#define CHECK_ACT_INTERVAL 30 /* when advertising */
+#define CHECK_QUIET_INTERVAL 300 /* when not */
+
+#define LIM_SEC(s,l) ((s).tv_sec = MIN((s).tv_sec, (l)))
+
+/* Metric used for fake default routes. It ought to be 15, but when
+ * processing advertised routes, previous versions of `routed` added
+ * to the received metric and discarded the route if the total was 16
+ * or larger.
+ */
+#define FAKE_METRIC (HOPCNT_INFINITY-2)
+
+
+/* Router Discovery parameters */
+#ifndef sgi
+#define INADDR_ALLROUTERS_GROUP 0xe0000002 /* 224.0.0.2 */
+#endif
+#define MaxMaxAdvertiseInterval 1800
+#define MinMaxAdvertiseInterval 4
+#define DefMaxAdvertiseInterval 600
+#define MIN_PreferenceLevel 0x80000000
+
+#define MAX_INITIAL_ADVERT_INTERVAL 16
+#define MAX_INITIAL_ADVERTS 3
+
+#define MAX_SOLICITATION_DELAY 1
+#define SOLICITATION_INTERVAL 3
+#define MAX_SOLICITATIONS 3
+
+
+/* Bloated packet size for systems that simply add authentication to
+ * full-sized packets
+ */
+#define OVER_MAXPACKETSIZE (MAXPACKETSIZE+sizeof(struct netinfo)*2)
+/* typical packet buffers */
+union pkt_buf {
+ char packet[OVER_MAXPACKETSIZE*2];
+ struct rip rip;
+};
+
+#define GNAME_LEN 64 /* assumed=64 in parms.c */
+/* bigger than IFNAMSIZ, with room for "external()" or "remote()" */
+#define IF_NAME_LEN (GNAME_LEN+15)
+
+/* No more routes than this, to protect ourself in case something goes
+ * whacko and starts broadcasting zillions of bogus routes.
+ */
+#define MAX_ROUTES (128*1024)
+extern int total_routes;
+
+/* Main, daemon routing table structure
+ */
+struct rt_entry {
+ struct radix_node rt_nodes[2]; /* radix tree glue */
+ u_int rt_state;
+# define RS_IF 0x001 /* for network interface */
+# define RS_NET_INT 0x002 /* authority route */
+# define RS_NET_SYN 0x004 /* fake net route for subnet */
+# define RS_NO_NET_SYN (RS_LOCAL | RS_LOCAL | RS_IF)
+# define RS_SUBNET 0x008 /* subnet route from any source */
+# define RS_LOCAL 0x010 /* loopback for pt-to-pt */
+# define RS_MHOME 0x020 /* from -m */
+# define RS_STATIC 0x040 /* from the kernel */
+# define RS_RDISC 0x080 /* from router discovery */
+ struct sockaddr_in rt_dst_sock;
+ naddr rt_mask;
+ struct rt_spare {
+ struct interface *rts_ifp;
+ naddr rts_gate; /* forward packets here */
+ naddr rts_router; /* on the authority of this router */
+ char rts_metric;
+ u_short rts_tag;
+ time_t rts_time; /* timer to junk stale routes */
+ u_int rts_de_ag; /* de-aggregation level */
+#define NUM_SPARES 4
+ } rt_spares[NUM_SPARES];
+ u_int rt_seqno; /* when last changed */
+ char rt_poison_metric; /* to notice maximum recently */
+ time_t rt_poison_time; /* advertised metric */
+};
+#define rt_dst rt_dst_sock.sin_addr.s_addr
+#define rt_ifp rt_spares[0].rts_ifp
+#define rt_gate rt_spares[0].rts_gate
+#define rt_router rt_spares[0].rts_router
+#define rt_metric rt_spares[0].rts_metric
+#define rt_tag rt_spares[0].rts_tag
+#define rt_time rt_spares[0].rts_time
+#define rt_de_ag rt_spares[0].rts_de_ag
+
+#define HOST_MASK 0xffffffff
+#define RT_ISHOST(rt) ((rt)->rt_mask == HOST_MASK)
+
+/* age all routes that
+ * are not from -g, -m, or static routes from the kernel
+ * not unbroken interface routes
+ * but not broken interfaces
+ * nor non-passive, remote interfaces that are not aliases
+ * (i.e. remote & metric=0)
+ */
+#define AGE_RT(rt_state,ifp) (0 == ((rt_state) & (RS_MHOME | RS_STATIC \
+ | RS_NET_SYN | RS_RDISC)) \
+ && (!((rt_state) & RS_IF) \
+ || (ifp) == 0 \
+ || (((ifp)->int_state & IS_REMOTE) \
+ && !((ifp)->int_state & IS_PASSIVE))))
+
+/* true if A is better than B
+ * Better if
+ * - A is not a poisoned route
+ * - and A is not stale
+ * - and A has a shorter path
+ * - or is the router speaking for itself
+ * - or the current route is equal but stale
+ * - or it is a host route advertised by a system for itself
+ */
+#define BETTER_LINK(rt,A,B) ((A)->rts_metric < HOPCNT_INFINITY \
+ && now_stale <= (A)->rts_time \
+ && ((A)->rts_metric < (B)->rts_metric \
+ || ((A)->rts_gate == (A)->rts_router \
+ && (B)->rts_gate != (B)->rts_router) \
+ || ((A)->rts_metric == (B)->rts_metric \
+ && now_stale > (B)->rts_time) \
+ || (RT_ISHOST(rt) \
+ && (rt)->rt_dst == (A)->rts_router \
+ && (A)->rts_metric == (B)->rts_metric)))
+
+
+/* An "interface" is similar to a kernel ifnet structure, except it also
+ * handles "logical" or "IS_REMOTE" interfaces (remote gateways).
+ */
+struct interface {
+ LIST_ENTRY(interface) int_list;
+ LIST_ENTRY(interface) remote_list;
+ struct interface *int_ahash, **int_ahash_prev;
+ struct interface *int_bhash, **int_bhash_prev;
+ struct interface *int_nhash, **int_nhash_prev;
+ char int_name[IF_NAME_LEN+1];
+ u_short int_index;
+ naddr int_addr; /* address on this host (net order) */
+ naddr int_brdaddr; /* broadcast address (n) */
+ naddr int_dstaddr; /* other end of pt-to-pt link (n) */
+ naddr int_net; /* working network # (host order)*/
+ naddr int_mask; /* working net mask (host order) */
+ naddr int_ripv1_mask; /* for inferring a mask (n) */
+ naddr int_std_addr; /* class A/B/C address (n) */
+ naddr int_std_net; /* class A/B/C network (h) */
+ naddr int_std_mask; /* class A/B/C netmask (h) */
+ int int_rip_sock; /* for queries */
+ int int_if_flags; /* some bits copied from kernel */
+ u_int int_state;
+ time_t int_act_time; /* last thought healthy */
+ time_t int_query_time;
+ u_short int_transitions; /* times gone up-down */
+ char int_metric;
+ u_char int_d_metric; /* for faked default route */
+ u_char int_adj_inmetric; /* adjust advertised metrics */
+ u_char int_adj_outmetric; /* instead of interface metric */
+ struct int_data {
+ u_int ipackets; /* previous network stats */
+ u_int ierrors;
+ u_int opackets;
+ u_int oerrors;
+#ifdef sgi
+ u_int odrops;
+#endif
+ time_t ts; /* timestamp on network stats */
+ } int_data;
+# define MAX_AUTH_KEYS 5
+ struct auth { /* authentication info */
+ u_int16_t type;
+ u_char key[RIP_AUTH_PW_LEN];
+ u_char keyid;
+ time_t start, end;
+ } int_auth[MAX_AUTH_KEYS];
+ /* router discovery parameters */
+ int int_rdisc_pref; /* signed preference to advertise */
+ int int_rdisc_int; /* MaxAdvertiseInterval */
+ int int_rdisc_cnt;
+ struct timeval int_rdisc_timer;
+};
+
+/* bits in int_state */
+#define IS_ALIAS 0x0000001 /* interface alias */
+#define IS_SUBNET 0x0000002 /* interface on subnetted network */
+#define IS_REMOTE 0x0000004 /* interface is not on this machine */
+#define IS_PASSIVE 0x0000008 /* remote and does not do RIP */
+#define IS_EXTERNAL 0x0000010 /* handled by EGP or something */
+#define IS_CHECKED 0x0000020 /* still exists */
+#define IS_ALL_HOSTS 0x0000040 /* in INADDR_ALLHOSTS_GROUP */
+#define IS_ALL_ROUTERS 0x0000080 /* in INADDR_ALLROUTERS_GROUP */
+#define IS_DISTRUST 0x0000100 /* ignore untrusted routers */
+#define IS_REDIRECT_OK 0x0000200 /* accept ICMP redirects */
+#define IS_BROKE 0x0000400 /* seems to be broken */
+#define IS_SICK 0x0000800 /* seems to be broken */
+#define IS_DUP 0x0001000 /* has a duplicate address */
+#define IS_NEED_NET_SYN 0x0002000 /* need RS_NET_SYN route */
+#define IS_NO_AG 0x0004000 /* do not aggregate subnets */
+#define IS_NO_SUPER_AG 0x0008000 /* do not aggregate networks */
+#define IS_NO_RIPV1_IN 0x0010000 /* no RIPv1 input at all */
+#define IS_NO_RIPV2_IN 0x0020000 /* no RIPv2 input at all */
+#define IS_NO_RIP_IN (IS_NO_RIPV1_IN | IS_NO_RIPV2_IN)
+#define IS_RIP_IN_OFF(s) (((s) & IS_NO_RIP_IN) == IS_NO_RIP_IN)
+#define IS_NO_RIPV1_OUT 0x0040000 /* no RIPv1 output at all */
+#define IS_NO_RIPV2_OUT 0x0080000 /* no RIPv2 output at all */
+#define IS_NO_RIP_OUT (IS_NO_RIPV1_OUT | IS_NO_RIPV2_OUT)
+#define IS_NO_RIP (IS_NO_RIP_OUT | IS_NO_RIP_IN)
+#define IS_RIP_OUT_OFF(s) (((s) & IS_NO_RIP_OUT) == IS_NO_RIP_OUT)
+#define IS_RIP_OFF(s) (((s) & IS_NO_RIP) == IS_NO_RIP)
+#define IS_NO_RIP_MCAST 0x0100000 /* broadcast RIPv2 */
+#define IS_NO_ADV_IN 0x0200000 /* do not listen to advertisements */
+#define IS_NO_SOL_OUT 0x0400000 /* send no solicitations */
+#define IS_SOL_OUT 0x0800000 /* send solicitations */
+#define GROUP_IS_SOL_OUT (IS_SOL_OUT | IS_NO_SOL_OUT)
+#define IS_NO_ADV_OUT 0x1000000 /* do not advertise rdisc */
+#define IS_ADV_OUT 0x2000000 /* advertise rdisc */
+#define GROUP_IS_ADV_OUT (IS_NO_ADV_OUT | IS_ADV_OUT)
+#define IS_BCAST_RDISC 0x4000000 /* broadcast instead of multicast */
+#define IS_NO_RDISC (IS_NO_ADV_IN | IS_NO_SOL_OUT | IS_NO_ADV_OUT)
+#define IS_PM_RDISC 0x8000000 /* poor-man's router discovery */
+
+#define iff_up(f) ((f) & IFF_UP)
+
+LIST_HEAD(ifhead, interface);
+
+/* Information for aggregating routes */
+#define NUM_AG_SLOTS 32
+struct ag_info {
+ struct ag_info *ag_fine; /* slot with finer netmask */
+ struct ag_info *ag_cors; /* more coarse netmask */
+ naddr ag_dst_h; /* destination in host byte order */
+ naddr ag_mask;
+ naddr ag_gate;
+ naddr ag_nhop;
+ char ag_metric; /* metric to be advertised */
+ char ag_pref; /* aggregate based on this */
+ u_int ag_seqno;
+ u_short ag_tag;
+ u_short ag_state;
+#define AGS_SUPPRESS 0x001 /* combine with coarser mask */
+#define AGS_AGGREGATE 0x002 /* synthesize combined routes */
+#define AGS_REDUN0 0x004 /* redundant, finer routes output */
+#define AGS_REDUN1 0x008
+#define AG_IS_REDUN(state) (((state) & (AGS_REDUN0 | AGS_REDUN1)) \
+ == (AGS_REDUN0 | AGS_REDUN1))
+#define AGS_GATEWAY 0x010 /* tell kernel RTF_GATEWAY */
+#define AGS_IF 0x020 /* for an interface */
+#define AGS_RIPV2 0x040 /* send only as RIPv2 */
+#define AGS_FINE_GATE 0x080 /* ignore differing ag_gate when this
+ * has the finer netmask */
+#define AGS_CORS_GATE 0x100 /* ignore differing gate when this
+ * has the coarser netmasks */
+#define AGS_SPLIT_HZ 0x200 /* suppress for split horizon */
+
+ /* some bits are set if they are set on either route */
+#define AGS_AGGREGATE_EITHER (AGS_RIPV2 | AGS_GATEWAY | \
+ AGS_SUPPRESS | AGS_CORS_GATE)
+};
+
+
+/* parameters for interfaces */
+struct parm {
+ struct parm *parm_next;
+ char parm_name[IF_NAME_LEN+1];
+ naddr parm_net;
+ naddr parm_mask;
+
+ u_char parm_d_metric;
+ u_char parm_adj_inmetric;
+ char parm_adj_outmetric;
+ u_int parm_int_state;
+ int parm_rdisc_pref; /* signed IRDP preference */
+ int parm_rdisc_int; /* IRDP advertising interval */
+ struct auth parm_auth[MAX_AUTH_KEYS];
+};
+
+/* authority for internal networks */
+extern struct intnet {
+ struct intnet *intnet_next;
+ naddr intnet_addr; /* network byte order */
+ naddr intnet_mask;
+ char intnet_metric;
+} *intnets;
+
+/* defined RIPv1 netmasks */
+extern struct r1net {
+ struct r1net *r1net_next;
+ naddr r1net_net; /* host order */
+ naddr r1net_match;
+ naddr r1net_mask;
+} *r1nets;
+
+/* trusted routers */
+extern struct tgate {
+ struct tgate *tgate_next;
+ naddr tgate_addr;
+#define MAX_TGATE_NETS 32
+ struct tgate_net {
+ naddr net; /* host order */
+ naddr mask;
+ } tgate_nets[MAX_TGATE_NETS];
+} *tgates;
+
+enum output_type {OUT_QUERY, OUT_UNICAST, OUT_BROADCAST, OUT_MULTICAST,
+ NO_OUT_MULTICAST, NO_OUT_RIPV2};
+
+/* common output buffers */
+extern struct ws_buf {
+ struct rip *buf;
+ struct netinfo *n;
+ struct netinfo *base;
+ struct netinfo *lim;
+ enum output_type type;
+} v12buf;
+
+extern pid_t mypid;
+extern naddr myaddr; /* main address of this system */
+
+extern int stopint; /* !=0 to stop */
+
+extern int rip_sock; /* RIP socket */
+extern const struct interface *rip_sock_mcast; /* current multicast interface */
+extern int rt_sock; /* routing socket */
+extern int rt_sock_seqno;
+extern int rdisc_sock; /* router-discovery raw socket */
+
+extern int supplier; /* process should supply updates */
+extern int supplier_set; /* -s or -q requested */
+extern int ridhosts; /* 1=reduce host routes */
+extern int mhome; /* 1=want multi-homed host route */
+extern int advertise_mhome; /* 1=must continue advertising it */
+extern int auth_ok; /* 1=ignore auth if we do not care */
+extern int insecure; /* Reply to special queries or not */
+
+extern struct timeval clk; /* system clock's idea of time */
+extern struct timeval epoch; /* system clock when started */
+extern struct timeval now; /* current idea of time */
+extern time_t now_stale;
+extern time_t now_expire;
+extern time_t now_garbage;
+
+extern struct timeval age_timer; /* next check of old routes */
+extern struct timeval no_flash; /* inhibit flash update until then */
+extern struct timeval rdisc_timer; /* next advert. or solicitation */
+extern int rdisc_ok; /* using solicited route */
+
+extern struct timeval ifinit_timer; /* time to check interfaces */
+
+extern naddr loopaddr; /* our address on loopback */
+extern int tot_interfaces; /* # of remote and local interfaces */
+extern int rip_interfaces; /* # of interfaces doing RIP */
+extern struct ifhead ifnet; /* all interfaces */
+extern struct ifhead remote_if; /* remote interfaces */
+extern int have_ripv1_out; /* have a RIPv1 interface */
+extern int need_flash; /* flash update needed */
+extern struct timeval need_kern; /* need to update kernel table */
+extern u_int update_seqno; /* a route has changed */
+
+extern int tracelevel, new_tracelevel;
+#define MAX_TRACELEVEL 4
+#define TRACEKERNEL (tracelevel >= 4) /* log kernel changes */
+#define TRACECONTENTS (tracelevel >= 3) /* display packet contents */
+#define TRACEPACKETS (tracelevel >= 2) /* note packets */
+#define TRACEACTIONS (tracelevel != 0)
+extern FILE *ftrace; /* output trace file */
+extern char inittracename[PATH_MAX];
+
+extern struct radix_node_head *rhead;
+
+
+#ifdef sgi
+/* Fix conflicts */
+#define dup2(x,y) BSDdup2(x,y)
+#endif /* sgi */
+
+void fix_sock(int, const char *);
+void fix_select(void);
+void rip_off(void);
+void rip_on(struct interface *);
+
+void bufinit(void);
+int output(enum output_type, struct sockaddr_in *,
+ struct interface *, struct rip *, int);
+void clr_ws_buf(struct ws_buf *, struct auth *);
+void rip_query(void);
+void rip_bcast(int);
+void supply(struct sockaddr_in *, struct interface *,
+ enum output_type, int, int, int);
+
+void msglog(const char *, ...) PATTRIB(1,2);
+struct msg_limit {
+ time_t reuse;
+ struct msg_sub {
+ naddr addr;
+ time_t until;
+# define MSG_SUBJECT_N 8
+ } subs[MSG_SUBJECT_N];
+};
+void msglim(struct msg_limit *, naddr,
+ const char *, ...) PATTRIB(3,4);
+#define LOGERR(msg) msglog(msg ": %s", strerror(errno))
+void logbad(int, const char *, ...) PATTRIB(2,3);
+#define BADERR(dump,msg) logbad(dump,msg ": %s", strerror(errno))
+#ifdef DEBUG
+#define DBGERR(dump,msg) BADERR(dump,msg)
+#else
+#define DBGERR(dump,msg) LOGERR(msg)
+#endif
+char *naddr_ntoa(naddr);
+const char *saddr_ntoa(struct sockaddr *);
+
+void *rtmalloc(size_t, const char *);
+void timevaladd(struct timeval *, struct timeval *);
+void intvl_random(struct timeval *, u_long, u_long);
+int getnet(char *, naddr *, naddr *);
+int gethost(char *, naddr *);
+void gwkludge(void);
+const char *parse_parms(char *, int);
+const char *check_parms(struct parm *);
+void get_parms(struct interface *);
+
+void lastlog(void);
+void trace_close(int);
+void set_tracefile(const char *, const char *, int);
+void tracelevel_msg(const char *, int);
+void trace_off(const char*, ...) PATTRIB(1,2);
+void set_tracelevel(void);
+void trace_flush(void);
+void trace_misc(const char *, ...) PATTRIB(1,2);
+void trace_act(const char *, ...) PATTRIB(1,2);
+void trace_pkt(const char *, ...) PATTRIB(1,2);
+void trace_add_del(const char *, struct rt_entry *);
+void trace_change(struct rt_entry *, u_int, struct rt_spare *,
+ const char *);
+void trace_if(const char *, struct interface *);
+void trace_upslot(struct rt_entry *, struct rt_spare *,
+ struct rt_spare *);
+void trace_rip(const char*, const char*, struct sockaddr_in *,
+ struct interface *, struct rip *, int);
+char *addrname(naddr, naddr, int);
+char *rtname(naddr, naddr, naddr);
+
+void rdisc_age(naddr);
+void set_rdisc_mg(struct interface *, int);
+void set_supplier(void);
+void if_bad_rdisc(struct interface *);
+void if_ok_rdisc(struct interface *);
+void read_rip(int, struct interface *);
+void read_rt(void);
+void read_d(void);
+void rdisc_adv(void);
+void rdisc_sol(void);
+
+void sigtrace_on(int);
+void sigtrace_off(int);
+
+void flush_kern(void);
+void age(naddr);
+
+void ag_flush(naddr, naddr, void (*)(struct ag_info *));
+void ag_check(naddr, naddr, naddr, naddr, char, char, u_int,
+ u_short, u_short, void (*)(struct ag_info *));
+void del_static(naddr, naddr, naddr, int);
+void del_redirects(naddr, time_t);
+struct rt_entry *rtget(naddr, naddr);
+struct rt_entry *rtfind(naddr);
+void rtinit(void);
+void rtadd(naddr, naddr, u_int, struct rt_spare *);
+void rtchange(struct rt_entry *, u_int, struct rt_spare *, char *);
+void rtdelete(struct rt_entry *);
+void rts_delete(struct rt_entry *, struct rt_spare *);
+void rtbad_sub(struct rt_entry *);
+void rtswitch(struct rt_entry *, struct rt_spare *);
+
+#define S_ADDR(x) (((struct sockaddr_in *)(x))->sin_addr.s_addr)
+#define INFO_DST(I) ((I)->rti_info[RTAX_DST])
+#define INFO_GATE(I) ((I)->rti_info[RTAX_GATEWAY])
+#define INFO_MASK(I) ((I)->rti_info[RTAX_NETMASK])
+#define INFO_IFA(I) ((I)->rti_info[RTAX_IFA])
+#define INFO_AUTHOR(I) ((I)->rti_info[RTAX_AUTHOR])
+#define INFO_BRD(I) ((I)->rti_info[RTAX_BRD])
+void rt_xaddrs(struct rt_addrinfo *, struct sockaddr *, struct sockaddr *,
+ int);
+
+naddr std_mask(naddr);
+naddr ripv1_mask_net(naddr, struct interface *);
+naddr ripv1_mask_host(naddr,struct interface *);
+#define on_net(a,net,mask) (((ntohl(a) ^ (net)) & (mask)) == 0)
+int check_dst(naddr);
+struct interface *check_dup(naddr, naddr, naddr, int);
+int check_remote(struct interface *);
+void ifinit(void);
+int walk_bad(struct radix_node *, struct walkarg *);
+int if_ok(struct interface *, const char *);
+void if_sick(struct interface *);
+void if_link(struct interface *);
+struct interface *ifwithaddr(naddr addr, int bcast, int remote);
+struct interface *ifwithindex(u_short, int);
+struct interface *iflookup(naddr);
+
+struct auth *find_auth(struct interface *);
+void end_md5_auth(struct ws_buf *, struct auth *);
+
+#if defined(__FreeBSD__) || defined(__NetBSD__)
+#include <md5.h>
+#else
+#define MD5_DIGEST_LEN 16
+typedef struct {
+ u_int32_t state[4]; /* state (ABCD) */
+ u_int32_t count[2]; /* # of bits, modulo 2^64 (LSB 1st) */
+ unsigned char buffer[64]; /* input buffer */
+} MD5_CTX;
+void MD5Init(MD5_CTX*);
+void MD5Update(MD5_CTX*, u_char*, u_int);
+void MD5Final(u_char[MD5_DIGEST_LEN], MD5_CTX*);
+#endif
diff --git a/sbin/routed/if.c b/sbin/routed/if.c
new file mode 100644
index 0000000..9160e6f
--- /dev/null
+++ b/sbin/routed/if.c
@@ -0,0 +1,1383 @@
+/*
+ * Copyright (c) 1983, 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.
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+#include <stdint.h>
+
+#include "defs.h"
+#include "pathnames.h"
+
+#ifdef __NetBSD__
+__RCSID("$NetBSD$");
+#elif defined(__FreeBSD__)
+__RCSID("$FreeBSD$");
+#else
+__RCSID("$Revision: 2.27 $");
+#ident "$Revision: 2.27 $"
+#endif
+
+struct ifhead ifnet = LIST_HEAD_INITIALIZER(ifnet); /* all interfaces */
+struct ifhead remote_if = LIST_HEAD_INITIALIZER(remote_if); /* remote interfaces */
+
+/* hash table for all interfaces, big enough to tolerate ridiculous
+ * numbers of IP aliases. Crazy numbers of aliases such as 7000
+ * still will not do well, but not just in looking up interfaces
+ * by name or address.
+ */
+#define AHASH_LEN 211 /* must be prime */
+#define AHASH(a) &ahash_tbl[(a)%AHASH_LEN]
+static struct interface *ahash_tbl[AHASH_LEN];
+
+#define BHASH_LEN 211 /* must be prime */
+#define BHASH(a) &bhash_tbl[(a)%BHASH_LEN]
+static struct interface *bhash_tbl[BHASH_LEN];
+
+
+/* hash for physical interface names.
+ * Assume there are never more 100 or 200 real interfaces, and that
+ * aliases are put on the end of the hash chains.
+ */
+#define NHASH_LEN 97
+static struct interface *nhash_tbl[NHASH_LEN];
+
+int tot_interfaces; /* # of remote and local interfaces */
+int rip_interfaces; /* # of interfaces doing RIP */
+static int foundloopback; /* valid flag for loopaddr */
+naddr loopaddr; /* our address on loopback */
+static struct rt_spare loop_rts;
+
+struct timeval ifinit_timer;
+static struct timeval last_ifinit;
+#define IF_RESCAN_DELAY() (last_ifinit.tv_sec == now.tv_sec \
+ && last_ifinit.tv_usec == now.tv_usec \
+ && timercmp(&ifinit_timer, &now, >))
+
+int have_ripv1_out; /* have a RIPv1 interface */
+static int have_ripv1_in;
+
+
+static void if_bad(struct interface *);
+static int addrouteforif(struct interface *);
+
+static struct interface**
+nhash(char *p)
+{
+ u_int i;
+
+ for (i = 0; *p != '\0'; p++) {
+ i = ((i<<1) & 0x7fffffff) | ((i>>31) & 1);
+ i ^= *p;
+ }
+ return &nhash_tbl[i % NHASH_LEN];
+}
+
+
+/* Link a new interface into the lists and hash tables.
+ */
+void
+if_link(struct interface *ifp)
+{
+ struct interface **hifp;
+
+ LIST_INSERT_HEAD(&ifnet, ifp, int_list);
+
+ hifp = AHASH(ifp->int_addr);
+ ifp->int_ahash_prev = hifp;
+ if ((ifp->int_ahash = *hifp) != 0)
+ (*hifp)->int_ahash_prev = &ifp->int_ahash;
+ *hifp = ifp;
+
+ if (ifp->int_if_flags & IFF_BROADCAST) {
+ hifp = BHASH(ifp->int_brdaddr);
+ ifp->int_bhash_prev = hifp;
+ if ((ifp->int_bhash = *hifp) != 0)
+ (*hifp)->int_bhash_prev = &ifp->int_bhash;
+ *hifp = ifp;
+ }
+
+ if (ifp->int_state & IS_REMOTE)
+ LIST_INSERT_HEAD(&remote_if, ifp, remote_list);
+
+ hifp = nhash(ifp->int_name);
+ if (ifp->int_state & IS_ALIAS) {
+ /* put aliases on the end of the hash chain */
+ while (*hifp != 0)
+ hifp = &(*hifp)->int_nhash;
+ }
+ ifp->int_nhash_prev = hifp;
+ if ((ifp->int_nhash = *hifp) != 0)
+ (*hifp)->int_nhash_prev = &ifp->int_nhash;
+ *hifp = ifp;
+}
+
+
+/* Find the interface with an address
+ */
+struct interface *
+ifwithaddr(naddr addr,
+ int bcast, /* notice IFF_BROADCAST address */
+ int remote) /* include IS_REMOTE interfaces */
+{
+ struct interface *ifp, *possible = 0;
+
+ remote = (remote == 0) ? IS_REMOTE : 0;
+
+ for (ifp = *AHASH(addr); ifp; ifp = ifp->int_ahash) {
+ if (ifp->int_addr != addr)
+ continue;
+ if ((ifp->int_state & remote) != 0)
+ continue;
+ if ((ifp->int_state & (IS_BROKE | IS_PASSIVE)) == 0)
+ return ifp;
+ possible = ifp;
+ }
+
+ if (possible || !bcast)
+ return possible;
+
+ for (ifp = *BHASH(addr); ifp; ifp = ifp->int_bhash) {
+ if (ifp->int_brdaddr != addr)
+ continue;
+ if ((ifp->int_state & remote) != 0)
+ continue;
+ if ((ifp->int_state & (IS_BROKE | IS_PASSIVE)) == 0)
+ return ifp;
+ possible = ifp;
+ }
+
+ return possible;
+}
+
+
+/* find the interface with a name
+ */
+static struct interface *
+ifwithname(char *name, /* "ec0" or whatever */
+ naddr addr) /* 0 or network address */
+{
+ struct interface *ifp;
+
+ for (;;) {
+ for (ifp = *nhash(name); ifp != 0; ifp = ifp->int_nhash) {
+ /* If the network address is not specified,
+ * ignore any alias interfaces. Otherwise, look
+ * for the interface with the target name and address.
+ */
+ if (!strcmp(ifp->int_name, name)
+ && ((addr == 0 && !(ifp->int_state & IS_ALIAS))
+ || (ifp->int_addr == addr)))
+ return ifp;
+ }
+
+ /* If there is no known interface, maybe there is a
+ * new interface. So just once look for new interfaces.
+ */
+ if (IF_RESCAN_DELAY())
+ return 0;
+ ifinit();
+ }
+}
+
+
+struct interface *
+ifwithindex(u_short ifindex,
+ int rescan_ok)
+{
+ struct interface *ifp;
+
+ for (;;) {
+ LIST_FOREACH(ifp, &ifnet, int_list) {
+ if (ifp->int_index == ifindex)
+ return ifp;
+ }
+
+ /* If there is no known interface, maybe there is a
+ * new interface. So just once look for new interfaces.
+ */
+ if (!rescan_ok
+ || IF_RESCAN_DELAY())
+ return 0;
+ ifinit();
+ }
+}
+
+
+/* Find an interface from which the specified address
+ * should have come from. Used for figuring out which
+ * interface a packet came in on.
+ */
+struct interface *
+iflookup(naddr addr)
+{
+ struct interface *ifp, *maybe;
+ int once = 0;
+
+ maybe = 0;
+ for (;;) {
+ LIST_FOREACH(ifp, &ifnet, int_list) {
+ if (ifp->int_if_flags & IFF_POINTOPOINT) {
+ /* finished with a match */
+ if (ifp->int_dstaddr == addr)
+ return ifp;
+
+ } else {
+ /* finished with an exact match */
+ if (ifp->int_addr == addr)
+ return ifp;
+
+ /* Look for the longest approximate match.
+ */
+ if (on_net(addr, ifp->int_net, ifp->int_mask)
+ && (maybe == 0
+ || ifp->int_mask > maybe->int_mask))
+ maybe = ifp;
+ }
+ }
+
+ if (maybe != 0 || once || IF_RESCAN_DELAY())
+ return maybe;
+ once = 1;
+
+ /* If there is no known interface, maybe there is a
+ * new interface. So just once look for new interfaces.
+ */
+ ifinit();
+ }
+}
+
+
+/* Return the classical netmask for an IP address.
+ */
+naddr /* host byte order */
+std_mask(naddr addr) /* network byte order */
+{
+ addr = ntohl(addr); /* was a host, not a network */
+
+ if (addr == 0) /* default route has mask 0 */
+ return 0;
+ if (IN_CLASSA(addr))
+ return IN_CLASSA_NET;
+ if (IN_CLASSB(addr))
+ return IN_CLASSB_NET;
+ return IN_CLASSC_NET;
+}
+
+
+/* Find the netmask that would be inferred by RIPv1 listeners
+ * on the given interface for a given network.
+ * If no interface is specified, look for the best fitting interface.
+ */
+naddr
+ripv1_mask_net(naddr addr, /* in network byte order */
+ struct interface *ifp) /* as seen on this interface */
+{
+ struct r1net *r1p;
+ naddr mask = 0;
+
+ if (addr == 0) /* default always has 0 mask */
+ return mask;
+
+ if (ifp != 0 && ifp->int_ripv1_mask != HOST_MASK) {
+ /* If the target network is that of the associated interface
+ * on which it arrived, then use the netmask of the interface.
+ */
+ if (on_net(addr, ifp->int_net, ifp->int_std_mask))
+ mask = ifp->int_ripv1_mask;
+
+ } else {
+ /* Examine all interfaces, and if it the target seems
+ * to have the same network number of an interface, use the
+ * netmask of that interface. If there is more than one
+ * such interface, prefer the interface with the longest
+ * match.
+ */
+ LIST_FOREACH(ifp, &ifnet, int_list) {
+ if (on_net(addr, ifp->int_std_net, ifp->int_std_mask)
+ && ifp->int_ripv1_mask > mask
+ && ifp->int_ripv1_mask != HOST_MASK)
+ mask = ifp->int_ripv1_mask;
+ }
+
+ }
+
+ /* check special definitions */
+ if (mask == 0) {
+ for (r1p = r1nets; r1p != 0; r1p = r1p->r1net_next) {
+ if (on_net(addr, r1p->r1net_net, r1p->r1net_match)
+ && r1p->r1net_mask > mask)
+ mask = r1p->r1net_mask;
+ }
+
+ /* Otherwise, make the classic A/B/C guess.
+ */
+ if (mask == 0)
+ mask = std_mask(addr);
+ }
+
+ return mask;
+}
+
+
+naddr
+ripv1_mask_host(naddr addr, /* in network byte order */
+ struct interface *ifp) /* as seen on this interface */
+{
+ naddr mask = ripv1_mask_net(addr, ifp);
+
+
+ /* If the computed netmask does not mask the address,
+ * then assume it is a host address
+ */
+ if ((ntohl(addr) & ~mask) != 0)
+ mask = HOST_MASK;
+ return mask;
+}
+
+
+/* See if an IP address looks reasonable as a destination.
+ */
+int /* 0=bad */
+check_dst(naddr addr)
+{
+ addr = ntohl(addr);
+
+ if (IN_CLASSA(addr)) {
+ if (addr == 0)
+ return 1; /* default */
+
+ addr >>= IN_CLASSA_NSHIFT;
+ return (addr != 0 && addr != IN_LOOPBACKNET);
+ }
+
+ return (IN_CLASSB(addr) || IN_CLASSC(addr));
+}
+
+
+/* See a new interface duplicates an existing interface.
+ */
+struct interface *
+check_dup(naddr addr, /* IP address, so network byte order */
+ naddr dstaddr, /* ditto */
+ naddr mask, /* mask, so host byte order */
+ int if_flags)
+{
+ struct interface *ifp;
+
+ LIST_FOREACH(ifp, &ifnet, int_list) {
+ if (ifp->int_mask != mask)
+ continue;
+
+ if (!iff_up(ifp->int_if_flags))
+ continue;
+
+ /* The local address can only be shared with a point-to-point
+ * link.
+ */
+ if ((!(ifp->int_state & IS_REMOTE) || !(if_flags & IS_REMOTE))
+ && ifp->int_addr == addr
+ && (((if_flags|ifp->int_if_flags) & IFF_POINTOPOINT) == 0))
+ return ifp;
+
+ if (on_net(ifp->int_dstaddr, ntohl(dstaddr),mask))
+ return ifp;
+ }
+ return 0;
+}
+
+
+/* See that a remote gateway is reachable.
+ * Note that the answer can change as real interfaces come and go.
+ */
+int /* 0=bad */
+check_remote(struct interface *ifp)
+{
+ struct rt_entry *rt;
+
+ /* do not worry about other kinds */
+ if (!(ifp->int_state & IS_REMOTE))
+ return 1;
+
+ rt = rtfind(ifp->int_addr);
+ if (rt != 0
+ && rt->rt_ifp != 0
+ &&on_net(ifp->int_addr,
+ rt->rt_ifp->int_net, rt->rt_ifp->int_mask))
+ return 1;
+
+ /* the gateway cannot be reached directly from one of our
+ * interfaces
+ */
+ if (!(ifp->int_state & IS_BROKE)) {
+ msglog("unreachable gateway %s in "_PATH_GATEWAYS,
+ naddr_ntoa(ifp->int_addr));
+ if_bad(ifp);
+ }
+ return 0;
+}
+
+
+/* Delete an interface.
+ */
+static void
+ifdel(struct interface *ifp)
+{
+ struct interface *ifp1;
+
+
+ trace_if("Del", ifp);
+
+ ifp->int_state |= IS_BROKE;
+
+ LIST_REMOVE(ifp, int_list);
+ *ifp->int_ahash_prev = ifp->int_ahash;
+ if (ifp->int_ahash != 0)
+ ifp->int_ahash->int_ahash_prev = ifp->int_ahash_prev;
+ *ifp->int_nhash_prev = ifp->int_nhash;
+ if (ifp->int_nhash != 0)
+ ifp->int_nhash->int_nhash_prev = ifp->int_nhash_prev;
+ if (ifp->int_if_flags & IFF_BROADCAST) {
+ *ifp->int_bhash_prev = ifp->int_bhash;
+ if (ifp->int_bhash != 0)
+ ifp->int_bhash->int_bhash_prev = ifp->int_bhash_prev;
+ }
+ if (ifp->int_state & IS_REMOTE)
+ LIST_REMOVE(ifp, remote_list);
+
+ if (!(ifp->int_state & IS_ALIAS)) {
+ /* delete aliases when the main interface dies
+ */
+ LIST_FOREACH(ifp1, &ifnet, int_list) {
+ if (ifp1 != ifp
+ && !strcmp(ifp->int_name, ifp1->int_name))
+ ifdel(ifp1);
+ }
+
+ if ((ifp->int_if_flags & IFF_MULTICAST) && rip_sock >= 0) {
+ struct group_req gr;
+ struct sockaddr_in *sin;
+
+ memset(&gr, 0, sizeof(gr));
+ gr.gr_interface = ifp->int_index;
+ sin = (struct sockaddr_in *)&gr.gr_group;
+ sin->sin_family = AF_INET;
+#ifdef _HAVE_SIN_LEN
+ sin->sin_len = sizeof(struct sockaddr_in);
+#endif
+ sin->sin_addr.s_addr = htonl(INADDR_RIP_GROUP);
+ if (setsockopt(rip_sock, IPPROTO_IP, MCAST_LEAVE_GROUP,
+ &gr, sizeof(gr)) < 0
+ && errno != EADDRNOTAVAIL
+ && !TRACEACTIONS)
+ LOGERR("setsockopt(MCAST_LEAVE_GROUP RIP)");
+ if (rip_sock_mcast == ifp)
+ rip_sock_mcast = 0;
+ }
+ if (ifp->int_rip_sock >= 0) {
+ (void)close(ifp->int_rip_sock);
+ ifp->int_rip_sock = -1;
+ fix_select();
+ }
+
+ tot_interfaces--;
+ if (!IS_RIP_OFF(ifp->int_state))
+ rip_interfaces--;
+
+ /* Zap all routes associated with this interface.
+ * Assume routes just using gateways beyond this interface
+ * will timeout naturally, and have probably already died.
+ */
+ (void)rn_walktree(rhead, walk_bad, 0);
+
+ set_rdisc_mg(ifp, 0);
+ if_bad_rdisc(ifp);
+ }
+
+ free(ifp);
+}
+
+
+/* Mark an interface ill.
+ */
+void
+if_sick(struct interface *ifp)
+{
+ if (0 == (ifp->int_state & (IS_SICK | IS_BROKE))) {
+ ifp->int_state |= IS_SICK;
+ ifp->int_act_time = NEVER;
+ trace_if("Chg", ifp);
+
+ LIM_SEC(ifinit_timer, now.tv_sec+CHECK_BAD_INTERVAL);
+ }
+}
+
+
+/* Mark an interface dead.
+ */
+static void
+if_bad(struct interface *ifp)
+{
+ struct interface *ifp1;
+
+
+ if (ifp->int_state & IS_BROKE)
+ return;
+
+ LIM_SEC(ifinit_timer, now.tv_sec+CHECK_BAD_INTERVAL);
+
+ ifp->int_state |= (IS_BROKE | IS_SICK);
+ ifp->int_act_time = NEVER;
+ ifp->int_query_time = NEVER;
+ ifp->int_data.ts = now.tv_sec;
+
+ trace_if("Chg", ifp);
+
+ if (!(ifp->int_state & IS_ALIAS)) {
+ LIST_FOREACH(ifp1, &ifnet, int_list) {
+ if (ifp1 != ifp
+ && !strcmp(ifp->int_name, ifp1->int_name))
+ if_bad(ifp1);
+ }
+ (void)rn_walktree(rhead, walk_bad, 0);
+ if_bad_rdisc(ifp);
+ }
+}
+
+
+/* Mark an interface alive
+ */
+int /* 1=it was dead */
+if_ok(struct interface *ifp,
+ const char *type)
+{
+ struct interface *ifp1;
+
+
+ if (!(ifp->int_state & IS_BROKE)) {
+ if (ifp->int_state & IS_SICK) {
+ trace_act("%sinterface %s to %s working better",
+ type,
+ ifp->int_name, naddr_ntoa(ifp->int_dstaddr));
+ ifp->int_state &= ~IS_SICK;
+ }
+ return 0;
+ }
+
+ msglog("%sinterface %s to %s restored",
+ type, ifp->int_name, naddr_ntoa(ifp->int_dstaddr));
+ ifp->int_state &= ~(IS_BROKE | IS_SICK);
+ ifp->int_data.ts = 0;
+
+ if (!(ifp->int_state & IS_ALIAS)) {
+ LIST_FOREACH(ifp1, &ifnet, int_list) {
+ if (ifp1 != ifp
+ && !strcmp(ifp->int_name, ifp1->int_name))
+ if_ok(ifp1, type);
+ }
+ if_ok_rdisc(ifp);
+ }
+
+ if (ifp->int_state & IS_REMOTE) {
+ if (!addrouteforif(ifp))
+ return 0;
+ }
+ return 1;
+}
+
+
+/* disassemble routing message
+ */
+void
+rt_xaddrs(struct rt_addrinfo *info,
+ struct sockaddr *sa,
+ struct sockaddr *lim,
+ int addrs)
+{
+ int i;
+#ifdef _HAVE_SA_LEN
+ static struct sockaddr sa_zero;
+#endif
+
+ memset(info, 0, sizeof(*info));
+ info->rti_addrs = addrs;
+ for (i = 0; i < RTAX_MAX && sa < lim; i++) {
+ if ((addrs & (1 << i)) == 0)
+ continue;
+ info->rti_info[i] = (sa->sa_len != 0) ? sa : &sa_zero;
+ sa = (struct sockaddr *)((char*)(sa) + SA_SIZE(sa));
+ }
+}
+
+
+/* Find the network interfaces which have configured themselves.
+ * This must be done regularly, if only for extra addresses
+ * that come and go on interfaces.
+ */
+void
+ifinit(void)
+{
+ static struct ifa_msghdr *sysctl_buf;
+ static size_t sysctl_buf_size = 0;
+ uint complaints = 0;
+ static u_int prev_complaints = 0;
+# define COMP_NOT_INET 0x001
+# define COMP_NOADDR 0x002
+# define COMP_BADADDR 0x004
+# define COMP_NODST 0x008
+# define COMP_NOBADR 0x010
+# define COMP_NOMASK 0x020
+# define COMP_DUP 0x040
+# define COMP_BAD_METRIC 0x080
+# define COMP_NETMASK 0x100
+
+ struct interface ifs, ifs0, *ifp, *ifp1;
+ struct rt_entry *rt;
+ size_t needed;
+ int mib[6];
+ struct if_msghdr *ifm;
+ void *ifam_lim;
+ struct ifa_msghdr *ifam, *ifam2;
+ int in, ierr, out, oerr;
+ struct intnet *intnetp;
+ struct rt_addrinfo info;
+#ifdef SIOCGIFMETRIC
+ struct ifreq ifr;
+#endif
+
+
+ last_ifinit = now;
+ ifinit_timer.tv_sec = now.tv_sec + (supplier
+ ? CHECK_ACT_INTERVAL
+ : CHECK_QUIET_INTERVAL);
+
+ /* mark all interfaces so we can get rid of those that disappear */
+ LIST_FOREACH(ifp, &ifnet, int_list)
+ ifp->int_state &= ~(IS_CHECKED | IS_DUP);
+
+ /* Fetch the interface list, without too many system calls
+ * since we do it repeatedly.
+ */
+ mib[0] = CTL_NET;
+ mib[1] = PF_ROUTE;
+ mib[2] = 0;
+ mib[3] = AF_INET;
+ mib[4] = NET_RT_IFLIST;
+ mib[5] = 0;
+ for (;;) {
+ if ((needed = sysctl_buf_size) != 0) {
+ if (sysctl(mib, 6, sysctl_buf,&needed, 0, 0) >= 0)
+ break;
+ /* retry if the table grew */
+ if (errno != ENOMEM && errno != EFAULT)
+ BADERR(1, "ifinit: sysctl(RT_IFLIST)");
+ free(sysctl_buf);
+ needed = 0;
+ }
+ if (sysctl(mib, 6, 0, &needed, 0, 0) < 0)
+ BADERR(1,"ifinit: sysctl(RT_IFLIST) estimate");
+ sysctl_buf = rtmalloc(sysctl_buf_size = needed,
+ "ifinit sysctl");
+ }
+
+ /* XXX: thanks to malloc(3), alignment can be presumed OK */
+ ifam_lim = (char *)sysctl_buf + needed;
+ for (ifam = sysctl_buf; (void *)ifam < ifam_lim; ifam = ifam2) {
+
+ ifam2 = (struct ifa_msghdr*)((char*)ifam + ifam->ifam_msglen);
+
+#ifdef RTM_OIFINFO
+ if (ifam->ifam_type == RTM_OIFINFO)
+ continue; /* just ignore compat message */
+#endif
+ if (ifam->ifam_type == RTM_IFINFO) {
+ struct sockaddr_dl *sdl;
+
+ ifm = (struct if_msghdr *)ifam;
+ /* make prototype structure for the IP aliases
+ */
+ memset(&ifs0, 0, sizeof(ifs0));
+ ifs0.int_rip_sock = -1;
+ ifs0.int_index = ifm->ifm_index;
+ ifs0.int_if_flags = ifm->ifm_flags;
+ ifs0.int_state = IS_CHECKED;
+ ifs0.int_query_time = NEVER;
+ ifs0.int_act_time = now.tv_sec;
+ ifs0.int_data.ts = now.tv_sec;
+ ifs0.int_data.ipackets = ifm->ifm_data.ifi_ipackets;
+ ifs0.int_data.ierrors = ifm->ifm_data.ifi_ierrors;
+ ifs0.int_data.opackets = ifm->ifm_data.ifi_opackets;
+ ifs0.int_data.oerrors = ifm->ifm_data.ifi_oerrors;
+#ifdef sgi
+ ifs0.int_data.odrops = ifm->ifm_data.ifi_odrops;
+#endif
+ sdl = (struct sockaddr_dl *)(ifm + 1);
+ sdl->sdl_data[sdl->sdl_nlen] = 0;
+ strncpy(ifs0.int_name, sdl->sdl_data,
+ MIN(sizeof(ifs0.int_name), sdl->sdl_nlen));
+ continue;
+ }
+ if (ifam->ifam_type != RTM_NEWADDR) {
+ logbad(1,"ifinit: out of sync");
+ continue;
+ }
+ rt_xaddrs(&info, (struct sockaddr *)(ifam+1),
+ (struct sockaddr *)ifam2,
+ ifam->ifam_addrs);
+
+ /* Prepare for the next address of this interface, which
+ * will be an alias.
+ * Do not output RIP or Router-Discovery packets via aliases.
+ */
+ memcpy(&ifs, &ifs0, sizeof(ifs));
+ ifs0.int_state |= (IS_ALIAS | IS_NO_RIP_OUT | IS_NO_RDISC);
+
+ if (INFO_IFA(&info) == 0) {
+ if (iff_up(ifs.int_if_flags)) {
+ if (!(prev_complaints & COMP_NOADDR))
+ msglog("%s has no address",
+ ifs.int_name);
+ complaints |= COMP_NOADDR;
+ }
+ continue;
+ }
+ if (INFO_IFA(&info)->sa_family != AF_INET) {
+ if (iff_up(ifs.int_if_flags)) {
+ if (!(prev_complaints & COMP_NOT_INET))
+ trace_act("%s: not AF_INET",
+ ifs.int_name);
+ complaints |= COMP_NOT_INET;
+ }
+ continue;
+ }
+
+ ifs.int_addr = S_ADDR(INFO_IFA(&info));
+
+ if (ntohl(ifs.int_addr)>>24 == 0
+ || ntohl(ifs.int_addr)>>24 == 0xff) {
+ if (iff_up(ifs.int_if_flags)) {
+ if (!(prev_complaints & COMP_BADADDR))
+ msglog("%s has a bad address",
+ ifs.int_name);
+ complaints |= COMP_BADADDR;
+ }
+ continue;
+ }
+
+ if (ifs.int_if_flags & IFF_LOOPBACK) {
+ ifs.int_state |= IS_NO_RIP | IS_NO_RDISC;
+ if (ifs.int_addr == htonl(INADDR_LOOPBACK))
+ ifs.int_state |= IS_PASSIVE;
+ ifs.int_dstaddr = ifs.int_addr;
+ ifs.int_mask = HOST_MASK;
+ ifs.int_ripv1_mask = HOST_MASK;
+ ifs.int_std_mask = std_mask(ifs.int_dstaddr);
+ ifs.int_net = ntohl(ifs.int_dstaddr);
+ if (!foundloopback) {
+ foundloopback = 1;
+ loopaddr = ifs.int_addr;
+ loop_rts.rts_gate = loopaddr;
+ loop_rts.rts_router = loopaddr;
+ }
+
+ } else if (ifs.int_if_flags & IFF_POINTOPOINT) {
+ if (INFO_BRD(&info) == 0
+ || INFO_BRD(&info)->sa_family != AF_INET) {
+ if (iff_up(ifs.int_if_flags)) {
+ if (!(prev_complaints & COMP_NODST))
+ msglog("%s has a bad"
+ " destination address",
+ ifs.int_name);
+ complaints |= COMP_NODST;
+ }
+ continue;
+ }
+ ifs.int_dstaddr = S_ADDR(INFO_BRD(&info));
+ if (ntohl(ifs.int_dstaddr)>>24 == 0
+ || ntohl(ifs.int_dstaddr)>>24 == 0xff) {
+ if (iff_up(ifs.int_if_flags)) {
+ if (!(prev_complaints & COMP_NODST))
+ msglog("%s has a bad"
+ " destination address",
+ ifs.int_name);
+ complaints |= COMP_NODST;
+ }
+ continue;
+ }
+ ifs.int_mask = HOST_MASK;
+ ifs.int_ripv1_mask = ntohl(S_ADDR(INFO_MASK(&info)));
+ ifs.int_std_mask = std_mask(ifs.int_dstaddr);
+ ifs.int_net = ntohl(ifs.int_dstaddr);
+
+ } else {
+ if (INFO_MASK(&info) == 0) {
+ if (iff_up(ifs.int_if_flags)) {
+ if (!(prev_complaints & COMP_NOMASK))
+ msglog("%s has no netmask",
+ ifs.int_name);
+ complaints |= COMP_NOMASK;
+ }
+ continue;
+ }
+ ifs.int_dstaddr = ifs.int_addr;
+ ifs.int_mask = ntohl(S_ADDR(INFO_MASK(&info)));
+ ifs.int_ripv1_mask = ifs.int_mask;
+ ifs.int_std_mask = std_mask(ifs.int_addr);
+ ifs.int_net = ntohl(ifs.int_addr) & ifs.int_mask;
+ if (ifs.int_mask != ifs.int_std_mask)
+ ifs.int_state |= IS_SUBNET;
+
+ if (ifs.int_if_flags & IFF_BROADCAST) {
+ if (INFO_BRD(&info) == 0) {
+ if (iff_up(ifs.int_if_flags)) {
+ if (!(prev_complaints
+ & COMP_NOBADR))
+ msglog("%s has"
+ "no broadcast address",
+ ifs.int_name);
+ complaints |= COMP_NOBADR;
+ }
+ continue;
+ }
+ ifs.int_brdaddr = S_ADDR(INFO_BRD(&info));
+ }
+ }
+ ifs.int_std_net = ifs.int_net & ifs.int_std_mask;
+ ifs.int_std_addr = htonl(ifs.int_std_net);
+
+ /* Use a minimum metric of one. Treat the interface metric
+ * (default 0) as an increment to the hop count of one.
+ *
+ * The metric obtained from the routing socket dump of
+ * interface addresses is wrong. It is not set by the
+ * SIOCSIFMETRIC ioctl.
+ */
+#ifdef SIOCGIFMETRIC
+ strncpy(ifr.ifr_name, ifs.int_name, sizeof(ifr.ifr_name));
+ if (ioctl(rt_sock, SIOCGIFMETRIC, &ifr) < 0) {
+ DBGERR(1, "ioctl(SIOCGIFMETRIC)");
+ ifs.int_metric = 0;
+ } else {
+ ifs.int_metric = ifr.ifr_metric;
+ }
+#else
+ ifs.int_metric = ifam->ifam_metric;
+#endif
+ if (ifs.int_metric > HOPCNT_INFINITY) {
+ ifs.int_metric = 0;
+ if (!(prev_complaints & COMP_BAD_METRIC)
+ && iff_up(ifs.int_if_flags)) {
+ complaints |= COMP_BAD_METRIC;
+ msglog("%s has a metric of %d",
+ ifs.int_name, ifs.int_metric);
+ }
+ }
+
+ /* See if this is a familiar interface.
+ * If so, stop worrying about it if it is the same.
+ * Start it over if it now is to somewhere else, as happens
+ * frequently with PPP and SLIP.
+ */
+ ifp = ifwithname(ifs.int_name, ((ifs.int_state & IS_ALIAS)
+ ? ifs.int_addr
+ : 0));
+ if (ifp != 0) {
+ ifp->int_state |= IS_CHECKED;
+
+ if (0 != ((ifp->int_if_flags ^ ifs.int_if_flags)
+ & (IFF_BROADCAST
+ | IFF_LOOPBACK
+ | IFF_POINTOPOINT
+ | IFF_MULTICAST))
+ || 0 != ((ifp->int_state ^ ifs.int_state)
+ & IS_ALIAS)
+ || ifp->int_addr != ifs.int_addr
+ || ifp->int_brdaddr != ifs.int_brdaddr
+ || ifp->int_dstaddr != ifs.int_dstaddr
+ || ifp->int_mask != ifs.int_mask
+ || ifp->int_metric != ifs.int_metric) {
+ /* Forget old information about
+ * a changed interface.
+ */
+ trace_act("interface %s has changed",
+ ifp->int_name);
+ ifdel(ifp);
+ ifp = 0;
+ }
+ }
+
+ if (ifp != 0) {
+ /* The primary representative of an alias worries
+ * about how things are working.
+ */
+ if (ifp->int_state & IS_ALIAS)
+ continue;
+
+ /* note interfaces that have been turned off
+ */
+ if (!iff_up(ifs.int_if_flags)) {
+ if (iff_up(ifp->int_if_flags)) {
+ msglog("interface %s to %s turned off",
+ ifp->int_name,
+ naddr_ntoa(ifp->int_dstaddr));
+ if_bad(ifp);
+ ifp->int_if_flags &= ~IFF_UP;
+ } else if (now.tv_sec>(ifp->int_data.ts
+ + CHECK_BAD_INTERVAL)) {
+ trace_act("interface %s has been off"
+ " %jd seconds; forget it",
+ ifp->int_name,
+ (intmax_t)now.tv_sec -
+ ifp->int_data.ts);
+ ifdel(ifp);
+ }
+ continue;
+ }
+ /* or that were off and are now ok */
+ if (!iff_up(ifp->int_if_flags)) {
+ ifp->int_if_flags |= IFF_UP;
+ (void)if_ok(ifp, "");
+ }
+
+ /* If it has been long enough,
+ * see if the interface is broken.
+ */
+ if (now.tv_sec < ifp->int_data.ts+CHECK_BAD_INTERVAL)
+ continue;
+
+ in = ifs.int_data.ipackets - ifp->int_data.ipackets;
+ ierr = ifs.int_data.ierrors - ifp->int_data.ierrors;
+ out = ifs.int_data.opackets - ifp->int_data.opackets;
+ oerr = ifs.int_data.oerrors - ifp->int_data.oerrors;
+#ifdef sgi
+ /* Through at least IRIX 6.2, PPP and SLIP
+ * count packets dropped by the filters.
+ * But FDDI rings stuck non-operational count
+ * dropped packets as they wait for improvement.
+ */
+ if (!(ifp->int_if_flags & IFF_POINTOPOINT))
+ oerr += (ifs.int_data.odrops
+ - ifp->int_data.odrops);
+#endif
+ /* If the interface just awoke, restart the counters.
+ */
+ if (ifp->int_data.ts == 0) {
+ ifp->int_data = ifs.int_data;
+ continue;
+ }
+ ifp->int_data = ifs.int_data;
+
+ /* Withhold judgment when the short error
+ * counters wrap or the interface is reset.
+ */
+ if (ierr < 0 || in < 0 || oerr < 0 || out < 0) {
+ LIM_SEC(ifinit_timer,
+ now.tv_sec+CHECK_BAD_INTERVAL);
+ continue;
+ }
+
+ /* Withhold judgement when there is no traffic
+ */
+ if (in == 0 && out == 0 && ierr == 0 && oerr == 0)
+ continue;
+
+ /* It is bad if input or output is not working.
+ * Require presistent problems before marking it dead.
+ */
+ if ((in <= ierr && ierr > 0)
+ || (out <= oerr && oerr > 0)) {
+ if (!(ifp->int_state & IS_SICK)) {
+ trace_act("interface %s to %s"
+ " sick: in=%d ierr=%d"
+ " out=%d oerr=%d",
+ ifp->int_name,
+ naddr_ntoa(ifp->int_dstaddr),
+ in, ierr, out, oerr);
+ if_sick(ifp);
+ continue;
+ }
+ if (!(ifp->int_state & IS_BROKE)) {
+ msglog("interface %s to %s broken:"
+ " in=%d ierr=%d out=%d oerr=%d",
+ ifp->int_name,
+ naddr_ntoa(ifp->int_dstaddr),
+ in, ierr, out, oerr);
+ if_bad(ifp);
+ }
+ continue;
+ }
+
+ /* otherwise, it is active and healthy
+ */
+ ifp->int_act_time = now.tv_sec;
+ (void)if_ok(ifp, "");
+ continue;
+ }
+
+ /* This is a new interface.
+ * If it is dead, forget it.
+ */
+ if (!iff_up(ifs.int_if_flags))
+ continue;
+
+ /* If it duplicates an existing interface,
+ * complain about it, mark the other one
+ * duplicated, and forget this one.
+ */
+ ifp = check_dup(ifs.int_addr,ifs.int_dstaddr,ifs.int_mask,
+ ifs.int_if_flags);
+ if (ifp != 0) {
+ /* Ignore duplicates of itself, caused by having
+ * IP aliases on the same network.
+ */
+ if (!strcmp(ifp->int_name, ifs.int_name))
+ continue;
+
+ if (!(prev_complaints & COMP_DUP)) {
+ complaints |= COMP_DUP;
+ msglog("%s (%s%s%s) is duplicated by"
+ " %s (%s%s%s)",
+ ifs.int_name,
+ addrname(ifs.int_addr,ifs.int_mask,1),
+ ((ifs.int_if_flags & IFF_POINTOPOINT)
+ ? "-->" : ""),
+ ((ifs.int_if_flags & IFF_POINTOPOINT)
+ ? naddr_ntoa(ifs.int_dstaddr) : ""),
+ ifp->int_name,
+ addrname(ifp->int_addr,ifp->int_mask,1),
+ ((ifp->int_if_flags & IFF_POINTOPOINT)
+ ? "-->" : ""),
+ ((ifp->int_if_flags & IFF_POINTOPOINT)
+ ? naddr_ntoa(ifp->int_dstaddr) : ""));
+ }
+ ifp->int_state |= IS_DUP;
+ continue;
+ }
+
+ if (0 == (ifs.int_if_flags & (IFF_POINTOPOINT | IFF_BROADCAST | IFF_LOOPBACK))) {
+ trace_act("%s is neither broadcast, point-to-point,"
+ " nor loopback",
+ ifs.int_name);
+ if (!(ifs.int_state & IFF_MULTICAST))
+ ifs.int_state |= IS_NO_RDISC;
+ }
+
+
+ /* It is new and ok. Add it to the list of interfaces
+ */
+ ifp = (struct interface *)rtmalloc(sizeof(*ifp), "ifinit ifp");
+ memcpy(ifp, &ifs, sizeof(*ifp));
+ get_parms(ifp);
+ if_link(ifp);
+ trace_if("Add", ifp);
+
+ /* Notice likely bad netmask.
+ */
+ if (!(prev_complaints & COMP_NETMASK)
+ && !(ifp->int_if_flags & IFF_POINTOPOINT)
+ && ifp->int_addr != RIP_DEFAULT) {
+ LIST_FOREACH(ifp1, &ifnet, int_list) {
+ if (ifp1->int_mask == ifp->int_mask)
+ continue;
+ if (ifp1->int_if_flags & IFF_POINTOPOINT)
+ continue;
+ if (ifp1->int_dstaddr == RIP_DEFAULT)
+ continue;
+ /* ignore aliases on the right network */
+ if (!strcmp(ifp->int_name, ifp1->int_name))
+ continue;
+ if (on_net(ifp->int_dstaddr,
+ ifp1->int_net, ifp1->int_mask)
+ || on_net(ifp1->int_dstaddr,
+ ifp->int_net, ifp->int_mask)) {
+ msglog("possible netmask problem"
+ " between %s:%s and %s:%s",
+ ifp->int_name,
+ addrname(htonl(ifp->int_net),
+ ifp->int_mask, 1),
+ ifp1->int_name,
+ addrname(htonl(ifp1->int_net),
+ ifp1->int_mask, 1));
+ complaints |= COMP_NETMASK;
+ }
+ }
+ }
+
+ if (!(ifp->int_state & IS_ALIAS)) {
+ /* Count the # of directly connected networks.
+ */
+ if (!(ifp->int_if_flags & IFF_LOOPBACK))
+ tot_interfaces++;
+ if (!IS_RIP_OFF(ifp->int_state))
+ rip_interfaces++;
+
+ /* turn on router discovery and RIP If needed */
+ if_ok_rdisc(ifp);
+ rip_on(ifp);
+ }
+ }
+
+ /* If we are multi-homed and have at least two interfaces
+ * listening to RIP, then output by default.
+ */
+ if (!supplier_set && rip_interfaces > 1)
+ set_supplier();
+
+ /* If we are multi-homed, optionally advertise a route to
+ * our main address.
+ */
+ if (advertise_mhome
+ || (tot_interfaces > 1
+ && mhome
+ && (ifp = ifwithaddr(myaddr, 0, 0)) != 0
+ && foundloopback)) {
+ advertise_mhome = 1;
+ rt = rtget(myaddr, HOST_MASK);
+ if (rt != 0) {
+ if (rt->rt_ifp != ifp
+ || rt->rt_router != loopaddr) {
+ rtdelete(rt);
+ rt = 0;
+ } else {
+ loop_rts.rts_ifp = ifp;
+ loop_rts.rts_metric = 0;
+ loop_rts.rts_time = rt->rt_time;
+ rtchange(rt, rt->rt_state | RS_MHOME,
+ &loop_rts, 0);
+ }
+ }
+ if (rt == 0) {
+ loop_rts.rts_ifp = ifp;
+ loop_rts.rts_metric = 0;
+ rtadd(myaddr, HOST_MASK, RS_MHOME, &loop_rts);
+ }
+ }
+
+ LIST_FOREACH_SAFE(ifp, &ifnet, int_list, ifp1) {
+ /* Forget any interfaces that have disappeared.
+ */
+ if (!(ifp->int_state & (IS_CHECKED | IS_REMOTE))) {
+ trace_act("interface %s has disappeared",
+ ifp->int_name);
+ ifdel(ifp);
+ continue;
+ }
+
+ if ((ifp->int_state & IS_BROKE)
+ && !(ifp->int_state & IS_PASSIVE))
+ LIM_SEC(ifinit_timer, now.tv_sec+CHECK_BAD_INTERVAL);
+
+ /* If we ever have a RIPv1 interface, assume we always will.
+ * It might come back if it ever goes away.
+ */
+ if (!(ifp->int_state & IS_NO_RIPV1_OUT) && supplier)
+ have_ripv1_out = 1;
+ if (!(ifp->int_state & IS_NO_RIPV1_IN))
+ have_ripv1_in = 1;
+ }
+
+ LIST_FOREACH(ifp, &ifnet, int_list) {
+ /* Ensure there is always a network route for interfaces,
+ * after any dead interfaces have been deleted, which
+ * might affect routes for point-to-point links.
+ */
+ if (!addrouteforif(ifp))
+ continue;
+
+ /* Add routes to the local end of point-to-point interfaces
+ * using loopback.
+ */
+ if ((ifp->int_if_flags & IFF_POINTOPOINT)
+ && !(ifp->int_state & IS_REMOTE)
+ && foundloopback) {
+ /* Delete any routes to the network address through
+ * foreign routers. Remove even static routes.
+ */
+ del_static(ifp->int_addr, HOST_MASK, 0, 0);
+ rt = rtget(ifp->int_addr, HOST_MASK);
+ if (rt != 0 && rt->rt_router != loopaddr) {
+ rtdelete(rt);
+ rt = 0;
+ }
+ if (rt != 0) {
+ if (!(rt->rt_state & RS_LOCAL)
+ || rt->rt_metric > ifp->int_metric) {
+ ifp1 = ifp;
+ } else {
+ ifp1 = rt->rt_ifp;
+ }
+ loop_rts.rts_ifp = ifp1;
+ loop_rts.rts_metric = 0;
+ loop_rts.rts_time = rt->rt_time;
+ rtchange(rt, ((rt->rt_state & ~RS_NET_SYN)
+ | (RS_IF|RS_LOCAL)),
+ &loop_rts, 0);
+ } else {
+ loop_rts.rts_ifp = ifp;
+ loop_rts.rts_metric = 0;
+ rtadd(ifp->int_addr, HOST_MASK,
+ (RS_IF | RS_LOCAL), &loop_rts);
+ }
+ }
+ }
+
+ /* add the authority routes */
+ for (intnetp = intnets; intnetp!=0; intnetp = intnetp->intnet_next) {
+ rt = rtget(intnetp->intnet_addr, intnetp->intnet_mask);
+ if (rt != 0
+ && !(rt->rt_state & RS_NO_NET_SYN)
+ && !(rt->rt_state & RS_NET_INT)) {
+ rtdelete(rt);
+ rt = 0;
+ }
+ if (rt == 0) {
+ loop_rts.rts_ifp = 0;
+ loop_rts.rts_metric = intnetp->intnet_metric-1;
+ rtadd(intnetp->intnet_addr, intnetp->intnet_mask,
+ RS_NET_SYN | RS_NET_INT, &loop_rts);
+ }
+ }
+
+ prev_complaints = complaints;
+}
+
+
+static void
+check_net_syn(struct interface *ifp)
+{
+ struct rt_entry *rt;
+ static struct rt_spare new;
+
+
+ /* Turn on the need to automatically synthesize a network route
+ * for this interface only if we are running RIPv1 on some other
+ * interface that is on a different class-A,B,or C network.
+ */
+ if (have_ripv1_out || have_ripv1_in) {
+ ifp->int_state |= IS_NEED_NET_SYN;
+ rt = rtget(ifp->int_std_addr, ifp->int_std_mask);
+ if (rt != 0
+ && 0 == (rt->rt_state & RS_NO_NET_SYN)
+ && (!(rt->rt_state & RS_NET_SYN)
+ || rt->rt_metric > ifp->int_metric)) {
+ rtdelete(rt);
+ rt = 0;
+ }
+ if (rt == 0) {
+ new.rts_ifp = ifp;
+ new.rts_gate = ifp->int_addr;
+ new.rts_router = ifp->int_addr;
+ new.rts_metric = ifp->int_metric;
+ rtadd(ifp->int_std_addr, ifp->int_std_mask,
+ RS_NET_SYN, &new);
+ }
+
+ } else {
+ ifp->int_state &= ~IS_NEED_NET_SYN;
+
+ rt = rtget(ifp->int_std_addr,
+ ifp->int_std_mask);
+ if (rt != 0
+ && (rt->rt_state & RS_NET_SYN)
+ && rt->rt_ifp == ifp)
+ rtbad_sub(rt);
+ }
+}
+
+
+/* Add route for interface if not currently installed.
+ * Create route to other end if a point-to-point link,
+ * otherwise a route to this (sub)network.
+ */
+static int /* 0=bad interface */
+addrouteforif(struct interface *ifp)
+{
+ struct rt_entry *rt;
+ static struct rt_spare new;
+ naddr dst;
+
+
+ /* skip sick interfaces
+ */
+ if (ifp->int_state & IS_BROKE)
+ return 0;
+
+ /* If the interface on a subnet, then install a RIPv1 route to
+ * the network as well (unless it is sick).
+ */
+ if (ifp->int_state & IS_SUBNET)
+ check_net_syn(ifp);
+
+ dst = (0 != (ifp->int_if_flags & (IFF_POINTOPOINT | IFF_LOOPBACK))
+ ? ifp->int_dstaddr
+ : htonl(ifp->int_net));
+
+ new.rts_ifp = ifp;
+ new.rts_router = ifp->int_addr;
+ new.rts_gate = ifp->int_addr;
+ new.rts_metric = ifp->int_metric;
+ new.rts_time = now.tv_sec;
+
+ /* If we are going to send packets to the gateway,
+ * it must be reachable using our physical interfaces
+ */
+ if ((ifp->int_state & IS_REMOTE)
+ && !(ifp->int_state & IS_EXTERNAL)
+ && !check_remote(ifp))
+ return 0;
+
+ /* We are finished if the correct main interface route exists.
+ * The right route must be for the right interface, not synthesized
+ * from a subnet, be a "gateway" or not as appropriate, and so forth.
+ */
+ del_static(dst, ifp->int_mask, 0, 0);
+ rt = rtget(dst, ifp->int_mask);
+ if (rt != 0) {
+ if ((rt->rt_ifp != ifp
+ || rt->rt_router != ifp->int_addr)
+ && (!(ifp->int_state & IS_DUP)
+ || rt->rt_ifp == 0
+ || (rt->rt_ifp->int_state & IS_BROKE))) {
+ rtdelete(rt);
+ rt = 0;
+ } else {
+ rtchange(rt, ((rt->rt_state | RS_IF)
+ & ~(RS_NET_SYN | RS_LOCAL)),
+ &new, 0);
+ }
+ }
+ if (rt == 0) {
+ if (ifp->int_transitions++ > 0)
+ trace_act("re-install interface %s",
+ ifp->int_name);
+
+ rtadd(dst, ifp->int_mask, RS_IF, &new);
+ }
+
+ return 1;
+}
diff --git a/sbin/routed/input.c b/sbin/routed/input.c
new file mode 100644
index 0000000..895ef50
--- /dev/null
+++ b/sbin/routed/input.c
@@ -0,0 +1,1028 @@
+/*
+ * Copyright (c) 1983, 1988, 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.
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+#include "defs.h"
+
+#ifdef __NetBSD__
+__RCSID("$NetBSD$");
+#elif defined(__FreeBSD__)
+__RCSID("$FreeBSD$");
+#else
+__RCSID("$Revision: 2.26 $");
+#ident "$Revision: 2.26 $"
+#endif
+
+static void input(struct sockaddr_in *, struct interface *, struct interface *,
+ struct rip *, int);
+static void input_route(naddr, naddr, struct rt_spare *, struct netinfo *);
+static int ck_passwd(struct interface *, struct rip *, void *,
+ naddr, struct msg_limit *);
+
+
+/* process RIP input
+ */
+void
+read_rip(int sock,
+ struct interface *sifp)
+{
+ struct sockaddr_in from;
+ struct interface *aifp;
+ socklen_t fromlen;
+ int cc;
+#ifdef USE_PASSIFNAME
+ static struct msg_limit bad_name;
+ struct {
+ char ifname[IFNAMSIZ];
+ union pkt_buf pbuf;
+ } inbuf;
+#else
+ struct {
+ union pkt_buf pbuf;
+ } inbuf;
+#endif
+
+
+ for (;;) {
+ fromlen = sizeof(from);
+ cc = recvfrom(sock, &inbuf, sizeof(inbuf), 0,
+ (struct sockaddr*)&from, &fromlen);
+ if (cc <= 0) {
+ if (cc < 0 && errno != EWOULDBLOCK)
+ LOGERR("recvfrom(rip)");
+ break;
+ }
+ if (fromlen != sizeof(struct sockaddr_in))
+ logbad(1,"impossible recvfrom(rip) fromlen=%d",
+ (int)fromlen);
+
+ /* aifp is the "authenticated" interface via which the packet
+ * arrived. In fact, it is only the interface on which
+ * the packet should have arrived based on is source
+ * address.
+ * sifp is interface associated with the socket through which
+ * the packet was received.
+ */
+#ifdef USE_PASSIFNAME
+ if ((cc -= sizeof(inbuf.ifname)) < 0)
+ logbad(0,"missing USE_PASSIFNAME; only %d bytes",
+ cc+sizeof(inbuf.ifname));
+
+ /* check the remote interfaces first */
+ LIST_FOREACH(aifp, &remote_if, remote_list) {
+ if (aifp->int_addr == from.sin_addr.s_addr)
+ break;
+ }
+ if (aifp == 0) {
+ aifp = ifwithname(inbuf.ifname, 0);
+ if (aifp == 0) {
+ msglim(&bad_name, from.sin_addr.s_addr,
+ "impossible interface name %.*s",
+ IFNAMSIZ, inbuf.ifname);
+ } else if (((aifp->int_if_flags & IFF_POINTOPOINT)
+ && aifp->int_dstaddr!=from.sin_addr.s_addr)
+ || (!(aifp->int_if_flags & IFF_POINTOPOINT)
+ && !on_net(from.sin_addr.s_addr,
+ aifp->int_net,
+ aifp->int_mask))) {
+ /* If it came via the wrong interface, do not
+ * trust it.
+ */
+ aifp = 0;
+ }
+ }
+#else
+ aifp = iflookup(from.sin_addr.s_addr);
+#endif
+ if (sifp == 0)
+ sifp = aifp;
+
+ input(&from, sifp, aifp, &inbuf.pbuf.rip, cc);
+ }
+}
+
+
+/* Process a RIP packet
+ */
+static void
+input(struct sockaddr_in *from, /* received from this IP address */
+ struct interface *sifp, /* interface of incoming socket */
+ struct interface *aifp, /* "authenticated" interface */
+ struct rip *rip,
+ int cc)
+{
+# define FROM_NADDR from->sin_addr.s_addr
+ static struct msg_limit use_auth, bad_len, bad_mask;
+ static struct msg_limit unk_router, bad_router, bad_nhop;
+
+ struct rt_entry *rt;
+ struct rt_spare new;
+ struct netinfo *n, *lim;
+ struct interface *ifp1;
+ naddr gate, mask, v1_mask, dst, ddst_h = 0;
+ struct auth *ap;
+ struct tgate *tg = 0;
+ struct tgate_net *tn;
+ int i, j;
+
+ /* Notice when we hear from a remote gateway
+ */
+ if (aifp != 0
+ && (aifp->int_state & IS_REMOTE))
+ aifp->int_act_time = now.tv_sec;
+
+ trace_rip("Recv", "from", from, sifp, rip, cc);
+
+ if (rip->rip_vers == 0) {
+ msglim(&bad_router, FROM_NADDR,
+ "RIP version 0, cmd %d, packet received from %s",
+ rip->rip_cmd, naddr_ntoa(FROM_NADDR));
+ return;
+ } else if (rip->rip_vers > RIPv2) {
+ rip->rip_vers = RIPv2;
+ }
+ if (cc > (int)OVER_MAXPACKETSIZE) {
+ msglim(&bad_router, FROM_NADDR,
+ "packet at least %d bytes too long received from %s",
+ cc-MAXPACKETSIZE, naddr_ntoa(FROM_NADDR));
+ return;
+ }
+
+ n = rip->rip_nets;
+ lim = (struct netinfo *)((char*)rip + cc);
+
+ /* Notice authentication.
+ * As required by section 4.2 in RFC 1723, discard authenticated
+ * RIPv2 messages, but only if configured for that silliness.
+ *
+ * RIPv2 authentication is lame. Why authenticate queries?
+ * Why should a RIPv2 implementation with authentication disabled
+ * not be able to listen to RIPv2 packets with authentication, while
+ * RIPv1 systems will listen? Crazy!
+ */
+ if (!auth_ok
+ && rip->rip_vers == RIPv2
+ && n < lim && n->n_family == RIP_AF_AUTH) {
+ msglim(&use_auth, FROM_NADDR,
+ "RIPv2 message with authentication from %s discarded",
+ naddr_ntoa(FROM_NADDR));
+ return;
+ }
+
+ switch (rip->rip_cmd) {
+ case RIPCMD_REQUEST:
+ /* For mere requests, be a little sloppy about the source
+ */
+ if (aifp == 0)
+ aifp = sifp;
+
+ /* Are we talking to ourself or a remote gateway?
+ */
+ ifp1 = ifwithaddr(FROM_NADDR, 0, 1);
+ if (ifp1) {
+ if (ifp1->int_state & IS_REMOTE) {
+ /* remote gateway */
+ aifp = ifp1;
+ if (check_remote(aifp)) {
+ aifp->int_act_time = now.tv_sec;
+ (void)if_ok(aifp, "remote ");
+ }
+ } else if (from->sin_port == htons(RIP_PORT)) {
+ trace_pkt(" discard our own RIP request");
+ return;
+ }
+ }
+
+ /* did the request come from a router?
+ */
+ if (from->sin_port == htons(RIP_PORT)) {
+ /* yes, ignore the request if RIP is off so that
+ * the router does not depend on us.
+ */
+ if (rip_sock < 0
+ || (aifp != 0
+ && IS_RIP_OUT_OFF(aifp->int_state))) {
+ trace_pkt(" discard request while RIP off");
+ return;
+ }
+ }
+
+ /* According to RFC 1723, we should ignore unauthenticated
+ * queries. That is too silly to bother with. Sheesh!
+ * Are forwarding tables supposed to be secret, when
+ * a bad guy can infer them with test traffic? When RIP
+ * is still the most common router-discovery protocol
+ * and so hosts need to send queries that will be answered?
+ * What about `rtquery`?
+ * Maybe on firewalls you'd care, but not enough to
+ * give up the diagnostic facilities of remote probing.
+ */
+
+ if (n >= lim) {
+ msglim(&bad_len, FROM_NADDR, "empty request from %s",
+ naddr_ntoa(FROM_NADDR));
+ return;
+ }
+ if (cc%sizeof(*n) != sizeof(struct rip)%sizeof(*n)) {
+ msglim(&bad_len, FROM_NADDR,
+ "request of bad length (%d) from %s",
+ cc, naddr_ntoa(FROM_NADDR));
+ }
+
+ if (rip->rip_vers == RIPv2
+ && (aifp == 0 || (aifp->int_state & IS_NO_RIPV1_OUT))) {
+ v12buf.buf->rip_vers = RIPv2;
+ /* If we have a secret but it is a cleartext secret,
+ * do not disclose our secret unless the other guy
+ * already knows it.
+ */
+ ap = find_auth(aifp);
+ if (ap != 0 && ap->type == RIP_AUTH_PW
+ && n->n_family == RIP_AF_AUTH
+ && !ck_passwd(aifp,rip,lim,FROM_NADDR,&use_auth))
+ ap = 0;
+ } else {
+ v12buf.buf->rip_vers = RIPv1;
+ ap = 0;
+ }
+ clr_ws_buf(&v12buf, ap);
+
+ do {
+ n->n_metric = ntohl(n->n_metric);
+
+ /* A single entry with family RIP_AF_UNSPEC and
+ * metric HOPCNT_INFINITY means "all routes".
+ * We respond to routers only if we are acting
+ * as a supplier, or to anyone other than a router
+ * (i.e. a query).
+ */
+ if (n->n_family == RIP_AF_UNSPEC
+ && n->n_metric == HOPCNT_INFINITY) {
+ /* Answer a query from a utility program
+ * with all we know.
+ */
+ if (aifp == NULL) {
+ trace_pkt("ignore remote query");
+ return;
+ }
+ if (from->sin_port != htons(RIP_PORT)) {
+ /*
+ * insecure: query from non-router node
+ * > 1: allow from distant node
+ * > 0: allow from neighbor node
+ * == 0: deny
+ */
+ if ((aifp != NULL && insecure > 0) ||
+ (aifp == NULL && insecure > 1))
+ supply(from, aifp, OUT_QUERY, 0,
+ rip->rip_vers, ap != 0);
+ else
+ trace_pkt("Warning: "
+ "possible attack detected");
+ return;
+ }
+
+ /* A router trying to prime its tables.
+ * Filter the answer in the about same way
+ * broadcasts are filtered.
+ *
+ * Only answer a router if we are a supplier
+ * to keep an unwary host that is just starting
+ * from picking us as a router.
+ */
+ if (aifp == 0) {
+ trace_pkt("ignore distant router");
+ return;
+ }
+ if (!supplier
+ || IS_RIP_OFF(aifp->int_state)) {
+ trace_pkt("ignore; not supplying");
+ return;
+ }
+
+ /* Do not answer a RIPv1 router if
+ * we are sending RIPv2. But do offer
+ * poor man's router discovery.
+ */
+ if ((aifp->int_state & IS_NO_RIPV1_OUT)
+ && rip->rip_vers == RIPv1) {
+ if (!(aifp->int_state & IS_PM_RDISC)) {
+ trace_pkt("ignore; sending RIPv2");
+ return;
+ }
+
+ v12buf.n->n_family = RIP_AF_INET;
+ v12buf.n->n_dst = RIP_DEFAULT;
+ i = aifp->int_d_metric;
+ if (0 != (rt = rtget(RIP_DEFAULT, 0))) {
+ j = (rt->rt_metric
+ +aifp->int_metric
+ +aifp->int_adj_outmetric
+ +1);
+ if (i > j)
+ i = j;
+ }
+ v12buf.n->n_metric = htonl(i);
+ v12buf.n++;
+ break;
+ }
+
+ /* Respond with RIPv1 instead of RIPv2 if
+ * that is what we are broadcasting on the
+ * interface to keep the remote router from
+ * getting the wrong initial idea of the
+ * routes we send.
+ */
+ supply(from, aifp, OUT_UNICAST, 0,
+ (aifp->int_state & IS_NO_RIPV1_OUT)
+ ? RIPv2 : RIPv1,
+ ap != 0);
+ return;
+ }
+
+ /* Ignore authentication */
+ if (n->n_family == RIP_AF_AUTH)
+ continue;
+
+ if (n->n_family != RIP_AF_INET) {
+ msglim(&bad_router, FROM_NADDR,
+ "request from %s for unsupported"
+ " (af %d) %s",
+ naddr_ntoa(FROM_NADDR),
+ ntohs(n->n_family),
+ naddr_ntoa(n->n_dst));
+ return;
+ }
+
+ /* We are being asked about a specific destination.
+ */
+ dst = n->n_dst;
+ if (!check_dst(dst)) {
+ msglim(&bad_router, FROM_NADDR,
+ "bad queried destination %s from %s",
+ naddr_ntoa(dst),
+ naddr_ntoa(FROM_NADDR));
+ return;
+ }
+
+ /* decide what mask was intended */
+ if (rip->rip_vers == RIPv1
+ || 0 == (mask = ntohl(n->n_mask))
+ || 0 != (ntohl(dst) & ~mask))
+ mask = ripv1_mask_host(dst, aifp);
+
+ /* try to find the answer */
+ rt = rtget(dst, mask);
+ if (!rt && dst != RIP_DEFAULT)
+ rt = rtfind(n->n_dst);
+
+ if (v12buf.buf->rip_vers != RIPv1)
+ v12buf.n->n_mask = mask;
+ if (rt == 0) {
+ /* we do not have the answer */
+ v12buf.n->n_metric = HOPCNT_INFINITY;
+ } else {
+ /* we have the answer, so compute the
+ * right metric and next hop.
+ */
+ v12buf.n->n_family = RIP_AF_INET;
+ v12buf.n->n_dst = dst;
+ j = rt->rt_metric+1;
+ if (!aifp)
+ ++j;
+ else
+ j += (aifp->int_metric
+ + aifp->int_adj_outmetric);
+ if (j < HOPCNT_INFINITY)
+ v12buf.n->n_metric = j;
+ else
+ v12buf.n->n_metric = HOPCNT_INFINITY;
+ if (v12buf.buf->rip_vers != RIPv1) {
+ v12buf.n->n_tag = rt->rt_tag;
+ v12buf.n->n_mask = mask;
+ if (aifp != 0
+ && on_net(rt->rt_gate,
+ aifp->int_net,
+ aifp->int_mask)
+ && rt->rt_gate != aifp->int_addr)
+ v12buf.n->n_nhop = rt->rt_gate;
+ }
+ }
+ v12buf.n->n_metric = htonl(v12buf.n->n_metric);
+
+ /* Stop paying attention if we fill the output buffer.
+ */
+ if (++v12buf.n >= v12buf.lim)
+ break;
+ } while (++n < lim);
+
+ /* Send the answer about specific routes.
+ */
+ if (ap != 0 && ap->type == RIP_AUTH_MD5)
+ end_md5_auth(&v12buf, ap);
+
+ if (from->sin_port != htons(RIP_PORT)) {
+ /* query */
+ (void)output(OUT_QUERY, from, aifp,
+ v12buf.buf,
+ ((char *)v12buf.n - (char*)v12buf.buf));
+ } else if (supplier) {
+ (void)output(OUT_UNICAST, from, aifp,
+ v12buf.buf,
+ ((char *)v12buf.n - (char*)v12buf.buf));
+ } else {
+ /* Only answer a router if we are a supplier
+ * to keep an unwary host that is just starting
+ * from picking us an a router.
+ */
+ ;
+ }
+ return;
+
+ case RIPCMD_TRACEON:
+ case RIPCMD_TRACEOFF:
+ /* Notice that trace messages are turned off for all possible
+ * abuse if _PATH_TRACE is undefined in pathnames.h.
+ * Notice also that because of the way the trace file is
+ * handled in trace.c, no abuse is plausible even if
+ * _PATH_TRACE_ is defined.
+ *
+ * First verify message came from a privileged port. */
+ if (ntohs(from->sin_port) > IPPORT_RESERVED) {
+ msglog("trace command from untrusted port on %s",
+ naddr_ntoa(FROM_NADDR));
+ return;
+ }
+ if (aifp == 0) {
+ msglog("trace command from unknown router %s",
+ naddr_ntoa(FROM_NADDR));
+ return;
+ }
+ if (rip->rip_cmd == RIPCMD_TRACEON) {
+ rip->rip_tracefile[cc-4] = '\0';
+ set_tracefile((char*)rip->rip_tracefile,
+ "trace command: %s\n", 0);
+ } else {
+ trace_off("tracing turned off by %s",
+ naddr_ntoa(FROM_NADDR));
+ }
+ return;
+
+ case RIPCMD_RESPONSE:
+ if (cc%sizeof(*n) != sizeof(struct rip)%sizeof(*n)) {
+ msglim(&bad_len, FROM_NADDR,
+ "response of bad length (%d) from %s",
+ cc, naddr_ntoa(FROM_NADDR));
+ }
+
+ /* verify message came from a router */
+ if (from->sin_port != ntohs(RIP_PORT)) {
+ msglim(&bad_router, FROM_NADDR,
+ " discard RIP response from unknown port"
+ " %d on %s",
+ ntohs(from->sin_port), naddr_ntoa(FROM_NADDR));
+ return;
+ }
+
+ if (rip_sock < 0) {
+ trace_pkt(" discard response while RIP off");
+ return;
+ }
+
+ /* Are we talking to ourself or a remote gateway?
+ */
+ ifp1 = ifwithaddr(FROM_NADDR, 0, 1);
+ if (ifp1) {
+ if (ifp1->int_state & IS_REMOTE) {
+ /* remote gateway */
+ aifp = ifp1;
+ if (check_remote(aifp)) {
+ aifp->int_act_time = now.tv_sec;
+ (void)if_ok(aifp, "remote ");
+ }
+ } else {
+ trace_pkt(" discard our own RIP response");
+ return;
+ }
+ }
+
+ /* Accept routing packets from routers directly connected
+ * via broadcast or point-to-point networks, and from
+ * those listed in /etc/gateways.
+ */
+ if (aifp == 0) {
+ msglim(&unk_router, FROM_NADDR,
+ " discard response from %s"
+ " via unexpected interface",
+ naddr_ntoa(FROM_NADDR));
+ return;
+ }
+ if (IS_RIP_IN_OFF(aifp->int_state)) {
+ trace_pkt(" discard RIPv%d response"
+ " via disabled interface %s",
+ rip->rip_vers, aifp->int_name);
+ return;
+ }
+
+ if (n >= lim) {
+ msglim(&bad_len, FROM_NADDR, "empty response from %s",
+ naddr_ntoa(FROM_NADDR));
+ return;
+ }
+
+ if (((aifp->int_state & IS_NO_RIPV1_IN)
+ && rip->rip_vers == RIPv1)
+ || ((aifp->int_state & IS_NO_RIPV2_IN)
+ && rip->rip_vers != RIPv1)) {
+ trace_pkt(" discard RIPv%d response",
+ rip->rip_vers);
+ return;
+ }
+
+ /* Ignore routes via dead interface.
+ */
+ if (aifp->int_state & IS_BROKE) {
+ trace_pkt("discard response via broken interface %s",
+ aifp->int_name);
+ return;
+ }
+
+ /* If the interface cares, ignore bad routers.
+ * Trace but do not log this problem, because where it
+ * happens, it happens frequently.
+ */
+ if (aifp->int_state & IS_DISTRUST) {
+ tg = tgates;
+ while (tg->tgate_addr != FROM_NADDR) {
+ tg = tg->tgate_next;
+ if (tg == 0) {
+ trace_pkt(" discard RIP response"
+ " from untrusted router %s",
+ naddr_ntoa(FROM_NADDR));
+ return;
+ }
+ }
+ }
+
+ /* Authenticate the packet if we have a secret.
+ * If we do not have any secrets, ignore the error in
+ * RFC 1723 and accept it regardless.
+ */
+ if (aifp->int_auth[0].type != RIP_AUTH_NONE
+ && rip->rip_vers != RIPv1
+ && !ck_passwd(aifp,rip,lim,FROM_NADDR,&use_auth))
+ return;
+
+ do {
+ if (n->n_family == RIP_AF_AUTH)
+ continue;
+
+ n->n_metric = ntohl(n->n_metric);
+ dst = n->n_dst;
+ if (n->n_family != RIP_AF_INET
+ && (n->n_family != RIP_AF_UNSPEC
+ || dst != RIP_DEFAULT)) {
+ msglim(&bad_router, FROM_NADDR,
+ "route from %s to unsupported"
+ " address family=%d destination=%s",
+ naddr_ntoa(FROM_NADDR),
+ n->n_family,
+ naddr_ntoa(dst));
+ continue;
+ }
+ if (!check_dst(dst)) {
+ msglim(&bad_router, FROM_NADDR,
+ "bad destination %s from %s",
+ naddr_ntoa(dst),
+ naddr_ntoa(FROM_NADDR));
+ return;
+ }
+ if (n->n_metric == 0
+ || n->n_metric > HOPCNT_INFINITY) {
+ msglim(&bad_router, FROM_NADDR,
+ "bad metric %d from %s"
+ " for destination %s",
+ n->n_metric,
+ naddr_ntoa(FROM_NADDR),
+ naddr_ntoa(dst));
+ return;
+ }
+
+ /* Notice the next-hop.
+ */
+ gate = FROM_NADDR;
+ if (n->n_nhop != 0) {
+ if (rip->rip_vers == RIPv1) {
+ n->n_nhop = 0;
+ } else {
+ /* Use it only if it is valid. */
+ if (on_net(n->n_nhop,
+ aifp->int_net, aifp->int_mask)
+ && check_dst(n->n_nhop)) {
+ gate = n->n_nhop;
+ } else {
+ msglim(&bad_nhop, FROM_NADDR,
+ "router %s to %s"
+ " has bad next hop %s",
+ naddr_ntoa(FROM_NADDR),
+ naddr_ntoa(dst),
+ naddr_ntoa(n->n_nhop));
+ n->n_nhop = 0;
+ }
+ }
+ }
+
+ if (rip->rip_vers == RIPv1
+ || 0 == (mask = ntohl(n->n_mask))) {
+ mask = ripv1_mask_host(dst,aifp);
+ } else if ((ntohl(dst) & ~mask) != 0) {
+ msglim(&bad_mask, FROM_NADDR,
+ "router %s sent bad netmask"
+ " %#lx with %s",
+ naddr_ntoa(FROM_NADDR),
+ (u_long)mask,
+ naddr_ntoa(dst));
+ continue;
+ }
+ if (rip->rip_vers == RIPv1)
+ n->n_tag = 0;
+
+ /* Adjust metric according to incoming interface..
+ */
+ n->n_metric += (aifp->int_metric
+ + aifp->int_adj_inmetric);
+ if (n->n_metric > HOPCNT_INFINITY)
+ n->n_metric = HOPCNT_INFINITY;
+
+ /* Should we trust this route from this router? */
+ if (tg && (tn = tg->tgate_nets)->mask != 0) {
+ for (i = 0; i < MAX_TGATE_NETS; i++, tn++) {
+ if (on_net(dst, tn->net, tn->mask)
+ && tn->mask <= mask)
+ break;
+ }
+ if (i >= MAX_TGATE_NETS || tn->mask == 0) {
+ trace_pkt(" ignored unauthorized %s",
+ addrname(dst,mask,0));
+ continue;
+ }
+ }
+
+ /* Recognize and ignore a default route we faked
+ * which is being sent back to us by a machine with
+ * broken split-horizon.
+ * Be a little more paranoid than that, and reject
+ * default routes with the same metric we advertised.
+ */
+ if (aifp->int_d_metric != 0
+ && dst == RIP_DEFAULT
+ && (int)n->n_metric >= aifp->int_d_metric)
+ continue;
+
+ /* We can receive aggregated RIPv2 routes that must
+ * be broken down before they are transmitted by
+ * RIPv1 via an interface on a subnet.
+ * We might also receive the same routes aggregated
+ * via other RIPv2 interfaces.
+ * This could cause duplicate routes to be sent on
+ * the RIPv1 interfaces. "Longest matching variable
+ * length netmasks" lets RIPv2 listeners understand,
+ * but breaking down the aggregated routes for RIPv1
+ * listeners can produce duplicate routes.
+ *
+ * Breaking down aggregated routes here bloats
+ * the daemon table, but does not hurt the kernel
+ * table, since routes are always aggregated for
+ * the kernel.
+ *
+ * Notice that this does not break down network
+ * routes corresponding to subnets. This is part
+ * of the defense against RS_NET_SYN.
+ */
+ if (have_ripv1_out
+ && (((rt = rtget(dst,mask)) == 0
+ || !(rt->rt_state & RS_NET_SYN)))
+ && (v1_mask = ripv1_mask_net(dst,0)) > mask) {
+ ddst_h = v1_mask & -v1_mask;
+ i = (v1_mask & ~mask)/ddst_h;
+ if (i >= 511) {
+ /* Punt if we would have to generate
+ * an unreasonable number of routes.
+ */
+ if (TRACECONTENTS)
+ trace_misc("accept %s-->%s as 1"
+ " instead of %d routes",
+ addrname(dst,mask,0),
+ naddr_ntoa(FROM_NADDR),
+ i+1);
+ i = 0;
+ } else {
+ mask = v1_mask;
+ }
+ } else {
+ i = 0;
+ }
+
+ new.rts_gate = gate;
+ new.rts_router = FROM_NADDR;
+ new.rts_metric = n->n_metric;
+ new.rts_tag = n->n_tag;
+ new.rts_time = now.tv_sec;
+ new.rts_ifp = aifp;
+ new.rts_de_ag = i;
+ j = 0;
+ for (;;) {
+ input_route(dst, mask, &new, n);
+ if (++j > i)
+ break;
+ dst = htonl(ntohl(dst) + ddst_h);
+ }
+ } while (++n < lim);
+ break;
+ }
+#undef FROM_NADDR
+}
+
+
+/* Process a single input route.
+ */
+static void
+input_route(naddr dst, /* network order */
+ naddr mask,
+ struct rt_spare *new,
+ struct netinfo *n)
+{
+ int i;
+ struct rt_entry *rt;
+ struct rt_spare *rts, *rts0;
+ struct interface *ifp1;
+
+
+ /* See if the other guy is telling us to send our packets to him.
+ * Sometimes network routes arrive over a point-to-point link for
+ * the network containing the address(es) of the link.
+ *
+ * If our interface is broken, switch to using the other guy.
+ */
+ ifp1 = ifwithaddr(dst, 1, 1);
+ if (ifp1 != 0
+ && (!(ifp1->int_state & IS_BROKE)
+ || (ifp1->int_state & IS_PASSIVE)))
+ return;
+
+ /* Look for the route in our table.
+ */
+ rt = rtget(dst, mask);
+
+ /* Consider adding the route if we do not already have it.
+ */
+ if (rt == 0) {
+ /* Ignore unknown routes being poisoned.
+ */
+ if (new->rts_metric == HOPCNT_INFINITY)
+ return;
+
+ /* Ignore the route if it points to us */
+ if (n->n_nhop != 0
+ && 0 != ifwithaddr(n->n_nhop, 1, 0))
+ return;
+
+ /* If something has not gone crazy and tried to fill
+ * our memory, accept the new route.
+ */
+ if (total_routes < MAX_ROUTES)
+ rtadd(dst, mask, 0, new);
+ return;
+ }
+
+ /* We already know about the route. Consider this update.
+ *
+ * If (rt->rt_state & RS_NET_SYN), then this route
+ * is the same as a network route we have inferred
+ * for subnets we know, in order to tell RIPv1 routers
+ * about the subnets.
+ *
+ * It is impossible to tell if the route is coming
+ * from a distant RIPv2 router with the standard
+ * netmask because that router knows about the entire
+ * network, or if it is a round-about echo of a
+ * synthetic, RIPv1 network route of our own.
+ * The worst is that both kinds of routes might be
+ * received, and the bad one might have the smaller
+ * metric. Partly solve this problem by never
+ * aggregating into such a route. Also keep it
+ * around as long as the interface exists.
+ */
+
+ rts0 = rt->rt_spares;
+ for (rts = rts0, i = NUM_SPARES; i != 0; i--, rts++) {
+ if (rts->rts_router == new->rts_router)
+ break;
+ /* Note the worst slot to reuse,
+ * other than the current slot.
+ */
+ if (rts0 == rt->rt_spares
+ || BETTER_LINK(rt, rts0, rts))
+ rts0 = rts;
+ }
+ if (i != 0) {
+ /* Found a route from the router already in the table.
+ */
+
+ /* If the new route is a route broken down from an
+ * aggregated route, and if the previous route is either
+ * not a broken down route or was broken down from a finer
+ * netmask, and if the previous route is current,
+ * then forget this one.
+ */
+ if (new->rts_de_ag > rts->rts_de_ag
+ && now_stale <= rts->rts_time)
+ return;
+
+ /* Keep poisoned routes around only long enough to pass
+ * the poison on. Use a new timestamp for good routes.
+ */
+ if (rts->rts_metric == HOPCNT_INFINITY
+ && new->rts_metric == HOPCNT_INFINITY)
+ new->rts_time = rts->rts_time;
+
+ /* If this is an update for the router we currently prefer,
+ * then note it.
+ */
+ if (i == NUM_SPARES) {
+ rtchange(rt, rt->rt_state, new, 0);
+ /* If the route got worse, check for something better.
+ */
+ if (new->rts_metric > rts->rts_metric)
+ rtswitch(rt, 0);
+ return;
+ }
+
+ /* This is an update for a spare route.
+ * Finished if the route is unchanged.
+ */
+ if (rts->rts_gate == new->rts_gate
+ && rts->rts_metric == new->rts_metric
+ && rts->rts_tag == new->rts_tag) {
+ trace_upslot(rt, rts, new);
+ *rts = *new;
+ return;
+ }
+ /* Forget it if it has gone bad.
+ */
+ if (new->rts_metric == HOPCNT_INFINITY) {
+ rts_delete(rt, rts);
+ return;
+ }
+
+ } else {
+ /* The update is for a route we know about,
+ * but not from a familiar router.
+ *
+ * Ignore the route if it points to us.
+ */
+ if (n->n_nhop != 0
+ && 0 != ifwithaddr(n->n_nhop, 1, 0))
+ return;
+
+ /* the loop above set rts0=worst spare */
+ rts = rts0;
+
+ /* Save the route as a spare only if it has
+ * a better metric than our worst spare.
+ * This also ignores poisoned routes (those
+ * received with metric HOPCNT_INFINITY).
+ */
+ if (new->rts_metric >= rts->rts_metric)
+ return;
+ }
+
+ trace_upslot(rt, rts, new);
+ *rts = *new;
+
+ /* try to switch to a better route */
+ rtswitch(rt, rts);
+}
+
+
+static int /* 0 if bad */
+ck_passwd(struct interface *aifp,
+ struct rip *rip,
+ void *lim,
+ naddr from,
+ struct msg_limit *use_authp)
+{
+# define NA (rip->rip_auths)
+ struct netauth *na2;
+ struct auth *ap;
+ MD5_CTX md5_ctx;
+ u_char hash[RIP_AUTH_PW_LEN];
+ int i, len;
+
+ assert(aifp != NULL);
+ if ((void *)NA >= lim || NA->a_family != RIP_AF_AUTH) {
+ msglim(use_authp, from, "missing password from %s",
+ naddr_ntoa(from));
+ return 0;
+ }
+
+ /* accept any current (+/- 24 hours) password
+ */
+ for (ap = aifp->int_auth, i = 0; i < MAX_AUTH_KEYS; i++, ap++) {
+ if (ap->type != NA->a_type
+ || (u_long)ap->start > (u_long)clk.tv_sec+DAY
+ || (u_long)ap->end+DAY < (u_long)clk.tv_sec)
+ continue;
+
+ if (NA->a_type == RIP_AUTH_PW) {
+ if (!memcmp(NA->au.au_pw, ap->key, RIP_AUTH_PW_LEN))
+ return 1;
+
+ } else {
+ /* accept MD5 secret with the right key ID
+ */
+ if (NA->au.a_md5.md5_keyid != ap->keyid)
+ continue;
+
+ len = ntohs(NA->au.a_md5.md5_pkt_len);
+ if ((len-sizeof(*rip)) % sizeof(*NA) != 0
+ || len != (char *)lim-(char*)rip-(int)sizeof(*NA)) {
+ msglim(use_authp, from,
+ "wrong MD5 RIPv2 packet length of %d"
+ " instead of %d from %s",
+ len, (int)((char *)lim-(char *)rip
+ -sizeof(*NA)),
+ naddr_ntoa(from));
+ return 0;
+ }
+ na2 = (struct netauth *)((char *)rip+len);
+
+ /* Given a good hash value, these are not security
+ * problems so be generous and accept the routes,
+ * after complaining.
+ */
+ if (TRACEPACKETS) {
+ if (NA->au.a_md5.md5_auth_len
+ != RIP_AUTH_MD5_HASH_LEN)
+ msglim(use_authp, from,
+ "unknown MD5 RIPv2 auth len %#x"
+ " instead of %#x from %s",
+ NA->au.a_md5.md5_auth_len,
+ (unsigned)RIP_AUTH_MD5_HASH_LEN,
+ naddr_ntoa(from));
+ if (na2->a_family != RIP_AF_AUTH)
+ msglim(use_authp, from,
+ "unknown MD5 RIPv2 family %#x"
+ " instead of %#x from %s",
+ na2->a_family, RIP_AF_AUTH,
+ naddr_ntoa(from));
+ if (na2->a_type != ntohs(1))
+ msglim(use_authp, from,
+ "MD5 RIPv2 hash has %#x"
+ " instead of %#x from %s",
+ na2->a_type, ntohs(1),
+ naddr_ntoa(from));
+ }
+
+ MD5Init(&md5_ctx);
+ MD5Update(&md5_ctx, (u_char *)rip,
+ len + RIP_AUTH_MD5_HASH_XTRA);
+ MD5Update(&md5_ctx, ap->key, RIP_AUTH_MD5_KEY_LEN);
+ MD5Final(hash, &md5_ctx);
+ if (!memcmp(hash, na2->au.au_pw, sizeof(hash)))
+ return 1;
+ }
+ }
+
+ msglim(use_authp, from, "bad password from %s",
+ naddr_ntoa(from));
+ return 0;
+#undef NA
+}
diff --git a/sbin/routed/main.c b/sbin/routed/main.c
new file mode 100644
index 0000000..5ebd7ec
--- /dev/null
+++ b/sbin/routed/main.c
@@ -0,0 +1,969 @@
+/*
+ * Copyright (c) 1983, 1988, 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.
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+#include "defs.h"
+#include "pathnames.h"
+#ifdef sgi
+#include "math.h"
+#endif
+#include <signal.h>
+#include <fcntl.h>
+#include <sys/file.h>
+
+__COPYRIGHT("@(#) Copyright (c) 1983, 1988, 1993 "
+ "The Regents of the University of California."
+ " All rights reserved.");
+#ifdef __NetBSD__
+__RCSID("$NetBSD$");
+#include <util.h>
+#elif defined(__FreeBSD__)
+__RCSID("$FreeBSD$");
+#else
+__RCSID("$Revision: 2.31 $");
+#ident "$Revision: 2.31 $"
+#endif
+
+pid_t mypid;
+
+naddr myaddr; /* system address */
+static char myname[MAXHOSTNAMELEN+1];
+
+static int verbose;
+
+int supplier; /* supply or broadcast updates */
+int supplier_set;
+static int ipforwarding = 1; /* kernel forwarding on */
+
+static int default_gateway; /* 1=advertise default */
+static int background = 1;
+int ridhosts; /* 1=reduce host routes */
+int mhome; /* 1=want multi-homed host route */
+int advertise_mhome; /* 1=must continue advertising it */
+int auth_ok = 1; /* 1=ignore auth if we do not care */
+int insecure; /* Reply to special queries or not */
+
+struct timeval epoch; /* when started */
+struct timeval clk;
+static struct timeval prev_clk;
+static int usec_fudge;
+struct timeval now; /* current idea of time */
+time_t now_stale;
+time_t now_expire;
+time_t now_garbage;
+
+static struct timeval next_bcast; /* next general broadcast */
+struct timeval no_flash = { /* inhibit flash update */
+ EPOCH+SUPPLY_INTERVAL, 0
+};
+
+static struct timeval flush_kern_timer;
+
+static fd_set fdbits;
+static int sock_max;
+int rip_sock = -1; /* RIP socket */
+const struct interface *rip_sock_mcast; /* current multicast interface */
+int rt_sock; /* routing socket */
+int rt_sock_seqno;
+
+
+static int get_rip_sock(naddr, int);
+static void timevalsub(struct timeval *, struct timeval *, struct timeval *);
+static void sigalrm(int s UNUSED);
+static void sigterm(int sig);
+
+int
+main(int argc,
+ char *argv[])
+{
+ int n, mib[4], off;
+ size_t len;
+ char *p, *q;
+ const char *cp;
+ struct timeval wtime, t2;
+ time_t dt;
+ fd_set ibits;
+ naddr p_net, p_mask;
+ struct interface *ifp;
+ struct parm parm;
+ char *tracename = 0;
+
+
+ /* Some shells are badly broken and send SIGHUP to backgrounded
+ * processes.
+ */
+ signal(SIGHUP, SIG_IGN);
+
+ openlog("routed", LOG_PID, LOG_DAEMON);
+ ftrace = stdout;
+
+ gettimeofday(&clk, 0);
+ prev_clk = clk;
+ epoch = clk;
+ epoch.tv_sec -= EPOCH;
+ now.tv_sec = EPOCH;
+ now_stale = EPOCH - STALE_TIME;
+ now_expire = EPOCH - EXPIRE_TIME;
+ now_garbage = EPOCH - GARBAGE_TIME;
+ wtime.tv_sec = 0;
+
+ (void)gethostname(myname, sizeof(myname)-1);
+ (void)gethost(myname, &myaddr);
+
+ while ((n = getopt(argc, argv, "isqdghmAtvT:F:P:")) != -1) {
+ switch (n) {
+ case 'i':
+ insecure++;
+ break;
+ case 's':
+ supplier = 1;
+ supplier_set = 1;
+ break;
+
+ case 'q':
+ supplier = 0;
+ supplier_set = 1;
+ break;
+
+ case 'd':
+ background = 0;
+ break;
+
+ case 'g':
+ memset(&parm, 0, sizeof(parm));
+ parm.parm_d_metric = 1;
+ cp = check_parms(&parm);
+ if (cp != 0)
+ msglog("bad -g: %s", cp);
+ else
+ default_gateway = 1;
+ break;
+
+ case 'h': /* suppress extra host routes */
+ ridhosts = 1;
+ break;
+
+ case 'm': /* advertise host route */
+ mhome = 1; /* on multi-homed hosts */
+ break;
+
+ case 'A':
+ /* Ignore authentication if we do not care.
+ * Crazy as it is, that is what RFC 1723 requires.
+ */
+ auth_ok = 0;
+ break;
+
+ case 't':
+ new_tracelevel++;
+ break;
+
+ case 'T':
+ tracename = optarg;
+ break;
+
+ case 'F': /* minimal routes for SLIP */
+ n = FAKE_METRIC;
+ p = strchr(optarg,',');
+ if (p && *p != '\0') {
+ n = (int)strtoul(p+1, &q, 0);
+ if (*q == '\0'
+ && n <= HOPCNT_INFINITY-1
+ && n >= 1)
+ *p = '\0';
+ }
+ if (!getnet(optarg, &p_net, &p_mask)) {
+ msglog("bad network; \"-F %s\"",
+ optarg);
+ break;
+ }
+ memset(&parm, 0, sizeof(parm));
+ parm.parm_net = p_net;
+ parm.parm_mask = p_mask;
+ parm.parm_d_metric = n;
+ cp = check_parms(&parm);
+ if (cp != 0)
+ msglog("bad -F: %s", cp);
+ break;
+
+ case 'P':
+ /* handle arbitrary parameters.
+ */
+ q = strdup(optarg);
+ cp = parse_parms(q, 0);
+ if (cp != 0)
+ msglog("%s in \"-P %s\"", cp, optarg);
+ free(q);
+ break;
+
+ case 'v':
+ /* display version */
+ verbose++;
+ msglog("version 2.31");
+ break;
+
+ default:
+ goto usage;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (tracename == 0 && argc >= 1) {
+ tracename = *argv++;
+ argc--;
+ }
+ if (tracename != 0 && tracename[0] == '\0')
+ goto usage;
+ if (argc != 0) {
+usage:
+ logbad(0, "usage: routed [-sqdghmAtv] [-T tracefile]"
+ " [-F net[,metric]] [-P parms]");
+ }
+ if (geteuid() != 0) {
+ if (verbose)
+ exit(0);
+ logbad(0, "requires UID 0");
+ }
+
+ mib[0] = CTL_NET;
+ mib[1] = PF_INET;
+ mib[2] = IPPROTO_IP;
+ mib[3] = IPCTL_FORWARDING;
+ len = sizeof(ipforwarding);
+ if (sysctl(mib, 4, &ipforwarding, &len, 0, 0) < 0)
+ LOGERR("sysctl(IPCTL_FORWARDING)");
+
+ if (!ipforwarding) {
+ if (supplier)
+ msglog("-s incompatible with ipforwarding=0");
+ if (default_gateway) {
+ msglog("-g incompatible with ipforwarding=0");
+ default_gateway = 0;
+ }
+ supplier = 0;
+ supplier_set = 1;
+ }
+ if (default_gateway) {
+ if (supplier_set && !supplier) {
+ msglog("-g and -q incompatible");
+ } else {
+ supplier = 1;
+ supplier_set = 1;
+ }
+ }
+
+
+ signal(SIGALRM, sigalrm);
+ if (!background)
+ signal(SIGHUP, sigterm); /* SIGHUP fatal during debugging */
+ signal(SIGTERM, sigterm);
+ signal(SIGINT, sigterm);
+ signal(SIGUSR1, sigtrace_on);
+ signal(SIGUSR2, sigtrace_off);
+
+ /* get into the background */
+#ifdef sgi
+ if (0 > _daemonize(background ? 0 : (_DF_NOCHDIR|_DF_NOFORK),
+ STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO))
+ BADERR(0, "_daemonize()");
+#else
+ if (background && daemon(0, 1) < 0)
+ BADERR(0,"daemon()");
+#endif
+
+#if defined(__NetBSD__)
+ pidfile(0);
+#endif
+ mypid = getpid();
+#ifdef __FreeBSD__
+ srandomdev();
+#else
+ srandom((int)(clk.tv_sec ^ clk.tv_usec ^ mypid));
+#endif
+
+ /* prepare socket connected to the kernel.
+ */
+ rt_sock = socket(AF_ROUTE, SOCK_RAW, 0);
+ if (rt_sock < 0)
+ BADERR(1,"rt_sock = socket()");
+ if (fcntl(rt_sock, F_SETFL, O_NONBLOCK) == -1)
+ logbad(1, "fcntl(rt_sock) O_NONBLOCK: %s", strerror(errno));
+ off = 0;
+ if (setsockopt(rt_sock, SOL_SOCKET,SO_USELOOPBACK,
+ &off,sizeof(off)) < 0)
+ LOGERR("setsockopt(SO_USELOOPBACK,0)");
+
+ fix_select();
+
+
+ if (tracename != 0) {
+ strncpy(inittracename, tracename, sizeof(inittracename)-1);
+ set_tracefile(inittracename, "%s", -1);
+ } else {
+ tracelevel_msg("%s", -1); /* turn on tracing to stdio */
+ }
+
+ bufinit();
+
+ /* initialize radix tree */
+ rtinit();
+
+ /* Pick a random part of the second for our output to minimize
+ * collisions.
+ *
+ * Start broadcasting after hearing from other routers, and
+ * at a random time so a bunch of systems do not get synchronized
+ * after a power failure.
+ */
+ intvl_random(&next_bcast, EPOCH+MIN_WAITTIME, EPOCH+SUPPLY_INTERVAL);
+ age_timer.tv_usec = next_bcast.tv_usec;
+ age_timer.tv_sec = EPOCH+MIN_WAITTIME;
+ rdisc_timer = next_bcast;
+ ifinit_timer.tv_usec = next_bcast.tv_usec;
+
+ /* Collect an initial view of the world by checking the interface
+ * configuration and the kludge file.
+ */
+ gwkludge();
+ ifinit();
+
+ /* Ask for routes */
+ rip_query();
+ rdisc_sol();
+
+ /* Now turn off stdio if not tracing */
+ if (new_tracelevel == 0)
+ trace_close(background);
+
+ /* Loop forever, listening and broadcasting.
+ */
+ for (;;) {
+ prev_clk = clk;
+ gettimeofday(&clk, 0);
+ if (prev_clk.tv_sec == clk.tv_sec
+ && prev_clk.tv_usec == clk.tv_usec+usec_fudge) {
+ /* Much of `routed` depends on time always advancing.
+ * On systems that do not guarantee that gettimeofday()
+ * produces unique timestamps even if called within
+ * a single tick, use trickery like that in classic
+ * BSD kernels.
+ */
+ clk.tv_usec += ++usec_fudge;
+
+ } else {
+ usec_fudge = 0;
+
+ timevalsub(&t2, &clk, &prev_clk);
+ if (t2.tv_sec < 0
+ || t2.tv_sec > wtime.tv_sec + 5) {
+ /* Deal with time changes before other
+ * housekeeping to keep everything straight.
+ */
+ dt = t2.tv_sec;
+ if (dt > 0)
+ dt -= wtime.tv_sec;
+ trace_act("time changed by %d sec", (int)dt);
+ epoch.tv_sec += dt;
+ }
+ }
+ timevalsub(&now, &clk, &epoch);
+ now_stale = now.tv_sec - STALE_TIME;
+ now_expire = now.tv_sec - EXPIRE_TIME;
+ now_garbage = now.tv_sec - GARBAGE_TIME;
+
+ /* deal with signals that should affect tracing */
+ set_tracelevel();
+
+ if (stopint != 0) {
+ rip_bcast(0);
+ rdisc_adv();
+ trace_off("exiting with signal %d", stopint);
+ exit(stopint | 128);
+ }
+
+ /* look for new or dead interfaces */
+ timevalsub(&wtime, &ifinit_timer, &now);
+ if (wtime.tv_sec <= 0) {
+ wtime.tv_sec = 0;
+ ifinit();
+ rip_query();
+ continue;
+ }
+
+ /* Check the kernel table occasionally for mysteriously
+ * evaporated routes
+ */
+ timevalsub(&t2, &flush_kern_timer, &now);
+ if (t2.tv_sec <= 0) {
+ flush_kern();
+ flush_kern_timer.tv_sec = (now.tv_sec
+ + CHECK_QUIET_INTERVAL);
+ continue;
+ }
+ if (timercmp(&t2, &wtime, <))
+ wtime = t2;
+
+ /* If it is time, then broadcast our routes.
+ */
+ if (supplier || advertise_mhome) {
+ timevalsub(&t2, &next_bcast, &now);
+ if (t2.tv_sec <= 0) {
+ /* Synchronize the aging and broadcast
+ * timers to minimize awakenings
+ */
+ age(0);
+
+ rip_bcast(0);
+
+ /* It is desirable to send routing updates
+ * regularly. So schedule the next update
+ * 30 seconds after the previous one was
+ * scheduled, instead of 30 seconds after
+ * the previous update was finished.
+ * Even if we just started after discovering
+ * a 2nd interface or were otherwise delayed,
+ * pick a 30-second anniversary of the
+ * original broadcast time.
+ */
+ n = 1 + (0-t2.tv_sec)/SUPPLY_INTERVAL;
+ next_bcast.tv_sec += n*SUPPLY_INTERVAL;
+
+ continue;
+ }
+
+ if (timercmp(&t2, &wtime, <))
+ wtime = t2;
+ }
+
+ /* If we need a flash update, either do it now or
+ * set the delay to end when it is time.
+ *
+ * If we are within MIN_WAITTIME seconds of a full update,
+ * do not bother.
+ */
+ if (need_flash
+ && supplier
+ && no_flash.tv_sec+MIN_WAITTIME < next_bcast.tv_sec) {
+ /* accurate to the millisecond */
+ if (!timercmp(&no_flash, &now, >))
+ rip_bcast(1);
+ timevalsub(&t2, &no_flash, &now);
+ if (timercmp(&t2, &wtime, <))
+ wtime = t2;
+ }
+
+ /* trigger the main aging timer.
+ */
+ timevalsub(&t2, &age_timer, &now);
+ if (t2.tv_sec <= 0) {
+ age(0);
+ continue;
+ }
+ if (timercmp(&t2, &wtime, <))
+ wtime = t2;
+
+ /* update the kernel routing table
+ */
+ timevalsub(&t2, &need_kern, &now);
+ if (t2.tv_sec <= 0) {
+ age(0);
+ continue;
+ }
+ if (timercmp(&t2, &wtime, <))
+ wtime = t2;
+
+ /* take care of router discovery,
+ * but do it in the correct the millisecond
+ */
+ if (!timercmp(&rdisc_timer, &now, >)) {
+ rdisc_age(0);
+ continue;
+ }
+ timevalsub(&t2, &rdisc_timer, &now);
+ if (timercmp(&t2, &wtime, <))
+ wtime = t2;
+
+
+ /* wait for input or a timer to expire.
+ */
+ trace_flush();
+ ibits = fdbits;
+ n = select(sock_max, &ibits, 0, 0, &wtime);
+ if (n <= 0) {
+ if (n < 0 && errno != EINTR && errno != EAGAIN)
+ BADERR(1,"select");
+ continue;
+ }
+
+ if (FD_ISSET(rt_sock, &ibits)) {
+ read_rt();
+ n--;
+ }
+ if (rdisc_sock >= 0 && FD_ISSET(rdisc_sock, &ibits)) {
+ read_d();
+ n--;
+ }
+ if (rip_sock >= 0 && FD_ISSET(rip_sock, &ibits)) {
+ read_rip(rip_sock, 0);
+ n--;
+ }
+
+ LIST_FOREACH(ifp, &ifnet, int_list) {
+ if (n <= 0)
+ break;
+ if (ifp->int_rip_sock >= 0
+ && FD_ISSET(ifp->int_rip_sock, &ibits)) {
+ read_rip(ifp->int_rip_sock, ifp);
+ n--;
+ }
+ }
+ }
+}
+
+
+/* ARGSUSED */
+static void
+sigalrm(int s UNUSED)
+{
+ /* Historically, SIGALRM would cause the daemon to check for
+ * new and broken interfaces.
+ */
+ ifinit_timer.tv_sec = now.tv_sec;
+ trace_act("SIGALRM");
+}
+
+
+/* watch for fatal signals */
+static void
+sigterm(int sig)
+{
+ stopint = sig;
+ (void)signal(sig, SIG_DFL); /* catch it only once */
+}
+
+
+void
+fix_select(void)
+{
+ struct interface *ifp;
+
+
+ FD_ZERO(&fdbits);
+ sock_max = 0;
+
+ FD_SET(rt_sock, &fdbits);
+ if (sock_max <= rt_sock)
+ sock_max = rt_sock+1;
+ if (rip_sock >= 0) {
+ FD_SET(rip_sock, &fdbits);
+ if (sock_max <= rip_sock)
+ sock_max = rip_sock+1;
+ }
+ LIST_FOREACH(ifp, &ifnet, int_list) {
+ if (ifp->int_rip_sock >= 0) {
+ FD_SET(ifp->int_rip_sock, &fdbits);
+ if (sock_max <= ifp->int_rip_sock)
+ sock_max = ifp->int_rip_sock+1;
+ }
+ }
+ if (rdisc_sock >= 0) {
+ FD_SET(rdisc_sock, &fdbits);
+ if (sock_max <= rdisc_sock)
+ sock_max = rdisc_sock+1;
+ }
+}
+
+
+void
+fix_sock(int sock,
+ const char *name)
+{
+ int on;
+#define MIN_SOCKBUF (4*1024)
+ static int rbuf;
+
+ if (fcntl(sock, F_SETFL, O_NONBLOCK) == -1)
+ logbad(1, "fcntl(%s) O_NONBLOCK: %s",
+ name, strerror(errno));
+ on = 1;
+ if (setsockopt(sock, SOL_SOCKET,SO_BROADCAST, &on,sizeof(on)) < 0)
+ msglog("setsockopt(%s,SO_BROADCAST): %s",
+ name, strerror(errno));
+#ifdef USE_PASSIFNAME
+ on = 1;
+ if (setsockopt(sock, SOL_SOCKET, SO_PASSIFNAME, &on,sizeof(on)) < 0)
+ msglog("setsockopt(%s,SO_PASSIFNAME): %s",
+ name, strerror(errno));
+#endif
+
+ if (rbuf >= MIN_SOCKBUF) {
+ if (setsockopt(sock, SOL_SOCKET, SO_RCVBUF,
+ &rbuf, sizeof(rbuf)) < 0)
+ msglog("setsockopt(%s,SO_RCVBUF=%d): %s",
+ name, rbuf, strerror(errno));
+ } else {
+ for (rbuf = 60*1024; ; rbuf -= 4096) {
+ if (setsockopt(sock, SOL_SOCKET, SO_RCVBUF,
+ &rbuf, sizeof(rbuf)) == 0) {
+ trace_act("RCVBUF=%d", rbuf);
+ break;
+ }
+ if (rbuf < MIN_SOCKBUF) {
+ msglog("setsockopt(%s,SO_RCVBUF = %d): %s",
+ name, rbuf, strerror(errno));
+ break;
+ }
+ }
+ }
+}
+
+
+/* get a rip socket
+ */
+static int /* <0 or file descriptor */
+get_rip_sock(naddr addr,
+ int serious) /* 1=failure to bind is serious */
+{
+ struct sockaddr_in rsin;
+ unsigned char ttl;
+ int s;
+
+
+ if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
+ BADERR(1,"rip_sock = socket()");
+
+ memset(&rsin, 0, sizeof(rsin));
+#ifdef _HAVE_SIN_LEN
+ rsin.sin_len = sizeof(rsin);
+#endif
+ rsin.sin_family = AF_INET;
+ rsin.sin_port = htons(RIP_PORT);
+ rsin.sin_addr.s_addr = addr;
+ if (bind(s, (struct sockaddr *)&rsin, sizeof(rsin)) < 0) {
+ if (serious)
+ BADERR(errno != EADDRINUSE, "bind(rip_sock)");
+ return -1;
+ }
+ fix_sock(s,"rip_sock");
+
+ ttl = 1;
+ if (setsockopt(s, IPPROTO_IP, IP_MULTICAST_TTL,
+ &ttl, sizeof(ttl)) < 0)
+ DBGERR(1,"rip_sock setsockopt(IP_MULTICAST_TTL)");
+
+ return s;
+}
+
+
+/* turn off main RIP socket */
+void
+rip_off(void)
+{
+ struct interface *ifp;
+ naddr addr;
+
+
+ if (rip_sock >= 0 && !mhome) {
+ trace_act("turn off RIP");
+
+ (void)close(rip_sock);
+ rip_sock = -1;
+
+ /* get non-broadcast sockets to listen to queries.
+ */
+ LIST_FOREACH(ifp, &ifnet, int_list) {
+ if (ifp->int_state & IS_REMOTE)
+ continue;
+ if (ifp->int_rip_sock < 0) {
+ addr = ((ifp->int_if_flags & IFF_POINTOPOINT)
+ ? ifp->int_dstaddr
+ : ifp->int_addr);
+ ifp->int_rip_sock = get_rip_sock(addr, 0);
+ }
+ }
+
+ fix_select();
+
+ age(0);
+ }
+}
+
+
+/* turn on RIP multicast input via an interface
+ */
+static void
+rip_mcast_on(struct interface *ifp)
+{
+ struct group_req gr;
+ struct sockaddr_in *sin;
+
+ if (!IS_RIP_IN_OFF(ifp->int_state)
+ && (ifp->int_if_flags & IFF_MULTICAST)
+ && !(ifp->int_state & IS_ALIAS)) {
+ memset(&gr, 0, sizeof(gr));
+ gr.gr_interface = ifp->int_index;
+ sin = (struct sockaddr_in *)&gr.gr_group;
+ sin->sin_family = AF_INET;
+#ifdef _HAVE_SIN_LEN
+ sin->sin_len = sizeof(struct sockaddr_in);
+#endif
+ sin->sin_addr.s_addr = htonl(INADDR_RIP_GROUP);
+ if (setsockopt(rip_sock, IPPROTO_IP, MCAST_JOIN_GROUP,
+ &gr, sizeof(gr)) < 0)
+ LOGERR("setsockopt(MCAST_JOIN_GROUP RIP)");
+ }
+}
+
+
+/* Prepare socket used for RIP.
+ */
+void
+rip_on(struct interface *ifp)
+{
+ /* If the main RIP socket is already alive, only start receiving
+ * multicasts for this interface.
+ */
+ if (rip_sock >= 0) {
+ if (ifp != 0)
+ rip_mcast_on(ifp);
+ return;
+ }
+
+ /* If the main RIP socket is off and it makes sense to turn it on,
+ * then turn it on for all of the interfaces.
+ * It makes sense if either router discovery is off, or if
+ * router discover is on and at most one interface is doing RIP.
+ */
+ if (rip_interfaces > 0 && (!rdisc_ok || rip_interfaces > 1)) {
+ trace_act("turn on RIP");
+
+ /* Close all of the query sockets so that we can open
+ * the main socket. SO_REUSEPORT is not a solution,
+ * since that would let two daemons bind to the broadcast
+ * socket.
+ */
+ LIST_FOREACH(ifp, &ifnet, int_list) {
+ if (ifp->int_rip_sock >= 0) {
+ (void)close(ifp->int_rip_sock);
+ ifp->int_rip_sock = -1;
+ }
+ }
+
+ rip_sock = get_rip_sock(INADDR_ANY, 1);
+ rip_sock_mcast = 0;
+
+ /* Do not advertise anything until we have heard something
+ */
+ if (next_bcast.tv_sec < now.tv_sec+MIN_WAITTIME)
+ next_bcast.tv_sec = now.tv_sec+MIN_WAITTIME;
+
+ LIST_FOREACH(ifp, &ifnet, int_list) {
+ ifp->int_query_time = NEVER;
+ rip_mcast_on(ifp);
+ }
+ ifinit_timer.tv_sec = now.tv_sec;
+
+ } else if (ifp != 0
+ && !(ifp->int_state & IS_REMOTE)
+ && ifp->int_rip_sock < 0) {
+ /* RIP is off, so ensure there are sockets on which
+ * to listen for queries.
+ */
+ ifp->int_rip_sock = get_rip_sock(ifp->int_addr, 0);
+ }
+
+ fix_select();
+}
+
+
+/* die if malloc(3) fails
+ */
+void *
+rtmalloc(size_t size,
+ const char *msg)
+{
+ void *p = malloc(size);
+ if (p == 0)
+ logbad(1,"malloc(%lu) failed in %s", (u_long)size, msg);
+ return p;
+}
+
+
+/* get a random instant in an interval
+ */
+void
+intvl_random(struct timeval *tp, /* put value here */
+ u_long lo, /* value is after this second */
+ u_long hi) /* and before this */
+{
+ tp->tv_sec = (time_t)(hi == lo
+ ? lo
+ : (lo + random() % ((hi - lo))));
+ tp->tv_usec = random() % 1000000;
+}
+
+
+void
+timevaladd(struct timeval *t1,
+ struct timeval *t2)
+{
+
+ t1->tv_sec += t2->tv_sec;
+ if ((t1->tv_usec += t2->tv_usec) >= 1000000) {
+ t1->tv_sec++;
+ t1->tv_usec -= 1000000;
+ }
+}
+
+
+/* t1 = t2 - t3
+ */
+static void
+timevalsub(struct timeval *t1,
+ struct timeval *t2,
+ struct timeval *t3)
+{
+ t1->tv_sec = t2->tv_sec - t3->tv_sec;
+ if ((t1->tv_usec = t2->tv_usec - t3->tv_usec) < 0) {
+ t1->tv_sec--;
+ t1->tv_usec += 1000000;
+ }
+}
+
+
+/* put a message into the system log
+ */
+void
+msglog(const char *p, ...)
+{
+ va_list args;
+
+ trace_flush();
+
+ va_start(args, p);
+ vsyslog(LOG_ERR, p, args);
+ va_end(args);
+ if (ftrace != 0) {
+ if (ftrace == stdout)
+ (void)fputs("routed: ", ftrace);
+ va_start(args, p);
+ (void)vfprintf(ftrace, p, args);
+ va_end(args);
+ (void)fputc('\n', ftrace);
+ }
+}
+
+
+/* Put a message about a bad system into the system log if
+ * we have not complained about it recently.
+ *
+ * It is desirable to complain about all bad systems, but not too often.
+ * In the worst case, it is not practical to keep track of all bad systems.
+ * For example, there can be many systems with the wrong password.
+ */
+void
+msglim(struct msg_limit *lim, naddr addr, const char *p, ...)
+{
+ va_list args;
+ int i;
+ struct msg_sub *ms1, *ms;
+ const char *p1;
+
+ /* look for the oldest slot in the table
+ * or the slot for the bad router.
+ */
+ ms = ms1 = lim->subs;
+ for (i = MSG_SUBJECT_N; ; i--, ms1++) {
+ if (i == 0) {
+ /* Reuse a slot at most once every 10 minutes.
+ */
+ if (lim->reuse > now.tv_sec) {
+ ms = 0;
+ } else {
+ ms = ms1;
+ lim->reuse = now.tv_sec + 10*60;
+ }
+ break;
+ }
+ if (ms->addr == addr) {
+ /* Repeat a complaint about a given system at
+ * most once an hour.
+ */
+ if (ms->until > now.tv_sec)
+ ms = 0;
+ break;
+ }
+ if (ms->until < ms1->until)
+ ms = ms1;
+ }
+ if (ms != 0) {
+ ms->addr = addr;
+ ms->until = now.tv_sec + 60*60; /* 60 minutes */
+
+ trace_flush();
+ for (p1 = p; *p1 == ' '; p1++)
+ continue;
+ va_start(args, p);
+ vsyslog(LOG_ERR, p1, args);
+ va_end(args);
+ }
+
+ /* always display the message if tracing */
+ if (ftrace != 0) {
+ va_start(args, p);
+ (void)vfprintf(ftrace, p, args);
+ va_end(args);
+ (void)fputc('\n', ftrace);
+ }
+}
+
+
+void
+logbad(int dump, const char *p, ...)
+{
+ va_list args;
+
+ trace_flush();
+
+ va_start(args, p);
+ vsyslog(LOG_ERR, p, args);
+ va_end(args);
+ (void)fputs("routed: ", stderr);
+ va_start(args, p);
+ (void)vfprintf(stderr, p, args);
+ va_end(args);
+ (void)fputs("; giving up\n",stderr);
+ (void)fflush(stderr);
+
+ if (dump)
+ abort();
+ exit(1);
+}
diff --git a/sbin/routed/output.c b/sbin/routed/output.c
new file mode 100644
index 0000000..c2ed468
--- /dev/null
+++ b/sbin/routed/output.c
@@ -0,0 +1,974 @@
+/*
+ * Copyright (c) 1983, 1988, 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.
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+#include "defs.h"
+
+#ifdef __NetBSD__
+__RCSID("$NetBSD$");
+#elif defined(__FreeBSD__)
+__RCSID("$FreeBSD$");
+#else
+__RCSID("$Revision: 2.27 $");
+#ident "$Revision: 2.27 $"
+#endif
+
+
+u_int update_seqno;
+
+
+/* walk the tree of routes with this for output
+ */
+static struct {
+ struct sockaddr_in to;
+ naddr to_mask;
+ naddr to_net;
+ naddr to_std_mask;
+ naddr to_std_net;
+ struct interface *ifp; /* usually output interface */
+ struct auth *a;
+ char metric; /* adjust metrics by interface */
+ int npackets;
+ int gen_limit;
+ u_int state;
+#define WS_ST_FLASH 0x001 /* send only changed routes */
+#define WS_ST_RIP2_ALL 0x002 /* send full featured RIPv2 */
+#define WS_ST_AG 0x004 /* ok to aggregate subnets */
+#define WS_ST_SUPER_AG 0x008 /* ok to aggregate networks */
+#define WS_ST_QUERY 0x010 /* responding to a query */
+#define WS_ST_TO_ON_NET 0x020 /* sending onto one of our nets */
+#define WS_ST_DEFAULT 0x040 /* faking a default */
+} ws;
+
+/* A buffer for what can be heard by both RIPv1 and RIPv2 listeners */
+struct ws_buf v12buf;
+static union pkt_buf ripv12_buf;
+
+/* Another for only RIPv2 listeners */
+static struct ws_buf v2buf;
+static union pkt_buf rip_v2_buf;
+
+
+
+void
+bufinit(void)
+{
+ ripv12_buf.rip.rip_cmd = RIPCMD_RESPONSE;
+ v12buf.buf = &ripv12_buf.rip;
+ v12buf.base = &v12buf.buf->rip_nets[0];
+
+ rip_v2_buf.rip.rip_cmd = RIPCMD_RESPONSE;
+ rip_v2_buf.rip.rip_vers = RIPv2;
+ v2buf.buf = &rip_v2_buf.rip;
+ v2buf.base = &v2buf.buf->rip_nets[0];
+}
+
+
+/* Send the contents of the global buffer via the non-multicast socket
+ */
+int /* <0 on failure */
+output(enum output_type type,
+ struct sockaddr_in *dst, /* send to here */
+ struct interface *ifp,
+ struct rip *buf,
+ int size) /* this many bytes */
+{
+ struct sockaddr_in osin;
+ int flags;
+ const char *msg;
+ int res;
+ int soc;
+ int serrno;
+
+ assert(ifp != NULL);
+ osin = *dst;
+ if (osin.sin_port == 0)
+ osin.sin_port = htons(RIP_PORT);
+#ifdef _HAVE_SIN_LEN
+ if (osin.sin_len == 0)
+ osin.sin_len = sizeof(osin);
+#endif
+
+ soc = rip_sock;
+ flags = 0;
+
+ switch (type) {
+ case OUT_QUERY:
+ msg = "Answer Query";
+ if (soc < 0)
+ soc = ifp->int_rip_sock;
+ break;
+ case OUT_UNICAST:
+ msg = "Send";
+ if (soc < 0)
+ soc = ifp->int_rip_sock;
+ flags = MSG_DONTROUTE;
+ break;
+ case OUT_BROADCAST:
+ if (ifp->int_if_flags & IFF_POINTOPOINT) {
+ msg = "Send";
+ } else {
+ msg = "Send bcast";
+ }
+ flags = MSG_DONTROUTE;
+ break;
+ case OUT_MULTICAST:
+ if ((ifp->int_if_flags & (IFF_POINTOPOINT|IFF_MULTICAST)) ==
+ IFF_POINTOPOINT) {
+ msg = "Send pt-to-pt";
+ } else if (ifp->int_state & IS_DUP) {
+ trace_act("abort multicast output via %s"
+ " with duplicate address",
+ ifp->int_name);
+ return 0;
+ } else {
+ msg = "Send mcast";
+ if (rip_sock_mcast != ifp) {
+ struct ip_mreqn mreqn;
+
+ memset(&mreqn, 0, sizeof(struct ip_mreqn));
+ mreqn.imr_ifindex = ifp->int_index;
+ if (0 > setsockopt(rip_sock,
+ IPPROTO_IP,
+ IP_MULTICAST_IF,
+ &mreqn,
+ sizeof(mreqn))) {
+ serrno = errno;
+ LOGERR("setsockopt(rip_sock, "
+ "IP_MULTICAST_IF)");
+ errno = serrno;
+ ifp = 0;
+ return -1;
+ }
+ rip_sock_mcast = ifp;
+ }
+ osin.sin_addr.s_addr = htonl(INADDR_RIP_GROUP);
+ }
+ break;
+
+ case NO_OUT_MULTICAST:
+ case NO_OUT_RIPV2:
+ default:
+#ifdef DEBUG
+ abort();
+#endif
+ return -1;
+ }
+
+ trace_rip(msg, "to", &osin, ifp, buf, size);
+
+ res = sendto(soc, buf, size, flags,
+ (struct sockaddr *)&osin, sizeof(osin));
+ if (res < 0
+ && (ifp == 0 || !(ifp->int_state & IS_BROKE))) {
+ serrno = errno;
+ msglog("%s sendto(%s%s%s.%d): %s", msg,
+ ifp != 0 ? ifp->int_name : "",
+ ifp != 0 ? ", " : "",
+ inet_ntoa(osin.sin_addr),
+ ntohs(osin.sin_port),
+ strerror(errno));
+ errno = serrno;
+ }
+
+ return res;
+}
+
+
+/* Find the first key for a packet to send.
+ * Try for a key that is eligible and has not expired, but settle for
+ * the last key if they have all expired.
+ * If no key is ready yet, give up.
+ */
+struct auth *
+find_auth(struct interface *ifp)
+{
+ struct auth *ap, *res;
+ int i;
+
+
+ if (ifp == 0)
+ return 0;
+
+ res = 0;
+ ap = ifp->int_auth;
+ for (i = 0; i < MAX_AUTH_KEYS; i++, ap++) {
+ /* stop looking after the last key */
+ if (ap->type == RIP_AUTH_NONE)
+ break;
+
+ /* ignore keys that are not ready yet */
+ if ((u_long)ap->start > (u_long)clk.tv_sec)
+ continue;
+
+ if ((u_long)ap->end < (u_long)clk.tv_sec) {
+ /* note best expired password as a fall-back */
+ if (res == 0 || (u_long)ap->end > (u_long)res->end)
+ res = ap;
+ continue;
+ }
+
+ /* note key with the best future */
+ if (res == 0 || (u_long)res->end < (u_long)ap->end)
+ res = ap;
+ }
+ return res;
+}
+
+
+void
+clr_ws_buf(struct ws_buf *wb,
+ struct auth *ap)
+{
+ struct netauth *na;
+
+ wb->lim = wb->base + NETS_LEN;
+ wb->n = wb->base;
+ memset(wb->n, 0, NETS_LEN*sizeof(*wb->n));
+
+ /* (start to) install authentication if appropriate
+ */
+ if (ap == 0)
+ return;
+
+ na = (struct netauth*)wb->n;
+ if (ap->type == RIP_AUTH_PW) {
+ na->a_family = RIP_AF_AUTH;
+ na->a_type = RIP_AUTH_PW;
+ memcpy(na->au.au_pw, ap->key, sizeof(na->au.au_pw));
+ wb->n++;
+
+ } else if (ap->type == RIP_AUTH_MD5) {
+ na->a_family = RIP_AF_AUTH;
+ na->a_type = RIP_AUTH_MD5;
+ na->au.a_md5.md5_keyid = ap->keyid;
+ na->au.a_md5.md5_auth_len = RIP_AUTH_MD5_KEY_LEN;
+ na->au.a_md5.md5_seqno = htonl(clk.tv_sec);
+ wb->n++;
+ wb->lim--; /* make room for trailer */
+ }
+}
+
+
+void
+end_md5_auth(struct ws_buf *wb,
+ struct auth *ap)
+{
+ struct netauth *na, *na2;
+ MD5_CTX md5_ctx;
+ int len;
+
+
+ na = (struct netauth*)wb->base;
+ na2 = (struct netauth*)wb->n;
+ len = (char *)na2-(char *)wb->buf;
+ na2->a_family = RIP_AF_AUTH;
+ na2->a_type = htons(1);
+ na->au.a_md5.md5_pkt_len = htons(len);
+ MD5Init(&md5_ctx);
+ MD5Update(&md5_ctx, (u_char *)wb->buf, len + RIP_AUTH_MD5_HASH_XTRA);
+ MD5Update(&md5_ctx, ap->key, RIP_AUTH_MD5_KEY_LEN);
+ MD5Final(na2->au.au_pw, &md5_ctx);
+ wb->n++;
+}
+
+
+/* Send the buffer
+ */
+static void
+supply_write(struct ws_buf *wb)
+{
+ /* Output multicast only if legal.
+ * If we would multicast and it would be illegal, then discard the
+ * packet.
+ */
+ switch (wb->type) {
+ case NO_OUT_MULTICAST:
+ trace_pkt("skip multicast to %s because impossible",
+ naddr_ntoa(ws.to.sin_addr.s_addr));
+ break;
+ case NO_OUT_RIPV2:
+ break;
+ default:
+ if (ws.a != 0 && ws.a->type == RIP_AUTH_MD5)
+ end_md5_auth(wb,ws.a);
+ if (output(wb->type, &ws.to, ws.ifp, wb->buf,
+ ((char *)wb->n - (char*)wb->buf)) < 0
+ && ws.ifp != 0)
+ if_sick(ws.ifp);
+ ws.npackets++;
+ break;
+ }
+
+ clr_ws_buf(wb,ws.a);
+}
+
+
+/* put an entry into the packet
+ */
+static void
+supply_out(struct ag_info *ag)
+{
+ int i;
+ naddr mask, v1_mask, dst_h, ddst_h = 0;
+ struct ws_buf *wb;
+
+
+ /* Skip this route if doing a flash update and it and the routes
+ * it aggregates have not changed recently.
+ */
+ if (ag->ag_seqno < update_seqno
+ && (ws.state & WS_ST_FLASH))
+ return;
+
+ dst_h = ag->ag_dst_h;
+ mask = ag->ag_mask;
+ v1_mask = ripv1_mask_host(htonl(dst_h),
+ (ws.state & WS_ST_TO_ON_NET) ? ws.ifp : 0);
+ i = 0;
+
+ /* If we are sending RIPv2 packets that cannot (or must not) be
+ * heard by RIPv1 listeners, do not worry about sub- or supernets.
+ * Subnets (from other networks) can only be sent via multicast.
+ * A pair of subnet routes might have been promoted so that they
+ * are legal to send by RIPv1.
+ * If RIPv1 is off, use the multicast buffer.
+ */
+ if ((ws.state & WS_ST_RIP2_ALL)
+ || ((ag->ag_state & AGS_RIPV2) && v1_mask != mask)) {
+ /* use the RIPv2-only buffer */
+ wb = &v2buf;
+
+ } else {
+ /* use the RIPv1-or-RIPv2 buffer */
+ wb = &v12buf;
+
+ /* Convert supernet route into corresponding set of network
+ * routes for RIPv1, but leave non-contiguous netmasks
+ * to ag_check().
+ */
+ if (v1_mask > mask
+ && mask + (mask & -mask) == 0) {
+ ddst_h = v1_mask & -v1_mask;
+ i = (v1_mask & ~mask)/ddst_h;
+
+ if (i > ws.gen_limit) {
+ /* Punt if we would have to generate an
+ * unreasonable number of routes.
+ */
+ if (TRACECONTENTS)
+ trace_misc("sending %s-->%s as 1"
+ " instead of %d routes",
+ addrname(htonl(dst_h), mask,
+ 1),
+ naddr_ntoa(ws.to.sin_addr
+ .s_addr),
+ i+1);
+ i = 0;
+
+ } else {
+ mask = v1_mask;
+ ws.gen_limit -= i;
+ }
+ }
+ }
+
+ do {
+ wb->n->n_family = RIP_AF_INET;
+ wb->n->n_dst = htonl(dst_h);
+ /* If the route is from router-discovery or we are
+ * shutting down, admit only a bad metric.
+ */
+ wb->n->n_metric = ((stopint || ag->ag_metric < 1)
+ ? HOPCNT_INFINITY
+ : ag->ag_metric);
+ wb->n->n_metric = htonl(wb->n->n_metric);
+ /* Any non-zero bits in the supposedly unused RIPv1 fields
+ * cause the old `routed` to ignore the route.
+ * That means the mask and so forth cannot be sent
+ * in the hybrid RIPv1/RIPv2 mode.
+ */
+ if (ws.state & WS_ST_RIP2_ALL) {
+ if (ag->ag_nhop != 0
+ && ((ws.state & WS_ST_QUERY)
+ || (ag->ag_nhop != ws.ifp->int_addr
+ && on_net(ag->ag_nhop,
+ ws.ifp->int_net,
+ ws.ifp->int_mask))))
+ wb->n->n_nhop = ag->ag_nhop;
+ wb->n->n_mask = htonl(mask);
+ wb->n->n_tag = ag->ag_tag;
+ }
+ dst_h += ddst_h;
+
+ if (++wb->n >= wb->lim)
+ supply_write(wb);
+ } while (i-- != 0);
+}
+
+
+/* supply one route from the table
+ */
+/* ARGSUSED */
+static int
+walk_supply(struct radix_node *rn,
+ struct walkarg *argp UNUSED)
+{
+#define RT ((struct rt_entry *)rn)
+ u_short ags;
+ char metric, pref;
+ naddr dst, nhop;
+ struct rt_spare *rts;
+ int i;
+
+
+ /* Do not advertise external remote interfaces or passive interfaces.
+ */
+ if ((RT->rt_state & RS_IF)
+ && RT->rt_ifp != 0
+ && (RT->rt_ifp->int_state & IS_PASSIVE)
+ && !(RT->rt_state & RS_MHOME))
+ return 0;
+
+ /* If being quiet about our ability to forward, then
+ * do not say anything unless responding to a query,
+ * except about our main interface.
+ */
+ if (!supplier && !(ws.state & WS_ST_QUERY)
+ && !(RT->rt_state & RS_MHOME))
+ return 0;
+
+ dst = RT->rt_dst;
+
+ /* do not collide with the fake default route */
+ if (dst == RIP_DEFAULT
+ && (ws.state & WS_ST_DEFAULT))
+ return 0;
+
+ if (RT->rt_state & RS_NET_SYN) {
+ if (RT->rt_state & RS_NET_INT) {
+ /* Do not send manual synthetic network routes
+ * into the subnet.
+ */
+ if (on_net(ws.to.sin_addr.s_addr,
+ ntohl(dst), RT->rt_mask))
+ return 0;
+
+ } else {
+ /* Do not send automatic synthetic network routes
+ * if they are not needed because no RIPv1 listeners
+ * can hear them.
+ */
+ if (ws.state & WS_ST_RIP2_ALL)
+ return 0;
+
+ /* Do not send automatic synthetic network routes to
+ * the real subnet.
+ */
+ if (on_net(ws.to.sin_addr.s_addr,
+ ntohl(dst), RT->rt_mask))
+ return 0;
+ }
+ nhop = 0;
+
+ } else {
+ /* Advertise the next hop if this is not a route for one
+ * of our interfaces and the next hop is on the same
+ * network as the target.
+ * The final determination is made by supply_out().
+ */
+ if (!(RT->rt_state & RS_IF)
+ && RT->rt_gate != myaddr
+ && RT->rt_gate != loopaddr)
+ nhop = RT->rt_gate;
+ else
+ nhop = 0;
+ }
+
+ metric = RT->rt_metric;
+ ags = 0;
+
+ if (RT->rt_state & RS_MHOME) {
+ /* retain host route of multi-homed servers */
+ ;
+
+ } else if (RT_ISHOST(RT)) {
+ /* We should always suppress (into existing network routes)
+ * the host routes for the local end of our point-to-point
+ * links.
+ * If we are suppressing host routes in general, then do so.
+ * Avoid advertising host routes onto their own network,
+ * where they should be handled by proxy-ARP.
+ */
+ if ((RT->rt_state & RS_LOCAL)
+ || ridhosts
+ || on_net(dst, ws.to_net, ws.to_mask))
+ ags |= AGS_SUPPRESS;
+
+ /* Aggregate stray host routes into network routes if allowed.
+ * We cannot aggregate host routes into small network routes
+ * without confusing RIPv1 listeners into thinking the
+ * network routes are host routes.
+ */
+ if ((ws.state & WS_ST_AG) && (ws.state & WS_ST_RIP2_ALL))
+ ags |= AGS_AGGREGATE;
+
+ } else {
+ /* Always suppress network routes into other, existing
+ * network routes
+ */
+ ags |= AGS_SUPPRESS;
+
+ /* Generate supernets if allowed.
+ * If we can be heard by RIPv1 systems, we will
+ * later convert back to ordinary nets.
+ * This unifies dealing with received supernets.
+ */
+ if ((ws.state & WS_ST_AG)
+ && ((RT->rt_state & RS_SUBNET)
+ || (ws.state & WS_ST_SUPER_AG)))
+ ags |= AGS_AGGREGATE;
+ }
+
+ /* Do not send RIPv1 advertisements of subnets to other
+ * networks. If possible, multicast them by RIPv2.
+ */
+ if ((RT->rt_state & RS_SUBNET)
+ && !(ws.state & WS_ST_RIP2_ALL)
+ && !on_net(dst, ws.to_std_net, ws.to_std_mask))
+ ags |= AGS_RIPV2 | AGS_AGGREGATE;
+
+
+ /* Do not send a route back to where it came from, except in
+ * response to a query. This is "split-horizon". That means not
+ * advertising back to the same network and so via the same interface.
+ *
+ * We want to suppress routes that might have been fragmented
+ * from this route by a RIPv1 router and sent back to us, and so we
+ * cannot forget this route here. Let the split-horizon route
+ * suppress the fragmented routes and then itself be forgotten.
+ *
+ * Include the routes for both ends of point-to-point interfaces
+ * among those suppressed by split-horizon, since the other side
+ * should knows them as well as we do.
+ *
+ * Notice spare routes with the same metric that we are about to
+ * advertise, to split the horizon on redundant, inactive paths.
+ *
+ * Do not suppress advertisements of interface-related addresses on
+ * non-point-to-point interfaces. This ensures that we have something
+ * to say every 30 seconds to help detect broken Ethernets or
+ * other interfaces where one packet every 30 seconds costs nothing.
+ */
+ if (ws.ifp != 0
+ && !(ws.state & WS_ST_QUERY)
+ && (ws.state & WS_ST_TO_ON_NET)
+ && (!(RT->rt_state & RS_IF)
+ || ws.ifp->int_if_flags & IFF_POINTOPOINT)) {
+ for (rts = RT->rt_spares, i = NUM_SPARES; i != 0; i--, rts++) {
+ if (rts->rts_metric > metric
+ || rts->rts_ifp != ws.ifp)
+ continue;
+
+ /* If we do not mark the route with AGS_SPLIT_HZ here,
+ * it will be poisoned-reverse, or advertised back
+ * toward its source with an infinite metric.
+ * If we have recently advertised the route with a
+ * better metric than we now have, then we should
+ * poison-reverse the route before suppressing it for
+ * split-horizon.
+ *
+ * In almost all cases, if there is no spare for the
+ * route then it is either old and dead or a brand
+ * new route. If it is brand new, there is no need
+ * for poison-reverse. If it is old and dead, it
+ * is already poisoned.
+ */
+ if (RT->rt_poison_time < now_expire
+ || RT->rt_poison_metric >= metric
+ || RT->rt_spares[1].rts_gate == 0) {
+ ags |= AGS_SPLIT_HZ;
+ ags &= ~AGS_SUPPRESS;
+ }
+ metric = HOPCNT_INFINITY;
+ break;
+ }
+ }
+
+ /* Keep track of the best metric with which the
+ * route has been advertised recently.
+ */
+ if (RT->rt_poison_metric >= metric
+ || RT->rt_poison_time < now_expire) {
+ RT->rt_poison_time = now.tv_sec;
+ RT->rt_poison_metric = metric;
+ }
+
+ /* Adjust the outgoing metric by the cost of the link.
+ * Avoid aggregation when a route is counting to infinity.
+ */
+ pref = RT->rt_poison_metric + ws.metric;
+ metric += ws.metric;
+
+ /* Do not advertise stable routes that will be ignored,
+ * unless we are answering a query.
+ * If the route recently was advertised with a metric that
+ * would have been less than infinity through this interface,
+ * we need to continue to advertise it in order to poison it.
+ */
+ if (metric >= HOPCNT_INFINITY) {
+ if (!(ws.state & WS_ST_QUERY)
+ && (pref >= HOPCNT_INFINITY
+ || RT->rt_poison_time < now_garbage))
+ return 0;
+
+ metric = HOPCNT_INFINITY;
+ }
+
+ ag_check(dst, RT->rt_mask, 0, nhop, metric, pref,
+ RT->rt_seqno, RT->rt_tag, ags, supply_out);
+ return 0;
+#undef RT
+}
+
+
+/* Supply dst with the contents of the routing tables.
+ * If this won't fit in one packet, chop it up into several.
+ */
+void
+supply(struct sockaddr_in *dst,
+ struct interface *ifp, /* output interface */
+ enum output_type type,
+ int flash, /* 1=flash update */
+ int vers, /* RIP version */
+ int passwd_ok) /* OK to include cleartext password */
+{
+ struct rt_entry *rt;
+ int def_metric;
+
+ ws.state = 0;
+ ws.gen_limit = 1024;
+
+ ws.to = *dst;
+ ws.to_std_mask = std_mask(ws.to.sin_addr.s_addr);
+ ws.to_std_net = ntohl(ws.to.sin_addr.s_addr) & ws.to_std_mask;
+
+ if (ifp != 0) {
+ ws.to_mask = ifp->int_mask;
+ ws.to_net = ifp->int_net;
+ if (on_net(ws.to.sin_addr.s_addr, ws.to_net, ws.to_mask))
+ ws.state |= WS_ST_TO_ON_NET;
+
+ } else {
+ ws.to_mask = ripv1_mask_net(ws.to.sin_addr.s_addr, 0);
+ ws.to_net = ntohl(ws.to.sin_addr.s_addr) & ws.to_mask;
+ rt = rtfind(dst->sin_addr.s_addr);
+ if (rt)
+ ifp = rt->rt_ifp;
+ }
+
+ ws.npackets = 0;
+ if (flash)
+ ws.state |= WS_ST_FLASH;
+
+ if ((ws.ifp = ifp) == 0) {
+ ws.metric = 1;
+ } else {
+ /* Adjust the advertised metric by the outgoing interface
+ * metric.
+ */
+ ws.metric = ifp->int_metric + 1 + ifp->int_adj_outmetric;
+ }
+
+ ripv12_buf.rip.rip_vers = vers;
+
+ switch (type) {
+ case OUT_MULTICAST:
+ if (ifp->int_if_flags & IFF_MULTICAST)
+ v2buf.type = OUT_MULTICAST;
+ else
+ v2buf.type = NO_OUT_MULTICAST;
+ v12buf.type = OUT_BROADCAST;
+ break;
+
+ case OUT_QUERY:
+ ws.state |= WS_ST_QUERY;
+ /* FALLTHROUGH */
+ case OUT_BROADCAST:
+ case OUT_UNICAST:
+ v2buf.type = (vers == RIPv2) ? type : NO_OUT_RIPV2;
+ v12buf.type = type;
+ break;
+
+ case NO_OUT_MULTICAST:
+ case NO_OUT_RIPV2:
+ break; /* no output */
+ }
+
+ if (vers == RIPv2) {
+ /* full RIPv2 only if cannot be heard by RIPv1 listeners */
+ if (type != OUT_BROADCAST)
+ ws.state |= WS_ST_RIP2_ALL;
+ if ((ws.state & WS_ST_QUERY)
+ || !(ws.state & WS_ST_TO_ON_NET)) {
+ ws.state |= (WS_ST_AG | WS_ST_SUPER_AG);
+ } else if (ifp == 0 || !(ifp->int_state & IS_NO_AG)) {
+ ws.state |= WS_ST_AG;
+ if (type != OUT_BROADCAST
+ && (ifp == 0
+ || !(ifp->int_state & IS_NO_SUPER_AG)))
+ ws.state |= WS_ST_SUPER_AG;
+ }
+ }
+
+ ws.a = (vers == RIPv2) ? find_auth(ifp) : 0;
+ if (!passwd_ok && ws.a != 0 && ws.a->type == RIP_AUTH_PW)
+ ws.a = 0;
+ clr_ws_buf(&v12buf,ws.a);
+ clr_ws_buf(&v2buf,ws.a);
+
+ /* Fake a default route if asked and if there is not already
+ * a better, real default route.
+ */
+ if (supplier && (def_metric = ifp->int_d_metric) != 0) {
+ if (0 == (rt = rtget(RIP_DEFAULT, 0))
+ || rt->rt_metric+ws.metric >= def_metric) {
+ ws.state |= WS_ST_DEFAULT;
+ ag_check(0, 0, 0, 0, def_metric, def_metric,
+ 0, 0, 0, supply_out);
+ } else {
+ def_metric = rt->rt_metric+ws.metric;
+ }
+
+ /* If both RIPv2 and the poor-man's router discovery
+ * kludge are on, arrange to advertise an extra
+ * default route via RIPv1.
+ */
+ if ((ws.state & WS_ST_RIP2_ALL)
+ && (ifp->int_state & IS_PM_RDISC)) {
+ ripv12_buf.rip.rip_vers = RIPv1;
+ v12buf.n->n_family = RIP_AF_INET;
+ v12buf.n->n_dst = htonl(RIP_DEFAULT);
+ v12buf.n->n_metric = htonl(def_metric);
+ v12buf.n++;
+ }
+ }
+
+ (void)rn_walktree(rhead, walk_supply, 0);
+ ag_flush(0,0,supply_out);
+
+ /* Flush the packet buffers, provided they are not empty and
+ * do not contain only the password.
+ */
+ if (v12buf.n != v12buf.base
+ && (v12buf.n > v12buf.base+1
+ || v12buf.base->n_family != RIP_AF_AUTH))
+ supply_write(&v12buf);
+ if (v2buf.n != v2buf.base
+ && (v2buf.n > v2buf.base+1
+ || v2buf.base->n_family != RIP_AF_AUTH))
+ supply_write(&v2buf);
+
+ /* If we sent nothing and this is an answer to a query, send
+ * an empty buffer.
+ */
+ if (ws.npackets == 0
+ && (ws.state & WS_ST_QUERY))
+ supply_write(&v12buf);
+}
+
+
+/* send all of the routing table or just do a flash update
+ */
+void
+rip_bcast(int flash)
+{
+#ifdef _HAVE_SIN_LEN
+ static struct sockaddr_in dst = {sizeof(dst), AF_INET, 0, {0}, {0}};
+#else
+ static struct sockaddr_in dst = {AF_INET};
+#endif
+ struct interface *ifp;
+ enum output_type type;
+ int vers;
+ struct timeval rtime;
+
+
+ need_flash = 0;
+ intvl_random(&rtime, MIN_WAITTIME, MAX_WAITTIME);
+ no_flash = rtime;
+ timevaladd(&no_flash, &now);
+
+ if (rip_sock < 0)
+ return;
+
+ trace_act("send %s and inhibit dynamic updates for %.3f sec",
+ flash ? "dynamic update" : "all routes",
+ rtime.tv_sec + ((float)rtime.tv_usec)/1000000.0);
+
+ LIST_FOREACH(ifp, &ifnet, int_list) {
+ /* Skip interfaces not doing RIP.
+ * Do try broken interfaces to see if they have healed.
+ */
+ if (IS_RIP_OUT_OFF(ifp->int_state))
+ continue;
+
+ /* skip turned off interfaces */
+ if (!iff_up(ifp->int_if_flags))
+ continue;
+
+ vers = (ifp->int_state & IS_NO_RIPV1_OUT) ? RIPv2 : RIPv1;
+
+ if (ifp->int_if_flags & IFF_BROADCAST) {
+ /* ordinary, hardware interface */
+ dst.sin_addr.s_addr = ifp->int_brdaddr;
+
+ if (vers == RIPv2
+ && !(ifp->int_state & IS_NO_RIP_MCAST)) {
+ type = OUT_MULTICAST;
+ } else {
+ type = OUT_BROADCAST;
+ }
+
+ } else if (ifp->int_if_flags & IFF_POINTOPOINT) {
+ /* point-to-point hardware interface */
+ dst.sin_addr.s_addr = ifp->int_dstaddr;
+ if (vers == RIPv2 &&
+ ifp->int_if_flags & IFF_MULTICAST &&
+ !(ifp->int_state & IS_NO_RIP_MCAST)) {
+ type = OUT_MULTICAST;
+ } else {
+ type = OUT_UNICAST;
+ }
+
+ } else if (ifp->int_state & IS_REMOTE) {
+ /* remote interface */
+ dst.sin_addr.s_addr = ifp->int_addr;
+ type = OUT_UNICAST;
+
+ } else {
+ /* ATM, HIPPI, etc. */
+ continue;
+ }
+
+ supply(&dst, ifp, type, flash, vers, 1);
+ }
+
+ update_seqno++; /* all routes are up to date */
+}
+
+
+/* Ask for routes
+ * Do it only once to an interface, and not even after the interface
+ * was broken and recovered.
+ */
+void
+rip_query(void)
+{
+#ifdef _HAVE_SIN_LEN
+ static struct sockaddr_in dst = {sizeof(dst), AF_INET, 0, {0}, {0}};
+#else
+ static struct sockaddr_in dst = {AF_INET};
+#endif
+ struct interface *ifp;
+ struct rip buf;
+ enum output_type type;
+
+
+ if (rip_sock < 0)
+ return;
+
+ memset(&buf, 0, sizeof(buf));
+
+ LIST_FOREACH(ifp, &ifnet, int_list) {
+ /* Skip interfaces those already queried.
+ * Do not ask via interfaces through which we don't
+ * accept input. Do not ask via interfaces that cannot
+ * send RIP packets.
+ * Do try broken interfaces to see if they have healed.
+ */
+ if (IS_RIP_IN_OFF(ifp->int_state)
+ || ifp->int_query_time != NEVER)
+ continue;
+
+ /* skip turned off interfaces */
+ if (!iff_up(ifp->int_if_flags))
+ continue;
+
+ buf.rip_vers = (ifp->int_state&IS_NO_RIPV1_OUT) ? RIPv2:RIPv1;
+ buf.rip_cmd = RIPCMD_REQUEST;
+ buf.rip_nets[0].n_family = RIP_AF_UNSPEC;
+ buf.rip_nets[0].n_metric = htonl(HOPCNT_INFINITY);
+
+ /* Send a RIPv1 query only if allowed and if we will
+ * listen to RIPv1 routers.
+ */
+ if ((ifp->int_state & IS_NO_RIPV1_OUT)
+ || (ifp->int_state & IS_NO_RIPV1_IN)) {
+ buf.rip_vers = RIPv2;
+ } else {
+ buf.rip_vers = RIPv1;
+ }
+
+ if (ifp->int_if_flags & IFF_BROADCAST) {
+ /* ordinary, hardware interface */
+ dst.sin_addr.s_addr = ifp->int_brdaddr;
+
+ /* Broadcast RIPv1 queries and RIPv2 queries
+ * when the hardware cannot multicast.
+ */
+ if (buf.rip_vers == RIPv2
+ && (ifp->int_if_flags & IFF_MULTICAST)
+ && !(ifp->int_state & IS_NO_RIP_MCAST)) {
+ type = OUT_MULTICAST;
+ } else {
+ type = OUT_BROADCAST;
+ }
+
+ } else if (ifp->int_if_flags & IFF_POINTOPOINT) {
+ /* point-to-point hardware interface */
+ dst.sin_addr.s_addr = ifp->int_dstaddr;
+ type = OUT_UNICAST;
+
+ } else if (ifp->int_state & IS_REMOTE) {
+ /* remote interface */
+ dst.sin_addr.s_addr = ifp->int_addr;
+ type = OUT_UNICAST;
+
+ } else {
+ /* ATM, HIPPI, etc. */
+ continue;
+ }
+
+ ifp->int_query_time = now.tv_sec+SUPPLY_INTERVAL;
+ if (output(type, &dst, ifp, &buf, sizeof(buf)) < 0)
+ if_sick(ifp);
+ }
+}
diff --git a/sbin/routed/parms.c b/sbin/routed/parms.c
new file mode 100644
index 0000000..d5313e0
--- /dev/null
+++ b/sbin/routed/parms.c
@@ -0,0 +1,1038 @@
+/*
+ * Copyright (c) 1983, 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.
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+#include "defs.h"
+#include "pathnames.h"
+#include <sys/stat.h>
+
+#ifdef __NetBSD__
+__RCSID("$NetBSD$");
+#elif defined(__FreeBSD__)
+__RCSID("$FreeBSD$");
+#else
+__RCSID("$Revision: 2.26 $");
+#ident "$Revision: 2.26 $"
+#endif
+
+
+static struct parm *parms;
+struct intnet *intnets;
+struct r1net *r1nets;
+struct tgate *tgates;
+
+
+/* use configured parameters
+ */
+void
+get_parms(struct interface *ifp)
+{
+ static int warned_auth_in, warned_auth_out;
+ struct parm *parmp;
+ int i, num_passwds = 0;
+
+ /* get all relevant parameters
+ */
+ for (parmp = parms; parmp != 0; parmp = parmp->parm_next) {
+ if (parmp->parm_name[0] == '\0'
+ || !strcmp(ifp->int_name, parmp->parm_name)
+ || (parmp->parm_name[0] == '\n'
+ && on_net(ifp->int_addr,
+ parmp->parm_net, parmp->parm_mask))) {
+
+ /* This group of parameters is relevant,
+ * so get its settings
+ */
+ ifp->int_state |= parmp->parm_int_state;
+ for (i = 0; i < MAX_AUTH_KEYS; i++) {
+ if (parmp->parm_auth[0].type == RIP_AUTH_NONE
+ || num_passwds >= MAX_AUTH_KEYS)
+ break;
+ memcpy(&ifp->int_auth[num_passwds++],
+ &parmp->parm_auth[i],
+ sizeof(ifp->int_auth[0]));
+ }
+ if (parmp->parm_rdisc_pref != 0)
+ ifp->int_rdisc_pref = parmp->parm_rdisc_pref;
+ if (parmp->parm_rdisc_int != 0)
+ ifp->int_rdisc_int = parmp->parm_rdisc_int;
+ if (parmp->parm_adj_inmetric != 0)
+ ifp->int_adj_inmetric = parmp->parm_adj_inmetric;
+ if (parmp->parm_adj_outmetric != 0)
+ ifp->int_adj_outmetric = parmp->parm_adj_outmetric;
+ }
+ }
+
+ /* Set general defaults.
+ *
+ * Default poor-man's router discovery to a metric that will
+ * be heard by old versions of `routed`. They ignored received
+ * routes with metric 15.
+ */
+ if ((ifp->int_state & IS_PM_RDISC)
+ && ifp->int_d_metric == 0)
+ ifp->int_d_metric = FAKE_METRIC;
+
+ if (ifp->int_rdisc_int == 0)
+ ifp->int_rdisc_int = DefMaxAdvertiseInterval;
+
+ if (!(ifp->int_if_flags & IFF_MULTICAST)
+ && !(ifp->int_state & IS_REMOTE))
+ ifp->int_state |= IS_BCAST_RDISC;
+
+ if (ifp->int_if_flags & IFF_POINTOPOINT) {
+ ifp->int_state |= IS_BCAST_RDISC;
+ /* By default, point-to-point links should be passive
+ * about router-discovery for the sake of demand-dialing.
+ */
+ if (0 == (ifp->int_state & GROUP_IS_SOL_OUT))
+ ifp->int_state |= IS_NO_SOL_OUT;
+ if (0 == (ifp->int_state & GROUP_IS_ADV_OUT))
+ ifp->int_state |= IS_NO_ADV_OUT;
+ }
+
+ if (0 != (ifp->int_state & (IS_PASSIVE | IS_REMOTE)))
+ ifp->int_state |= IS_NO_RDISC;
+ if (ifp->int_state & IS_PASSIVE)
+ ifp->int_state |= IS_NO_RIP;
+
+ if (!IS_RIP_IN_OFF(ifp->int_state)
+ && ifp->int_auth[0].type != RIP_AUTH_NONE
+ && !(ifp->int_state & IS_NO_RIPV1_IN)
+ && !warned_auth_in) {
+ msglog("Warning: RIPv1 input via %s"
+ " will be accepted without authentication",
+ ifp->int_name);
+ warned_auth_in = 1;
+ }
+ if (!IS_RIP_OUT_OFF(ifp->int_state)
+ && ifp->int_auth[0].type != RIP_AUTH_NONE
+ && !(ifp->int_state & IS_NO_RIPV1_OUT)) {
+ if (!warned_auth_out) {
+ msglog("Warning: RIPv1 output via %s"
+ " will be sent without authentication",
+ ifp->int_name);
+ warned_auth_out = 1;
+ }
+ }
+}
+
+
+/* Read a list of gateways from /etc/gateways and add them to our tables.
+ *
+ * This file contains a list of "remote" gateways. That is usually
+ * a gateway which we cannot immediately determine if it is present or
+ * not as we can do for those provided by directly connected hardware.
+ *
+ * If a gateway is marked "passive" in the file, then we assume it
+ * does not understand RIP and assume it is always present. Those
+ * not marked passive are treated as if they were directly connected
+ * and assumed to be broken if they do not send us advertisements.
+ * All remote interfaces are added to our list, and those not marked
+ * passive are sent routing updates.
+ *
+ * A passive interface can also be local, hardware interface exempt
+ * from RIP.
+ */
+void
+gwkludge(void)
+{
+ FILE *fp;
+ char *p, *lptr;
+ const char *cp;
+ char lbuf[200], net_host[5], dname[64+1+64+1];
+ char gname[GNAME_LEN+1], qual[9];
+ struct interface *ifp;
+ naddr dst, netmask, gate;
+ int metric, n, lnum;
+ struct stat sb;
+ u_int state;
+ const char *type;
+
+
+ fp = fopen(_PATH_GATEWAYS, "r");
+ if (fp == 0)
+ return;
+
+ if (0 > fstat(fileno(fp), &sb)) {
+ msglog("could not stat() "_PATH_GATEWAYS);
+ (void)fclose(fp);
+ return;
+ }
+
+ for (lnum = 1; ; lnum++) {
+ if (fgets(lbuf, sizeof(lbuf), fp) == NULL)
+ break;
+ lptr = lbuf;
+ while (*lptr == ' ')
+ lptr++;
+ p = lptr+strlen(lptr)-1;
+ while (*p == '\n'
+ || (*p == ' ' && (p == lptr+1 || *(p-1) != '\\')))
+ *p-- = '\0';
+ if (*lptr == '\0' /* ignore null and comment lines */
+ || *lptr == '#')
+ continue;
+
+ /* notice newfangled parameter lines
+ */
+ if (strncasecmp("net", lptr, 3)
+ && strncasecmp("host", lptr, 4)) {
+ cp = parse_parms(lptr,
+ (sb.st_uid == 0
+ && !(sb.st_mode&(S_IRWXG|S_IRWXO))));
+ if (cp != 0)
+ msglog("%s in line %d of "_PATH_GATEWAYS,
+ cp, lnum);
+ continue;
+ }
+
+/* {net | host} XX[/M] XX gateway XX metric DD [passive | external]\n */
+ qual[0] = '\0';
+ /* the '64' here must be GNAME_LEN */
+ n = sscanf(lptr, "%4s %129[^ \t] gateway"
+ " %64[^ / \t] metric %u %8s\n",
+ net_host, dname, gname, &metric, qual);
+ if (n != 4 && n != 5) {
+ msglog("bad "_PATH_GATEWAYS" entry \"%s\"; %d values",
+ lptr, n);
+ continue;
+ }
+ if (metric >= HOPCNT_INFINITY) {
+ msglog("bad metric in "_PATH_GATEWAYS" entry \"%s\"",
+ lptr);
+ continue;
+ }
+ if (!strcasecmp(net_host, "host")) {
+ if (!gethost(dname, &dst)) {
+ msglog("bad host \"%s\" in "_PATH_GATEWAYS
+ " entry \"%s\"", dname, lptr);
+ continue;
+ }
+ netmask = HOST_MASK;
+ } else if (!strcasecmp(net_host, "net")) {
+ if (!getnet(dname, &dst, &netmask)) {
+ msglog("bad net \"%s\" in "_PATH_GATEWAYS
+ " entry \"%s\"", dname, lptr);
+ continue;
+ }
+ if (dst == RIP_DEFAULT) {
+ msglog("bad net \"%s\" in "_PATH_GATEWAYS
+ " entry \"%s\"--cannot be default",
+ dname, lptr);
+ continue;
+ }
+ /* Turn network # into IP address. */
+ dst = htonl(dst);
+ } else {
+ msglog("bad \"%s\" in "_PATH_GATEWAYS
+ " entry \"%s\"", net_host, lptr);
+ continue;
+ }
+
+ if (!gethost(gname, &gate)) {
+ msglog("bad gateway \"%s\" in "_PATH_GATEWAYS
+ " entry \"%s\"", gname, lptr);
+ continue;
+ }
+
+ if (!strcasecmp(qual, type = "passive")) {
+ /* Passive entries are not placed in our tables,
+ * only the kernel's, so we don't copy all of the
+ * external routing information within a net.
+ * Internal machines should use the default
+ * route to a suitable gateway (like us).
+ */
+ state = IS_REMOTE | IS_PASSIVE;
+ if (metric == 0)
+ metric = 1;
+
+ } else if (!strcasecmp(qual, type = "external")) {
+ /* External entries are handled by other means
+ * such as EGP, and are placed only in the daemon
+ * tables to prevent overriding them with something
+ * else.
+ */
+ strcpy(qual,"external");
+ state = IS_REMOTE | IS_PASSIVE | IS_EXTERNAL;
+ if (metric == 0)
+ metric = 1;
+
+ } else if (!strcasecmp(qual, "active")
+ || qual[0] == '\0') {
+ if (metric != 0) {
+ /* Entries that are neither "passive" nor
+ * "external" are "remote" and must behave
+ * like physical interfaces. If they are not
+ * heard from regularly, they are deleted.
+ */
+ state = IS_REMOTE;
+ type = "remote";
+ } else {
+ /* "remote" entries with a metric of 0
+ * are aliases for our own interfaces
+ */
+ state = IS_REMOTE | IS_PASSIVE | IS_ALIAS;
+ type = "alias";
+ }
+
+ } else {
+ msglog("bad "_PATH_GATEWAYS" entry \"%s\";"
+ " unknown type %s", lptr, qual);
+ continue;
+ }
+
+ if (0 != (state & (IS_PASSIVE | IS_REMOTE)))
+ state |= IS_NO_RDISC;
+ if (state & IS_PASSIVE)
+ state |= IS_NO_RIP;
+
+ ifp = check_dup(gate,dst,netmask,state);
+ if (ifp != 0) {
+ msglog("duplicate "_PATH_GATEWAYS" entry \"%s\"",lptr);
+ continue;
+ }
+
+ ifp = (struct interface *)rtmalloc(sizeof(*ifp), "gwkludge()");
+ memset(ifp, 0, sizeof(*ifp));
+
+ ifp->int_state = state;
+ if (netmask == HOST_MASK)
+ ifp->int_if_flags = IFF_POINTOPOINT | IFF_UP;
+ else
+ ifp->int_if_flags = IFF_UP;
+ ifp->int_act_time = NEVER;
+ ifp->int_addr = gate;
+ ifp->int_dstaddr = dst;
+ ifp->int_mask = netmask;
+ ifp->int_ripv1_mask = netmask;
+ ifp->int_std_mask = std_mask(gate);
+ ifp->int_net = ntohl(dst);
+ ifp->int_std_net = ifp->int_net & ifp->int_std_mask;
+ ifp->int_std_addr = htonl(ifp->int_std_net);
+ ifp->int_metric = metric;
+ if (!(state & IS_EXTERNAL)
+ && ifp->int_mask != ifp->int_std_mask)
+ ifp->int_state |= IS_SUBNET;
+ (void)sprintf(ifp->int_name, "%s(%s)", type, gname);
+ ifp->int_index = -1;
+
+ if_link(ifp);
+ }
+
+ /* After all of the parameter lines have been read,
+ * apply them to any remote interfaces.
+ */
+ LIST_FOREACH(ifp, &ifnet, int_list) {
+ get_parms(ifp);
+
+ tot_interfaces++;
+ if (!IS_RIP_OFF(ifp->int_state))
+ rip_interfaces++;
+
+ trace_if("Add", ifp);
+ }
+
+ (void)fclose(fp);
+}
+
+
+/* like strtok(), but honoring backslash and not changing the source string
+ */
+static int /* 0=ok, -1=bad */
+parse_quote(char **linep, /* look here */
+ const char *delims, /* for these delimiters */
+ char *delimp, /* 0 or put found delimiter here */
+ char *buf, /* copy token to here */
+ int lim) /* at most this many bytes */
+{
+ char c = '\0', *pc;
+ const char *p;
+
+
+ pc = *linep;
+ if (*pc == '\0')
+ return -1;
+
+ while (lim != 0) {
+ c = *pc++;
+ if (c == '\0')
+ break;
+
+ if (c == '\\' && *pc != '\0') {
+ if ((c = *pc++) == 'n') {
+ c = '\n';
+ } else if (c == 'r') {
+ c = '\r';
+ } else if (c == 't') {
+ c = '\t';
+ } else if (c == 'b') {
+ c = '\b';
+ } else if (c >= '0' && c <= '7') {
+ c -= '0';
+ if (*pc >= '0' && *pc <= '7') {
+ c = (c<<3)+(*pc++ - '0');
+ if (*pc >= '0' && *pc <= '7')
+ c = (c<<3)+(*pc++ - '0');
+ }
+ }
+
+ } else {
+ for (p = delims; *p != '\0'; ++p) {
+ if (*p == c)
+ goto exit;
+ }
+ }
+
+ *buf++ = c;
+ --lim;
+ }
+exit:
+ if (lim == 0)
+ return -1;
+
+ *buf = '\0'; /* terminate copy of token */
+ if (delimp != 0)
+ *delimp = c; /* return delimiter */
+ *linep = pc-1; /* say where we ended */
+ return 0;
+}
+
+
+/* Parse password timestamp
+ */
+static char *
+parse_ts(time_t *tp,
+ char **valp,
+ char *val0,
+ char *delimp,
+ char *buf,
+ u_int bufsize)
+{
+ struct tm tm;
+ char *ptr;
+
+ if (0 > parse_quote(valp, "| ,\n\r", delimp,
+ buf,bufsize)
+ || buf[bufsize-1] != '\0'
+ || buf[bufsize-2] != '\0') {
+ sprintf(buf,"bad timestamp %.25s", val0);
+ return buf;
+ }
+ strcat(buf,"\n");
+ memset(&tm, 0, sizeof(tm));
+ ptr = strptime(buf, "%y/%m/%d@%H:%M\n", &tm);
+ if (ptr == NULL || *ptr != '\0') {
+ sprintf(buf,"bad timestamp %.25s", val0);
+ return buf;
+ }
+
+ if ((*tp = mktime(&tm)) == -1) {
+ sprintf(buf,"bad timestamp %.25s", val0);
+ return buf;
+ }
+
+ return 0;
+}
+
+
+/* Get a password, key ID, and expiration date in the format
+ * passwd|keyID|year/mon/day@hour:min|year/mon/day@hour:min
+ */
+static const char * /* 0 or error message */
+get_passwd(char *tgt,
+ char *val,
+ struct parm *parmp,
+ u_int16_t type,
+ int safe) /* 1=from secure file */
+{
+ static char buf[80];
+ char *val0, *p, delim;
+ struct auth k, *ap, *ap2;
+ int i;
+ u_long l;
+
+ assert(val != NULL);
+ if (!safe)
+ return "ignore unsafe password";
+
+ for (ap = parmp->parm_auth, i = 0;
+ ap->type != RIP_AUTH_NONE; i++, ap++) {
+ if (i >= MAX_AUTH_KEYS)
+ return "too many passwords";
+ }
+
+ memset(&k, 0, sizeof(k));
+ k.type = type;
+ k.end = -1-DAY;
+
+ val0 = val;
+ if (0 > parse_quote(&val, "| ,\n\r", &delim,
+ (char *)k.key, sizeof(k.key)))
+ return tgt;
+
+ if (delim != '|') {
+ if (type == RIP_AUTH_MD5)
+ return "missing Keyid";
+ } else {
+ val0 = ++val;
+ buf[sizeof(buf)-1] = '\0';
+ if (0 > parse_quote(&val, "| ,\n\r", &delim, buf,sizeof(buf))
+ || buf[sizeof(buf)-1] != '\0'
+ || (l = strtoul(buf,&p,0)) > 255
+ || *p != '\0') {
+ sprintf(buf,"bad KeyID \"%.20s\"", val0);
+ return buf;
+ }
+ for (ap2 = parmp->parm_auth; ap2 < ap; ap2++) {
+ if (ap2->keyid == l) {
+ sprintf(buf,"duplicate KeyID \"%.20s\"", val0);
+ return buf;
+ }
+ }
+ k.keyid = (int)l;
+
+ if (delim == '|') {
+ val0 = ++val;
+ if (0 != (p = parse_ts(&k.start,&val,val0,&delim,
+ buf,sizeof(buf))))
+ return p;
+ if (delim != '|')
+ return "missing second timestamp";
+ val0 = ++val;
+ if (0 != (p = parse_ts(&k.end,&val,val0,&delim,
+ buf,sizeof(buf))))
+ return p;
+ if ((u_long)k.start > (u_long)k.end) {
+ sprintf(buf,"out of order timestamp %.30s",
+ val0);
+ return buf;
+ }
+ }
+ }
+ if (delim != '\0')
+ return tgt;
+
+ memmove(ap, &k, sizeof(*ap));
+ return 0;
+}
+
+
+static const char *
+bad_str(const char *estr)
+{
+ static char buf[100+8];
+
+ sprintf(buf, "bad \"%.100s\"", estr);
+ return buf;
+}
+
+
+/* Parse a set of parameters for an interface.
+ */
+const char * /* 0 or error message */
+parse_parms(char *line,
+ int safe) /* 1=from secure file */
+{
+#define PARS(str) (!strcasecmp(tgt, str))
+#define PARSEQ(str) (!strncasecmp(tgt, str"=", sizeof(str)))
+#define CKF(g,b) {if (0 != (parm.parm_int_state & ((g) & ~(b)))) break; \
+ parm.parm_int_state |= (b);}
+ struct parm parm;
+ struct intnet *intnetp;
+ struct r1net *r1netp;
+ struct tgate *tg;
+ naddr addr, mask;
+ char delim, *val0 = 0, *tgt, *val, *p;
+ const char *msg;
+ char buf[BUFSIZ], buf2[BUFSIZ];
+ int i;
+
+
+ /* "subnet=x.y.z.u/mask[,metric]" must be alone on the line */
+ if (!strncasecmp(line, "subnet=", sizeof("subnet=")-1)
+ && *(val = &line[sizeof("subnet=")-1]) != '\0') {
+ if (0 > parse_quote(&val, ",", &delim, buf, sizeof(buf)))
+ return bad_str(line);
+ intnetp = (struct intnet*)rtmalloc(sizeof(*intnetp),
+ "parse_parms subnet");
+ intnetp->intnet_metric = 1;
+ if (delim == ',') {
+ intnetp->intnet_metric = (int)strtol(val+1,&p,0);
+ if (*p != '\0'
+ || intnetp->intnet_metric <= 0
+ || intnetp->intnet_metric >= HOPCNT_INFINITY)
+ return bad_str(line);
+ }
+ if (!getnet(buf, &intnetp->intnet_addr, &intnetp->intnet_mask)
+ || intnetp->intnet_mask == HOST_MASK
+ || intnetp->intnet_addr == RIP_DEFAULT) {
+ free(intnetp);
+ return bad_str(line);
+ }
+ intnetp->intnet_addr = htonl(intnetp->intnet_addr);
+ intnetp->intnet_next = intnets;
+ intnets = intnetp;
+ return 0;
+ }
+
+ /* "ripv1_mask=x.y.z.u/mask1,mask2" must be alone on the line.
+ * This requires that x.y.z.u/mask1 be considered a subnet of
+ * x.y.z.u/mask2, as if x.y.z.u/mask2 were a class-full network.
+ */
+ if (!strncasecmp(line, "ripv1_mask=", sizeof("ripv1_mask=")-1)
+ && *(val = &line[sizeof("ripv1_mask=")-1]) != '\0') {
+ if (0 > parse_quote(&val, ",", &delim, buf, sizeof(buf))
+ || delim == '\0')
+ return bad_str(line);
+ if ((i = (int)strtol(val+1, &p, 0)) <= 0
+ || i > 32 || *p != '\0')
+ return bad_str(line);
+ r1netp = (struct r1net *)rtmalloc(sizeof(*r1netp),
+ "parse_parms ripv1_mask");
+ r1netp->r1net_mask = HOST_MASK << (32-i);
+ if (!getnet(buf, &r1netp->r1net_net, &r1netp->r1net_match)
+ || r1netp->r1net_net == RIP_DEFAULT
+ || r1netp->r1net_mask > r1netp->r1net_match) {
+ free(r1netp);
+ return bad_str(line);
+ }
+ r1netp->r1net_next = r1nets;
+ r1nets = r1netp;
+ return 0;
+ }
+
+ memset(&parm, 0, sizeof(parm));
+
+ for (;;) {
+ tgt = line + strspn(line, " ,\n\r");
+ if (*tgt == '\0' || *tgt == '#')
+ break;
+ line = tgt+strcspn(tgt, "= #,\n\r");
+ delim = *line;
+ if (delim == '=') {
+ val0 = ++line;
+ if (0 > parse_quote(&line, " #,\n\r",&delim,
+ buf,sizeof(buf)))
+ return bad_str(tgt);
+ } else {
+ val0 = NULL;
+ }
+ if (delim != '\0') {
+ for (;;) {
+ *line = '\0';
+ if (delim == '#')
+ break;
+ ++line;
+ if (delim != ' '
+ || (delim = *line) != ' ')
+ break;
+ }
+ }
+
+ if (PARSEQ("if")) {
+ if (parm.parm_name[0] != '\0'
+ || strlen(buf) > IF_NAME_LEN)
+ return bad_str(tgt);
+ strcpy(parm.parm_name, buf);
+
+ } else if (PARSEQ("addr")) {
+ /* This is a bad idea, because the address based
+ * sets of parameters cannot be checked for
+ * consistency with the interface name parameters.
+ * The parm_net stuff is needed to allow several
+ * -F settings.
+ */
+ if (!getnet(val0, &addr, &mask)
+ || parm.parm_name[0] != '\0')
+ return bad_str(tgt);
+ parm.parm_net = addr;
+ parm.parm_mask = mask;
+ parm.parm_name[0] = '\n';
+
+ } else if (PARSEQ("passwd")) {
+ /* since cleartext passwords are so weak allow
+ * them anywhere
+ */
+ msg = get_passwd(tgt,val0,&parm,RIP_AUTH_PW,1);
+ if (msg) {
+ *val0 = '\0';
+ return bad_str(msg);
+ }
+
+ } else if (PARSEQ("md5_passwd")) {
+ msg = get_passwd(tgt,val0,&parm,RIP_AUTH_MD5,safe);
+ if (msg) {
+ *val0 = '\0';
+ return bad_str(msg);
+ }
+
+ } else if (PARS("no_ag")) {
+ parm.parm_int_state |= (IS_NO_AG | IS_NO_SUPER_AG);
+
+ } else if (PARS("no_super_ag")) {
+ parm.parm_int_state |= IS_NO_SUPER_AG;
+
+ } else if (PARS("no_rip_out")) {
+ parm.parm_int_state |= IS_NO_RIP_OUT;
+
+ } else if (PARS("no_ripv1_in")) {
+ parm.parm_int_state |= IS_NO_RIPV1_IN;
+
+ } else if (PARS("no_ripv2_in")) {
+ parm.parm_int_state |= IS_NO_RIPV2_IN;
+
+ } else if (PARS("ripv2_out")) {
+ if (parm.parm_int_state & IS_NO_RIPV2_OUT)
+ return bad_str(tgt);
+ parm.parm_int_state |= IS_NO_RIPV1_OUT;
+
+ } else if (PARS("ripv2")) {
+ if ((parm.parm_int_state & IS_NO_RIPV2_OUT)
+ || (parm.parm_int_state & IS_NO_RIPV2_IN))
+ return bad_str(tgt);
+ parm.parm_int_state |= (IS_NO_RIPV1_IN
+ | IS_NO_RIPV1_OUT);
+
+ } else if (PARS("no_rip")) {
+ CKF(IS_PM_RDISC, IS_NO_RIP);
+
+ } else if (PARS("no_rip_mcast")) {
+ parm.parm_int_state |= IS_NO_RIP_MCAST;
+
+ } else if (PARS("no_rdisc")) {
+ CKF((GROUP_IS_SOL_OUT|GROUP_IS_ADV_OUT), IS_NO_RDISC);
+
+ } else if (PARS("no_solicit")) {
+ CKF(GROUP_IS_SOL_OUT, IS_NO_SOL_OUT);
+
+ } else if (PARS("send_solicit")) {
+ CKF(GROUP_IS_SOL_OUT, IS_SOL_OUT);
+
+ } else if (PARS("no_rdisc_adv")) {
+ CKF(GROUP_IS_ADV_OUT, IS_NO_ADV_OUT);
+
+ } else if (PARS("rdisc_adv")) {
+ CKF(GROUP_IS_ADV_OUT, IS_ADV_OUT);
+
+ } else if (PARS("bcast_rdisc")) {
+ parm.parm_int_state |= IS_BCAST_RDISC;
+
+ } else if (PARS("passive")) {
+ CKF((GROUP_IS_SOL_OUT|GROUP_IS_ADV_OUT), IS_NO_RDISC);
+ parm.parm_int_state |= IS_NO_RIP | IS_PASSIVE;
+
+ } else if (PARSEQ("rdisc_pref")) {
+ if (parm.parm_rdisc_pref != 0
+ || (parm.parm_rdisc_pref = (int)strtol(buf,&p,0),
+ *p != '\0'))
+ return bad_str(tgt);
+
+ } else if (PARS("pm_rdisc")) {
+ if (IS_RIP_OUT_OFF(parm.parm_int_state))
+ return bad_str(tgt);
+ parm.parm_int_state |= IS_PM_RDISC;
+
+ } else if (PARSEQ("rdisc_interval")) {
+ if (parm.parm_rdisc_int != 0
+ || (parm.parm_rdisc_int = (int)strtoul(buf,&p,0),
+ *p != '\0')
+ || parm.parm_rdisc_int < MinMaxAdvertiseInterval
+ || parm.parm_rdisc_int > MaxMaxAdvertiseInterval)
+ return bad_str(tgt);
+
+ } else if (PARSEQ("fake_default")) {
+ if (parm.parm_d_metric != 0
+ || IS_RIP_OUT_OFF(parm.parm_int_state)
+ || (i = strtoul(buf,&p,0), *p != '\0')
+ || i > HOPCNT_INFINITY-1)
+ return bad_str(tgt);
+ parm.parm_d_metric = i;
+
+ } else if (PARSEQ("adj_inmetric")) {
+ if (parm.parm_adj_inmetric != 0
+ || (i = strtoul(buf,&p,0), *p != '\0')
+ || i > HOPCNT_INFINITY-1)
+ return bad_str(tgt);
+ parm.parm_adj_inmetric = i;
+
+ } else if (PARSEQ("adj_outmetric")) {
+ if (parm.parm_adj_outmetric != 0
+ || (i = strtoul(buf,&p,0), *p != '\0')
+ || i > HOPCNT_INFINITY-1)
+ return bad_str(tgt);
+ parm.parm_adj_outmetric = i;
+
+ } else if (PARSEQ("trust_gateway")) {
+ /* look for trust_gateway=x.y.z|net/mask|...) */
+ p = buf;
+ if (0 > parse_quote(&p, "|", &delim,
+ buf2, sizeof(buf2))
+ || !gethost(buf2,&addr))
+ return bad_str(tgt);
+ tg = (struct tgate *)rtmalloc(sizeof(*tg),
+ "parse_parms"
+ "trust_gateway");
+ memset(tg, 0, sizeof(*tg));
+ tg->tgate_addr = addr;
+ i = 0;
+ /* The default is to trust all routes. */
+ while (delim == '|') {
+ p++;
+ if (i >= MAX_TGATE_NETS
+ || 0 > parse_quote(&p, "|", &delim,
+ buf2, sizeof(buf2))
+ || !getnet(buf2, &tg->tgate_nets[i].net,
+ &tg->tgate_nets[i].mask)
+ || tg->tgate_nets[i].net == RIP_DEFAULT
+ || tg->tgate_nets[i].mask == 0)
+ return bad_str(tgt);
+ i++;
+ }
+ tg->tgate_next = tgates;
+ tgates = tg;
+ parm.parm_int_state |= IS_DISTRUST;
+
+ } else if (PARS("redirect_ok")) {
+ parm.parm_int_state |= IS_REDIRECT_OK;
+
+ } else {
+ return bad_str(tgt); /* error */
+ }
+ }
+
+ return check_parms(&parm);
+#undef PARS
+#undef PARSEQ
+}
+
+
+/* check for duplicate parameter specifications */
+const char * /* 0 or error message */
+check_parms(struct parm *new)
+{
+ struct parm *parmp, **parmpp;
+ int i, num_passwds;
+
+ /* set implicit values
+ */
+ if (new->parm_int_state & IS_NO_ADV_IN)
+ new->parm_int_state |= IS_NO_SOL_OUT;
+ if (new->parm_int_state & IS_NO_SOL_OUT)
+ new->parm_int_state |= IS_NO_ADV_IN;
+
+ for (i = num_passwds = 0; i < MAX_AUTH_KEYS; i++) {
+ if (new->parm_auth[i].type != RIP_AUTH_NONE)
+ num_passwds++;
+ }
+
+ /* compare with existing sets of parameters
+ */
+ for (parmpp = &parms;
+ (parmp = *parmpp) != 0;
+ parmpp = &parmp->parm_next) {
+ if (strcmp(new->parm_name, parmp->parm_name))
+ continue;
+ if (!on_net(htonl(parmp->parm_net),
+ new->parm_net, new->parm_mask)
+ && !on_net(htonl(new->parm_net),
+ parmp->parm_net, parmp->parm_mask))
+ continue;
+
+ for (i = 0; i < MAX_AUTH_KEYS; i++) {
+ if (parmp->parm_auth[i].type != RIP_AUTH_NONE)
+ num_passwds++;
+ }
+ if (num_passwds > MAX_AUTH_KEYS)
+ return "too many conflicting passwords";
+
+ if ((0 != (new->parm_int_state & GROUP_IS_SOL_OUT)
+ && 0 != (parmp->parm_int_state & GROUP_IS_SOL_OUT)
+ && 0 != ((new->parm_int_state ^ parmp->parm_int_state)
+ & GROUP_IS_SOL_OUT))
+ || (0 != (new->parm_int_state & GROUP_IS_ADV_OUT)
+ && 0 != (parmp->parm_int_state & GROUP_IS_ADV_OUT)
+ && 0 != ((new->parm_int_state ^ parmp->parm_int_state)
+ & GROUP_IS_ADV_OUT))
+ || (new->parm_rdisc_pref != 0
+ && parmp->parm_rdisc_pref != 0
+ && new->parm_rdisc_pref != parmp->parm_rdisc_pref)
+ || (new->parm_rdisc_int != 0
+ && parmp->parm_rdisc_int != 0
+ && new->parm_rdisc_int != parmp->parm_rdisc_int)) {
+ return ("conflicting, duplicate router discovery"
+ " parameters");
+
+ }
+
+ if (new->parm_d_metric != 0
+ && parmp->parm_d_metric != 0
+ && new->parm_d_metric != parmp->parm_d_metric) {
+ return ("conflicting, duplicate poor man's router"
+ " discovery or fake default metric");
+ }
+
+ if (new->parm_adj_inmetric != 0
+ && parmp->parm_adj_inmetric != 0
+ && new->parm_adj_inmetric != parmp->parm_adj_inmetric) {
+ return ("conflicting interface input "
+ "metric adjustments");
+ }
+
+ if (new->parm_adj_outmetric != 0
+ && parmp->parm_adj_outmetric != 0
+ && new->parm_adj_outmetric != parmp->parm_adj_outmetric) {
+ return ("conflicting interface output "
+ "metric adjustments");
+ }
+ }
+
+ /* link new entry on the list so that when the entries are scanned,
+ * they affect the result in the order the operator specified.
+ */
+ parmp = (struct parm*)rtmalloc(sizeof(*parmp), "check_parms");
+ memcpy(parmp, new, sizeof(*parmp));
+ *parmpp = parmp;
+
+ return 0;
+}
+
+
+/* get a network number as a name or a number, with an optional "/xx"
+ * netmask.
+ */
+int /* 0=bad */
+getnet(char *name,
+ naddr *netp, /* network in host byte order */
+ naddr *maskp) /* masks are always in host order */
+{
+ int i;
+ struct netent *np;
+ naddr mask; /* in host byte order */
+ struct in_addr in; /* a network and so host byte order */
+ char hname[MAXHOSTNAMELEN+1];
+ char *mname, *p;
+
+
+ /* Detect and separate "1.2.3.4/24"
+ */
+ if (0 != (mname = strrchr(name,'/'))) {
+ i = (int)(mname - name);
+ if (i > (int)sizeof(hname)-1) /* name too long */
+ return 0;
+ memmove(hname, name, i);
+ hname[i] = '\0';
+ mname++;
+ name = hname;
+ }
+
+ np = getnetbyname(name);
+ if (np != 0) {
+ in.s_addr = (naddr)np->n_net;
+ if (0 == (in.s_addr & 0xff000000))
+ in.s_addr <<= 8;
+ if (0 == (in.s_addr & 0xff000000))
+ in.s_addr <<= 8;
+ if (0 == (in.s_addr & 0xff000000))
+ in.s_addr <<= 8;
+ } else if (inet_aton(name, &in) == 1) {
+ in.s_addr = ntohl(in.s_addr);
+ } else if (!mname && !strcasecmp(name,"default")) {
+ in.s_addr = RIP_DEFAULT;
+ } else {
+ return 0;
+ }
+
+ if (!mname) {
+ /* we cannot use the interfaces here because we have not
+ * looked at them yet.
+ */
+ mask = std_mask(htonl(in.s_addr));
+ if ((~mask & in.s_addr) != 0)
+ mask = HOST_MASK;
+ } else {
+ mask = (naddr)strtoul(mname, &p, 0);
+ if (*p != '\0' || mask > 32)
+ return 0;
+ if (mask != 0)
+ mask = HOST_MASK << (32-mask);
+ }
+
+ /* must have mask of 0 with default */
+ if (mask != 0 && in.s_addr == RIP_DEFAULT)
+ return 0;
+ /* no host bits allowed in a network number */
+ if ((~mask & in.s_addr) != 0)
+ return 0;
+ /* require non-zero network number */
+ if ((mask & in.s_addr) == 0 && in.s_addr != RIP_DEFAULT)
+ return 0;
+ if (in.s_addr>>24 == 0 && in.s_addr != RIP_DEFAULT)
+ return 0;
+ if (in.s_addr>>24 == 0xff)
+ return 0;
+
+ *netp = in.s_addr;
+ *maskp = mask;
+ return 1;
+}
+
+
+int /* 0=bad */
+gethost(char *name,
+ naddr *addrp)
+{
+ struct hostent *hp;
+ struct in_addr in;
+
+
+ /* Try for a number first, even in IRIX where gethostbyname()
+ * is smart. This avoids hitting the name server which
+ * might be sick because routing is.
+ */
+ if (inet_aton(name, &in) == 1) {
+ /* get a good number, but check that it it makes some
+ * sense.
+ */
+ if (ntohl(in.s_addr)>>24 == 0
+ || ntohl(in.s_addr)>>24 == 0xff)
+ return 0;
+ *addrp = in.s_addr;
+ return 1;
+ }
+
+ hp = gethostbyname(name);
+ if (hp) {
+ memcpy(addrp, hp->h_addr, sizeof(*addrp));
+ return 1;
+ }
+
+ return 0;
+}
diff --git a/sbin/routed/pathnames.h b/sbin/routed/pathnames.h
new file mode 100644
index 0000000..c45405e
--- /dev/null
+++ b/sbin/routed/pathnames.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 1989, 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.
+ * 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.
+ *
+ * @(#)pathnames.h 8.1 (Berkeley) 6/5/93
+ *
+ * $FreeBSD$
+ */
+
+#include <paths.h>
+
+#define _PATH_GATEWAYS "/etc/gateways"
+
+/* All remotely requested trace files must either start with this prefix
+ * or be the same as the tracefile specified when the daemon was started.
+ * If this is a directory, routed will create log files in it. That
+ * might be a security problem. However, if bad guys can write in the
+ * default value, /etc, you have far worse security problems than anything
+ * this might do. In other words, it makes no sense to turn this off.
+ *
+ * Leave this undefined, and only the trace file originally specified
+ * when routed was started, if any, will be appended to.
+ */
+#ifndef __NetBSD__
+#define _PATH_TRACE "/etc/routed.trace"
+#else
+#undef _PATH_TRACE
+#endif
diff --git a/sbin/routed/radix.c b/sbin/routed/radix.c
new file mode 100644
index 0000000..5365e6d
--- /dev/null
+++ b/sbin/routed/radix.c
@@ -0,0 +1,897 @@
+/*
+ * Copyright (c) 1988, 1989, 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.
+ * 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.
+ *
+ * @(#)radix.c 8.4 (Berkeley) 11/2/94
+ *
+ * $FreeBSD$
+ */
+
+/*
+ * Routines to build and maintain radix trees for routing lookups.
+ */
+
+#include "defs.h"
+
+#ifdef __NetBSD__
+__RCSID("$NetBSD$");
+#elif defined(__FreeBSD__)
+__RCSID("$FreeBSD$");
+#else
+__RCSID("$Revision: 2.23 $");
+#ident "$Revision: 2.23 $"
+#endif
+
+#define log(x, msg) syslog(x, msg)
+#define panic(s) {log(LOG_ERR,s); exit(1);}
+#define min(a,b) (((a)<(b))?(a):(b))
+
+int max_keylen;
+static struct radix_mask *rn_mkfreelist;
+static struct radix_node_head *mask_rnhead;
+static char *addmask_key;
+static const uint8_t normal_chars[] =
+ { 0, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff};
+static char *rn_zeros, *rn_ones;
+
+#define rn_masktop (mask_rnhead->rnh_treetop)
+#define Bcmp(a, b, l) (l == 0 ? 0 \
+ : memcmp((caddr_t)(a), (caddr_t)(b), (size_t)l))
+
+static int rn_satisfies_leaf(char *, struct radix_node *, int);
+static struct radix_node *rn_addmask(void *n_arg, int search, int skip);
+static struct radix_node *rn_addroute(void *v_arg, void *n_arg,
+ struct radix_node_head *head, struct radix_node treenodes[2]);
+static struct radix_node *rn_match(void *v_arg, struct radix_node_head *head);
+
+/*
+ * The data structure for the keys is a radix tree with one way
+ * branching removed. The index rn_b at an internal node n represents a bit
+ * position to be tested. The tree is arranged so that all descendants
+ * of a node n have keys whose bits all agree up to position rn_b - 1.
+ * (We say the index of n is rn_b.)
+ *
+ * There is at least one descendant which has a one bit at position rn_b,
+ * and at least one with a zero there.
+ *
+ * A route is determined by a pair of key and mask. We require that the
+ * bit-wise logical and of the key and mask to be the key.
+ * We define the index of a route to associated with the mask to be
+ * the first bit number in the mask where 0 occurs (with bit number 0
+ * representing the highest order bit).
+ *
+ * We say a mask is normal if every bit is 0, past the index of the mask.
+ * If a node n has a descendant (k, m) with index(m) == index(n) == rn_b,
+ * and m is a normal mask, then the route applies to every descendant of n.
+ * If the index(m) < rn_b, this implies the trailing last few bits of k
+ * before bit b are all 0, (and hence consequently true of every descendant
+ * of n), so the route applies to all descendants of the node as well.
+ *
+ * Similar logic shows that a non-normal mask m such that
+ * index(m) <= index(n) could potentially apply to many children of n.
+ * Thus, for each non-host route, we attach its mask to a list at an internal
+ * node as high in the tree as we can go.
+ *
+ * The present version of the code makes use of normal routes in short-
+ * circuiting an explicit mask and compare operation when testing whether
+ * a key satisfies a normal route, and also in remembering the unique leaf
+ * that governs a subtree.
+ */
+
+static struct radix_node *
+rn_search(void *v_arg,
+ struct radix_node *head)
+{
+ struct radix_node *x;
+ caddr_t v;
+
+ for (x = head, v = v_arg; x->rn_b >= 0;) {
+ if (x->rn_bmask & v[x->rn_off])
+ x = x->rn_r;
+ else
+ x = x->rn_l;
+ }
+ return (x);
+}
+
+static struct radix_node *
+rn_search_m(void *v_arg,
+ struct radix_node *head,
+ void *m_arg)
+{
+ struct radix_node *x;
+ caddr_t v = v_arg, m = m_arg;
+
+ for (x = head; x->rn_b >= 0;) {
+ if ((x->rn_bmask & m[x->rn_off]) &&
+ (x->rn_bmask & v[x->rn_off]))
+ x = x->rn_r;
+ else
+ x = x->rn_l;
+ }
+ return x;
+}
+
+static int
+rn_refines(void* m_arg, void *n_arg)
+{
+ caddr_t m = m_arg, n = n_arg;
+ caddr_t lim, lim2 = lim = n + *(u_char *)n;
+ int longer = (*(u_char *)n++) - (int)(*(u_char *)m++);
+ int masks_are_equal = 1;
+
+ if (longer > 0)
+ lim -= longer;
+ while (n < lim) {
+ if (*n & ~(*m))
+ return 0;
+ if (*n++ != *m++)
+ masks_are_equal = 0;
+ }
+ while (n < lim2)
+ if (*n++)
+ return 0;
+ if (masks_are_equal && (longer < 0))
+ for (lim2 = m - longer; m < lim2; )
+ if (*m++)
+ return 1;
+ return (!masks_are_equal);
+}
+
+static struct radix_node *
+rn_lookup(void *v_arg, void *m_arg, struct radix_node_head *head)
+{
+ struct radix_node *x;
+ caddr_t netmask = 0;
+
+ if (m_arg) {
+ if ((x = rn_addmask(m_arg, 1, head->rnh_treetop->rn_off)) == 0)
+ return (0);
+ netmask = x->rn_key;
+ }
+ x = rn_match(v_arg, head);
+ if (x && netmask) {
+ while (x && x->rn_mask != netmask)
+ x = x->rn_dupedkey;
+ }
+ return x;
+}
+
+static int
+rn_satisfies_leaf(char *trial,
+ struct radix_node *leaf,
+ int skip)
+{
+ char *cp = trial, *cp2 = leaf->rn_key, *cp3 = leaf->rn_mask;
+ char *cplim;
+ int length = min(*(u_char *)cp, *(u_char *)cp2);
+
+ if (cp3 == 0)
+ cp3 = rn_ones;
+ else
+ length = min(length, *(u_char *)cp3);
+ cplim = cp + length; cp3 += skip; cp2 += skip;
+ for (cp += skip; cp < cplim; cp++, cp2++, cp3++)
+ if ((*cp ^ *cp2) & *cp3)
+ return 0;
+ return 1;
+}
+
+static struct radix_node *
+rn_match(void *v_arg,
+ struct radix_node_head *head)
+{
+ caddr_t v = v_arg;
+ struct radix_node *t = head->rnh_treetop, *x;
+ caddr_t cp = v, cp2;
+ caddr_t cplim;
+ struct radix_node *saved_t, *top = t;
+ int off = t->rn_off, vlen = *(u_char *)cp, matched_off;
+ int test, b, rn_b;
+
+ /*
+ * Open code rn_search(v, top) to avoid overhead of extra
+ * subroutine call.
+ */
+ for (; t->rn_b >= 0; ) {
+ if (t->rn_bmask & cp[t->rn_off])
+ t = t->rn_r;
+ else
+ t = t->rn_l;
+ }
+ /*
+ * See if we match exactly as a host destination
+ * or at least learn how many bits match, for normal mask finesse.
+ *
+ * It doesn't hurt us to limit how many bytes to check
+ * to the length of the mask, since if it matches we had a genuine
+ * match and the leaf we have is the most specific one anyway;
+ * if it didn't match with a shorter length it would fail
+ * with a long one. This wins big for class B&C netmasks which
+ * are probably the most common case...
+ */
+ if (t->rn_mask)
+ vlen = *(u_char *)t->rn_mask;
+ cp += off; cp2 = t->rn_key + off; cplim = v + vlen;
+ for (; cp < cplim; cp++, cp2++)
+ if (*cp != *cp2)
+ goto on1;
+ /*
+ * This extra grot is in case we are explicitly asked
+ * to look up the default. Ugh!
+ * Or 255.255.255.255
+ *
+ * In this case, we have a complete match of the key. Unless
+ * the node is one of the roots, we are finished.
+ * If it is the zeros root, then take what we have, preferring
+ * any real data.
+ * If it is the ones root, then pretend the target key was followed
+ * by a byte of zeros.
+ */
+ if (!(t->rn_flags & RNF_ROOT))
+ return t; /* not a root */
+ if (t->rn_dupedkey) {
+ t = t->rn_dupedkey;
+ return t; /* have some real data */
+ }
+ if (*(cp-1) == 0)
+ return t; /* not the ones root */
+ b = 0; /* fake a zero after 255.255.255.255 */
+ goto on2;
+on1:
+ test = (*cp ^ *cp2) & 0xff; /* find first bit that differs */
+ for (b = 7; (test >>= 1) > 0;)
+ b--;
+on2:
+ matched_off = cp - v;
+ b += matched_off << 3;
+ rn_b = -1 - b;
+ /*
+ * If there is a host route in a duped-key chain, it will be first.
+ */
+ if ((saved_t = t)->rn_mask == 0)
+ t = t->rn_dupedkey;
+ for (; t; t = t->rn_dupedkey) {
+ /*
+ * Even if we don't match exactly as a host,
+ * we may match if the leaf we wound up at is
+ * a route to a net.
+ */
+ if (t->rn_flags & RNF_NORMAL) {
+ if (rn_b <= t->rn_b)
+ return t;
+ } else if (rn_satisfies_leaf(v, t, matched_off)) {
+ return t;
+ }
+ }
+ t = saved_t;
+ /* start searching up the tree */
+ do {
+ struct radix_mask *m;
+ t = t->rn_p;
+ if ((m = t->rn_mklist)) {
+ /*
+ * If non-contiguous masks ever become important
+ * we can restore the masking and open coding of
+ * the search and satisfaction test and put the
+ * calculation of "off" back before the "do".
+ */
+ do {
+ if (m->rm_flags & RNF_NORMAL) {
+ if (rn_b <= m->rm_b)
+ return (m->rm_leaf);
+ } else {
+ off = min(t->rn_off, matched_off);
+ x = rn_search_m(v, t, m->rm_mask);
+ while (x && x->rn_mask != m->rm_mask)
+ x = x->rn_dupedkey;
+ if (x && rn_satisfies_leaf(v, x, off))
+ return x;
+ }
+ } while ((m = m->rm_mklist));
+ }
+ } while (t != top);
+ return 0;
+}
+
+#ifdef RN_DEBUG
+int rn_nodenum;
+struct radix_node *rn_clist;
+int rn_saveinfo;
+int rn_debug = 1;
+#endif
+
+static struct radix_node *
+rn_newpair(void *v, int b, struct radix_node nodes[2])
+{
+ struct radix_node *tt = nodes, *t = tt + 1;
+ t->rn_b = b; t->rn_bmask = 0x80 >> (b & 7);
+ t->rn_l = tt; t->rn_off = b >> 3;
+ tt->rn_b = -1; tt->rn_key = (caddr_t)v; tt->rn_p = t;
+ tt->rn_flags = t->rn_flags = RNF_ACTIVE;
+#ifdef RN_DEBUG
+ tt->rn_info = rn_nodenum++; t->rn_info = rn_nodenum++;
+ tt->rn_twin = t; tt->rn_ybro = rn_clist; rn_clist = tt;
+#endif
+ return t;
+}
+
+static struct radix_node *
+rn_insert(void* v_arg,
+ struct radix_node_head *head,
+ int *dupentry,
+ struct radix_node nodes[2])
+{
+ caddr_t v = v_arg;
+ struct radix_node *top = head->rnh_treetop;
+ int head_off = top->rn_off, vlen = (int)*((u_char *)v);
+ struct radix_node *t = rn_search(v_arg, top);
+ caddr_t cp = v + head_off;
+ int b;
+ struct radix_node *tt;
+
+ /*
+ * Find first bit at which v and t->rn_key differ
+ */
+ {
+ caddr_t cp2 = t->rn_key + head_off;
+ int cmp_res;
+ caddr_t cplim = v + vlen;
+
+ while (cp < cplim)
+ if (*cp2++ != *cp++)
+ goto on1;
+ /* handle adding 255.255.255.255 */
+ if (!(t->rn_flags & RNF_ROOT) || *(cp2-1) == 0) {
+ *dupentry = 1;
+ return t;
+ }
+on1:
+ *dupentry = 0;
+ cmp_res = (cp[-1] ^ cp2[-1]) & 0xff;
+ for (b = (cp - v) << 3; cmp_res; b--)
+ cmp_res >>= 1;
+ }
+ {
+ struct radix_node *p, *x = top;
+ cp = v;
+ do {
+ p = x;
+ if (cp[x->rn_off] & x->rn_bmask)
+ x = x->rn_r;
+ else x = x->rn_l;
+ } while ((unsigned)b > (unsigned)x->rn_b);
+#ifdef RN_DEBUG
+ if (rn_debug)
+ log(LOG_DEBUG, "rn_insert: Going In:\n"), traverse(p);
+#endif
+ t = rn_newpair(v_arg, b, nodes); tt = t->rn_l;
+ if ((cp[p->rn_off] & p->rn_bmask) == 0)
+ p->rn_l = t;
+ else
+ p->rn_r = t;
+ x->rn_p = t; t->rn_p = p; /* frees x, p as temp vars below */
+ if ((cp[t->rn_off] & t->rn_bmask) == 0) {
+ t->rn_r = x;
+ } else {
+ t->rn_r = tt; t->rn_l = x;
+ }
+#ifdef RN_DEBUG
+ if (rn_debug)
+ log(LOG_DEBUG, "rn_insert: Coming Out:\n"), traverse(p);
+#endif
+ }
+ return (tt);
+}
+
+static struct radix_node *
+rn_addmask(void *n_arg, int search, int skip)
+{
+ caddr_t netmask = (caddr_t)n_arg;
+ struct radix_node *x;
+ caddr_t cp, cplim;
+ int b = 0, mlen, j;
+ int maskduplicated, m0, isnormal;
+ struct radix_node *saved_x;
+ static int last_zeroed = 0;
+
+ if ((mlen = *(u_char *)netmask) > max_keylen)
+ mlen = max_keylen;
+ if (skip == 0)
+ skip = 1;
+ if (mlen <= skip)
+ return (mask_rnhead->rnh_nodes);
+ if (skip > 1)
+ Bcopy(rn_ones + 1, addmask_key + 1, skip - 1);
+ if ((m0 = mlen) > skip)
+ Bcopy(netmask + skip, addmask_key + skip, mlen - skip);
+ /*
+ * Trim trailing zeroes.
+ */
+ for (cp = addmask_key + mlen; (cp > addmask_key) && cp[-1] == 0;)
+ cp--;
+ mlen = cp - addmask_key;
+ if (mlen <= skip) {
+ if (m0 >= last_zeroed)
+ last_zeroed = mlen;
+ return (mask_rnhead->rnh_nodes);
+ }
+ if (m0 < last_zeroed)
+ Bzero(addmask_key + m0, last_zeroed - m0);
+ *addmask_key = last_zeroed = mlen;
+ x = rn_search(addmask_key, rn_masktop);
+ if (Bcmp(addmask_key, x->rn_key, mlen) != 0)
+ x = 0;
+ if (x || search)
+ return (x);
+ x = (struct radix_node *)rtmalloc(max_keylen + 2*sizeof(*x),
+ "rn_addmask");
+ saved_x = x;
+ Bzero(x, max_keylen + 2 * sizeof (*x));
+ netmask = cp = (caddr_t)(x + 2);
+ Bcopy(addmask_key, cp, mlen);
+ x = rn_insert(cp, mask_rnhead, &maskduplicated, x);
+ if (maskduplicated) {
+ log(LOG_ERR, "rn_addmask: mask impossibly already in tree");
+ Free(saved_x);
+ return (x);
+ }
+ /*
+ * Calculate index of mask, and check for normalcy.
+ */
+ cplim = netmask + mlen; isnormal = 1;
+ for (cp = netmask + skip; (cp < cplim) && *(u_char *)cp == 0xff;)
+ cp++;
+ if (cp != cplim) {
+ for (j = 0x80; (j & *cp) != 0; j >>= 1)
+ b++;
+ if (*cp != normal_chars[b] || cp != (cplim - 1))
+ isnormal = 0;
+ }
+ b += (cp - netmask) << 3;
+ x->rn_b = -1 - b;
+ if (isnormal)
+ x->rn_flags |= RNF_NORMAL;
+ return (x);
+}
+
+static int /* XXX: arbitrary ordering for non-contiguous masks */
+rn_lexobetter(void *m_arg, void *n_arg)
+{
+ u_char *mp = m_arg, *np = n_arg, *lim;
+
+ if (*mp > *np)
+ return 1; /* not really, but need to check longer one first */
+ if (*mp == *np)
+ for (lim = mp + *mp; mp < lim;)
+ if (*mp++ > *np++)
+ return 1;
+ return 0;
+}
+
+static struct radix_mask *
+rn_new_radix_mask(struct radix_node *tt,
+ struct radix_mask *next)
+{
+ struct radix_mask *m;
+
+ MKGet(m);
+ if (m == 0) {
+ log(LOG_ERR, "Mask for route not entered\n");
+ return (0);
+ }
+ Bzero(m, sizeof *m);
+ m->rm_b = tt->rn_b;
+ m->rm_flags = tt->rn_flags;
+ if (tt->rn_flags & RNF_NORMAL)
+ m->rm_leaf = tt;
+ else
+ m->rm_mask = tt->rn_mask;
+ m->rm_mklist = next;
+ tt->rn_mklist = m;
+ return m;
+}
+
+static struct radix_node *
+rn_addroute(void *v_arg,
+ void *n_arg,
+ struct radix_node_head *head,
+ struct radix_node treenodes[2])
+{
+ caddr_t v = (caddr_t)v_arg, netmask = (caddr_t)n_arg;
+ struct radix_node *t, *x = 0, *tt;
+ struct radix_node *saved_tt, *top = head->rnh_treetop;
+ short b = 0, b_leaf = 0;
+ int keyduplicated;
+ caddr_t mmask;
+ struct radix_mask *m, **mp;
+
+ /*
+ * In dealing with non-contiguous masks, there may be
+ * many different routes which have the same mask.
+ * We will find it useful to have a unique pointer to
+ * the mask to speed avoiding duplicate references at
+ * nodes and possibly save time in calculating indices.
+ */
+ if (netmask) {
+ if ((x = rn_addmask(netmask, 0, top->rn_off)) == 0)
+ return (0);
+ b_leaf = x->rn_b;
+ b = -1 - x->rn_b;
+ netmask = x->rn_key;
+ }
+ /*
+ * Deal with duplicated keys: attach node to previous instance
+ */
+ saved_tt = tt = rn_insert(v, head, &keyduplicated, treenodes);
+ if (keyduplicated) {
+ for (t = tt; tt; t = tt, tt = tt->rn_dupedkey) {
+ if (tt->rn_mask == netmask)
+ return (0);
+ if (netmask == 0 ||
+ (tt->rn_mask &&
+ ((b_leaf < tt->rn_b) || /* index(netmask) > node */
+ rn_refines(netmask, tt->rn_mask) ||
+ rn_lexobetter(netmask, tt->rn_mask))))
+ break;
+ }
+ /*
+ * If the mask is not duplicated, we wouldn't
+ * find it among possible duplicate key entries
+ * anyway, so the above test doesn't hurt.
+ *
+ * We sort the masks for a duplicated key the same way as
+ * in a masklist -- most specific to least specific.
+ * This may require the unfortunate nuisance of relocating
+ * the head of the list.
+ */
+ if (tt == saved_tt) {
+ struct radix_node *xx = x;
+ /* link in at head of list */
+ (tt = treenodes)->rn_dupedkey = t;
+ tt->rn_flags = t->rn_flags;
+ tt->rn_p = x = t->rn_p;
+ if (x->rn_l == t) x->rn_l = tt; else x->rn_r = tt;
+ saved_tt = tt; x = xx;
+ } else {
+ (tt = treenodes)->rn_dupedkey = t->rn_dupedkey;
+ t->rn_dupedkey = tt;
+ }
+#ifdef RN_DEBUG
+ t=tt+1; tt->rn_info = rn_nodenum++; t->rn_info = rn_nodenum++;
+ tt->rn_twin = t; tt->rn_ybro = rn_clist; rn_clist = tt;
+#endif
+ tt->rn_key = (caddr_t) v;
+ tt->rn_b = -1;
+ tt->rn_flags = RNF_ACTIVE;
+ }
+ /*
+ * Put mask in tree.
+ */
+ if (netmask) {
+ tt->rn_mask = netmask;
+ tt->rn_b = x->rn_b;
+ tt->rn_flags |= x->rn_flags & RNF_NORMAL;
+ }
+ t = saved_tt->rn_p;
+ if (keyduplicated)
+ goto on2;
+ b_leaf = -1 - t->rn_b;
+ if (t->rn_r == saved_tt) x = t->rn_l; else x = t->rn_r;
+ /* Promote general routes from below */
+ if (x->rn_b < 0) {
+ for (mp = &t->rn_mklist; x; x = x->rn_dupedkey)
+ if (x->rn_mask && (x->rn_b >= b_leaf) && x->rn_mklist == 0) {
+ if ((*mp = m = rn_new_radix_mask(x, 0)))
+ mp = &m->rm_mklist;
+ }
+ } else if (x->rn_mklist) {
+ /*
+ * Skip over masks whose index is > that of new node
+ */
+ for (mp = &x->rn_mklist; (m = *mp); mp = &m->rm_mklist)
+ if (m->rm_b >= b_leaf)
+ break;
+ t->rn_mklist = m; *mp = 0;
+ }
+on2:
+ /* Add new route to highest possible ancestor's list */
+ if ((netmask == 0) || (b > t->rn_b ))
+ return tt; /* can't lift at all */
+ b_leaf = tt->rn_b;
+ do {
+ x = t;
+ t = t->rn_p;
+ } while (b <= t->rn_b && x != top);
+ /*
+ * Search through routes associated with node to
+ * insert new route according to index.
+ * Need same criteria as when sorting dupedkeys to avoid
+ * double loop on deletion.
+ */
+ for (mp = &x->rn_mklist; (m = *mp); mp = &m->rm_mklist) {
+ if (m->rm_b < b_leaf)
+ continue;
+ if (m->rm_b > b_leaf)
+ break;
+ if (m->rm_flags & RNF_NORMAL) {
+ mmask = m->rm_leaf->rn_mask;
+ if (tt->rn_flags & RNF_NORMAL) {
+ log(LOG_ERR,
+ "Non-unique normal route, mask not entered");
+ return tt;
+ }
+ } else
+ mmask = m->rm_mask;
+ if (mmask == netmask) {
+ m->rm_refs++;
+ tt->rn_mklist = m;
+ return tt;
+ }
+ if (rn_refines(netmask, mmask) || rn_lexobetter(netmask, mmask))
+ break;
+ }
+ *mp = rn_new_radix_mask(tt, *mp);
+ return tt;
+}
+
+static struct radix_node *
+rn_delete(void *v_arg,
+ void *netmask_arg,
+ struct radix_node_head *head)
+{
+ struct radix_node *t, *p, *x, *tt;
+ struct radix_mask *m, *saved_m, **mp;
+ struct radix_node *dupedkey, *saved_tt, *top;
+ caddr_t v, netmask;
+ int b, head_off, vlen;
+
+ v = v_arg;
+ netmask = netmask_arg;
+ x = head->rnh_treetop;
+ tt = rn_search(v, x);
+ head_off = x->rn_off;
+ vlen = *(u_char *)v;
+ saved_tt = tt;
+ top = x;
+ if (tt == 0 ||
+ Bcmp(v + head_off, tt->rn_key + head_off, vlen - head_off))
+ return (0);
+ /*
+ * Delete our route from mask lists.
+ */
+ if (netmask) {
+ if ((x = rn_addmask(netmask, 1, head_off)) == 0)
+ return (0);
+ netmask = x->rn_key;
+ while (tt->rn_mask != netmask)
+ if ((tt = tt->rn_dupedkey) == 0)
+ return (0);
+ }
+ if (tt->rn_mask == 0 || (saved_m = m = tt->rn_mklist) == 0)
+ goto on1;
+ if (tt->rn_flags & RNF_NORMAL) {
+ if (m->rm_leaf != tt || m->rm_refs > 0) {
+ log(LOG_ERR, "rn_delete: inconsistent annotation\n");
+ return 0; /* dangling ref could cause disaster */
+ }
+ } else {
+ if (m->rm_mask != tt->rn_mask) {
+ log(LOG_ERR, "rn_delete: inconsistent annotation\n");
+ goto on1;
+ }
+ if (--m->rm_refs >= 0)
+ goto on1;
+ }
+ b = -1 - tt->rn_b;
+ t = saved_tt->rn_p;
+ if (b > t->rn_b)
+ goto on1; /* Wasn't lifted at all */
+ do {
+ x = t;
+ t = t->rn_p;
+ } while (b <= t->rn_b && x != top);
+ for (mp = &x->rn_mklist; (m = *mp); mp = &m->rm_mklist)
+ if (m == saved_m) {
+ *mp = m->rm_mklist;
+ MKFree(m);
+ break;
+ }
+ if (m == 0) {
+ log(LOG_ERR, "rn_delete: couldn't find our annotation\n");
+ if (tt->rn_flags & RNF_NORMAL)
+ return (0); /* Dangling ref to us */
+ }
+on1:
+ /*
+ * Eliminate us from tree
+ */
+ if (tt->rn_flags & RNF_ROOT)
+ return (0);
+#ifdef RN_DEBUG
+ /* Get us out of the creation list */
+ for (t = rn_clist; t && t->rn_ybro != tt; t = t->rn_ybro) {}
+ if (t) t->rn_ybro = tt->rn_ybro;
+#endif
+ t = tt->rn_p;
+ if ((dupedkey = saved_tt->rn_dupedkey)) {
+ if (tt == saved_tt) {
+ x = dupedkey; x->rn_p = t;
+ if (t->rn_l == tt) t->rn_l = x; else t->rn_r = x;
+ } else {
+ for (x = p = saved_tt; p && p->rn_dupedkey != tt;)
+ p = p->rn_dupedkey;
+ if (p) p->rn_dupedkey = tt->rn_dupedkey;
+ else log(LOG_ERR, "rn_delete: couldn't find us\n");
+ }
+ t = tt + 1;
+ if (t->rn_flags & RNF_ACTIVE) {
+#ifndef RN_DEBUG
+ *++x = *t; p = t->rn_p;
+#else
+ b = t->rn_info; *++x = *t; t->rn_info = b; p = t->rn_p;
+#endif
+ if (p->rn_l == t) p->rn_l = x; else p->rn_r = x;
+ x->rn_l->rn_p = x; x->rn_r->rn_p = x;
+ }
+ goto out;
+ }
+ if (t->rn_l == tt) x = t->rn_r; else x = t->rn_l;
+ p = t->rn_p;
+ if (p->rn_r == t) p->rn_r = x; else p->rn_l = x;
+ x->rn_p = p;
+ /*
+ * Demote routes attached to us.
+ */
+ if (t->rn_mklist) {
+ if (x->rn_b >= 0) {
+ for (mp = &x->rn_mklist; (m = *mp);)
+ mp = &m->rm_mklist;
+ *mp = t->rn_mklist;
+ } else {
+ /* If there are any key,mask pairs in a sibling
+ duped-key chain, some subset will appear sorted
+ in the same order attached to our mklist */
+ for (m = t->rn_mklist; m && x; x = x->rn_dupedkey)
+ if (m == x->rn_mklist) {
+ struct radix_mask *mm = m->rm_mklist;
+ x->rn_mklist = 0;
+ if (--(m->rm_refs) < 0)
+ MKFree(m);
+ m = mm;
+ }
+ if (m)
+ syslog(LOG_ERR, "%s 0x%lx at 0x%lx\n",
+ "rn_delete: Orphaned Mask",
+ (unsigned long)m,
+ (unsigned long)x);
+ }
+ }
+ /*
+ * We may be holding an active internal node in the tree.
+ */
+ x = tt + 1;
+ if (t != x) {
+#ifndef RN_DEBUG
+ *t = *x;
+#else
+ b = t->rn_info; *t = *x; t->rn_info = b;
+#endif
+ t->rn_l->rn_p = t; t->rn_r->rn_p = t;
+ p = x->rn_p;
+ if (p->rn_l == x) p->rn_l = t; else p->rn_r = t;
+ }
+out:
+ tt->rn_flags &= ~RNF_ACTIVE;
+ tt[1].rn_flags &= ~RNF_ACTIVE;
+ return (tt);
+}
+
+int
+rn_walktree(struct radix_node_head *h,
+ int (*f)(struct radix_node *, struct walkarg *),
+ struct walkarg *w)
+{
+ int error;
+ struct radix_node *base, *next;
+ struct radix_node *rn = h->rnh_treetop;
+ /*
+ * This gets complicated because we may delete the node
+ * while applying the function f to it, so we need to calculate
+ * the successor node in advance.
+ */
+ /* First time through node, go left */
+ while (rn->rn_b >= 0)
+ rn = rn->rn_l;
+ for (;;) {
+ base = rn;
+ /* If at right child go back up, otherwise, go right */
+ while (rn->rn_p->rn_r == rn && (rn->rn_flags & RNF_ROOT) == 0)
+ rn = rn->rn_p;
+ /* Find the next *leaf* since next node might vanish, too */
+ for (rn = rn->rn_p->rn_r; rn->rn_b >= 0;)
+ rn = rn->rn_l;
+ next = rn;
+ /* Process leaves */
+ while ((rn = base)) {
+ base = rn->rn_dupedkey;
+ if (!(rn->rn_flags & RNF_ROOT) && (error = (*f)(rn, w)))
+ return (error);
+ }
+ rn = next;
+ if (rn->rn_flags & RNF_ROOT)
+ return (0);
+ }
+ /* NOTREACHED */
+}
+
+int
+rn_inithead(struct radix_node_head **head, int off)
+{
+ struct radix_node_head *rnh;
+ struct radix_node *t, *tt, *ttt;
+ if (*head)
+ return (1);
+ rnh = (struct radix_node_head *)rtmalloc(sizeof(*rnh), "rn_inithead");
+ Bzero(rnh, sizeof (*rnh));
+ *head = rnh;
+ t = rn_newpair(rn_zeros, off, rnh->rnh_nodes);
+ ttt = rnh->rnh_nodes + 2;
+ t->rn_r = ttt;
+ t->rn_p = t;
+ tt = t->rn_l;
+ tt->rn_flags = t->rn_flags = RNF_ROOT | RNF_ACTIVE;
+ tt->rn_b = -1 - off;
+ *ttt = *tt;
+ ttt->rn_key = rn_ones;
+ rnh->rnh_addaddr = rn_addroute;
+ rnh->rnh_deladdr = rn_delete;
+ rnh->rnh_matchaddr = rn_match;
+ rnh->rnh_lookup = rn_lookup;
+ rnh->rnh_walktree = rn_walktree;
+ rnh->rnh_treetop = t;
+ return (1);
+}
+
+void
+rn_init(void)
+{
+ char *cp, *cplim;
+ if (max_keylen == 0) {
+ printf("rn_init: radix functions require max_keylen be set\n");
+ return;
+ }
+ rn_zeros = (char *)rtmalloc(3 * max_keylen, "rn_init");
+ Bzero(rn_zeros, 3 * max_keylen);
+ rn_ones = cp = rn_zeros + max_keylen;
+ addmask_key = cplim = rn_ones + max_keylen;
+ while (cp < cplim)
+ *cp++ = -1;
+ if (rn_inithead(&mask_rnhead, 0) == 0)
+ panic("rn_init 2");
+}
+
diff --git a/sbin/routed/radix.h b/sbin/routed/radix.h
new file mode 100644
index 0000000..5cc177e
--- /dev/null
+++ b/sbin/routed/radix.h
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 1988, 1989, 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.
+ * 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.
+ *
+ * @(#)radix.h 8.2 (Berkeley) 10/31/94
+ *
+ * $FreeBSD$
+ */
+
+#ifndef __RADIX_H_
+#define __RADIX_H_
+
+#include <sys/cdefs.h>
+struct walkarg;
+
+/*
+ * Radix search tree node layout.
+ */
+
+struct radix_node {
+ struct radix_mask *rn_mklist; /* list of masks contained in subtree */
+ struct radix_node *rn_p; /* parent */
+ short rn_b; /* bit offset; -1-index(netmask) */
+ char rn_bmask; /* node: mask for bit test*/
+ u_char rn_flags; /* enumerated next */
+#define RNF_NORMAL 1 /* leaf contains normal route */
+#define RNF_ROOT 2 /* leaf is root leaf for tree */
+#define RNF_ACTIVE 4 /* This node is alive (for rtfree) */
+ union {
+ struct { /* leaf only data: */
+ caddr_t rn_Key; /* object of search */
+ caddr_t rn_Mask; /* netmask, if present */
+ struct radix_node *rn_Dupedkey;
+ } rn_leaf;
+ struct { /* node only data: */
+ int rn_Off; /* where to start compare */
+ struct radix_node *rn_L;/* progeny */
+ struct radix_node *rn_R;/* progeny */
+ }rn_node;
+ } rn_u;
+#ifdef RN_DEBUG
+ int rn_info;
+ struct radix_node *rn_twin;
+ struct radix_node *rn_ybro;
+#endif
+};
+
+#define rn_dupedkey rn_u.rn_leaf.rn_Dupedkey
+#define rn_key rn_u.rn_leaf.rn_Key
+#define rn_mask rn_u.rn_leaf.rn_Mask
+#define rn_off rn_u.rn_node.rn_Off
+#define rn_l rn_u.rn_node.rn_L
+#define rn_r rn_u.rn_node.rn_R
+
+/*
+ * Annotations to tree concerning potential routes applying to subtrees.
+ */
+
+struct radix_mask {
+ short rm_b; /* bit offset; -1-index(netmask) */
+ char rm_unused; /* cf. rn_bmask */
+ u_char rm_flags; /* cf. rn_flags */
+ struct radix_mask *rm_mklist; /* more masks to try */
+ union {
+ caddr_t rmu_mask; /* the mask */
+ struct radix_node *rmu_leaf; /* for normal routes */
+ } rm_rmu;
+ int rm_refs; /* # of references to this struct */
+};
+
+#define rm_mask rm_rmu.rmu_mask
+#define rm_leaf rm_rmu.rmu_leaf /* extra field would make 32 bytes */
+
+#define MKGet(m) {\
+ if (rn_mkfreelist) {\
+ m = rn_mkfreelist; \
+ rn_mkfreelist = (m)->rm_mklist; \
+ } else \
+ m = (struct radix_mask *)rtmalloc(sizeof(*(m)), "MKGet"); }\
+
+#define MKFree(m) { (m)->rm_mklist = rn_mkfreelist; rn_mkfreelist = (m);}
+
+struct radix_node_head {
+ struct radix_node *rnh_treetop;
+ int rnh_addrsize; /* permit, but not require fixed keys */
+ int rnh_pktsize; /* permit, but not require fixed keys */
+ struct radix_node *(*rnh_addaddr) /* add based on sockaddr */
+ (void *v, void *mask,
+ struct radix_node_head *head, struct radix_node nodes[]);
+ struct radix_node *(*rnh_addpkt) /* add based on packet hdr */
+ (void *v, void *mask,
+ struct radix_node_head *head, struct radix_node nodes[]);
+ struct radix_node *(*rnh_deladdr) /* remove based on sockaddr */
+ (void *v, void *mask, struct radix_node_head *head);
+ struct radix_node *(*rnh_delpkt) /* remove based on packet hdr */
+ (void *v, void *mask, struct radix_node_head *head);
+ struct radix_node *(*rnh_matchaddr) /* locate based on sockaddr */
+ (void *v, struct radix_node_head *head);
+ struct radix_node *(*rnh_lookup) /* locate based on sockaddr */
+ (void *v, void *mask, struct radix_node_head *head);
+ struct radix_node *(*rnh_matchpkt) /* locate based on packet hdr */
+ (void *v, struct radix_node_head *head);
+ int (*rnh_walktree) /* traverse tree */
+ (struct radix_node_head *head,
+ int (*f)(struct radix_node *, struct walkarg *),
+ struct walkarg *w);
+ struct radix_node rnh_nodes[3]; /* empty tree for common case */
+};
+
+
+#define Bcopy(a, b, n) memmove(((void *)(b)), ((void *)(a)), (size_t)(n))
+#define Bzero(p, n) memset((void *)(p), 0, (size_t)(n));
+#define Free(p) free((void *)p);
+
+void rn_init(void);
+int rn_inithead(struct radix_node_head **head, int off);
+int rn_walktree(struct radix_node_head *,
+ int (*)(struct radix_node *, struct walkarg *),
+ struct walkarg *);
+
+#endif /* __RADIX_H_ */
diff --git a/sbin/routed/rdisc.c b/sbin/routed/rdisc.c
new file mode 100644
index 0000000..260f6e0
--- /dev/null
+++ b/sbin/routed/rdisc.c
@@ -0,0 +1,1062 @@
+/*
+ * Copyright (c) 1995
+ * 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.
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+#include "defs.h"
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <netinet/ip_icmp.h>
+
+#ifdef __NetBSD__
+__RCSID("$NetBSD$");
+#elif defined(__FreeBSD__)
+__RCSID("$FreeBSD$");
+#else
+__RCSID("$Revision: 2.27 $");
+#ident "$Revision: 2.27 $"
+#endif
+
+/* router advertisement ICMP packet */
+struct icmp_ad {
+ u_int8_t icmp_type; /* type of message */
+ u_int8_t icmp_code; /* type sub code */
+ u_int16_t icmp_cksum; /* ones complement cksum of struct */
+ u_int8_t icmp_ad_num; /* # of following router addresses */
+ u_int8_t icmp_ad_asize; /* 2--words in each advertisement */
+ u_int16_t icmp_ad_life; /* seconds of validity */
+ struct icmp_ad_info {
+ n_long icmp_ad_addr;
+ n_long icmp_ad_pref;
+ } icmp_ad_info[1];
+};
+
+/* router solicitation ICMP packet */
+struct icmp_so {
+ u_int8_t icmp_type; /* type of message */
+ u_int8_t icmp_code; /* type sub code */
+ u_int16_t icmp_cksum; /* ones complement cksum of struct */
+ n_long icmp_so_rsvd;
+};
+
+union ad_u {
+ struct icmp icmp;
+ struct icmp_ad ad;
+ struct icmp_so so;
+};
+
+
+int rdisc_sock = -1; /* router-discovery raw socket */
+static const struct interface *rdisc_sock_mcast; /* current multicast interface */
+
+struct timeval rdisc_timer;
+int rdisc_ok; /* using solicited route */
+
+
+#define MAX_ADS 16 /* at least one per interface */
+struct dr { /* accumulated advertisements */
+ struct interface *dr_ifp;
+ naddr dr_gate; /* gateway */
+ time_t dr_ts; /* when received */
+ time_t dr_life; /* lifetime in host byte order */
+ n_long dr_recv_pref; /* received but biased preference */
+ n_long dr_pref; /* preference adjusted by metric */
+};
+static const struct dr *cur_drp;
+static struct dr drs[MAX_ADS];
+
+/* convert between signed, balanced around zero,
+ * and unsigned zero-based preferences */
+#define SIGN_PREF(p) ((p) ^ MIN_PreferenceLevel)
+#define UNSIGN_PREF(p) SIGN_PREF(p)
+/* adjust unsigned preference by interface metric,
+ * without driving it to infinity */
+#define PREF(p, ifp) ((int)(p) <= ((ifp)->int_metric+(ifp)->int_adj_outmetric)\
+ ? ((p) != 0 ? 1 : 0) \
+ : (p) - ((ifp)->int_metric+(ifp)->int_adj_outmetric))
+
+static void rdisc_sort(void);
+
+
+/* dump an ICMP Router Discovery Advertisement Message
+ */
+static void
+trace_rdisc(const char *act,
+ naddr from,
+ naddr to,
+ struct interface *ifp,
+ union ad_u *p,
+ u_int len)
+{
+ int i;
+ n_long *wp, *lim;
+
+
+ if (!TRACEPACKETS || ftrace == 0)
+ return;
+
+ lastlog();
+
+ if (p->icmp.icmp_type == ICMP_ROUTERADVERT) {
+ (void)fprintf(ftrace, "%s Router Ad"
+ " from %s to %s via %s life=%d\n",
+ act, naddr_ntoa(from), naddr_ntoa(to),
+ ifp ? ifp->int_name : "?",
+ ntohs(p->ad.icmp_ad_life));
+ if (!TRACECONTENTS)
+ return;
+
+ wp = &p->ad.icmp_ad_info[0].icmp_ad_addr;
+ lim = &wp[(len - sizeof(p->ad)) / sizeof(*wp)];
+ for (i = 0; i < p->ad.icmp_ad_num && wp <= lim; i++) {
+ (void)fprintf(ftrace, "\t%s preference=%d",
+ naddr_ntoa(wp[0]), (int)ntohl(wp[1]));
+ wp += p->ad.icmp_ad_asize;
+ }
+ (void)fputc('\n',ftrace);
+
+ } else {
+ trace_act("%s Router Solic. from %s to %s via %s value=%#x",
+ act, naddr_ntoa(from), naddr_ntoa(to),
+ ifp ? ifp->int_name : "?",
+ (int)ntohl(p->so.icmp_so_rsvd));
+ }
+}
+
+/* prepare Router Discovery socket.
+ */
+static void
+get_rdisc_sock(void)
+{
+ if (rdisc_sock < 0) {
+ rdisc_sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
+ if (rdisc_sock < 0)
+ BADERR(1,"rdisc_sock = socket()");
+ fix_sock(rdisc_sock,"rdisc_sock");
+ fix_select();
+ }
+}
+
+
+/* Pick multicast group for router-discovery socket
+ */
+void
+set_rdisc_mg(struct interface *ifp,
+ int on) /* 0=turn it off */
+{
+ struct group_req gr;
+ struct sockaddr_in *sin;
+
+ assert(ifp != NULL);
+
+ if (rdisc_sock < 0) {
+ /* Create the raw socket so that we can hear at least
+ * broadcast router discovery packets.
+ */
+ if ((ifp->int_state & IS_NO_RDISC) == IS_NO_RDISC
+ || !on)
+ return;
+ get_rdisc_sock();
+ }
+
+ if (!(ifp->int_if_flags & IFF_MULTICAST)) {
+ ifp->int_state &= ~(IS_ALL_HOSTS | IS_ALL_ROUTERS);
+ return;
+ }
+
+ memset(&gr, 0, sizeof(gr));
+ gr.gr_interface = ifp->int_index;
+ sin = (struct sockaddr_in *)&gr.gr_group;
+ sin->sin_family = AF_INET;
+#ifdef _HAVE_SIN_LEN
+ sin->sin_len = sizeof(struct sockaddr_in);
+#endif
+
+ if (supplier
+ || (ifp->int_state & IS_NO_ADV_IN)
+ || !on) {
+ /* stop listening to advertisements
+ */
+ if (ifp->int_state & IS_ALL_HOSTS) {
+ sin->sin_addr.s_addr = htonl(INADDR_ALLHOSTS_GROUP);
+ if (setsockopt(rdisc_sock, IPPROTO_IP,
+ MCAST_LEAVE_GROUP,
+ &gr, sizeof(gr)) < 0)
+ LOGERR("MCAST_LEAVE_GROUP ALLHOSTS");
+ ifp->int_state &= ~IS_ALL_HOSTS;
+ }
+
+ } else if (!(ifp->int_state & IS_ALL_HOSTS)) {
+ /* start listening to advertisements
+ */
+ sin->sin_addr.s_addr = htonl(INADDR_ALLHOSTS_GROUP);
+ if (setsockopt(rdisc_sock, IPPROTO_IP, MCAST_JOIN_GROUP,
+ &gr, sizeof(gr)) < 0) {
+ LOGERR("MCAST_JOIN_GROUP ALLHOSTS");
+ } else {
+ ifp->int_state |= IS_ALL_HOSTS;
+ }
+ }
+
+ if (!supplier
+ || (ifp->int_state & IS_NO_ADV_OUT)
+ || !on) {
+ /* stop listening to solicitations
+ */
+ if (ifp->int_state & IS_ALL_ROUTERS) {
+ sin->sin_addr.s_addr = htonl(INADDR_ALLROUTERS_GROUP);
+ if (setsockopt(rdisc_sock, IPPROTO_IP,
+ MCAST_LEAVE_GROUP,
+ &gr, sizeof(gr)) < 0)
+ LOGERR("MCAST_LEAVE_GROUP ALLROUTERS");
+ ifp->int_state &= ~IS_ALL_ROUTERS;
+ }
+
+ } else if (!(ifp->int_state & IS_ALL_ROUTERS)) {
+ /* start hearing solicitations
+ */
+ sin->sin_addr.s_addr = htonl(INADDR_ALLROUTERS_GROUP);
+ if (setsockopt(rdisc_sock, IPPROTO_IP, MCAST_JOIN_GROUP,
+ &gr, sizeof(gr)) < 0) {
+ LOGERR("MCAST_JOIN_GROUP ALLROUTERS");
+ } else {
+ ifp->int_state |= IS_ALL_ROUTERS;
+ }
+ }
+}
+
+
+/* start supplying routes
+ */
+void
+set_supplier(void)
+{
+ struct interface *ifp;
+ struct dr *drp;
+
+ if (supplier_set)
+ return;
+
+ trace_act("start supplying routes");
+
+ /* Forget discovered routes.
+ */
+ for (drp = drs; drp < &drs[MAX_ADS]; drp++) {
+ drp->dr_recv_pref = 0;
+ drp->dr_life = 0;
+ }
+ rdisc_age(0);
+
+ supplier_set = 1;
+ supplier = 1;
+
+ /* Do not start advertising until we have heard some RIP routes */
+ LIM_SEC(rdisc_timer, now.tv_sec+MIN_WAITTIME);
+
+ /* Switch router discovery multicast groups from soliciting
+ * to advertising.
+ */
+ LIST_FOREACH(ifp, &ifnet, int_list) {
+ if (ifp->int_state & IS_BROKE)
+ continue;
+ ifp->int_rdisc_cnt = 0;
+ ifp->int_rdisc_timer.tv_usec = rdisc_timer.tv_usec;
+ ifp->int_rdisc_timer.tv_sec = now.tv_sec+MIN_WAITTIME;
+ set_rdisc_mg(ifp, 1);
+ }
+
+ /* get rid of any redirects */
+ del_redirects(0,0);
+}
+
+
+/* age discovered routes and find the best one
+ */
+void
+rdisc_age(naddr bad_gate)
+{
+ time_t sec;
+ struct dr *drp;
+
+
+ /* If only advertising, then do only that. */
+ if (supplier) {
+ /* If switching from client to server, get rid of old
+ * default routes.
+ */
+ if (cur_drp != 0)
+ rdisc_sort();
+ rdisc_adv();
+ return;
+ }
+
+ /* If we are being told about a bad router,
+ * then age the discovered default route, and if there is
+ * no alternative, solicit a replacement.
+ */
+ if (bad_gate != 0) {
+ /* Look for the bad discovered default route.
+ * Age it and note its interface.
+ */
+ for (drp = drs; drp < &drs[MAX_ADS]; drp++) {
+ if (drp->dr_ts == 0)
+ continue;
+
+ /* When we find the bad router, then age the route
+ * to at most SUPPLY_INTERVAL.
+ * This is contrary to RFC 1256, but defends against
+ * black holes.
+ */
+ if (drp->dr_gate == bad_gate) {
+ sec = (now.tv_sec - drp->dr_life
+ + SUPPLY_INTERVAL);
+ if (drp->dr_ts > sec) {
+ trace_act("age 0.0.0.0 --> %s via %s",
+ naddr_ntoa(drp->dr_gate),
+ drp->dr_ifp->int_name);
+ drp->dr_ts = sec;
+ }
+ break;
+ }
+ }
+ }
+
+ rdisc_sol();
+ rdisc_sort();
+
+ /* Delete old redirected routes to keep the kernel table small,
+ * and to prevent black holes. Check that the kernel table
+ * matches the daemon table (i.e. has the default route).
+ * But only if RIP is not running and we are not dealing with
+ * a bad gateway, since otherwise age() will be called.
+ */
+ if (rip_sock < 0 && bad_gate == 0)
+ age(0);
+}
+
+
+/* Zap all routes discovered via an interface that has gone bad
+ * This should only be called when !(ifp->int_state & IS_ALIAS)
+ */
+void
+if_bad_rdisc(struct interface *ifp)
+{
+ struct dr *drp;
+
+ for (drp = drs; drp < &drs[MAX_ADS]; drp++) {
+ if (drp->dr_ifp != ifp)
+ continue;
+ drp->dr_recv_pref = 0;
+ drp->dr_ts = 0;
+ drp->dr_life = 0;
+ }
+
+ /* make a note to re-solicit, turn RIP on or off, etc. */
+ rdisc_timer.tv_sec = 0;
+}
+
+
+/* mark an interface ok for router discovering.
+ */
+void
+if_ok_rdisc(struct interface *ifp)
+{
+ set_rdisc_mg(ifp, 1);
+
+ ifp->int_rdisc_cnt = 0;
+ ifp->int_rdisc_timer.tv_sec = now.tv_sec + (supplier
+ ? MIN_WAITTIME
+ : MAX_SOLICITATION_DELAY);
+ if (timercmp(&rdisc_timer, &ifp->int_rdisc_timer, >))
+ rdisc_timer = ifp->int_rdisc_timer;
+}
+
+
+/* get rid of a dead discovered router
+ */
+static void
+del_rdisc(struct dr *drp)
+{
+ struct interface *ifp;
+ naddr gate;
+ int i;
+
+
+ del_redirects(gate = drp->dr_gate, 0);
+ drp->dr_ts = 0;
+ drp->dr_life = 0;
+
+
+ /* Count the other discovered routes on the interface.
+ */
+ i = 0;
+ ifp = drp->dr_ifp;
+ for (drp = drs; drp < &drs[MAX_ADS]; drp++) {
+ if (drp->dr_ts != 0
+ && drp->dr_ifp == ifp)
+ i++;
+ }
+
+ /* If that was the last good discovered router on the interface,
+ * then solicit a new one.
+ * This is contrary to RFC 1256, but defends against black holes.
+ */
+ if (i != 0) {
+ trace_act("discovered router %s via %s"
+ " is bad--have %d remaining",
+ naddr_ntoa(gate), ifp->int_name, i);
+ } else if (ifp->int_rdisc_cnt >= MAX_SOLICITATIONS) {
+ trace_act("last discovered router %s via %s"
+ " is bad--re-solicit",
+ naddr_ntoa(gate), ifp->int_name);
+ ifp->int_rdisc_cnt = 0;
+ ifp->int_rdisc_timer.tv_sec = 0;
+ rdisc_sol();
+ } else {
+ trace_act("last discovered router %s via %s"
+ " is bad--wait to solicit",
+ naddr_ntoa(gate), ifp->int_name);
+ }
+}
+
+
+/* Find the best discovered route,
+ * and discard stale routers.
+ */
+static void
+rdisc_sort(void)
+{
+ struct dr *drp, *new_drp;
+ struct rt_entry *rt;
+ struct rt_spare new;
+ struct interface *ifp;
+ u_int new_st = 0;
+ n_long new_pref = 0;
+
+
+ /* Find the best discovered route.
+ */
+ new_drp = 0;
+ for (drp = drs; drp < &drs[MAX_ADS]; drp++) {
+ if (drp->dr_ts == 0)
+ continue;
+ ifp = drp->dr_ifp;
+
+ /* Get rid of expired discovered routers.
+ */
+ if (drp->dr_ts + drp->dr_life <= now.tv_sec) {
+ del_rdisc(drp);
+ continue;
+ }
+
+ LIM_SEC(rdisc_timer, drp->dr_ts+drp->dr_life+1);
+
+ /* Update preference with possibly changed interface
+ * metric.
+ */
+ drp->dr_pref = PREF(drp->dr_recv_pref, ifp);
+
+ /* Prefer the current route to prevent thrashing.
+ * Prefer shorter lifetimes to speed the detection of
+ * bad routers.
+ * Avoid sick interfaces.
+ */
+ if (new_drp == 0
+ || (!((new_st ^ drp->dr_ifp->int_state) & IS_SICK)
+ && (new_pref < drp->dr_pref
+ || (new_pref == drp->dr_pref
+ && (drp == cur_drp
+ || (new_drp != cur_drp
+ && new_drp->dr_life > drp->dr_life)))))
+ || ((new_st & IS_SICK)
+ && !(drp->dr_ifp->int_state & IS_SICK))) {
+ new_drp = drp;
+ new_st = drp->dr_ifp->int_state;
+ new_pref = drp->dr_pref;
+ }
+ }
+
+ /* switch to a better default route
+ */
+ if (new_drp != cur_drp) {
+ rt = rtget(RIP_DEFAULT, 0);
+
+ /* Stop using discovered routes if they are all bad
+ */
+ if (new_drp == 0) {
+ trace_act("turn off Router Discovery client");
+ rdisc_ok = 0;
+
+ if (rt != 0
+ && (rt->rt_state & RS_RDISC)) {
+ new = rt->rt_spares[0];
+ new.rts_metric = HOPCNT_INFINITY;
+ new.rts_time = now.tv_sec - GARBAGE_TIME;
+ rtchange(rt, rt->rt_state & ~RS_RDISC,
+ &new, 0);
+ rtswitch(rt, 0);
+ }
+
+ } else {
+ if (cur_drp == 0) {
+ trace_act("turn on Router Discovery client"
+ " using %s via %s",
+ naddr_ntoa(new_drp->dr_gate),
+ new_drp->dr_ifp->int_name);
+ rdisc_ok = 1;
+
+ } else {
+ trace_act("switch Router Discovery from"
+ " %s via %s to %s via %s",
+ naddr_ntoa(cur_drp->dr_gate),
+ cur_drp->dr_ifp->int_name,
+ naddr_ntoa(new_drp->dr_gate),
+ new_drp->dr_ifp->int_name);
+ }
+
+ memset(&new, 0, sizeof(new));
+ new.rts_ifp = new_drp->dr_ifp;
+ new.rts_gate = new_drp->dr_gate;
+ new.rts_router = new_drp->dr_gate;
+ new.rts_metric = HOPCNT_INFINITY-1;
+ new.rts_time = now.tv_sec;
+ if (rt != 0) {
+ rtchange(rt, rt->rt_state | RS_RDISC, &new, 0);
+ } else {
+ rtadd(RIP_DEFAULT, 0, RS_RDISC, &new);
+ }
+ }
+
+ cur_drp = new_drp;
+ }
+
+ /* turn RIP on or off */
+ if (!rdisc_ok || rip_interfaces > 1) {
+ rip_on(0);
+ } else {
+ rip_off();
+ }
+}
+
+
+/* handle a single address in an advertisement
+ */
+static void
+parse_ad(naddr from,
+ naddr gate,
+ n_long pref, /* signed and in network order */
+ u_short life, /* in host byte order */
+ struct interface *ifp)
+{
+ static struct msg_limit bad_gate;
+ struct dr *drp, *new_drp;
+
+
+ if (gate == RIP_DEFAULT
+ || !check_dst(gate)) {
+ msglim(&bad_gate, from,"router %s advertising bad gateway %s",
+ naddr_ntoa(from),
+ naddr_ntoa(gate));
+ return;
+ }
+
+ /* ignore pointers to ourself and routes via unreachable networks
+ */
+ if (ifwithaddr(gate, 1, 0) != 0) {
+ trace_pkt(" discard Router Discovery Ad pointing at us");
+ return;
+ }
+ if (!on_net(gate, ifp->int_net, ifp->int_mask)) {
+ trace_pkt(" discard Router Discovery Ad"
+ " toward unreachable net");
+ return;
+ }
+
+ /* Convert preference to an unsigned value
+ * and later bias it by the metric of the interface.
+ */
+ pref = UNSIGN_PREF(ntohl(pref));
+
+ if (pref == 0 || life < MinMaxAdvertiseInterval) {
+ pref = 0;
+ life = 0;
+ }
+
+ for (new_drp = 0, drp = drs; drp < &drs[MAX_ADS]; drp++) {
+ /* accept new info for a familiar entry
+ */
+ if (drp->dr_gate == gate) {
+ new_drp = drp;
+ break;
+ }
+
+ if (life == 0)
+ continue; /* do not worry about dead ads */
+
+ if (drp->dr_ts == 0) {
+ new_drp = drp; /* use unused entry */
+
+ } else if (new_drp == 0) {
+ /* look for an entry worse than the new one to
+ * reuse.
+ */
+ if ((!(ifp->int_state & IS_SICK)
+ && (drp->dr_ifp->int_state & IS_SICK))
+ || (pref > drp->dr_pref
+ && !((ifp->int_state ^ drp->dr_ifp->int_state)
+ & IS_SICK)))
+ new_drp = drp;
+
+ } else if (new_drp->dr_ts != 0) {
+ /* look for the least valuable entry to reuse
+ */
+ if ((!(new_drp->dr_ifp->int_state & IS_SICK)
+ && (drp->dr_ifp->int_state & IS_SICK))
+ || (new_drp->dr_pref > drp->dr_pref
+ && !((new_drp->dr_ifp->int_state
+ ^ drp->dr_ifp->int_state)
+ & IS_SICK)))
+ new_drp = drp;
+ }
+ }
+
+ /* forget it if all of the current entries are better */
+ if (new_drp == 0)
+ return;
+
+ new_drp->dr_ifp = ifp;
+ new_drp->dr_gate = gate;
+ new_drp->dr_ts = now.tv_sec;
+ new_drp->dr_life = life;
+ new_drp->dr_recv_pref = pref;
+ /* bias functional preference by metric of the interface */
+ new_drp->dr_pref = PREF(pref,ifp);
+
+ /* after hearing a good advertisement, stop asking
+ */
+ if (!(ifp->int_state & IS_SICK))
+ ifp->int_rdisc_cnt = MAX_SOLICITATIONS;
+}
+
+
+/* Compute the IP checksum
+ * This assumes the packet is less than 32K long.
+ */
+static u_short
+in_cksum(u_short *p,
+ u_int len)
+{
+ u_int sum = 0;
+ int nwords = len >> 1;
+
+ while (nwords-- != 0)
+ sum += *p++;
+
+ if (len & 1)
+ sum += *(u_char *)p;
+
+ /* end-around-carry */
+ sum = (sum >> 16) + (sum & 0xffff);
+ sum += (sum >> 16);
+ return (~sum);
+}
+
+
+/* Send a router discovery advertisement or solicitation ICMP packet.
+ */
+static void
+send_rdisc(union ad_u *p,
+ int p_size,
+ struct interface *ifp,
+ naddr dst, /* 0 or unicast destination */
+ int type) /* 0=unicast, 1=bcast, 2=mcast */
+{
+ struct sockaddr_in rsin;
+ int flags;
+ const char *msg;
+
+
+ memset(&rsin, 0, sizeof(rsin));
+ rsin.sin_addr.s_addr = dst;
+ rsin.sin_family = AF_INET;
+#ifdef _HAVE_SIN_LEN
+ rsin.sin_len = sizeof(rsin);
+#endif
+ flags = MSG_DONTROUTE;
+
+ switch (type) {
+ case 0: /* unicast */
+ default:
+ msg = "Send";
+ break;
+
+ case 1: /* broadcast */
+ if (ifp->int_if_flags & IFF_POINTOPOINT) {
+ msg = "Send pt-to-pt";
+ rsin.sin_addr.s_addr = ifp->int_dstaddr;
+ } else {
+ msg = "Send broadcast";
+ rsin.sin_addr.s_addr = ifp->int_brdaddr;
+ }
+ break;
+
+ case 2: /* multicast */
+ msg = "Send multicast";
+ if (ifp->int_state & IS_DUP) {
+ trace_act("abort multicast output via %s"
+ " with duplicate address",
+ ifp->int_name);
+ return;
+ }
+ if (rdisc_sock_mcast != ifp) {
+ /* select the right interface. */
+ struct ip_mreqn mreqn;
+
+ memset(&mreqn, 0, sizeof(struct ip_mreqn));
+ mreqn.imr_ifindex = ifp->int_index;
+ if (0 > setsockopt(rdisc_sock,
+ IPPROTO_IP, IP_MULTICAST_IF,
+ &mreqn,
+ sizeof(mreqn))) {
+ LOGERR("setsockopt(rdisc_sock,"
+ "IP_MULTICAST_IF)");
+ rdisc_sock_mcast = 0;
+ return;
+ }
+ rdisc_sock_mcast = ifp;
+ }
+ flags = 0;
+ break;
+ }
+
+ if (rdisc_sock < 0)
+ get_rdisc_sock();
+
+ trace_rdisc(msg, ifp->int_addr, rsin.sin_addr.s_addr, ifp,
+ p, p_size);
+
+ if (0 > sendto(rdisc_sock, p, p_size, flags,
+ (struct sockaddr *)&rsin, sizeof(rsin))) {
+ if (ifp == 0 || !(ifp->int_state & IS_BROKE))
+ msglog("sendto(%s%s%s): %s",
+ ifp != 0 ? ifp->int_name : "",
+ ifp != 0 ? ", " : "",
+ inet_ntoa(rsin.sin_addr),
+ strerror(errno));
+ if (ifp != 0)
+ if_sick(ifp);
+ }
+}
+
+
+/* Send an advertisement
+ */
+static void
+send_adv(struct interface *ifp,
+ naddr dst, /* 0 or unicast destination */
+ int type) /* 0=unicast, 1=bcast, 2=mcast */
+{
+ union ad_u u;
+ n_long pref;
+
+
+ memset(&u, 0, sizeof(u.ad));
+
+ u.ad.icmp_type = ICMP_ROUTERADVERT;
+ u.ad.icmp_ad_num = 1;
+ u.ad.icmp_ad_asize = sizeof(u.ad.icmp_ad_info[0])/4;
+
+ u.ad.icmp_ad_life = stopint ? 0 : htons(ifp->int_rdisc_int*3);
+
+ /* Convert the configured preference to an unsigned value,
+ * bias it by the interface metric, and then send it as a
+ * signed, network byte order value.
+ */
+ pref = UNSIGN_PREF(ifp->int_rdisc_pref);
+ u.ad.icmp_ad_info[0].icmp_ad_pref = htonl(SIGN_PREF(PREF(pref, ifp)));
+
+ u.ad.icmp_ad_info[0].icmp_ad_addr = ifp->int_addr;
+
+ u.ad.icmp_cksum = in_cksum((u_short*)&u.ad, sizeof(u.ad));
+
+ send_rdisc(&u, sizeof(u.ad), ifp, dst, type);
+}
+
+
+/* Advertise for Router Discovery
+ */
+void
+rdisc_adv(void)
+{
+ struct interface *ifp;
+
+ if (!supplier)
+ return;
+
+ rdisc_timer.tv_sec = now.tv_sec + NEVER;
+
+ LIST_FOREACH(ifp, &ifnet, int_list) {
+ if (0 != (ifp->int_state & (IS_NO_ADV_OUT | IS_BROKE)))
+ continue;
+
+ if (!timercmp(&ifp->int_rdisc_timer, &now, >)
+ || stopint) {
+ send_adv(ifp, htonl(INADDR_ALLHOSTS_GROUP),
+ (ifp->int_state&IS_BCAST_RDISC) ? 1 : 2);
+ ifp->int_rdisc_cnt++;
+
+ intvl_random(&ifp->int_rdisc_timer,
+ (ifp->int_rdisc_int*3)/4,
+ ifp->int_rdisc_int);
+ if (ifp->int_rdisc_cnt < MAX_INITIAL_ADVERTS
+ && (ifp->int_rdisc_timer.tv_sec
+ > MAX_INITIAL_ADVERT_INTERVAL)) {
+ ifp->int_rdisc_timer.tv_sec
+ = MAX_INITIAL_ADVERT_INTERVAL;
+ }
+ timevaladd(&ifp->int_rdisc_timer, &now);
+ }
+
+ if (timercmp(&rdisc_timer, &ifp->int_rdisc_timer, >))
+ rdisc_timer = ifp->int_rdisc_timer;
+ }
+}
+
+
+/* Solicit for Router Discovery
+ */
+void
+rdisc_sol(void)
+{
+ struct interface *ifp;
+ union ad_u u;
+
+
+ if (supplier)
+ return;
+
+ rdisc_timer.tv_sec = now.tv_sec + NEVER;
+
+ LIST_FOREACH(ifp, &ifnet, int_list) {
+ if (0 != (ifp->int_state & (IS_NO_SOL_OUT | IS_BROKE))
+ || ifp->int_rdisc_cnt >= MAX_SOLICITATIONS)
+ continue;
+
+ if (!timercmp(&ifp->int_rdisc_timer, &now, >)) {
+ memset(&u, 0, sizeof(u.so));
+ u.so.icmp_type = ICMP_ROUTERSOLICIT;
+ u.so.icmp_cksum = in_cksum((u_short*)&u.so,
+ sizeof(u.so));
+ send_rdisc(&u, sizeof(u.so), ifp,
+ htonl(INADDR_ALLROUTERS_GROUP),
+ ((ifp->int_state&IS_BCAST_RDISC) ? 1 : 2));
+
+ if (++ifp->int_rdisc_cnt >= MAX_SOLICITATIONS)
+ continue;
+
+ ifp->int_rdisc_timer.tv_sec = SOLICITATION_INTERVAL;
+ ifp->int_rdisc_timer.tv_usec = 0;
+ timevaladd(&ifp->int_rdisc_timer, &now);
+ }
+
+ if (timercmp(&rdisc_timer, &ifp->int_rdisc_timer, >))
+ rdisc_timer = ifp->int_rdisc_timer;
+ }
+}
+
+
+/* check the IP header of a possible Router Discovery ICMP packet */
+static struct interface * /* 0 if bad */
+ck_icmp(const char *act,
+ naddr from,
+ struct interface *ifp,
+ naddr to,
+ union ad_u *p,
+ u_int len)
+{
+ const char *type;
+
+
+ if (p->icmp.icmp_type == ICMP_ROUTERADVERT) {
+ type = "advertisement";
+ } else if (p->icmp.icmp_type == ICMP_ROUTERSOLICIT) {
+ type = "solicitation";
+ } else {
+ return 0;
+ }
+
+ if (p->icmp.icmp_code != 0) {
+ trace_pkt("unrecognized ICMP Router %s code=%d from %s to %s",
+ type, p->icmp.icmp_code,
+ naddr_ntoa(from), naddr_ntoa(to));
+ return 0;
+ }
+
+ trace_rdisc(act, from, to, ifp, p, len);
+
+ if (ifp == 0)
+ trace_pkt("unknown interface for router-discovery %s"
+ " from %s to %s",
+ type, naddr_ntoa(from), naddr_ntoa(to));
+
+ return ifp;
+}
+
+
+/* read packets from the router discovery socket
+ */
+void
+read_d(void)
+{
+ static struct msg_limit bad_asize, bad_len;
+#ifdef USE_PASSIFNAME
+ static struct msg_limit bad_name;
+#endif
+ struct sockaddr_in from;
+ int n, fromlen, cc, hlen;
+ struct {
+#ifdef USE_PASSIFNAME
+ char ifname[IFNAMSIZ];
+#endif
+ union {
+ struct ip ip;
+ u_char b[512];
+ } pkt;
+ } buf;
+ union ad_u *p;
+ n_long *wp;
+ struct interface *ifp;
+
+
+ for (;;) {
+ fromlen = sizeof(from);
+ cc = recvfrom(rdisc_sock, &buf, sizeof(buf), 0,
+ (struct sockaddr*)&from,
+ &fromlen);
+ if (cc <= 0) {
+ if (cc < 0 && errno != EWOULDBLOCK)
+ LOGERR("recvfrom(rdisc_sock)");
+ break;
+ }
+ if (fromlen != sizeof(struct sockaddr_in))
+ logbad(1,"impossible recvfrom(rdisc_sock) fromlen=%d",
+ fromlen);
+#ifdef USE_PASSIFNAME
+ if ((cc -= sizeof(buf.ifname)) < 0)
+ logbad(0,"missing USE_PASSIFNAME; only %d bytes",
+ cc+sizeof(buf.ifname));
+#endif
+
+ hlen = buf.pkt.ip.ip_hl << 2;
+ if (cc < hlen + ICMP_MINLEN)
+ continue;
+ p = (union ad_u *)&buf.pkt.b[hlen];
+ cc -= hlen;
+
+#ifdef USE_PASSIFNAME
+ ifp = ifwithname(buf.ifname, 0);
+ if (ifp == 0)
+ msglim(&bad_name, from.sin_addr.s_addr,
+ "impossible rdisc if_ name %.*s",
+ IFNAMSIZ, buf.ifname);
+#else
+ /* If we could tell the interface on which a packet from
+ * address 0 arrived, we could deal with such solicitations.
+ */
+ ifp = ((from.sin_addr.s_addr == 0)
+ ? 0 : iflookup(from.sin_addr.s_addr));
+#endif
+ ifp = ck_icmp("Recv", from.sin_addr.s_addr, ifp,
+ buf.pkt.ip.ip_dst.s_addr, p, cc);
+ if (ifp == 0)
+ continue;
+ if (ifwithaddr(from.sin_addr.s_addr, 0, 0)) {
+ trace_pkt(" "
+ "discard our own Router Discovery message");
+ continue;
+ }
+
+ switch (p->icmp.icmp_type) {
+ case ICMP_ROUTERADVERT:
+ if (p->ad.icmp_ad_asize*4
+ < (int)sizeof(p->ad.icmp_ad_info[0])) {
+ msglim(&bad_asize, from.sin_addr.s_addr,
+ "intolerable rdisc address size=%d",
+ p->ad.icmp_ad_asize);
+ continue;
+ }
+ if (p->ad.icmp_ad_num == 0) {
+ trace_pkt(" empty?");
+ continue;
+ }
+ if (cc != (int)(sizeof(p->ad)
+ - sizeof(p->ad.icmp_ad_info)
+ + (p->ad.icmp_ad_num
+ * sizeof(p->ad.icmp_ad_info[0])))) {
+ msglim(&bad_len, from.sin_addr.s_addr,
+ "rdisc length %d does not match ad_num"
+ " %d", cc, p->ad.icmp_ad_num);
+ continue;
+ }
+ if (supplier)
+ continue;
+ if (ifp->int_state & IS_NO_ADV_IN)
+ continue;
+
+ wp = &p->ad.icmp_ad_info[0].icmp_ad_addr;
+ for (n = 0; n < p->ad.icmp_ad_num; n++) {
+ parse_ad(from.sin_addr.s_addr,
+ wp[0], wp[1],
+ ntohs(p->ad.icmp_ad_life),
+ ifp);
+ wp += p->ad.icmp_ad_asize;
+ }
+ break;
+
+
+ case ICMP_ROUTERSOLICIT:
+ if (!supplier)
+ continue;
+ if (ifp->int_state & IS_NO_ADV_OUT)
+ continue;
+ if (stopint)
+ continue;
+
+ /* XXX
+ * We should handle messages from address 0.
+ */
+
+ /* Respond with a point-to-point advertisement */
+ send_adv(ifp, from.sin_addr.s_addr, 0);
+ break;
+ }
+ }
+
+ rdisc_sort();
+}
diff --git a/sbin/routed/routed.8 b/sbin/routed/routed.8
new file mode 100644
index 0000000..68d3f96
--- /dev/null
+++ b/sbin/routed/routed.8
@@ -0,0 +1,745 @@
+.\" $Revision: 2.26 $
+.\"
+.\" Copyright (c) 1983, 1991, 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.
+.\" 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.
+.\"
+.\" @(#)routed.8 8.2 (Berkeley) 12/11/93
+.\" $FreeBSD$
+.\"
+.Dd August 26, 2014
+.Dt ROUTED 8
+.Os
+.Sh NAME
+.Nm routed ,
+.Nm rdisc
+.Nd network RIP and router discovery routing daemon
+.Sh SYNOPSIS
+.Nm
+.Op Fl isqdghmpAtv
+.Op Fl T Ar tracefile
+.Oo
+.Fl F
+.Ar net Ns Op /mask Ns Op ,metric
+.Oc
+.Op Fl P Ar parms
+.Sh DESCRIPTION
+The
+.Nm
+utility is a daemon invoked at boot time to manage the network
+routing tables.
+It uses Routing Information Protocol, RIPv1 (RFC\ 1058),
+RIPv2 (RFC\ 1723),
+and Internet Router Discovery Protocol (RFC 1256)
+to maintain the kernel routing table.
+The RIPv1 protocol is based on the reference
+.Bx 4.3
+daemon.
+.Pp
+It listens on the
+.Xr udp 4
+socket for the
+.Xr route 8
+service (see
+.Xr services 5 )
+for Routing Information Protocol packets.
+It also sends and receives multicast Router Discovery ICMP messages.
+If the host is a router,
+.Nm
+periodically supplies copies
+of its routing tables to any directly connected hosts and networks.
+It also advertises or solicits default routes using Router Discovery
+ICMP messages.
+.Pp
+When started (or when a network interface is later turned on),
+.Nm
+uses an AF_ROUTE address family facility to find those
+directly connected interfaces configured into the
+system and marked "up".
+It adds necessary routes for the interfaces
+to the kernel routing table.
+Soon after being first started, and provided there is at least one
+interface on which RIP has not been disabled,
+.Nm
+deletes all pre-existing
+non-static routes in kernel table.
+Static routes in the kernel table are preserved and
+included in RIP responses if they have a valid RIP -hopcount
+(see
+.Xr route 8 ) .
+.Pp
+If more than one interface is present (not counting the loopback interface),
+it is assumed that the host should forward packets among the
+connected networks.
+After transmitting a RIP
+.Em request
+and
+Router Discovery Advertisements or Solicitations on a new interface,
+the daemon enters a loop, listening for
+RIP request and response and Router Discovery packets from other hosts.
+.Pp
+When a
+.Em request
+packet is received,
+.Nm
+formulates a reply based on the information maintained in its
+internal tables.
+The
+.Em response
+packet generated contains a list of known routes, each marked
+with a "hop count" metric (a count of 16 or greater is
+considered "infinite").
+The advertised metric for a route reflects the metrics associated
+with interfaces
+(see
+.Xr ifconfig 8 )
+though which it is received and sent,
+so setting the metric on an interface
+is an effective way to steer traffic.
+See also
+.Cm adj_inmetric
+and
+.Cm adj_outmetric
+parameters below.
+.Pp
+Responses do not include routes with a first hop on the requesting
+network to implement in part
+.Em split-horizon .
+Requests from query programs
+such as
+.Xr rtquery 8
+are answered with the complete table.
+.Pp
+The routing table maintained by the daemon
+includes space for several gateways for each destination
+to speed recovery from a failing router.
+RIP
+.Em response
+packets received are used to update the routing tables provided they are
+from one of the several currently recognized gateways or
+advertise a better metric than at least one of the existing
+gateways.
+.Pp
+When an update is applied,
+.Nm
+records the change in its own tables and updates the kernel routing table
+if the best route to the destination changes.
+The change in the kernel routing table is reflected in the next batch of
+.Em response
+packets sent.
+If the next response is not scheduled for a while, a
+.Em flash update
+response containing only recently changed routes is sent.
+.Pp
+In addition to processing incoming packets,
+.Nm
+also periodically checks the routing table entries.
+If an entry has not been updated for 3 minutes, the entry's metric
+is set to infinity and marked for deletion.
+Deletions are delayed until the route has been advertised with
+an infinite metric to ensure the invalidation
+is propagated throughout the local internet.
+This is a form of
+.Em poison reverse .
+.Pp
+Routes in the kernel table that are added or changed as a result
+of ICMP Redirect messages are deleted after a while to minimize
+.Em black-holes .
+When a TCP connection suffers a timeout,
+the kernel tells
+.Nm ,
+which deletes all redirected routes
+through the gateway involved, advances the age of all RIP routes through
+the gateway to allow an alternate to be chosen, and advances of the
+age of any relevant Router Discovery Protocol default routes.
+.Pp
+Hosts acting as internetwork routers gratuitously supply their
+routing tables every 30 seconds to all directly connected hosts
+and networks.
+These RIP responses are sent to the broadcast address on nets that support
+broadcasting,
+to the destination address on point-to-point links, and to the router's
+own address on other networks.
+If RIPv2 is enabled, multicast packets are sent on interfaces that
+support multicasting.
+.Pp
+If no response is received on a remote interface, if there are errors
+while sending responses,
+or if there are more errors than input or output (see
+.Xr netstat 1 ) ,
+then the cable or some other part of the interface is assumed to be
+disconnected or broken, and routes are adjusted appropriately.
+.Pp
+The
+.Em Internet Router Discovery Protocol
+is handled similarly.
+When the daemon is supplying RIP routes, it also listens for
+Router Discovery Solicitations and sends Advertisements.
+When it is quiet and listening to other RIP routers, it
+sends Solicitations and listens for Advertisements.
+If it receives
+a good Advertisement and it is not multi-homed,
+it stops listening for broadcast or multicast RIP responses.
+It tracks several advertising routers to speed recovery when the
+currently chosen router dies.
+If all discovered routers disappear,
+the daemon resumes listening to RIP responses.
+It continues listening to RIP while using Router Discovery
+if multi-homed to ensure all interfaces are used.
+.Pp
+The Router Discovery standard requires that advertisements
+have a default "lifetime" of 30 minutes.
+That means should
+something happen, a client can be without a good route for
+30 minutes.
+It is a good idea to reduce the default to 45
+seconds using
+.Fl P Cm rdisc_interval=45
+on the command line or
+.Cm rdisc_interval=45
+in the
+.Pa /etc/gateways
+file.
+.Pp
+While using Router Discovery (which happens by default when
+the system has a single network interface and a Router Discover Advertisement
+is received), there is a single default route and a variable number of
+redirected host routes in the kernel table.
+On a host with more than one network interface,
+this default route will be via only one of the interfaces.
+Thus, multi-homed hosts running with
+.Fl q
+might need
+.Cm no_rdisc
+described below.
+.Pp
+See the
+.Cm pm_rdisc
+facility described below to support "legacy" systems
+that can handle neither RIPv2 nor Router Discovery.
+.Pp
+By default, neither Router Discovery advertisements nor solicitations
+are sent over point to point links (e.g.\& PPP).
+The netmask associated with point-to-point links (such as SLIP
+or PPP, with the IFF_POINTOPOINT flag) is used by
+.Nm
+to infer the netmask used by the remote system when RIPv1 is used.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl i
+allow
+.Nm
+to accept a RIP request from non-router node.
+When specified once,
+.Nm
+replies to a route information query from neighbor nodes.
+When specified twice,
+it replies to a query from remote nodes in addition.
+.Xr rtquery 8
+utility can be used to send a request.
+.Pp
+This feature is disabled by default because of a risk of reflection attack
+though it is useful for debugging purpose.
+.It Fl s
+force
+.Nm
+to supply routing information.
+This is the default if multiple network interfaces are present on which
+RIP or Router Discovery have not been disabled, and if the kernel switch
+ipforwarding=1.
+.It Fl q
+is the opposite of the
+.Fl s
+option.
+This is the default when only one interface is present.
+With this explicit option, the daemon is always in "quiet-mode" for RIP
+and does not supply routing information to other computers.
+.It Fl d
+do not run in the background.
+This option is meant for interactive use.
+.It Fl g
+used on internetwork routers to offer a route
+to the "default" destination.
+It is equivalent to
+.Fl F
+.Cm 0/0,1
+and is present mostly for historical reasons.
+A better choice is
+.Fl P Cm pm_rdisc
+on the command line or
+.Cm pm_rdisc
+in the
+.Pa /etc/gateways
+file,
+since a larger metric
+will be used, reducing the spread of the potentially dangerous
+default route.
+This is typically used on a gateway to the Internet,
+or on a gateway that uses another routing protocol whose routes
+are not reported to other local routers.
+Notice that because a metric of 1 is used, this feature is
+dangerous.
+It is more commonly accidentally used to create chaos with a
+routing loop than to solve problems.
+.It Fl h
+cause host or point-to-point routes to not be advertised,
+provided there is a network route going the same direction.
+That is a limited kind of aggregation.
+This option is useful on gateways to Ethernets that have other gateway
+machines connected with point-to-point links such as SLIP.
+.It Fl m
+cause the machine to advertise a host or point-to-point route to
+its primary interface.
+It is useful on multi-homed machines such as NFS servers.
+This option should not be used except when the cost of
+the host routes it generates is justified by the popularity of
+the server.
+It is effective only when the machine is supplying
+routing information, because there is more than one interface.
+The
+.Fl m
+option overrides the
+.Fl q
+option to the limited extent of advertising the host route.
+.It Fl A
+do not ignore RIPv2 authentication if we do not care about RIPv2
+authentication.
+This option is required for conformance with RFC 1723.
+However, it makes no sense and breaks using RIP as a discovery protocol
+to ignore all RIPv2 packets that carry authentication when this machine
+does not care about authentication.
+.It Fl t
+increase the debugging level, which causes more information to be logged
+on the tracefile specified with
+.Fl T
+or standard out.
+The debugging level can be increased or decreased
+with the
+.Em SIGUSR1
+or
+.Em SIGUSR2
+signals or with the
+.Xr rtquery 8
+command.
+.It Fl T Ar tracefile
+increases the debugging level to at least 1 and
+causes debugging information to be appended to the trace file.
+Note that because of security concerns, it is wisest to not run
+.Nm
+routinely with tracing directed to a file.
+.It Fl v
+display and logs the version of daemon.
+.It Fl F Ar net[/mask][,metric]
+minimize routes in transmissions via interfaces with addresses that match
+.Em net/mask ,
+and synthesizes a default route to this machine with the
+.Em metric .
+The intent is to reduce RIP traffic on slow, point-to-point links
+such as PPP links by replacing many large UDP packets of RIP information
+with a single, small packet containing a "fake" default route.
+If
+.Em metric
+is absent, a value of 14 is assumed to limit
+the spread of the "fake" default route.
+This is a dangerous feature that when used carelessly can cause routing
+loops.
+Notice also that more than one interface can match the specified network
+number and mask.
+See also
+.Fl g .
+.It Fl P Ar parms
+is equivalent to adding the parameter
+line
+.Em parms
+to the
+.Pa /etc/gateways
+file.
+.El
+.Pp
+Any other argument supplied is interpreted as the name
+of a file in which the actions of
+.Nm
+should be logged.
+It is better to use
+.Fl T
+instead of
+appending the name of the trace file to the command.
+.Pp
+The
+.Nm
+utility also supports the notion of
+"distant"
+.Em passive
+or
+.Em active
+gateways.
+When
+.Nm
+is started, it reads the file
+.Pa /etc/gateways
+to find such distant gateways which may not be located using
+only information from a routing socket, to discover if some
+of the local gateways are
+.Em passive ,
+and to obtain other parameters.
+Gateways specified in this manner should be marked passive
+if they are not expected to exchange routing information,
+while gateways marked active
+should be willing to exchange RIP packets.
+Routes through
+.Em passive
+gateways are installed in the
+kernel's routing tables once upon startup and are not included in
+transmitted RIP responses.
+.Pp
+Distant active gateways are treated like network interfaces.
+RIP responses are sent
+to the distant
+.Em active
+gateway.
+If no responses are received, the associated route is deleted from
+the kernel table and RIP responses advertised via other interfaces.
+If the distant gateway resumes sending RIP responses, the associated
+route is restored.
+.Pp
+Such gateways can be useful on media that do not support broadcasts
+or multicasts but otherwise act like classic shared media like
+Ethernets such as some ATM networks.
+One can list all RIP routers reachable on the HIPPI or ATM network in
+.Pa /etc/gateways
+with a series of
+"host" lines.
+Note that it is usually desirable to use RIPv2 in such situations
+to avoid generating lists of inferred host routes.
+.Pp
+Gateways marked
+.Em external
+are also passive, but are not placed in the kernel
+routing table nor are they included in routing updates.
+The function of external entries is to indicate
+that another routing process
+will install such a route if necessary,
+and that other routes to that destination should not be installed
+by
+.Nm .
+Such entries are only required when both routers may learn of routes
+to the same destination.
+.Pp
+The
+.Pa /etc/gateways
+file is comprised of a series of lines, each in
+one of the following two formats or consist of parameters described later.
+Blank lines and lines starting with '#' are comments.
+.Bd -ragged
+.Cm net
+.Ar Nname[/mask]
+.Cm gateway
+.Ar Gname
+.Cm metric
+.Ar value
+.Pf < Cm passive No \&|
+.Cm active No \&|
+.Cm extern Ns >
+.Ed
+.Bd -ragged
+.Cm host
+.Ar Hname
+.Cm gateway
+.Ar Gname
+.Cm metric
+.Ar value
+.Pf < Cm passive No \&|
+.Cm active No \&|
+.Cm extern Ns >
+.Ed
+.Pp
+.Ar Nname
+or
+.Ar Hname
+is the name of the destination network or host.
+It may be a symbolic network name or an Internet address
+specified in "dot" notation (see
+.Xr inet 3 ) .
+(If it is a name, then it must either be defined in
+.Pa /etc/networks
+or
+.Pa /etc/hosts ,
+or
+.Xr named 8 ,
+must have been started before
+.Nm . )
+.Pp
+.Ar Mask
+is an optional number between 1 and 32 indicating the netmask associated
+with
+.Ar Nname .
+.Pp
+.Ar Gname
+is the name or address of the gateway to which RIP responses should
+be forwarded.
+.Pp
+.Ar Value
+is the hop count to the destination host or network.
+.Pp
+.Cm Host Ar hname
+is equivalent to
+.Cm net Ar nname/32 .
+.Pp
+One of the keywords
+.Cm passive ,
+.Cm active
+or
+.Cm external
+must be present to indicate whether the gateway should be treated as
+.Cm passive
+or
+.Cm active
+(as described above),
+or whether the gateway is
+.Cm external
+to the scope of the RIP protocol.
+.Pp
+As can be seen when debugging is turned on with
+.Fl t ,
+such lines create pseudo-interfaces.
+To set parameters for remote or external interfaces,
+a line starting with
+.Cm if=alias(Hname) ,
+.Cm if=remote(Hname) ,
+etc.\& should be used.
+.Ss Parameters
+Lines that start with neither "net" nor "host" must consist of one
+or more of the following parameter settings, separated by commas or
+blanks:
+.Bl -tag -width indent
+.It Cm if Ns = Ns Ar ifname
+indicates that the other parameters on the line apply to the interface
+name
+.Ar ifname .
+.It Cm subnet Ns = Ns Ar nname Ns Oo / Ns Ar mask Oc Ns Op , Ns Ar metric
+advertises a route to network
+.Ar nname
+with mask
+.Ar mask
+and the supplied metric (default 1).
+This is useful for filling "holes" in CIDR allocations.
+This parameter must appear by itself on a line.
+The network number must specify a full, 32-bit value, as in 192.0.2.0
+instead of 192.0.2.
+.Pp
+Do not use this feature unless necessary.
+It is dangerous.
+.It Cm ripv1_mask Ns = Ns Ar nname Ns / Ns Ar mask1 , Ns Ar mask2
+specifies that netmask of the network of which
+.Ar nname Ns / Ns Ar mask1
+is
+a subnet should be
+.Ar mask2 .
+For example,
+.Dq Li ripv1_mask=192.0.2.16/28,27
+marks 192.0.2.16/28
+as a subnet of 192.0.2.0/27 instead of 192.0.2.0/24.
+It is better to turn on RIPv2 instead of using this facility, for example
+with
+.Cm ripv2_out .
+.It Cm passwd Ns = Ns Ar XXX[|KeyID[start|stop]]
+specifies a RIPv2 cleartext password that will be included on
+all RIPv2 responses sent, and checked on all RIPv2 responses received.
+Any blanks, tab characters, commas, or '#', '|', or NULL characters in the
+password must be escaped with a backslash (\\).
+The common escape sequences \\n, \\r, \\t, \\b, and \\xxx have their
+usual meanings.
+The
+.Cm KeyID
+must be unique but is ignored for cleartext passwords.
+If present,
+.Cm start
+and
+.Cm stop
+are timestamps in the form year/month/day@hour:minute.
+They specify when the password is valid.
+The valid password with the most future is used on output packets, unless
+all passwords have expired, in which case the password that expired most
+recently is used, or unless no passwords are valid yet, in which case
+no password is output.
+Incoming packets can carry any password that is valid, will
+be valid within the next 24 hours, or that was valid within the preceding
+24 hours.
+To protect the secrets, the passwd settings are valid only in the
+.Pa /etc/gateways
+file and only when that file is readable only by UID 0.
+.It Cm md5_passwd Ns \&= Ns Ar XXX|KeyID[start|stop]
+specifies a RIPv2 MD5 password.
+Except that a
+.Cm KeyID
+is required, this keyword is similar to
+.Cm passwd .
+.It Cm no_ag
+turns off aggregation of subnets in RIPv1 and RIPv2 responses.
+.It Cm no_super_ag
+turns off aggregation of networks into supernets in RIPv2 responses.
+.It Cm passive
+marks the interface to not be advertised in updates sent via other
+interfaces, and turns off all RIP and router discovery through the interface.
+.It Cm no_rip
+disables all RIP processing on the specified interface.
+If no interfaces are allowed to process RIP packets,
+.Nm
+acts purely as a router discovery daemon.
+.Pp
+Note that turning off RIP without explicitly turning on router
+discovery advertisements with
+.Cm rdisc_adv
+or
+.Fl s
+causes
+.Nm
+to act as a client router discovery daemon, not advertising.
+.It Cm no_rip_mcast
+causes RIPv2 packets to be broadcast instead of multicast.
+.It Cm no_rip_out
+causes no RIP updates to be sent.
+.It Cm no_ripv1_in
+causes RIPv1 received responses to be ignored.
+.It Cm no_ripv2_in
+causes RIPv2 received responses to be ignored.
+.It Cm ripv2_out
+turns on RIPv2 output and causes RIPv2 advertisements to be
+multicast when possible.
+.It Cm ripv2
+is equivalent to
+.Cm no_ripv1_in
+and
+.Cm no_ripv1_out .
+This enables RIPv2.
+.It Cm no_rdisc
+disables the Internet Router Discovery Protocol.
+.It Cm no_solicit
+disables the transmission of Router Discovery Solicitations.
+.It Cm send_solicit
+specifies that Router Discovery solicitations should be sent,
+even on point-to-point links,
+which by default only listen to Router Discovery messages.
+.It Cm no_rdisc_adv
+disables the transmission of Router Discovery Advertisements.
+.It Cm rdisc_adv
+specifies that Router Discovery Advertisements should be sent,
+even on point-to-point links,
+which by default only listen to Router Discovery messages.
+.It Cm bcast_rdisc
+specifies that Router Discovery packets should be broadcast instead of
+multicast.
+.It Cm rdisc_pref Ns \&= Ns Ar N
+sets the preference in Router Discovery Advertisements to the optionally
+signed integer
+.Ar N .
+The default preference is 0.
+Default routes with smaller or more negative preferences are preferred by
+clients.
+.It Cm rdisc_interval Ns \&= Ns Ar N
+sets the nominal interval with which Router Discovery Advertisements
+are transmitted to N seconds and their lifetime to 3*N.
+.It Cm fake_default Ns \&= Ns Ar metric
+has an identical effect to
+.Fl F Ar net[/mask][=metric]
+with the network and mask coming from the specified interface.
+.It Cm pm_rdisc
+is similar to
+.Cm fake_default .
+When RIPv2 routes are multicast, so that RIPv1 listeners cannot
+receive them, this feature causes a RIPv1 default route to be
+broadcast to RIPv1 listeners.
+Unless modified with
+.Cm fake_default ,
+the default route is broadcast with a metric of 14.
+That serves as a "poor man's router discovery" protocol.
+.It Cm adj_inmetric Ns \&= Ns Ar delta
+adjusts the hop count or metric of received RIP routes by
+.Ar delta .
+The metric of every received RIP route is increased by the sum
+of two values associated with the interface.
+One is the adj_inmetric value and the other is the interface
+metric set with
+.Xr ifconfig 8 .
+.It Cm adj_outmetric Ns \&= Ns Ar delta
+adjusts the hop count or metric of advertised RIP routes by
+.Ar delta .
+The metric of every received RIP route is increased by the metric
+associated with the interface by which it was received, or by 1 if
+the interface does not have a non-zero metric.
+The metric of the received route is then increased by the
+adj_outmetric associated with the interface.
+Every advertised route is increased by a total of four
+values,
+the metric set for the interface by which it was received with
+.Xr ifconfig 8 ,
+the
+.Cm adj_inmetric Ar delta
+of the receiving interface,
+the metric set for the interface by which it is transmitted with
+.Xr ifconfig 8 ,
+and the
+.Cm adj_outmetric Ar delta
+of the transmitting interface.
+.It Cm trust_gateway Ns \&= Ns Ar rname[|net1/mask1|net2/mask2|...]
+causes RIP packets from router
+.Ar rname
+and other routers named in other
+.Cm trust_gateway
+keywords to be accepted, and packets from other routers to be ignored.
+If networks are specified, then routes to other networks will be ignored
+from that router.
+.It Cm redirect_ok
+allows the kernel to listen ICMP Redirect messages when the system is acting
+as a router and forwarding packets.
+Otherwise, ICMP Redirect messages are overridden and deleted when the
+system is acting as a router.
+.El
+.Sh FILES
+.Bl -tag -width /etc/gateways -compact
+.It Pa /etc/gateways
+for distant gateways
+.El
+.Sh SEE ALSO
+.Xr icmp 4 ,
+.Xr udp 4 ,
+.Xr rtquery 8
+.Rs
+.%T Internet Transport Protocols
+.%R XSIS 028112
+.%Q Xerox System Integration Standard
+.Re
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Bx 4.2 .
+.\" LocalWords: loopback ICMP rtquery ifconfig multicasting Solicitations RIPv
+.\" LocalWords: netstat rdisc
+.Sh BUGS
+It does not always detect unidirectional failures in network interfaces,
+for example, when the output side fails.
diff --git a/sbin/routed/rtquery/Makefile b/sbin/routed/rtquery/Makefile
new file mode 100644
index 0000000..4df7dc2
--- /dev/null
+++ b/sbin/routed/rtquery/Makefile
@@ -0,0 +1,11 @@
+# Make `routed` tools for BSD/OS
+# $Revision: 1.6 $
+# $FreeBSD$
+
+PROG= rtquery
+MAN= rtquery.8
+LIBADD= md
+WARNS?= 3
+NO_WARRAY_BOUNDS=
+
+.include <bsd.prog.mk>
diff --git a/sbin/routed/rtquery/rtquery.8 b/sbin/routed/rtquery/rtquery.8
new file mode 100644
index 0000000..4f16e88
--- /dev/null
+++ b/sbin/routed/rtquery/rtquery.8
@@ -0,0 +1,131 @@
+.\" $Revision: 2.27 $
+.\" $FreeBSD$
+.\"
+.Dd June 1, 1996
+.Dt RTQUERY 8
+.Os
+.Sh NAME
+.Nm rtquery
+.Nd query routing daemons for their routing tables
+.Sh SYNOPSIS
+.Nm
+.Op Fl np1
+.Op Fl w Ar timeout
+.Op Fl r Ar addr
+.Op Fl a Ar secret
+.Ar host ...
+.Nm
+.Op Fl t Ar op
+.Ar host ...
+.Sh DESCRIPTION
+The
+.Nm
+utility is used to query a RIP network routing daemon, such as
+.Xr routed 8 ,
+for its routing table by sending a
+.Em request
+or
+.Em poll
+command.
+The routing information in any routing
+.Em response
+packets returned is displayed numerically and symbolically.
+.Pp
+The
+.Nm
+utility by default uses the
+.Em request
+command.
+When the
+.Fl p
+option is specified,
+.Nm
+uses the
+.Em poll
+command, an
+undocumented extension to the RIP protocol supported by
+the commercial
+.Nm gated
+routing product.
+When querying
+.Nm gated ,
+the
+.Em poll
+command is preferred over the
+.Em request
+command because the response is not subject to Split Horizon and/or
+Poisoned Reverse, and because some versions of
+.Nm gated
+do not answer the
+.Em request
+command.
+The
+.Xr routed 8
+utility does not answer the
+.Em poll
+command, but recognizes
+.Em requests
+coming from
+.Nm
+and so answers completely.
+.Pp
+The
+.Nm
+utility is also used to turn tracing on or off in
+.Xr routed 8 .
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl n
+displays only the numeric network and host numbers instead of both
+numeric and symbolic.
+.It Fl p
+uses the
+.Em poll
+command to request full routing information from
+.Nm gated .
+This is an undocumented extension RIP protocol supported only by
+.Nm gated .
+.It Fl 1
+queries using RIP version 1 instead of RIP version 2.
+.It Fl w Ar timeout
+changes the delay for an answer from each host.
+By default, each host is given 15 seconds to respond.
+.It Fl r Ar addr
+asks about the route to destination
+.Em addr .
+.It Fl a Ar passwd=XXX
+.It Fl a Ar md5_passwd=XXX|KeyID
+causes the query to be sent with the indicated cleartext or MD5 password.
+.It Fl t Ar op
+changes tracing, where
+.Em op
+is one of the following.
+Requests from processes not running with UID 0 or on distant networks
+are generally ignored by the daemon except for a message in the system log.
+.Nm gated
+is likely to ignore these debugging requests.
+.El
+.Bl -tag -width Ds -offset indent-two
+.It Em on=tracefile
+turns tracing on into the specified file.
+That file must usually have been specified when the daemon was
+started or be the same as a fixed name, often
+.Pa /etc/routed.trace .
+.It Em more
+increases the debugging level.
+.It Em off
+turns off tracing.
+.It Em dump
+dumps the daemon's routing table to the current tracefile.
+.El
+.Sh SEE ALSO
+.Xr routed 8
+.Rs
+.%T Routing Information Protocol, RIPv1
+.%O RFC1058
+.Re
+.Rs
+.%T Routing Information Protocol, RIPv2
+.%O RFC1723
+.Re
diff --git a/sbin/routed/rtquery/rtquery.c b/sbin/routed/rtquery/rtquery.c
new file mode 100644
index 0000000..1b3a47b
--- /dev/null
+++ b/sbin/routed/rtquery/rtquery.c
@@ -0,0 +1,919 @@
+/*-
+ * Copyright (c) 1982, 1986, 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.
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/cdefs.h>
+#include <sys/param.h>
+#include <sys/protosw.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <netinet/in.h>
+#define RIPVERSION RIPv2
+#include <protocols/routed.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef sgi
+#include <strings.h>
+#include <bstring.h>
+#endif
+
+#define UNUSED __attribute__((unused))
+#ifndef __RCSID
+#define __RCSID(_s) static const char rcsid[] UNUSED = _s
+#endif
+#ifndef __COPYRIGHT
+#define __COPYRIGHT(_s) static const char copyright[] UNUSED = _s
+#endif
+__COPYRIGHT("@(#) Copyright (c) 1983, 1988, 1993\n"
+ "The Regents of the University of California."
+ " All rights reserved.\n");
+#ifdef __NetBSD__
+__RCSID("$NetBSD$");
+#elif defined(__FreeBSD__)
+__RCSID("$FreeBSD$");
+#else
+__RCSID("$Revision: 2.26 $");
+#ident "$Revision: 2.26 $"
+#endif
+
+#ifndef sgi
+#define _HAVE_SIN_LEN
+#endif
+
+#ifdef __NetBSD__
+#include <md5.h>
+#else
+#define MD5_DIGEST_LEN 16
+typedef struct {
+ u_int32_t state[4]; /* state (ABCD) */
+ u_int32_t count[2]; /* # of bits, modulo 2^64 (LSB 1st) */
+ unsigned char buffer[64]; /* input buffer */
+} MD5_CTX;
+extern void MD5Init(MD5_CTX*);
+extern void MD5Update(MD5_CTX*, u_char*, u_int);
+extern void MD5Final(u_char[MD5_DIGEST_LEN], MD5_CTX*);
+#endif
+
+
+#define WTIME 15 /* Time to wait for all responses */
+#define STIME (250*1000) /* usec to wait for another response */
+
+int soc;
+
+const char *pgmname;
+
+union {
+ struct rip rip;
+ char packet[MAXPACKETSIZE+MAXPATHLEN];
+} omsg_buf;
+#define OMSG omsg_buf.rip
+int omsg_len = sizeof(struct rip);
+
+union {
+ struct rip rip;
+ char packet[MAXPACKETSIZE+1024];
+ } imsg_buf;
+#define IMSG imsg_buf.rip
+
+int nflag; /* numbers, no names */
+int pflag; /* play the `gated` game */
+int ripv2 = 1; /* use RIP version 2 */
+int wtime = WTIME;
+int rflag; /* 1=ask about a particular route */
+int trace, not_trace; /* send trace command or not */
+int auth_type = RIP_AUTH_NONE;
+char passwd[RIP_AUTH_PW_LEN];
+u_long keyid;
+
+struct timeval sent; /* when query sent */
+
+static char localhost_str[] = "localhost";
+static char *default_argv[] = {localhost_str, 0};
+
+static void rip_input(struct sockaddr_in*, int);
+static int out(const char *);
+static void trace_loop(char *argv[]) __attribute((__noreturn__));
+static void query_loop(char *argv[], int) __attribute((__noreturn__));
+static int getnet(char *, struct netinfo *);
+static u_int std_mask(u_int);
+static int parse_quote(char **, const char *, char *, char *, int);
+static void usage(void);
+
+
+int
+main(int argc,
+ char *argv[])
+{
+ int ch, bsize;
+ char *p, *options, *value, delim;
+ const char *result;
+
+ OMSG.rip_nets[0].n_dst = RIP_DEFAULT;
+ OMSG.rip_nets[0].n_family = RIP_AF_UNSPEC;
+ OMSG.rip_nets[0].n_metric = htonl(HOPCNT_INFINITY);
+
+ pgmname = argv[0];
+ while ((ch = getopt(argc, argv, "np1w:r:t:a:")) != -1)
+ switch (ch) {
+ case 'n':
+ not_trace = 1;
+ nflag = 1;
+ break;
+
+ case 'p':
+ not_trace = 1;
+ pflag = 1;
+ break;
+
+ case '1':
+ ripv2 = 0;
+ break;
+
+ case 'w':
+ not_trace = 1;
+ wtime = (int)strtoul(optarg, &p, 0);
+ if (*p != '\0'
+ || wtime <= 0)
+ usage();
+ break;
+
+ case 'r':
+ not_trace = 1;
+ if (rflag)
+ usage();
+ rflag = getnet(optarg, &OMSG.rip_nets[0]);
+ if (!rflag) {
+ struct hostent *hp = gethostbyname(optarg);
+ if (hp == 0) {
+ fprintf(stderr, "%s: %s:",
+ pgmname, optarg);
+ herror(0);
+ exit(1);
+ }
+ memcpy(&OMSG.rip_nets[0].n_dst, hp->h_addr,
+ sizeof(OMSG.rip_nets[0].n_dst));
+ OMSG.rip_nets[0].n_family = RIP_AF_INET;
+ OMSG.rip_nets[0].n_mask = -1;
+ rflag = 1;
+ }
+ break;
+
+ case 't':
+ trace = 1;
+ options = optarg;
+ while (*options != '\0') {
+ /* messy complications to make -W -Wall happy */
+ static char on_str[] = "on";
+ static char more_str[] = "more";
+ static char off_str[] = "off";
+ static char dump_str[] = "dump";
+ static char *traceopts[] = {
+# define TRACE_ON 0
+ on_str,
+# define TRACE_MORE 1
+ more_str,
+# define TRACE_OFF 2
+ off_str,
+# define TRACE_DUMP 3
+ dump_str,
+ 0
+ };
+ result = "";
+ switch (getsubopt(&options,traceopts,&value)) {
+ case TRACE_ON:
+ OMSG.rip_cmd = RIPCMD_TRACEON;
+ if (!value
+ || strlen(value) > MAXPATHLEN)
+ usage();
+ result = value;
+ break;
+ case TRACE_MORE:
+ if (value)
+ usage();
+ OMSG.rip_cmd = RIPCMD_TRACEON;
+ break;
+ case TRACE_OFF:
+ if (value)
+ usage();
+ OMSG.rip_cmd = RIPCMD_TRACEOFF;
+ break;
+ case TRACE_DUMP:
+ if (value)
+ usage();
+ OMSG.rip_cmd = RIPCMD_TRACEON;
+ result = "dump/../table";
+ break;
+ default:
+ usage();
+ }
+ strcpy((char*)OMSG.rip_tracefile, result);
+ omsg_len += strlen(result) - sizeof(OMSG.ripun);
+ }
+ break;
+
+ case 'a':
+ not_trace = 1;
+ p = strchr(optarg,'=');
+ if (!p)
+ usage();
+ *p++ = '\0';
+ if (!strcasecmp("passwd",optarg))
+ auth_type = RIP_AUTH_PW;
+ else if (!strcasecmp("md5_passwd",optarg))
+ auth_type = RIP_AUTH_MD5;
+ else
+ usage();
+ if (0 > parse_quote(&p,"|",&delim,
+ passwd, sizeof(passwd)))
+ usage();
+ if (auth_type == RIP_AUTH_MD5
+ && delim == '|') {
+ keyid = strtoul(p+1,&p,0);
+ if (keyid > 255 || *p != '\0')
+ usage();
+ } else if (delim != '\0') {
+ usage();
+ }
+ break;
+
+ default:
+ usage();
+ }
+ argv += optind;
+ argc -= optind;
+ if (not_trace && trace)
+ usage();
+ if (argc == 0) {
+ argc = 1;
+ argv = default_argv;
+ }
+
+ soc = socket(AF_INET, SOCK_DGRAM, 0);
+ if (soc < 0) {
+ perror("socket");
+ exit(2);
+ }
+
+ /* be prepared to receive a lot of routes */
+ for (bsize = 127*1024; ; bsize -= 1024) {
+ if (setsockopt(soc, SOL_SOCKET, SO_RCVBUF,
+ &bsize, sizeof(bsize)) == 0)
+ break;
+ if (bsize <= 4*1024) {
+ perror("setsockopt SO_RCVBUF");
+ break;
+ }
+ }
+
+ if (trace)
+ trace_loop(argv);
+ else
+ query_loop(argv, argc);
+ /* NOTREACHED */
+ return 0;
+}
+
+
+static void
+usage(void)
+{
+ fprintf(stderr,
+ "usage: rtquery [-np1] [-r tgt_rt] [-w wtime]"
+ " [-a type=passwd] host1 [host2 ...]\n"
+ "\trtquery -t {on=filename|more|off|dump}"
+ " host1 [host2 ...]\n");
+ exit(1);
+}
+
+
+/* tell the target hosts about tracing
+ */
+static void
+trace_loop(char *argv[])
+{
+ struct sockaddr_in myaddr;
+ int res;
+
+ if (geteuid() != 0) {
+ (void)fprintf(stderr, "-t requires UID 0\n");
+ exit(1);
+ }
+
+ if (ripv2) {
+ OMSG.rip_vers = RIPv2;
+ } else {
+ OMSG.rip_vers = RIPv1;
+ }
+
+ memset(&myaddr, 0, sizeof(myaddr));
+ myaddr.sin_family = AF_INET;
+#ifdef _HAVE_SIN_LEN
+ myaddr.sin_len = sizeof(myaddr);
+#endif
+ myaddr.sin_port = htons(IPPORT_RESERVED-1);
+ while (bind(soc, (struct sockaddr *)&myaddr, sizeof(myaddr)) < 0) {
+ if (errno != EADDRINUSE
+ || myaddr.sin_port == 0) {
+ perror("bind");
+ exit(2);
+ }
+ myaddr.sin_port = htons(ntohs(myaddr.sin_port)-1);
+ }
+
+ res = 1;
+ while (*argv != 0) {
+ if (out(*argv++) <= 0)
+ res = 0;
+ }
+ exit(res);
+}
+
+
+/* query all of the listed hosts
+ */
+static void
+query_loop(char *argv[], int argc)
+{
+# define NA0 (OMSG.rip_auths[0])
+# define NA2 (OMSG.rip_auths[2])
+ struct seen {
+ struct seen *next;
+ struct in_addr addr;
+ } *seen, *sp;
+ int answered = 0;
+ int cc;
+ fd_set bits;
+ struct timeval now, delay;
+ struct sockaddr_in from;
+ int fromlen;
+ MD5_CTX md5_ctx;
+
+
+ OMSG.rip_cmd = (pflag) ? RIPCMD_POLL : RIPCMD_REQUEST;
+ if (ripv2) {
+ OMSG.rip_vers = RIPv2;
+ if (auth_type == RIP_AUTH_PW) {
+ OMSG.rip_nets[1] = OMSG.rip_nets[0];
+ NA0.a_family = RIP_AF_AUTH;
+ NA0.a_type = RIP_AUTH_PW;
+ memcpy(NA0.au.au_pw, passwd, RIP_AUTH_PW_LEN);
+ omsg_len += sizeof(OMSG.rip_nets[0]);
+
+ } else if (auth_type == RIP_AUTH_MD5) {
+ OMSG.rip_nets[1] = OMSG.rip_nets[0];
+ NA0.a_family = RIP_AF_AUTH;
+ NA0.a_type = RIP_AUTH_MD5;
+ NA0.au.a_md5.md5_keyid = (int8_t)keyid;
+ NA0.au.a_md5.md5_auth_len = RIP_AUTH_MD5_KEY_LEN;
+ NA0.au.a_md5.md5_seqno = 0;
+ cc = (char *)&NA2-(char *)&OMSG;
+ NA0.au.a_md5.md5_pkt_len = htons(cc);
+ NA2.a_family = RIP_AF_AUTH;
+ NA2.a_type = htons(1);
+ MD5Init(&md5_ctx);
+ MD5Update(&md5_ctx,
+ (u_char *)&OMSG, cc);
+ MD5Update(&md5_ctx,
+ (u_char *)passwd, RIP_AUTH_MD5_HASH_LEN);
+ MD5Final(NA2.au.au_pw, &md5_ctx);
+ omsg_len += 2*sizeof(OMSG.rip_nets[0]);
+ }
+
+ } else {
+ OMSG.rip_vers = RIPv1;
+ OMSG.rip_nets[0].n_mask = 0;
+ }
+
+ /* ask the first (valid) host */
+ seen = 0;
+ while (0 > out(*argv++)) {
+ if (*argv == 0)
+ exit(1);
+ answered++;
+ }
+
+ FD_ZERO(&bits);
+ for (;;) {
+ FD_SET(soc, &bits);
+ delay.tv_sec = 0;
+ delay.tv_usec = STIME;
+ cc = select(soc+1, &bits, 0,0, &delay);
+ if (cc > 0) {
+ fromlen = sizeof(from);
+ cc = recvfrom(soc, imsg_buf.packet,
+ sizeof(imsg_buf.packet), 0,
+ (struct sockaddr *)&from, &fromlen);
+ if (cc < 0) {
+ perror("recvfrom");
+ exit(1);
+ }
+ /* count the distinct responding hosts.
+ * You cannot match responding hosts with
+ * addresses to which queries were transmitted,
+ * because a router might respond with a
+ * different source address.
+ */
+ for (sp = seen; sp != 0; sp = sp->next) {
+ if (sp->addr.s_addr == from.sin_addr.s_addr)
+ break;
+ }
+ if (sp == 0) {
+ sp = malloc(sizeof(*sp));
+ if (sp == 0) {
+ fprintf(stderr,
+ "rtquery: malloc failed\n");
+ exit(1);
+ }
+ sp->addr = from.sin_addr;
+ sp->next = seen;
+ seen = sp;
+ answered++;
+ }
+
+ rip_input(&from, cc);
+ continue;
+ }
+
+ if (cc < 0) {
+ if (errno == EINTR)
+ continue;
+ perror("select");
+ exit(1);
+ }
+
+ /* After a pause in responses, probe another host.
+ * This reduces the intermingling of answers.
+ */
+ while (*argv != 0 && 0 > out(*argv++))
+ answered++;
+
+ /* continue until no more packets arrive
+ * or we have heard from all hosts
+ */
+ if (answered >= argc)
+ break;
+
+ /* or until we have waited a long time
+ */
+ if (gettimeofday(&now, 0) < 0) {
+ perror("gettimeofday(now)");
+ exit(1);
+ }
+ if (sent.tv_sec + wtime <= now.tv_sec)
+ break;
+ }
+
+ /* fail if there was no answer */
+ exit (answered >= argc ? 0 : 1);
+}
+
+
+/* send to one host
+ */
+static int
+out(const char *host)
+{
+ struct sockaddr_in router;
+ struct hostent *hp;
+
+ if (gettimeofday(&sent, 0) < 0) {
+ perror("gettimeofday(sent)");
+ return -1;
+ }
+
+ memset(&router, 0, sizeof(router));
+ router.sin_family = AF_INET;
+#ifdef _HAVE_SIN_LEN
+ router.sin_len = sizeof(router);
+#endif
+ if (!inet_aton(host, &router.sin_addr)) {
+ hp = gethostbyname(host);
+ if (hp == 0) {
+ herror(host);
+ return -1;
+ }
+ memcpy(&router.sin_addr, hp->h_addr, sizeof(router.sin_addr));
+ }
+ router.sin_port = htons(RIP_PORT);
+
+ if (sendto(soc, &omsg_buf, omsg_len, 0,
+ (struct sockaddr *)&router, sizeof(router)) < 0) {
+ perror(host);
+ return -1;
+ }
+
+ return 0;
+}
+
+
+/*
+ * Convert string to printable characters
+ */
+static char *
+qstring(u_char *s, int len)
+{
+ static char buf[8*20+1];
+ char *p;
+ u_char *s2, c;
+
+
+ for (p = buf; len != 0 && p < &buf[sizeof(buf)-1]; len--) {
+ c = *s++;
+ if (c == '\0') {
+ for (s2 = s+1; s2 < &s[len]; s2++) {
+ if (*s2 != '\0')
+ break;
+ }
+ if (s2 >= &s[len])
+ goto exit;
+ }
+
+ if (c >= ' ' && c < 0x7f && c != '\\') {
+ *p++ = c;
+ continue;
+ }
+ *p++ = '\\';
+ switch (c) {
+ case '\\':
+ *p++ = '\\';
+ break;
+ case '\n':
+ *p++= 'n';
+ break;
+ case '\r':
+ *p++= 'r';
+ break;
+ case '\t':
+ *p++ = 't';
+ break;
+ case '\b':
+ *p++ = 'b';
+ break;
+ default:
+ p += sprintf(p,"%o",c);
+ break;
+ }
+ }
+exit:
+ *p = '\0';
+ return buf;
+}
+
+
+/*
+ * Handle an incoming RIP packet.
+ */
+static void
+rip_input(struct sockaddr_in *from,
+ int size)
+{
+ struct netinfo *n, *lim;
+ struct in_addr in;
+ const char *name;
+ char net_buf[80];
+ u_char hash[RIP_AUTH_MD5_KEY_LEN];
+ MD5_CTX md5_ctx;
+ u_char md5_authed = 0;
+ u_int mask, dmask;
+ char *sp;
+ int i;
+ struct hostent *hp;
+ struct netent *np;
+ struct netauth *na;
+
+
+ if (nflag) {
+ printf("%s:", inet_ntoa(from->sin_addr));
+ } else {
+ hp = gethostbyaddr((char*)&from->sin_addr,
+ sizeof(struct in_addr), AF_INET);
+ if (hp == 0) {
+ printf("%s:",
+ inet_ntoa(from->sin_addr));
+ } else {
+ printf("%s (%s):", hp->h_name,
+ inet_ntoa(from->sin_addr));
+ }
+ }
+ if (IMSG.rip_cmd != RIPCMD_RESPONSE) {
+ printf("\n unexpected response type %d\n", IMSG.rip_cmd);
+ return;
+ }
+ printf(" RIPv%d%s %d bytes\n", IMSG.rip_vers,
+ (IMSG.rip_vers != RIPv1 && IMSG.rip_vers != RIPv2) ? " ?" : "",
+ size);
+ if (size > MAXPACKETSIZE) {
+ if (size > (int)sizeof(imsg_buf) - (int)sizeof(*n)) {
+ printf(" at least %d bytes too long\n",
+ size-MAXPACKETSIZE);
+ size = (int)sizeof(imsg_buf) - (int)sizeof(*n);
+ } else {
+ printf(" %d bytes too long\n",
+ size-MAXPACKETSIZE);
+ }
+ } else if (size%sizeof(*n) != sizeof(struct rip)%sizeof(*n)) {
+ printf(" response of bad length=%d\n", size);
+ }
+
+ n = IMSG.rip_nets;
+ lim = (struct netinfo *)((char*)n + size) - 1;
+ for (; n <= lim; n++) {
+ name = "";
+ if (n->n_family == RIP_AF_INET) {
+ in.s_addr = n->n_dst;
+ (void)strcpy(net_buf, inet_ntoa(in));
+
+ mask = ntohl(n->n_mask);
+ dmask = mask & -mask;
+ if (mask != 0) {
+ sp = &net_buf[strlen(net_buf)];
+ if (IMSG.rip_vers == RIPv1) {
+ (void)sprintf(sp," mask=%#x ? ",mask);
+ mask = 0;
+ } else if (mask + dmask == 0) {
+ for (i = 0;
+ (i != 32
+ && ((1<<i)&mask) == 0);
+ i++)
+ continue;
+ (void)sprintf(sp, "/%d",32-i);
+ } else {
+ (void)sprintf(sp," (mask %#x)", mask);
+ }
+ }
+
+ if (!nflag) {
+ if (mask == 0) {
+ mask = std_mask(in.s_addr);
+ if ((ntohl(in.s_addr) & ~mask) != 0)
+ mask = 0;
+ }
+ /* Without a netmask, do not worry about
+ * whether the destination is a host or a
+ * network. Try both and use the first name
+ * we get.
+ *
+ * If we have a netmask we can make a
+ * good guess.
+ */
+ if ((in.s_addr & ~mask) == 0) {
+ np = getnetbyaddr((long)in.s_addr,
+ AF_INET);
+ if (np != 0)
+ name = np->n_name;
+ else if (in.s_addr == 0)
+ name = "default";
+ }
+ if (name[0] == '\0'
+ && ((in.s_addr & ~mask) != 0
+ || mask == 0xffffffff)) {
+ hp = gethostbyaddr((char*)&in,
+ sizeof(in),
+ AF_INET);
+ if (hp != 0)
+ name = hp->h_name;
+ }
+ }
+
+ } else if (n->n_family == RIP_AF_AUTH) {
+ na = (struct netauth*)n;
+ if (na->a_type == RIP_AUTH_PW
+ && n == IMSG.rip_nets) {
+ (void)printf(" Password Authentication:"
+ " \"%s\"\n",
+ qstring(na->au.au_pw,
+ RIP_AUTH_PW_LEN));
+ continue;
+ }
+
+ if (na->a_type == RIP_AUTH_MD5
+ && n == IMSG.rip_nets) {
+ (void)printf(" MD5 Auth"
+ " len=%d KeyID=%d"
+ " auth_len=%d"
+ " seqno=%#x"
+ " rsvd=%#x,%#x\n",
+ ntohs(na->au.a_md5.md5_pkt_len),
+ na->au.a_md5.md5_keyid,
+ na->au.a_md5.md5_auth_len,
+ (int)ntohl(na->au.a_md5.md5_seqno),
+ na->au.a_md5.rsvd[0],
+ na->au.a_md5.rsvd[1]);
+ md5_authed = 1;
+ continue;
+ }
+ (void)printf(" Authentication type %d: ",
+ ntohs(na->a_type));
+ for (i = 0; i < (int)sizeof(na->au.au_pw); i++)
+ (void)printf("%02x ", na->au.au_pw[i]);
+ putc('\n', stdout);
+ if (md5_authed && n+1 > lim
+ && na->a_type == ntohs(1)) {
+ MD5Init(&md5_ctx);
+ MD5Update(&md5_ctx, (u_char *)&IMSG,
+ (char *)na-(char *)&IMSG
+ +RIP_AUTH_MD5_HASH_XTRA);
+ MD5Update(&md5_ctx, (u_char *)passwd,
+ RIP_AUTH_MD5_KEY_LEN);
+ MD5Final(hash, &md5_ctx);
+ (void)printf(" %s hash\n",
+ memcmp(hash, na->au.au_pw,
+ sizeof(hash))
+ ? "WRONG" : "correct");
+ }
+ continue;
+
+ } else {
+ (void)sprintf(net_buf, "(af %#x) %d.%d.%d.%d",
+ ntohs(n->n_family),
+ (u_char)(n->n_dst >> 24),
+ (u_char)(n->n_dst >> 16),
+ (u_char)(n->n_dst >> 8),
+ (u_char)n->n_dst);
+ }
+
+ (void)printf(" %-18s metric %2d %-10s",
+ net_buf, (int)ntohl(n->n_metric), name);
+
+ if (n->n_nhop != 0) {
+ in.s_addr = n->n_nhop;
+ if (nflag)
+ hp = 0;
+ else
+ hp = gethostbyaddr((char*)&in, sizeof(in),
+ AF_INET);
+ (void)printf(" nhop=%-15s%s",
+ (hp != 0) ? hp->h_name : inet_ntoa(in),
+ (IMSG.rip_vers == RIPv1) ? " ?" : "");
+ }
+ if (n->n_tag != 0)
+ (void)printf(" tag=%#x%s", n->n_tag,
+ (IMSG.rip_vers == RIPv1) ? " ?" : "");
+ putc('\n', stdout);
+ }
+}
+
+
+/* Return the classical netmask for an IP address.
+ */
+static u_int
+std_mask(u_int addr) /* in network order */
+{
+ addr = ntohl(addr); /* was a host, not a network */
+
+ if (addr == 0) /* default route has mask 0 */
+ return 0;
+ if (IN_CLASSA(addr))
+ return IN_CLASSA_NET;
+ if (IN_CLASSB(addr))
+ return IN_CLASSB_NET;
+ return IN_CLASSC_NET;
+}
+
+
+/* get a network number as a name or a number, with an optional "/xx"
+ * netmask.
+ */
+static int /* 0=bad */
+getnet(char *name,
+ struct netinfo *rt)
+{
+ int i;
+ struct netent *nentp;
+ u_int mask;
+ struct in_addr in;
+ char hname[MAXHOSTNAMELEN+1];
+ char *mname, *p;
+
+
+ /* Detect and separate "1.2.3.4/24"
+ */
+ if (0 != (mname = strrchr(name,'/'))) {
+ i = (int)(mname - name);
+ if (i > (int)sizeof(hname)-1) /* name too long */
+ return 0;
+ memmove(hname, name, i);
+ hname[i] = '\0';
+ mname++;
+ name = hname;
+ }
+
+ nentp = getnetbyname(name);
+ if (nentp != 0) {
+ in.s_addr = nentp->n_net;
+ } else if (inet_aton(name, &in) == 1) {
+ in.s_addr = ntohl(in.s_addr);
+ } else {
+ return 0;
+ }
+
+ if (mname == 0) {
+ mask = std_mask(in.s_addr);
+ if ((~mask & in.s_addr) != 0)
+ mask = 0xffffffff;
+ } else {
+ mask = (u_int)strtoul(mname, &p, 0);
+ if (*p != '\0' || mask > 32)
+ return 0;
+ mask = 0xffffffff << (32-mask);
+ }
+
+ rt->n_dst = htonl(in.s_addr);
+ rt->n_family = RIP_AF_INET;
+ rt->n_mask = htonl(mask);
+ return 1;
+}
+
+
+/* strtok(), but honoring backslash
+ */
+static int /* -1=bad */
+parse_quote(char **linep,
+ const char *delims,
+ char *delimp,
+ char *buf,
+ int lim)
+{
+ char c, *pc;
+ const char *p;
+
+
+ pc = *linep;
+ if (*pc == '\0')
+ return -1;
+
+ for (;;) {
+ if (lim == 0)
+ return -1;
+ c = *pc++;
+ if (c == '\0')
+ break;
+
+ if (c == '\\' && *pc != '\0') {
+ if ((c = *pc++) == 'n') {
+ c = '\n';
+ } else if (c == 'r') {
+ c = '\r';
+ } else if (c == 't') {
+ c = '\t';
+ } else if (c == 'b') {
+ c = '\b';
+ } else if (c >= '0' && c <= '7') {
+ c -= '0';
+ if (*pc >= '0' && *pc <= '7') {
+ c = (c<<3)+(*pc++ - '0');
+ if (*pc >= '0' && *pc <= '7')
+ c = (c<<3)+(*pc++ - '0');
+ }
+ }
+
+ } else {
+ for (p = delims; *p != '\0'; ++p) {
+ if (*p == c)
+ goto exit;
+ }
+ }
+
+ *buf++ = c;
+ --lim;
+ }
+exit:
+ if (delimp != 0)
+ *delimp = c;
+ *linep = pc-1;
+ if (lim != 0)
+ *buf = '\0';
+ return 0;
+}
diff --git a/sbin/routed/table.c b/sbin/routed/table.c
new file mode 100644
index 0000000..ec36989
--- /dev/null
+++ b/sbin/routed/table.c
@@ -0,0 +1,2155 @@
+/*
+ * Copyright (c) 1983, 1988, 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.
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+#include "defs.h"
+
+#ifdef __NetBSD__
+__RCSID("$NetBSD$");
+#elif defined(__FreeBSD__)
+__RCSID("$FreeBSD$");
+#else
+__RCSID("$Revision: 2.27 $");
+#ident "$Revision: 2.27 $"
+#endif
+
+static struct rt_spare *rts_better(struct rt_entry *);
+static struct rt_spare rts_empty = {0,0,0,HOPCNT_INFINITY,0,0,0};
+static void set_need_flash(void);
+#ifdef _HAVE_SIN_LEN
+static void masktrim(struct sockaddr_in *ap);
+#else
+static void masktrim(struct sockaddr_in_new *ap);
+#endif
+static void rtbad(struct rt_entry *);
+
+
+struct radix_node_head *rhead; /* root of the radix tree */
+
+int need_flash = 1; /* flash update needed
+ * start =1 to suppress the 1st
+ */
+
+struct timeval age_timer; /* next check of old routes */
+struct timeval need_kern = { /* need to update kernel table */
+ EPOCH+MIN_WAITTIME-1, 0
+};
+
+int stopint;
+
+int total_routes;
+
+/* zap any old routes through this gateway */
+static naddr age_bad_gate;
+
+
+/* It is desirable to "aggregate" routes, to combine differing routes of
+ * the same metric and next hop into a common route with a smaller netmask
+ * or to suppress redundant routes, routes that add no information to
+ * routes with smaller netmasks.
+ *
+ * A route is redundant if and only if any and all routes with smaller
+ * but matching netmasks and nets are the same. Since routes are
+ * kept sorted in the radix tree, redundant routes always come second.
+ *
+ * There are two kinds of aggregations. First, two routes of the same bit
+ * mask and differing only in the least significant bit of the network
+ * number can be combined into a single route with a coarser mask.
+ *
+ * Second, a route can be suppressed in favor of another route with a more
+ * coarse mask provided no incompatible routes with intermediate masks
+ * are present. The second kind of aggregation involves suppressing routes.
+ * A route must not be suppressed if an incompatible route exists with
+ * an intermediate mask, since the suppressed route would be covered
+ * by the intermediate.
+ *
+ * This code relies on the radix tree walk encountering routes
+ * sorted first by address, with the smallest address first.
+ */
+
+static struct ag_info ag_slots[NUM_AG_SLOTS], *ag_avail, *ag_corsest, *ag_finest;
+
+/* #define DEBUG_AG */
+#ifdef DEBUG_AG
+#define CHECK_AG() {int acnt = 0; struct ag_info *cag; \
+ for (cag = ag_avail; cag != 0; cag = cag->ag_fine) \
+ acnt++; \
+ for (cag = ag_corsest; cag != 0; cag = cag->ag_fine) \
+ acnt++; \
+ if (acnt != NUM_AG_SLOTS) { \
+ (void)fflush(stderr); \
+ abort(); \
+ } \
+}
+#else
+#define CHECK_AG()
+#endif
+
+
+/* Output the contents of an aggregation table slot.
+ * This function must always be immediately followed with the deletion
+ * of the target slot.
+ */
+static void
+ag_out(struct ag_info *ag,
+ void (*out)(struct ag_info *))
+{
+ struct ag_info *ag_cors;
+ naddr bit;
+
+
+ /* Forget it if this route should not be output for split-horizon. */
+ if (ag->ag_state & AGS_SPLIT_HZ)
+ return;
+
+ /* If we output both the even and odd twins, then the immediate parent,
+ * if it is present, is redundant, unless the parent manages to
+ * aggregate into something coarser.
+ * On successive calls, this code detects the even and odd twins,
+ * and marks the parent.
+ *
+ * Note that the order in which the radix tree code emits routes
+ * ensures that the twins are seen before the parent is emitted.
+ */
+ ag_cors = ag->ag_cors;
+ if (ag_cors != 0
+ && ag_cors->ag_mask == ag->ag_mask<<1
+ && ag_cors->ag_dst_h == (ag->ag_dst_h & ag_cors->ag_mask)) {
+ ag_cors->ag_state |= ((ag_cors->ag_dst_h == ag->ag_dst_h)
+ ? AGS_REDUN0
+ : AGS_REDUN1);
+ }
+
+ /* Skip it if this route is itself redundant.
+ *
+ * It is ok to change the contents of the slot here, since it is
+ * always deleted next.
+ */
+ if (ag->ag_state & AGS_REDUN0) {
+ if (ag->ag_state & AGS_REDUN1)
+ return; /* quit if fully redundant */
+ /* make it finer if it is half-redundant */
+ bit = (-ag->ag_mask) >> 1;
+ ag->ag_dst_h |= bit;
+ ag->ag_mask |= bit;
+
+ } else if (ag->ag_state & AGS_REDUN1) {
+ /* make it finer if it is half-redundant */
+ bit = (-ag->ag_mask) >> 1;
+ ag->ag_mask |= bit;
+ }
+ out(ag);
+}
+
+
+static void
+ag_del(struct ag_info *ag)
+{
+ CHECK_AG();
+
+ if (ag->ag_cors == 0)
+ ag_corsest = ag->ag_fine;
+ else
+ ag->ag_cors->ag_fine = ag->ag_fine;
+
+ if (ag->ag_fine == 0)
+ ag_finest = ag->ag_cors;
+ else
+ ag->ag_fine->ag_cors = ag->ag_cors;
+
+ ag->ag_fine = ag_avail;
+ ag_avail = ag;
+
+ CHECK_AG();
+}
+
+
+/* Flush routes waiting for aggregation.
+ * This must not suppress a route unless it is known that among all
+ * routes with coarser masks that match it, the one with the longest
+ * mask is appropriate. This is ensured by scanning the routes
+ * in lexical order, and with the most restrictive mask first
+ * among routes to the same destination.
+ */
+void
+ag_flush(naddr lim_dst_h, /* flush routes to here */
+ naddr lim_mask, /* matching this mask */
+ void (*out)(struct ag_info *))
+{
+ struct ag_info *ag, *ag_cors;
+ naddr dst_h;
+
+
+ for (ag = ag_finest;
+ ag != 0 && ag->ag_mask >= lim_mask;
+ ag = ag_cors) {
+ ag_cors = ag->ag_cors;
+
+ /* work on only the specified routes */
+ dst_h = ag->ag_dst_h;
+ if ((dst_h & lim_mask) != lim_dst_h)
+ continue;
+
+ if (!(ag->ag_state & AGS_SUPPRESS))
+ ag_out(ag, out);
+
+ else for ( ; ; ag_cors = ag_cors->ag_cors) {
+ /* Look for a route that can suppress the
+ * current route */
+ if (ag_cors == 0) {
+ /* failed, so output it and look for
+ * another route to work on
+ */
+ ag_out(ag, out);
+ break;
+ }
+
+ if ((dst_h & ag_cors->ag_mask) == ag_cors->ag_dst_h) {
+ /* We found a route with a coarser mask that
+ * aggregates the current target.
+ *
+ * If it has a different next hop, it
+ * cannot replace the target, so output
+ * the target.
+ */
+ if (ag->ag_gate != ag_cors->ag_gate
+ && !(ag->ag_state & AGS_FINE_GATE)
+ && !(ag_cors->ag_state & AGS_CORS_GATE)) {
+ ag_out(ag, out);
+ break;
+ }
+
+ /* If the coarse route has a good enough
+ * metric, it suppresses the target.
+ * If the suppressed target was redundant,
+ * then mark the suppressor redundant.
+ */
+ if (ag_cors->ag_pref <= ag->ag_pref) {
+ if (AG_IS_REDUN(ag->ag_state)
+ && ag_cors->ag_mask==ag->ag_mask<<1) {
+ if (ag_cors->ag_dst_h == dst_h)
+ ag_cors->ag_state |= AGS_REDUN0;
+ else
+ ag_cors->ag_state |= AGS_REDUN1;
+ }
+ if (ag->ag_tag != ag_cors->ag_tag)
+ ag_cors->ag_tag = 0;
+ if (ag->ag_nhop != ag_cors->ag_nhop)
+ ag_cors->ag_nhop = 0;
+ break;
+ }
+ }
+ }
+
+ /* That route has either been output or suppressed */
+ ag_cors = ag->ag_cors;
+ ag_del(ag);
+ }
+
+ CHECK_AG();
+}
+
+
+/* Try to aggregate a route with previous routes.
+ */
+void
+ag_check(naddr dst,
+ naddr mask,
+ naddr gate,
+ naddr nhop,
+ char metric,
+ char pref,
+ u_int new_seqno,
+ u_short tag,
+ u_short state,
+ void (*out)(struct ag_info *)) /* output using this */
+{
+ struct ag_info *ag, *nag, *ag_cors;
+ naddr xaddr;
+ int x;
+
+ dst = ntohl(dst);
+
+ /* Punt non-contiguous subnet masks.
+ *
+ * (X & -X) contains a single bit if and only if X is a power of 2.
+ * (X + (X & -X)) == 0 if and only if X is a power of 2.
+ */
+ if ((mask & -mask) + mask != 0) {
+ struct ag_info nc_ag;
+
+ nc_ag.ag_dst_h = dst;
+ nc_ag.ag_mask = mask;
+ nc_ag.ag_gate = gate;
+ nc_ag.ag_nhop = nhop;
+ nc_ag.ag_metric = metric;
+ nc_ag.ag_pref = pref;
+ nc_ag.ag_tag = tag;
+ nc_ag.ag_state = state;
+ nc_ag.ag_seqno = new_seqno;
+ out(&nc_ag);
+ return;
+ }
+
+ /* Search for the right slot in the aggregation table.
+ */
+ ag_cors = 0;
+ ag = ag_corsest;
+ while (ag != 0) {
+ if (ag->ag_mask >= mask)
+ break;
+
+ /* Suppress old routes (i.e. combine with compatible routes
+ * with coarser masks) as we look for the right slot in the
+ * aggregation table for the new route.
+ * A route to an address less than the current destination
+ * will not be affected by the current route or any route
+ * seen hereafter. That means it is safe to suppress it.
+ * This check keeps poor routes (e.g. with large hop counts)
+ * from preventing suppression of finer routes.
+ */
+ if (ag_cors != 0
+ && ag->ag_dst_h < dst
+ && (ag->ag_state & AGS_SUPPRESS)
+ && ag_cors->ag_pref <= ag->ag_pref
+ && (ag->ag_dst_h & ag_cors->ag_mask) == ag_cors->ag_dst_h
+ && (ag_cors->ag_gate == ag->ag_gate
+ || (ag->ag_state & AGS_FINE_GATE)
+ || (ag_cors->ag_state & AGS_CORS_GATE))) {
+ /* If the suppressed target was redundant,
+ * then mark the suppressor redundant.
+ */
+ if (AG_IS_REDUN(ag->ag_state)
+ && ag_cors->ag_mask == ag->ag_mask<<1) {
+ if (ag_cors->ag_dst_h == dst)
+ ag_cors->ag_state |= AGS_REDUN0;
+ else
+ ag_cors->ag_state |= AGS_REDUN1;
+ }
+ if (ag->ag_tag != ag_cors->ag_tag)
+ ag_cors->ag_tag = 0;
+ if (ag->ag_nhop != ag_cors->ag_nhop)
+ ag_cors->ag_nhop = 0;
+ ag_del(ag);
+ CHECK_AG();
+ } else {
+ ag_cors = ag;
+ }
+ ag = ag_cors->ag_fine;
+ }
+
+ /* If we find the even/odd twin of the new route, and if the
+ * masks and so forth are equal, we can aggregate them.
+ * We can probably promote one of the pair.
+ *
+ * Since the routes are encountered in lexical order,
+ * the new route must be odd. However, the second or later
+ * times around this loop, it could be the even twin promoted
+ * from the even/odd pair of twins of the finer route.
+ */
+ while (ag != 0
+ && ag->ag_mask == mask
+ && ((ag->ag_dst_h ^ dst) & (mask<<1)) == 0) {
+
+ /* Here we know the target route and the route in the current
+ * slot have the same netmasks and differ by at most the
+ * last bit. They are either for the same destination, or
+ * for an even/odd pair of destinations.
+ */
+ if (ag->ag_dst_h == dst) {
+ /* We have two routes to the same destination.
+ * Routes are encountered in lexical order, so a
+ * route is never promoted until the parent route is
+ * already present. So we know that the new route is
+ * a promoted (or aggregated) pair and the route
+ * already in the slot is the explicit route.
+ *
+ * Prefer the best route if their metrics differ,
+ * or the aggregated one if not, following a sort
+ * of longest-match rule.
+ */
+ if (pref <= ag->ag_pref) {
+ ag->ag_gate = gate;
+ ag->ag_nhop = nhop;
+ ag->ag_tag = tag;
+ ag->ag_metric = metric;
+ ag->ag_pref = pref;
+ if (ag->ag_seqno < new_seqno)
+ ag->ag_seqno = new_seqno;
+ x = ag->ag_state;
+ ag->ag_state = state;
+ state = x;
+ }
+
+ /* Some bits are set if they are set on either route,
+ * except when the route is for an interface.
+ */
+ if (!(ag->ag_state & AGS_IF))
+ ag->ag_state |= (state & (AGS_AGGREGATE_EITHER
+ | AGS_REDUN0
+ | AGS_REDUN1));
+ return;
+ }
+
+ /* If one of the routes can be promoted and the other can
+ * be suppressed, it may be possible to combine them or
+ * worthwhile to promote one.
+ *
+ * Any route that can be promoted is always
+ * marked to be eligible to be suppressed.
+ */
+ if (!((state & AGS_AGGREGATE)
+ && (ag->ag_state & AGS_SUPPRESS))
+ && !((ag->ag_state & AGS_AGGREGATE)
+ && (state & AGS_SUPPRESS)))
+ break;
+
+ /* A pair of even/odd twin routes can be combined
+ * if either is redundant, or if they are via the
+ * same gateway and have the same metric.
+ */
+ if (AG_IS_REDUN(ag->ag_state)
+ || AG_IS_REDUN(state)
+ || (ag->ag_gate == gate
+ && ag->ag_pref == pref
+ && (state & ag->ag_state & AGS_AGGREGATE) != 0)) {
+
+ /* We have both the even and odd pairs.
+ * Since the routes are encountered in order,
+ * the route in the slot must be the even twin.
+ *
+ * Combine and promote (aggregate) the pair of routes.
+ */
+ if (new_seqno < ag->ag_seqno)
+ new_seqno = ag->ag_seqno;
+ if (!AG_IS_REDUN(state))
+ state &= ~AGS_REDUN1;
+ if (AG_IS_REDUN(ag->ag_state))
+ state |= AGS_REDUN0;
+ else
+ state &= ~AGS_REDUN0;
+ state |= (ag->ag_state & AGS_AGGREGATE_EITHER);
+ if (ag->ag_tag != tag)
+ tag = 0;
+ if (ag->ag_nhop != nhop)
+ nhop = 0;
+
+ /* Get rid of the even twin that was already
+ * in the slot.
+ */
+ ag_del(ag);
+
+ } else if (ag->ag_pref >= pref
+ && (ag->ag_state & AGS_AGGREGATE)) {
+ /* If we cannot combine the pair, maybe the route
+ * with the worse metric can be promoted.
+ *
+ * Promote the old, even twin, by giving its slot
+ * in the table to the new, odd twin.
+ */
+ ag->ag_dst_h = dst;
+
+ xaddr = ag->ag_gate;
+ ag->ag_gate = gate;
+ gate = xaddr;
+
+ xaddr = ag->ag_nhop;
+ ag->ag_nhop = nhop;
+ nhop = xaddr;
+
+ x = ag->ag_tag;
+ ag->ag_tag = tag;
+ tag = x;
+
+ /* The promoted route is even-redundant only if the
+ * even twin was fully redundant. It is not
+ * odd-redundant because the odd-twin will still be
+ * in the table.
+ */
+ x = ag->ag_state;
+ if (!AG_IS_REDUN(x))
+ x &= ~AGS_REDUN0;
+ x &= ~AGS_REDUN1;
+ ag->ag_state = state;
+ state = x;
+
+ x = ag->ag_metric;
+ ag->ag_metric = metric;
+ metric = x;
+
+ x = ag->ag_pref;
+ ag->ag_pref = pref;
+ pref = x;
+
+ /* take the newest sequence number */
+ if (new_seqno <= ag->ag_seqno)
+ new_seqno = ag->ag_seqno;
+ else
+ ag->ag_seqno = new_seqno;
+
+ } else {
+ if (!(state & AGS_AGGREGATE))
+ break; /* cannot promote either twin */
+
+ /* Promote the new, odd twin by shaving its
+ * mask and address.
+ * The promoted route is odd-redundant only if the
+ * odd twin was fully redundant. It is not
+ * even-redundant because the even twin is still in
+ * the table.
+ */
+ if (!AG_IS_REDUN(state))
+ state &= ~AGS_REDUN1;
+ state &= ~AGS_REDUN0;
+ if (new_seqno < ag->ag_seqno)
+ new_seqno = ag->ag_seqno;
+ else
+ ag->ag_seqno = new_seqno;
+ }
+
+ mask <<= 1;
+ dst &= mask;
+
+ if (ag_cors == 0) {
+ ag = ag_corsest;
+ break;
+ }
+ ag = ag_cors;
+ ag_cors = ag->ag_cors;
+ }
+
+ /* When we can no longer promote and combine routes,
+ * flush the old route in the target slot. Also flush
+ * any finer routes that we know will never be aggregated by
+ * the new route.
+ *
+ * In case we moved toward coarser masks,
+ * get back where we belong
+ */
+ if (ag != 0
+ && ag->ag_mask < mask) {
+ ag_cors = ag;
+ ag = ag->ag_fine;
+ }
+
+ /* Empty the target slot
+ */
+ if (ag != 0 && ag->ag_mask == mask) {
+ ag_flush(ag->ag_dst_h, ag->ag_mask, out);
+ ag = (ag_cors == 0) ? ag_corsest : ag_cors->ag_fine;
+ }
+
+#ifdef DEBUG_AG
+ (void)fflush(stderr);
+ if (ag == 0 && ag_cors != ag_finest)
+ abort();
+ if (ag_cors == 0 && ag != ag_corsest)
+ abort();
+ if (ag != 0 && ag->ag_cors != ag_cors)
+ abort();
+ if (ag_cors != 0 && ag_cors->ag_fine != ag)
+ abort();
+ CHECK_AG();
+#endif
+
+ /* Save the new route on the end of the table.
+ */
+ nag = ag_avail;
+ ag_avail = nag->ag_fine;
+
+ nag->ag_dst_h = dst;
+ nag->ag_mask = mask;
+ nag->ag_gate = gate;
+ nag->ag_nhop = nhop;
+ nag->ag_metric = metric;
+ nag->ag_pref = pref;
+ nag->ag_tag = tag;
+ nag->ag_state = state;
+ nag->ag_seqno = new_seqno;
+
+ nag->ag_fine = ag;
+ if (ag != 0)
+ ag->ag_cors = nag;
+ else
+ ag_finest = nag;
+ nag->ag_cors = ag_cors;
+ if (ag_cors == 0)
+ ag_corsest = nag;
+ else
+ ag_cors->ag_fine = nag;
+ CHECK_AG();
+}
+
+static const char *
+rtm_type_name(u_char type)
+{
+ static const char * const rtm_types[] = {
+ "RTM_ADD",
+ "RTM_DELETE",
+ "RTM_CHANGE",
+ "RTM_GET",
+ "RTM_LOSING",
+ "RTM_REDIRECT",
+ "RTM_MISS",
+ "RTM_LOCK",
+ "RTM_OLDADD",
+ "RTM_OLDDEL",
+ "RTM_RESOLVE",
+ "RTM_NEWADDR",
+ "RTM_DELADDR",
+#ifdef RTM_OIFINFO
+ "RTM_OIFINFO",
+#endif
+ "RTM_IFINFO",
+ "RTM_NEWMADDR",
+ "RTM_DELMADDR"
+ };
+#define NEW_RTM_PAT "RTM type %#x"
+ static char name0[sizeof(NEW_RTM_PAT)+2];
+
+
+ if (type > sizeof(rtm_types)/sizeof(rtm_types[0])
+ || type == 0) {
+ snprintf(name0, sizeof(name0), NEW_RTM_PAT, type);
+ return name0;
+ } else {
+ return rtm_types[type-1];
+ }
+#undef NEW_RTM_PAT
+}
+
+
+/* Trim a mask in a sockaddr
+ * Produce a length of 0 for an address of 0.
+ * Otherwise produce the index of the first zero byte.
+ */
+void
+#ifdef _HAVE_SIN_LEN
+masktrim(struct sockaddr_in *ap)
+#else
+masktrim(struct sockaddr_in_new *ap)
+#endif
+{
+ char *cp;
+
+ if (ap->sin_addr.s_addr == 0) {
+ ap->sin_len = 0;
+ return;
+ }
+ cp = (char *)(&ap->sin_addr.s_addr+1);
+ while (*--cp == 0)
+ continue;
+ ap->sin_len = cp - (char*)ap + 1;
+}
+
+
+/* Tell the kernel to add, delete or change a route
+ */
+static void
+rtioctl(int action, /* RTM_DELETE, etc */
+ naddr dst,
+ naddr gate,
+ naddr mask,
+ int metric,
+ int flags)
+{
+ struct {
+ struct rt_msghdr w_rtm;
+ struct sockaddr_in w_dst;
+ struct sockaddr_in w_gate;
+#ifdef _HAVE_SA_LEN
+ struct sockaddr_in w_mask;
+#else
+ struct sockaddr_in_new w_mask;
+#endif
+ } w;
+ long cc;
+# define PAT " %-10s %s metric=%d flags=%#x"
+# define ARGS rtm_type_name(action), rtname(dst,mask,gate), metric, flags
+
+again:
+ memset(&w, 0, sizeof(w));
+ w.w_rtm.rtm_msglen = sizeof(w);
+ w.w_rtm.rtm_version = RTM_VERSION;
+ w.w_rtm.rtm_type = action;
+ w.w_rtm.rtm_flags = flags;
+ w.w_rtm.rtm_seq = ++rt_sock_seqno;
+ w.w_rtm.rtm_addrs = RTA_DST|RTA_GATEWAY;
+ if (metric != 0 || action == RTM_CHANGE) {
+ w.w_rtm.rtm_rmx.rmx_hopcount = metric;
+ w.w_rtm.rtm_inits |= RTV_HOPCOUNT;
+ }
+ w.w_dst.sin_family = AF_INET;
+ w.w_dst.sin_addr.s_addr = dst;
+ w.w_gate.sin_family = AF_INET;
+ w.w_gate.sin_addr.s_addr = gate;
+#ifdef _HAVE_SA_LEN
+ w.w_dst.sin_len = sizeof(w.w_dst);
+ w.w_gate.sin_len = sizeof(w.w_gate);
+#endif
+ if (mask == HOST_MASK) {
+ w.w_rtm.rtm_flags |= RTF_HOST;
+ w.w_rtm.rtm_msglen -= sizeof(w.w_mask);
+ } else {
+ w.w_rtm.rtm_addrs |= RTA_NETMASK;
+ w.w_mask.sin_addr.s_addr = htonl(mask);
+#ifdef _HAVE_SA_LEN
+ masktrim(&w.w_mask);
+ if (w.w_mask.sin_len == 0)
+ w.w_mask.sin_len = sizeof(long);
+ w.w_rtm.rtm_msglen -= (sizeof(w.w_mask) - w.w_mask.sin_len);
+#endif
+ }
+
+#ifndef NO_INSTALL
+ cc = write(rt_sock, &w, w.w_rtm.rtm_msglen);
+ if (cc < 0) {
+ if (errno == ESRCH
+ && (action == RTM_CHANGE || action == RTM_DELETE)) {
+ trace_act("route disappeared before" PAT, ARGS);
+ if (action == RTM_CHANGE) {
+ action = RTM_ADD;
+ goto again;
+ }
+ return;
+ }
+ msglog("write(rt_sock)" PAT ": %s", ARGS, strerror(errno));
+ return;
+ } else if (cc != w.w_rtm.rtm_msglen) {
+ msglog("write(rt_sock) wrote %ld instead of %d for" PAT,
+ cc, w.w_rtm.rtm_msglen, ARGS);
+ return;
+ }
+#endif
+ if (TRACEKERNEL)
+ trace_misc("write kernel" PAT, ARGS);
+#undef PAT
+#undef ARGS
+}
+
+
+#define KHASH_SIZE 71 /* should be prime */
+#define KHASH(a,m) khash_bins[((a) ^ (m)) % KHASH_SIZE]
+static struct khash {
+ struct khash *k_next;
+ naddr k_dst;
+ naddr k_mask;
+ naddr k_gate;
+ short k_metric;
+ u_short k_state;
+#define KS_NEW 0x001
+#define KS_DELETE 0x002 /* need to delete the route */
+#define KS_ADD 0x004 /* add to the kernel */
+#define KS_CHANGE 0x008 /* tell kernel to change the route */
+#define KS_DEL_ADD 0x010 /* delete & add to change the kernel */
+#define KS_STATIC 0x020 /* Static flag in kernel */
+#define KS_GATEWAY 0x040 /* G flag in kernel */
+#define KS_DYNAMIC 0x080 /* result of redirect */
+#define KS_DELETED 0x100 /* already deleted from kernel */
+#define KS_CHECK 0x200
+ time_t k_keep;
+#define K_KEEP_LIM 30
+ time_t k_redirect_time; /* when redirected route 1st seen */
+} *khash_bins[KHASH_SIZE];
+
+
+static struct khash*
+kern_find(naddr dst, naddr mask, struct khash ***ppk)
+{
+ struct khash *k, **pk;
+
+ for (pk = &KHASH(dst,mask); (k = *pk) != 0; pk = &k->k_next) {
+ if (k->k_dst == dst && k->k_mask == mask)
+ break;
+ }
+ if (ppk != 0)
+ *ppk = pk;
+ return k;
+}
+
+
+static struct khash*
+kern_add(naddr dst, naddr mask)
+{
+ struct khash *k, **pk;
+
+ k = kern_find(dst, mask, &pk);
+ if (k != 0)
+ return k;
+
+ k = (struct khash *)rtmalloc(sizeof(*k), "kern_add");
+
+ memset(k, 0, sizeof(*k));
+ k->k_dst = dst;
+ k->k_mask = mask;
+ k->k_state = KS_NEW;
+ k->k_keep = now.tv_sec;
+ *pk = k;
+
+ return k;
+}
+
+
+/* If a kernel route has a non-zero metric, check that it is still in the
+ * daemon table, and not deleted by interfaces coming and going.
+ */
+static void
+kern_check_static(struct khash *k,
+ struct interface *ifp)
+{
+ struct rt_entry *rt;
+ struct rt_spare new;
+
+ if (k->k_metric == 0)
+ return;
+
+ memset(&new, 0, sizeof(new));
+ new.rts_ifp = ifp;
+ new.rts_gate = k->k_gate;
+ new.rts_router = (ifp != 0) ? ifp->int_addr : loopaddr;
+ new.rts_metric = k->k_metric;
+ new.rts_time = now.tv_sec;
+
+ rt = rtget(k->k_dst, k->k_mask);
+ if (rt != 0) {
+ if (!(rt->rt_state & RS_STATIC))
+ rtchange(rt, rt->rt_state | RS_STATIC, &new, 0);
+ } else {
+ rtadd(k->k_dst, k->k_mask, RS_STATIC, &new);
+ }
+}
+
+
+/* operate on a kernel entry
+ */
+static void
+kern_ioctl(struct khash *k,
+ int action, /* RTM_DELETE, etc */
+ int flags)
+
+{
+ switch (action) {
+ case RTM_DELETE:
+ k->k_state &= ~KS_DYNAMIC;
+ if (k->k_state & KS_DELETED)
+ return;
+ k->k_state |= KS_DELETED;
+ break;
+ case RTM_ADD:
+ k->k_state &= ~KS_DELETED;
+ break;
+ case RTM_CHANGE:
+ if (k->k_state & KS_DELETED) {
+ action = RTM_ADD;
+ k->k_state &= ~KS_DELETED;
+ }
+ break;
+ }
+
+ rtioctl(action, k->k_dst, k->k_gate, k->k_mask, k->k_metric, flags);
+}
+
+
+/* add a route the kernel told us
+ */
+static void
+rtm_add(struct rt_msghdr *rtm,
+ struct rt_addrinfo *info,
+ time_t keep)
+{
+ struct khash *k;
+ struct interface *ifp;
+ naddr mask;
+
+
+ if (rtm->rtm_flags & RTF_HOST) {
+ mask = HOST_MASK;
+ } else if (INFO_MASK(info) != 0) {
+ mask = ntohl(S_ADDR(INFO_MASK(info)));
+ } else {
+ msglog("ignore %s without mask", rtm_type_name(rtm->rtm_type));
+ return;
+ }
+
+ k = kern_add(S_ADDR(INFO_DST(info)), mask);
+ if (k->k_state & KS_NEW)
+ k->k_keep = now.tv_sec+keep;
+ if (INFO_GATE(info) == 0) {
+ trace_act("note %s without gateway",
+ rtm_type_name(rtm->rtm_type));
+ k->k_metric = HOPCNT_INFINITY;
+ } else if (INFO_GATE(info)->sa_family != AF_INET) {
+ trace_act("note %s with gateway AF=%d",
+ rtm_type_name(rtm->rtm_type),
+ INFO_GATE(info)->sa_family);
+ k->k_metric = HOPCNT_INFINITY;
+ } else {
+ k->k_gate = S_ADDR(INFO_GATE(info));
+ k->k_metric = rtm->rtm_rmx.rmx_hopcount;
+ if (k->k_metric < 0)
+ k->k_metric = 0;
+ else if (k->k_metric > HOPCNT_INFINITY-1)
+ k->k_metric = HOPCNT_INFINITY-1;
+ }
+ k->k_state &= ~(KS_DELETE | KS_ADD | KS_CHANGE | KS_DEL_ADD
+ | KS_DELETED | KS_GATEWAY | KS_STATIC
+ | KS_NEW | KS_CHECK);
+ if (rtm->rtm_flags & RTF_GATEWAY)
+ k->k_state |= KS_GATEWAY;
+ if (rtm->rtm_flags & RTF_STATIC)
+ k->k_state |= KS_STATIC;
+
+ if (0 != (rtm->rtm_flags & (RTF_DYNAMIC | RTF_MODIFIED))) {
+ if (INFO_AUTHOR(info) != 0
+ && INFO_AUTHOR(info)->sa_family == AF_INET)
+ ifp = iflookup(S_ADDR(INFO_AUTHOR(info)));
+ else
+ ifp = 0;
+ if (supplier
+ && (ifp == 0 || !(ifp->int_state & IS_REDIRECT_OK))) {
+ /* Routers are not supposed to listen to redirects,
+ * so delete it if it came via an unknown interface
+ * or the interface does not have special permission.
+ */
+ k->k_state &= ~KS_DYNAMIC;
+ k->k_state |= KS_DELETE;
+ LIM_SEC(need_kern, 0);
+ trace_act("mark for deletion redirected %s --> %s"
+ " via %s",
+ addrname(k->k_dst, k->k_mask, 0),
+ naddr_ntoa(k->k_gate),
+ ifp ? ifp->int_name : "unknown interface");
+ } else {
+ k->k_state |= KS_DYNAMIC;
+ k->k_redirect_time = now.tv_sec;
+ trace_act("accept redirected %s --> %s via %s",
+ addrname(k->k_dst, k->k_mask, 0),
+ naddr_ntoa(k->k_gate),
+ ifp ? ifp->int_name : "unknown interface");
+ }
+ return;
+ }
+
+ /* If it is not a static route, quit until the next comparison
+ * between the kernel and daemon tables, when it will be deleted.
+ */
+ if (!(k->k_state & KS_STATIC)) {
+ k->k_state |= KS_DELETE;
+ LIM_SEC(need_kern, k->k_keep);
+ return;
+ }
+
+ /* Put static routes with real metrics into the daemon table so
+ * they can be advertised.
+ *
+ * Find the interface toward the gateway.
+ */
+ ifp = iflookup(k->k_gate);
+ if (ifp == 0)
+ msglog("static route %s --> %s impossibly lacks ifp",
+ addrname(S_ADDR(INFO_DST(info)), mask, 0),
+ naddr_ntoa(k->k_gate));
+
+ kern_check_static(k, ifp);
+}
+
+
+/* deal with packet loss
+ */
+static void
+rtm_lose(struct rt_msghdr *rtm,
+ struct rt_addrinfo *info)
+{
+ if (INFO_GATE(info) == 0
+ || INFO_GATE(info)->sa_family != AF_INET) {
+ trace_act("ignore %s without gateway",
+ rtm_type_name(rtm->rtm_type));
+ return;
+ }
+
+ if (rdisc_ok)
+ rdisc_age(S_ADDR(INFO_GATE(info)));
+ age(S_ADDR(INFO_GATE(info)));
+}
+
+
+/* Make the gateway slot of an info structure point to something
+ * useful. If it is not already useful, but it specifies an interface,
+ * then fill in the sockaddr_in provided and point it there.
+ */
+static int
+get_info_gate(struct sockaddr **sap,
+ struct sockaddr_in *rsin)
+{
+ struct sockaddr_dl *sdl = (struct sockaddr_dl *)*sap;
+ struct interface *ifp;
+
+ if (sdl == 0)
+ return 0;
+ if ((sdl)->sdl_family == AF_INET)
+ return 1;
+ if ((sdl)->sdl_family != AF_LINK)
+ return 0;
+
+ ifp = ifwithindex(sdl->sdl_index, 1);
+ if (ifp == 0)
+ return 0;
+
+ rsin->sin_addr.s_addr = ifp->int_addr;
+#ifdef _HAVE_SA_LEN
+ rsin->sin_len = sizeof(*rsin);
+#endif
+ rsin->sin_family = AF_INET;
+ *sap = (struct sockaddr*)rsin;
+
+ return 1;
+}
+
+
+/* Clean the kernel table by copying it to the daemon image.
+ * Eventually the daemon will delete any extra routes.
+ */
+void
+flush_kern(void)
+{
+ static char *sysctl_buf;
+ static size_t sysctl_buf_size = 0;
+ size_t needed;
+ int mib[6];
+ char *next, *lim;
+ struct rt_msghdr *rtm;
+ struct sockaddr_in gate_sin;
+ struct rt_addrinfo info;
+ int i;
+ struct khash *k;
+
+
+ for (i = 0; i < KHASH_SIZE; i++) {
+ for (k = khash_bins[i]; k != 0; k = k->k_next) {
+ k->k_state |= KS_CHECK;
+ }
+ }
+
+ mib[0] = CTL_NET;
+ mib[1] = PF_ROUTE;
+ mib[2] = 0; /* protocol */
+ mib[3] = 0; /* wildcard address family */
+ mib[4] = NET_RT_DUMP;
+ mib[5] = 0; /* no flags */
+ for (;;) {
+ if ((needed = sysctl_buf_size) != 0) {
+ if (sysctl(mib, 6, sysctl_buf,&needed, 0, 0) >= 0)
+ break;
+ if (errno != ENOMEM && errno != EFAULT)
+ BADERR(1,"flush_kern: sysctl(RT_DUMP)");
+ free(sysctl_buf);
+ needed = 0;
+ }
+ if (sysctl(mib, 6, 0, &needed, 0, 0) < 0)
+ BADERR(1,"flush_kern: sysctl(RT_DUMP) estimate");
+ /* Kludge around the habit of some systems, such as
+ * BSD/OS 3.1, to not admit how many routes are in the
+ * kernel, or at least to be quite wrong.
+ */
+ needed += 50*(sizeof(*rtm)+5*sizeof(struct sockaddr));
+ sysctl_buf = rtmalloc(sysctl_buf_size = needed,
+ "flush_kern sysctl(RT_DUMP)");
+ }
+
+ lim = sysctl_buf + needed;
+ for (next = sysctl_buf; next < lim; next += rtm->rtm_msglen) {
+ rtm = (struct rt_msghdr *)next;
+ if (rtm->rtm_msglen == 0) {
+ msglog("zero length kernel route at "
+ " %#lx in buffer %#lx before %#lx",
+ (u_long)rtm, (u_long)sysctl_buf, (u_long)lim);
+ break;
+ }
+
+ rt_xaddrs(&info,
+ (struct sockaddr *)(rtm+1),
+ (struct sockaddr *)(next + rtm->rtm_msglen),
+ rtm->rtm_addrs);
+
+ if (INFO_DST(&info) == 0
+ || INFO_DST(&info)->sa_family != AF_INET)
+ continue;
+
+#if defined (RTF_LLINFO)
+ /* ignore ARP table entries on systems with a merged route
+ * and ARP table.
+ */
+ if (rtm->rtm_flags & RTF_LLINFO)
+ continue;
+#endif
+#if defined(RTF_WASCLONED) && defined(__FreeBSD__)
+ /* ignore cloned routes
+ */
+ if (rtm->rtm_flags & RTF_WASCLONED)
+ continue;
+#endif
+
+ /* ignore multicast addresses
+ */
+ if (IN_MULTICAST(ntohl(S_ADDR(INFO_DST(&info)))))
+ continue;
+
+ if (!get_info_gate(&INFO_GATE(&info), &gate_sin))
+ continue;
+
+ /* Note static routes and interface routes, and also
+ * preload the image of the kernel table so that
+ * we can later clean it, as well as avoid making
+ * unneeded changes. Keep the old kernel routes for a
+ * few seconds to allow a RIP or router-discovery
+ * response to be heard.
+ */
+ rtm_add(rtm,&info,MIN_WAITTIME);
+ }
+
+ for (i = 0; i < KHASH_SIZE; i++) {
+ for (k = khash_bins[i]; k != 0; k = k->k_next) {
+ if (k->k_state & KS_CHECK) {
+ msglog("%s --> %s disappeared from kernel",
+ addrname(k->k_dst, k->k_mask, 0),
+ naddr_ntoa(k->k_gate));
+ del_static(k->k_dst, k->k_mask, k->k_gate, 1);
+ }
+ }
+ }
+}
+
+
+/* Listen to announcements from the kernel
+ */
+void
+read_rt(void)
+{
+ long cc;
+ struct interface *ifp;
+ struct sockaddr_in gate_sin;
+ naddr mask, gate;
+ union {
+ struct {
+ struct rt_msghdr rtm;
+ struct sockaddr addrs[RTAX_MAX];
+ } r;
+ struct if_msghdr ifm;
+ } m;
+ char str[100], *strp;
+ struct rt_addrinfo info;
+
+
+ for (;;) {
+ cc = read(rt_sock, &m, sizeof(m));
+ if (cc <= 0) {
+ if (cc < 0 && errno != EWOULDBLOCK)
+ LOGERR("read(rt_sock)");
+ return;
+ }
+
+ if (m.r.rtm.rtm_version != RTM_VERSION) {
+ msglog("bogus routing message version %d",
+ m.r.rtm.rtm_version);
+ continue;
+ }
+
+ /* Ignore our own results.
+ */
+ if (m.r.rtm.rtm_type <= RTM_CHANGE
+ && m.r.rtm.rtm_pid == mypid) {
+ static int complained = 0;
+ if (!complained) {
+ msglog("receiving our own change messages");
+ complained = 1;
+ }
+ continue;
+ }
+
+ if (m.r.rtm.rtm_type == RTM_IFINFO
+ || m.r.rtm.rtm_type == RTM_NEWADDR
+ || m.r.rtm.rtm_type == RTM_DELADDR) {
+ ifp = ifwithindex(m.ifm.ifm_index,
+ m.r.rtm.rtm_type != RTM_DELADDR);
+ if (ifp == 0)
+ trace_act("note %s with flags %#x"
+ " for unknown interface index #%d",
+ rtm_type_name(m.r.rtm.rtm_type),
+ m.ifm.ifm_flags,
+ m.ifm.ifm_index);
+ else
+ trace_act("note %s with flags %#x for %s",
+ rtm_type_name(m.r.rtm.rtm_type),
+ m.ifm.ifm_flags,
+ ifp->int_name);
+
+ /* After being informed of a change to an interface,
+ * check them all now if the check would otherwise
+ * be a long time from now, if the interface is
+ * not known, or if the interface has been turned
+ * off or on.
+ */
+ if (ifinit_timer.tv_sec-now.tv_sec>=CHECK_BAD_INTERVAL
+ || ifp == 0
+ || ((ifp->int_if_flags ^ m.ifm.ifm_flags)
+ & IFF_UP) != 0)
+ ifinit_timer.tv_sec = now.tv_sec;
+ continue;
+ }
+#ifdef RTM_OIFINFO
+ if (m.r.rtm.rtm_type == RTM_OIFINFO)
+ continue; /* ignore compat message */
+#endif
+
+ strcpy(str, rtm_type_name(m.r.rtm.rtm_type));
+ strp = &str[strlen(str)];
+ if (m.r.rtm.rtm_type <= RTM_CHANGE)
+ strp += sprintf(strp," from pid %d",m.r.rtm.rtm_pid);
+
+ rt_xaddrs(&info, m.r.addrs, &m.r.addrs[RTAX_MAX],
+ m.r.rtm.rtm_addrs);
+
+ if (INFO_DST(&info) == 0) {
+ trace_act("ignore %s without dst", str);
+ continue;
+ }
+
+ if (INFO_DST(&info)->sa_family != AF_INET) {
+ trace_act("ignore %s for AF %d", str,
+ INFO_DST(&info)->sa_family);
+ continue;
+ }
+
+ mask = ((INFO_MASK(&info) != 0)
+ ? ntohl(S_ADDR(INFO_MASK(&info)))
+ : (m.r.rtm.rtm_flags & RTF_HOST)
+ ? HOST_MASK
+ : std_mask(S_ADDR(INFO_DST(&info))));
+
+ strp += sprintf(strp, ": %s",
+ addrname(S_ADDR(INFO_DST(&info)), mask, 0));
+
+ if (IN_MULTICAST(ntohl(S_ADDR(INFO_DST(&info))))) {
+ trace_act("ignore multicast %s", str);
+ continue;
+ }
+
+#if defined(RTF_LLINFO)
+ if (m.r.rtm.rtm_flags & RTF_LLINFO) {
+ trace_act("ignore ARP %s", str);
+ continue;
+ }
+#endif
+
+#if defined(RTF_WASCLONED) && defined(__FreeBSD__)
+ if (m.r.rtm.rtm_flags & RTF_WASCLONED) {
+ trace_act("ignore cloned %s", str);
+ continue;
+ }
+#endif
+
+ if (get_info_gate(&INFO_GATE(&info), &gate_sin)) {
+ gate = S_ADDR(INFO_GATE(&info));
+ strp += sprintf(strp, " --> %s", naddr_ntoa(gate));
+ } else {
+ gate = 0;
+ }
+
+ if (INFO_AUTHOR(&info) != 0)
+ strp += sprintf(strp, " by authority of %s",
+ saddr_ntoa(INFO_AUTHOR(&info)));
+
+ switch (m.r.rtm.rtm_type) {
+ case RTM_ADD:
+ case RTM_CHANGE:
+ case RTM_REDIRECT:
+ if (m.r.rtm.rtm_errno != 0) {
+ trace_act("ignore %s with \"%s\" error",
+ str, strerror(m.r.rtm.rtm_errno));
+ } else {
+ trace_act("%s", str);
+ rtm_add(&m.r.rtm,&info,0);
+ }
+ break;
+
+ case RTM_DELETE:
+ if (m.r.rtm.rtm_errno != 0
+ && m.r.rtm.rtm_errno != ESRCH) {
+ trace_act("ignore %s with \"%s\" error",
+ str, strerror(m.r.rtm.rtm_errno));
+ } else {
+ trace_act("%s", str);
+ del_static(S_ADDR(INFO_DST(&info)), mask,
+ gate, 1);
+ }
+ break;
+
+ case RTM_LOSING:
+ trace_act("%s", str);
+ rtm_lose(&m.r.rtm,&info);
+ break;
+
+ default:
+ trace_act("ignore %s", str);
+ break;
+ }
+ }
+}
+
+
+/* after aggregating, note routes that belong in the kernel
+ */
+static void
+kern_out(struct ag_info *ag)
+{
+ struct khash *k;
+
+
+ /* Do not install bad routes if they are not already present.
+ * This includes routes that had RS_NET_SYN for interfaces that
+ * recently died.
+ */
+ if (ag->ag_metric == HOPCNT_INFINITY) {
+ k = kern_find(htonl(ag->ag_dst_h), ag->ag_mask, 0);
+ if (k == 0)
+ return;
+ } else {
+ k = kern_add(htonl(ag->ag_dst_h), ag->ag_mask);
+ }
+
+ if (k->k_state & KS_NEW) {
+ /* will need to add new entry to the kernel table */
+ k->k_state = KS_ADD;
+ if (ag->ag_state & AGS_GATEWAY)
+ k->k_state |= KS_GATEWAY;
+ k->k_gate = ag->ag_gate;
+ k->k_metric = ag->ag_metric;
+ return;
+ }
+
+ if (k->k_state & KS_STATIC)
+ return;
+
+ /* modify existing kernel entry if necessary */
+ if (k->k_gate != ag->ag_gate
+ || k->k_metric != ag->ag_metric) {
+ /* Must delete bad interface routes etc. to change them. */
+ if (k->k_metric == HOPCNT_INFINITY)
+ k->k_state |= KS_DEL_ADD;
+ k->k_gate = ag->ag_gate;
+ k->k_metric = ag->ag_metric;
+ k->k_state |= KS_CHANGE;
+ }
+
+ /* If the daemon thinks the route should exist, forget
+ * about any redirections.
+ * If the daemon thinks the route should exist, eventually
+ * override manual intervention by the operator.
+ */
+ if ((k->k_state & (KS_DYNAMIC | KS_DELETED)) != 0) {
+ k->k_state &= ~KS_DYNAMIC;
+ k->k_state |= (KS_ADD | KS_DEL_ADD);
+ }
+
+ if ((k->k_state & KS_GATEWAY)
+ && !(ag->ag_state & AGS_GATEWAY)) {
+ k->k_state &= ~KS_GATEWAY;
+ k->k_state |= (KS_ADD | KS_DEL_ADD);
+ } else if (!(k->k_state & KS_GATEWAY)
+ && (ag->ag_state & AGS_GATEWAY)) {
+ k->k_state |= KS_GATEWAY;
+ k->k_state |= (KS_ADD | KS_DEL_ADD);
+ }
+
+ /* Deleting-and-adding is necessary to change aspects of a route.
+ * Just delete instead of deleting and then adding a bad route.
+ * Otherwise, we want to keep the route in the kernel.
+ */
+ if (k->k_metric == HOPCNT_INFINITY
+ && (k->k_state & KS_DEL_ADD))
+ k->k_state |= KS_DELETE;
+ else
+ k->k_state &= ~KS_DELETE;
+#undef RT
+}
+
+
+/* ARGSUSED */
+static int
+walk_kern(struct radix_node *rn,
+ struct walkarg *argp UNUSED)
+{
+#define RT ((struct rt_entry *)rn)
+ char metric, pref;
+ u_int ags = 0;
+
+
+ /* Do not install synthetic routes */
+ if (RT->rt_state & RS_NET_SYN)
+ return 0;
+
+ if (!(RT->rt_state & RS_IF)) {
+ /* This is an ordinary route, not for an interface.
+ */
+
+ /* aggregate, ordinary good routes without regard to
+ * their metric
+ */
+ pref = 1;
+ ags |= (AGS_GATEWAY | AGS_SUPPRESS | AGS_AGGREGATE);
+
+ /* Do not install host routes directly to hosts, to avoid
+ * interfering with ARP entries in the kernel table.
+ */
+ if (RT_ISHOST(RT)
+ && ntohl(RT->rt_dst) == RT->rt_gate)
+ return 0;
+
+ } else {
+ /* This is an interface route.
+ * Do not install routes for "external" remote interfaces.
+ */
+ if (RT->rt_ifp != 0 && (RT->rt_ifp->int_state & IS_EXTERNAL))
+ return 0;
+
+ /* Interfaces should override received routes.
+ */
+ pref = 0;
+ ags |= (AGS_IF | AGS_CORS_GATE);
+
+ /* If it is not an interface, or an alias for an interface,
+ * it must be a "gateway."
+ *
+ * If it is a "remote" interface, it is also a "gateway" to
+ * the kernel if is not an alias.
+ */
+ if (RT->rt_ifp == 0
+ || (RT->rt_ifp->int_state & IS_REMOTE))
+ ags |= (AGS_GATEWAY | AGS_SUPPRESS | AGS_AGGREGATE);
+ }
+
+ /* If RIP is off and IRDP is on, let the route to the discovered
+ * route suppress any RIP routes. Eventually the RIP routes
+ * will time-out and be deleted. This reaches the steady-state
+ * quicker.
+ */
+ if ((RT->rt_state & RS_RDISC) && rip_sock < 0)
+ ags |= AGS_CORS_GATE;
+
+ metric = RT->rt_metric;
+ if (metric == HOPCNT_INFINITY) {
+ /* if the route is dead, so try hard to aggregate. */
+ pref = HOPCNT_INFINITY;
+ ags |= (AGS_FINE_GATE | AGS_SUPPRESS);
+ ags &= ~(AGS_IF | AGS_CORS_GATE);
+ }
+
+ ag_check(RT->rt_dst, RT->rt_mask, RT->rt_gate, 0,
+ metric,pref, 0, 0, ags, kern_out);
+ return 0;
+#undef RT
+}
+
+
+/* Update the kernel table to match the daemon table.
+ */
+static void
+fix_kern(void)
+{
+ int i;
+ struct khash *k, **pk;
+
+
+ need_kern = age_timer;
+
+ /* Walk daemon table, updating the copy of the kernel table.
+ */
+ (void)rn_walktree(rhead, walk_kern, 0);
+ ag_flush(0,0,kern_out);
+
+ for (i = 0; i < KHASH_SIZE; i++) {
+ for (pk = &khash_bins[i]; (k = *pk) != 0; ) {
+ /* Do not touch static routes */
+ if (k->k_state & KS_STATIC) {
+ kern_check_static(k,0);
+ pk = &k->k_next;
+ continue;
+ }
+
+ /* check hold on routes deleted by the operator */
+ if (k->k_keep > now.tv_sec) {
+ /* ensure we check when the hold is over */
+ LIM_SEC(need_kern, k->k_keep);
+ /* mark for the next cycle */
+ k->k_state |= KS_DELETE;
+ pk = &k->k_next;
+ continue;
+ }
+
+ if ((k->k_state & KS_DELETE)
+ && !(k->k_state & KS_DYNAMIC)) {
+ kern_ioctl(k, RTM_DELETE, 0);
+ *pk = k->k_next;
+ free(k);
+ continue;
+ }
+
+ if (k->k_state & KS_DEL_ADD)
+ kern_ioctl(k, RTM_DELETE, 0);
+
+ if (k->k_state & KS_ADD) {
+ kern_ioctl(k, RTM_ADD,
+ ((0 != (k->k_state & (KS_GATEWAY
+ | KS_DYNAMIC)))
+ ? RTF_GATEWAY : 0));
+ } else if (k->k_state & KS_CHANGE) {
+ kern_ioctl(k, RTM_CHANGE,
+ ((0 != (k->k_state & (KS_GATEWAY
+ | KS_DYNAMIC)))
+ ? RTF_GATEWAY : 0));
+ }
+ k->k_state &= ~(KS_ADD|KS_CHANGE|KS_DEL_ADD);
+
+ /* Mark this route to be deleted in the next cycle.
+ * This deletes routes that disappear from the
+ * daemon table, since the normal aging code
+ * will clear the bit for routes that have not
+ * disappeared from the daemon table.
+ */
+ k->k_state |= KS_DELETE;
+ pk = &k->k_next;
+ }
+ }
+}
+
+
+/* Delete a static route in the image of the kernel table.
+ */
+void
+del_static(naddr dst,
+ naddr mask,
+ naddr gate,
+ int gone)
+{
+ struct khash *k;
+ struct rt_entry *rt;
+
+ /* Just mark it in the table to be deleted next time the kernel
+ * table is updated.
+ * If it has already been deleted, mark it as such, and set its
+ * keep-timer so that it will not be deleted again for a while.
+ * This lets the operator delete a route added by the daemon
+ * and add a replacement.
+ */
+ k = kern_find(dst, mask, 0);
+ if (k != 0 && (gate == 0 || k->k_gate == gate)) {
+ k->k_state &= ~(KS_STATIC | KS_DYNAMIC | KS_CHECK);
+ k->k_state |= KS_DELETE;
+ if (gone) {
+ k->k_state |= KS_DELETED;
+ k->k_keep = now.tv_sec + K_KEEP_LIM;
+ }
+ }
+
+ rt = rtget(dst, mask);
+ if (rt != 0 && (rt->rt_state & RS_STATIC))
+ rtbad(rt);
+}
+
+
+/* Delete all routes generated from ICMP Redirects that use a given gateway,
+ * as well as old redirected routes.
+ */
+void
+del_redirects(naddr bad_gate,
+ time_t old)
+{
+ int i;
+ struct khash *k;
+
+
+ for (i = 0; i < KHASH_SIZE; i++) {
+ for (k = khash_bins[i]; k != 0; k = k->k_next) {
+ if (!(k->k_state & KS_DYNAMIC)
+ || (k->k_state & KS_STATIC))
+ continue;
+
+ if (k->k_gate != bad_gate
+ && k->k_redirect_time > old
+ && !supplier)
+ continue;
+
+ k->k_state |= KS_DELETE;
+ k->k_state &= ~KS_DYNAMIC;
+ need_kern.tv_sec = now.tv_sec;
+ trace_act("mark redirected %s --> %s for deletion",
+ addrname(k->k_dst, k->k_mask, 0),
+ naddr_ntoa(k->k_gate));
+ }
+ }
+}
+
+
+/* Start the daemon tables.
+ */
+extern int max_keylen;
+
+void
+rtinit(void)
+{
+ int i;
+ struct ag_info *ag;
+
+ /* Initialize the radix trees */
+ max_keylen = sizeof(struct sockaddr_in);
+ rn_init();
+ rn_inithead(&rhead, 32);
+
+ /* mark all of the slots in the table free */
+ ag_avail = ag_slots;
+ for (ag = ag_slots, i = 1; i < NUM_AG_SLOTS; i++) {
+ ag->ag_fine = ag+1;
+ ag++;
+ }
+}
+
+
+#ifdef _HAVE_SIN_LEN
+static struct sockaddr_in dst_sock = {sizeof(dst_sock), AF_INET, 0, {0}, {0}};
+static struct sockaddr_in mask_sock = {sizeof(mask_sock), AF_INET, 0, {0}, {0}};
+#else
+static struct sockaddr_in_new dst_sock = {_SIN_ADDR_SIZE, AF_INET};
+static struct sockaddr_in_new mask_sock = {_SIN_ADDR_SIZE, AF_INET};
+#endif
+
+
+static void
+set_need_flash(void)
+{
+ if (!need_flash) {
+ need_flash = 1;
+ /* Do not send the flash update immediately. Wait a little
+ * while to hear from other routers.
+ */
+ no_flash.tv_sec = now.tv_sec + MIN_WAITTIME;
+ }
+}
+
+
+/* Get a particular routing table entry
+ */
+struct rt_entry *
+rtget(naddr dst, naddr mask)
+{
+ struct rt_entry *rt;
+
+ dst_sock.sin_addr.s_addr = dst;
+ mask_sock.sin_addr.s_addr = htonl(mask);
+ masktrim(&mask_sock);
+ rt = (struct rt_entry *)rhead->rnh_lookup(&dst_sock,&mask_sock,rhead);
+ if (!rt
+ || rt->rt_dst != dst
+ || rt->rt_mask != mask)
+ return 0;
+
+ return rt;
+}
+
+
+/* Find a route to dst as the kernel would.
+ */
+struct rt_entry *
+rtfind(naddr dst)
+{
+ dst_sock.sin_addr.s_addr = dst;
+ return (struct rt_entry *)rhead->rnh_matchaddr(&dst_sock, rhead);
+}
+
+
+/* add a route to the table
+ */
+void
+rtadd(naddr dst,
+ naddr mask,
+ u_int state, /* rt_state for the entry */
+ struct rt_spare *new)
+{
+ struct rt_entry *rt;
+ naddr smask;
+ int i;
+ struct rt_spare *rts;
+
+ rt = (struct rt_entry *)rtmalloc(sizeof (*rt), "rtadd");
+ memset(rt, 0, sizeof(*rt));
+ for (rts = rt->rt_spares, i = NUM_SPARES; i != 0; i--, rts++)
+ rts->rts_metric = HOPCNT_INFINITY;
+
+ rt->rt_nodes->rn_key = (caddr_t)&rt->rt_dst_sock;
+ rt->rt_dst = dst;
+ rt->rt_dst_sock.sin_family = AF_INET;
+#ifdef _HAVE_SIN_LEN
+ rt->rt_dst_sock.sin_len = dst_sock.sin_len;
+#endif
+ if (mask != HOST_MASK) {
+ smask = std_mask(dst);
+ if ((smask & ~mask) == 0 && mask > smask)
+ state |= RS_SUBNET;
+ }
+ mask_sock.sin_addr.s_addr = htonl(mask);
+ masktrim(&mask_sock);
+ rt->rt_mask = mask;
+ rt->rt_state = state;
+ rt->rt_spares[0] = *new;
+ rt->rt_time = now.tv_sec;
+ rt->rt_poison_metric = HOPCNT_INFINITY;
+ rt->rt_seqno = update_seqno;
+
+ if (++total_routes == MAX_ROUTES)
+ msglog("have maximum (%d) routes", total_routes);
+ if (TRACEACTIONS)
+ trace_add_del("Add", rt);
+
+ need_kern.tv_sec = now.tv_sec;
+ set_need_flash();
+
+ if (0 == rhead->rnh_addaddr(&rt->rt_dst_sock, &mask_sock,
+ rhead, rt->rt_nodes)) {
+ msglog("rnh_addaddr() failed for %s mask=%#lx",
+ naddr_ntoa(dst), (u_long)mask);
+ free(rt);
+ }
+}
+
+
+/* notice a changed route
+ */
+void
+rtchange(struct rt_entry *rt,
+ u_int state, /* new state bits */
+ struct rt_spare *new,
+ char *label)
+{
+ if (rt->rt_metric != new->rts_metric) {
+ /* Fix the kernel immediately if it seems the route
+ * has gone bad, since there may be a working route that
+ * aggregates this route.
+ */
+ if (new->rts_metric == HOPCNT_INFINITY) {
+ need_kern.tv_sec = now.tv_sec;
+ if (new->rts_time >= now.tv_sec - EXPIRE_TIME)
+ new->rts_time = now.tv_sec - EXPIRE_TIME;
+ }
+ rt->rt_seqno = update_seqno;
+ set_need_flash();
+ }
+
+ if (rt->rt_gate != new->rts_gate) {
+ need_kern.tv_sec = now.tv_sec;
+ rt->rt_seqno = update_seqno;
+ set_need_flash();
+ }
+
+ state |= (rt->rt_state & RS_SUBNET);
+
+ /* Keep various things from deciding ageless routes are stale.
+ */
+ if (!AGE_RT(state, new->rts_ifp))
+ new->rts_time = now.tv_sec;
+
+ if (TRACEACTIONS)
+ trace_change(rt, state, new,
+ label ? label : "Chg ");
+
+ rt->rt_state = state;
+ rt->rt_spares[0] = *new;
+}
+
+
+/* check for a better route among the spares
+ */
+static struct rt_spare *
+rts_better(struct rt_entry *rt)
+{
+ struct rt_spare *rts, *rts1;
+ int i;
+
+ /* find the best alternative among the spares */
+ rts = rt->rt_spares+1;
+ for (i = NUM_SPARES, rts1 = rts+1; i > 2; i--, rts1++) {
+ if (BETTER_LINK(rt,rts1,rts))
+ rts = rts1;
+ }
+
+ return rts;
+}
+
+
+/* switch to a backup route
+ */
+void
+rtswitch(struct rt_entry *rt,
+ struct rt_spare *rts)
+{
+ struct rt_spare swap;
+ char label[10];
+
+
+ /* Do not change permanent routes */
+ if (0 != (rt->rt_state & (RS_MHOME | RS_STATIC | RS_RDISC
+ | RS_NET_SYN | RS_IF)))
+ return;
+
+ /* find the best alternative among the spares */
+ if (rts == 0)
+ rts = rts_better(rt);
+
+ /* Do not bother if it is not worthwhile.
+ */
+ if (!BETTER_LINK(rt, rts, rt->rt_spares))
+ return;
+
+ swap = rt->rt_spares[0];
+ (void)sprintf(label, "Use #%d", (int)(rts - rt->rt_spares));
+ rtchange(rt, rt->rt_state & ~(RS_NET_SYN | RS_RDISC), rts, label);
+ if (swap.rts_metric == HOPCNT_INFINITY) {
+ *rts = rts_empty;
+ } else {
+ *rts = swap;
+ }
+}
+
+
+void
+rtdelete(struct rt_entry *rt)
+{
+ struct khash *k;
+
+
+ if (TRACEACTIONS)
+ trace_add_del("Del", rt);
+
+ k = kern_find(rt->rt_dst, rt->rt_mask, 0);
+ if (k != 0) {
+ k->k_state |= KS_DELETE;
+ need_kern.tv_sec = now.tv_sec;
+ }
+
+ dst_sock.sin_addr.s_addr = rt->rt_dst;
+ mask_sock.sin_addr.s_addr = htonl(rt->rt_mask);
+ masktrim(&mask_sock);
+ if (rt != (struct rt_entry *)rhead->rnh_deladdr(&dst_sock, &mask_sock,
+ rhead)) {
+ msglog("rnh_deladdr() failed");
+ } else {
+ free(rt);
+ total_routes--;
+ }
+}
+
+
+void
+rts_delete(struct rt_entry *rt,
+ struct rt_spare *rts)
+{
+ trace_upslot(rt, rts, &rts_empty);
+ *rts = rts_empty;
+}
+
+
+/* Get rid of a bad route, and try to switch to a replacement.
+ */
+static void
+rtbad(struct rt_entry *rt)
+{
+ struct rt_spare new;
+
+ /* Poison the route */
+ new = rt->rt_spares[0];
+ new.rts_metric = HOPCNT_INFINITY;
+ rtchange(rt, rt->rt_state & ~(RS_IF | RS_LOCAL | RS_STATIC), &new, 0);
+ rtswitch(rt, 0);
+}
+
+
+/* Junk a RS_NET_SYN or RS_LOCAL route,
+ * unless it is needed by another interface.
+ */
+void
+rtbad_sub(struct rt_entry *rt)
+{
+ struct interface *ifp, *ifp1;
+ struct intnet *intnetp;
+ u_int state;
+
+
+ ifp1 = 0;
+ state = 0;
+
+ if (rt->rt_state & RS_LOCAL) {
+ /* Is this the route through loopback for the interface?
+ * If so, see if it is used by any other interfaces, such
+ * as a point-to-point interface with the same local address.
+ */
+ LIST_FOREACH(ifp, &ifnet, int_list) {
+ /* Retain it if another interface needs it.
+ */
+ if (ifp->int_addr == rt->rt_ifp->int_addr) {
+ state |= RS_LOCAL;
+ ifp1 = ifp;
+ break;
+ }
+ }
+
+ }
+
+ if (!(state & RS_LOCAL)) {
+ /* Retain RIPv1 logical network route if there is another
+ * interface that justifies it.
+ */
+ if (rt->rt_state & RS_NET_SYN) {
+ LIST_FOREACH(ifp, &ifnet, int_list) {
+ if ((ifp->int_state & IS_NEED_NET_SYN)
+ && rt->rt_mask == ifp->int_std_mask
+ && rt->rt_dst == ifp->int_std_addr) {
+ state |= RS_NET_SYN;
+ ifp1 = ifp;
+ break;
+ }
+ }
+ }
+
+ /* or if there is an authority route that needs it. */
+ for (intnetp = intnets;
+ intnetp != 0;
+ intnetp = intnetp->intnet_next) {
+ if (intnetp->intnet_addr == rt->rt_dst
+ && intnetp->intnet_mask == rt->rt_mask) {
+ state |= (RS_NET_SYN | RS_NET_INT);
+ break;
+ }
+ }
+ }
+
+ if (ifp1 != 0 || (state & RS_NET_SYN)) {
+ struct rt_spare new = rt->rt_spares[0];
+ new.rts_ifp = ifp1;
+ rtchange(rt, ((rt->rt_state & ~(RS_NET_SYN|RS_LOCAL)) | state),
+ &new, 0);
+ } else {
+ rtbad(rt);
+ }
+}
+
+
+/* Called while walking the table looking for sick interfaces
+ * or after a time change.
+ */
+/* ARGSUSED */
+int
+walk_bad(struct radix_node *rn,
+ struct walkarg *argp UNUSED)
+{
+#define RT ((struct rt_entry *)rn)
+ struct rt_spare *rts;
+ int i;
+
+
+ /* fix any spare routes through the interface
+ */
+ rts = RT->rt_spares;
+ for (i = NUM_SPARES; i != 1; i--) {
+ rts++;
+ if (rts->rts_metric < HOPCNT_INFINITY
+ && (rts->rts_ifp == 0
+ || (rts->rts_ifp->int_state & IS_BROKE)))
+ rts_delete(RT, rts);
+ }
+
+ /* Deal with the main route
+ */
+ /* finished if it has been handled before or if its interface is ok
+ */
+ if (RT->rt_ifp == 0 || !(RT->rt_ifp->int_state & IS_BROKE))
+ return 0;
+
+ /* Bad routes for other than interfaces are easy.
+ */
+ if (0 == (RT->rt_state & (RS_IF | RS_NET_SYN | RS_LOCAL))) {
+ rtbad(RT);
+ return 0;
+ }
+
+ rtbad_sub(RT);
+ return 0;
+#undef RT
+}
+
+
+/* Check the age of an individual route.
+ */
+/* ARGSUSED */
+static int
+walk_age(struct radix_node *rn,
+ struct walkarg *argp UNUSED)
+{
+#define RT ((struct rt_entry *)rn)
+ struct interface *ifp;
+ struct rt_spare *rts;
+ int i;
+
+
+ /* age all of the spare routes, including the primary route
+ * currently in use
+ */
+ rts = RT->rt_spares;
+ for (i = NUM_SPARES; i != 0; i--, rts++) {
+
+ ifp = rts->rts_ifp;
+ if (i == NUM_SPARES) {
+ if (!AGE_RT(RT->rt_state, ifp)) {
+ /* Keep various things from deciding ageless
+ * routes are stale
+ */
+ rts->rts_time = now.tv_sec;
+ continue;
+ }
+
+ /* forget RIP routes after RIP has been turned off.
+ */
+ if (rip_sock < 0) {
+ rtdelete(RT);
+ return 0;
+ }
+ }
+
+ /* age failing routes
+ */
+ if (age_bad_gate == rts->rts_gate
+ && rts->rts_time >= now_stale) {
+ rts->rts_time -= SUPPLY_INTERVAL;
+ }
+
+ /* trash the spare routes when they go bad */
+ if (rts->rts_metric < HOPCNT_INFINITY
+ && now_garbage > rts->rts_time
+ && i != NUM_SPARES)
+ rts_delete(RT, rts);
+ }
+
+
+ /* finished if the active route is still fresh */
+ if (now_stale <= RT->rt_time)
+ return 0;
+
+ /* try to switch to an alternative */
+ rtswitch(RT, 0);
+
+ /* Delete a dead route after it has been publicly mourned. */
+ if (now_garbage > RT->rt_time) {
+ rtdelete(RT);
+ return 0;
+ }
+
+ /* Start poisoning a bad route before deleting it. */
+ if (now.tv_sec - RT->rt_time > EXPIRE_TIME) {
+ struct rt_spare new = RT->rt_spares[0];
+ new.rts_metric = HOPCNT_INFINITY;
+ rtchange(RT, RT->rt_state, &new, 0);
+ }
+ return 0;
+}
+
+
+/* Watch for dead routes and interfaces.
+ */
+void
+age(naddr bad_gate)
+{
+ struct interface *ifp;
+ int need_query = 0;
+
+ /* If not listening to RIP, there is no need to age the routes in
+ * the table.
+ */
+ age_timer.tv_sec = (now.tv_sec
+ + ((rip_sock < 0) ? NEVER : SUPPLY_INTERVAL));
+
+ /* Check for dead IS_REMOTE interfaces by timing their
+ * transmissions.
+ */
+ LIST_FOREACH(ifp, &ifnet, int_list) {
+ if (!(ifp->int_state & IS_REMOTE))
+ continue;
+
+ /* ignore unreachable remote interfaces */
+ if (!check_remote(ifp))
+ continue;
+
+ /* Restore remote interface that has become reachable
+ */
+ if (ifp->int_state & IS_BROKE)
+ if_ok(ifp, "remote ");
+
+ if (ifp->int_act_time != NEVER
+ && now.tv_sec - ifp->int_act_time > EXPIRE_TIME) {
+ msglog("remote interface %s to %s timed out after"
+ " %ld:%ld",
+ ifp->int_name,
+ naddr_ntoa(ifp->int_dstaddr),
+ (long)(now.tv_sec - ifp->int_act_time)/60,
+ (long)(now.tv_sec - ifp->int_act_time)%60);
+ if_sick(ifp);
+ }
+
+ /* If we have not heard from the other router
+ * recently, ask it.
+ */
+ if (now.tv_sec >= ifp->int_query_time) {
+ ifp->int_query_time = NEVER;
+ need_query = 1;
+ }
+ }
+
+ /* Age routes. */
+ age_bad_gate = bad_gate;
+ (void)rn_walktree(rhead, walk_age, 0);
+
+ /* delete old redirected routes to keep the kernel table small
+ * and prevent blackholes
+ */
+ del_redirects(bad_gate, now.tv_sec-STALE_TIME);
+
+ /* Update the kernel routing table. */
+ fix_kern();
+
+ /* poke reticent remote gateways */
+ if (need_query)
+ rip_query();
+}
diff --git a/sbin/routed/trace.c b/sbin/routed/trace.c
new file mode 100644
index 0000000..8797e57
--- /dev/null
+++ b/sbin/routed/trace.c
@@ -0,0 +1,1021 @@
+/*
+ * Copyright (c) 1983, 1988, 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.
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+#define RIPCMDS
+#include "defs.h"
+#include "pathnames.h"
+#include <sys/stat.h>
+#include <sys/signal.h>
+#include <fcntl.h>
+
+#ifdef __NetBSD__
+__RCSID("$NetBSD$");
+#elif defined(__FreeBSD__)
+__RCSID("$FreeBSD$");
+#else
+__RCSID("$Revision: 2.27 $");
+#ident "$Revision: 2.27 $"
+#endif
+
+
+#ifdef sgi
+/* use *stat64 for files on large file systems */
+#define stat stat64
+#endif
+
+int tracelevel, new_tracelevel;
+FILE *ftrace; /* output trace file */
+static const char *sigtrace_pat = "%s";
+static char savetracename[PATH_MAX];
+char inittracename[PATH_MAX];
+static int file_trace; /* 1=tracing to file, not stdout */
+
+static void trace_dump(void);
+static void tmsg(const char *, ...) PATTRIB(1,2);
+
+
+/* convert string to printable characters
+ */
+static char *
+qstring(u_char *s, int len)
+{
+ static char buf[8*20+1];
+ char *p;
+ u_char *s2, c;
+
+
+ for (p = buf; len != 0 && p < &buf[sizeof(buf)-1]; len--) {
+ c = *s++;
+ if (c == '\0') {
+ for (s2 = s+1; s2 < &s[len]; s2++) {
+ if (*s2 != '\0')
+ break;
+ }
+ if (s2 >= &s[len])
+ goto exit;
+ }
+
+ if (c >= ' ' && c < 0x7f && c != '\\') {
+ *p++ = c;
+ continue;
+ }
+ *p++ = '\\';
+ switch (c) {
+ case '\\':
+ *p++ = '\\';
+ break;
+ case '\n':
+ *p++= 'n';
+ break;
+ case '\r':
+ *p++= 'r';
+ break;
+ case '\t':
+ *p++ = 't';
+ break;
+ case '\b':
+ *p++ = 'b';
+ break;
+ default:
+ p += sprintf(p,"%o",c);
+ break;
+ }
+ }
+exit:
+ *p = '\0';
+ return buf;
+}
+
+
+/* convert IP address to a string, but not into a single buffer
+ */
+char *
+naddr_ntoa(naddr a)
+{
+#define NUM_BUFS 4
+ static int bufno;
+ static struct {
+ char str[16]; /* xxx.xxx.xxx.xxx\0 */
+ } bufs[NUM_BUFS];
+ char *s;
+ struct in_addr addr;
+
+ addr.s_addr = a;
+ s = strcpy(bufs[bufno].str, inet_ntoa(addr));
+ bufno = (bufno+1) % NUM_BUFS;
+ return s;
+#undef NUM_BUFS
+}
+
+
+const char *
+saddr_ntoa(struct sockaddr *sa)
+{
+ return (sa == 0) ? "?" : naddr_ntoa(S_ADDR(sa));
+}
+
+
+static char *
+ts(time_t secs) {
+ static char s[20];
+
+ secs += epoch.tv_sec;
+#ifdef sgi
+ (void)cftime(s, "%T", &secs);
+#else
+ memcpy(s, ctime(&secs)+11, 8);
+ s[8] = '\0';
+#endif
+ return s;
+}
+
+
+/* On each event, display a time stamp.
+ * This assumes that 'now' is update once for each event, and
+ * that at least now.tv_usec changes.
+ */
+static struct timeval lastlog_time;
+
+void
+lastlog(void)
+{
+ if (lastlog_time.tv_sec != now.tv_sec
+ || lastlog_time.tv_usec != now.tv_usec) {
+ (void)fprintf(ftrace, "-- %s --\n", ts(now.tv_sec));
+ lastlog_time = now;
+ }
+}
+
+
+static void
+tmsg(const char *p, ...)
+{
+ va_list args;
+
+ if (ftrace != 0) {
+ lastlog();
+ va_start(args, p);
+ vfprintf(ftrace, p, args);
+ va_end(args);
+ (void)fputc('\n',ftrace);
+ fflush(ftrace);
+ }
+}
+
+
+void
+trace_close(int zap_stdio)
+{
+ int fd;
+
+
+ fflush(stdout);
+ fflush(stderr);
+
+ if (ftrace != 0 && zap_stdio) {
+ if (ftrace != stdout)
+ fclose(ftrace);
+ ftrace = 0;
+ fd = open(_PATH_DEVNULL, O_RDWR);
+ if (isatty(STDIN_FILENO))
+ (void)dup2(fd, STDIN_FILENO);
+ if (isatty(STDOUT_FILENO))
+ (void)dup2(fd, STDOUT_FILENO);
+ if (isatty(STDERR_FILENO))
+ (void)dup2(fd, STDERR_FILENO);
+ (void)close(fd);
+ }
+ lastlog_time.tv_sec = 0;
+}
+
+
+void
+trace_flush(void)
+{
+ if (ftrace != 0) {
+ fflush(ftrace);
+ if (ferror(ftrace))
+ trace_off("tracing off: %s", strerror(ferror(ftrace)));
+ }
+}
+
+
+void
+trace_off(const char *p, ...)
+{
+ va_list args;
+
+
+ if (ftrace != 0) {
+ lastlog();
+ va_start(args, p);
+ vfprintf(ftrace, p, args);
+ va_end(args);
+ (void)fputc('\n',ftrace);
+ }
+ trace_close(file_trace);
+
+ new_tracelevel = tracelevel = 0;
+}
+
+
+/* log a change in tracing
+ */
+void
+tracelevel_msg(const char *pat,
+ int dump) /* -1=no dump, 0=default, 1=force */
+{
+ static const char * const off_msgs[MAX_TRACELEVEL] = {
+ "Tracing actions stopped",
+ "Tracing packets stopped",
+ "Tracing packet contents stopped",
+ "Tracing kernel changes stopped",
+ };
+ static const char * const on_msgs[MAX_TRACELEVEL] = {
+ "Tracing actions started",
+ "Tracing packets started",
+ "Tracing packet contents started",
+ "Tracing kernel changes started",
+ };
+ u_int old_tracelevel = tracelevel;
+
+
+ if (new_tracelevel < 0)
+ new_tracelevel = 0;
+ else if (new_tracelevel > MAX_TRACELEVEL)
+ new_tracelevel = MAX_TRACELEVEL;
+
+ if (new_tracelevel < tracelevel) {
+ if (new_tracelevel <= 0) {
+ trace_off(pat, off_msgs[0]);
+ } else do {
+ tmsg(pat, off_msgs[tracelevel]);
+ }
+ while (--tracelevel != new_tracelevel);
+
+ } else if (new_tracelevel > tracelevel) {
+ do {
+ tmsg(pat, on_msgs[tracelevel++]);
+ } while (tracelevel != new_tracelevel);
+ }
+
+ if (dump > 0
+ || (dump == 0 && old_tracelevel == 0 && tracelevel != 0))
+ trace_dump();
+}
+
+
+void
+set_tracefile(const char *filename,
+ const char *pat,
+ int dump) /* -1=no dump, 0=default, 1=force */
+{
+ struct stat stbuf;
+ FILE *n_ftrace;
+ const char *fn;
+
+
+ /* Allow a null filename to increase the level if the trace file
+ * is already open or if coming from a trusted source, such as
+ * a signal or the command line.
+ */
+ if (filename == 0 || filename[0] == '\0') {
+ filename = 0;
+ if (ftrace == 0) {
+ if (inittracename[0] == '\0') {
+ msglog("missing trace file name");
+ return;
+ }
+ fn = inittracename;
+ } else {
+ fn = 0;
+ }
+
+ } else if (!strcmp(filename,"dump/../table")) {
+ trace_dump();
+ return;
+
+ } else {
+ /* Allow the file specified with "-T file" to be reopened,
+ * but require all other names specified over the net to
+ * match the official path. The path can specify a directory
+ * in which the file is to be created.
+ */
+ if (strcmp(filename, inittracename)
+#ifdef _PATH_TRACE
+ && (strncmp(filename, _PATH_TRACE, sizeof(_PATH_TRACE)-1)
+ || strstr(filename,"../")
+ || 0 > stat(_PATH_TRACE, &stbuf))
+#endif
+ ) {
+ msglog("wrong trace file \"%s\"", filename);
+ return;
+ }
+
+ /* If the new tracefile exists, it must be a regular file.
+ */
+ if (stat(filename, &stbuf) >= 0 && !S_ISREG(stbuf.st_mode)) {
+ msglog("wrong type (%#x) of trace file \"%s\"",
+ stbuf.st_mode, filename);
+ return;
+ }
+
+ fn = filename;
+ }
+
+ if (fn != 0) {
+ n_ftrace = fopen(fn, "a");
+ if (n_ftrace == 0) {
+ msglog("failed to open trace file \"%s\" %s",
+ fn, strerror(errno));
+ if (fn == inittracename)
+ inittracename[0] = '\0';
+ return;
+ }
+
+ tmsg("switch to trace file %s", fn);
+
+ trace_close(file_trace = 1);
+
+ if (fn != savetracename)
+ strncpy(savetracename, fn, sizeof(savetracename)-1);
+ ftrace = n_ftrace;
+
+ fflush(stdout);
+ fflush(stderr);
+ dup2(fileno(ftrace), STDOUT_FILENO);
+ dup2(fileno(ftrace), STDERR_FILENO);
+ }
+
+ if (new_tracelevel == 0 || filename == 0)
+ new_tracelevel++;
+ tracelevel_msg(pat, dump != 0 ? dump : (filename != 0));
+}
+
+
+/* ARGSUSED */
+void
+sigtrace_on(int s UNUSED)
+{
+ new_tracelevel++;
+ sigtrace_pat = "SIGUSR1: %s";
+}
+
+
+/* ARGSUSED */
+void
+sigtrace_off(int s UNUSED)
+{
+ new_tracelevel--;
+ sigtrace_pat = "SIGUSR2: %s";
+}
+
+
+/* Set tracing after a signal.
+ */
+void
+set_tracelevel(void)
+{
+ if (new_tracelevel == tracelevel)
+ return;
+
+ /* If tracing entirely off, and there was no tracefile specified
+ * on the command line, then leave it off.
+ */
+ if (new_tracelevel > tracelevel && ftrace == 0) {
+ if (savetracename[0] != '\0') {
+ set_tracefile(savetracename,sigtrace_pat,0);
+ } else if (inittracename[0] != '\0') {
+ set_tracefile(inittracename,sigtrace_pat,0);
+ } else {
+ new_tracelevel = 0;
+ return;
+ }
+ } else {
+ tracelevel_msg(sigtrace_pat, 0);
+ }
+}
+
+
+/* display an address
+ */
+char *
+addrname(naddr addr, /* in network byte order */
+ naddr mask,
+ int force) /* 0=show mask if nonstandard, */
+{ /* 1=always show mask, 2=never */
+#define NUM_BUFS 4
+ static int bufno;
+ static struct {
+ char str[15+20];
+ } bufs[NUM_BUFS];
+ char *s, *sp;
+ naddr dmask;
+ int i;
+
+ s = strcpy(bufs[bufno].str, naddr_ntoa(addr));
+ bufno = (bufno+1) % NUM_BUFS;
+
+ if (force == 1 || (force == 0 && mask != std_mask(addr))) {
+ sp = &s[strlen(s)];
+
+ dmask = mask & -mask;
+ if (mask + dmask == 0) {
+ for (i = 0; i != 32 && ((1<<i) & mask) == 0; i++)
+ continue;
+ (void)sprintf(sp, "/%d", 32-i);
+
+ } else {
+ (void)sprintf(sp, " (mask %#x)", (u_int)mask);
+ }
+ }
+
+ return s;
+#undef NUM_BUFS
+}
+
+
+/* display a bit-field
+ */
+struct bits {
+ u_int bits_mask;
+ u_int bits_clear;
+ const char *bits_name;
+};
+
+static const struct bits if_bits[] = {
+ { IFF_LOOPBACK, 0, "LOOPBACK" },
+ { IFF_POINTOPOINT, 0, "PT-TO-PT" },
+ { 0, 0, 0}
+};
+
+static const struct bits is_bits[] = {
+ { IS_ALIAS, 0, "ALIAS" },
+ { IS_SUBNET, 0, "" },
+ { IS_REMOTE, (IS_NO_RDISC
+ | IS_BCAST_RDISC), "REMOTE" },
+ { IS_PASSIVE, (IS_NO_RDISC
+ | IS_NO_RIP
+ | IS_NO_SUPER_AG
+ | IS_PM_RDISC
+ | IS_NO_AG), "PASSIVE" },
+ { IS_EXTERNAL, 0, "EXTERNAL" },
+ { IS_CHECKED, 0, "" },
+ { IS_ALL_HOSTS, 0, "" },
+ { IS_ALL_ROUTERS, 0, "" },
+ { IS_DISTRUST, 0, "DISTRUST" },
+ { IS_BROKE, IS_SICK, "BROKEN" },
+ { IS_SICK, 0, "SICK" },
+ { IS_DUP, 0, "DUPLICATE" },
+ { IS_REDIRECT_OK, 0, "REDIRECT_OK" },
+ { IS_NEED_NET_SYN, 0, "" },
+ { IS_NO_AG, IS_NO_SUPER_AG, "NO_AG" },
+ { IS_NO_SUPER_AG, 0, "NO_SUPER_AG" },
+ { (IS_NO_RIPV1_IN
+ | IS_NO_RIPV2_IN
+ | IS_NO_RIPV1_OUT
+ | IS_NO_RIPV2_OUT), 0, "NO_RIP" },
+ { (IS_NO_RIPV1_IN
+ | IS_NO_RIPV1_OUT), 0, "RIPV2" },
+ { IS_NO_RIPV1_IN, 0, "NO_RIPV1_IN" },
+ { IS_NO_RIPV2_IN, 0, "NO_RIPV2_IN" },
+ { IS_NO_RIPV1_OUT, 0, "NO_RIPV1_OUT" },
+ { IS_NO_RIPV2_OUT, 0, "NO_RIPV2_OUT" },
+ { (IS_NO_ADV_IN
+ | IS_NO_SOL_OUT
+ | IS_NO_ADV_OUT), IS_BCAST_RDISC, "NO_RDISC" },
+ { IS_NO_SOL_OUT, 0, "NO_SOLICIT" },
+ { IS_SOL_OUT, 0, "SEND_SOLICIT" },
+ { IS_NO_ADV_OUT, IS_BCAST_RDISC, "NO_RDISC_ADV" },
+ { IS_ADV_OUT, 0, "RDISC_ADV" },
+ { IS_BCAST_RDISC, 0, "BCAST_RDISC" },
+ { IS_PM_RDISC, 0, "" },
+ { 0, 0, "%#x"}
+};
+
+static const struct bits rs_bits[] = {
+ { RS_IF, 0, "IF" },
+ { RS_NET_INT, RS_NET_SYN, "NET_INT" },
+ { RS_NET_SYN, 0, "NET_SYN" },
+ { RS_SUBNET, 0, "" },
+ { RS_LOCAL, 0, "LOCAL" },
+ { RS_MHOME, 0, "MHOME" },
+ { RS_STATIC, 0, "STATIC" },
+ { RS_RDISC, 0, "RDISC" },
+ { 0, 0, "%#x"}
+};
+
+
+static void
+trace_bits(const struct bits *tbl,
+ u_int field,
+ int force)
+{
+ u_int b;
+ char c;
+
+ if (force) {
+ (void)putc('<', ftrace);
+ c = 0;
+ } else {
+ c = '<';
+ }
+
+ while (field != 0
+ && (b = tbl->bits_mask) != 0) {
+ if ((b & field) == b) {
+ if (tbl->bits_name[0] != '\0') {
+ if (c)
+ (void)putc(c, ftrace);
+ (void)fprintf(ftrace, "%s", tbl->bits_name);
+ c = '|';
+ }
+ if (0 == (field &= ~(b | tbl->bits_clear)))
+ break;
+ }
+ tbl++;
+ }
+ if (field != 0 && tbl->bits_name != 0) {
+ if (c)
+ (void)putc(c, ftrace);
+ (void)fprintf(ftrace, tbl->bits_name, field);
+ c = '|';
+ }
+
+ if (c != '<' || force)
+ (void)fputs("> ", ftrace);
+}
+
+
+char *
+rtname(naddr dst,
+ naddr mask,
+ naddr gate)
+{
+ static char buf[3*4+3+1+2+3 /* "xxx.xxx.xxx.xxx/xx-->" */
+ +3*4+3+1]; /* "xxx.xxx.xxx.xxx" */
+ int i;
+
+ i = sprintf(buf, "%-16s-->", addrname(dst, mask, 0));
+ (void)sprintf(&buf[i], "%-*s", 15+20-MAX(20,i), naddr_ntoa(gate));
+ return buf;
+}
+
+
+static void
+print_rts(struct rt_spare *rts,
+ int force_metric, /* -1=suppress, 0=default */
+ int force_ifp, /* -1=suppress, 0=default */
+ int force_router, /* -1=suppress, 0=default, 1=display */
+ int force_tag, /* -1=suppress, 0=default, 1=display */
+ int force_time) /* 0=suppress, 1=display */
+{
+ int i;
+
+
+ if (force_metric >= 0)
+ (void)fprintf(ftrace, "metric=%-2d ", rts->rts_metric);
+ if (force_ifp >= 0)
+ (void)fprintf(ftrace, "%s ", (rts->rts_ifp == 0 ?
+ "if?" : rts->rts_ifp->int_name));
+ if (force_router > 0
+ || (force_router == 0 && rts->rts_router != rts->rts_gate))
+ (void)fprintf(ftrace, "router=%s ",
+ naddr_ntoa(rts->rts_router));
+ if (force_time > 0)
+ (void)fprintf(ftrace, "%s ", ts(rts->rts_time));
+ if (force_tag > 0
+ || (force_tag == 0 && rts->rts_tag != 0))
+ (void)fprintf(ftrace, "tag=%#x ", ntohs(rts->rts_tag));
+ if (rts->rts_de_ag != 0) {
+ for (i = 1; (u_int)(1 << i) <= rts->rts_de_ag; i++)
+ continue;
+ (void)fprintf(ftrace, "de_ag=%d ", i);
+ }
+
+}
+
+
+void
+trace_if(const char *act,
+ struct interface *ifp)
+{
+ if (!TRACEACTIONS || ftrace == 0)
+ return;
+
+ lastlog();
+ (void)fprintf(ftrace, "%-3s interface %-4s ", act, ifp->int_name);
+ (void)fprintf(ftrace, "%-15s-->%-15s ",
+ naddr_ntoa(ifp->int_addr),
+ addrname(((ifp->int_if_flags & IFF_POINTOPOINT)
+ ? ifp->int_dstaddr
+ : htonl(ifp->int_net)),
+ ifp->int_mask, 1));
+ if (ifp->int_metric != 0)
+ (void)fprintf(ftrace, "metric=%d ", ifp->int_metric);
+ if (ifp->int_adj_inmetric != 0)
+ (void)fprintf(ftrace, "adj_inmetric=%u ",
+ ifp->int_adj_inmetric);
+ if (ifp->int_adj_outmetric != 0)
+ (void)fprintf(ftrace, "adj_outmetric=%u ",
+ ifp->int_adj_outmetric);
+ if (!IS_RIP_OUT_OFF(ifp->int_state)
+ && ifp->int_d_metric != 0)
+ (void)fprintf(ftrace, "fake_default=%u ", ifp->int_d_metric);
+ trace_bits(if_bits, ifp->int_if_flags, 0);
+ trace_bits(is_bits, ifp->int_state, 0);
+ (void)fputc('\n',ftrace);
+}
+
+
+void
+trace_upslot(struct rt_entry *rt,
+ struct rt_spare *rts,
+ struct rt_spare *new)
+{
+ if (!TRACEACTIONS || ftrace == 0)
+ return;
+
+ if (rts->rts_gate == new->rts_gate
+ && rts->rts_router == new->rts_router
+ && rts->rts_metric == new->rts_metric
+ && rts->rts_tag == new->rts_tag
+ && rts->rts_de_ag == new->rts_de_ag)
+ return;
+
+ lastlog();
+ if (new->rts_gate == 0) {
+ (void)fprintf(ftrace, "Del #%d %-35s ",
+ (int)(rts - rt->rt_spares),
+ rtname(rt->rt_dst, rt->rt_mask, rts->rts_gate));
+ print_rts(rts, 0,0,0,0,
+ (rts != rt->rt_spares
+ || AGE_RT(rt->rt_state,new->rts_ifp)));
+
+ } else if (rts->rts_gate != RIP_DEFAULT) {
+ (void)fprintf(ftrace, "Chg #%d %-35s ",
+ (int)(rts - rt->rt_spares),
+ rtname(rt->rt_dst, rt->rt_mask, rts->rts_gate));
+ print_rts(rts, 0,0,
+ rts->rts_gate != new->rts_gate,
+ rts->rts_tag != new->rts_tag,
+ rts != rt->rt_spares || AGE_RT(rt->rt_state,
+ rt->rt_ifp));
+
+ (void)fprintf(ftrace, "\n %19s%-16s ", "",
+ (new->rts_gate != rts->rts_gate
+ ? naddr_ntoa(new->rts_gate) : ""));
+ print_rts(new,
+ -(new->rts_metric == rts->rts_metric),
+ -(new->rts_ifp == rts->rts_ifp),
+ 0,
+ rts->rts_tag != new->rts_tag,
+ (new->rts_time != rts->rts_time
+ && (rts != rt->rt_spares
+ || AGE_RT(rt->rt_state, new->rts_ifp))));
+
+ } else {
+ (void)fprintf(ftrace, "Add #%d %-35s ",
+ (int)(rts - rt->rt_spares),
+ rtname(rt->rt_dst, rt->rt_mask, new->rts_gate));
+ print_rts(new, 0,0,0,0,
+ (rts != rt->rt_spares
+ || AGE_RT(rt->rt_state,new->rts_ifp)));
+ }
+ (void)fputc('\n',ftrace);
+}
+
+
+/* miscellaneous message checked by the caller
+ */
+void
+trace_misc(const char *p, ...)
+{
+ va_list args;
+
+ if (ftrace == 0)
+ return;
+
+ lastlog();
+ va_start(args, p);
+ vfprintf(ftrace, p, args);
+ va_end(args);
+ (void)fputc('\n',ftrace);
+}
+
+
+/* display a message if tracing actions
+ */
+void
+trace_act(const char *p, ...)
+{
+ va_list args;
+
+ if (!TRACEACTIONS || ftrace == 0)
+ return;
+
+ lastlog();
+ va_start(args, p);
+ vfprintf(ftrace, p, args);
+ va_end(args);
+ (void)fputc('\n',ftrace);
+}
+
+
+/* display a message if tracing packets
+ */
+void
+trace_pkt(const char *p, ...)
+{
+ va_list args;
+
+ if (!TRACEPACKETS || ftrace == 0)
+ return;
+
+ lastlog();
+ va_start(args, p);
+ vfprintf(ftrace, p, args);
+ va_end(args);
+ (void)fputc('\n',ftrace);
+}
+
+
+void
+trace_change(struct rt_entry *rt,
+ u_int state,
+ struct rt_spare *new,
+ const char *label)
+{
+ if (ftrace == 0)
+ return;
+
+ if (rt->rt_metric == new->rts_metric
+ && rt->rt_gate == new->rts_gate
+ && rt->rt_router == new->rts_router
+ && rt->rt_state == state
+ && rt->rt_tag == new->rts_tag
+ && rt->rt_de_ag == new->rts_de_ag)
+ return;
+
+ lastlog();
+ (void)fprintf(ftrace, "%s %-35s ",
+ label,
+ rtname(rt->rt_dst, rt->rt_mask, rt->rt_gate));
+ print_rts(rt->rt_spares,
+ 0,0,0,0, AGE_RT(rt->rt_state, rt->rt_ifp));
+ trace_bits(rs_bits, rt->rt_state, rt->rt_state != state);
+
+ (void)fprintf(ftrace, "\n%*s %19s%-16s ",
+ (int)strlen(label), "", "",
+ (rt->rt_gate != new->rts_gate
+ ? naddr_ntoa(new->rts_gate) : ""));
+ print_rts(new,
+ -(new->rts_metric == rt->rt_metric),
+ -(new->rts_ifp == rt->rt_ifp),
+ 0,
+ rt->rt_tag != new->rts_tag,
+ (rt->rt_time != new->rts_time
+ && AGE_RT(rt->rt_state,new->rts_ifp)));
+ if (rt->rt_state != state)
+ trace_bits(rs_bits, state, 1);
+ (void)fputc('\n',ftrace);
+}
+
+
+void
+trace_add_del(const char * action, struct rt_entry *rt)
+{
+ if (ftrace == 0)
+ return;
+
+ lastlog();
+ (void)fprintf(ftrace, "%s %-35s ",
+ action,
+ rtname(rt->rt_dst, rt->rt_mask, rt->rt_gate));
+ print_rts(rt->rt_spares, 0,0,0,0,AGE_RT(rt->rt_state,rt->rt_ifp));
+ trace_bits(rs_bits, rt->rt_state, 0);
+ (void)fputc('\n',ftrace);
+}
+
+
+/* ARGSUSED */
+static int
+walk_trace(struct radix_node *rn,
+ struct walkarg *w UNUSED)
+{
+#define RT ((struct rt_entry *)rn)
+ struct rt_spare *rts;
+ int i;
+
+ (void)fprintf(ftrace, " %-35s ",
+ rtname(RT->rt_dst, RT->rt_mask, RT->rt_gate));
+ print_rts(&RT->rt_spares[0], 0,0,0,0, AGE_RT(RT->rt_state, RT->rt_ifp));
+ trace_bits(rs_bits, RT->rt_state, 0);
+ if (RT->rt_poison_time >= now_garbage
+ && RT->rt_poison_metric < RT->rt_metric)
+ (void)fprintf(ftrace, "pm=%d@%s",
+ RT->rt_poison_metric, ts(RT->rt_poison_time));
+
+ rts = &RT->rt_spares[1];
+ for (i = 1; i < NUM_SPARES; i++, rts++) {
+ if (rts->rts_gate != RIP_DEFAULT) {
+ (void)fprintf(ftrace,"\n #%d%15s%-16s ",
+ i, "", naddr_ntoa(rts->rts_gate));
+ print_rts(rts, 0,0,0,0,1);
+ }
+ }
+ (void)fputc('\n',ftrace);
+
+ return 0;
+}
+
+
+static void
+trace_dump(void)
+{
+ struct interface *ifp;
+
+ if (ftrace == 0)
+ return;
+ lastlog();
+
+ (void)fputs("current daemon state:\n", ftrace);
+ LIST_FOREACH(ifp, &ifnet, int_list)
+ trace_if("", ifp);
+ (void)rn_walktree(rhead, walk_trace, 0);
+}
+
+
+void
+trace_rip(const char *dir1, const char *dir2,
+ struct sockaddr_in *who,
+ struct interface *ifp,
+ struct rip *msg,
+ int size) /* total size of message */
+{
+ struct netinfo *n, *lim;
+# define NA ((struct netauth*)n)
+ int i, seen_route;
+
+ if (!TRACEPACKETS || ftrace == 0)
+ return;
+
+ lastlog();
+ if (msg->rip_cmd >= RIPCMD_MAX
+ || msg->rip_vers == 0) {
+ (void)fprintf(ftrace, "%s bad RIPv%d cmd=%d %s"
+ " %s.%d size=%d\n",
+ dir1, msg->rip_vers, msg->rip_cmd, dir2,
+ naddr_ntoa(who->sin_addr.s_addr),
+ ntohs(who->sin_port),
+ size);
+ return;
+ }
+
+ (void)fprintf(ftrace, "%s RIPv%d %s %s %s.%d%s%s\n",
+ dir1, msg->rip_vers, ripcmds[msg->rip_cmd], dir2,
+ naddr_ntoa(who->sin_addr.s_addr), ntohs(who->sin_port),
+ ifp ? " via " : "", ifp ? ifp->int_name : "");
+ if (!TRACECONTENTS)
+ return;
+
+ seen_route = 0;
+ switch (msg->rip_cmd) {
+ case RIPCMD_REQUEST:
+ case RIPCMD_RESPONSE:
+ n = msg->rip_nets;
+ lim = (struct netinfo *)((char*)msg + size);
+ for (; n < lim; n++) {
+ if (!seen_route
+ && n->n_family == RIP_AF_UNSPEC
+ && ntohl(n->n_metric) == HOPCNT_INFINITY
+ && msg->rip_cmd == RIPCMD_REQUEST
+ && (n+1 == lim
+ || (n+2 == lim
+ && (n+1)->n_family == RIP_AF_AUTH))) {
+ (void)fputs("\tQUERY ", ftrace);
+ if (n->n_dst != 0)
+ (void)fprintf(ftrace, "%s ",
+ naddr_ntoa(n->n_dst));
+ if (n->n_mask != 0)
+ (void)fprintf(ftrace, "mask=%#x ",
+ (u_int)ntohl(n->n_mask));
+ if (n->n_nhop != 0)
+ (void)fprintf(ftrace, "nhop=%s ",
+ naddr_ntoa(n->n_nhop));
+ if (n->n_tag != 0)
+ (void)fprintf(ftrace, "tag=%#x ",
+ ntohs(n->n_tag));
+ (void)fputc('\n',ftrace);
+ continue;
+ }
+
+ if (n->n_family == RIP_AF_AUTH) {
+ if (NA->a_type == RIP_AUTH_PW
+ && n == msg->rip_nets) {
+ (void)fprintf(ftrace, "\tPassword"
+ " Authentication:"
+ " \"%s\"\n",
+ qstring(NA->au.au_pw,
+ RIP_AUTH_PW_LEN));
+ continue;
+ }
+
+ if (NA->a_type == RIP_AUTH_MD5
+ && n == msg->rip_nets) {
+ (void)fprintf(ftrace,
+ "\tMD5 Auth"
+ " pkt_len=%d KeyID=%u"
+ " auth_len=%d"
+ " seqno=%#x"
+ " rsvd=%#x,%#x\n",
+ ntohs(NA->au.a_md5.md5_pkt_len),
+ NA->au.a_md5.md5_keyid,
+ NA->au.a_md5.md5_auth_len,
+ (int)ntohl(NA->au.a_md5.md5_seqno),
+ (int)ntohs(NA->au.a_md5.rsvd[0]),
+ (int)ntohs(NA->au.a_md5.rsvd[1]));
+ continue;
+ }
+ (void)fprintf(ftrace,
+ "\tAuthentication type %d: ",
+ ntohs(NA->a_type));
+ for (i = 0;
+ i < (int)sizeof(NA->au.au_pw);
+ i++)
+ (void)fprintf(ftrace, "%02x ",
+ NA->au.au_pw[i]);
+ (void)fputc('\n',ftrace);
+ continue;
+ }
+
+ seen_route = 1;
+ if (n->n_family != RIP_AF_INET) {
+ (void)fprintf(ftrace,
+ "\t(af %d) %-18s mask=%#x ",
+ ntohs(n->n_family),
+ naddr_ntoa(n->n_dst),
+ (u_int)ntohl(n->n_mask));
+ } else if (msg->rip_vers == RIPv1) {
+ (void)fprintf(ftrace, "\t%-18s ",
+ addrname(n->n_dst,
+ ntohl(n->n_mask),
+ n->n_mask==0 ? 2 : 1));
+ } else {
+ (void)fprintf(ftrace, "\t%-18s ",
+ addrname(n->n_dst,
+ ntohl(n->n_mask),
+ n->n_mask==0 ? 2 : 0));
+ }
+ (void)fprintf(ftrace, "metric=%-2d ",
+ (u_int)ntohl(n->n_metric));
+ if (n->n_nhop != 0)
+ (void)fprintf(ftrace, " nhop=%s ",
+ naddr_ntoa(n->n_nhop));
+ if (n->n_tag != 0)
+ (void)fprintf(ftrace, "tag=%#x",
+ ntohs(n->n_tag));
+ (void)fputc('\n',ftrace);
+ }
+ if (size != (char *)n - (char *)msg)
+ (void)fprintf(ftrace, "truncated record, len %d\n",
+ size);
+ break;
+
+ case RIPCMD_TRACEON:
+ fprintf(ftrace, "\tfile=\"%.*s\"\n", size-4,
+ msg->rip_tracefile);
+ break;
+
+ case RIPCMD_TRACEOFF:
+ break;
+ }
+}
diff --git a/sbin/rtsol/Makefile b/sbin/rtsol/Makefile
new file mode 100644
index 0000000..d738008
--- /dev/null
+++ b/sbin/rtsol/Makefile
@@ -0,0 +1,28 @@
+# Copyright (c) 1996 WIDE Project. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modifications, are permitted provided that the above copyright notice
+# and this paragraph are duplicated in all such forms and that any
+# documentation, advertising materials, and other materials related to
+# such distribution and use acknowledge that the software was developed
+# by the WIDE Project, Japan. The name of the Project 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 WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT
+# LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE.
+#
+# $FreeBSD$
+
+SRCDIR= ${.CURDIR}/../../usr.sbin/rtsold
+
+.PATH: ${SRCDIR}
+
+PROG= rtsol
+SRCS= rtsold.c rtsol.c if.c probe.c dump.c rtsock.c
+MAN=
+
+WARNS?= 3
+CFLAGS+= -DHAVE_ARC4RANDOM -DHAVE_POLL_H -DSMALL
+
+.include <bsd.prog.mk>
diff --git a/sbin/savecore/Makefile b/sbin/savecore/Makefile
new file mode 100644
index 0000000..5134e4c
--- /dev/null
+++ b/sbin/savecore/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+PROG= savecore
+LIBADD= z xo
+MAN= savecore.8
+
+.include <bsd.prog.mk>
diff --git a/sbin/savecore/savecore.8 b/sbin/savecore/savecore.8
new file mode 100644
index 0000000..c7d7e95
--- /dev/null
+++ b/sbin/savecore/savecore.8
@@ -0,0 +1,170 @@
+.\" Copyright (c) 1980, 1991, 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.
+.\" 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.
+.\"
+.\" From: @(#)savecore.8 8.1 (Berkeley) 6/5/93
+.\" $FreeBSD$
+.\"
+.Dd December 17, 2012
+.Dt SAVECORE 8
+.Os
+.Sh NAME
+.Nm savecore
+.Nd "save a core dump of the operating system"
+.Sh SYNOPSIS
+.Nm
+.Fl c
+.Op Fl v
+.Op Ar device ...
+.Nm
+.Fl C
+.Op Fl v
+.Op Ar device ...
+.Nm
+.Op Fl fkvz
+.Op Fl m Ar maxdumps
+.Op Ar directory Op Ar device ...
+.Sh DESCRIPTION
+The
+.Nm
+utility
+copies a core dump into
+.Ar directory ,
+or the current working directory if no
+.Ar directory
+argument is given,
+and enters a reboot message and information about the core dump into
+the system log.
+.Pp
+The options are as follows:
+.Bl -tag -width ".Fl m Ar maxdumps"
+.It Fl C
+Check to see if a dump exists,
+and display a brief message to indicate the status.
+An exit status of 0 indicates that a dump is there,
+1 indicates that none exists.
+This option is compatible only with the
+.Op Fl v
+option.
+.It Fl c
+Clear the dump, so that future invocations of
+.Nm
+will ignore it.
+.It Fl f
+Force a dump to be taken even if either the dump was cleared or if the
+dump header information is inconsistent.
+.It Fl k
+Do not clear the dump after saving it.
+.It Fl m Ar maxdumps
+Maximum number of dumps to store.
+Once the number of stored dumps is equal to
+.Ar maxdumps
+the counter will restart from
+.Dv 0 .
+.It Fl v
+Print out some additional debugging information.
+Specify twice for more information.
+.It Fl z
+Compress the core dump and kernel (see
+.Xr gzip 1 ) .
+.El
+.Pp
+The
+.Nm
+utility
+looks for dumps on each device specified by the
+.Ar device
+argument(s), or on each device in
+.Pa /etc/fstab
+marked as
+.Dq dump
+or
+.Dq swap .
+The
+.Nm
+utility
+checks the core dump in various ways to make sure that it is complete.
+If it passes these checks, it saves the core image in
+.Ar directory Ns Pa /vmcore.#
+and information about the core in
+.Ar directory Ns Pa /info.# .
+For kernel textdumps generated with the
+.Xr textdump 4
+facility, output will be stored in the
+.Xr tar 5
+format and named
+.Ar directory Ns Pa /textdump.tar.# .
+The
+.Dq #
+is the number from the first line of the file
+.Ar directory Ns Pa /bounds ,
+and it is incremented and stored back into the file each time
+.Nm
+successfully runs.
+.Pp
+The
+.Nm
+utility
+also checks the available disk space before attempting to make the copies.
+If there is insufficient disk space in the file system containing
+.Ar directory ,
+or if the file
+.Ar directory Ns Pa /minfree
+exists and the number of free kilobytes (for non-superusers) in the
+file system after the copies were made would be less than the number
+in the first line of this file, the copies are not attempted.
+.Pp
+If
+.Nm
+successfully copies the kernel and the core dump, the core dump is cleared
+so that future invocations of
+.Nm
+will ignore it.
+.Pp
+The
+.Nm
+utility
+is meant to be called near the end of the initialization file
+.Pa /etc/rc
+(see
+.Xr rc 8 ) .
+.Sh SEE ALSO
+.Xr gzip 1 ,
+.Xr getbootfile 3 ,
+.Xr textdump 4 ,
+.Xr tar 5 ,
+.Xr dumpon 8 ,
+.Xr syslogd 8
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Bx 4.1 .
+.Pp
+Support for kernel textdumps appeared in
+.Fx 7.1 .
+.Sh BUGS
+The minfree code does not consider the effect of compression or sparse files.
diff --git a/sbin/savecore/savecore.c b/sbin/savecore/savecore.c
new file mode 100644
index 0000000..705261f
--- /dev/null
+++ b/sbin/savecore/savecore.c
@@ -0,0 +1,860 @@
+/*-
+ * Copyright (c) 2002 Poul-Henning Kamp
+ * Copyright (c) 2002 Networks Associates Technology, Inc.
+ * All rights reserved.
+ *
+ * This software was developed for the FreeBSD Project by Poul-Henning Kamp
+ * and NAI Labs, the Security Research Division of Network Associates, Inc.
+ * under DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), as part of the
+ * DARPA CHATS research program.
+ *
+ * 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 names of the authors 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 AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * Copyright (c) 1986, 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.
+ * 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/disk.h>
+#include <sys/kerneldump.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <fstab.h>
+#include <paths.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <time.h>
+#include <unistd.h>
+#include <libxo/xo.h>
+
+/* The size of the buffer used for I/O. */
+#define BUFFERSIZE (1024*1024)
+
+#define STATUS_BAD 0
+#define STATUS_GOOD 1
+#define STATUS_UNKNOWN 2
+
+static int checkfor, compress, clear, force, keep, verbose; /* flags */
+static int nfound, nsaved, nerr; /* statistics */
+static int maxdumps;
+
+extern FILE *zopen(const char *, const char *);
+
+static sig_atomic_t got_siginfo;
+static void infohandler(int);
+
+static void
+printheader(xo_handle_t *xo, const struct kerneldumpheader *h, const char *device,
+ int bounds, const int status)
+{
+ uint64_t dumplen;
+ time_t t;
+ const char *stat_str;
+
+ xo_flush_h(xo);
+ xo_emit_h(xo, "{Lwc:Dump header from device}{:dump_device/%s}\n", device);
+ xo_emit_h(xo, "{P: }{Lwc:Architecture}{:architecture/%s}\n", h->architecture);
+ xo_emit_h(xo, "{P: }{Lwc:Architecture Version}{:architecture_version/%u}\n", dtoh32(h->architectureversion));
+ dumplen = dtoh64(h->dumplength);
+ xo_emit_h(xo, "{P: }{Lwc:Dump Length}{:dump_length_bytes/%lld}\n", (long long)dumplen);
+ xo_emit_h(xo, "{P: }{Lwc:Blocksize}{:blocksize/%d}\n", dtoh32(h->blocksize));
+ t = dtoh64(h->dumptime);
+ xo_emit_h(xo, "{P: }{Lwc:Dumptime}{:dumptime/%s}", ctime(&t));
+ xo_emit_h(xo, "{P: }{Lwc:Hostname}{:hostname/%s}\n", h->hostname);
+ xo_emit_h(xo, "{P: }{Lwc:Magic}{:magic/%s}\n", h->magic);
+ xo_emit_h(xo, "{P: }{Lwc:Version String}{:version_string/%s}", h->versionstring);
+ xo_emit_h(xo, "{P: }{Lwc:Panic String}{:panic_string/%s}\n", h->panicstring);
+ xo_emit_h(xo, "{P: }{Lwc:Dump Parity}{:dump_parity/%u}\n", h->parity);
+ xo_emit_h(xo, "{P: }{Lwc:Bounds}{:bounds/%d}\n", bounds);
+
+ switch(status) {
+ case STATUS_BAD:
+ stat_str = "bad";
+ break;
+ case STATUS_GOOD:
+ stat_str = "good";
+ break;
+ default:
+ stat_str = "unknown";
+ }
+ xo_emit_h(xo, "{P: }{Lwc:Dump Status}{:dump_status/%s}\n", stat_str);
+ xo_flush_h(xo);
+}
+
+static int
+getbounds(void) {
+ FILE *fp;
+ char buf[6];
+ int ret;
+
+ ret = 0;
+
+ if ((fp = fopen("bounds", "r")) == NULL) {
+ if (verbose)
+ printf("unable to open bounds file, using 0\n");
+ return (ret);
+ }
+
+ if (fgets(buf, sizeof buf, fp) == NULL) {
+ if (feof(fp))
+ syslog(LOG_WARNING, "bounds file is empty, using 0");
+ else
+ syslog(LOG_WARNING, "bounds file: %s", strerror(errno));
+ fclose(fp);
+ return (ret);
+ }
+
+ errno = 0;
+ ret = (int)strtol(buf, NULL, 10);
+ if (ret == 0 && (errno == EINVAL || errno == ERANGE))
+ syslog(LOG_WARNING, "invalid value found in bounds, using 0");
+ fclose(fp);
+ return (ret);
+}
+
+static void
+writebounds(int bounds) {
+ FILE *fp;
+
+ if ((fp = fopen("bounds", "w")) == NULL) {
+ syslog(LOG_WARNING, "unable to write to bounds file: %m");
+ return;
+ }
+
+ if (verbose)
+ printf("bounds number: %d\n", bounds);
+
+ fprintf(fp, "%d\n", bounds);
+ fclose(fp);
+}
+
+static off_t
+file_size(const char *path)
+{
+ struct stat sb;
+
+ /* Ignore all errors, those file may not exists. */
+ if (stat(path, &sb) == -1)
+ return (0);
+ return (sb.st_size);
+}
+
+static off_t
+saved_dump_size(int bounds)
+{
+ static char path[PATH_MAX];
+ off_t dumpsize;
+
+ dumpsize = 0;
+
+ (void)snprintf(path, sizeof(path), "info.%d", bounds);
+ dumpsize += file_size(path);
+ (void)snprintf(path, sizeof(path), "vmcore.%d", bounds);
+ dumpsize += file_size(path);
+ (void)snprintf(path, sizeof(path), "vmcore.%d.gz", bounds);
+ dumpsize += file_size(path);
+ (void)snprintf(path, sizeof(path), "textdump.tar.%d", bounds);
+ dumpsize += file_size(path);
+ (void)snprintf(path, sizeof(path), "textdump.tar.%d.gz", bounds);
+ dumpsize += file_size(path);
+
+ return (dumpsize);
+}
+
+static void
+saved_dump_remove(int bounds)
+{
+ static char path[PATH_MAX];
+
+ (void)snprintf(path, sizeof(path), "info.%d", bounds);
+ (void)unlink(path);
+ (void)snprintf(path, sizeof(path), "vmcore.%d", bounds);
+ (void)unlink(path);
+ (void)snprintf(path, sizeof(path), "vmcore.%d.gz", bounds);
+ (void)unlink(path);
+ (void)snprintf(path, sizeof(path), "textdump.tar.%d", bounds);
+ (void)unlink(path);
+ (void)snprintf(path, sizeof(path), "textdump.tar.%d.gz", bounds);
+ (void)unlink(path);
+}
+
+static void
+symlinks_remove(void)
+{
+
+ (void)unlink("info.last");
+ (void)unlink("vmcore.last");
+ (void)unlink("vmcore.last.gz");
+ (void)unlink("textdump.tar.last");
+ (void)unlink("textdump.tar.last.gz");
+}
+
+/*
+ * Check that sufficient space is available on the disk that holds the
+ * save directory.
+ */
+static int
+check_space(const char *savedir, off_t dumpsize, int bounds)
+{
+ FILE *fp;
+ off_t minfree, spacefree, totfree, needed;
+ struct statfs fsbuf;
+ char buf[100];
+
+ if (statfs(".", &fsbuf) < 0) {
+ syslog(LOG_ERR, "%s: %m", savedir);
+ exit(1);
+ }
+ spacefree = ((off_t) fsbuf.f_bavail * fsbuf.f_bsize) / 1024;
+ totfree = ((off_t) fsbuf.f_bfree * fsbuf.f_bsize) / 1024;
+
+ if ((fp = fopen("minfree", "r")) == NULL)
+ minfree = 0;
+ else {
+ if (fgets(buf, sizeof(buf), fp) == NULL)
+ minfree = 0;
+ else
+ minfree = atoi(buf);
+ (void)fclose(fp);
+ }
+
+ needed = dumpsize / 1024 + 2; /* 2 for info file */
+ needed -= saved_dump_size(bounds);
+ if ((minfree > 0 ? spacefree : totfree) - needed < minfree) {
+ syslog(LOG_WARNING,
+ "no dump, not enough free space on device (%lld available, need %lld)",
+ (long long)(minfree > 0 ? spacefree : totfree),
+ (long long)needed);
+ return (0);
+ }
+ if (spacefree - needed < 0)
+ syslog(LOG_WARNING,
+ "dump performed, but free space threshold crossed");
+ return (1);
+}
+
+#define BLOCKSIZE (1<<12)
+#define BLOCKMASK (~(BLOCKSIZE-1))
+
+static int
+DoRegularFile(int fd, off_t dumpsize, char *buf, const char *device,
+ const char *filename, FILE *fp)
+{
+ int he, hs, nr, nw, wl;
+ off_t dmpcnt, origsize;
+
+ dmpcnt = 0;
+ origsize = dumpsize;
+ he = 0;
+ while (dumpsize > 0) {
+ wl = BUFFERSIZE;
+ if (wl > dumpsize)
+ wl = dumpsize;
+ nr = read(fd, buf, wl);
+ if (nr != wl) {
+ if (nr == 0)
+ syslog(LOG_WARNING,
+ "WARNING: EOF on dump device");
+ else
+ syslog(LOG_ERR, "read error on %s: %m", device);
+ nerr++;
+ return (-1);
+ }
+ if (compress) {
+ nw = fwrite(buf, 1, wl, fp);
+ } else {
+ for (nw = 0; nw < nr; nw = he) {
+ /* find a contiguous block of zeroes */
+ for (hs = nw; hs < nr; hs += BLOCKSIZE) {
+ for (he = hs; he < nr && buf[he] == 0;
+ ++he)
+ /* nothing */ ;
+ /* is the hole long enough to matter? */
+ if (he >= hs + BLOCKSIZE)
+ break;
+ }
+
+ /* back down to a block boundary */
+ he &= BLOCKMASK;
+
+ /*
+ * 1) Don't go beyond the end of the buffer.
+ * 2) If the end of the buffer is less than
+ * BLOCKSIZE bytes away, we're at the end
+ * of the file, so just grab what's left.
+ */
+ if (hs + BLOCKSIZE > nr)
+ hs = he = nr;
+
+ /*
+ * At this point, we have a partial ordering:
+ * nw <= hs <= he <= nr
+ * If hs > nw, buf[nw..hs] contains non-zero data.
+ * If he > hs, buf[hs..he] is all zeroes.
+ */
+ if (hs > nw)
+ if (fwrite(buf + nw, hs - nw, 1, fp)
+ != 1)
+ break;
+ if (he > hs)
+ if (fseeko(fp, he - hs, SEEK_CUR) == -1)
+ break;
+ }
+ }
+ if (nw != wl) {
+ syslog(LOG_ERR,
+ "write error on %s file: %m", filename);
+ syslog(LOG_WARNING,
+ "WARNING: vmcore may be incomplete");
+ nerr++;
+ return (-1);
+ }
+ if (verbose) {
+ dmpcnt += wl;
+ printf("%llu\r", (unsigned long long)dmpcnt);
+ fflush(stdout);
+ }
+ dumpsize -= wl;
+ if (got_siginfo) {
+ printf("%s %.1lf%%\n", filename, (100.0 - (100.0 *
+ (double)dumpsize / (double)origsize)));
+ got_siginfo = 0;
+ }
+ }
+ return (0);
+}
+
+/*
+ * Specialized version of dump-reading logic for use with textdumps, which
+ * are written backwards from the end of the partition, and must be reversed
+ * before being written to the file. Textdumps are small, so do a bit less
+ * work to optimize/sparsify.
+ */
+static int
+DoTextdumpFile(int fd, off_t dumpsize, off_t lasthd, char *buf,
+ const char *device, const char *filename, FILE *fp)
+{
+ int nr, nw, wl;
+ off_t dmpcnt, totsize;
+
+ totsize = dumpsize;
+ dmpcnt = 0;
+ wl = 512;
+ if ((dumpsize % wl) != 0) {
+ syslog(LOG_ERR, "textdump uneven multiple of 512 on %s",
+ device);
+ nerr++;
+ return (-1);
+ }
+ while (dumpsize > 0) {
+ nr = pread(fd, buf, wl, lasthd - (totsize - dumpsize) - wl);
+ if (nr != wl) {
+ if (nr == 0)
+ syslog(LOG_WARNING,
+ "WARNING: EOF on dump device");
+ else
+ syslog(LOG_ERR, "read error on %s: %m", device);
+ nerr++;
+ return (-1);
+ }
+ nw = fwrite(buf, 1, wl, fp);
+ if (nw != wl) {
+ syslog(LOG_ERR,
+ "write error on %s file: %m", filename);
+ syslog(LOG_WARNING,
+ "WARNING: textdump may be incomplete");
+ nerr++;
+ return (-1);
+ }
+ if (verbose) {
+ dmpcnt += wl;
+ printf("%llu\r", (unsigned long long)dmpcnt);
+ fflush(stdout);
+ }
+ dumpsize -= wl;
+ }
+ return (0);
+}
+
+static void
+DoFile(const char *savedir, const char *device)
+{
+ xo_handle_t *xostdout, *xoinfo;
+ static char infoname[PATH_MAX], corename[PATH_MAX], linkname[PATH_MAX];
+ static char *buf = NULL;
+ struct kerneldumpheader kdhf, kdhl;
+ off_t mediasize, dumpsize, firsthd, lasthd;
+ FILE *info, *fp;
+ mode_t oumask;
+ int fd, fdinfo, error;
+ int bounds, status;
+ u_int sectorsize, xostyle;
+ int istextdump;
+
+ bounds = getbounds();
+ mediasize = 0;
+ status = STATUS_UNKNOWN;
+
+ xostdout = xo_create_to_file(stdout, XO_STYLE_TEXT, 0);
+ if (xostdout == NULL) {
+ syslog(LOG_ERR, "%s: %m", infoname);
+ return;
+ }
+
+ if (maxdumps > 0 && bounds == maxdumps)
+ bounds = 0;
+
+ if (buf == NULL) {
+ buf = malloc(BUFFERSIZE);
+ if (buf == NULL) {
+ syslog(LOG_ERR, "%m");
+ return;
+ }
+ }
+
+ if (verbose)
+ printf("checking for kernel dump on device %s\n", device);
+
+ fd = open(device, (checkfor || keep) ? O_RDONLY : O_RDWR);
+ if (fd < 0) {
+ syslog(LOG_ERR, "%s: %m", device);
+ return;
+ }
+
+ error = ioctl(fd, DIOCGMEDIASIZE, &mediasize);
+ if (!error)
+ error = ioctl(fd, DIOCGSECTORSIZE, &sectorsize);
+ if (error) {
+ syslog(LOG_ERR,
+ "couldn't find media and/or sector size of %s: %m", device);
+ goto closefd;
+ }
+
+ if (verbose) {
+ printf("mediasize = %lld\n", (long long)mediasize);
+ printf("sectorsize = %u\n", sectorsize);
+ }
+
+ lasthd = mediasize - sectorsize;
+ lseek(fd, lasthd, SEEK_SET);
+ error = read(fd, &kdhl, sizeof kdhl);
+ if (error != sizeof kdhl) {
+ syslog(LOG_ERR,
+ "error reading last dump header at offset %lld in %s: %m",
+ (long long)lasthd, device);
+ goto closefd;
+ }
+ istextdump = 0;
+ if (strncmp(kdhl.magic, TEXTDUMPMAGIC, sizeof kdhl) == 0) {
+ if (verbose)
+ printf("textdump magic on last dump header on %s\n",
+ device);
+ istextdump = 1;
+ if (dtoh32(kdhl.version) != KERNELDUMP_TEXT_VERSION) {
+ syslog(LOG_ERR,
+ "unknown version (%d) in last dump header on %s",
+ dtoh32(kdhl.version), device);
+
+ status = STATUS_BAD;
+ if (force == 0)
+ goto closefd;
+ }
+ } else if (memcmp(kdhl.magic, KERNELDUMPMAGIC, sizeof kdhl.magic) ==
+ 0) {
+ if (dtoh32(kdhl.version) != KERNELDUMPVERSION) {
+ syslog(LOG_ERR,
+ "unknown version (%d) in last dump header on %s",
+ dtoh32(kdhl.version), device);
+
+ status = STATUS_BAD;
+ if (force == 0)
+ goto closefd;
+ }
+ } else {
+ if (verbose)
+ printf("magic mismatch on last dump header on %s\n",
+ device);
+
+ status = STATUS_BAD;
+ if (force == 0)
+ goto closefd;
+
+ if (memcmp(kdhl.magic, KERNELDUMPMAGIC_CLEARED,
+ sizeof kdhl.magic) == 0) {
+ if (verbose)
+ printf("forcing magic on %s\n", device);
+ memcpy(kdhl.magic, KERNELDUMPMAGIC,
+ sizeof kdhl.magic);
+ } else {
+ syslog(LOG_ERR, "unable to force dump - bad magic");
+ goto closefd;
+ }
+ if (dtoh32(kdhl.version) != KERNELDUMPVERSION) {
+ syslog(LOG_ERR,
+ "unknown version (%d) in last dump header on %s",
+ dtoh32(kdhl.version), device);
+
+ status = STATUS_BAD;
+ if (force == 0)
+ goto closefd;
+ }
+ }
+
+ nfound++;
+ if (clear)
+ goto nuke;
+
+ if (kerneldump_parity(&kdhl)) {
+ syslog(LOG_ERR,
+ "parity error on last dump header on %s", device);
+ nerr++;
+ status = STATUS_BAD;
+ if (force == 0)
+ goto closefd;
+ }
+ dumpsize = dtoh64(kdhl.dumplength);
+ firsthd = lasthd - dumpsize - sizeof kdhf;
+ lseek(fd, firsthd, SEEK_SET);
+ error = read(fd, &kdhf, sizeof kdhf);
+ if (error != sizeof kdhf) {
+ syslog(LOG_ERR,
+ "error reading first dump header at offset %lld in %s: %m",
+ (long long)firsthd, device);
+ nerr++;
+ goto closefd;
+ }
+
+ if (verbose >= 2) {
+ printf("First dump headers:\n");
+ printheader(xostdout, &kdhf, device, bounds, -1);
+
+ printf("\nLast dump headers:\n");
+ printheader(xostdout, &kdhl, device, bounds, -1);
+ printf("\n");
+ }
+
+ if (memcmp(&kdhl, &kdhf, sizeof kdhl)) {
+ syslog(LOG_ERR,
+ "first and last dump headers disagree on %s", device);
+ nerr++;
+ status = STATUS_BAD;
+ if (force == 0)
+ goto closefd;
+ } else {
+ status = STATUS_GOOD;
+ }
+
+ if (checkfor) {
+ printf("A dump exists on %s\n", device);
+ close(fd);
+ exit(0);
+ }
+
+ if (kdhl.panicstring[0])
+ syslog(LOG_ALERT, "reboot after panic: %s", kdhl.panicstring);
+ else
+ syslog(LOG_ALERT, "reboot");
+
+ if (verbose)
+ printf("Checking for available free space\n");
+
+ if (!check_space(savedir, dumpsize, bounds)) {
+ nerr++;
+ goto closefd;
+ }
+
+ writebounds(bounds + 1);
+
+ saved_dump_remove(bounds);
+
+ snprintf(infoname, sizeof(infoname), "info.%d", bounds);
+
+ /*
+ * Create or overwrite any existing dump header files.
+ */
+ fdinfo = open(infoname, O_WRONLY | O_CREAT | O_TRUNC, 0600);
+ if (fdinfo < 0) {
+ syslog(LOG_ERR, "%s: %m", infoname);
+ nerr++;
+ goto closefd;
+ }
+
+ oumask = umask(S_IRWXG|S_IRWXO); /* Restrict access to the core file.*/
+ if (compress) {
+ snprintf(corename, sizeof(corename), "%s.%d.gz",
+ istextdump ? "textdump.tar" : "vmcore", bounds);
+ fp = zopen(corename, "w");
+ } else {
+ snprintf(corename, sizeof(corename), "%s.%d",
+ istextdump ? "textdump.tar" : "vmcore", bounds);
+ fp = fopen(corename, "w");
+ }
+ if (fp == NULL) {
+ syslog(LOG_ERR, "%s: %m", corename);
+ close(fdinfo);
+ nerr++;
+ goto closefd;
+ }
+ (void)umask(oumask);
+
+ info = fdopen(fdinfo, "w");
+
+ if (info == NULL) {
+ syslog(LOG_ERR, "fdopen failed: %m");
+ nerr++;
+ goto closefd;
+ }
+
+ xostyle = xo_get_style(NULL);
+ xoinfo = xo_create_to_file(info, xostyle, 0);
+ if (xoinfo == NULL) {
+ syslog(LOG_ERR, "%s: %m", infoname);
+ nerr++;
+ goto closefd;
+ }
+ xo_open_container_h(xoinfo, "crashdump");
+
+ if (verbose)
+ printheader(xostdout, &kdhl, device, bounds, status);
+
+ printheader(xoinfo, &kdhl, device, bounds, status);
+ xo_close_container_h(xoinfo, "crashdump");
+ xo_flush_h(xoinfo);
+ xo_finish_h(xoinfo);
+ fclose(info);
+
+ syslog(LOG_NOTICE, "writing %score to %s/%s",
+ compress ? "compressed " : "", savedir, corename);
+
+ if (istextdump) {
+ if (DoTextdumpFile(fd, dumpsize, lasthd, buf, device,
+ corename, fp) < 0)
+ goto closeall;
+ } else {
+ if (DoRegularFile(fd, dumpsize, buf, device, corename, fp)
+ < 0)
+ goto closeall;
+ }
+ if (verbose)
+ printf("\n");
+
+ if (fclose(fp) < 0) {
+ syslog(LOG_ERR, "error on %s: %m", corename);
+ nerr++;
+ goto closefd;
+ }
+
+ symlinks_remove();
+ if (symlink(infoname, "info.last") == -1) {
+ syslog(LOG_WARNING, "unable to create symlink %s/%s: %m",
+ savedir, "info.last");
+ }
+ if (compress) {
+ snprintf(linkname, sizeof(linkname), "%s.last.gz",
+ istextdump ? "textdump.tar" : "vmcore");
+ } else {
+ snprintf(linkname, sizeof(linkname), "%s.last",
+ istextdump ? "textdump.tar" : "vmcore");
+ }
+ if (symlink(corename, linkname) == -1) {
+ syslog(LOG_WARNING, "unable to create symlink %s/%s: %m",
+ savedir, linkname);
+ }
+
+ nsaved++;
+
+ if (verbose)
+ printf("dump saved\n");
+
+nuke:
+ if (!keep) {
+ if (verbose)
+ printf("clearing dump header\n");
+ memcpy(kdhl.magic, KERNELDUMPMAGIC_CLEARED, sizeof kdhl.magic);
+ lseek(fd, lasthd, SEEK_SET);
+ error = write(fd, &kdhl, sizeof kdhl);
+ if (error != sizeof kdhl)
+ syslog(LOG_ERR,
+ "error while clearing the dump header: %m");
+ }
+ xo_close_container_h(xostdout, "crashdump");
+ xo_finish_h(xostdout);
+ close(fd);
+ return;
+
+closeall:
+ fclose(fp);
+
+closefd:
+ close(fd);
+}
+
+static void
+usage(void)
+{
+ xo_error("%s\n%s\n%s\n",
+ "usage: savecore -c [-v] [device ...]",
+ " savecore -C [-v] [device ...]",
+ " savecore [-fkvz] [-m maxdumps] [directory [device ...]]");
+ exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+ const char *savedir = ".";
+ struct fstab *fsp;
+ int i, ch, error;
+
+ checkfor = compress = clear = force = keep = verbose = 0;
+ nfound = nsaved = nerr = 0;
+
+ openlog("savecore", LOG_PERROR, LOG_DAEMON);
+ signal(SIGINFO, infohandler);
+
+ argc = xo_parse_args(argc, argv);
+ if (argc < 0)
+ exit(1);
+
+ while ((ch = getopt(argc, argv, "Ccfkm:vz")) != -1)
+ switch(ch) {
+ case 'C':
+ checkfor = 1;
+ break;
+ case 'c':
+ clear = 1;
+ break;
+ case 'f':
+ force = 1;
+ break;
+ case 'k':
+ keep = 1;
+ break;
+ case 'm':
+ maxdumps = atoi(optarg);
+ if (maxdumps <= 0) {
+ syslog(LOG_ERR, "Invalid maxdump value");
+ exit(1);
+ }
+ break;
+ case 'v':
+ verbose++;
+ break;
+ case 'z':
+ compress = 1;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ if (checkfor && (clear || force || keep))
+ usage();
+ if (clear && (compress || keep))
+ usage();
+ if (maxdumps > 0 && (checkfor || clear))
+ usage();
+ argc -= optind;
+ argv += optind;
+ if (argc >= 1 && !checkfor && !clear) {
+ error = chdir(argv[0]);
+ if (error) {
+ syslog(LOG_ERR, "chdir(%s): %m", argv[0]);
+ exit(1);
+ }
+ savedir = argv[0];
+ argc--;
+ argv++;
+ }
+ if (argc == 0) {
+ for (;;) {
+ fsp = getfsent();
+ if (fsp == NULL)
+ break;
+ if (strcmp(fsp->fs_vfstype, "swap") &&
+ strcmp(fsp->fs_vfstype, "dump"))
+ continue;
+ DoFile(savedir, fsp->fs_spec);
+ }
+ } else {
+ for (i = 0; i < argc; i++)
+ DoFile(savedir, argv[i]);
+ }
+
+ /* Emit minimal output. */
+ if (nfound == 0) {
+ if (checkfor) {
+ printf("No dump exists\n");
+ exit(1);
+ }
+ syslog(LOG_WARNING, "no dumps found");
+ }
+ else if (nsaved == 0) {
+ if (nerr != 0)
+ syslog(LOG_WARNING, "unsaved dumps found but not saved");
+ else
+ syslog(LOG_WARNING, "no unsaved dumps found");
+ }
+
+ return (0);
+}
+
+static void
+infohandler(int sig __unused)
+{
+ got_siginfo = 1;
+}
diff --git a/sbin/sconfig/Makefile b/sbin/sconfig/Makefile
new file mode 100644
index 0000000..9698c04
--- /dev/null
+++ b/sbin/sconfig/Makefile
@@ -0,0 +1,9 @@
+# Cronyx Id: sbin.sconfig.Makefile,v 1.1.4.1 2003/02/17 12:51:24 rik Exp $
+# $FreeBSD$
+
+PROG= sconfig
+MAN= sconfig.8
+MANSUBDIR= /i386
+WARNS?= 2
+
+.include <bsd.prog.mk>
diff --git a/sbin/sconfig/sconfig.8 b/sbin/sconfig/sconfig.8
new file mode 100644
index 0000000..aa52b64
--- /dev/null
+++ b/sbin/sconfig/sconfig.8
@@ -0,0 +1,607 @@
+.\" Copyright (c) 2002-2004 Roman Kurakin <rik@cronyx.ru>
+.\" Copyright (c) 2002-2004 Cronyx Engineering
+.\" All rights reserved.
+.\"
+.\" This software is distributed with NO WARRANTIES, not even the implied
+.\" warranties for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.\"
+.\" Authors grant any other persons or organisations a permission to use,
+.\" modify and redistribute this software in source and binary forms,
+.\" as long as this message is kept with the software, all derivative
+.\" works or modified versions.
+.\"
+.\" $FreeBSD$
+.Dd May 19, 2004
+.Dt SCONFIG 8 i386
+.Os
+.Sh NAME
+.Nm sconfig
+.Nd "channel configuration utility for Cronyx adapters"
+.Sh SYNOPSIS
+.Nm
+.Op Fl aimsxeftuc
+.Op Ar device
+.Op Ar data_rate_options
+.Op Ar protocol_options ...
+.Op Ar interface_options ...
+.Sh DESCRIPTION
+The
+.Nm
+utility is used for configuring the channel options of the Cronyx
+adapters.
+In asynchronous mode, all parameters should be set using the standard
+.Xr stty 1
+utility.
+With
+.Nm ,
+it is only possible to set some of them (see below).
+.Pp
+Some of the options can be set only on free channels,
+that is when the corresponding network interface is in the
+.Cm down
+state in the synchronous mode,
+and the terminal device
+.Pa /dev/tty*
+is closed in asynchronous mode.
+.Pp
+Other channel options can be changed
+.Dq "on the fly" .
+Generally, the channel options are set up during the operating system startup,
+for example, from the
+.Pa /etc/rc
+script.
+.Pp
+Note that not all options make sense in every particular case,
+and an attempt to set some of them may hung up the channel
+or the whole adapter.
+.\"--------------------------------------------------------------
+.Ss "Information Options"
+Only one of these options can be specified.
+If information option is specified,
+.Nm
+will show the corresponding information and will ignore all other options,
+except
+.Ar device .
+See also the description of the
+.Ar device
+argument.
+.Bl -tag -width indent
+.It <none>
+This will show settings of the channel.
+.It Fl a
+Print all settings of the channel.
+.It Fl i
+Print interface settings, equal to the output of the
+.Xr ifconfig 8
+utility.
+.It Fl m
+Print modem signal status.
+The description of all signals can be found in any modem documentation.
+Only LE signal should be described.
+If this signal is ON then the channel is busy.
+If it is OFF then the channel is free.
+.It Fl s
+Print brief channel statistics.
+This is the generic statistics,
+see also the
+.Fl x , e , f , t ,
+and
+.Fl u
+options.
+For a description of the output, see below.
+.Pp
+This statistics is very useful if something goes wrong.
+For example, if all interrupt counters are zero then the device
+was configured to use an interrupt that was not registered in the
+BIOS for use with the ISA bus.
+.It Fl x
+Print full channel statistics.
+This options prints additional counters,
+but with less precision than with the
+.Fl s
+option.
+.It Fl e
+Print brief E1/G703 statistics.
+If this option is selected, the
+statistics accumulated over the last 15 minutes is printed.
+For a description of the output, see below.
+.It Fl f
+Print full E1/G703 statistics.
+This option shows all E1/G703 statistics that the
+.Fl e
+option shows,
+plus total statistics for the whole period of time and statistics for
+last 24 hours (if available).
+For a description of the output, see below.
+.It Fl t
+Print brief E3/T3/STS-1 statistics.
+If this option is selected, the
+statistics accumulated over the last 15 minutes is printed.
+For a description of the output, see below.
+.It Fl u
+Print full E3/T3/STS-1 statistics.
+This option shows all E3/T3/STS-1 statistics that the
+.Fl t
+option shows,
+plus total statistics for the whole period of time and statistics for
+last 24 hours (if available).
+For a description of the output, see below.
+.It Fl c
+Cleans all kind of statistics.
+.El
+.\"--------------------------------------------------------------
+.Ss "Device Selection"
+The device is selected using the name of the network interface,
+as shown by
+.Xr ifconfig 8 .
+The channel number depends on the order the drivers were loaded into the system.
+Sometimes people confuse channel number and adapter number because of the
+same spelling.
+The adapter number appears in the kernel context, while the channel number
+is in the configuration context.
+.Bl -tag -width indent
+.It <none>
+If the device name is omitted,
+.Nm
+will print information about all channels of all Cronyx adapters
+available in the system.
+If some settings need to be made, the device name must be specified.
+.It Li cx Ns Ar ##
+This is the channel name for the Sigma family of Cronyx adapters.
+(ISA bus.)
+.It Li ct Ns Ar ##
+This is the channel name for the Tau family of Cronyx adapters.
+(ISA bus.)
+.It Li cp Ns Ar ##
+This is the channel name for the Tau-PCI family of Cronyx adapters.
+(PCI bus.)
+.It Li ce Ns Ar ##
+This is the channel name for the Tau32-PCI family of Cronyx adapters.
+(PCI bus.)
+.El
+.\"--------------------------------------------------------------
+.Ss "Data Rate Options"
+.Bl -tag -width indent
+.It Ar value
+A non-zero value will set the data rate to a given value
+in asynchronous mode,
+and will set the data rate and internal clock source of synchronization
+in synchronous mode.
+A zero value is equivalent to specifying the
+.Cm extclock
+option.
+The transmitted data (TxD) are synchronized using the internal on-board timing
+generator, the internally generated timing signal is driven on the TXCOUT pin,
+and the signal on the TXCIN pin is ignored.
+This mode is used for direct
+terminal-to-terminal communication, e.g., when connecting two computers together
+in synchronous mode with a relatively short cable.
+This method should also be
+used for testing channels with an external loopback connector.
+.It Cm extclock
+Set the external timing clock source for synchronous channels.
+External clock mode is the most commonly used method for connecting
+external modem hardware.
+In this mode,
+the external timing signal is received on the TXCIN pin of the connector,
+and it is used as a synchronization clock for transmitting data (TxD).
+.Pp
+Note: in
+.Cm extclock
+mode, the device cannot determine the value of the external timing clock
+since it does not have the built-in clock gauge.
+.El
+.\"--------------------------------------------------------------
+.Ss "Protocol Options"
+Note: these option can only be used on a free channel, and they require
+specifying the device name.
+.Bl -tag -width indent
+.It Cm async
+(Only for Sigma family.)
+Select the asynchronous protocol (or mode).
+In this mode, Cronyx adapters behave as normal serial devices,
+and standard serial communications utilities can be used to
+work with them.
+All asynchronous settings should be set using the standard
+serial communications configuration utilities, e.g.,
+.Xr stty 1 .
+With
+.Nm ,
+it is only possible to set some of them.
+.It Cm cisco
+Select the Cisco HDLC synchronous protocol.
+.It Cm fr
+Select the Frame Relay synchronous protocol
+.Tn ( ANSI
+T1.617 Annex D).
+.It Cm ppp
+Select the synchronous PPP protocol.
+PPP parameters can be configured using the
+.Xr spppcontrol 8
+utility.
+.It Sm Cm keepalive No = Bro Cm on , off Brc Sm
+Turn on/off transmission of keepalive messages.
+This option is used only for synchronous PPP.
+If this option is
+.Cm on ,
+PPP will periodically send ECHO-REQUEST messages.
+If it will not receive any ECHO-REPLY messages for
+some (definite) period of time it will break the connection.
+It is used for tracking the line state.
+.It Cm idle
+This mode is reported when using Netgraph.
+An actual protocol depends on the type of a connected Netgraph node,
+and it cannot be changed with
+.Nm .
+.El
+.\"--------------------------------------------------------------
+.Ss "Interface Options"
+Not all of these options can be set on a busy channel, and not all of them
+are applicable to all kinds of adapters/channels.
+For all dual-state options,
+.Cm off
+is the default value.
+None of these options can be used in the asynchronous mode,
+except for the
+.Cm debug
+option.
+.Bl -tag -width indent
+.It Sm Cm port No = Bro Cm rs232 , v35 , rs449 Brc Sm
+Set the port type for old Sigma models.
+.It Sm Cm cfg No = Bro Cm A , B , C Brc Sm
+Set the configuration for the adapter.
+This option can be used only with Tau/E1
+and Tau/G703 adapters, and only if all channels are free.
+.Bl -tag -width ".Cm cfg Ns = Ns Cm A"
+.It Cm cfg Ns = Ns Cm A
+Two independent E1/G703 channels.
+This is the default setting.
+.It Cm cfg Ns = Ns Cm B
+(Only for ISA models.)
+For Tau/G703 this means one G703 channel and one digital channel.
+For Tau/E1, the first physical channel is divided into two subchannels.
+One of them goes to the first logical channel, another one goes to the
+second physical channel.
+Second (logical) channel is the digital channel.
+.It Cm cfg Ns = Ns Cm C
+(Only for E1 models.)
+In this mode, first
+physical channel consists of three data flows.
+Two of them go to the two (logical) channels.
+The last one goes to the second physical channel.
+On newer models (Tau32-PCI, Tau-PCI/2E1 and Tau-PCI/4E1),
+this programs the hardware to use a single source of synchronization
+and pass all unused (in both channels) timeslots from
+one channel to another.
+.El
+.Pp
+For a detailed description of available configuration modes,
+see the adapter documentation.
+This option cannot be set on a busy channel.
+.It Sm Cm loop No = Bro Cm on , off Brc Sm
+Turn on/off internal loopback.
+This mode is useful for debugging.
+When this mode is
+.Cm on ,
+some data should be sent.
+If no interrupts are generated, chances are that
+the corresponding IRQ configuration entry in the BIOS
+was not switched from
+.Dq Li "PCI/ISA PNP"
+to
+.Dq Li "Legacy ISA" .
+.It Sm Cm rloop No = Bro Cm on , off Brc Sm
+(Only for Tau32-PCI and Tau-PCI/E3.)
+Turn on/off remote loopback feature.
+This mode is also useful for debugging.
+.It Sm Cm dpll No = Bro Cm on , off Brc Sm
+Turn on/off digital phase locked loop mode (DPLL).
+When enabled, the receiver
+timing clock signal is derived from the received data.
+Must be used with the NRZI
+encoding to avoid the synchronization loss.
+.It Sm Cm nrzi No = Bro Cm on , off Brc Sm
+Turn on/off NRZI encoding.
+If
+.Cm off ,
+NRZ encoding is used.
+.Bl -tag -width "NRZI"
+.It NRZ
+The zero bit is transmitted by the zero signal level,
+the one bit is transmitted by the positive signal level.
+.It NRZI
+The zero bit is transmitted by the change of the signal
+level, the one bit is by the constant signal level.
+Commonly used with the
+.Cm dpll Ns = Ns Cm on
+option.
+.El
+.It Sm Cm invclk No = Bro Cm on , off Brc Sm
+(Tau and Tau-PCI only.)
+Invert both the transmit and receive clock signals.
+.It Sm Cm invrclk No = Bro Cm on , off Brc Sm
+(Tau-PCI only.)
+Invert the receive clock signals.
+.It Sm Cm invtclk No = Bro Cm on , off Brc Sm
+(Tau-PCI only.)
+Invert the transmit clock signals.
+.It Sm Cm higain No = Bro Cm on , off Brc Sm
+(E1 only.)
+In off state the sensitivity is -12 dB.
+Turn on/off increasing the E1 receiver's non-linear sensitivity to -30dB.
+This allows increasing of the line distance.
+.It Sm Cm cablen No = Bro Cm on , off Brc Sm
+(Tau-PCI/T3 and Tau-PCI/STS-1 only.)
+Turn on/off adjusting of the transmit signal for a long cable T3/STS-1.
+.It Sm Cm monitor No = Bro Cm on , off Brc Sm
+(Tau32-PCI, Tau-PCI/2E1 and Tau-PCI/4E1 only.)
+Turn on/off increasing of the E1 receiver's linear sensitivity to -30dB.
+This can be used for the interception purposes.
+.It Sm Cm phony No = Bro Cm on , off Brc Sm
+(Tau32-PCI and Tau-PCI E1 family only.)
+Turn on/off the so-called
+.Dq phony
+mode.
+This mode allows
+receiving raw CEPT frames from the E1 line.
+Raw frames can be accessed, for example, with the raw protocol.
+Packets would come at a rate of 500 frames per second
+with length
+.No 16* Ns Ar N
+(for Tau-PCI/E1 model), where
+.Ar N
+is the number of timeslots.
+For
+Tau-PCI/2E1 and Tau-PCI/4E1,
+.Ar N
+should be equal to 32 regardless of the number of
+used timeslots.
+.It Sm Cm unfram No = Bro Cm on , off Brc Sm
+(Tau32-PCI, Tau-PCI/2E1 and Tau-PCI/4E1 only.)
+Turn on/off unframed mode.
+.Bl -tag -width ".Cm unfram Ns = Ns Cm off"
+.It Cm unfram Ns = Ns Cm on
+Switch channel to the unframed G.703 mode.
+.It Cm unfram Ns = Ns Cm off
+Switch channel to the framed E1 (G.704) mode.
+.El
+.It Sm Cm scrambler No = Bro Cm on , off Brc Sm
+(Tau32-PCI, Tau-PCI/G.703, Tau-PCI/2E1, and
+Tau-PCI/4E1 in unframed mode only.)
+Turn on/off scrambling of the G.703 data.
+.It Sm Cm use16 No = Bro Cm on , off Brc Sm
+(Tau32-PCI and Tau-PCI E1 family only.)
+Turn on/off the usage of the 16th timeslot for data transmission.
+Normally, the 16th timeslot is used for signalling information
+(multiframing CAS).
+.It Sm Cm crc4 No = Bro Cm on , off Brc Sm
+(E1 only.)
+Turn on/off CRC4 superframe mode.
+.It Sm Cm syn No = Bro Cm int , rcv , rcv0 , rcv1 , rcv2 , rcv3 Brc Sm
+.Bl -tag -width ".Cm rcv3"
+.It Cm int
+Use an internal clock generator for G703 transmitter
+(clock master).
+.It Cm rcv
+Use the G703 receiver data clock as the transmit clock
+(clock slave).
+.It Cm rcv0 , rcv1 , rcv2 , rcv3
+Use the G703 receiver clock of the other channel
+(E1 models only).
+.El
+.It Cm dir Ns = Ns Ar number
+(Tau32-PCI, Tau-PCI/2E1 and Tau-PCI/4E1 only.)
+Bind a logical channel to a physical channel.
+Using this parameter it is possible, for example, to split
+physical E1 channel into several logical channels.
+.It Cm ts Ns = Ns Ar interval
+(E1 only.)
+Set up the list of timeslots for use by the channel.
+The timeslots are numbered from 1 to 31,
+and are separated by a comma or a minus sign,
+giving an interval.
+Example:
+.Dq Li ts=1-3,5,17 .
+.It Cm pass Ns = Ns Ar interval
+(Tau/E1 only.)
+Set up the list of timeslots, translated to the E1 subchannel in
+.Cm cfg Ns = Ns Cm B
+and
+.Cm cfg Ns = Ns Cm C
+configurations.
+.It Sm Cm debug No = Bro Cm 0 , 1 , 2 Brc Sm
+Turn on/off debug messages.
+.Bl -tag -width 2n
+.It Cm 0
+Turn debug messages off.
+.It Cm 1
+Turn debug messages on, equivalent to the
+.Cm debug
+option of the
+.Xr ifconfig 8
+utility.
+.It Cm 2
+High intensive debug messages, for developers only.
+.El
+.El
+.\"--------------------------------------------------------------
+.Sh EXAMPLES
+Set up channel 1 for use with the HDSL modem or any other
+synchronous leased-line modem, and PPP/HDLC protocol (for Sigma):
+.Bd -literal -offset indent
+sconfig cx1 ppp extclock
+ifconfig cx1 158.250.244.2 158.250.244.1 up
+.Ed
+.Pp
+Set up channel 0 of Tau/E1 for use with the Cisco protocol
+over the E1 link, with a single virtual connection.
+The DLCI number is detected automatically.
+Use timeslots 1-10:
+.Bd -literal -offset indent
+sconfig ct0 cisco ts=1-10
+ifconfig ct0 158.250.244.2 158.250.244.1 up
+.Ed
+.Pp
+Set up channel 0 for the synchronous null-modem link to the nearby computer,
+internal clock source, 256000 bits/sec, protocol Cisco/HDLC (for Tau):
+.Bd -literal -offset indent
+sconfig ct0 cisco 256000
+ifconfig ct0 200.1.1.1 200.1.1.2 up
+.Ed
+.Pp
+Set up channel 1 for the leased line link using the data-only
+null-modem cable (or modems like Zelax+ M115).
+Synchronous DPLL mode, 128000
+bits/sec, protocol PPP/HDLC, NRZI encoding (for Sigma):
+.Bd -literal -offset indent
+sconfig cx1 ppp 128000 nrzi=on dpll=on
+ifconfig cx1 158.250.244.2 158.250.244.1 up
+.Ed
+.\"--------------------------------------------------------------
+.Sh DIAGNOSTICS
+This section contains a description of abbreviations used by
+.Nm
+while displaying various statistics.
+For a description of options related to
+statistics, please see above.
+.\"--------------------------------------------------------------
+.Ss Statistics
+When running, the driver gathers statistics about the channels, which
+can be accessed using the
+.Nm
+utility,
+or through the
+.Xr ioctl 2
+call
+.Dv SERIAL_GETSTAT .
+.Pp
+.Bl -tag -width indent -compact
+.It Va Rintr
+Total number of receive interrupts.
+.It Va Tintr
+Total number of transmit interrupts.
+.It Va Mintr
+Total number of modem interrupts.
+.It Va Ibytes
+Total bytes received.
+.It Va Ipkts
+Total packets received (for HDLC mode).
+.It Va Ierrs
+Number of receive errors.
+.It Va Obytes
+Total bytes transmitted.
+.It Va Opkts
+Total packets transmitted (for HDLC mode).
+.It Va Oerrs
+Number of transmit errors.
+.El
+.\"--------------------------------------------------------------
+.Ss E1/G.703 Statistics
+For E1 and G.703 channels, the SNMP-compatible statistics data are gathered
+(see RFC 1406).
+It can be accessed using the
+.Nm
+utility,
+or through the
+.Xr ioctl 2
+call
+.Dv SERIAL_GETESTAT .
+.Bl -tag -width ".Va RCRC Pq Va rcrce"
+.It Va Unav Pq Va uas
+Unavailable seconds: receiving all ones, loss of carrier, or loss of
+signal.
+.It Va Degr Pq Va dm
+Degraded minutes: having error rate more than 10E-6, not counting unavailable
+and severely errored seconds.
+.It Va Bpv Pq Va bpv
+HDB3 bipolar violation errors.
+.It Va Fsyn Pq Va fse
+Frame synchronization errors (E1 only).
+.It Va CRC Pq Va crce
+CRC4 errors (E1).
+.It Va RCRC Pq Va rcrce
+Remote CRC4 errors: E-bit counter (E1).
+.It Va Err Pq Va es
+Errored seconds: any framing errors, or out of frame sync, or any slip events.
+.It Va Lerr Pq Va les
+Line errored seconds: any BPV.
+.It Va Sev Pq Va ses
+Severely errored seconds: 832 or more framing errors, or 2048 or more bipolar
+violations.
+.It Va Bur Pq Va bes
+Bursty errored seconds: more than 1 framing error, but not severely errored.
+.It Va Oof Pq Va oofs
+Severely errored framing seconds: out of frame sync.
+.It Va Slp Pq Va css
+Controlled slip seconds: any slip buffer overflow or underflow.
+.El
+.\"--------------------------------------------------------------
+.Ss E1/G.703 Status
+The
+.Nm
+utility also prints the E1/G.703 channel status.
+The status can have the
+following values (non-exclusive):
+.Pp
+.Bl -tag -width ".Li FARLOMF" -compact
+.It Li Ok
+The channel is in a valid state, synchronized.
+.It Li LOS
+Loss of sync.
+.It Li AIS
+Receiving unframed all ones (E1 only).
+.It Li LOF
+Loss of framing (E1 only).
+.It Li LOMF
+Loss of multiframing (E1 only).
+.It Li FARLOF
+Receiving remote alarm (E1 only).
+.It Li AIS16
+Receiving all ones in the timeslot 16 (E1 only).
+.It Li FARLOMF
+Receiving distant multiframe alarm (E1 only).
+.It Li TSTREQ
+Receiving test request code (G.703 only).
+.It Li TSTERR
+Test error (G.703 only).
+.El
+.\"--------------------------------------------------------------
+.Sh SEE ALSO
+.Xr stty 1 ,
+.Xr ioctl 2 ,
+.Xr sppp 4 ,
+.Xr ifconfig 8 ,
+.Xr route 8 ,
+.Xr spppcontrol 8
+.\"--------------------------------------------------------------
+.Sh HISTORY
+This utility is a replacement for the
+.Nm cxconfig
+and
+.Nm ctconfig
+utilities that were used in the past with
+.Fx
+drivers.
+Those two utilities and
+.Nm
+are not compatible,
+and therefore all scripts using them have to be rewritten.
+Moreover,
+.Tn Linux
+and
+.Fx
+versions of the
+.Nm
+utility are not fully compatible.
+.\"--------------------------------------------------------------
+.Sh AUTHORS
+.An Cronyx Engineering Aq Mt info@cronyx.ru
+.Pp
+.Pa http://www.cronyx.ru
+.\"--------------------------------------------------------------
+.Sh BUGS
+All software produced by Cronyx Engineering is thoroughly tested.
+But as created by a man, it can contain some bugs.
+If you have caught one, try to localize it and send an email with the
+description of the bug, and all operations that you have done.
+We will try to reproduce the error and fix it.
diff --git a/sbin/sconfig/sconfig.c b/sbin/sconfig/sconfig.c
new file mode 100644
index 0000000..09255a1
--- /dev/null
+++ b/sbin/sconfig/sconfig.c
@@ -0,0 +1,1214 @@
+/*
+ * Channel configuration utility for Cronyx serial adapters.
+ *
+ * Copyright (C) 1997-2002 Cronyx Engineering.
+ * Author: Serge Vakulenko, <vak@cronyx.ru>
+ *
+ * Copyright (C) 1999-2005 Cronyx Engineering.
+ * Author: Roman Kurakin, <rik@cronyx.ru>
+ *
+ * This software is distributed with NO WARRANTIES, not even the implied
+ * warranties for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * Authors grant any other persons or organisations permission to use
+ * or modify this software as long as this message is kept with the software,
+ * all derivative works or modified versions.
+ *
+ * Cronyx Id: sconfig.c,v 1.4.2.2 2005/11/09 13:01:35 rik Exp $
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <ctype.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <machine/cserial.h>
+
+#define MAXCHAN 128
+
+int vflag, eflag, sflag, mflag, cflag, fflag, iflag, aflag, xflag;
+int tflag, uflag;
+char mask[64];
+int adapter_type; /* 0-sigma, 1-tau, 2-taupci, 3-tau32 */
+char chan_name[16];
+
+static void
+usage (void)
+{
+ printf(
+"Serial Adapter Configuration Utility\n"
+"Copyright (C) 1998-2005 Cronyx Engineering.\n"
+"See also man sconfig (8)\n"
+"Usage:\n"
+"\tsconfig [-aimsxeftuc] [device [parameters ...]]\n"
+"\n"
+"Options:\n"
+"\t<no options>\t\t -- print channel options\n"
+"\t-a\t\t\t -- print all settings of the channel\n"
+"\t-i\t\t\t -- print network interface status\n"
+"\t-m\t\t\t -- print modem signal status\n"
+"\t-s\t\t\t -- print channel statistics\n"
+"\t-x\t\t\t -- print extended channel statistics\n"
+"\t-e\t\t\t -- print short E1/G703 statistics\n"
+"\t-f\t\t\t -- print full E1/G703 statistics\n"
+"\t-t\t\t\t -- print short E3/T3/STS-1 statistics\n"
+"\t-u\t\t\t -- print full E3/T3/STS-1 statistics\n"
+"\t-c\t\t\t -- clear statistics\n"
+"\nParameters:\n"
+"\t<number>\t\t -- baud rate, internal clock\n"
+"\textclock\t\t -- external clock (default)\n"
+"\nProtocol options:\n"
+"\tasync\t\t\t -- asynchronous protocol\n"
+#ifdef __linux__
+"\tsync\t\t\t -- synchronous protocol\n"
+#endif
+"\tcisco\t\t\t -- Cisco/HDLC protocol\n"
+"\tfr\t\t\t -- Frame Relay protocol\n"
+#ifdef __linux__
+"\t dlci<number>\t -- Add new DLCI\n"
+#endif
+"\tppp\t\t\t -- PPP protocol\n"
+#ifdef __linux__
+"\trbrg\t\t\t -- Remote bridge\n"
+"\traw\t\t\t -- raw HDLC protocol\n"
+"\tpacket\t\t\t -- packetized HDLC protocol\n"
+"\tidle\t\t\t -- no protocol\n"
+#else
+"\t keepalive={on,of}\t -- Enable/disable keepalive\n"
+#endif
+"\nInterface options:\n"
+"\tport={rs232,v35,rs449}\t -- port type (for old models of Sigma)\n"
+"\tcfg={A,B,C}\t\t -- adapter configuration\n"
+"\tloop={on,off}\t\t -- internal loopback\n"
+"\trloop={on,off}\t\t -- remote loopback\n"
+"\tdpll={on,off}\t\t -- DPLL mode\n"
+"\tnrzi={on,off}\t\t -- NRZI encoding\n"
+"\tinvclk={on,off}\t\t -- invert receive and transmit clock\n"
+"\tinvrclk={on,off}\t -- invert receive clock\n"
+"\tinvtclk={on,off}\t -- invert transmit clock\n"
+"\thigain={on,off}\t\t -- E1 high non linear input sensitivity \n\t\t\t\t (long line)\n"
+"\tmonitor={on,off}\t -- E1 high linear input sensitivity \n\t\t\t\t (interception mode)\n"
+"\tphony={on,off}\t\t -- E1 telepnony mode\n"
+"\tunfram={on,off}\t\t -- E1 unframed mode\n"
+"\tscrambler={on,off}\t -- G.703 scrambling mode\n"
+"\tuse16={on,off}\t\t -- E1 timeslot 16 usage\n"
+"\tcrc4={on,off}\t\t -- E1 CRC4 mode\n"
+#ifdef __linux__
+"\tami={on,off}\t\t -- E1 AMI or HDB3 line code\n"
+"\tmtu={size}\t\t -- set MTU in bytes\n"
+#endif
+"\tsyn={int,rcv,rcvX}\t -- G.703 transmit clock\n"
+"\tts=...\t\t\t -- E1 timeslots\n"
+"\tpass=...\t\t -- E1 subchannel timeslots\n"
+"\tdir=<num>\t\t -- connect channel to link<num>\n"
+/*"\trqken={size}\t\t -- set receive queue length in packets\n"*/
+/*"\tcablen={on,off}\t\t -- T3/STS-1 high transmitter output for long cable\n"*/
+"\tdebug={0,1,2}\t\t -- enable/disable debug messages\n"
+ );
+ exit (0);
+}
+
+static unsigned long
+scan_timeslots (char *s)
+{
+ char *e;
+ long v;
+ int i;
+ unsigned long ts, lastv;
+
+ ts = lastv = 0;
+ for (;;) {
+ v = strtol (s, &e, 10);
+ if (e == s)
+ break;
+ if (*e == '-') {
+ lastv = v;
+ s = e+1;
+ continue;
+ }
+ if (*e == ',')
+ ++e;
+
+ if (lastv)
+ for (i=lastv; i<v; ++i)
+ ts |= 1L << i;
+ ts |= 1L << v;
+
+ lastv = 0;
+ s = e;
+ }
+ return ts;
+}
+
+static int
+ppp_ok (void)
+{
+#ifdef __linux__
+ int s, p;
+ struct ifreq ifr;
+ char pttyname[32];
+ char *p1, *p2;
+ int i, j;
+ int ppp_disc = N_PPP;
+
+ /*
+ * Open a socket for doing the ioctl operations.
+ */
+ s = socket (AF_INET, SOCK_DGRAM, 0);
+ if (s < 0) {
+ fprintf (stderr, "Error opening socket.\n");
+ return 0;
+ }
+ strncpy (ifr.ifr_name, "ppp0", sizeof (ifr.ifr_name));
+ if (ioctl (s, SIOCGIFFLAGS, (caddr_t) &ifr) >= 0) {
+ /* Ok. */
+ close (s);
+ return 1;
+ }
+ close (s);
+
+ /* open pseudo-tty and try to set PPP discipline */
+ sprintf (pttyname, "/dev/ptyXX");
+ p1 = &pttyname[8];
+ p2 = &pttyname[9];
+ for (i=0; i<16; i++) {
+ struct stat stb;
+
+ *p1 = "pqrstuvwxyzabcde"[i];
+ *p2 = '0';
+ if (stat (pttyname, &stb) < 0)
+ continue;
+ for (j=0; j<16; j++) {
+ *p2 = "0123456789abcdef"[j];
+ p = open (pttyname, 2);
+ if (p > 0) {
+ if (ioctl (p, TIOCSETD, &ppp_disc) < 0) {
+ fprintf (stderr, "No PPP discipline in kernel.\n");
+ close (p);
+ return 0;
+ }
+ close (p);
+ return 1;
+ }
+ }
+ }
+ fprintf (stderr, "Cannot get pseudo-tty.\n");
+ return 0;
+#else
+ return 1;
+#endif
+}
+
+static char *
+format_timeslots (unsigned long s)
+{
+ static char buf [100];
+ char *p = buf;
+ int i;
+
+ for (i=1; i<32; ++i)
+ if ((s >> i) & 1) {
+ int prev = (i > 1) & (s >> (i-1));
+ int next = (i < 31) & (s >> (i+1));
+
+ if (prev) {
+ if (next)
+ continue;
+ *p++ = '-';
+ } else if (p > buf)
+ *p++ = ',';
+
+ if (i >= 10)
+ *p++ = '0' + i / 10;
+ *p++ = '0' + i % 10;
+ }
+ *p = 0;
+ return buf;
+}
+
+static void
+print_modems (int fd, int need_header)
+{
+ int status;
+
+ if (ioctl (fd, TIOCMGET, &status) < 0) {
+ perror ("getting modem status");
+ return;
+ }
+ if (need_header)
+ printf ("Channel\tLE\tDTR\tDSR\tRTS\tCTS\tCD\n");
+ printf ("%s\t%s\t%s\t%s\t%s\t%s\t%s\n", chan_name,
+ status & TIOCM_LE ? "On" : "-",
+ status & TIOCM_DTR ? "On" : "-",
+ status & TIOCM_DSR ? "On" : "-",
+ status & TIOCM_RTS ? "On" : "-",
+ status & TIOCM_CTS ? "On" : "-",
+ status & TIOCM_CD ? "On" : "-");
+}
+
+static void
+#ifdef __linux__
+print_ifconfig (int fd)
+#else
+print_ifconfig (int fd __unused)
+#endif
+{
+ char buf [64];
+#ifdef __linux__
+ char protocol [8];
+
+ if (ioctl (fd, SERIAL_GETPROTO, &protocol) >= 0 &&
+ strcmp (protocol, "fr") == 0)
+ sprintf (buf, "ifconfig %sd16 2>/dev/null", chan_name);
+ else
+#endif
+ sprintf (buf, "ifconfig %s 2>/dev/null", chan_name);
+ system (buf);
+}
+
+static void
+set_debug_ifconfig (int on)
+{
+ char buf [64];
+ sprintf (buf, "ifconfig %s %sdebug 2>/dev/null", chan_name,
+ on ? "" : "-");
+ system (buf);
+}
+
+static char *
+format_long (unsigned long val)
+{
+ static char s[32];
+ int l;
+ l = sprintf (s, "%lu", val);
+ if (l>7 && !sflag) {
+ s[3] = s[2];
+ s[2] = s[1];
+ s[1] = '.';
+ s[4] = 'e';
+ sprintf (s + 5, "%02d", l-1);
+ }
+ return s;
+}
+
+static void
+print_stats (int fd, int need_header)
+{
+ struct serial_statistics st;
+ unsigned long sarr [9];
+ int i;
+
+ if (ioctl (fd, SERIAL_GETSTAT, &st) < 0) {
+ perror ("getting statistics");
+ return;
+ }
+ if (need_header) {
+ if (sflag) {
+ printf (" ------------Receive----------- "
+ "------------Transmit----------\n");
+ printf ("Channel Interrupts Packets Errors "
+ "Interrupts Packets Errors\n");
+ }
+ else {
+ printf (" --------Receive--------------- "
+ "--------Transmit-------------- Modem\n");
+ printf ("Channel Intrs Bytes Packets Errors "
+ "Intrs Bytes Packets Errors Intrs\n");
+ }
+ }
+
+ sarr [0] = st.rintr;
+ sarr [1] = st.ibytes;
+ sarr [2] = st.ipkts;
+ sarr [3] = st.ierrs;
+ sarr [4] = st.tintr;
+ sarr [5] = st.obytes;
+ sarr [6] = st.opkts;
+ sarr [7] = st.oerrs;
+ sarr [8] = st.mintr;
+ printf ("%s", chan_name);
+ if (sflag) {
+ printf ("\t%-12lu%-12lu%-12lu%-12lu%-12lu%-12lu", sarr[0],
+ sarr[2], sarr[3], sarr[4], sarr[6], sarr[7]);
+ } else {
+ for (i = 0; i < 9; i++)
+ printf ("\t%s", format_long (sarr [i]));
+ printf ("\n");
+ }
+}
+
+static void
+clear_stats (int fd)
+{
+ if (ioctl (fd, SERIAL_CLRSTAT, 0) < 0) {
+ perror ("clearing statistics");
+ exit (-1);
+ }
+}
+
+static char *
+format_e1_status (unsigned long status)
+{
+ static char buf [80];
+
+ if (status == 0)
+ return "n/a";
+ if (status & E1_NOALARM)
+ return "Ok";
+ buf[0] = 0;
+ if (status & E1_LOS) strcat (buf, ",LOS");
+ if (status & E1_AIS) strcat (buf, ",AIS");
+ if (status & E1_LOF) strcat (buf, ",LOF");
+ if (status & E1_LOMF) strcat (buf, ",LOMF");
+ if (status & E1_CRC4E) strcat (buf, ",CRC4E");
+ if (status & E1_FARLOF) strcat (buf, ",FARLOF");
+ if (status & E1_AIS16) strcat (buf, ",AIS16");
+ if (status & E1_FARLOMF) strcat (buf, ",FARLOMF");
+/* if (status & E1_TSTREQ) strcat (buf, ",TSTREQ");*/
+/* if (status & E1_TSTERR) strcat (buf, ",TSTERR");*/
+ if (buf[0] == ',')
+ return buf+1;
+ return "Unknown";
+}
+
+static void
+print_frac (int leftalign, unsigned long numerator, unsigned long divider)
+{
+ int n;
+
+ if (numerator < 1 || divider < 1) {
+ printf (leftalign ? "/- " : " -");
+ return;
+ }
+ n = (int) (0.5 + 1000.0 * numerator / divider);
+ if (n < 1000) {
+ printf (leftalign ? "/.%-3d" : " .%03d", n);
+ return;
+ }
+ putchar (leftalign ? '/' : ' ');
+
+ if (n >= 1000000) n = (n+500) / 1000 * 1000;
+ else if (n >= 100000) n = (n+50) / 100 * 100;
+ else if (n >= 10000) n = (n+5) / 10 * 10;
+
+ switch (n) {
+ case 1000: printf (".999"); return;
+ case 10000: n = 9990; break;
+ case 100000: n = 99900; break;
+ case 1000000: n = 999000; break;
+ }
+ if (n < 10000) printf ("%d.%d", n/1000, n/10%100);
+ else if (n < 100000) printf ("%d.%d", n/1000, n/100%10);
+ else if (n < 1000000) printf ("%d.", n/1000);
+ else printf ("%d", n/1000);
+}
+
+static void
+print_e1_stats (int fd, int need_header)
+{
+ struct e1_statistics st;
+ int i, maxi;
+
+ if (need_header)
+ printf ("Chan\t Unav/Degr Bpv/Fsyn CRC/RCRC Err/Lerr Sev/Bur Oof/Slp Status\n");
+
+ if (ioctl (fd, SERIAL_GETESTAT, &st) < 0)
+ return;
+ printf ("%s\t", chan_name);
+
+ /* Unavailable seconds, degraded minutes */
+ print_frac (0, st.currnt.uas, st.cursec);
+ print_frac (1, 60 * st.currnt.dm, st.cursec);
+
+ /* Bipolar violations, frame sync errors */
+ print_frac (0, st.currnt.bpv, st.cursec);
+ print_frac (1, st.currnt.fse, st.cursec);
+
+ /* CRC errors, remote CRC errors (E-bit) */
+ print_frac (0, st.currnt.crce, st.cursec);
+ print_frac (1, st.currnt.rcrce, st.cursec);
+
+ /* Errored seconds, line errored seconds */
+ print_frac (0, st.currnt.es, st.cursec);
+ print_frac (1, st.currnt.les, st.cursec);
+
+ /* Severely errored seconds, bursty errored seconds */
+ print_frac (0, st.currnt.ses, st.cursec);
+ print_frac (1, st.currnt.bes, st.cursec);
+
+ /* Out of frame seconds, controlled slip seconds */
+ print_frac (0, st.currnt.oofs, st.cursec);
+ print_frac (1, st.currnt.css, st.cursec);
+
+ printf (" %s\n", format_e1_status (st.status));
+
+ if (fflag) {
+ /* Print total statistics. */
+ printf ("\t");
+ print_frac (0, st.total.uas, st.totsec);
+ print_frac (1, 60 * st.total.dm, st.totsec);
+
+ print_frac (0, st.total.bpv, st.totsec);
+ print_frac (1, st.total.fse, st.totsec);
+
+ print_frac (0, st.total.crce, st.totsec);
+ print_frac (1, st.total.rcrce, st.totsec);
+
+ print_frac (0, st.total.es, st.totsec);
+ print_frac (1, st.total.les, st.totsec);
+
+ print_frac (0, st.total.ses, st.totsec);
+ print_frac (1, st.total.bes, st.totsec);
+
+ print_frac (0, st.total.oofs, st.totsec);
+ print_frac (1, st.total.css, st.totsec);
+
+ printf (" -- Total\n");
+
+ /* Print 24-hour history. */
+ maxi = (st.totsec - st.cursec) / 900;
+ if (maxi > 48)
+ maxi = 48;
+ for (i=0; i<maxi; ++i) {
+ printf (" ");
+ print_frac (0, st.interval[i].uas, 15*60);
+ print_frac (1, 60 * st.interval[i].dm, 15*60);
+
+ print_frac (0, st.interval[i].bpv, 15*60);
+ print_frac (1, st.interval[i].fse, 15*60);
+
+ print_frac (0, st.interval[i].crce, 15*60);
+ print_frac (1, st.interval[i].rcrce, 15*60);
+
+ print_frac (0, st.interval[i].es, 15*60);
+ print_frac (1, st.interval[i].les, 15*60);
+
+ print_frac (0, st.interval[i].ses, 15*60);
+ print_frac (1, st.interval[i].bes, 15*60);
+
+ print_frac (0, st.interval[i].oofs, 15*60);
+ print_frac (1, st.interval[i].css, 15*60);
+
+ if (i < 3)
+ printf (" -- %dm\n", (i+1)*15);
+ else
+ printf (" -- %dh %dm\n", (i+1)/4, (i+1)%4*15);
+ }
+ }
+}
+
+static char *
+format_e3_status (unsigned long status)
+{
+ static char buf [80];
+
+ buf[0] = 0;
+ if (status & E3_LOS) strcat (buf, ",LOS");
+ if (status & E3_TXE) strcat (buf, ",XMIT");
+ if (buf[0] == ',')
+ return buf+1;
+ return "Ok";
+}
+
+static char *
+format_e3_cv (unsigned long cv, unsigned long baud, unsigned long atime)
+{
+ static char buf[80];
+
+ if (!cv || !baud || !atime)
+ sprintf (buf, " - ");
+ else
+ sprintf (buf, "%10lu (%.1e)", cv, (double)cv/baud/atime);
+ return buf;
+}
+
+static void
+print_e3_stats (int fd, int need_header)
+{
+ struct e3_statistics st;
+ int i, maxi;
+ long baud;
+
+ if (need_header)
+ printf ("Chan\t--Code Violations---\t\t\t\t\t ----Status----\n");
+
+ if (ioctl (fd, SERIAL_GETE3STAT, &st) < 0 ||
+ ioctl (fd, SERIAL_GETBAUD, &baud) < 0)
+ return;
+
+ if (!st.cursec)
+ st.cursec = 1;
+
+ printf ("%s\t%s\t\t\t\t\t", chan_name,
+ format_e3_cv (st.ccv, baud, st.cursec));
+
+ printf (" %s\n", format_e3_status (st.status));
+
+
+ if (uflag) {
+ /* Print total statistics. */
+ printf ("\t%s\t\t\t\t\t",
+ format_e3_cv (st.tcv, baud, st.totsec));
+ printf (" -- Total\n");
+
+ /* Print 24-hour history. */
+ maxi = (st.totsec - st.cursec) / 900;
+ if (maxi > 48)
+ maxi = 48;
+ for (i=0; i<maxi; ++i) {
+ printf ("\t%s\t\t\t\t\t",
+ format_e3_cv (st.icv[i], baud, 15*60));
+ if (i < 3)
+ printf (" -- %2dm\n", (i+1)*15);
+ else
+ printf (" -- %2dh %2dm\n", (i+1)/4, (i+1)%4*15);
+ }
+ }
+}
+
+static void
+print_chan (int fd)
+{
+ char protocol [8];
+ char cfg;
+ int loop, dpll, nrzi, invclk, clk, higain, phony, use16, crc4;
+ int level, keepalive, debug, port, invrclk, invtclk, unfram, monitor;
+ int cable, dir, scrambler, ami, mtu;
+ int cablen, rloop, rqlen;
+ long baud, timeslots, subchan;
+ int protocol_valid, baud_valid, loop_valid, use16_valid, crc4_valid;
+ int dpll_valid, nrzi_valid, invclk_valid, clk_valid, phony_valid;
+ int timeslots_valid, subchan_valid, higain_valid, level_valid;
+ int keepalive_valid, debug_valid, cfg_valid, port_valid;
+ int invrclk_valid, invtclk_valid, unfram_valid, monitor_valid;
+ int cable_valid, dir_valid, scrambler_valid, ami_valid, mtu_valid;
+ int cablen_valid, rloop_valid, rqlen_valid;
+
+ protocol_valid = ioctl (fd, SERIAL_GETPROTO, &protocol) >= 0;
+ cfg_valid = ioctl (fd, SERIAL_GETCFG, &cfg) >= 0;
+ baud_valid = ioctl (fd, SERIAL_GETBAUD, &baud) >= 0;
+ loop_valid = ioctl (fd, SERIAL_GETLOOP, &loop) >= 0;
+ dpll_valid = ioctl (fd, SERIAL_GETDPLL, &dpll) >= 0;
+ nrzi_valid = ioctl (fd, SERIAL_GETNRZI, &nrzi) >= 0;
+ invclk_valid = ioctl (fd, SERIAL_GETINVCLK, &invclk) >= 0;
+ invrclk_valid = ioctl (fd, SERIAL_GETINVRCLK, &invrclk) >= 0;
+ invtclk_valid = ioctl (fd, SERIAL_GETINVTCLK, &invtclk) >= 0;
+ clk_valid = ioctl (fd, SERIAL_GETCLK, &clk) >= 0;
+ timeslots_valid = ioctl (fd, SERIAL_GETTIMESLOTS, &timeslots) >= 0;
+ subchan_valid = ioctl (fd, SERIAL_GETSUBCHAN, &subchan) >= 0;
+ higain_valid = ioctl (fd, SERIAL_GETHIGAIN, &higain) >= 0;
+ phony_valid = ioctl (fd, SERIAL_GETPHONY, &phony) >= 0;
+ unfram_valid = ioctl (fd, SERIAL_GETUNFRAM, &unfram) >= 0;
+ monitor_valid = ioctl (fd, SERIAL_GETMONITOR, &monitor) >= 0;
+ use16_valid = ioctl (fd, SERIAL_GETUSE16, &use16) >= 0;
+ crc4_valid = ioctl (fd, SERIAL_GETCRC4, &crc4) >= 0;
+ ami_valid = ioctl (fd, SERIAL_GETLCODE, &ami) >= 0;
+ level_valid = ioctl (fd, SERIAL_GETLEVEL, &level) >= 0;
+ keepalive_valid = ioctl (fd, SERIAL_GETKEEPALIVE, &keepalive) >= 0;
+ debug_valid = ioctl (fd, SERIAL_GETDEBUG, &debug) >= 0;
+ port_valid = ioctl (fd, SERIAL_GETPORT, &port) >= 0;
+ cable_valid = ioctl (fd, SERIAL_GETCABLE, &cable) >= 0;
+ dir_valid = ioctl (fd, SERIAL_GETDIR, &dir) >= 0;
+ scrambler_valid = ioctl (fd, SERIAL_GETSCRAMBLER, &scrambler) >= 0;
+ cablen_valid = ioctl (fd, SERIAL_GETCABLEN, &cablen) >= 0;
+ rloop_valid = ioctl (fd, SERIAL_GETRLOOP, &rloop) >= 0;
+ mtu_valid = ioctl (fd, SERIAL_GETMTU, &mtu) >= 0;
+ rqlen_valid = ioctl (fd, SERIAL_GETRQLEN, &rqlen) >= 0;
+
+ printf ("%s", chan_name);
+ if (port_valid)
+ switch (port) {
+ case 0: printf (" (rs232)"); break;
+ case 1: printf (" (v35)"); break;
+ case 2: printf (" (rs530)"); break;
+ }
+ else if (cable_valid)
+ switch (cable) {
+ case 0: printf (" (rs232)"); break;
+ case 1: printf (" (v35)"); break;
+ case 2: printf (" (rs530)"); break;
+ case 3: printf (" (x21)"); break;
+ case 4: printf (" (rs485)"); break;
+ case 9: printf (" (no cable)"); break;
+ }
+ if (debug_valid && debug)
+ printf (" debug=%d", debug);
+ if (protocol_valid && *protocol)
+ printf (" %.8s", protocol);
+ else
+ printf (" idle");
+ if (cablen_valid)
+ printf (" cablen=%s", cablen ? "on" : "off");
+ if (keepalive_valid)
+ printf (" keepalive=%s", keepalive ? "on" : "off");
+
+ if (cfg_valid)
+ switch (cfg) {
+ case 'a' : printf (" cfg=A"); break;
+ case 'b' : printf (" cfg=B"); break;
+ case 'c' : printf (" cfg=C"); break;
+ case 'd' : printf (" cfg=D"); break;
+ default : printf (" cfg=unknown");
+ }
+ if (dir_valid)
+ printf (" dir=%d", dir);
+
+ if (baud_valid) {
+ if (baud)
+ printf (" %ld", baud);
+ else
+ printf (" extclock");
+ }
+ if (mtu_valid)
+ printf (" mtu=%d", mtu);
+
+ if (aflag && rqlen_valid)
+ printf (" rqlen=%d", rqlen);
+
+ if (clk_valid)
+ switch (clk) {
+ case E1CLK_INTERNAL: printf (" syn=int"); break;
+ case E1CLK_RECEIVE: printf (" syn=rcv"); break;
+ case E1CLK_RECEIVE_CHAN0: printf (" syn=rcv0"); break;
+ case E1CLK_RECEIVE_CHAN1: printf (" syn=rcv1"); break;
+ case E1CLK_RECEIVE_CHAN2: printf (" syn=rcv2"); break;
+ case E1CLK_RECEIVE_CHAN3: printf (" syn=rcv3"); break;
+ default: printf (" syn=%d", clk); break;
+ }
+
+ if (dpll_valid)
+ printf (" dpll=%s", dpll ? "on" : "off");
+ if (nrzi_valid)
+ printf (" nrzi=%s", nrzi ? "on" : "off");
+ if (invclk_valid)
+ printf (" invclk=%s", invclk ? "on" : "off");
+ if (invrclk_valid)
+ printf (" invrclk=%s", invrclk ? "on" : "off");
+ if (invtclk_valid)
+ printf (" invtclk=%s", invtclk ? "on" : "off");
+ if (unfram_valid)
+ printf (" unfram=%s", unfram ? "on" : "off");
+ if (use16_valid)
+ printf (" use16=%s", use16 ? "on" : "off");
+ if (aflag) {
+ if (crc4_valid)
+ printf (" crc4=%s", crc4 ? "on" : "off");
+ if (higain_valid)
+ printf (" higain=%s", higain ? "on" : "off");
+ if (monitor_valid)
+ printf (" monitor=%s", monitor ? "on" : "off");
+ if (phony_valid)
+ printf (" phony=%s", phony ? "on" : "off");
+ if (scrambler_valid)
+ printf (" scrambler=%s", scrambler ? "on" : "off");
+ if (loop_valid)
+ printf (" loop=%s", loop ? "on" : "off");
+ if (rloop_valid)
+ printf (" rloop=%s", rloop ? "on" : "off");
+ if (ami_valid)
+ printf (" ami=%s", ami ? "on" : "off");
+ }
+ if (timeslots_valid)
+ printf (" ts=%s", format_timeslots (timeslots));
+ if (subchan_valid)
+ printf (" pass=%s", format_timeslots (subchan));
+ if (level_valid)
+ printf (" (level=-%.1fdB)", level / 10.0);
+ printf ("\n");
+}
+
+static void
+setup_chan (int fd, int argc, char **argv)
+{
+ int i, mode, loop, nrzi, dpll, invclk, phony, use16, crc4, unfram, ami;
+ int higain, clk, keepalive, debug, port, dlci, invrclk, invtclk;
+ int monitor, dir, scrambler, rloop, cablen;
+ int mode_valid;
+ long baud, timeslots, mtu, rqlen;
+
+ for (i=0; i<argc; ++i) {
+ if (argv[i][0] >= '0' && argv[i][0] <= '9') {
+ baud = strtol (argv[i], 0, 10);
+ ioctl (fd, SERIAL_SETBAUD, &baud);
+ } else if (strcasecmp ("extclock", argv[i]) == 0) {
+ baud = 0;
+ ioctl (fd, SERIAL_SETBAUD, &baud);
+ } else if (strncasecmp ("cfg=", argv[i], 4) == 0) {
+ if (strncasecmp ("a", argv[i]+4, 1) == 0)
+ ioctl (fd, SERIAL_SETCFG, "a");
+ else if (strncasecmp ("b", argv[i]+4, 1) == 0)
+ ioctl (fd, SERIAL_SETCFG, "b");
+ else if (strncasecmp ("c", argv[i]+4, 1) == 0)
+ ioctl (fd, SERIAL_SETCFG, "c");
+ else if (strncasecmp ("d", argv[i]+4, 1) == 0)
+ ioctl (fd, SERIAL_SETCFG, "d");
+ else {
+ fprintf (stderr, "invalid cfg\n");
+ exit (-1);
+ }
+ } else if (strcasecmp ("idle", argv[i]) == 0)
+ ioctl (fd, SERIAL_SETPROTO, "\0\0\0\0\0\0\0");
+ else if (strcasecmp ("async", argv[i]) == 0) {
+ mode = SERIAL_ASYNC;
+ if (ioctl (fd, SERIAL_SETMODE, &mode) >= 0)
+ ioctl (fd, SERIAL_SETPROTO, "async\0\0");
+ } else if (strcasecmp ("sync", argv[i]) == 0) {
+ mode = SERIAL_HDLC;
+ if (ioctl (fd, SERIAL_SETMODE, &mode) >= 0)
+ ioctl (fd, SERIAL_SETPROTO, "sync\0\0\0");
+ } else if (strcasecmp ("cisco", argv[i]) == 0) {
+ mode = SERIAL_HDLC;
+ ioctl (fd, SERIAL_SETMODE, &mode);
+ ioctl (fd, SERIAL_SETPROTO, "cisco\0\0");
+ } else if (strcasecmp ("rbrg", argv[i]) == 0) {
+ mode = SERIAL_HDLC;
+ ioctl (fd, SERIAL_SETMODE, &mode);
+ ioctl (fd, SERIAL_SETPROTO, "rbrg\0\0\0");
+ } else if (strcasecmp ("raw", argv[i]) == 0) {
+ mode = SERIAL_HDLC;
+ ioctl (fd, SERIAL_SETMODE, &mode);
+ ioctl (fd, SERIAL_SETPROTO, "raw\0\0\0\0");
+ } else if (strcasecmp ("packet", argv[i]) == 0) {
+ mode = SERIAL_HDLC;
+ ioctl (fd, SERIAL_SETMODE, &mode);
+ ioctl (fd, SERIAL_SETPROTO, "packet\0");
+ } else if (strcasecmp ("ppp", argv[i]) == 0) {
+ /* check that ppp line discipline is present */
+ if (ppp_ok ()) {
+ mode = SERIAL_HDLC;
+ ioctl (fd, SERIAL_SETMODE, &mode);
+ ioctl (fd, SERIAL_SETPROTO, "ppp\0\0\0\0");
+ }
+ } else if (strncasecmp ("keepalive=", argv[i], 10) == 0) {
+ keepalive = (strcasecmp ("on", argv[i] + 10) == 0);
+ ioctl (fd, SERIAL_SETKEEPALIVE, &keepalive);
+ } else if (strcasecmp ("fr", argv[i]) == 0) {
+ mode = SERIAL_HDLC;
+ ioctl (fd, SERIAL_SETMODE, &mode);
+ ioctl (fd, SERIAL_SETPROTO, "fr\0\0\0\0\0");
+ } else if (strcasecmp ("zaptel", argv[i]) == 0) {
+ mode = SERIAL_HDLC;
+ ioctl (fd, SERIAL_SETMODE, &mode);
+ ioctl (fd, SERIAL_SETPROTO, "zaptel\0");
+ } else if (strncasecmp ("debug=", argv[i], 6) == 0) {
+ debug = strtol (argv[i]+6, 0, 10);
+ mode_valid = ioctl (fd, SERIAL_GETMODE, &mode) >= 0;
+ if (!mode_valid || mode != SERIAL_ASYNC) {
+ if (debug == 0) {
+ set_debug_ifconfig(0);
+ } else {
+ ioctl (fd, SERIAL_SETDEBUG, &debug);
+ set_debug_ifconfig(1);
+ }
+ } else {
+ ioctl (fd, SERIAL_SETDEBUG, &debug);
+ }
+ } else if (strncasecmp ("loop=", argv[i], 5) == 0) {
+ loop = (strcasecmp ("on", argv[i] + 5) == 0);
+ ioctl (fd, SERIAL_SETLOOP, &loop);
+ } else if (strncasecmp ("rloop=", argv[i], 6) == 0) {
+ rloop = (strcasecmp ("on", argv[i] + 6) == 0);
+ ioctl (fd, SERIAL_SETRLOOP, &rloop);
+ } else if (strncasecmp ("dpll=", argv[i], 5) == 0) {
+ dpll = (strcasecmp ("on", argv[i] + 5) == 0);
+ ioctl (fd, SERIAL_SETDPLL, &dpll);
+ } else if (strncasecmp ("nrzi=", argv[i], 5) == 0) {
+ nrzi = (strcasecmp ("on", argv[i] + 5) == 0);
+ ioctl (fd, SERIAL_SETNRZI, &nrzi);
+ } else if (strncasecmp ("invclk=", argv[i], 7) == 0) {
+ invclk = (strcasecmp ("on", argv[i] + 7) == 0);
+ ioctl (fd, SERIAL_SETINVCLK, &invclk);
+ } else if (strncasecmp ("invrclk=", argv[i], 8) == 0) {
+ invrclk = (strcasecmp ("on", argv[i] + 8) == 0);
+ ioctl (fd, SERIAL_SETINVRCLK, &invrclk);
+ } else if (strncasecmp ("invtclk=", argv[i], 8) == 0) {
+ invtclk = (strcasecmp ("on", argv[i] + 8) == 0);
+ ioctl (fd, SERIAL_SETINVTCLK, &invtclk);
+ } else if (strncasecmp ("higain=", argv[i], 7) == 0) {
+ higain = (strcasecmp ("on", argv[i] + 7) == 0);
+ ioctl (fd, SERIAL_SETHIGAIN, &higain);
+ } else if (strncasecmp ("phony=", argv[i], 6) == 0) {
+ phony = (strcasecmp ("on", argv[i] + 6) == 0);
+ ioctl (fd, SERIAL_SETPHONY, &phony);
+ } else if (strncasecmp ("unfram=", argv[i], 7) == 0) {
+ unfram = (strcasecmp ("on", argv[i] + 7) == 0);
+ ioctl (fd, SERIAL_SETUNFRAM, &unfram);
+ } else if (strncasecmp ("scrambler=", argv[i], 10) == 0) {
+ scrambler = (strcasecmp ("on", argv[i] + 10) == 0);
+ ioctl (fd, SERIAL_SETSCRAMBLER, &scrambler);
+ } else if (strncasecmp ("monitor=", argv[i], 8) == 0) {
+ monitor = (strcasecmp ("on", argv[i] + 8) == 0);
+ ioctl (fd, SERIAL_SETMONITOR, &monitor);
+ } else if (strncasecmp ("use16=", argv[i], 6) == 0) {
+ use16 = (strcasecmp ("on", argv[i] + 6) == 0);
+ ioctl (fd, SERIAL_SETUSE16, &use16);
+ } else if (strncasecmp ("crc4=", argv[i], 5) == 0) {
+ crc4 = (strcasecmp ("on", argv[i] + 5) == 0);
+ ioctl (fd, SERIAL_SETCRC4, &crc4);
+ } else if (strncasecmp ("ami=", argv[i], 4) == 0) {
+ ami = (strcasecmp ("on", argv[i] + 4) == 0);
+ ioctl (fd, SERIAL_SETLCODE, &ami);
+ } else if (strncasecmp ("mtu=", argv[i], 4) == 0) {
+ mtu = strtol (argv[i] + 4, 0, 10);
+ ioctl (fd, SERIAL_SETMTU, &mtu);
+ } else if (strncasecmp ("rqlen=", argv[i], 6) == 0) {
+ rqlen = strtol (argv[i] + 6, 0, 10);
+ ioctl (fd, SERIAL_SETRQLEN, &rqlen);
+ } else if (strcasecmp ("syn=int", argv[i]) == 0) {
+ clk = E1CLK_INTERNAL;
+ ioctl (fd, SERIAL_SETCLK, &clk);
+ } else if (strcasecmp ("syn=rcv", argv[i]) == 0) {
+ clk = E1CLK_RECEIVE;
+ ioctl (fd, SERIAL_SETCLK, &clk);
+ } else if (strcasecmp ("syn=rcv0", argv[i]) == 0) {
+ clk = E1CLK_RECEIVE_CHAN0;
+ ioctl (fd, SERIAL_SETCLK, &clk);
+ } else if (strcasecmp ("syn=rcv1", argv[i]) == 0) {
+ clk = E1CLK_RECEIVE_CHAN1;
+ ioctl (fd, SERIAL_SETCLK, &clk);
+ } else if (strcasecmp ("syn=rcv2", argv[i]) == 0) {
+ clk = E1CLK_RECEIVE_CHAN2;
+ ioctl (fd, SERIAL_SETCLK, &clk);
+ } else if (strcasecmp ("syn=rcv3", argv[i]) == 0) {
+ clk = E1CLK_RECEIVE_CHAN3;
+ ioctl (fd, SERIAL_SETCLK, &clk);
+ } else if (strncasecmp ("ts=", argv[i], 3) == 0) {
+ timeslots = scan_timeslots (argv[i] + 3);
+ ioctl (fd, SERIAL_SETTIMESLOTS, &timeslots);
+ } else if (strncasecmp ("pass=", argv[i], 5) == 0) {
+ timeslots = scan_timeslots (argv[i] + 5);
+ ioctl (fd, SERIAL_SETSUBCHAN, &timeslots);
+ } else if (strncasecmp ("dlci", argv[i], 4) == 0) {
+ dlci = strtol (argv[i]+4, 0, 10);
+ ioctl (fd, SERIAL_ADDDLCI, &dlci);
+ } else if (strncasecmp ("dir=", argv[i], 4) == 0) {
+ dir = strtol (argv[i]+4, 0, 10);
+ ioctl (fd, SERIAL_SETDIR, &dir);
+ } else if (strncasecmp ("port=", argv[i], 5) == 0) {
+ if (strncasecmp ("rs232", argv[i]+5, 5) == 0) {
+ port = 0;
+ ioctl (fd, SERIAL_SETPORT, &port);
+ } else if (strncasecmp ("v35", argv[i]+5, 3) == 0) {
+ port = 1;
+ ioctl (fd, SERIAL_SETPORT, &port);
+ } else if (strncasecmp ("rs449", argv[i]+5, 5) == 0) {
+ port = 2;
+ ioctl (fd, SERIAL_SETPORT, &port);
+ } else
+ fprintf (stderr, "invalid port type\n");
+ exit (-1);
+#if 1
+ } else if (strcasecmp ("reset", argv[i]) == 0) {
+ ioctl (fd, SERIAL_RESET, 0);
+ } else if (strcasecmp ("hwreset", argv[i]) == 0) {
+ ioctl (fd, SERIAL_HARDRESET, 0);
+#endif
+ } else if (strncasecmp ("cablen=", argv[i], 7) == 0) {
+ loop = (strcasecmp ("on", argv[i] + 7) == 0);
+ ioctl (fd, SERIAL_SETCABLEN, &cablen);
+ }
+ }
+}
+
+static void
+get_mask (void)
+{
+#ifdef __linux__
+ int fd;
+
+ fd = open ("/dev/serial/ctl0", 0);
+ if (fd < 0) {
+ perror ("/dev/serial/ctl0");
+ exit (-1);
+ }
+ if (ioctl (fd, SERIAL_GETREGISTERED, &mask) < 0) {
+ perror ("getting list of channels");
+ exit (-1);
+ }
+ close (fd);
+#else
+ int fd, fd1, fd2, fd3, i;
+ char buf [80];
+
+ for (i=0, fd=-1; i<12 && fd<0; i++) {
+ sprintf (buf, "/dev/cx%d", i*4);
+ fd = open (buf, 0);
+ }
+
+ for (i=0, fd1=-1; i<3 && fd1<0; i++) {
+ sprintf (buf, "/dev/ct%d", i*2);
+ fd1 = open (buf, 0);
+ }
+
+ for (i=0, fd2=-1; i<3 && fd2<0; i++) {
+ sprintf (buf, "/dev/cp%d", i*4);
+ fd2 = open (buf, 0);
+ }
+
+ /* Try only one */
+ for (i=0, fd3=-1; i<1 && fd3<0; i++) {
+ sprintf (buf, "/dev/ce%d", i*4);
+ fd3 = open (buf, 0);
+ }
+
+ if ((fd < 0) && (fd1 < 0) && (fd2 < 0) && (fd3 < 0)) {
+ fprintf (stderr, "No Cronyx adapters installed\n");
+ exit (-1);
+ }
+
+ if (fd >= 0) {
+ if (ioctl (fd, SERIAL_GETREGISTERED, &mask) < 0) {
+ perror ("getting list of channels");
+ exit (-1);
+ }
+ close (fd);
+ }
+
+ if (fd1 >= 0) {
+ if (ioctl (fd1, SERIAL_GETREGISTERED, (mask+16)) < 0) {
+ perror ("getting list of channels");
+ exit (-1);
+ }
+ close (fd1);
+ }
+
+ if (fd2 >= 0) {
+ if (ioctl (fd2, SERIAL_GETREGISTERED, (mask+32)) < 0) {
+ perror ("getting list of channels");
+ exit (-1);
+ }
+ close (fd2);
+ }
+
+ if (fd3 >= 0) {
+ if (ioctl (fd3, SERIAL_GETREGISTERED, (mask+48)) < 0) {
+ perror ("getting list of channels");
+ exit (-1);
+ }
+ close (fd3);
+ }
+#endif
+}
+
+static int
+open_chan_ctl (int num)
+{
+ char device [80];
+ int fd;
+
+#ifdef __linux__
+ sprintf (device, "/dev/serial/ctl%d", num);
+#else
+ switch (adapter_type) {
+ case 0:
+ sprintf (device, "/dev/cx%d", num);
+ break;
+ case 1:
+ sprintf (device, "/dev/ct%d", num);
+ break;
+ case 2:
+ sprintf (device, "/dev/cp%d", num);
+ break;
+ case 3:
+ sprintf (device, "/dev/ce%d", num);
+ break;
+ }
+#endif
+ fd = open (device, 0);
+ if (fd < 0) {
+ if (errno == ENODEV)
+ fprintf (stderr, "chan%d: not configured\n", num);
+ else
+ perror (device);
+ exit (-1);
+ }
+#ifdef __linux__
+ if (ioctl (fd, SERIAL_GETNAME, &chan_name) < 0)
+ sprintf (chan_name, "chan%d", num);
+#else
+ switch (adapter_type) {
+ case 0: sprintf (chan_name, "cx%d", num); break;
+ case 1: sprintf (chan_name, "ct%d", num); break;
+ case 2: sprintf (chan_name, "cp%d", num); break;
+ case 3: sprintf (chan_name, "ce%d", num); break;
+ }
+#endif
+ return fd;
+}
+
+int
+main (int argc, char **argv)
+{
+ char *p;
+ int fd, need_header, chan_num;
+
+ if (argc > 1 && strcmp(argv[1], "help") == 0)
+ usage();
+
+ for (;;) {
+ switch (getopt (argc, argv, "mseftucviax")) {
+ case -1:
+ break;
+ case 'a':
+ ++aflag;
+ continue;
+ case 'm':
+ ++mflag;
+ continue;
+ case 's':
+ ++sflag;
+ continue;
+ case 'e':
+ ++eflag;
+ continue;
+ case 'f':
+ ++eflag;
+ ++fflag;
+ continue;
+ case 't':
+ ++tflag;
+ continue;
+ case 'u':
+ ++tflag;
+ ++uflag;
+ continue;
+ case 'c':
+ ++cflag;
+ continue;
+ case 'v':
+ ++vflag;
+ continue;
+ case 'i':
+ ++iflag;
+ continue;
+ case 'x':
+ ++xflag;
+ continue;
+ default:
+ usage();
+ }
+ break;
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc <= 0) {
+ get_mask ();
+ need_header = 1;
+ adapter_type = 0;
+#ifndef __linux__
+ for (; adapter_type < 4; ++adapter_type)
+#endif
+ {
+ for (chan_num=0; chan_num<MAXCHAN; ++chan_num)
+ if (mask[adapter_type*16+chan_num/8] & 1 << (chan_num & 7)) {
+ fd = open_chan_ctl (chan_num);
+ if (vflag) {
+#ifdef __linux__
+ char buf[256];
+ if (ioctl (fd, SERIAL_GETVERSIONSTRING, &buf) >= 0) {
+ printf ("Version: %s\n", buf);
+ close (fd);
+ return (0);
+ }
+#endif
+ }
+ if (iflag) {
+ print_chan (fd);
+ print_ifconfig (fd);
+ } else if (sflag||xflag)
+ print_stats (fd, need_header);
+ else if (mflag)
+ print_modems (fd, need_header);
+ else if (eflag)
+ print_e1_stats (fd, need_header);
+ else if (tflag)
+ print_e3_stats (fd, need_header);
+ else if (cflag)
+ clear_stats (fd);
+ else
+ print_chan (fd);
+ close (fd);
+ need_header = 0;
+ }
+ }
+ return (0);
+ }
+
+ p = argv[0] + strlen (argv[0]);
+ while (p > argv[0] && p[-1] >= '0' && p[-1] <= '9')
+ --p;
+ chan_num = strtol (p, 0, 10);
+#ifndef __linux__
+ if (strncasecmp ("cx", argv[0], 2)==0)
+ adapter_type = 0;
+ else if (strncasecmp ("ct", argv[0], 2)==0)
+ adapter_type = 1;
+ else if (strncasecmp ("cp", argv[0], 2)==0)
+ adapter_type = 2;
+ else if (strncasecmp ("ce", argv[0], 2)==0)
+ adapter_type = 3;
+ else {
+ fprintf (stderr, "Wrong channel name\n");
+ exit (-1);
+ }
+#endif
+ argc--;
+ argv++;
+
+ fd = open_chan_ctl (chan_num);
+ if (vflag) {
+#ifdef __linux__
+ char buf[256];
+ if (ioctl (fd, SERIAL_GETVERSIONSTRING, &buf) >= 0)
+ printf ("Version: %s\n", buf);
+#endif
+ }
+ if (iflag) {
+ print_chan (fd);
+ print_ifconfig (fd);
+ close (fd);
+ return (0);
+ }
+ if (sflag||xflag) {
+ print_stats (fd, 1);
+ close (fd);
+ return (0);
+ }
+ if (mflag) {
+ print_modems (fd, 1);
+ close (fd);
+ return (0);
+ }
+ if (eflag) {
+ print_e1_stats (fd, 1);
+ close (fd);
+ return (0);
+ }
+ if (tflag) {
+ print_e3_stats (fd, 1);
+ close (fd);
+ return (0);
+ }
+ if (cflag) {
+ clear_stats (fd);
+ close (fd);
+ return (0);
+ }
+ if (argc > 0)
+ setup_chan (fd, argc, argv);
+ else
+ print_chan (fd);
+ close (fd);
+ return (0);
+}
diff --git a/sbin/setkey/Makefile b/sbin/setkey/Makefile
new file mode 100644
index 0000000..e974af5
--- /dev/null
+++ b/sbin/setkey/Makefile
@@ -0,0 +1,67 @@
+# Copyright (C) 1995, 1996, 1997, 1998, and 1999 WIDE Project.
+# 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 project 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 PROJECT 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 PROJECT 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.
+#
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+PROG= setkey
+MAN= setkey.8
+SRCS= setkey.c parse.y token.l
+WARNS?= 1
+
+CFLAGS+= -I${.CURDIR} -I${.CURDIR}/../../lib/libipsec
+YFLAGS= -d
+
+LIBADD= l y
+
+CLEANFILES= y.tab.c y.tab.h key_test.o keytest
+
+# libpfkey
+# ipsec_strerror.c is for avoiding shlib reference to non-exported function.
+.PATH: ${.CURDIR}/../../lib/libipsec ${.CURDIR}/../../sys/netipsec
+SRCS+= pfkey.c pfkey_dump.c key_debug.c ipsec_strerror.c
+CFLAGS+= -I${.CURDIR}/../../lib/libipsec -I${.CURDIR}/../../sys/netipsec
+
+SRCS+= y.tab.h
+y.tab.h: parse.y
+CFLAGS+= -DIPSEC_DEBUG -DYY_NO_UNPUT
+.if ${MK_INET6_SUPPORT} != "no"
+CFLAGS+= -DINET6
+.endif
+CFLAGS+= -I.
+LIBADD+= ipsec
+CLEANFILES+= scriptdump y.tab.h
+
+#SCRIPTS= scriptdump
+
+LOCALPREFIX= /usr/local
+
+scriptdump: scriptdump.pl
+ sed -e 's#@LOCALPREFIX@#${LOCALPREFIX}#' < $> > scriptdump
+
+.include <bsd.prog.mk>
diff --git a/sbin/setkey/parse.y b/sbin/setkey/parse.y
new file mode 100644
index 0000000..c551c35
--- /dev/null
+++ b/sbin/setkey/parse.y
@@ -0,0 +1,1268 @@
+/* $FreeBSD$ */
+/* $KAME: parse.y,v 1.83 2004/05/18 08:48:23 sakane Exp $ */
+
+/*
+ * Copyright (C) 1995, 1996, 1997, 1998, and 1999 WIDE Project.
+ * 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 project 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 PROJECT 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 PROJECT 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 <sys/types.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+
+#include <net/route.h>
+#include <netinet/in.h>
+#include <net/pfkeyv2.h>
+#include <netipsec/key_var.h>
+#include <netipsec/ipsec.h>
+#include <arpa/inet.h>
+
+#include <string.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <netdb.h>
+#include <ctype.h>
+#include <errno.h>
+
+#include "libpfkey.h"
+#include "vchar.h"
+
+#define ATOX(c) \
+ (isdigit(c) ? (c - '0') : (isupper(c) ? (c - 'A' + 10) : (c - 'a' + 10)))
+
+u_int32_t p_spi;
+u_int p_ext, p_alg_enc, p_alg_auth, p_replay, p_mode;
+u_int32_t p_reqid;
+u_int p_key_enc_len, p_key_auth_len;
+caddr_t p_key_enc, p_key_auth;
+time_t p_lt_hard, p_lt_soft;
+
+static int p_aiflags = 0, p_aifamily = PF_UNSPEC;
+
+static struct addrinfo *parse_addr(char *, char *);
+static int fix_portstr(vchar_t *, vchar_t *, vchar_t *);
+static int setvarbuf(char *, int *, struct sadb_ext *, int, caddr_t, int);
+void parse_init(void);
+void free_buffer(void);
+
+int setkeymsg0(struct sadb_msg *, unsigned int, unsigned int, size_t);
+static int setkeymsg_spdaddr(unsigned int, unsigned int, vchar_t *,
+ struct addrinfo *, int, struct addrinfo *, int);
+static int setkeymsg_addr(unsigned int, unsigned int,
+ struct addrinfo *, struct addrinfo *, int);
+static int setkeymsg_add(unsigned int, unsigned int,
+ struct addrinfo *, struct addrinfo *);
+extern int setkeymsg(char *, size_t *);
+extern int sendkeymsg(char *, size_t);
+
+extern int yylex(void);
+extern void yyfatal(const char *);
+extern void yyerror(const char *);
+%}
+
+%union {
+ int num;
+ unsigned long ulnum;
+ vchar_t val;
+ struct addrinfo *res;
+}
+
+%token EOT SLASH BLCL ELCL
+%token ADD GET DELETE DELETEALL FLUSH DUMP
+%token PR_ESP PR_AH PR_IPCOMP PR_TCP
+%token F_PROTOCOL F_AUTH F_ENC F_REPLAY F_COMP F_RAWCPI
+%token F_MODE MODE F_REQID
+%token F_EXT EXTENSION NOCYCLICSEQ
+%token ALG_AUTH ALG_AUTH_NOKEY
+%token ALG_ENC ALG_ENC_NOKEY ALG_ENC_DESDERIV ALG_ENC_DES32IV ALG_ENC_OLD
+%token ALG_COMP
+%token F_LIFETIME_HARD F_LIFETIME_SOFT
+%token DECSTRING QUOTEDSTRING HEXSTRING STRING ANY
+ /* SPD management */
+%token SPDADD SPDDELETE SPDDUMP SPDFLUSH
+%token F_POLICY PL_REQUESTS
+%token F_AIFLAGS
+%token TAGGED
+
+%type <num> prefix protocol_spec upper_spec
+%type <num> ALG_ENC ALG_ENC_DESDERIV ALG_ENC_DES32IV ALG_ENC_OLD ALG_ENC_NOKEY
+%type <num> ALG_AUTH ALG_AUTH_NOKEY
+%type <num> ALG_COMP
+%type <num> PR_ESP PR_AH PR_IPCOMP PR_TCP
+%type <num> EXTENSION MODE
+%type <ulnum> DECSTRING
+%type <val> PL_REQUESTS portstr key_string
+%type <val> policy_requests
+%type <val> QUOTEDSTRING HEXSTRING STRING
+%type <val> F_AIFLAGS
+%type <val> upper_misc_spec policy_spec
+%type <res> ipaddr
+
+%%
+commands
+ : /*NOTHING*/
+ | commands command
+ {
+ free_buffer();
+ parse_init();
+ }
+ ;
+
+command
+ : add_command
+ | get_command
+ | delete_command
+ | deleteall_command
+ | flush_command
+ | dump_command
+ | spdadd_command
+ | spddelete_command
+ | spddump_command
+ | spdflush_command
+ ;
+ /* commands concerned with management, there is in tail of this file. */
+
+ /* add command */
+add_command
+ : ADD ipaddropts ipaddr ipaddr protocol_spec spi extension_spec algorithm_spec EOT
+ {
+ int status;
+
+ status = setkeymsg_add(SADB_ADD, $5, $3, $4);
+ if (status < 0)
+ return -1;
+ }
+ ;
+
+ /* delete */
+delete_command
+ : DELETE ipaddropts ipaddr ipaddr protocol_spec spi extension_spec EOT
+ {
+ int status;
+
+ if ($3->ai_next || $4->ai_next) {
+ yyerror("multiple address specified");
+ return -1;
+ }
+ if (p_mode != IPSEC_MODE_ANY)
+ yyerror("WARNING: mode is obsolete");
+
+ status = setkeymsg_addr(SADB_DELETE, $5, $3, $4, 0);
+ if (status < 0)
+ return -1;
+ }
+ ;
+
+ /* deleteall command */
+deleteall_command
+ : DELETEALL ipaddropts ipaddr ipaddr protocol_spec EOT
+ {
+ int status;
+
+ status = setkeymsg_addr(SADB_DELETE, $5, $3, $4, 1);
+ if (status < 0)
+ return -1;
+ }
+ ;
+
+ /* get command */
+get_command
+ : GET ipaddropts ipaddr ipaddr protocol_spec spi extension_spec EOT
+ {
+ int status;
+
+ if (p_mode != IPSEC_MODE_ANY)
+ yyerror("WARNING: mode is obsolete");
+
+ status = setkeymsg_addr(SADB_GET, $5, $3, $4, 0);
+ if (status < 0)
+ return -1;
+ }
+ ;
+
+ /* flush */
+flush_command
+ : FLUSH protocol_spec EOT
+ {
+ struct sadb_msg msg;
+ setkeymsg0(&msg, SADB_FLUSH, $2, sizeof(msg));
+ sendkeymsg((char *)&msg, sizeof(msg));
+ }
+ ;
+
+ /* dump */
+dump_command
+ : DUMP protocol_spec EOT
+ {
+ struct sadb_msg msg;
+ setkeymsg0(&msg, SADB_DUMP, $2, sizeof(msg));
+ sendkeymsg((char *)&msg, sizeof(msg));
+ }
+ ;
+
+protocol_spec
+ : /*NOTHING*/
+ {
+ $$ = SADB_SATYPE_UNSPEC;
+ }
+ | PR_ESP
+ {
+ $$ = SADB_SATYPE_ESP;
+ if ($1 == 1)
+ p_ext |= SADB_X_EXT_OLD;
+ else
+ p_ext &= ~SADB_X_EXT_OLD;
+ }
+ | PR_AH
+ {
+ $$ = SADB_SATYPE_AH;
+ if ($1 == 1)
+ p_ext |= SADB_X_EXT_OLD;
+ else
+ p_ext &= ~SADB_X_EXT_OLD;
+ }
+ | PR_IPCOMP
+ {
+ $$ = SADB_X_SATYPE_IPCOMP;
+ }
+ | PR_TCP
+ {
+ $$ = SADB_X_SATYPE_TCPSIGNATURE;
+ }
+ ;
+
+spi
+ : DECSTRING { p_spi = $1; }
+ | HEXSTRING
+ {
+ char *ep;
+ unsigned long v;
+
+ ep = NULL;
+ v = strtoul($1.buf, &ep, 16);
+ if (!ep || *ep) {
+ yyerror("invalid SPI");
+ return -1;
+ }
+ if (v & ~0xffffffff) {
+ yyerror("SPI too big.");
+ return -1;
+ }
+
+ p_spi = v;
+ }
+ ;
+
+algorithm_spec
+ : esp_spec
+ | ah_spec
+ | ipcomp_spec
+ ;
+
+esp_spec
+ : F_ENC enc_alg F_AUTH auth_alg
+ | F_ENC enc_alg
+ ;
+
+ah_spec
+ : F_AUTH auth_alg
+ ;
+
+ipcomp_spec
+ : F_COMP ALG_COMP
+ {
+ if ($2 < 0) {
+ yyerror("unsupported algorithm");
+ return -1;
+ }
+ p_alg_enc = $2;
+ }
+ | F_COMP ALG_COMP F_RAWCPI
+ {
+ if ($2 < 0) {
+ yyerror("unsupported algorithm");
+ return -1;
+ }
+ p_alg_enc = $2;
+ p_ext |= SADB_X_EXT_RAWCPI;
+ }
+ ;
+
+enc_alg
+ : ALG_ENC_NOKEY {
+ if ($1 < 0) {
+ yyerror("unsupported algorithm");
+ return -1;
+ }
+ p_alg_enc = $1;
+
+ p_key_enc_len = 0;
+ p_key_enc = NULL;
+ if (ipsec_check_keylen(SADB_EXT_SUPPORTED_ENCRYPT,
+ p_alg_enc, PFKEY_UNUNIT64(p_key_enc_len)) < 0) {
+ yyerror(ipsec_strerror());
+ return -1;
+ }
+ }
+ | ALG_ENC key_string {
+ if ($1 < 0) {
+ yyerror("unsupported algorithm");
+ return -1;
+ }
+ p_alg_enc = $1;
+
+ p_key_enc_len = $2.len;
+ p_key_enc = $2.buf;
+ if (ipsec_check_keylen(SADB_EXT_SUPPORTED_ENCRYPT,
+ p_alg_enc, PFKEY_UNUNIT64(p_key_enc_len)) < 0) {
+ yyerror(ipsec_strerror());
+ return -1;
+ }
+ }
+ | ALG_ENC_OLD {
+ if ($1 < 0) {
+ yyerror("unsupported algorithm");
+ return -1;
+ }
+ yyerror("WARNING: obsolete algorithm");
+ p_alg_enc = $1;
+
+ p_key_enc_len = 0;
+ p_key_enc = NULL;
+ if (ipsec_check_keylen(SADB_EXT_SUPPORTED_ENCRYPT,
+ p_alg_enc, PFKEY_UNUNIT64(p_key_enc_len)) < 0) {
+ yyerror(ipsec_strerror());
+ return -1;
+ }
+ }
+ | ALG_ENC_DESDERIV key_string
+ {
+ if ($1 < 0) {
+ yyerror("unsupported algorithm");
+ return -1;
+ }
+ p_alg_enc = $1;
+ if (p_ext & SADB_X_EXT_OLD) {
+ yyerror("algorithm mismatched");
+ return -1;
+ }
+ p_ext |= SADB_X_EXT_DERIV;
+
+ p_key_enc_len = $2.len;
+ p_key_enc = $2.buf;
+ if (ipsec_check_keylen(SADB_EXT_SUPPORTED_ENCRYPT,
+ p_alg_enc, PFKEY_UNUNIT64(p_key_enc_len)) < 0) {
+ yyerror(ipsec_strerror());
+ return -1;
+ }
+ }
+ | ALG_ENC_DES32IV key_string
+ {
+ if ($1 < 0) {
+ yyerror("unsupported algorithm");
+ return -1;
+ }
+ p_alg_enc = $1;
+ if (!(p_ext & SADB_X_EXT_OLD)) {
+ yyerror("algorithm mismatched");
+ return -1;
+ }
+ p_ext |= SADB_X_EXT_IV4B;
+
+ p_key_enc_len = $2.len;
+ p_key_enc = $2.buf;
+ if (ipsec_check_keylen(SADB_EXT_SUPPORTED_ENCRYPT,
+ p_alg_enc, PFKEY_UNUNIT64(p_key_enc_len)) < 0) {
+ yyerror(ipsec_strerror());
+ return -1;
+ }
+ }
+ ;
+
+auth_alg
+ : ALG_AUTH key_string {
+ if ($1 < 0) {
+ yyerror("unsupported algorithm");
+ return -1;
+ }
+ p_alg_auth = $1;
+
+ p_key_auth_len = $2.len;
+ p_key_auth = $2.buf;
+
+ if (p_alg_auth == SADB_X_AALG_TCP_MD5) {
+ if ((p_key_auth_len < 1) || (p_key_auth_len >
+ 80))
+ return -1;
+ } else if (ipsec_check_keylen(SADB_EXT_SUPPORTED_AUTH,
+ p_alg_auth, PFKEY_UNUNIT64(p_key_auth_len)) < 0) {
+ yyerror(ipsec_strerror());
+ return -1;
+ }
+ }
+ | ALG_AUTH_NOKEY {
+ if ($1 < 0) {
+ yyerror("unsupported algorithm");
+ return -1;
+ }
+ p_alg_auth = $1;
+
+ p_key_auth_len = 0;
+ p_key_auth = NULL;
+ }
+ ;
+
+key_string
+ : QUOTEDSTRING
+ {
+ $$ = $1;
+ }
+ | HEXSTRING
+ {
+ caddr_t pp_key;
+ caddr_t bp;
+ caddr_t yp = $1.buf;
+ int l;
+
+ l = strlen(yp) % 2 + strlen(yp) / 2;
+ if ((pp_key = malloc(l)) == 0) {
+ yyerror("not enough core");
+ return -1;
+ }
+ memset(pp_key, 0, l);
+
+ bp = pp_key;
+ if (strlen(yp) % 2) {
+ *bp = ATOX(yp[0]);
+ yp++, bp++;
+ }
+ while (*yp) {
+ *bp = (ATOX(yp[0]) << 4) | ATOX(yp[1]);
+ yp += 2, bp++;
+ }
+
+ $$.len = l;
+ $$.buf = pp_key;
+ }
+ ;
+
+extension_spec
+ : /*NOTHING*/
+ | extension_spec extension
+ ;
+
+extension
+ : F_EXT EXTENSION { p_ext |= $2; }
+ | F_EXT NOCYCLICSEQ { p_ext &= ~SADB_X_EXT_CYCSEQ; }
+ | F_MODE MODE { p_mode = $2; }
+ | F_MODE ANY { p_mode = IPSEC_MODE_ANY; }
+ | F_REQID DECSTRING { p_reqid = $2; }
+ | F_REPLAY DECSTRING
+ {
+ if ((p_ext & SADB_X_EXT_OLD) != 0) {
+ yyerror("replay prevention cannot be used with "
+ "ah/esp-old");
+ return -1;
+ }
+ p_replay = $2;
+ }
+ | F_LIFETIME_HARD DECSTRING { p_lt_hard = $2; }
+ | F_LIFETIME_SOFT DECSTRING { p_lt_soft = $2; }
+ ;
+
+ /* definition about command for SPD management */
+ /* spdadd */
+spdadd_command
+ : SPDADD ipaddropts STRING prefix portstr STRING prefix portstr upper_spec upper_misc_spec policy_spec EOT
+ {
+ int status;
+ struct addrinfo *src, *dst;
+
+ /* fixed port fields if ulp is icmpv6 */
+ if ($10.buf != NULL) {
+ if ($9 != IPPROTO_ICMPV6)
+ return -1;
+ free($5.buf);
+ free($8.buf);
+ if (fix_portstr(&$10, &$5, &$8))
+ return -1;
+ }
+
+ src = parse_addr($3.buf, $5.buf);
+ dst = parse_addr($6.buf, $8.buf);
+ if (!src || !dst) {
+ /* yyerror is already called */
+ return -1;
+ }
+ if (src->ai_next || dst->ai_next) {
+ yyerror("multiple address specified");
+ freeaddrinfo(src);
+ freeaddrinfo(dst);
+ return -1;
+ }
+
+ status = setkeymsg_spdaddr(SADB_X_SPDADD, $9, &$11,
+ src, $4, dst, $7);
+ freeaddrinfo(src);
+ freeaddrinfo(dst);
+ if (status < 0)
+ return -1;
+ }
+ | SPDADD TAGGED QUOTEDSTRING policy_spec EOT
+ {
+ return -1;
+ }
+ ;
+
+spddelete_command
+ : SPDDELETE ipaddropts STRING prefix portstr STRING prefix portstr upper_spec upper_misc_spec policy_spec EOT
+ {
+ int status;
+ struct addrinfo *src, *dst;
+
+ /* fixed port fields if ulp is icmpv6 */
+ if ($10.buf != NULL) {
+ if ($9 != IPPROTO_ICMPV6)
+ return -1;
+ free($5.buf);
+ free($8.buf);
+ if (fix_portstr(&$10, &$5, &$8))
+ return -1;
+ }
+
+ src = parse_addr($3.buf, $5.buf);
+ dst = parse_addr($6.buf, $8.buf);
+ if (!src || !dst) {
+ /* yyerror is already called */
+ return -1;
+ }
+ if (src->ai_next || dst->ai_next) {
+ yyerror("multiple address specified");
+ freeaddrinfo(src);
+ freeaddrinfo(dst);
+ return -1;
+ }
+
+ status = setkeymsg_spdaddr(SADB_X_SPDDELETE, $9, &$11,
+ src, $4, dst, $7);
+ freeaddrinfo(src);
+ freeaddrinfo(dst);
+ if (status < 0)
+ return -1;
+ }
+ ;
+
+spddump_command:
+ SPDDUMP EOT
+ {
+ struct sadb_msg msg;
+ setkeymsg0(&msg, SADB_X_SPDDUMP, SADB_SATYPE_UNSPEC,
+ sizeof(msg));
+ sendkeymsg((char *)&msg, sizeof(msg));
+ }
+ ;
+
+spdflush_command:
+ SPDFLUSH EOT
+ {
+ struct sadb_msg msg;
+ setkeymsg0(&msg, SADB_X_SPDFLUSH, SADB_SATYPE_UNSPEC,
+ sizeof(msg));
+ sendkeymsg((char *)&msg, sizeof(msg));
+ }
+ ;
+
+ipaddropts
+ : /* nothing */
+ | ipaddropts ipaddropt
+ ;
+
+ipaddropt
+ : F_AIFLAGS
+ {
+ char *p;
+
+ for (p = $1.buf + 1; *p; p++)
+ switch (*p) {
+ case '4':
+ p_aifamily = AF_INET;
+ break;
+#ifdef INET6
+ case '6':
+ p_aifamily = AF_INET6;
+ break;
+#endif
+ case 'n':
+ p_aiflags = AI_NUMERICHOST;
+ break;
+ default:
+ yyerror("invalid flag");
+ return -1;
+ }
+ }
+ ;
+
+ipaddr
+ : STRING
+ {
+ $$ = parse_addr($1.buf, NULL);
+ if ($$ == NULL) {
+ /* yyerror already called by parse_addr */
+ return -1;
+ }
+ }
+ ;
+
+prefix
+ : /*NOTHING*/ { $$ = -1; }
+ | SLASH DECSTRING { $$ = $2; }
+ ;
+
+portstr
+ : /*NOTHING*/
+ {
+ $$.buf = strdup("0");
+ if (!$$.buf) {
+ yyerror("insufficient memory");
+ return -1;
+ }
+ $$.len = strlen($$.buf);
+ }
+ | BLCL ANY ELCL
+ {
+ $$.buf = strdup("0");
+ if (!$$.buf) {
+ yyerror("insufficient memory");
+ return -1;
+ }
+ $$.len = strlen($$.buf);
+ }
+ | BLCL DECSTRING ELCL
+ {
+ char buf[20];
+ snprintf(buf, sizeof(buf), "%lu", $2);
+ $$.buf = strdup(buf);
+ if (!$$.buf) {
+ yyerror("insufficient memory");
+ return -1;
+ }
+ $$.len = strlen($$.buf);
+ }
+ | BLCL STRING ELCL
+ {
+ $$ = $2;
+ }
+ ;
+
+upper_spec
+ : DECSTRING { $$ = $1; }
+ | ANY { $$ = IPSEC_ULPROTO_ANY; }
+ | PR_TCP { $$ = IPPROTO_TCP; }
+ | PR_ESP { $$ = IPPROTO_ESP; }
+ | STRING
+ {
+ struct protoent *ent;
+
+ ent = getprotobyname($1.buf);
+ if (ent)
+ $$ = ent->p_proto;
+ else {
+ if (strcmp("icmp6", $1.buf) == 0) {
+ $$ = IPPROTO_ICMPV6;
+ } else if(strcmp("ip4", $1.buf) == 0) {
+ $$ = IPPROTO_IPV4;
+ } else {
+ yyerror("invalid upper layer protocol");
+ return -1;
+ }
+ }
+ endprotoent();
+ }
+ ;
+
+upper_misc_spec
+ : /*NOTHING*/
+ {
+ $$.buf = NULL;
+ $$.len = 0;
+ }
+ | STRING
+ {
+ $$.buf = strdup($1.buf);
+ if (!$$.buf) {
+ yyerror("insufficient memory");
+ return -1;
+ }
+ $$.len = strlen($$.buf);
+ }
+ ;
+
+policy_spec
+ : F_POLICY policy_requests
+ {
+ char *policy;
+
+ policy = ipsec_set_policy($2.buf, $2.len);
+ if (policy == NULL) {
+ yyerror(ipsec_strerror());
+ return -1;
+ }
+
+ $$.buf = policy;
+ $$.len = ipsec_get_policylen(policy);
+ }
+ ;
+
+policy_requests
+ : PL_REQUESTS { $$ = $1; }
+ ;
+
+%%
+
+int
+setkeymsg0(msg, type, satype, l)
+ struct sadb_msg *msg;
+ unsigned int type;
+ unsigned int satype;
+ size_t l;
+{
+
+ msg->sadb_msg_version = PF_KEY_V2;
+ msg->sadb_msg_type = type;
+ msg->sadb_msg_errno = 0;
+ msg->sadb_msg_satype = satype;
+ msg->sadb_msg_reserved = 0;
+ msg->sadb_msg_seq = 0;
+ msg->sadb_msg_pid = getpid();
+ msg->sadb_msg_len = PFKEY_UNIT64(l);
+ return 0;
+}
+
+/* XXX NO BUFFER OVERRUN CHECK! BAD BAD! */
+static int
+setkeymsg_spdaddr(type, upper, policy, srcs, splen, dsts, dplen)
+ unsigned int type;
+ unsigned int upper;
+ vchar_t *policy;
+ struct addrinfo *srcs;
+ int splen;
+ struct addrinfo *dsts;
+ int dplen;
+{
+ struct sadb_msg *msg;
+ char buf[BUFSIZ];
+ int l, l0;
+ struct sadb_address m_addr;
+ struct addrinfo *s, *d;
+ int n;
+ int plen;
+ struct sockaddr *sa;
+ int salen;
+
+ msg = (struct sadb_msg *)buf;
+
+ if (!srcs || !dsts)
+ return -1;
+
+ /* fix up length afterwards */
+ setkeymsg0(msg, type, SADB_SATYPE_UNSPEC, 0);
+ l = sizeof(struct sadb_msg);
+
+ memcpy(buf + l, policy->buf, policy->len);
+ l += policy->len;
+
+ l0 = l;
+ n = 0;
+
+ /* do it for all src/dst pairs */
+ for (s = srcs; s; s = s->ai_next) {
+ for (d = dsts; d; d = d->ai_next) {
+ /* rewind pointer */
+ l = l0;
+
+ if (s->ai_addr->sa_family != d->ai_addr->sa_family)
+ continue;
+ switch (s->ai_addr->sa_family) {
+ case AF_INET:
+ plen = sizeof(struct in_addr) << 3;
+ break;
+#ifdef INET6
+ case AF_INET6:
+ plen = sizeof(struct in6_addr) << 3;
+ break;
+#endif
+ default:
+ continue;
+ }
+
+ /* set src */
+ sa = s->ai_addr;
+ salen = s->ai_addr->sa_len;
+ m_addr.sadb_address_len = PFKEY_UNIT64(sizeof(m_addr) +
+ PFKEY_ALIGN8(salen));
+ m_addr.sadb_address_exttype = SADB_EXT_ADDRESS_SRC;
+ m_addr.sadb_address_proto = upper;
+ m_addr.sadb_address_prefixlen =
+ (splen >= 0 ? splen : plen);
+ m_addr.sadb_address_reserved = 0;
+
+ setvarbuf(buf, &l, (struct sadb_ext *)&m_addr,
+ sizeof(m_addr), (caddr_t)sa, salen);
+
+ /* set dst */
+ sa = d->ai_addr;
+ salen = d->ai_addr->sa_len;
+ m_addr.sadb_address_len = PFKEY_UNIT64(sizeof(m_addr) +
+ PFKEY_ALIGN8(salen));
+ m_addr.sadb_address_exttype = SADB_EXT_ADDRESS_DST;
+ m_addr.sadb_address_proto = upper;
+ m_addr.sadb_address_prefixlen =
+ (dplen >= 0 ? dplen : plen);
+ m_addr.sadb_address_reserved = 0;
+
+ setvarbuf(buf, &l, (struct sadb_ext *)&m_addr,
+ sizeof(m_addr), (caddr_t)sa, salen);
+
+ msg->sadb_msg_len = PFKEY_UNIT64(l);
+
+ sendkeymsg(buf, l);
+
+ n++;
+ }
+ }
+
+ if (n == 0)
+ return -1;
+ else
+ return 0;
+}
+
+/* XXX NO BUFFER OVERRUN CHECK! BAD BAD! */
+static int
+setkeymsg_addr(type, satype, srcs, dsts, no_spi)
+ unsigned int type;
+ unsigned int satype;
+ struct addrinfo *srcs;
+ struct addrinfo *dsts;
+ int no_spi;
+{
+ struct sadb_msg *msg;
+ char buf[BUFSIZ];
+ int l, l0, len;
+ struct sadb_sa m_sa;
+ struct sadb_x_sa2 m_sa2;
+ struct sadb_address m_addr;
+ struct addrinfo *s, *d;
+ int n;
+ int plen;
+ struct sockaddr *sa;
+ int salen;
+
+ msg = (struct sadb_msg *)buf;
+
+ if (!srcs || !dsts)
+ return -1;
+
+ /* fix up length afterwards */
+ setkeymsg0(msg, type, satype, 0);
+ l = sizeof(struct sadb_msg);
+
+ if (!no_spi) {
+ len = sizeof(struct sadb_sa);
+ m_sa.sadb_sa_len = PFKEY_UNIT64(len);
+ m_sa.sadb_sa_exttype = SADB_EXT_SA;
+ m_sa.sadb_sa_spi = htonl(p_spi);
+ m_sa.sadb_sa_replay = p_replay;
+ m_sa.sadb_sa_state = 0;
+ m_sa.sadb_sa_auth = p_alg_auth;
+ m_sa.sadb_sa_encrypt = p_alg_enc;
+ m_sa.sadb_sa_flags = p_ext;
+
+ memcpy(buf + l, &m_sa, len);
+ l += len;
+
+ len = sizeof(struct sadb_x_sa2);
+ m_sa2.sadb_x_sa2_len = PFKEY_UNIT64(len);
+ m_sa2.sadb_x_sa2_exttype = SADB_X_EXT_SA2;
+ m_sa2.sadb_x_sa2_mode = p_mode;
+ m_sa2.sadb_x_sa2_reqid = p_reqid;
+
+ memcpy(buf + l, &m_sa2, len);
+ l += len;
+ }
+
+ l0 = l;
+ n = 0;
+
+ /* do it for all src/dst pairs */
+ for (s = srcs; s; s = s->ai_next) {
+ for (d = dsts; d; d = d->ai_next) {
+ /* rewind pointer */
+ l = l0;
+
+ if (s->ai_addr->sa_family != d->ai_addr->sa_family)
+ continue;
+ switch (s->ai_addr->sa_family) {
+ case AF_INET:
+ plen = sizeof(struct in_addr) << 3;
+ break;
+#ifdef INET6
+ case AF_INET6:
+ plen = sizeof(struct in6_addr) << 3;
+ break;
+#endif
+ default:
+ continue;
+ }
+
+ /* set src */
+ sa = s->ai_addr;
+ salen = s->ai_addr->sa_len;
+ m_addr.sadb_address_len = PFKEY_UNIT64(sizeof(m_addr) +
+ PFKEY_ALIGN8(salen));
+ m_addr.sadb_address_exttype = SADB_EXT_ADDRESS_SRC;
+ m_addr.sadb_address_proto = IPSEC_ULPROTO_ANY;
+ m_addr.sadb_address_prefixlen = plen;
+ m_addr.sadb_address_reserved = 0;
+
+ setvarbuf(buf, &l, (struct sadb_ext *)&m_addr,
+ sizeof(m_addr), (caddr_t)sa, salen);
+
+ /* set dst */
+ sa = d->ai_addr;
+ salen = d->ai_addr->sa_len;
+ m_addr.sadb_address_len = PFKEY_UNIT64(sizeof(m_addr) +
+ PFKEY_ALIGN8(salen));
+ m_addr.sadb_address_exttype = SADB_EXT_ADDRESS_DST;
+ m_addr.sadb_address_proto = IPSEC_ULPROTO_ANY;
+ m_addr.sadb_address_prefixlen = plen;
+ m_addr.sadb_address_reserved = 0;
+
+ setvarbuf(buf, &l, (struct sadb_ext *)&m_addr,
+ sizeof(m_addr), (caddr_t)sa, salen);
+
+ msg->sadb_msg_len = PFKEY_UNIT64(l);
+
+ sendkeymsg(buf, l);
+
+ n++;
+ }
+ }
+
+ if (n == 0)
+ return -1;
+ else
+ return 0;
+}
+
+/* XXX NO BUFFER OVERRUN CHECK! BAD BAD! */
+static int
+setkeymsg_add(type, satype, srcs, dsts)
+ unsigned int type;
+ unsigned int satype;
+ struct addrinfo *srcs;
+ struct addrinfo *dsts;
+{
+ struct sadb_msg *msg;
+ char buf[BUFSIZ];
+ int l, l0, len;
+ struct sadb_sa m_sa;
+ struct sadb_x_sa2 m_sa2;
+ struct sadb_address m_addr;
+ struct addrinfo *s, *d;
+ int n;
+ int plen;
+ struct sockaddr *sa;
+ int salen;
+
+ msg = (struct sadb_msg *)buf;
+
+ if (!srcs || !dsts)
+ return -1;
+
+ /* fix up length afterwards */
+ setkeymsg0(msg, type, satype, 0);
+ l = sizeof(struct sadb_msg);
+
+ /* set encryption algorithm, if present. */
+ if (satype != SADB_X_SATYPE_IPCOMP && p_key_enc) {
+ struct sadb_key m_key;
+
+ m_key.sadb_key_len =
+ PFKEY_UNIT64(sizeof(m_key)
+ + PFKEY_ALIGN8(p_key_enc_len));
+ m_key.sadb_key_exttype = SADB_EXT_KEY_ENCRYPT;
+ m_key.sadb_key_bits = p_key_enc_len * 8;
+ m_key.sadb_key_reserved = 0;
+
+ setvarbuf(buf, &l,
+ (struct sadb_ext *)&m_key, sizeof(m_key),
+ (caddr_t)p_key_enc, p_key_enc_len);
+ }
+
+ /* set authentication algorithm, if present. */
+ if (p_key_auth) {
+ struct sadb_key m_key;
+
+ m_key.sadb_key_len =
+ PFKEY_UNIT64(sizeof(m_key)
+ + PFKEY_ALIGN8(p_key_auth_len));
+ m_key.sadb_key_exttype = SADB_EXT_KEY_AUTH;
+ m_key.sadb_key_bits = p_key_auth_len * 8;
+ m_key.sadb_key_reserved = 0;
+
+ setvarbuf(buf, &l,
+ (struct sadb_ext *)&m_key, sizeof(m_key),
+ (caddr_t)p_key_auth, p_key_auth_len);
+ }
+
+ /* set lifetime for HARD */
+ if (p_lt_hard != 0) {
+ struct sadb_lifetime m_lt;
+ u_int slen = sizeof(struct sadb_lifetime);
+
+ m_lt.sadb_lifetime_len = PFKEY_UNIT64(slen);
+ m_lt.sadb_lifetime_exttype = SADB_EXT_LIFETIME_HARD;
+ m_lt.sadb_lifetime_allocations = 0;
+ m_lt.sadb_lifetime_bytes = 0;
+ m_lt.sadb_lifetime_addtime = p_lt_hard;
+ m_lt.sadb_lifetime_usetime = 0;
+
+ memcpy(buf + l, &m_lt, slen);
+ l += slen;
+ }
+
+ /* set lifetime for SOFT */
+ if (p_lt_soft != 0) {
+ struct sadb_lifetime m_lt;
+ u_int slen = sizeof(struct sadb_lifetime);
+
+ m_lt.sadb_lifetime_len = PFKEY_UNIT64(slen);
+ m_lt.sadb_lifetime_exttype = SADB_EXT_LIFETIME_SOFT;
+ m_lt.sadb_lifetime_allocations = 0;
+ m_lt.sadb_lifetime_bytes = 0;
+ m_lt.sadb_lifetime_addtime = p_lt_soft;
+ m_lt.sadb_lifetime_usetime = 0;
+
+ memcpy(buf + l, &m_lt, slen);
+ l += slen;
+ }
+
+ len = sizeof(struct sadb_sa);
+ m_sa.sadb_sa_len = PFKEY_UNIT64(len);
+ m_sa.sadb_sa_exttype = SADB_EXT_SA;
+ m_sa.sadb_sa_spi = htonl(p_spi);
+ m_sa.sadb_sa_replay = p_replay;
+ m_sa.sadb_sa_state = 0;
+ m_sa.sadb_sa_auth = p_alg_auth;
+ m_sa.sadb_sa_encrypt = p_alg_enc;
+ m_sa.sadb_sa_flags = p_ext;
+
+ memcpy(buf + l, &m_sa, len);
+ l += len;
+
+ len = sizeof(struct sadb_x_sa2);
+ m_sa2.sadb_x_sa2_len = PFKEY_UNIT64(len);
+ m_sa2.sadb_x_sa2_exttype = SADB_X_EXT_SA2;
+ m_sa2.sadb_x_sa2_mode = p_mode;
+ m_sa2.sadb_x_sa2_reqid = p_reqid;
+
+ memcpy(buf + l, &m_sa2, len);
+ l += len;
+
+ l0 = l;
+ n = 0;
+
+ /* do it for all src/dst pairs */
+ for (s = srcs; s; s = s->ai_next) {
+ for (d = dsts; d; d = d->ai_next) {
+ /* rewind pointer */
+ l = l0;
+
+ if (s->ai_addr->sa_family != d->ai_addr->sa_family)
+ continue;
+ switch (s->ai_addr->sa_family) {
+ case AF_INET:
+ plen = sizeof(struct in_addr) << 3;
+ break;
+#ifdef INET6
+ case AF_INET6:
+ plen = sizeof(struct in6_addr) << 3;
+ break;
+#endif
+ default:
+ continue;
+ }
+
+ /* set src */
+ sa = s->ai_addr;
+ salen = s->ai_addr->sa_len;
+ m_addr.sadb_address_len = PFKEY_UNIT64(sizeof(m_addr) +
+ PFKEY_ALIGN8(salen));
+ m_addr.sadb_address_exttype = SADB_EXT_ADDRESS_SRC;
+ m_addr.sadb_address_proto = IPSEC_ULPROTO_ANY;
+ m_addr.sadb_address_prefixlen = plen;
+ m_addr.sadb_address_reserved = 0;
+
+ setvarbuf(buf, &l, (struct sadb_ext *)&m_addr,
+ sizeof(m_addr), (caddr_t)sa, salen);
+
+ /* set dst */
+ sa = d->ai_addr;
+ salen = d->ai_addr->sa_len;
+ m_addr.sadb_address_len = PFKEY_UNIT64(sizeof(m_addr) +
+ PFKEY_ALIGN8(salen));
+ m_addr.sadb_address_exttype = SADB_EXT_ADDRESS_DST;
+ m_addr.sadb_address_proto = IPSEC_ULPROTO_ANY;
+ m_addr.sadb_address_prefixlen = plen;
+ m_addr.sadb_address_reserved = 0;
+
+ setvarbuf(buf, &l, (struct sadb_ext *)&m_addr,
+ sizeof(m_addr), (caddr_t)sa, salen);
+
+ msg->sadb_msg_len = PFKEY_UNIT64(l);
+
+ sendkeymsg(buf, l);
+
+ n++;
+ }
+ }
+
+ if (n == 0)
+ return -1;
+ else
+ return 0;
+}
+
+static struct addrinfo *
+parse_addr(host, port)
+ char *host;
+ char *port;
+{
+ struct addrinfo hints, *res = NULL;
+ int error;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = p_aifamily;
+ hints.ai_socktype = SOCK_DGRAM; /*dummy*/
+ hints.ai_protocol = IPPROTO_UDP; /*dummy*/
+ hints.ai_flags = p_aiflags;
+ error = getaddrinfo(host, port, &hints, &res);
+ if (error != 0) {
+ yyerror(gai_strerror(error));
+ return NULL;
+ }
+ return res;
+}
+
+static int
+fix_portstr(spec, sport, dport)
+ vchar_t *spec, *sport, *dport;
+{
+ char *p, *p2;
+ u_int l;
+
+ l = 0;
+ for (p = spec->buf; *p != ',' && *p != '\0' && l < spec->len; p++, l++)
+ ;
+ if (*p == '\0') {
+ p2 = "0";
+ } else {
+ if (*p == ',') {
+ *p = '\0';
+ p2 = ++p;
+ }
+ for (p = p2; *p != '\0' && l < spec->len; p++, l++)
+ ;
+ if (*p != '\0' || *p2 == '\0') {
+ yyerror("invalid an upper layer protocol spec");
+ return -1;
+ }
+ }
+
+ sport->buf = strdup(spec->buf);
+ if (!sport->buf) {
+ yyerror("insufficient memory");
+ return -1;
+ }
+ sport->len = strlen(sport->buf);
+ dport->buf = strdup(p2);
+ if (!dport->buf) {
+ yyerror("insufficient memory");
+ return -1;
+ }
+ dport->len = strlen(dport->buf);
+
+ return 0;
+}
+
+static int
+setvarbuf(buf, off, ebuf, elen, vbuf, vlen)
+ char *buf;
+ int *off;
+ struct sadb_ext *ebuf;
+ int elen;
+ caddr_t vbuf;
+ int vlen;
+{
+ memset(buf + *off, 0, PFKEY_UNUNIT64(ebuf->sadb_ext_len));
+ memcpy(buf + *off, (caddr_t)ebuf, elen);
+ memcpy(buf + *off + elen, vbuf, vlen);
+ (*off) += PFKEY_ALIGN8(elen + vlen);
+
+ return 0;
+}
+
+void
+parse_init()
+{
+ p_spi = 0;
+
+ p_ext = SADB_X_EXT_CYCSEQ;
+ p_alg_enc = SADB_EALG_NONE;
+ p_alg_auth = SADB_AALG_NONE;
+ p_mode = IPSEC_MODE_ANY;
+ p_reqid = 0;
+ p_replay = 0;
+ p_key_enc_len = p_key_auth_len = 0;
+ p_key_enc = p_key_auth = 0;
+ p_lt_hard = p_lt_soft = 0;
+
+ p_aiflags = 0;
+ p_aifamily = PF_UNSPEC;
+
+ return;
+}
+
+void
+free_buffer()
+{
+ /* we got tons of memory leaks in the parser anyways, leave them */
+
+ return;
+}
diff --git a/sbin/setkey/sample.cf b/sbin/setkey/sample.cf
new file mode 100644
index 0000000..c534fa1
--- /dev/null
+++ b/sbin/setkey/sample.cf
@@ -0,0 +1,219 @@
+# Copyright (C) 1995, 1996, 1997, 1998, and 1999 WIDE Project.
+# 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 project 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 PROJECT 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 PROJECT 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.
+#
+# $FreeBSD$
+
+# There are sample scripts for IPsec configuration by manual keying.
+# A security association is uniquely identified by a triple consisting
+# of a Security Parameter Index (SPI), an IP Destination Address, and a
+# security protocol (AH or ESP) identifier. You must take care of these
+# parameters when you configure by manual keying.
+
+# ESP transport mode is recommended for TCP port number 110 between
+# Host-A and Host-B. Encryption algorithm is blowfish-cbc whose key
+# is "kamekame", and authentication algorithm is hmac-sha1 whose key
+# is "this is the test key".
+#
+# ============ ESP ============
+# | |
+# Host-A Host-B
+# fec0::10 -------------------- fec0::11
+#
+# At Host-A and Host-B,
+spdadd fec0::10[any] fec0::11[110] tcp -P out ipsec
+ esp/transport//use ;
+spdadd fec0::11[110] fec0::10[any] tcp -P in ipsec
+ esp/transport//use ;
+add fec0::10 fec0::11 esp 0x10001
+ -m transport
+ -E blowfish-cbc "kamekame"
+ -A hmac-sha1 "this is the test key" ;
+add fec0::11 fec0::10 esp 0x10002
+ -m transport
+ -E blowfish-cbc "kamekame"
+ -A hmac-sha1 "this is the test key" ;
+
+# "[any]" is wildcard of port number. Note that "[0]" is the number of
+# zero in port number.
+
+# Security protocol is old AH tunnel mode, i.e. RFC1826, with keyed-md5
+# whose key is "this is the test" as authentication algorithm.
+# That protocol takes place between Gateway-A and Gateway-B.
+#
+# ======= AH =======
+# | |
+# Network-A Gateway-A Gateway-B Network-B
+# 10.0.1.0/24 ---- 172.16.0.1 ----- 172.16.0.2 ---- 10.0.2.0/24
+#
+# At Gateway-A:
+spdadd 10.0.1.0/24 10.0.2.0/24 any -P out ipsec
+ ah/tunnel/172.16.0.1-172.16.0.2/require ;
+spdadd 10.0.2.0/24 10.0.1.0/24 any -P in ipsec
+ ah/tunnel/172.16.0.2-172.16.0.1/require ;
+add 172.16.0.1 172.16.0.2 ah-old 0x10003
+ -m any
+ -A keyed-md5 "this is the test" ;
+add 172.16.0.2 172.16.0.1 ah-old 0x10004
+ -m any
+ -A keyed-md5 "this is the test" ;
+
+# If port number field is omitted such above then "[any]" is employed.
+# -m specifies the mode of SA to be used. "-m any" means wildcard of
+# mode of security protocol. You can use this SAs for both tunnel and
+# transport mode.
+
+# At Gateway-B. Attention to the selector and peer's IP address for tunnel.
+spdadd 10.0.2.0/24 10.0.1.0/24 any -P out ipsec
+ ah/tunnel/172.16.0.2-172.16.0.1/require ;
+spdadd 10.0.1.0/24 10.0.2.0/24 any -P in ipsec
+ ah/tunnel/172.16.0.1-172.16.0.2/require ;
+add 172.16.0.1 172.16.0.2 ah-old 0x10003
+ -m tunnel
+ -A keyed-md5 "this is the test" ;
+add 172.16.0.2 172.16.0.1 ah-old 0x10004
+ -m tunnel
+ -A keyed-md5 "this is the test" ;
+
+# AH transport mode followed by ESP tunnel mode is required between
+# Gateway-A and Gateway-B.
+# Encryption algorithm is 3des-cbc, and authentication algorithm for ESP
+# is hmac-sha1. Authentication algorithm for AH is hmac-md5.
+#
+# ========== AH =========
+# | ======= ESP ===== |
+# | | | |
+# Network-A Gateway-A Gateway-B Network-B
+# fec0:0:0:1::/64 --- fec0:0:0:1::1 ---- fec0:0:0:2::1 --- fec0:0:0:2::/64
+#
+# At Gateway-A:
+spdadd fec0:0:0:1::/64 fec0:0:0:2::/64 any -P out ipsec
+ esp/tunnel/fec0:0:0:1::1-fec0:0:0:2::1/require
+ ah/transport//require ;
+spdadd fec0:0:0:2::/64 fec0:0:0:1::/64 any -P in ipsec
+ esp/tunnel/fec0:0:0:2::1-fec0:0:0:1::1/require
+ ah/transport//require ;
+add fec0:0:0:1::1 fec0:0:0:2::1 esp 0x10001
+ -m tunnel
+ -E 3des-cbc "kamekame12341234kame1234"
+ -A hmac-sha1 "this is the test key" ;
+add fec0:0:0:1::1 fec0:0:0:2::1 ah 0x10001
+ -m transport
+ -A hmac-md5 "this is the test" ;
+add fec0:0:0:2::1 fec0:0:0:1::1 esp 0x10001
+ -m tunnel
+ -E 3des-cbc "kamekame12341234kame1234"
+ -A hmac-sha1 "this is the test key" ;
+add fec0:0:0:2::1 fec0:0:0:1::1 ah 0x10001
+ -m transport
+ -A hmac-md5 "this is the test" ;
+
+# ESP tunnel mode is required between Host-A and Gateway-A.
+# Encryption algorithm is cast128-cbc, and authentication algorithm
+# for ESP is hmac-sha1.
+# ESP transport mode is recommended between Host-A and Host-B.
+# Encryption algorithm is rc5-cbc, and authentication algorithm
+# for ESP is hmac-md5.
+#
+# ================== ESP =================
+# | ======= ESP ======= |
+# | | | |
+# Host-A Gateway-A Host-B
+# fec0:0:0:1::1 ---- fec0:0:0:2::1 ---- fec0:0:0:2::2
+#
+# At Host-A:
+spdadd fec0:0:0:1::1[any] fec0:0:0:2::2[80] tcp -P out ipsec
+ esp/transport//use
+ esp/tunnel/fec0:0:0:1::1-fec0:0:0:2::1/require ;
+spdadd fec0:0:0:2::1[80] fec0:0:0:1::1[any] tcp -P in ipsec
+ esp/transport//use
+ esp/tunnel/fec0:0:0:2::1-fec0:0:0:1::1/require ;
+add fec0:0:0:1::1 fec0:0:0:2::2 esp 0x10001
+ -m transport
+ -E cast128-cbc "12341234"
+ -A hmac-sha1 "this is the test key" ;
+add fec0:0:0:1::1 fec0:0:0:2::1 esp 0x10002
+ -E rc5-cbc "kamekame"
+ -A hmac-md5 "this is the test" ;
+add fec0:0:0:2::2 fec0:0:0:1::1 esp 0x10003
+ -m transport
+ -E cast128-cbc "12341234"
+ -A hmac-sha1 "this is the test key" ;
+add fec0:0:0:2::1 fec0:0:0:1::1 esp 0x10004
+ -E rc5-cbc "kamekame"
+ -A hmac-md5 "this is the test" ;
+
+# By "get" command, you can get a entry of either SP or SA.
+get fec0:0:0:1::1 fec0:0:0:2::2 ah 0x10004 ;
+
+# Also delete command, you can delete a entry of either SP or SA.
+spddelete fec0:0:0:1::/64 fec0:0:0:2::/64 any -P out;
+delete fec0:0:0:1::1 fec0:0:0:2::2 ah 0x10004 ;
+
+# By dump command, you can dump all entry of either SP or SA.
+dump ;
+spddump ;
+dump esp ;
+flush esp ;
+
+# By flush command, you can flush all entry of either SP or SA.
+flush ;
+spdflush ;
+
+# "flush" and "dump" commands can specify a security protocol.
+dump esp ;
+flush ah ;
+
+# XXX
+add ::1 ::1 esp 10001 -m transport -E null ;
+add ::1 ::1 esp 10002 -m transport -E des-deriv "12341234" ;
+add ::1 ::1 esp-old 10003 -m transport -E des-32iv "12341234" ;
+add ::1 ::1 esp 10004 -m transport -E null -A null ;
+add ::1 ::1 esp 10005 -m transport -E null -A hmac-md5 "1234123412341234" ;
+add ::1 ::1 esp 10006 -m tunnel -E null -A hmac-sha1 "12341234123412341234" ;
+add ::1 ::1 esp 10007 -m transport -E null -A keyed-md5 "1234123412341234" ;
+add ::1 ::1 esp 10008 -m any -E null -A keyed-sha1 "12341234123412341234" ;
+add ::1 ::1 esp 10009 -m transport -E des-cbc "testtest" ;
+add ::1 ::1 esp 10010 -m transport -E 3des-cbc "testtest12341234testtest" ;
+add ::1 ::1 esp 10011 -m tunnel -E cast128-cbc "testtest1234" ;
+add ::1 ::1 esp 10012 -m tunnel -E blowfish-cbc "testtest1234" ;
+add ::1 ::1 esp 10013 -m tunnel -E rc5-cbc "testtest1234" ;
+add ::1 ::1 esp 10014 -m any -E rc5-cbc "testtest1234" ;
+add ::1 ::1 esp 10015 -m transport -f zero-pad -E null ;
+add ::1 ::1 esp 10016 -m tunnel -f random-pad -r 8 -lh 100 -ls 80 -E null ;
+add ::1 ::1 esp 10017 -m transport -f seq-pad -f nocyclic-seq -E null ;
+add ::1 ::1 esp 10018 -m transport -E null ;
+#add ::1 ::1 ah 20000 -m transport -A null ;
+add ::1 ::1 ah 20001 -m any -A hmac-md5 "1234123412341234";
+add ::1 ::1 ah 20002 -m tunnel -A hmac-sha1 "12341234123412341234";
+add ::1 ::1 ah 20003 -m transport -A keyed-md5 "1234123412341234";
+add ::1 ::1 ah-old 20004 -m transport -A keyed-md5 "1234123412341234";
+add ::1 ::1 ah 20005 -m transport -A keyed-sha1 "12341234123412341234";
+#add ::1 ::1 ipcomp 30000 -C oui ;
+add ::1 ::1 ipcomp 30001 -C deflate ;
+#add ::1 ::1 ipcomp 30002 -C lzs ;
+
+# enjoy.
diff --git a/sbin/setkey/scriptdump.pl b/sbin/setkey/scriptdump.pl
new file mode 100644
index 0000000..a1d8adb
--- /dev/null
+++ b/sbin/setkey/scriptdump.pl
@@ -0,0 +1,56 @@
+#! @LOCALPREFIX@/bin/perl
+# $FreeBSD$
+
+if ($< != 0) {
+ print STDERR "must be root to invoke this\n";
+ exit 1;
+}
+
+$mode = 'add';
+while ($i = shift @ARGV) {
+ if ($i eq '-d') {
+ $mode = 'delete';
+ } else {
+ print STDERR "usage: scriptdump [-d]\n";
+ exit 1;
+ }
+}
+
+open(IN, "setkey -D |") || die;
+foreach $_ (<IN>) {
+ if (/^[^\t]/) {
+ ($src, $dst) = split(/\s+/, $_);
+ } elsif (/^\t(esp|ah) mode=(\S+) spi=(\d+).*reqid=(\d+)/) {
+ ($proto, $ipsecmode, $spi, $reqid) = ($1, $2, $3, $4);
+ } elsif (/^\tE: (\S+) (.*)/) {
+ $ealgo = $1;
+ $ekey = $2;
+ $ekey =~ s/\s//g;
+ $ekey =~ s/^/0x/g;
+ } elsif (/^\tA: (\S+) (.*)/) {
+ $aalgo = $1;
+ $akey = $2;
+ $akey =~ s/\s//g;
+ $akey =~ s/^/0x/g;
+ } elsif (/^\tseq=(0x\d+) replay=(\d+) flags=(0x\d+) state=/) {
+ print "$mode $src $dst $proto $spi";
+ $replay = $2;
+ print " -u $reqid" if $reqid;
+ if ($mode eq 'add') {
+ print " -m $ipsecmode -r $replay" if $replay;
+ if ($proto eq 'esp') {
+ print " -E $ealgo $ekey" if $ealgo;
+ print " -A $aalgo $akey" if $aalgo;
+ } elsif ($proto eq 'ah') {
+ print " -A $aalgo $akey" if $aalgo;
+ }
+ }
+ print ";\n";
+
+ $src = $dst = $upper = $proxy = '';
+ $ealgo = $ekey = $aalgo = $akey = '';
+ }
+}
+close(IN);
+
+exit 0;
diff --git a/sbin/setkey/setkey.8 b/sbin/setkey/setkey.8
new file mode 100644
index 0000000..4306ec2
--- /dev/null
+++ b/sbin/setkey/setkey.8
@@ -0,0 +1,729 @@
+.\" $KAME: setkey.8,v 1.89 2003/09/07 22:17:41 itojun Exp $
+.\"
+.\" Copyright (C) 1995, 1996, 1997, 1998, and 1999 WIDE Project.
+.\" 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 project 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 PROJECT 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 PROJECT 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd July 25, 2014
+.Dt SETKEY 8
+.Os
+.\"
+.Sh NAME
+.Nm setkey
+.Nd "manually manipulate the IPsec SA/SP database"
+.\"
+.Sh SYNOPSIS
+.Nm
+.Op Fl v
+.Fl c
+.Nm
+.Op Fl v
+.Fl f Ar filename
+.Nm
+.Op Fl aPlv
+.Fl D
+.Nm
+.Op Fl Pv
+.Fl F
+.Nm
+.Op Fl h
+.Fl x
+.\"
+.Sh DESCRIPTION
+The
+.Nm
+utility adds, updates, dumps, or flushes
+Security Association Database (SAD) entries
+as well as Security Policy Database (SPD) entries in the kernel.
+.Pp
+The
+.Nm
+utility takes a series of operations from the standard input
+(if invoked with
+.Fl c )
+or the file named
+.Ar filename
+(if invoked with
+.Fl f Ar filename ) .
+.Bl -tag -width indent
+.It Fl D
+Dump the SAD entries.
+If with
+.Fl P ,
+the SPD entries are dumped.
+.It Fl F
+Flush the SAD entries.
+If with
+.Fl P ,
+the SPD entries are flushed.
+.It Fl a
+The
+.Nm
+utility
+usually does not display dead SAD entries with
+.Fl D .
+If with
+.Fl a ,
+the dead SAD entries will be displayed as well.
+A dead SAD entry means that
+it has been expired but remains in the system
+because it is referenced by some SPD entries.
+.It Fl h
+Add hexadecimal dump on
+.Fl x
+mode.
+.It Fl l
+Loop forever with short output on
+.Fl D .
+.It Fl v
+Be verbose.
+The program will dump messages exchanged on
+.Dv PF_KEY
+socket, including messages sent from other processes to the kernel.
+.It Fl x
+Loop forever and dump all the messages transmitted to
+.Dv PF_KEY
+socket.
+.Fl xx
+makes each timestamp unformatted.
+.El
+.Ss Configuration syntax
+With
+.Fl c
+or
+.Fl f
+on the command line,
+.Nm
+accepts the following configuration syntax.
+Lines starting with hash signs
+.Pq Ql #
+are treated as comment lines.
+.Bl -tag -width indent
+.It Xo
+.Li add
+.Op Fl 46n
+.Ar src Ar dst Ar protocol Ar spi
+.Op Ar extensions
+.Ar algorithm ...
+.Li \&;
+.Xc
+Add an SAD entry.
+.Li add
+can fail with multiple reasons,
+including when the key length does not match the specified algorithm.
+.\"
+.It Xo
+.Li get
+.Op Fl 46n
+.Ar src Ar dst Ar protocol Ar spi
+.Li \&;
+.Xc
+Show an SAD entry.
+.\"
+.It Xo
+.Li delete
+.Op Fl 46n
+.Ar src Ar dst Ar protocol Ar spi
+.Li \&;
+.Xc
+Remove an SAD entry.
+.\"
+.It Xo
+.Li deleteall
+.Op Fl 46n
+.Ar src Ar dst Ar protocol
+.Li \&;
+.Xc
+Remove all SAD entries that match the specification.
+.\"
+.It Xo
+.Li flush
+.Op Ar protocol
+.Li \&;
+.Xc
+Clear all SAD entries matched by the options.
+.Fl F
+on the command line achieves the same functionality.
+.\"
+.It Xo
+.Li dump
+.Op Ar protocol
+.Li \&;
+.Xc
+Dumps all SAD entries matched by the options.
+.Fl D
+on the command line achieves the same functionality.
+.\"
+.It Xo
+.Li spdadd
+.Op Fl 46n
+.Ar src_range Ar dst_range Ar upperspec Ar policy
+.Li \&;
+.Xc
+Add an SPD entry.
+.\"
+.It Xo
+.Li spddelete
+.Op Fl 46n
+.Ar src_range Ar dst_range Ar upperspec Fl P Ar direction
+.Li \&;
+.Xc
+Delete an SPD entry.
+.\"
+.It Xo
+.Li spdflush
+.Li \&;
+.Xc
+Clear all SPD entries.
+.Fl FP
+on the command line achieves the same functionality.
+.\"
+.It Xo
+.Li spddump
+.Li \&;
+.Xc
+Dumps all SPD entries.
+.Fl DP
+on the command line achieves the same functionality.
+.El
+.\"
+.Pp
+Meta-arguments are as follows:
+.Pp
+.Bl -tag -compact -width indent
+.It Ar src
+.It Ar dst
+Source/destination of the secure communication is specified as
+IPv4/v6 address.
+The
+.Nm
+utility
+can resolve a FQDN into numeric addresses.
+If the FQDN resolves into multiple addresses,
+.Nm
+will install multiple SAD/SPD entries into the kernel
+by trying all possible combinations.
+.Fl 4 ,
+.Fl 6
+and
+.Fl n
+restricts the address resolution of FQDN in certain ways.
+.Fl 4
+and
+.Fl 6
+restrict results into IPv4/v6 addresses only, respectively.
+.Fl n
+avoids FQDN resolution and requires addresses to be numeric addresses.
+.\"
+.Pp
+.It Ar protocol
+.Ar protocol
+is one of following:
+.Bl -tag -width Fl -compact
+.It Li esp
+ESP based on rfc2406
+.It Li esp-old
+ESP based on rfc1827
+.It Li ah
+AH based on rfc2402
+.It Li ah-old
+AH based on rfc1826
+.It Li ipcomp
+IPComp
+.It Li tcp
+TCP-MD5 based on rfc2385
+.El
+.\"
+.Pp
+.It Ar spi
+Security Parameter Index
+(SPI)
+for the SAD and the SPD.
+.Ar spi
+must be a decimal number, or a hexadecimal number with
+.Ql 0x
+prefix.
+SPI values between 0 and 255 are reserved for future use by IANA
+and they cannot be used.
+TCP-MD5 associations must use 0x1000 and therefore only have per-host
+granularity at this time.
+.\"
+.Pp
+.It Ar extensions
+take some of the following:
+.Bl -tag -width Fl -compact
+.\"
+.It Fl m Ar mode
+Specify a security protocol mode for use.
+.Ar mode
+is one of following:
+.Li transport , tunnel
+or
+.Li any .
+The default value is
+.Li any .
+.\"
+.It Fl r Ar size
+Specify window size of bytes for replay prevention.
+.Ar size
+must be decimal number in 32-bit word.
+If
+.Ar size
+is zero or not specified, replay check does not take place.
+.\"
+.It Fl u Ar id
+Specify the identifier of the policy entry in SPD.
+See
+.Ar policy .
+.\"
+.It Fl f Ar pad_option
+defines the content of the ESP padding.
+.Ar pad_option
+is one of following:
+.Bl -tag -width random-pad -compact
+.It Li zero-pad
+All of the padding are zero.
+.It Li random-pad
+A series of randomized values are set.
+.It Li seq-pad
+A series of sequential increasing numbers started from 1 are set.
+.El
+.\"
+.It Fl f Li nocyclic-seq
+Do not allow cyclic sequence number.
+.\"
+.It Fl lh Ar time
+.It Fl ls Ar time
+Specify hard/soft life time duration of the SA.
+.El
+.\"
+.Pp
+.It Ar algorithm
+.Bl -tag -width Fl -compact
+.It Fl E Ar ealgo Ar key
+Specify an encryption algorithm
+.Ar ealgo
+for ESP.
+.It Xo
+.Fl E Ar ealgo Ar key
+.Fl A Ar aalgo Ar key
+.Xc
+Specify a encryption algorithm
+.Ar ealgo ,
+as well as a payload authentication algorithm
+.Ar aalgo ,
+for ESP.
+.It Fl A Ar aalgo Ar key
+Specify an authentication algorithm for AH.
+.It Fl C Ar calgo Op Fl R
+Specify a compression algorithm for IPComp.
+If
+.Fl R
+is specified, the
+.Ar spi
+field value will be used as the IPComp CPI
+(compression parameter index)
+on wire as is.
+If
+.Fl R
+is not specified,
+the kernel will use well-known CPI on wire, and
+.Ar spi
+field will be used only as an index for kernel internal usage.
+.El
+.Pp
+.Ar key
+must be double-quoted character string, or a series of hexadecimal digits
+preceded by
+.Ql 0x .
+.Pp
+Possible values for
+.Ar ealgo ,
+.Ar aalgo
+and
+.Ar calgo
+are specified in separate section.
+.\"
+.Pp
+.It Ar src_range
+.It Ar dst_range
+These are selections of the secure communication specified as
+IPv4/v6 address or IPv4/v6 address range, and it may accompany
+TCP/UDP port specification.
+This takes the following form:
+.Bd -unfilled
+.Ar address
+.Ar address/prefixlen
+.Ar address[port]
+.Ar address/prefixlen[port]
+.Ed
+.Pp
+.Ar prefixlen
+and
+.Ar port
+must be a decimal number.
+The square brackets around
+.Ar port
+are necessary and are not manpage metacharacters.
+For FQDN resolution, the rules applicable to
+.Ar src
+and
+.Ar dst
+apply here as well.
+.\"
+.Pp
+.It Ar upperspec
+The upper layer protocol to be used.
+You can use one of the words in
+.Pa /etc/protocols
+as
+.Ar upperspec ,
+as well as
+.Li icmp6 ,
+.Li ip4 ,
+or
+.Li any .
+The word
+.Li any
+stands for
+.Dq any protocol .
+The protocol number may also be used to specify the
+.Ar upperspec .
+A type and code related to ICMPv6 may also be specified as an
+.Ar upperspec .
+The type is specified first, followed by a comma and then the relevant
+code.
+The specification must be placed after
+.Li icmp6 .
+The kernel considers a zero to be a wildcard but
+cannot distinguish between a wildcard and an ICMPv6
+type which is zero.
+The following example shows a policy where IPSec is not required for
+inbound Neighbor Solicitations:
+.Pp
+.Dl "spdadd ::/0 ::/0 icmp6 135,0 -P in none;"
+.Pp
+NOTE:
+.Ar upperspec
+does not work in the forwarding case at this moment,
+as it requires extra reassembly at forwarding node,
+which is not implemented at this moment.
+Although there are many protocols in
+.Pa /etc/protocols ,
+protocols other than TCP, UDP and ICMP may not be suitable to use with IPsec.
+.\"
+.Pp
+.It Ar policy
+.Ar policy
+is expressed in one of the following three formats:
+.Pp
+.Bl -tag -width 2n -compact
+.It Fl P Ar direction Li discard
+.It Fl P Ar direction Li none
+.It Xo Fl P Ar direction Li ipsec
+.Ar protocol/mode/src-dst/level Op ...
+.Xc
+.El
+.Pp
+The direction of a policy must be specified as
+one of:
+.Li out ,
+.Li in ,
+.Li discard ,
+.Li none ,
+or
+.Li ipsec .
+The
+.Li discard
+direction
+means that packets matching the supplied indices will be discarded
+while
+.Li none
+means that IPsec operations will not take place on the packet and
+.Li ipsec
+means that IPsec operation will take place onto the packet.
+The
+.Ar protocol/mode/src-dst/level
+statement gives the rule for how to process the packet.
+The
+.Ar protocol
+is specified as
+.Li ah ,
+.Li esp
+or
+.Li ipcomp .
+The
+.Ar mode
+is either
+.Li transport
+or
+.Li tunnel .
+If
+.Ar mode
+is
+.Li tunnel ,
+you must specify the end-point addresses of the SA as
+.Ar src
+and
+.Ar dst
+with a dash,
+.Sq - ,
+between the addresses.
+If
+.Ar mode
+is
+.Li transport ,
+both
+.Ar src
+and
+.Ar dst
+can be omitted.
+The
+.Ar level
+is one of the following:
+.Li default , use , require
+or
+.Li unique .
+If the SA is not available in every level, the kernel will request
+the SA from the key exchange daemon.
+A value of
+.Li default
+tells the kernel to use the system wide default protocol
+e.g.,\& the one from the
+.Li esp_trans_deflev
+sysctl variable, when the kernel processes the packet.
+A value of
+.Li use
+means that the kernel will use an SA if it is available,
+otherwise the kernel will pass the packet as it would normally.
+A value of
+.Li require
+means that an SA is required whenever the kernel sends a packet matched
+that matches the policy.
+The
+.Li unique
+level is the same as
+.Li require
+but, in addition, it allows the policy to bind with the unique out-bound SA.
+For example, if you specify the policy level
+.Li unique ,
+.Xr racoon 8
+will configure the SA for the policy.
+If you configure the SA by manual keying for that policy,
+you can put the decimal number as the policy identifier after
+.Li unique
+separated by colon
+.Ql :\&
+as in the following example:
+.Li unique:number .
+In order to bind this policy to the SA,
+.Li number
+must be between 1 and 32767,
+which corresponds to
+.Ar extensions Fl u
+of manual SA configuration.
+.Pp
+When you want to use an SA bundle, you can define multiple rules.
+For
+example, if an IP header was followed by an AH header followed by an
+ESP header followed by an upper layer protocol header, the rule would
+be:
+.Pp
+.Dl esp/transport//require ah/transport//require ;
+.Pp
+The rule order is very important.
+.Pp
+Note that
+.Dq Li discard
+and
+.Dq Li none
+are not in the syntax described in
+.Xr ipsec_set_policy 3 .
+There are small, but important, differences in the syntax.
+See
+.Xr ipsec_set_policy 3
+for details.
+.El
+.\"
+.Sh ALGORITHMS
+The following list shows the supported algorithms.
+The
+.Sy protocol
+and
+.Sy algorithm
+are almost completely orthogonal.
+The following list of authentication algorithms can be used as
+.Ar aalgo
+in the
+.Fl A Ar aalgo
+of the
+.Ar protocol
+parameter:
+.Bd -literal -offset indent
+algorithm keylen (bits) comment
+hmac-md5 128 ah: rfc2403
+ 128 ah-old: rfc2085
+hmac-sha1 160 ah: rfc2404
+ 160 ah-old: 128bit ICV (no document)
+keyed-md5 128 ah: 96bit ICV (no document)
+ 128 ah-old: rfc1828
+keyed-sha1 160 ah: 96bit ICV (no document)
+ 160 ah-old: 128bit ICV (no document)
+null 0 to 2048 for debugging
+hmac-sha2-256 256 ah: 96bit ICV
+ (draft-ietf-ipsec-ciph-sha-256-00)
+ 256 ah-old: 128bit ICV (no document)
+hmac-sha2-384 384 ah: 96bit ICV (no document)
+ 384 ah-old: 128bit ICV (no document)
+hmac-sha2-512 512 ah: 96bit ICV (no document)
+ 512 ah-old: 128bit ICV (no document)
+hmac-ripemd160 160 ah: 96bit ICV (RFC2857)
+ ah-old: 128bit ICV (no document)
+aes-xcbc-mac 128 ah: 96bit ICV (RFC3566)
+ 128 ah-old: 128bit ICV (no document)
+tcp-md5 8 to 640 tcp: rfc2385
+.Ed
+.Pp
+The following is the list of encryption algorithms that can be used as the
+.Ar ealgo
+in the
+.Fl E Ar ealgo
+of the
+.Ar protocol
+parameter:
+.Bd -literal -offset indent
+algorithm keylen (bits) comment
+des-cbc 64 esp-old: rfc1829, esp: rfc2405
+3des-cbc 192 rfc2451
+null 0 to 2048 rfc2410
+blowfish-cbc 40 to 448 rfc2451
+cast128-cbc 40 to 128 rfc2451
+des-deriv 64 ipsec-ciph-des-derived-01
+3des-deriv 192 no document
+rijndael-cbc 128/192/256 rfc3602
+aes-ctr 160/224/288 draft-ietf-ipsec-ciph-aes-ctr-03
+camellia-cbc 128/192/256 rfc4312
+.Ed
+.Pp
+Note that the first 128/192/256 bits of a key for
+.Li aes-ctr
+will be used as AES key, and remaining 32 bits will be used as nonce.
+.Pp
+The following are the list of compression algorithms that can be used
+as the
+.Ar calgo
+in the
+.Fl C Ar calgo
+of the
+.Ar protocol
+parameter:
+.Bd -literal -offset indent
+algorithm comment
+deflate rfc2394
+.Ed
+.\"
+.Sh EXIT STATUS
+.Ex -std
+.\"
+.Sh EXAMPLES
+Add an ESP SA between two IPv6 addresses using the
+des-cbc encryption algorithm.
+.Bd -literal -offset indent
+add 3ffe:501:4819::1 3ffe:501:481d::1 esp 123457
+ -E des-cbc 0x3ffe05014819ffff ;
+.Pp
+.Ed
+.\"
+Add an authentication SA between two FQDN specified hosts:
+.Bd -literal -offset indent
+add -6 myhost.example.com yourhost.example.com ah 123456
+ -A hmac-sha1 "AH SA configuration!" ;
+.Pp
+.Ed
+Use both ESP and AH between two numerically specified hosts:
+.Bd -literal -offset indent
+add 10.0.11.41 10.0.11.33 esp 0x10001
+ -E des-cbc 0x3ffe05014819ffff
+ -A hmac-md5 "authentication!!" ;
+.Pp
+.Ed
+Get the SA information associated with first example above:
+.Bd -literal -offset indent
+get 3ffe:501:4819::1 3ffe:501:481d::1 ah 123456 ;
+.Pp
+.Ed
+Flush all entries from the database:
+.Bd -literal -offset indent
+flush ;
+.Pp
+.Ed
+Dump the ESP entries from the database:
+.Bd -literal -offset indent
+dump esp ;
+.Pp
+.Ed
+Add a security policy between two networks that uses ESP in tunnel mode:
+.Bd -literal -offset indent
+spdadd 10.0.11.41/32[21] 10.0.11.33/32[any] any
+ -P out ipsec esp/tunnel/192.168.0.1-192.168.1.2/require ;
+.Pp
+.Ed
+Use TCP MD5 between two numerically specified hosts:
+.Bd -literal -offset indent
+add 10.1.10.34 10.1.10.36 tcp 0x1000 -A tcp-md5 "TCP-MD5 BGP secret" ;
+.Ed
+.\"
+.Sh SEE ALSO
+.Xr ipsec_set_policy 3 ,
+.Xr racoon 8 ,
+.Xr sysctl 8
+.Rs
+.%T "Changed manual key configuration for IPsec"
+.%U http://www.kame.net/newsletter/19991007/
+.%D "October 1999"
+.Re
+.\"
+.Sh HISTORY
+The
+.Nm
+utility first appeared in WIDE Hydrangea IPv6 protocol stack kit.
+The utility was completely re-designed in June 1998.
+.\"
+.Sh BUGS
+The
+.Nm
+utility
+should report and handle syntax errors better.
+.Pp
+For IPsec gateway configuration,
+.Ar src_range
+and
+.Ar dst_range
+with TCP/UDP port number do not work, as the gateway does not reassemble
+packets
+(cannot inspect upper-layer headers).
diff --git a/sbin/setkey/setkey.c b/sbin/setkey/setkey.c
new file mode 100644
index 0000000..543f428
--- /dev/null
+++ b/sbin/setkey/setkey.c
@@ -0,0 +1,632 @@
+/* $FreeBSD$ */
+/* $KAME: setkey.c,v 1.28 2003/06/27 07:15:45 itojun Exp $ */
+
+/*
+ * Copyright (C) 1995, 1996, 1997, 1998, and 1999 WIDE Project.
+ * 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 project 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 PROJECT 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 PROJECT 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 <sys/types.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <err.h>
+#include <net/route.h>
+#include <netinet/in.h>
+#include <net/pfkeyv2.h>
+#include <netipsec/keydb.h>
+#include <netipsec/key_debug.h>
+#include <netipsec/ipsec.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <errno.h>
+#include <netdb.h>
+
+#include "libpfkey.h"
+
+void usage(void);
+int main(int, char **);
+int get_supported(void);
+void sendkeyshort(u_int);
+void promisc(void);
+int sendkeymsg(char *, size_t);
+int postproc(struct sadb_msg *, int);
+const char *numstr(int);
+void shortdump_hdr(void);
+void shortdump(struct sadb_msg *);
+static void printdate(void);
+static int32_t gmt2local(time_t);
+
+#define MODE_SCRIPT 1
+#define MODE_CMDDUMP 2
+#define MODE_CMDFLUSH 3
+#define MODE_PROMISC 4
+
+int so;
+
+int f_forever = 0;
+int f_all = 0;
+int f_verbose = 0;
+int f_mode = 0;
+int f_cmddump = 0;
+int f_policy = 0;
+int f_hexdump = 0;
+int f_tflag = 0;
+static time_t thiszone;
+
+extern int lineno;
+
+extern int parse(FILE **);
+
+void
+usage()
+{
+
+ printf("usage: setkey [-v] -c\n");
+ printf(" setkey [-v] -f filename\n");
+ printf(" setkey [-Palv] -D\n");
+ printf(" setkey [-Pv] -F\n");
+ printf(" setkey [-h] -x\n");
+ exit(1);
+}
+
+int
+main(ac, av)
+ int ac;
+ char **av;
+{
+ FILE *fp = stdin;
+ int c;
+
+ if (ac == 1) {
+ usage();
+ /* NOTREACHED */
+ }
+
+ thiszone = gmt2local(0);
+
+ while ((c = getopt(ac, av, "acdf:hlvxDFP")) != -1) {
+ switch (c) {
+ case 'c':
+ f_mode = MODE_SCRIPT;
+ fp = stdin;
+ break;
+ case 'f':
+ f_mode = MODE_SCRIPT;
+ if ((fp = fopen(optarg, "r")) == NULL) {
+ err(-1, "fopen");
+ /*NOTREACHED*/
+ }
+ break;
+ case 'D':
+ f_mode = MODE_CMDDUMP;
+ break;
+ case 'F':
+ f_mode = MODE_CMDFLUSH;
+ break;
+ case 'a':
+ f_all = 1;
+ break;
+ case 'l':
+ f_forever = 1;
+ break;
+ case 'h':
+ f_hexdump = 1;
+ break;
+ case 'x':
+ f_mode = MODE_PROMISC;
+ f_tflag++;
+ break;
+ case 'P':
+ f_policy = 1;
+ break;
+ case 'v':
+ f_verbose = 1;
+ break;
+ default:
+ usage();
+ /*NOTREACHED*/
+ }
+ }
+
+ so = pfkey_open();
+ if (so < 0) {
+ perror("pfkey_open");
+ exit(1);
+ }
+
+ switch (f_mode) {
+ case MODE_CMDDUMP:
+ sendkeyshort(f_policy ? SADB_X_SPDDUMP: SADB_DUMP);
+ break;
+ case MODE_CMDFLUSH:
+ sendkeyshort(f_policy ? SADB_X_SPDFLUSH: SADB_FLUSH);
+ break;
+ case MODE_SCRIPT:
+ if (get_supported() < 0) {
+ errx(-1, "%s", ipsec_strerror());
+ /*NOTREACHED*/
+ }
+ if (parse(&fp))
+ exit (1);
+ break;
+ case MODE_PROMISC:
+ promisc();
+ /*NOTREACHED*/
+ default:
+ usage();
+ /*NOTREACHED*/
+ }
+
+ exit(0);
+}
+
+int
+get_supported()
+{
+
+ if (pfkey_send_register(so, SADB_SATYPE_UNSPEC) < 0)
+ return -1;
+
+ if (pfkey_recv_register(so) < 0)
+ return -1;
+
+ return 0;
+}
+
+void
+sendkeyshort(type)
+ u_int type;
+{
+ struct sadb_msg msg;
+
+ msg.sadb_msg_version = PF_KEY_V2;
+ msg.sadb_msg_type = type;
+ msg.sadb_msg_errno = 0;
+ msg.sadb_msg_satype = SADB_SATYPE_UNSPEC;
+ msg.sadb_msg_len = PFKEY_UNIT64(sizeof(msg));
+ msg.sadb_msg_reserved = 0;
+ msg.sadb_msg_seq = 0;
+ msg.sadb_msg_pid = getpid();
+
+ sendkeymsg((char *)&msg, sizeof(msg));
+
+ return;
+}
+
+void
+promisc()
+{
+ struct sadb_msg msg;
+ u_char rbuf[1024 * 32]; /* XXX: Enough ? Should I do MSG_PEEK ? */
+ ssize_t l;
+
+ msg.sadb_msg_version = PF_KEY_V2;
+ msg.sadb_msg_type = SADB_X_PROMISC;
+ msg.sadb_msg_errno = 0;
+ msg.sadb_msg_satype = 1;
+ msg.sadb_msg_len = PFKEY_UNIT64(sizeof(msg));
+ msg.sadb_msg_reserved = 0;
+ msg.sadb_msg_seq = 0;
+ msg.sadb_msg_pid = getpid();
+
+ if ((l = send(so, &msg, sizeof(msg), 0)) < 0) {
+ err(1, "send");
+ /*NOTREACHED*/
+ }
+
+ while (1) {
+ struct sadb_msg *base;
+
+ if ((l = recv(so, rbuf, sizeof(*base), MSG_PEEK)) < 0) {
+ err(1, "recv");
+ /*NOTREACHED*/
+ }
+
+ if (l != sizeof(*base))
+ continue;
+
+ base = (struct sadb_msg *)rbuf;
+ if ((l = recv(so, rbuf, PFKEY_UNUNIT64(base->sadb_msg_len),
+ 0)) < 0) {
+ err(1, "recv");
+ /*NOTREACHED*/
+ }
+ printdate();
+ if (f_hexdump) {
+ int i;
+ for (i = 0; i < l; i++) {
+ if (i % 16 == 0)
+ printf("%08x: ", i);
+ printf("%02x ", rbuf[i] & 0xff);
+ if (i % 16 == 15)
+ printf("\n");
+ }
+ if (l % 16)
+ printf("\n");
+ }
+ /* adjust base pointer for promisc mode */
+ if (base->sadb_msg_type == SADB_X_PROMISC) {
+ if ((ssize_t)sizeof(*base) < l)
+ base++;
+ else
+ base = NULL;
+ }
+ if (base) {
+ kdebug_sadb(base);
+ printf("\n");
+ fflush(stdout);
+ }
+ }
+}
+
+int
+sendkeymsg(buf, len)
+ char *buf;
+ size_t len;
+{
+ u_char rbuf[1024 * 32]; /* XXX: Enough ? Should I do MSG_PEEK ? */
+ ssize_t l;
+ struct sadb_msg *msg;
+
+ {
+ struct timeval tv;
+ tv.tv_sec = 1;
+ tv.tv_usec = 0;
+ if (setsockopt(so, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) {
+ perror("setsockopt");
+ goto end;
+ }
+ }
+
+ if (f_forever)
+ shortdump_hdr();
+again:
+ if (f_verbose) {
+ kdebug_sadb((struct sadb_msg *)buf);
+ printf("\n");
+ }
+ if (f_hexdump) {
+ int i;
+ for (i = 0; i < len; i++) {
+ if (i % 16 == 0)
+ printf("%08x: ", i);
+ printf("%02x ", buf[i] & 0xff);
+ if (i % 16 == 15)
+ printf("\n");
+ }
+ if (len % 16)
+ printf("\n");
+ }
+
+ if ((l = send(so, buf, len, 0)) < 0) {
+ perror("send");
+ goto end;
+ }
+
+ msg = (struct sadb_msg *)rbuf;
+ do {
+ if ((l = recv(so, rbuf, sizeof(rbuf), 0)) < 0) {
+ perror("recv");
+ goto end;
+ }
+
+ if (PFKEY_UNUNIT64(msg->sadb_msg_len) != l) {
+ warnx("invalid keymsg length");
+ break;
+ }
+
+ if (f_verbose) {
+ kdebug_sadb((struct sadb_msg *)rbuf);
+ printf("\n");
+ }
+ if (postproc(msg, l) < 0)
+ break;
+ } while (msg->sadb_msg_errno || msg->sadb_msg_seq);
+
+ if (f_forever) {
+ fflush(stdout);
+ sleep(1);
+ goto again;
+ }
+
+end:
+ return(0);
+}
+
+int
+postproc(msg, len)
+ struct sadb_msg *msg;
+ int len;
+{
+
+ if (msg->sadb_msg_errno != 0) {
+ char inf[80];
+ const char *errmsg = NULL;
+
+ if (f_mode == MODE_SCRIPT)
+ snprintf(inf, sizeof(inf), "The result of line %d: ", lineno);
+ else
+ inf[0] = '\0';
+
+ switch (msg->sadb_msg_errno) {
+ case ENOENT:
+ switch (msg->sadb_msg_type) {
+ case SADB_DELETE:
+ case SADB_GET:
+ case SADB_X_SPDDELETE:
+ errmsg = "No entry";
+ break;
+ case SADB_DUMP:
+ errmsg = "No SAD entries";
+ break;
+ case SADB_X_SPDDUMP:
+ errmsg = "No SPD entries";
+ break;
+ }
+ break;
+ default:
+ errmsg = strerror(msg->sadb_msg_errno);
+ }
+ printf("%s%s.\n", inf, errmsg);
+ return(-1);
+ }
+
+ switch (msg->sadb_msg_type) {
+ case SADB_GET:
+ pfkey_sadump(msg);
+ break;
+
+ case SADB_DUMP:
+ /* filter out DEAD SAs */
+ if (!f_all) {
+ caddr_t mhp[SADB_EXT_MAX + 1];
+ struct sadb_sa *sa;
+ pfkey_align(msg, mhp);
+ pfkey_check(mhp);
+ if ((sa = (struct sadb_sa *)mhp[SADB_EXT_SA]) != NULL) {
+ if (sa->sadb_sa_state == SADB_SASTATE_DEAD)
+ break;
+ }
+ }
+ if (f_forever)
+ shortdump(msg);
+ else
+ pfkey_sadump(msg);
+ msg = (struct sadb_msg *)((caddr_t)msg +
+ PFKEY_UNUNIT64(msg->sadb_msg_len));
+ if (f_verbose) {
+ kdebug_sadb((struct sadb_msg *)msg);
+ printf("\n");
+ }
+ break;
+
+ case SADB_X_SPDDUMP:
+ pfkey_spdump(msg);
+ if (msg->sadb_msg_seq == 0) break;
+ msg = (struct sadb_msg *)((caddr_t)msg +
+ PFKEY_UNUNIT64(msg->sadb_msg_len));
+ if (f_verbose) {
+ kdebug_sadb((struct sadb_msg *)msg);
+ printf("\n");
+ }
+ break;
+ }
+
+ return(0);
+}
+
+/*------------------------------------------------------------*/
+static const char *satype[] = {
+ NULL, NULL, "ah", "esp"
+};
+static const char *sastate[] = {
+ "L", "M", "D", "d"
+};
+static const char *ipproto[] = {
+/*0*/ "ip", "icmp", "igmp", "ggp", "ip4",
+ NULL, "tcp", NULL, "egp", NULL,
+/*10*/ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, "udp", NULL, NULL,
+/*20*/ NULL, NULL, "idp", NULL, NULL,
+ NULL, NULL, NULL, NULL, "tp",
+/*30*/ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+/*40*/ NULL, "ip6", NULL, "rt6", "frag6",
+ NULL, "rsvp", "gre", NULL, NULL,
+/*50*/ "esp", "ah", NULL, NULL, NULL,
+ NULL, NULL, NULL, "icmp6", "none",
+/*60*/ "dst6",
+};
+
+#define STR_OR_ID(x, tab) \
+ (((x) < sizeof(tab)/sizeof(tab[0]) && tab[(x)]) ? tab[(x)] : numstr(x))
+
+const char *
+numstr(x)
+ int x;
+{
+ static char buf[20];
+ snprintf(buf, sizeof(buf), "#%d", x);
+ return buf;
+}
+
+void
+shortdump_hdr()
+{
+ printf("%-4s %-3s %-1s %-8s %-7s %s -> %s\n",
+ "time", "p", "s", "spi", "ltime", "src", "dst");
+}
+
+void
+shortdump(msg)
+ struct sadb_msg *msg;
+{
+ caddr_t mhp[SADB_EXT_MAX + 1];
+ char buf[NI_MAXHOST], pbuf[NI_MAXSERV];
+ struct sadb_sa *sa;
+ struct sadb_address *saddr;
+ struct sadb_lifetime *lts, *lth, *ltc;
+ struct sockaddr *s;
+ u_int t;
+ time_t cur = time(0);
+
+ pfkey_align(msg, mhp);
+ pfkey_check(mhp);
+
+ printf("%02lu%02lu", (u_long)(cur % 3600) / 60, (u_long)(cur % 60));
+
+ printf(" %-3s", STR_OR_ID(msg->sadb_msg_satype, satype));
+
+ if ((sa = (struct sadb_sa *)mhp[SADB_EXT_SA]) != NULL) {
+ printf(" %-1s", STR_OR_ID(sa->sadb_sa_state, sastate));
+ printf(" %08x", (u_int32_t)ntohl(sa->sadb_sa_spi));
+ } else
+ printf("%-1s %-8s", "?", "?");
+
+ lts = (struct sadb_lifetime *)mhp[SADB_EXT_LIFETIME_SOFT];
+ lth = (struct sadb_lifetime *)mhp[SADB_EXT_LIFETIME_HARD];
+ ltc = (struct sadb_lifetime *)mhp[SADB_EXT_LIFETIME_CURRENT];
+ if (lts && lth && ltc) {
+ if (ltc->sadb_lifetime_addtime == 0)
+ t = (u_long)0;
+ else
+ t = (u_long)(cur - ltc->sadb_lifetime_addtime);
+ if (t >= 1000)
+ strlcpy(buf, " big/", sizeof(buf));
+ else
+ snprintf(buf, sizeof(buf), " %3lu/", (u_long)t);
+ printf("%s", buf);
+
+ t = (u_long)lth->sadb_lifetime_addtime;
+ if (t >= 1000)
+ strlcpy(buf, "big", sizeof(buf));
+ else
+ snprintf(buf, sizeof(buf), "%-3lu", (u_long)t);
+ printf("%s", buf);
+ } else
+ printf(" ??\?/???"); /* backslash to avoid trigraph ??/ */
+
+ printf(" ");
+
+ if ((saddr = (struct sadb_address *)mhp[SADB_EXT_ADDRESS_SRC]) != NULL) {
+ if (saddr->sadb_address_proto)
+ printf("%s ", STR_OR_ID(saddr->sadb_address_proto, ipproto));
+ s = (struct sockaddr *)(saddr + 1);
+ getnameinfo(s, s->sa_len, buf, sizeof(buf),
+ pbuf, sizeof(pbuf), NI_NUMERICHOST|NI_NUMERICSERV);
+ if (strcmp(pbuf, "0") != 0)
+ printf("%s[%s]", buf, pbuf);
+ else
+ printf("%s", buf);
+ } else
+ printf("?");
+
+ printf(" -> ");
+
+ if ((saddr = (struct sadb_address *)mhp[SADB_EXT_ADDRESS_DST]) != NULL) {
+ if (saddr->sadb_address_proto)
+ printf("%s ", STR_OR_ID(saddr->sadb_address_proto, ipproto));
+
+ s = (struct sockaddr *)(saddr + 1);
+ getnameinfo(s, s->sa_len, buf, sizeof(buf),
+ pbuf, sizeof(pbuf), NI_NUMERICHOST|NI_NUMERICSERV);
+ if (strcmp(pbuf, "0") != 0)
+ printf("%s[%s]", buf, pbuf);
+ else
+ printf("%s", buf);
+ } else
+ printf("?");
+
+ printf("\n");
+}
+
+/* From: tcpdump(1):gmt2local.c and util.c */
+/*
+ * Print the timestamp
+ */
+static void
+printdate()
+{
+ struct timeval tp;
+ int s;
+
+ if (gettimeofday(&tp, NULL) == -1) {
+ perror("gettimeofday");
+ return;
+ }
+
+ if (f_tflag == 1) {
+ /* Default */
+ s = (tp.tv_sec + thiszone ) % 86400;
+ (void)printf("%02d:%02d:%02d.%06u ",
+ s / 3600, (s % 3600) / 60, s % 60, (u_int32_t)tp.tv_usec);
+ } else if (f_tflag > 1) {
+ /* Unix timeval style */
+ (void)printf("%u.%06u ",
+ (u_int32_t)tp.tv_sec, (u_int32_t)tp.tv_usec);
+ }
+
+ printf("\n");
+}
+
+/*
+ * Returns the difference between gmt and local time in seconds.
+ * Use gmtime() and localtime() to keep things simple.
+ */
+int32_t
+gmt2local(time_t t)
+{
+ register int dt, dir;
+ register struct tm *gmt, *loc;
+ struct tm sgmt;
+
+ if (t == 0)
+ t = time(NULL);
+ gmt = &sgmt;
+ *gmt = *gmtime(&t);
+ loc = localtime(&t);
+ dt = (loc->tm_hour - gmt->tm_hour) * 60 * 60 +
+ (loc->tm_min - gmt->tm_min) * 60;
+
+ /*
+ * If the year or julian day is different, we span 00:00 GMT
+ * and must add or subtract a day. Check the year first to
+ * avoid problems when the julian day wraps.
+ */
+ dir = loc->tm_year - gmt->tm_year;
+ if (dir == 0)
+ dir = loc->tm_yday - gmt->tm_yday;
+ dt += dir * 24 * 60 * 60;
+
+ return (dt);
+}
diff --git a/sbin/setkey/test-pfkey.c b/sbin/setkey/test-pfkey.c
new file mode 100644
index 0000000..d3af162
--- /dev/null
+++ b/sbin/setkey/test-pfkey.c
@@ -0,0 +1,531 @@
+/* $FreeBSD$ */
+/* $KAME: test-pfkey.c,v 1.4 2000/06/07 00:29:14 itojun Exp $ */
+
+/*
+ * Copyright (C) 1995, 1996, 1997, 1998, and 1999 WIDE Project.
+ * 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 project 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 PROJECT 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 PROJECT 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 <sys/types.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <net/route.h>
+#include <net/pfkeyv2.h>
+#include <netinet/in.h>
+#include <netipsec/keydb.h>
+#include <netipsec/key_var.h>
+#include <netipsec/key_debug.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <errno.h>
+#include <netdb.h>
+
+u_char m_buf[BUFSIZ];
+u_int m_len;
+char *pname;
+
+void Usage(void);
+int sendkeymsg(void);
+void key_setsadbmsg(u_int);
+void key_setsadbsens(void);
+void key_setsadbprop(void);
+void key_setsadbid(u_int, caddr_t);
+void key_setsadblft(u_int, u_int);
+void key_setspirange(void);
+void key_setsadbkey(u_int, caddr_t);
+void key_setsadbsa(void);
+void key_setsadbaddr(u_int, u_int, caddr_t);
+void key_setsadbextbuf(caddr_t, int, caddr_t, int, caddr_t, int);
+
+void
+Usage()
+{
+ printf("Usage:\t%s number\n", pname);
+ exit(0);
+}
+
+int
+main(ac, av)
+ int ac;
+ char **av;
+{
+ pname = *av;
+
+ if (ac == 1) Usage();
+
+ key_setsadbmsg(atoi(*(av+1)));
+ sendkeymsg();
+
+ exit(0);
+}
+
+/* %%% */
+int
+sendkeymsg()
+{
+ u_char rbuf[1024 * 32]; /* XXX: Enough ? Should I do MSG_PEEK ? */
+ int so, len;
+
+ if ((so = socket(PF_KEY, SOCK_RAW, PF_KEY_V2)) < 0) {
+ perror("socket(PF_KEY)");
+ goto end;
+ }
+#if 0
+ {
+#include <sys/time.h>
+ struct timeval tv;
+ tv.tv_sec = 1;
+ tv.tv_usec = 0;
+ if (setsockopt(so, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) {
+ perror("setsockopt");
+ goto end;
+ }
+ }
+#endif
+
+ pfkey_sadump((struct sadb_msg *)m_buf);
+
+ if ((len = send(so, m_buf, m_len, 0)) < 0) {
+ perror("send");
+ goto end;
+ }
+
+ if ((len = recv(so, rbuf, sizeof(rbuf), 0)) < 0) {
+ perror("recv");
+ goto end;
+ }
+
+ pfkey_sadump((struct sadb_msg *)rbuf);
+
+end:
+ (void)close(so);
+ return(0);
+}
+
+void
+key_setsadbmsg(type)
+ u_int type;
+{
+ struct sadb_msg m_msg;
+
+ memset(&m_msg, 0, sizeof(m_msg));
+ m_msg.sadb_msg_version = PF_KEY_V2;
+ m_msg.sadb_msg_type = type;
+ m_msg.sadb_msg_errno = 0;
+ m_msg.sadb_msg_satype = SADB_SATYPE_ESP;
+#if 0
+ m_msg.sadb_msg_reserved = 0;
+#endif
+ m_msg.sadb_msg_seq = 0;
+ m_msg.sadb_msg_pid = getpid();
+
+ m_len = sizeof(struct sadb_msg);
+ memcpy(m_buf, &m_msg, m_len);
+
+ switch (type) {
+ case SADB_GETSPI:
+ /*<base, address(SD), SPI range>*/
+ key_setsadbaddr(SADB_EXT_ADDRESS_SRC, AF_INET, "10.0.3.4");
+ key_setsadbaddr(SADB_EXT_ADDRESS_DST, AF_INET, "127.0.0.1");
+ key_setspirange();
+ /*<base, SA(*), address(SD)>*/
+ break;
+
+ case SADB_ADD:
+ /* <base, SA, (lifetime(HSC),) address(SD), (address(P),)
+ key(AE), (identity(SD),) (sensitivity)> */
+ key_setsadbaddr(SADB_EXT_ADDRESS_PROXY, AF_INET6, "3ffe::1");
+ case SADB_UPDATE:
+ key_setsadbsa();
+ key_setsadblft(SADB_EXT_LIFETIME_HARD, 10);
+ key_setsadblft(SADB_EXT_LIFETIME_SOFT, 5);
+ key_setsadbaddr(SADB_EXT_ADDRESS_SRC, AF_INET, "192.168.1.1");
+ key_setsadbaddr(SADB_EXT_ADDRESS_DST, AF_INET, "10.0.3.4");
+ /* XXX key_setsadbkey(SADB_EXT_KEY_AUTH, "abcde"); */
+ key_setsadbkey(SADB_EXT_KEY_AUTH, "1234567812345678");
+ key_setsadbkey(SADB_EXT_KEY_ENCRYPT, "12345678");
+ key_setsadbid(SADB_EXT_IDENTITY_SRC, "hoge1234@hoge.com");
+ key_setsadbid(SADB_EXT_IDENTITY_DST, "hage5678@hage.net");
+ key_setsadbsens();
+ /* <base, SA, (lifetime(HSC),) address(SD), (address(P),)
+ (identity(SD),) (sensitivity)> */
+ break;
+
+ case SADB_DELETE:
+ /* <base, SA(*), address(SDP)> */
+ key_setsadbsa();
+ key_setsadbaddr(SADB_EXT_ADDRESS_SRC, AF_INET, "192.168.1.1");
+ key_setsadbaddr(SADB_EXT_ADDRESS_DST, AF_INET, "10.0.3.4");
+ key_setsadbaddr(SADB_EXT_ADDRESS_PROXY, AF_INET6, "3ffe::1");
+ /* <base, SA(*), address(SDP)> */
+ break;
+
+ case SADB_GET:
+ /* <base, SA(*), address(SDP)> */
+ key_setsadbsa();
+ key_setsadbaddr(SADB_EXT_ADDRESS_SRC, AF_INET, "192.168.1.1");
+ key_setsadbaddr(SADB_EXT_ADDRESS_DST, AF_INET, "10.0.3.4");
+ key_setsadbaddr(SADB_EXT_ADDRESS_PROXY, AF_INET6, "3ffe::1");
+ /* <base, SA, (lifetime(HSC),) address(SD), (address(P),)
+ key(AE), (identity(SD),) (sensitivity)> */
+ break;
+
+ case SADB_ACQUIRE:
+ /* <base, address(SD), (address(P),) (identity(SD),)
+ (sensitivity,) proposal> */
+ key_setsadbaddr(SADB_EXT_ADDRESS_SRC, AF_INET, "192.168.1.1");
+ key_setsadbaddr(SADB_EXT_ADDRESS_DST, AF_INET, "10.0.3.4");
+ key_setsadbaddr(SADB_EXT_ADDRESS_PROXY, AF_INET6, "3ffe::1");
+ key_setsadbid(SADB_EXT_IDENTITY_SRC, "hoge1234@hoge.com");
+ key_setsadbid(SADB_EXT_IDENTITY_DST, "hage5678@hage.net");
+ key_setsadbsens();
+ key_setsadbprop();
+ /* <base, address(SD), (address(P),) (identity(SD),)
+ (sensitivity,) proposal> */
+ break;
+
+ case SADB_REGISTER:
+ /* <base> */
+ /* <base, supported> */
+ break;
+
+ case SADB_EXPIRE:
+ case SADB_FLUSH:
+ break;
+
+ case SADB_DUMP:
+ break;
+
+ case SADB_X_PROMISC:
+ /* <base> */
+ /* <base, base(, others)> */
+ break;
+
+ case SADB_X_PCHANGE:
+ break;
+
+ /* for SPD management */
+ case SADB_X_SPDFLUSH:
+ case SADB_X_SPDDUMP:
+ break;
+
+ case SADB_X_SPDADD:
+#if 0
+ {
+ struct sadb_x_policy m_policy;
+
+ m_policy.sadb_x_policy_len = PFKEY_UNIT64(sizeof(m_policy));
+ m_policy.sadb_x_policy_exttype = SADB_X_EXT_POLICY;
+ m_policy.sadb_x_policy_type = SADB_X_PL_IPSEC;
+ m_policy.sadb_x_policy_esp_trans = 1;
+ m_policy.sadb_x_policy_ah_trans = 2;
+ m_policy.sadb_x_policy_esp_network = 3;
+ m_policy.sadb_x_policy_ah_network = 4;
+ m_policy.sadb_x_policy_reserved = 0;
+
+ memcpy(m_buf + m_len, &m_policy, sizeof(struct sadb_x_policy));
+ m_len += sizeof(struct sadb_x_policy);
+ }
+#endif
+
+ case SADB_X_SPDDELETE:
+ key_setsadbaddr(SADB_EXT_ADDRESS_SRC, AF_INET, "192.168.1.1");
+ key_setsadbaddr(SADB_EXT_ADDRESS_DST, AF_INET, "10.0.3.4");
+ break;
+ }
+
+ ((struct sadb_msg *)m_buf)->sadb_msg_len = PFKEY_UNIT64(m_len);
+
+ return;
+}
+
+void
+key_setsadbsens()
+{
+ struct sadb_sens m_sens;
+ u_char buf[64];
+ u_int s, i, slen, ilen, len;
+
+ /* make sens & integ */
+ s = htonl(0x01234567);
+ i = htonl(0x89abcdef);
+ slen = sizeof(s);
+ ilen = sizeof(i);
+ memcpy(buf, &s, slen);
+ memcpy(buf + slen, &i, ilen);
+
+ len = sizeof(m_sens) + PFKEY_ALIGN8(slen) + PFKEY_ALIGN8(ilen);
+ m_sens.sadb_sens_len = PFKEY_UNIT64(len);
+ m_sens.sadb_sens_exttype = SADB_EXT_SENSITIVITY;
+ m_sens.sadb_sens_dpd = 1;
+ m_sens.sadb_sens_sens_level = 2;
+ m_sens.sadb_sens_sens_len = PFKEY_ALIGN8(slen);
+ m_sens.sadb_sens_integ_level = 3;
+ m_sens.sadb_sens_integ_len = PFKEY_ALIGN8(ilen);
+ m_sens.sadb_sens_reserved = 0;
+
+ key_setsadbextbuf(m_buf, m_len,
+ (caddr_t)&m_sens, sizeof(struct sadb_sens),
+ buf, slen + ilen);
+ m_len += len;
+
+ return;
+}
+
+void
+key_setsadbprop()
+{
+ struct sadb_prop m_prop;
+ struct sadb_comb *m_comb;
+ u_char buf[256];
+ u_int len = sizeof(m_prop) + sizeof(m_comb) * 2;
+
+ /* make prop & comb */
+ m_prop.sadb_prop_len = PFKEY_UNIT64(len);
+ m_prop.sadb_prop_exttype = SADB_EXT_PROPOSAL;
+ m_prop.sadb_prop_replay = 0;
+ m_prop.sadb_prop_reserved[0] = 0;
+ m_prop.sadb_prop_reserved[1] = 0;
+ m_prop.sadb_prop_reserved[2] = 0;
+
+ /* the 1st is ESP DES-CBC HMAC-MD5 */
+ m_comb = (struct sadb_comb *)buf;
+ m_comb->sadb_comb_auth = SADB_AALG_MD5HMAC;
+ m_comb->sadb_comb_encrypt = SADB_EALG_DESCBC;
+ m_comb->sadb_comb_flags = 0;
+ m_comb->sadb_comb_auth_minbits = 8;
+ m_comb->sadb_comb_auth_maxbits = 96;
+ m_comb->sadb_comb_encrypt_minbits = 64;
+ m_comb->sadb_comb_encrypt_maxbits = 64;
+ m_comb->sadb_comb_reserved = 0;
+ m_comb->sadb_comb_soft_allocations = 0;
+ m_comb->sadb_comb_hard_allocations = 0;
+ m_comb->sadb_comb_soft_bytes = 0;
+ m_comb->sadb_comb_hard_bytes = 0;
+ m_comb->sadb_comb_soft_addtime = 0;
+ m_comb->sadb_comb_hard_addtime = 0;
+ m_comb->sadb_comb_soft_usetime = 0;
+ m_comb->sadb_comb_hard_usetime = 0;
+
+ /* the 2st is ESP 3DES-CBC and AH HMAC-SHA1 */
+ m_comb = (struct sadb_comb *)(buf + sizeof(*m_comb));
+ m_comb->sadb_comb_auth = SADB_AALG_SHA1HMAC;
+ m_comb->sadb_comb_encrypt = SADB_EALG_3DESCBC;
+ m_comb->sadb_comb_flags = 0;
+ m_comb->sadb_comb_auth_minbits = 8;
+ m_comb->sadb_comb_auth_maxbits = 96;
+ m_comb->sadb_comb_encrypt_minbits = 64;
+ m_comb->sadb_comb_encrypt_maxbits = 64;
+ m_comb->sadb_comb_reserved = 0;
+ m_comb->sadb_comb_soft_allocations = 0;
+ m_comb->sadb_comb_hard_allocations = 0;
+ m_comb->sadb_comb_soft_bytes = 0;
+ m_comb->sadb_comb_hard_bytes = 0;
+ m_comb->sadb_comb_soft_addtime = 0;
+ m_comb->sadb_comb_hard_addtime = 0;
+ m_comb->sadb_comb_soft_usetime = 0;
+ m_comb->sadb_comb_hard_usetime = 0;
+
+ key_setsadbextbuf(m_buf, m_len,
+ (caddr_t)&m_prop, sizeof(struct sadb_prop),
+ buf, sizeof(*m_comb) * 2);
+ m_len += len;
+
+ return;
+}
+
+void
+key_setsadbid(ext, str)
+ u_int ext;
+ caddr_t str;
+{
+ struct sadb_ident m_id;
+ u_int idlen = strlen(str), len;
+
+ len = sizeof(m_id) + PFKEY_ALIGN8(idlen);
+ m_id.sadb_ident_len = PFKEY_UNIT64(len);
+ m_id.sadb_ident_exttype = ext;
+ m_id.sadb_ident_type = SADB_IDENTTYPE_USERFQDN;
+ m_id.sadb_ident_reserved = 0;
+ m_id.sadb_ident_id = getpid();
+
+ key_setsadbextbuf(m_buf, m_len,
+ (caddr_t)&m_id, sizeof(struct sadb_ident),
+ str, idlen);
+ m_len += len;
+
+ return;
+}
+
+void
+key_setsadblft(ext, time)
+ u_int ext, time;
+{
+ struct sadb_lifetime m_lft;
+
+ m_lft.sadb_lifetime_len = PFKEY_UNIT64(sizeof(m_lft));
+ m_lft.sadb_lifetime_exttype = ext;
+ m_lft.sadb_lifetime_allocations = 0x2;
+ m_lft.sadb_lifetime_bytes = 0x1000;
+ m_lft.sadb_lifetime_addtime = time;
+ m_lft.sadb_lifetime_usetime = 0x0020;
+
+ memcpy(m_buf + m_len, &m_lft, sizeof(struct sadb_lifetime));
+ m_len += sizeof(struct sadb_lifetime);
+
+ return;
+}
+
+void
+key_setspirange()
+{
+ struct sadb_spirange m_spi;
+
+ m_spi.sadb_spirange_len = PFKEY_UNIT64(sizeof(m_spi));
+ m_spi.sadb_spirange_exttype = SADB_EXT_SPIRANGE;
+ m_spi.sadb_spirange_min = 0x00001000;
+ m_spi.sadb_spirange_max = 0x00002000;
+ m_spi.sadb_spirange_reserved = 0;
+
+ memcpy(m_buf + m_len, &m_spi, sizeof(struct sadb_spirange));
+ m_len += sizeof(struct sadb_spirange);
+
+ return;
+}
+
+void
+key_setsadbkey(ext, str)
+ u_int ext;
+ caddr_t str;
+{
+ struct sadb_key m_key;
+ u_int keylen = strlen(str);
+ u_int len;
+
+ len = sizeof(struct sadb_key) + PFKEY_ALIGN8(keylen);
+ m_key.sadb_key_len = PFKEY_UNIT64(len);
+ m_key.sadb_key_exttype = ext;
+ m_key.sadb_key_bits = keylen * 8;
+ m_key.sadb_key_reserved = 0;
+
+ key_setsadbextbuf(m_buf, m_len,
+ (caddr_t)&m_key, sizeof(struct sadb_key),
+ str, keylen);
+ m_len += len;
+
+ return;
+}
+
+void
+key_setsadbsa()
+{
+ struct sadb_sa m_sa;
+
+ m_sa.sadb_sa_len = PFKEY_UNIT64(sizeof(struct sadb_sa));
+ m_sa.sadb_sa_exttype = SADB_EXT_SA;
+ m_sa.sadb_sa_spi = htonl(0x12345678);
+ m_sa.sadb_sa_replay = 4;
+ m_sa.sadb_sa_state = 0;
+ m_sa.sadb_sa_auth = SADB_AALG_MD5HMAC;
+ m_sa.sadb_sa_encrypt = SADB_EALG_DESCBC;
+ m_sa.sadb_sa_flags = 0;
+
+ memcpy(m_buf + m_len, &m_sa, sizeof(struct sadb_sa));
+ m_len += sizeof(struct sadb_sa);
+
+ return;
+}
+
+void
+key_setsadbaddr(ext, af, str)
+ u_int ext, af;
+ caddr_t str;
+{
+ struct sadb_address m_addr;
+ u_int len;
+ struct addrinfo hints, *res;
+ const char *serv;
+ int plen;
+
+ switch (af) {
+ case AF_INET:
+ plen = sizeof(struct in_addr) << 3;
+ break;
+ case AF_INET6:
+ plen = sizeof(struct in6_addr) << 3;
+ break;
+ default:
+ /* XXX bark */
+ exit(1);
+ }
+
+ /* make sockaddr buffer */
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = af;
+ hints.ai_socktype = SOCK_DGRAM; /*dummy*/
+ hints.ai_flags = AI_NUMERICHOST;
+ serv = (ext == SADB_EXT_ADDRESS_PROXY ? "0" : "4660"); /*0x1234*/
+ if (getaddrinfo(str, serv, &hints, &res) != 0 || res->ai_next) {
+ /* XXX bark */
+ exit(1);
+ }
+
+ len = sizeof(struct sadb_address) + PFKEY_ALIGN8(res->ai_addrlen);
+ m_addr.sadb_address_len = PFKEY_UNIT64(len);
+ m_addr.sadb_address_exttype = ext;
+ m_addr.sadb_address_proto =
+ (ext == SADB_EXT_ADDRESS_PROXY ? 0 : IPPROTO_TCP);
+ m_addr.sadb_address_prefixlen = plen;
+ m_addr.sadb_address_reserved = 0;
+
+ key_setsadbextbuf(m_buf, m_len,
+ (caddr_t)&m_addr, sizeof(struct sadb_address),
+ (caddr_t)res->ai_addr, res->ai_addrlen);
+ m_len += len;
+
+ freeaddrinfo(res);
+
+ return;
+}
+
+void
+key_setsadbextbuf(dst, off, ebuf, elen, vbuf, vlen)
+ caddr_t dst, ebuf, vbuf;
+ int off, elen, vlen;
+{
+ memset(dst + off, 0, elen + vlen);
+ memcpy(dst + off, (caddr_t)ebuf, elen);
+ memcpy(dst + off + elen, vbuf, vlen);
+
+ return;
+}
+
diff --git a/sbin/setkey/test-policy.c b/sbin/setkey/test-policy.c
new file mode 100644
index 0000000..9e9b723
--- /dev/null
+++ b/sbin/setkey/test-policy.c
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 1995, 1996, 1997, 1998, and 1999 WIDE Project.
+ * 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 project 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 PROJECT 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 PROJECT 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.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet6/in6.h>
+#include <netipsec/ipsec.h>
+#include <stdlib.h>
+#include <string.h>
+
+
+char *requests[] = {
+"must_error", /* must be error */
+"ipsec must_error", /* must be error */
+"ipsec esp/must_error", /* must be error */
+"discard",
+"none",
+"entrust",
+"bypass", /* may be error */
+"ipsec esp", /* must be error */
+"ipsec ah/require",
+"ipsec ah/use/",
+"ipsec esp/require ah/default/203.178.141.194",
+"ipsec ah/use/203.178.141.195 esp/use/203.178.141.194",
+"ipsec esp/elf.wide.ydc.co.jp esp/www.wide.ydc.co.jp"
+"
+ipsec esp/require ah/use esp/require/10.0.0.1
+ah/use/3ffe:501:481d::1 ah/use/3ffe:501:481d::1 ah/use/3ffe:501:481d::1
+ah/use/3ffe:501:481d::1 ah/use/3ffe:501:481d::1 ah/use/3ffe:501:481d::1
+ah/use/3ffe:501:481d::1 ah/use/3ffe:501:481d::1 ah/use/3ffe:501:481d::1
+ah/use/3ffe:501:481d::1 ah/use/3ffe:501:481d::1 ah/use/3ffe:501:481d::1
+ah/use/3ffe:501:481d::1 ah/use/3ffe:501:481d::1 ah/use/3ffe:501:481d::1
+ah/use/3ffe:501:481d::1 ah/use/3ffe:501:481d::1 ah/use/3ffe:501:481d::1
+ah/use/3ffe:501:481d::1 ah/use/3ffe:501:481d::1 ah/use/3ffe:501:481d::1
+ah/use/3ffe:501:481d::1 ah/use/3ffe:501:481d::1ah/use/3ffe:501:481d::1
+",
+};
+
+u_char *p_secpolicy;
+
+int test(char *buf, int family);
+char *setpolicy(char *req);
+
+main()
+{
+ int i;
+ char *buf;
+
+ for (i = 0; i < sizeof(requests)/sizeof(requests[0]); i++) {
+ printf("* requests:[%s]\n", requests[i]);
+ if ((buf = setpolicy(requests[i])) == NULL)
+ continue;
+ printf("\tsetlen:%d\n", PFKEY_EXTLEN(buf));
+
+ printf("\tPF_INET:\n");
+ test(buf, PF_INET);
+
+ printf("\tPF_INET6:\n");
+ test(buf, PF_INET6);
+ free(buf);
+ }
+}
+
+int test(char *policy, int family)
+{
+ int so, proto, optname;
+ int len;
+ char getbuf[1024];
+
+ switch (family) {
+ case PF_INET:
+ proto = IPPROTO_IP;
+ optname = IP_IPSEC_POLICY;
+ break;
+ case PF_INET6:
+ proto = IPPROTO_IPV6;
+ optname = IPV6_IPSEC_POLICY;
+ break;
+ }
+
+ if ((so = socket(family, SOCK_DGRAM, 0)) < 0)
+ perror("socket");
+
+ if (setsockopt(so, proto, optname, policy, PFKEY_EXTLEN(policy)) < 0)
+ perror("setsockopt");
+
+ len = sizeof(getbuf);
+ memset(getbuf, 0, sizeof(getbuf));
+ if (getsockopt(so, proto, optname, getbuf, &len) < 0)
+ perror("getsockopt");
+
+ {
+ char *buf = NULL;
+
+ printf("\tgetlen:%d\n", len);
+
+ if ((buf = ipsec_dump_policy(getbuf, NULL)) == NULL)
+ ipsec_strerror();
+ else
+ printf("\t[%s]\n", buf);
+
+ free(buf);
+ }
+
+ close (so);
+}
+
+char *setpolicy(char *req)
+{
+ int len;
+ char *buf;
+
+ if ((len = ipsec_get_policylen(req)) < 0) {
+ printf("ipsec_get_policylen: %s\n", ipsec_strerror());
+ return NULL;
+ }
+
+ if ((buf = malloc(len)) == NULL) {
+ perror("malloc");
+ return NULL;
+ }
+
+ if ((len = ipsec_set_policy(buf, len, req)) < 0) {
+ printf("ipsec_set_policy: %s\n", ipsec_strerror());
+ free(buf);
+ return NULL;
+ }
+
+ return buf;
+}
diff --git a/sbin/setkey/token.l b/sbin/setkey/token.l
new file mode 100644
index 0000000..c89982f
--- /dev/null
+++ b/sbin/setkey/token.l
@@ -0,0 +1,287 @@
+/* $FreeBSD$ */
+/* $KAME: token.l,v 1.43 2003/07/25 09:35:28 itojun Exp $ */
+
+/*
+ * Copyright (C) 1995, 1996, 1997, 1998, and 1999 WIDE Project.
+ * 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 project 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 PROJECT 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 PROJECT 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 <sys/types.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <net/route.h>
+#include <net/pfkeyv2.h>
+#include <netipsec/keydb.h>
+#include <netipsec/key_debug.h>
+#include <netinet/in.h>
+#include <netipsec/ipsec.h>
+
+#include <stdlib.h>
+#include <limits.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <netdb.h>
+
+#include "vchar.h"
+#include "y.tab.h"
+
+int lineno = 1;
+
+extern u_char m_buf[BUFSIZ];
+extern u_int m_len;
+extern int f_debug;
+
+int yylex(void);
+void yyfatal(const char *s);
+void yyerror(const char *s);
+extern void parse_init(void);
+int parse(FILE **);
+int yyparse(void);
+%}
+
+/* common section */
+nl \n
+ws [ \t]+
+digit [0-9]
+letter [0-9A-Za-z]
+hexdigit [0-9A-Fa-f]
+dot \.
+hyphen \-
+slash \/
+blcl \[
+elcl \]
+semi \;
+comment \#.*
+quotedstring \"[^"]*\"
+decstring {digit}+
+hexstring 0[xX]{hexdigit}+
+ipaddress [a-fA-F0-9:]([a-fA-F0-9:\.]*|[a-fA-F0-9:\.]*%[a-zA-Z0-9]*)
+ipaddrmask {slash}{digit}{1,3}
+name {letter}(({letter}|{digit}|{hyphen})*({letter}|{digit}))*
+hostname {name}(({dot}{name})+{dot}?)?
+
+%s S_PL S_AUTHALG S_ENCALG
+
+%%
+
+add { return(ADD); }
+delete { return(DELETE); }
+deleteall { return(DELETEALL); }
+get { return(GET); }
+flush { return(FLUSH); }
+dump { return(DUMP); }
+
+ /* for management SPD */
+spdadd { return(SPDADD); }
+spddelete { return(SPDDELETE); }
+spddump { return(SPDDUMP); }
+spdflush { return(SPDFLUSH); }
+tagged { return(TAGGED); }
+{hyphen}P { BEGIN S_PL; return(F_POLICY); }
+<S_PL>[a-zA-Z0-9:\.\-_/ \n\t][a-zA-Z0-9:\.%\-_/ \n\t]* {
+ yymore();
+
+ /* count up for nl */
+ {
+ char *p;
+ for (p = yytext; *p != '\0'; p++)
+ if (*p == '\n')
+ lineno++;
+ }
+
+ yylval.val.len = strlen(yytext);
+ yylval.val.buf = strdup(yytext);
+ if (!yylval.val.buf)
+ yyfatal("insufficient memory");
+
+ return(PL_REQUESTS);
+ }
+<S_PL>{semi} { BEGIN INITIAL; return(EOT); }
+
+ /* address resolution flags */
+{hyphen}[n46][n46]* {
+ yylval.val.len = strlen(yytext);
+ yylval.val.buf = strdup(yytext);
+ if (!yylval.val.buf)
+ yyfatal("insufficient memory");
+ return(F_AIFLAGS);
+ }
+
+ /* security protocols */
+ah { yylval.num = 0; return(PR_AH); }
+esp { yylval.num = 0; return(PR_ESP); }
+ah-old { yylval.num = 1; return(PR_AH); }
+esp-old { yylval.num = 1; return(PR_ESP); }
+ipcomp { yylval.num = 0; return(PR_IPCOMP); }
+tcp { yylval.num = 0; return(PR_TCP); }
+
+ /* authentication alogorithm */
+{hyphen}A { BEGIN S_AUTHALG; return(F_AUTH); }
+<S_AUTHALG>hmac-md5 { yylval.num = SADB_AALG_MD5HMAC; BEGIN INITIAL; return(ALG_AUTH); }
+<S_AUTHALG>hmac-sha1 { yylval.num = SADB_AALG_SHA1HMAC; BEGIN INITIAL; return(ALG_AUTH); }
+<S_AUTHALG>keyed-md5 { yylval.num = SADB_X_AALG_MD5; BEGIN INITIAL; return(ALG_AUTH); }
+<S_AUTHALG>keyed-sha1 { yylval.num = SADB_X_AALG_SHA; BEGIN INITIAL; return(ALG_AUTH); }
+<S_AUTHALG>hmac-sha2-256 { yylval.num = SADB_X_AALG_SHA2_256; BEGIN INITIAL; return(ALG_AUTH); }
+<S_AUTHALG>hmac-sha2-384 { yylval.num = SADB_X_AALG_SHA2_384; BEGIN INITIAL; return(ALG_AUTH); }
+<S_AUTHALG>hmac-sha2-512 { yylval.num = SADB_X_AALG_SHA2_512; BEGIN INITIAL; return(ALG_AUTH); }
+<S_AUTHALG>hmac-ripemd160 { yylval.num = SADB_X_AALG_RIPEMD160HMAC; BEGIN INITIAL; return(ALG_AUTH); }
+<S_AUTHALG>aes-xcbc-mac { yylval.num = SADB_X_AALG_AES_XCBC_MAC; BEGIN INITIAL; return(ALG_AUTH); }
+<S_AUTHALG>tcp-md5 { yylval.num = SADB_X_AALG_TCP_MD5; BEGIN INITIAL; return(ALG_AUTH); }
+<S_AUTHALG>null { yylval.num = SADB_X_AALG_NULL; BEGIN INITIAL; return(ALG_AUTH_NOKEY); }
+
+ /* encryption alogorithm */
+{hyphen}E { BEGIN S_ENCALG; return(F_ENC); }
+<S_ENCALG>des-cbc { yylval.num = SADB_EALG_DESCBC; BEGIN INITIAL; return(ALG_ENC); }
+<S_ENCALG>3des-cbc { yylval.num = SADB_EALG_3DESCBC; BEGIN INITIAL; return(ALG_ENC); }
+<S_ENCALG>null { yylval.num = SADB_EALG_NULL; BEGIN INITIAL; return(ALG_ENC_NOKEY); }
+<S_ENCALG>simple { yylval.num = SADB_EALG_NULL; BEGIN INITIAL; return(ALG_ENC_OLD); }
+<S_ENCALG>blowfish-cbc { yylval.num = SADB_X_EALG_BLOWFISHCBC; BEGIN INITIAL; return(ALG_ENC); }
+<S_ENCALG>cast128-cbc { yylval.num = SADB_X_EALG_CAST128CBC; BEGIN INITIAL; return(ALG_ENC); }
+<S_ENCALG>des-deriv { yylval.num = SADB_EALG_DESCBC; BEGIN INITIAL; return(ALG_ENC_DESDERIV); }
+<S_ENCALG>des-32iv { yylval.num = SADB_EALG_DESCBC; BEGIN INITIAL; return(ALG_ENC_DES32IV); }
+<S_ENCALG>rijndael-cbc { yylval.num = SADB_X_EALG_RIJNDAELCBC; BEGIN INITIAL; return(ALG_ENC); }
+<S_ENCALG>aes-ctr { yylval.num = SADB_X_EALG_AESCTR; BEGIN INITIAL; return(ALG_ENC); }
+<S_ENCALG>camellia-cbc { yylval.num = SADB_X_EALG_CAMELLIACBC; BEGIN INITIAL; return(ALG_ENC); }
+
+ /* compression algorithms */
+{hyphen}C { return(F_COMP); }
+oui { yylval.num = SADB_X_CALG_OUI; return(ALG_COMP); }
+deflate { yylval.num = SADB_X_CALG_DEFLATE; return(ALG_COMP); }
+lzs { yylval.num = SADB_X_CALG_LZS; return(ALG_COMP); }
+{hyphen}R { return(F_RAWCPI); }
+
+ /* extension */
+{hyphen}m { return(F_MODE); }
+transport { yylval.num = IPSEC_MODE_TRANSPORT; return(MODE); }
+tunnel { yylval.num = IPSEC_MODE_TUNNEL; return(MODE); }
+{hyphen}u { return(F_REQID); }
+{hyphen}f { return(F_EXT); }
+random-pad { yylval.num = SADB_X_EXT_PRAND; return(EXTENSION); }
+seq-pad { yylval.num = SADB_X_EXT_PSEQ; return(EXTENSION); }
+zero-pad { yylval.num = SADB_X_EXT_PZERO; return(EXTENSION); }
+nocyclic-seq { return(NOCYCLICSEQ); }
+{hyphen}r { return(F_REPLAY); }
+{hyphen}lh { return(F_LIFETIME_HARD); }
+{hyphen}ls { return(F_LIFETIME_SOFT); }
+
+ /* ... */
+any { return(ANY); }
+{ws} { }
+{nl} { lineno++; }
+{comment}
+{semi} { return(EOT); }
+
+ /* for address parameters: /prefix, [port] */
+{slash} { return SLASH; }
+{blcl} { return BLCL; }
+{elcl} { return ELCL; }
+
+ /* parameter */
+{decstring} {
+ char *bp;
+
+ yylval.ulnum = strtoul(yytext, &bp, 10);
+ return(DECSTRING);
+ }
+
+{hexstring} {
+ yylval.val.buf = strdup(yytext + 2);
+ if (!yylval.val.buf)
+ yyfatal("insufficient memory");
+ yylval.val.len = strlen(yylval.val.buf);
+
+ return(HEXSTRING);
+ }
+
+{quotedstring} {
+ char *p = yytext;
+ while (*++p != '"') ;
+ *p = '\0';
+ yytext++;
+ yylval.val.len = yyleng - 2;
+ yylval.val.buf = strdup(yytext);
+ if (!yylval.val.buf)
+ yyfatal("insufficient memory");
+
+ return(QUOTEDSTRING);
+ }
+
+[A-Za-z0-9:][A-Za-z0-9:%\.-]* {
+ yylval.val.len = yyleng;
+ yylval.val.buf = strdup(yytext);
+ if (!yylval.val.buf)
+ yyfatal("insufficient memory");
+ return(STRING);
+ }
+
+[0-9,]+ {
+ yylval.val.len = yyleng;
+ yylval.val.buf = strdup(yytext);
+ if (!yylval.val.buf)
+ yyfatal("insufficient memory");
+ return(STRING);
+ }
+
+. {
+ yyfatal("Syntax error");
+ /*NOTREACHED*/
+ }
+
+%%
+
+void
+yyfatal(s)
+ const char *s;
+{
+ yyerror(s);
+ exit(1);
+}
+
+void
+yyerror(s)
+ const char *s;
+{
+ printf("line %d: %s at [%s]\n", lineno, s, yytext);
+}
+
+int
+parse(fp)
+ FILE **fp;
+{
+ yyin = *fp;
+
+ parse_init();
+
+ if (yyparse()) {
+ printf("parse failed, line %d.\n", lineno);
+ return(-1);
+ }
+
+ return(0);
+}
diff --git a/sbin/setkey/vchar.h b/sbin/setkey/vchar.h
new file mode 100644
index 0000000..f3251c7
--- /dev/null
+++ b/sbin/setkey/vchar.h
@@ -0,0 +1,36 @@
+/* $FreeBSD$ */
+/* $KAME: vchar.h,v 1.2 2000/06/07 00:29:14 itojun Exp $ */
+
+/*
+ * Copyright (C) 1995, 1996, 1997, 1998, and 1999 WIDE Project.
+ * 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 project 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 PROJECT 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 PROJECT 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.
+ */
+
+typedef struct {
+ u_int len;
+ caddr_t buf;
+} vchar_t;
diff --git a/sbin/shutdown/Makefile b/sbin/shutdown/Makefile
new file mode 100644
index 0000000..905d1bc
--- /dev/null
+++ b/sbin/shutdown/Makefile
@@ -0,0 +1,13 @@
+# @(#)Makefile 8.1 (Berkeley) 6/5/93
+# $FreeBSD$
+
+PROG= shutdown
+MAN= shutdown.8
+LINKS= ${BINDIR}/shutdown ${BINDIR}/poweroff
+MLINKS= shutdown.8 poweroff.8
+
+BINOWN= root
+BINGRP= operator
+BINMODE=4554
+
+.include <bsd.prog.mk>
diff --git a/sbin/shutdown/shutdown.8 b/sbin/shutdown/shutdown.8
new file mode 100644
index 0000000..4145ba5
--- /dev/null
+++ b/sbin/shutdown/shutdown.8
@@ -0,0 +1,221 @@
+.\" Copyright (c) 1988, 1991, 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.
+.\" 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.
+.\"
+.\" @(#)shutdown.8 8.2 (Berkeley) 4/27/95
+.\" $FreeBSD$
+.\"
+.Dd December 15, 2014
+.Dt SHUTDOWN 8
+.Os
+.Sh NAME
+.Nm shutdown ,
+.Nm poweroff
+.Nd "close down the system at a given time"
+.Sh SYNOPSIS
+.Nm
+.Op Fl
+.Oo
+.Fl h | Fl p |
+.Fl r | Fl k
+.Oc
+.Oo
+.Fl o
+.Op Fl n
+.Oc
+.Ar time
+.Op Ar warning-message ...
+.Nm poweroff
+.Sh DESCRIPTION
+The
+.Nm
+utility provides an automated shutdown procedure for super-users
+to nicely notify users when the system is shutting down,
+saving them from system administrators, hackers, and gurus, who
+would otherwise not bother with such niceties.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl h
+The system is halted at the specified
+.Ar time .
+.It Fl p
+The system is halted and the power is turned off
+(hardware support required)
+at the specified
+.Ar time .
+.It Fl r
+The system is rebooted at the specified
+.Ar time .
+.It Fl k
+Kick everybody off.
+The
+.Fl k
+option
+does not actually halt the system, but leaves the
+system multi-user with logins disabled (for all but super-user).
+.It Fl o
+If one of the
+.Fl h ,
+.Fl p
+or
+.Fl r
+options are specified,
+.Nm
+will execute
+.Xr halt 8
+or
+.Xr reboot 8
+instead of sending a signal to
+.Xr init 8 .
+.It Fl n
+If the
+.Fl o
+option is specified, prevent the file system cache from being flushed by passing
+.Fl n
+to
+.Xr halt 8
+or
+.Xr reboot 8 .
+This option should probably not be used.
+.It Ar time
+.Ar Time
+is the time at which
+.Nm
+will bring the system down and
+may be the case-insensitive word
+.Ar now
+(indicating an immediate shutdown) or
+a future time in one of two formats:
+.Ar +number ,
+or
+.Ar yymmddhhmm ,
+where the year, month, and day may be defaulted
+to the current system values.
+The first form brings the system down in
+.Ar number
+minutes and the second at the absolute time specified.
+.Ar +number
+may be specified in units other than minutes by appending the corresponding
+suffix:
+.Dq Li s ,
+.Dq Li sec ,
+.Dq Li m ,
+.Dq Li min .
+.Dq Li h ,
+.Dq Li hour .
+.It Ar warning-message
+Any other arguments comprise the warning message that is broadcast
+to users currently logged into the system.
+.It Fl
+If
+.Sq Fl
+is supplied as an option, the warning message is read from the standard
+input.
+.El
+.Pp
+At intervals, becoming more frequent as apocalypse approaches
+and starting at ten hours before shutdown, warning messages are displayed
+on the terminals of all users logged in.
+Five minutes before
+shutdown, or immediately if shutdown is in less than 5 minutes,
+logins are disabled by creating
+.Pa /var/run/nologin
+and copying the
+warning message there.
+If this file exists when a user attempts to
+log in,
+.Xr login 1
+prints its contents and exits.
+The file is
+removed just before
+.Nm
+exits.
+.Pp
+At shutdown time a message is written to the system log, containing the
+time of shutdown, the person who initiated the shutdown and the reason.
+The corresponding signal is then sent to
+.Xr init 8
+to respectively halt, reboot or bring the system down to single-user state
+(depending on the above options).
+The time of the shutdown and the warning message
+are placed in
+.Pa /var/run/nologin
+and should be used to
+inform the users about when the system will be back up
+and why it is going down (or anything else).
+.Pp
+A scheduled shutdown can be canceled by killing the
+.Nm
+process (a
+.Dv SIGTERM
+should suffice).
+The
+.Pa /var/run/nologin
+file that
+.Nm
+created will be removed automatically.
+.Pp
+When run without options, the
+.Nm
+utility will place the system into single user mode at the
+.Ar time
+specified.
+.Pp
+Calling
+.Dq Nm poweroff
+is equivalent to running:
+.Bd -literal -offset indent
+shutdown -p now
+.Ed
+.Sh FILES
+.Bl -tag -width /var/run/nologin -compact
+.It Pa /var/run/nologin
+tells
+.Xr login 1
+not to let anyone log in
+.El
+.Sh EXAMPLES
+Reboot the system in 30 minutes and display a warning message on the terminals
+of all users currently logged in:
+.Pp
+.Dl # shutdown -r +30 \&"System will reboot\&"
+.Sh COMPATIBILITY
+The hours and minutes in the second time format may be separated by
+a colon (``:'') for backward compatibility.
+.Sh SEE ALSO
+.Xr kill 1 ,
+.Xr login 1 ,
+.Xr wall 1 ,
+.Xr nologin 5 ,
+.Xr halt 8 ,
+.Xr init 8 ,
+.Xr reboot 8
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Bx 4.0 .
diff --git a/sbin/shutdown/shutdown.c b/sbin/shutdown/shutdown.c
new file mode 100644
index 0000000..b04e0bd
--- /dev/null
+++ b/sbin/shutdown/shutdown.c
@@ -0,0 +1,559 @@
+/*
+ * Copyright (c) 1988, 1990, 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.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if 0
+#ifndef lint
+static const char copyright[] =
+"@(#) Copyright (c) 1988, 1990, 1993\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static char sccsid[] = "@(#)shutdown.c 8.4 (Berkeley) 4/28/95";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/syslog.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <pwd.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifdef DEBUG
+#undef _PATH_NOLOGIN
+#define _PATH_NOLOGIN "./nologin"
+#endif
+
+#define H *60*60
+#define M *60
+#define S *1
+#define NOLOG_TIME 5*60
+static struct interval {
+ int timeleft, timetowait;
+} tlist[] = {
+ { 10 H, 5 H },
+ { 5 H, 3 H },
+ { 2 H, 1 H },
+ { 1 H, 30 M },
+ { 30 M, 10 M },
+ { 20 M, 10 M },
+ { 10 M, 5 M },
+ { 5 M, 3 M },
+ { 2 M, 1 M },
+ { 1 M, 30 S },
+ { 30 S, 30 S },
+ { 0 , 0 }
+};
+#undef H
+#undef M
+#undef S
+
+static time_t offset, shuttime;
+static int dohalt, dopower, doreboot, killflg, mbuflen, oflag;
+static char mbuf[BUFSIZ];
+static const char *nosync, *whom;
+
+static void badtime(void);
+static void die_you_gravy_sucking_pig_dog(void);
+static void finish(int);
+static void getoffset(char *);
+static void loop(void);
+static void nolog(void);
+static void timeout(int);
+static void timewarn(int);
+static void usage(const char *);
+
+extern const char **environ;
+
+int
+main(int argc, char **argv)
+{
+ char *p, *endp;
+ struct passwd *pw;
+ int arglen, ch, len, readstdin;
+
+#ifndef DEBUG
+ if (geteuid())
+ errx(1, "NOT super-user");
+#endif
+
+ nosync = NULL;
+ readstdin = 0;
+
+ /*
+ * Test for the special case where the utility is called as
+ * "poweroff", for which it runs 'shutdown -p now'.
+ */
+ if ((p = strrchr(argv[0], '/')) == NULL)
+ p = argv[0];
+ else
+ ++p;
+ if (strcmp(p, "poweroff") == 0) {
+ if (getopt(argc, argv, "") != -1)
+ usage((char *)NULL);
+ argc -= optind;
+ argv += optind;
+ if (argc != 0)
+ usage((char *)NULL);
+ dopower = 1;
+ offset = 0;
+ (void)time(&shuttime);
+ goto poweroff;
+ }
+
+ while ((ch = getopt(argc, argv, "-hknopr")) != -1)
+ switch (ch) {
+ case '-':
+ readstdin = 1;
+ break;
+ case 'h':
+ dohalt = 1;
+ break;
+ case 'k':
+ killflg = 1;
+ break;
+ case 'n':
+ nosync = "-n";
+ break;
+ case 'o':
+ oflag = 1;
+ break;
+ case 'p':
+ dopower = 1;
+ break;
+ case 'r':
+ doreboot = 1;
+ break;
+ case '?':
+ default:
+ usage((char *)NULL);
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 1)
+ usage((char *)NULL);
+
+ if (killflg + doreboot + dohalt + dopower > 1)
+ usage("incompatible switches -h, -k, -p and -r");
+
+ if (oflag && !(dohalt || dopower || doreboot))
+ usage("-o requires -h, -p or -r");
+
+ if (nosync != NULL && !oflag)
+ usage("-n requires -o");
+
+ getoffset(*argv++);
+
+poweroff:
+ if (*argv) {
+ for (p = mbuf, len = sizeof(mbuf); *argv; ++argv) {
+ arglen = strlen(*argv);
+ if ((len -= arglen) <= 2)
+ break;
+ if (p != mbuf)
+ *p++ = ' ';
+ memmove(p, *argv, arglen);
+ p += arglen;
+ }
+ *p = '\n';
+ *++p = '\0';
+ }
+
+ if (readstdin) {
+ p = mbuf;
+ endp = mbuf + sizeof(mbuf) - 2;
+ for (;;) {
+ if (!fgets(p, endp - p + 1, stdin))
+ break;
+ for (; *p && p < endp; ++p);
+ if (p == endp) {
+ *p = '\n';
+ *++p = '\0';
+ break;
+ }
+ }
+ }
+ mbuflen = strlen(mbuf);
+
+ if (offset)
+ (void)printf("Shutdown at %.24s.\n", ctime(&shuttime));
+ else
+ (void)printf("Shutdown NOW!\n");
+
+ if (!(whom = getlogin()))
+ whom = (pw = getpwuid(getuid())) ? pw->pw_name : "???";
+
+#ifdef DEBUG
+ (void)putc('\n', stdout);
+#else
+ (void)setpriority(PRIO_PROCESS, 0, PRIO_MIN);
+ {
+ int forkpid;
+
+ forkpid = fork();
+ if (forkpid == -1)
+ err(1, "fork");
+ if (forkpid)
+ errx(0, "[pid %d]", forkpid);
+ }
+ setsid();
+#endif
+ openlog("shutdown", LOG_CONS, LOG_AUTH);
+ loop();
+ return(0);
+}
+
+static void
+loop(void)
+{
+ struct interval *tp;
+ u_int sltime;
+ int logged;
+
+ if (offset <= NOLOG_TIME) {
+ logged = 1;
+ nolog();
+ }
+ else
+ logged = 0;
+ tp = tlist;
+ if (tp->timeleft < offset)
+ (void)sleep((u_int)(offset - tp->timeleft));
+ else {
+ while (tp->timeleft && offset < tp->timeleft)
+ ++tp;
+ /*
+ * Warn now, if going to sleep more than a fifth of
+ * the next wait time.
+ */
+ if ((sltime = offset - tp->timeleft)) {
+ if (sltime > (u_int)(tp->timetowait / 5))
+ timewarn(offset);
+ (void)sleep(sltime);
+ }
+ }
+ for (;; ++tp) {
+ timewarn(tp->timeleft);
+ if (!logged && tp->timeleft <= NOLOG_TIME) {
+ logged = 1;
+ nolog();
+ }
+ (void)sleep((u_int)tp->timetowait);
+ if (!tp->timeleft)
+ break;
+ }
+ die_you_gravy_sucking_pig_dog();
+}
+
+static jmp_buf alarmbuf;
+
+static const char *restricted_environ[] = {
+ "PATH=" _PATH_STDPATH,
+ NULL
+};
+
+static void
+timewarn(int timeleft)
+{
+ static int first;
+ static char hostname[MAXHOSTNAMELEN + 1];
+ FILE *pf;
+ char wcmd[MAXPATHLEN + 4];
+
+ if (!first++)
+ (void)gethostname(hostname, sizeof(hostname));
+
+ /* undoc -n option to wall suppresses normal wall banner */
+ (void)snprintf(wcmd, sizeof(wcmd), "%s -n", _PATH_WALL);
+ environ = restricted_environ;
+ if (!(pf = popen(wcmd, "w"))) {
+ syslog(LOG_ERR, "shutdown: can't find %s: %m", _PATH_WALL);
+ return;
+ }
+
+ (void)fprintf(pf,
+ "\007*** %sSystem shutdown message from %s@%s ***\007\n",
+ timeleft ? "": "FINAL ", whom, hostname);
+
+ if (timeleft > 10*60)
+ (void)fprintf(pf, "System going down at %5.5s\n\n",
+ ctime(&shuttime) + 11);
+ else if (timeleft > 59)
+ (void)fprintf(pf, "System going down in %d minute%s\n\n",
+ timeleft / 60, (timeleft > 60) ? "s" : "");
+ else if (timeleft)
+ (void)fprintf(pf, "System going down in %s30 seconds\n\n",
+ (offset > 0 && offset < 30 ? "less than " : ""));
+ else
+ (void)fprintf(pf, "System going down IMMEDIATELY\n\n");
+
+ if (mbuflen)
+ (void)fwrite(mbuf, sizeof(*mbuf), mbuflen, pf);
+
+ /*
+ * play some games, just in case wall doesn't come back
+ * probably unnecessary, given that wall is careful.
+ */
+ if (!setjmp(alarmbuf)) {
+ (void)signal(SIGALRM, timeout);
+ (void)alarm((u_int)30);
+ (void)pclose(pf);
+ (void)alarm((u_int)0);
+ (void)signal(SIGALRM, SIG_DFL);
+ }
+}
+
+static void
+timeout(int signo __unused)
+{
+ longjmp(alarmbuf, 1);
+}
+
+static void
+die_you_gravy_sucking_pig_dog(void)
+{
+ char *empty_environ[] = { NULL };
+
+ syslog(LOG_NOTICE, "%s by %s: %s",
+ doreboot ? "reboot" : dohalt ? "halt" : dopower ? "power-down" :
+ "shutdown", whom, mbuf);
+
+ (void)printf("\r\nSystem shutdown time has arrived\007\007\r\n");
+ if (killflg) {
+ (void)printf("\rbut you'll have to do it yourself\r\n");
+ exit(0);
+ }
+#ifdef DEBUG
+ if (doreboot)
+ (void)printf("reboot");
+ else if (dohalt)
+ (void)printf("halt");
+ else if (dopower)
+ (void)printf("power-down");
+ if (nosync != NULL)
+ (void)printf(" no sync");
+ (void)printf("\nkill -HUP 1\n");
+#else
+ if (!oflag) {
+ (void)kill(1, doreboot ? SIGINT : /* reboot */
+ dohalt ? SIGUSR1 : /* halt */
+ dopower ? SIGUSR2 : /* power-down */
+ SIGTERM); /* single-user */
+ } else {
+ if (doreboot) {
+ execle(_PATH_REBOOT, "reboot", "-l", nosync,
+ (char *)NULL, empty_environ);
+ syslog(LOG_ERR, "shutdown: can't exec %s: %m.",
+ _PATH_REBOOT);
+ warn(_PATH_REBOOT);
+ }
+ else if (dohalt) {
+ execle(_PATH_HALT, "halt", "-l", nosync,
+ (char *)NULL, empty_environ);
+ syslog(LOG_ERR, "shutdown: can't exec %s: %m.",
+ _PATH_HALT);
+ warn(_PATH_HALT);
+ }
+ else if (dopower) {
+ execle(_PATH_HALT, "halt", "-l", "-p", nosync,
+ (char *)NULL, empty_environ);
+ syslog(LOG_ERR, "shutdown: can't exec %s: %m.",
+ _PATH_HALT);
+ warn(_PATH_HALT);
+ }
+ (void)kill(1, SIGTERM); /* to single-user */
+ }
+#endif
+ finish(0);
+}
+
+#define ATOI2(p) (p[0] - '0') * 10 + (p[1] - '0'); p += 2;
+
+static void
+getoffset(char *timearg)
+{
+ struct tm *lt;
+ char *p;
+ time_t now;
+ int this_year;
+ char *timeunit;
+
+ (void)time(&now);
+
+ if (!strcasecmp(timearg, "now")) { /* now */
+ offset = 0;
+ shuttime = now;
+ return;
+ }
+
+ if (*timearg == '+') { /* +minutes */
+ if (!isdigit(*++timearg))
+ badtime();
+ errno = 0;
+ offset = strtol(timearg, &timeunit, 10);
+ if (offset < 0 || offset == LONG_MAX || errno != 0)
+ badtime();
+ if (timeunit[0] == '\0' || strcasecmp(timeunit, "m") == 0 ||
+ strcasecmp(timeunit, "min") == 0 ||
+ strcasecmp(timeunit, "mins") == 0) {
+ offset *= 60;
+ } else if (strcasecmp(timeunit, "h") == 0 ||
+ strcasecmp(timeunit, "hour") == 0 ||
+ strcasecmp(timeunit, "hours") == 0) {
+ offset *= 60 * 60;
+ } else if (strcasecmp(timeunit, "s") == 0 ||
+ strcasecmp(timeunit, "sec") == 0 ||
+ strcasecmp(timeunit, "secs") == 0) {
+ offset *= 1;
+ } else {
+ badtime();
+ }
+ shuttime = now + offset;
+ return;
+ }
+
+ /* handle hh:mm by getting rid of the colon */
+ for (p = timearg; *p; ++p)
+ if (!isascii(*p) || !isdigit(*p)) {
+ if (*p == ':' && strlen(p) == 3) {
+ p[0] = p[1];
+ p[1] = p[2];
+ p[2] = '\0';
+ }
+ else
+ badtime();
+ }
+
+ unsetenv("TZ"); /* OUR timezone */
+ lt = localtime(&now); /* current time val */
+
+ switch(strlen(timearg)) {
+ case 10:
+ this_year = lt->tm_year;
+ lt->tm_year = ATOI2(timearg);
+ /*
+ * check if the specified year is in the next century.
+ * allow for one year of user error as many people will
+ * enter n - 1 at the start of year n.
+ */
+ if (lt->tm_year < (this_year % 100) - 1)
+ lt->tm_year += 100;
+ /* adjust for the year 2000 and beyond */
+ lt->tm_year += (this_year - (this_year % 100));
+ /* FALLTHROUGH */
+ case 8:
+ lt->tm_mon = ATOI2(timearg);
+ if (--lt->tm_mon < 0 || lt->tm_mon > 11)
+ badtime();
+ /* FALLTHROUGH */
+ case 6:
+ lt->tm_mday = ATOI2(timearg);
+ if (lt->tm_mday < 1 || lt->tm_mday > 31)
+ badtime();
+ /* FALLTHROUGH */
+ case 4:
+ lt->tm_hour = ATOI2(timearg);
+ if (lt->tm_hour < 0 || lt->tm_hour > 23)
+ badtime();
+ lt->tm_min = ATOI2(timearg);
+ if (lt->tm_min < 0 || lt->tm_min > 59)
+ badtime();
+ lt->tm_sec = 0;
+ if ((shuttime = mktime(lt)) == -1)
+ badtime();
+ if ((offset = shuttime - now) < 0)
+ errx(1, "that time is already past.");
+ break;
+ default:
+ badtime();
+ }
+}
+
+#define NOMSG "\n\nNO LOGINS: System going down at "
+static void
+nolog(void)
+{
+ int logfd;
+ char *ct;
+
+ (void)unlink(_PATH_NOLOGIN); /* in case linked to another file */
+ (void)signal(SIGINT, finish);
+ (void)signal(SIGHUP, finish);
+ (void)signal(SIGQUIT, finish);
+ (void)signal(SIGTERM, finish);
+ if ((logfd = open(_PATH_NOLOGIN, O_WRONLY|O_CREAT|O_TRUNC,
+ 0664)) >= 0) {
+ (void)write(logfd, NOMSG, sizeof(NOMSG) - 1);
+ ct = ctime(&shuttime);
+ (void)write(logfd, ct + 11, 5);
+ (void)write(logfd, "\n\n", 2);
+ (void)write(logfd, mbuf, strlen(mbuf));
+ (void)close(logfd);
+ }
+}
+
+static void
+finish(int signo __unused)
+{
+ if (!killflg)
+ (void)unlink(_PATH_NOLOGIN);
+ exit(0);
+}
+
+static void
+badtime(void)
+{
+ errx(1, "bad time format");
+}
+
+static void
+usage(const char *cp)
+{
+ if (cp != NULL)
+ warnx("%s", cp);
+ (void)fprintf(stderr,
+ "usage: shutdown [-] [-h | -p | -r | -k] [-o [-n]] time [warning-message ...]\n"
+ " poweroff\n");
+ exit(1);
+}
diff --git a/sbin/spppcontrol/Makefile b/sbin/spppcontrol/Makefile
new file mode 100644
index 0000000..3287fcf
--- /dev/null
+++ b/sbin/spppcontrol/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+PROG= spppcontrol
+MAN= spppcontrol.8
+WARNS?= 2
+
+.include <bsd.prog.mk>
diff --git a/sbin/spppcontrol/spppcontrol.8 b/sbin/spppcontrol/spppcontrol.8
new file mode 100644
index 0000000..4d948a6
--- /dev/null
+++ b/sbin/spppcontrol/spppcontrol.8
@@ -0,0 +1,275 @@
+.\" Copyright (C) 1997, 2001 by Joerg Wunsch, Dresden
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd December 30, 2001
+.Dt SPPPCONTROL 8
+.Os
+.Sh NAME
+.Nm spppcontrol
+.Nd display or set parameters for an sppp interface
+.Sh SYNOPSIS
+.Nm
+.Op Fl v
+.Ar ifname
+.Op Ar parameter Ns Op Li = Ns Ar value
+.Op Ar ...
+.Sh DESCRIPTION
+The
+.Xr sppp 4
+driver might require a number of additional arguments or optional
+parameters besides the settings that can be adjusted with
+.Xr ifconfig 8 .
+These are things like authentication protocol parameters, but also
+other tunable configuration variables.
+The
+.Nm
+utility can be used to display the current settings, or adjust these
+parameters as required.
+.Pp
+For whatever intent
+.Nm
+is being called, at least the parameter
+.Ar ifname
+needs to be specified, naming the interface for which the settings
+are to be performed or displayed.
+Use
+.Xr ifconfig 8 ,
+or
+.Xr netstat 1
+to see which interfaces are available.
+.Pp
+If no other parameter is given,
+.Nm
+will just list the current settings for
+.Ar ifname
+and exit.
+The reported settings include the current PPP phase the
+interface is in, which can be one of the names
+.Em dead ,
+.Em establish ,
+.Em authenticate ,
+.Em network ,
+or
+.Em terminate .
+If an authentication protocol is configured for the interface, the
+name of the protocol to be used, as well as the system name to be used
+or expected will be displayed, plus any possible options to the
+authentication protocol if applicable.
+Note that the authentication
+secrets (sometimes also called
+.Em keys )
+are not being returned by the underlying system call, and are thus not
+displayed.
+.Pp
+If any additional parameter is supplied, superuser privileges are
+required, and the command works in the
+.Dq set
+mode.
+This is normally done quietly, unless the option
+.Fl v
+is also enabled, which will cause a final printout of the settings as
+described above once all other actions have been taken.
+Use of this
+mode will be rejected if the interface is currently in any other phase
+than
+.Em dead .
+Note that you can force an interface into
+.Em dead
+phase by calling
+.Xr ifconfig 8
+with the parameter
+.Cm down .
+.Pp
+The currently supported parameters include:
+.Bl -tag -offset indent -width indent
+.It Va authproto Ns Li = Ns Ar protoname
+Set both, his and my authentication protocol to
+.Ar protoname .
+The protocol name can be one of
+.Dq Li chap ,
+.Dq Li pap ,
+or
+.Dq Li none .
+In the latter case, the use of an authentication protocol will be
+turned off for the named interface.
+This has the side-effect of
+clearing the other authentication-related parameters for this
+interface as well (i.e., system name and authentication secret will
+be forgotten).
+.It Va myauthproto Ns Li = Ns Ar protoname
+Same as above, but only for my end of the link.
+I.e., this is the
+protocol when remote is authenticator, and I am the peer required to
+authenticate.
+.It Va hisauthproto Ns Li = Ns Ar protoname
+Same as above, but only for his end of the link.
+.It Va myauthname Ns Li = Ns Ar name
+Set my system name for the authentication protocol.
+.It Va hisauthname Ns Li = Ns Ar name
+Set his system name for the authentication protocol.
+For CHAP, this
+will only be used as a hint, causing a warning message if remote did
+supply a different name.
+For PAP, it is the name remote must use to
+authenticate himself (in connection with his secret).
+.It Va myauthsecret Ns Li = Ns Ar secret
+Set my secret (key, password) for use in the authentication phase.
+For CHAP, this will be used to compute the response hash value, based
+on remote's challenge.
+For PAP, it will be transmitted as plain text
+together with the system name.
+Do not forget to quote the secrets from
+the shell if they contain shell metacharacters (or white space).
+.It Va myauthkey Ns Li = Ns Ar secret
+Same as above.
+.It Va hisauthsecret Ns Li = Ns Ar secret
+Same as above, to be used if we are an authenticator and the remote peer
+needs to authenticate.
+.It Va hisauthkey Ns Li = Ns Va secret
+Same as above.
+.It Va callin
+Require remote to authenticate himself only when he is calling in, but
+not when we are caller.
+This is required for some peers that do not
+implement the authentication protocols symmetrically (like Ascend
+routers, for example).
+.It Va always
+The opposite of
+.Va callin .
+Require remote to always authenticate, regardless of which side is
+placing the call.
+This is the default, and will not be explicitly
+displayed in the
+.Dq list
+mode.
+.It Va norechallenge
+Only meaningful with CHAP.
+Do not re-challenge peer once the initial
+CHAP handshake was successful.
+Used to work around broken peer
+implementations that cannot grok being re-challenged once the
+connection is up.
+.It Ar rechallenge
+With CHAP, send re-challenges at random intervals while the connection
+is in network phase.
+(The intervals are currently in the range of 300
+through approximately 800 seconds.)
+This is the default, and will not
+be explicitly displayed in the
+.Dq list
+mode.
+.It Va lcp-timeout Ns Li = Ns Ar timeout-value
+Allows to change the value of the LCP restart timer.
+Values are
+specified in milliseconds.
+The value must be between 10 and 20000 ms,
+defaulting to 3000 ms.
+.It Va enable-vj
+Enable negotiation of Van Jacobsen header compression.
+(Enabled by default.)
+.It Va disable-vj
+Disable negotiation of Van Jacobsen header compression.
+.It Va enable-ipv6
+Enable negotiation of the IPv6 network control protocol.
+(Enabled by default if the kernel has IPv6 enabled.)
+.It Va disable-ipv6
+Disable negotiation of the IPv6 network control protocol.
+Since every
+IPv4 interface in an IPv6-enabled kernel automatically gets an IPv6
+address assigned, this option provides for a way to administratively
+prevent the link from attempting to negotiate IPv6.
+Note that
+initialization of an IPv6 interface causes a multicast packet to be
+sent, which can cause unwanted traffic costs (for dial-on-demand
+interfaces).
+.El
+.Sh EXAMPLES
+.Bd -literal
+# spppcontrol bppp0
+bppp0: phase=dead
+ myauthproto=chap myauthname="uriah"
+ hisauthproto=chap hisauthname="ifb-gw" norechallenge
+ lcp-timeout=3000
+ enable-vj
+ enable-ipv6
+.Ed
+.Pp
+Display the settings for
+.Li bppp0 .
+The interface is currently in
+.Em dead
+phase, i.e., the LCP layer is down, and no traffic is possible.
+Both
+ends of the connection use the CHAP protocol, my end tells remote the
+system name
+.Dq Li uriah ,
+and remote is expected to authenticate by the name
+.Dq Li ifb-gw .
+Once the initial CHAP handshake was successful, no further CHAP
+challenges will be transmitted.
+There are supposedly some known CHAP
+secrets for both ends of the link which are not being shown.
+.Bd -literal
+# spppcontrol bppp0 \e
+ authproto=chap \e
+ myauthname=uriah myauthsecret='some secret' \e
+ hisauthname=ifb-gw hisauthsecret='another' \e
+ norechallenge
+.Ed
+.Pp
+A possible call to
+.Nm
+that could have been used to bring the interface into the state shown
+by the previous example.
+.Sh SEE ALSO
+.Xr netstat 1 ,
+.Xr sppp 4 ,
+.Xr ifconfig 8
+.Rs
+.%A B. Lloyd
+.%A W. Simpson
+.%T "PPP Authentication Protocols"
+.%O RFC 1334
+.Re
+.Rs
+.%A W. Simpson, Editor
+.%T "The Point-to-Point Protocol (PPP)"
+.%O RFC 1661
+.Re
+.Rs
+.%A W. Simpson
+.%T "PPP Challenge Handshake Authentication Protocol (CHAP)"
+.%O RFC 1994
+.Re
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Fx 3.0 .
+.Sh AUTHORS
+The program was written by
+.An J\(:org Wunsch ,
+Dresden.
diff --git a/sbin/spppcontrol/spppcontrol.c b/sbin/spppcontrol/spppcontrol.c
new file mode 100644
index 0000000..9cb84ab
--- /dev/null
+++ b/sbin/spppcontrol/spppcontrol.c
@@ -0,0 +1,264 @@
+/*
+ * Copyright (c) 1997, 2001 Joerg Wunsch
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``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 DEVELOPERS 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 <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <net/if.h>
+#include <net/if_sppp.h>
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+static void usage(void);
+void print_vals(const char *ifname, struct spppreq *sp);
+const char *phase_name(enum ppp_phase phase);
+const char *proto_name(u_short proto);
+const char *authflags(u_short flags);
+
+#define PPP_PAP 0xc023
+#define PPP_CHAP 0xc223
+
+int
+main(int argc, char **argv)
+{
+ int s, c;
+ int errs = 0, verbose = 0;
+ size_t off;
+ long to;
+ char *endp;
+ const char *ifname, *cp;
+ struct ifreq ifr;
+ struct spppreq spr;
+
+ while ((c = getopt(argc, argv, "v")) != -1)
+ switch (c) {
+ case 'v':
+ verbose++;
+ break;
+
+ default:
+ errs++;
+ break;
+ }
+ argv += optind;
+ argc -= optind;
+
+ if (errs || argc < 1)
+ usage();
+
+ ifname = argv[0];
+ strncpy(ifr.ifr_name, ifname, sizeof ifr.ifr_name);
+
+ /* use a random AF to create the socket */
+ if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
+ err(EX_UNAVAILABLE, "ifconfig: socket");
+
+ argc--;
+ argv++;
+
+ spr.cmd = (uintptr_t) SPPPIOGDEFS;
+ ifr.ifr_data = (caddr_t)&spr;
+
+ if (ioctl(s, SIOCGIFGENERIC, &ifr) == -1)
+ err(EX_OSERR, "SIOCGIFGENERIC(SPPPIOGDEFS)");
+
+ if (argc == 0) {
+ /* list only mode */
+ print_vals(ifname, &spr);
+ return 0;
+ }
+
+#define startswith(s) strncmp(argv[0], s, (off = strlen(s))) == 0
+
+ while (argc > 0) {
+ if (startswith("authproto=")) {
+ cp = argv[0] + off;
+ if (strcmp(cp, "pap") == 0)
+ spr.defs.myauth.proto =
+ spr.defs.hisauth.proto = PPP_PAP;
+ else if (strcmp(cp, "chap") == 0)
+ spr.defs.myauth.proto =
+ spr.defs.hisauth.proto = PPP_CHAP;
+ else if (strcmp(cp, "none") == 0)
+ spr.defs.myauth.proto =
+ spr.defs.hisauth.proto = 0;
+ else
+ errx(EX_DATAERR, "bad auth proto: %s", cp);
+ } else if (startswith("myauthproto=")) {
+ cp = argv[0] + off;
+ if (strcmp(cp, "pap") == 0)
+ spr.defs.myauth.proto = PPP_PAP;
+ else if (strcmp(cp, "chap") == 0)
+ spr.defs.myauth.proto = PPP_CHAP;
+ else if (strcmp(cp, "none") == 0)
+ spr.defs.myauth.proto = 0;
+ else
+ errx(EX_DATAERR, "bad auth proto: %s", cp);
+ } else if (startswith("myauthname="))
+ strncpy(spr.defs.myauth.name, argv[0] + off,
+ AUTHNAMELEN);
+ else if (startswith("myauthsecret=") ||
+ startswith("myauthkey="))
+ strncpy(spr.defs.myauth.secret, argv[0] + off,
+ AUTHKEYLEN);
+ else if (startswith("hisauthproto=")) {
+ cp = argv[0] + off;
+ if (strcmp(cp, "pap") == 0)
+ spr.defs.hisauth.proto = PPP_PAP;
+ else if (strcmp(cp, "chap") == 0)
+ spr.defs.hisauth.proto = PPP_CHAP;
+ else if (strcmp(cp, "none") == 0)
+ spr.defs.hisauth.proto = 0;
+ else
+ errx(EX_DATAERR, "bad auth proto: %s", cp);
+ } else if (startswith("hisauthname="))
+ strncpy(spr.defs.hisauth.name, argv[0] + off,
+ AUTHNAMELEN);
+ else if (startswith("hisauthsecret=") ||
+ startswith("hisauthkey="))
+ strncpy(spr.defs.hisauth.secret, argv[0] + off,
+ AUTHKEYLEN);
+ else if (strcmp(argv[0], "callin") == 0)
+ spr.defs.hisauth.flags |= AUTHFLAG_NOCALLOUT;
+ else if (strcmp(argv[0], "always") == 0)
+ spr.defs.hisauth.flags &= ~AUTHFLAG_NOCALLOUT;
+ else if (strcmp(argv[0], "norechallenge") == 0)
+ spr.defs.hisauth.flags |= AUTHFLAG_NORECHALLENGE;
+ else if (strcmp(argv[0], "rechallenge") == 0)
+ spr.defs.hisauth.flags &= ~AUTHFLAG_NORECHALLENGE;
+ else if (startswith("lcp-timeout=")) {
+ cp = argv[0] + off;
+ to = strtol(cp, &endp, 10);
+ if (*cp == '\0' || *endp != '\0' ||
+ /*
+ * NB: 10 ms is the minimal possible value for
+ * hz=100. We assume no kernel has less clock
+ * frequency than that...
+ */
+ to < 10 || to > 20000)
+ errx(EX_DATAERR, "bad lcp timeout value: %s",
+ cp);
+ spr.defs.lcp.timeout = to;
+ } else if (strcmp(argv[0], "enable-vj") == 0)
+ spr.defs.enable_vj = 1;
+ else if (strcmp(argv[0], "disable-vj") == 0)
+ spr.defs.enable_vj = 0;
+ else if (strcmp(argv[0], "enable-ipv6") == 0)
+ spr.defs.enable_ipv6 = 1;
+ else if (strcmp(argv[0], "disable-ipv6") == 0)
+ spr.defs.enable_ipv6 = 0;
+ else
+ errx(EX_DATAERR, "bad parameter: \"%s\"", argv[0]);
+
+ argv++;
+ argc--;
+ }
+
+ spr.cmd = (uintptr_t)SPPPIOSDEFS;
+
+ if (ioctl(s, SIOCSIFGENERIC, &ifr) == -1)
+ err(EX_OSERR, "SIOCSIFGENERIC(SPPPIOSDEFS)");
+
+ if (verbose)
+ print_vals(ifname, &spr);
+
+ return 0;
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "%s\n%s\n",
+ "usage: spppcontrol [-v] ifname [{my|his}auth{proto|name|secret}=...]",
+ " spppcontrol [-v] ifname callin|always");
+ exit(EX_USAGE);
+}
+
+void
+print_vals(const char *ifname, struct spppreq *sp)
+{
+ printf("%s:\tphase=%s\n", ifname, phase_name(sp->defs.pp_phase));
+ if (sp->defs.myauth.proto) {
+ printf("\tmyauthproto=%s myauthname=\"%.*s\"\n",
+ proto_name(sp->defs.myauth.proto),
+ AUTHNAMELEN, sp->defs.myauth.name);
+ }
+ if (sp->defs.hisauth.proto) {
+ printf("\thisauthproto=%s hisauthname=\"%.*s\"%s\n",
+ proto_name(sp->defs.hisauth.proto),
+ AUTHNAMELEN, sp->defs.hisauth.name,
+ authflags(sp->defs.hisauth.flags));
+ }
+ printf("\tlcp-timeout=%d ms\n", sp->defs.lcp.timeout);
+ printf("\t%sable-vj\n", sp->defs.enable_vj? "en": "dis");
+ printf("\t%sable-ipv6\n", sp->defs.enable_ipv6? "en": "dis");
+}
+
+const char *
+phase_name(enum ppp_phase phase)
+{
+ switch (phase) {
+ case PHASE_DEAD: return "dead";
+ case PHASE_ESTABLISH: return "establish";
+ case PHASE_TERMINATE: return "terminate";
+ case PHASE_AUTHENTICATE: return "authenticate";
+ case PHASE_NETWORK: return "network";
+ }
+ return "illegal";
+}
+
+const char *
+proto_name(u_short proto)
+{
+ static char buf[12];
+ switch (proto) {
+ case PPP_PAP: return "pap";
+ case PPP_CHAP: return "chap";
+ }
+ sprintf(buf, "0x%x", (unsigned)proto);
+ return buf;
+}
+
+const char *
+authflags(u_short flags)
+{
+ static char buf[30];
+ buf[0] = '\0';
+ if (flags & AUTHFLAG_NOCALLOUT)
+ strcat(buf, " callin");
+ if (flags & AUTHFLAG_NORECHALLENGE)
+ strcat(buf, " norechallenge");
+ return buf;
+}
diff --git a/sbin/sunlabel/Makefile b/sbin/sunlabel/Makefile
new file mode 100644
index 0000000..21ccfd7
--- /dev/null
+++ b/sbin/sunlabel/Makefile
@@ -0,0 +1,22 @@
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../../sys/geom
+
+PROG= sunlabel
+SRCS= sunlabel.c geom_sunlabel_enc.c
+MAN= sunlabel.8
+
+.if ${MACHINE_CPUARCH} == "sparc64"
+LINKS= ${BINDIR}/sunlabel ${BINDIR}/disklabel
+MLINKS= sunlabel.8 disklabel.8
+.endif
+
+LIBADD= geom
+
+.include <bsd.prog.mk>
+
+test: ${PROG}
+ sh ${.CURDIR}/runtest.sh
+
+testx: ${PROG}
+ sh -x ${.CURDIR}/runtest.sh
diff --git a/sbin/sunlabel/runtest.sh b/sbin/sunlabel/runtest.sh
new file mode 100644
index 0000000..73833e0
--- /dev/null
+++ b/sbin/sunlabel/runtest.sh
@@ -0,0 +1,157 @@
+#!/bin/sh
+# $FreeBSD$
+
+TMP=/tmp/$$.
+set -e
+MD=`mdconfig -a -t malloc -s 2m`
+trap "exec 7</dev/null; rm -f ${TMP}* ; mdconfig -d -u ${MD}" EXIT INT TERM
+
+./sunlabel -r -w $MD auto
+
+dd if=/dev/$MD of=${TMP}i0 count=16 > /dev/null 2>&1
+./sunlabel $MD > ${TMP}l0
+
+sed '
+/ c:/{
+p
+s/c:/a:/
+s/3969/1024/
+}
+' ${TMP}l0 > ${TMP}l1
+
+./sunlabel -R $MD ${TMP}l1
+if [ -c /dev/${MD}a ] ; then
+ echo "PASS: Created a: partition" 1>&2
+else
+ echo "FAIL: Did not create a: partition" 1>&2
+ exit 2
+fi
+
+# Spoil and rediscover
+
+true > /dev/${MD}
+if [ -c /dev/${MD}a ] ; then
+ echo "PASS: Recreated a: partition after spoilage" 1>&2
+else
+ echo "FAIL: Did not recreate a: partition after spoilage" 1>&2
+ exit 2
+fi
+
+dd if=/dev/$MD of=${TMP}i1 count=16 > /dev/null 2>&1
+sed '
+/ c:/{
+p
+s/c:/a:/
+s/3969/2048/
+}
+' ${TMP}l0 > ${TMP}l2
+
+./sunlabel -R $MD ${TMP}l2
+dd if=/dev/$MD of=${TMP}i2 count=16 > /dev/null 2>&1
+
+exec 7< /dev/${MD}a
+
+for t in a c
+do
+ if dd if=${TMP}i2 of=/dev/${MD}$t 2>/dev/null ; then
+ echo "PASS: Could rewrite same label to ...$t while ...a open" 1>&2
+ else
+ echo "FAIL: Could not rewrite same label to ...$t while ...a open" 1>&2
+ exit 2
+ fi
+
+ if dd if=${TMP}i1 of=/dev/${MD}$t 2>/dev/null ; then
+ echo "FAIL: Could label with smaller ...a to ...$t while ...a open" 1>&2
+ exit 2
+ else
+ echo "PASS: Could not label with smaller ...a to ...$t while ...a open" 1>&2
+ fi
+
+ if dd if=${TMP}i0 of=/dev/${MD}$t 2>/dev/null ; then
+ echo "FAIL: Could write label missing ...a to ...$t while ...a open" 1>&2
+ exit 2
+ else
+ echo "PASS: Could not write label missing ...a to ...$t while ...a open" 1>&2
+ fi
+done
+
+exec 7< /dev/null
+
+if dd if=${TMP}i0 of=/dev/${MD}c 2>/dev/null ; then
+ echo "PASS: Could write missing ...a label to ...c" 1>&2
+else
+ echo "FAIL: Could not write missing ...a label to ...c" 1>&2
+ exit 2
+fi
+
+if dd if=${TMP}i2 of=/dev/${MD}c 2>/dev/null ; then
+ echo "PASS: Could write large ...a label to ...c" 1>&2
+else
+ echo "FAIL: Could not write large ...a label to ...c" 1>&2
+ exit 2
+fi
+
+if dd if=${TMP}i1 of=/dev/${MD}c 2>/dev/null ; then
+ echo "PASS: Could write small ...a label to ...c" 1>&2
+else
+ echo "FAIL: Could not write small ...a label to ...c" 1>&2
+ exit 2
+fi
+
+if dd if=${TMP}i2 of=/dev/${MD}a 2>/dev/null ; then
+ echo "PASS: Could increase size of ...a by writing to ...a" 1>&2
+else
+ echo "FAIL: Could not increase size of ...a by writing to ...a" 1>&2
+ exit 2
+fi
+
+if dd if=${TMP}i1 of=/dev/${MD}a 2>/dev/null ; then
+ echo "FAIL: Could decrease size of ...a by writing to ...a" 1>&2
+ exit 2
+else
+ echo "PASS: Could not decrease size of ...a by writing to ...a" 1>&2
+fi
+
+if dd if=${TMP}i0 of=/dev/${MD}a 2>/dev/null ; then
+ echo "FAIL: Could delete ...a by writing to ...a" 1>&2
+ exit 2
+else
+ echo "PASS: Could not delete ...a by writing to ...a" 1>&2
+fi
+
+if ./sunlabel -B -b ${TMP}i0 ${MD} ; then
+ if [ ! -c /dev/${MD}a ] ; then
+ echo "FAILED: Writing bootcode killed ...a" 1>&2
+ exit 2
+ else
+ echo "PASS: Could write bootcode while closed" 1>&2
+ fi
+else
+ echo "FAILED: Could not write bootcode while closed" 1>&2
+ exit 2
+fi
+
+exec 7> /dev/${MD}c
+if ktrace ./sunlabel -B -b ${TMP}i0 ${MD} ; then
+ if [ ! -c /dev/${MD}a ] ; then
+ echo "FAILED: Writing bootcode killed ...a" 1>&2
+ exit 2
+ else
+ echo "PASS: Could write bootcode while open" 1>&2
+ fi
+else
+ echo "FAILED: Could not write bootcode while open" 1>&2
+ exit 2
+fi
+exec 7> /dev/null
+
+if dd if=${TMP}i0 of=/dev/${MD}c 2>/dev/null ; then
+ echo "PASS: Could delete ...a by writing to ...c" 1>&2
+else
+ echo "FAIL: Could not delete ...a by writing to ...c" 1>&2
+ exit 2
+fi
+
+# XXX: need to add a 'b' partition and check for overlaps.
+
+exit 0
diff --git a/sbin/sunlabel/sunlabel.8 b/sbin/sunlabel/sunlabel.8
new file mode 100644
index 0000000..fc6afa4
--- /dev/null
+++ b/sbin/sunlabel/sunlabel.8
@@ -0,0 +1,432 @@
+.\" Copyright (c) 2004
+.\" David E. O'Brien. All rights reserved.
+.\" Copyright (c) 2004, 2005
+.\" Joerg Wunsch. 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd March 30, 2005
+.Dt SUNLABEL 8
+.Os
+.Sh NAME
+.Nm sunlabel
+.Nd read and write disk pack label suitable for Sun's OpenBoot PROM
+.Sh SYNOPSIS
+.Nm
+.Op Fl r
+.Op Fl c No \&| Fl h
+.Ar disk
+.Nm
+.Fl B
+.Op Fl b Ar boot1
+.Op Fl n
+.Ar disk
+.Nm
+.Fl R
+.Op Fl B Op Fl b Ar boot1
+.Op Fl r
+.Op Fl n
+.Op Fl c
+.Ar disk protofile
+.Nm
+.Fl e
+.Op Fl B Op Fl b Ar boot1
+.Op Fl r
+.Op Fl n
+.Op Fl c
+.Ar disk
+.Nm
+.Fl w
+.Op Fl B Op Fl b Ar boot1
+.Op Fl r
+.Op Fl n
+.Op Fl c
+.Ar disk type
+.Sh DESCRIPTION
+The
+.Nm
+utility
+installs, examines or modifies the
+.Em Sun OpenBoot PROM
+label on a disk.
+In addition,
+.Nm
+can install bootstrap code.
+.Ss Introduction
+The label occupies the first sector (i.e., 512 bytes) of each disk.
+It starts with a textual description which by convention also mentions
+the disk geometry in textual form (number of cylinders, alternate
+cylinders, heads, and sectors per track), optionally followed by a
+table of SVR4-compatible VTOC tags and flags per partition, followed
+by the partition table itself.
+Finally, a checksum is recorded to ensure the label has not been
+tampered with.
+.Pp
+The
+.Em Sun OpenBoot PROM
+label allows for 8 disk partitions.
+The partition table lists the starting cylinder of the partition,
+plus the size of the partition in 512-byte sectors.
+Thus, partitions in the
+.Em Sun OpenBoot PROM
+must always start at a cylinder boundary (for whatever geometry
+emulation has been chosen).
+.Pp
+The optional SVR4-compatible VTOC tag and flags table is not used
+by the
+.Fx
+kernel.
+It is maintained solely for compatibility with the
+.Tn Solaris
+operating system that might share disks with
+.Fx
+on the same hardware platform.
+.Pp
+The
+.Em Sun OpenBoot PROM
+label is natively understood by the underlying hardware, which can
+bootstrap from a single partition entry, as opposed to the very first
+block(s) of the entire disk as on many other hardware platforms.
+.Pp
+Note that the hardware platform mandates that two cylinders are set
+aside as
+.Em alternate cylinders
+which are not available to user programs (and not even through the
+.Dq Li backup
+partition).
+.Ss Options
+Options are listed in alphabetical order here.
+Note that only those option combinations listed under
+.Sx SYNOPSIS
+are allowable.
+.Bl -tag -width ".Fl b Ar bootpath"
+.It Fl b Ar bootpath
+Specify that
+.Ar bootpath
+is to be used as the boot image, rather than the default of
+.Pa /boot/boot1 .
+.It Fl B
+Install bootstrap code onto the disk.
+Note that since the underlying hardware platform bootstraps from
+partitions, not disks, this operation is only useful if there is
+a partition starting at offset 0.
+.It Fl c
+Use cylinders for partition size display rather than
+(512-byte) sectors.
+This also changes the default interpretation of the partition
+size entries when editing the label, or reading from a prototype
+file.
+Thus, prototype files are only compatible when both, obtaining
+the file and re-installing it is done using the same
+.Fl c
+option setting.
+.It Fl e
+Enter edit mode.
+See
+.Sx Edit mode
+below for a more detailed explanation.
+.It Fl h
+When displaying the label, make the partition size and offset
+values
+.Dq human readable .
+The displayed numbers will get a suffix of
+.Ql B
+for bytes,
+.Ql K
+for 1024 bytes each,
+.Ql M
+for 1048576 bytes each, or
+.Ql G
+for 1073741824 bytes each appended.
+Note that due to possible rounding errors, prototype files
+obtained using the
+.Fl h
+option are not suited for re-installing using the
+.Fl R
+option.
+.It Fl n
+No changes.
+All operations, checks etc., are performed normally, but nothing
+is written to disk.
+.It Fl r
+Obsolete option that used to indicate that the operation should
+be done directly on disk, as opposed through the respective kernel
+services.
+Ignored.
+.It Fl R
+Restore label from the prototype in
+.Ar protofile .
+A prototype file is simply the textual representation of the
+label as printed using the first form of the
+.Nm
+utility shown in the
+.Sx SYNOPSIS .
+Note that the
+.Fl c
+option used to obtain the prototype must match the option used
+when restoring the label (both present, or both absent).
+.It Fl w
+Write mode.
+Suitable to write an initial label to disk.
+The
+.Ar type
+argument used to be an entry into a table of predefined labels,
+but this functionality is not supported by
+.Nm .
+Instead, the only allowable
+.Ar type
+argument is the string
+.Dq Li auto ,
+indicating that an automatically created label should be written
+to disk.
+This automatism will try to create an initial label that fits as
+best as possible into the available disk capacity.
+.El
+.Pp
+If neither of the
+.Fl e , R ,
+or
+.Fl w
+options are present, the existing label for
+.Ar disk
+will be printed to standard output.
+.Pp
+The
+.Ar disk
+argument
+must be given as a plain disk name, without any leading
+.Pa /dev/ .
+.Ss Edit mode
+In edit mode, the existing label from
+.Ar disk
+will be read, and put into a template file.
+The command referenced by the
+.Ev EDITOR
+environmental variable will be started to allow the user
+to edit the label.
+The label is then checked and examined for any errors.
+If no errors have been found, the new label is written to disk.
+If there were any errors, a message is printed to standard
+error output, and the user is given the opportunity to edit
+the template file again.
+If accepted, editing starts over.
+If declined, no changes will
+be written to disk.
+.Pp
+The label presented for editing is the same as the standard
+printout, with some added hints about the possible options to
+specify the sector size and starting cylinder.
+The following areas in the template can be edited:
+.Bl -tag -width indent
+.It Sy Textual label, geometry emulation
+The line
+.D1 Li text: Ar XXXX Li cyl Ar CC Li alt 2 hd Ar HH Li sec Ar SS
+represents the label text.
+It must be retained exactly in the form shown.
+The editable text
+.Ar XXXX
+is a simple (non-whitespace) text describing the disk.
+By convention, this text mentions the approximate size of the
+disk, as in
+.Dq Li SUN9.0G
+for a 9 GB disk shipped by Sun.
+.Pp
+The values
+.Ar CC ,
+.Ar HH ,
+and
+.Ar SS
+describe the number of cylinders, heads (tracks per
+cylinder), and sectors per track respectively.
+They might be modified to change the geometry emulation.
+Each number must be between 1 and 65535.
+The product
+.D1 Em (CC + 2) * HH * SS
+must be less than or equal to the total number of sectors of the
+disk (which is given as a hint in a comment field).
+.It Sy Volume name
+The volume name (if present) is introduced by the string
+.Dq "volume name:" .
+It can be up to 8 characters long, and might be useful to distinguish
+different disks in a system.
+Note that volume names require the VTOC elements to be present, so
+any of the VTOC constraints described below need to be obeyed as well
+if a volume name is to be set.
+Setting an empty volume name will delete it from the label.
+.It Sy Partition entries
+Partition entries start with a letter from
+.Ql a
+through
+.Ql h ,
+immediately followed by a colon, followed by the size of this
+partition, and the starting cylinder of the partition.
+The unit of the size field defaults to sectors, or to cylinders
+if the
+.Fl c
+option is in effect.
+Alternatively, a different unit may be specified by appending
+.Ql s
+for (512-byte) sectors,
+.Ql c
+for cylinders,
+.Ql k
+for kilobytes,
+.Ql m
+for megabytes, or
+.Ql g
+for gigabytes.
+The last partition entry may specify the size as
+.Ql *
+to indicate that this entry should consume the rest of disk not
+consumed by any other partition so far.
+.Pp
+The start of partition is always taken as a cylinder number (starting
+at 0) since this is what the underlying hardware uses.
+Alternatively, specifying it as
+.Ql *
+will make the computation automatically chose the nearest possible
+cylinder boundary.
+.Pp
+Partition
+.Ql c
+must always be present, must start at 0, and must cover the entire
+disk (without considering the alternate cylinders though).
+.Pp
+Optionally, each partition entry may be followed by an SVR4-compatible
+VTOC tag name, and a flag description.
+The following VTOC tag names are known:
+.Bl -column -offset indent ".Li unassigned" ".Sy value" ".Sy comment"
+.It Sy name Ta Sy value Ta Sy comment
+.It Li unassigned Ta No 0x00 Ta \&
+.It Li boot Ta No 0x01 Ta \&
+.It Li root Ta No 0x02 Ta \&
+.It Li swap Ta No 0x03 Ta \&
+.It Li usr Ta No 0x04 Ta \&
+.It Li backup Ta No 0x05 Ta c partition, entire disk
+.It Li stand Ta No 0x06 Ta \&
+.It Li var Ta No 0x07 Ta \&
+.It Li home Ta No 0x08 Ta \&
+.It Li altsctr Ta No 0x09 Ta alternate sector partition
+.It Li cache Ta No 0x0a Ta Solaris cachefs partition
+.It Li VxVM_pub Ta No 0x0e Ta VxVM public region
+.It Li VxVM_priv Ta No 0x0f Ta VxVM private region
+.El
+.Pp
+The following VTOC flags are known:
+.Bl -column -offset indent ".Sy name" ".Sy value" ".Sy comment"
+.It Sy name Ta Sy value Ta Sy comment
+.It Li wm Ta No 0x00 Ta read/write, mountable
+.It Li wu Ta No 0x01 Ta read/write, unmountable
+.It Li rm Ta No 0x10 Ta read/only, mountable
+.It Li ru Ta No 0x11 Ta read/only, unmountable
+.El
+.Pp
+Optionally, both the tag and/or the flag name may be specified
+numerically, using standard
+.Ql C
+numerical notation (prefix
+.Ql 0x
+for hexadecimal numbers,
+.Ql 0
+for octal numbers).
+If the flag field is omitted, it defaults to
+.Ql wm .
+If the tag field is also omitted, it defaults to
+.Dq Li unassigned .
+If none of the partitions lists any VTOC tag/flags, no
+SVR4-compatible VTOC elements will be written to disk.
+If VTOC-style elements are present, partition
+.Ql c
+must be marked as
+.Dq Li backup
+(and should be marked
+.Ql wu ) .
+.El
+.Pp
+When checking the label, partition
+.Ql c
+is checked for presence, and for the mentioned restrictions.
+All other partitions are checked for possible overlaps, as
+well as for not extending past the end of unit.
+If VTOC-style elements are present, overlaps of unmountable
+partitions against other partitions will be warned still but
+do not cause a rejection of the label.
+That way,
+.Em encapsulated disks
+of volume management software are acceptable as long as the
+volume management partitions are clearly marked as unmountable.
+.Pp
+Any other fields in the label template are informational only,
+and will not be parsed when reading the label.
+.Pp
+Note that when changing the geometry emulation by editing the
+textual description line, all partition entries will be
+considered based on the new geometry emulation.
+.Sh ENVIRONMENT
+.Bl -tag -width ".Ev EDITOR" -compact
+.It Ev EDITOR
+Name of the command to edit the template file in edit-mode.
+Defaults to
+.Xr vi 1 .
+.El
+.Sh FILES
+.Bl -tag -width ".Pa /boot/boot1" -compact
+.It Pa /boot/boot1
+Default boot image.
+.El
+.Sh SEE ALSO
+.Xr vi 1 ,
+.Xr geom 4 ,
+.Xr bsdlabel 8
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Fx 5.1 .
+.Sh AUTHORS
+The
+.Nm
+utility was written by
+.An Jake Burkholder ,
+modeling it after the
+.Xr bsdlabel 8
+command available on other architectures.
+.Pp
+.An -nosplit
+This man page was initially written by
+.An David O'Brien ,
+and later substantially updated by
+.An J\(:org Wunsch .
+.Sh BUGS
+Installing bootstrap code onto an entire disk is merely pointless.
+.Nm
+should rather support installing bootstrap code into a partition
+instead.
+.Pp
+The
+.Dq auto
+layout algorithm could be smarter.
+By now, it tends to emulate fairly large cylinders which due to
+the two reserved alternate cylinders causes a fair amount of
+wasted disk space.
diff --git a/sbin/sunlabel/sunlabel.c b/sbin/sunlabel/sunlabel.c
new file mode 100644
index 0000000..7bf051e
--- /dev/null
+++ b/sbin/sunlabel/sunlabel.c
@@ -0,0 +1,1007 @@
+/*-
+ * Copyright (c) 2003 Jake Burkholder.
+ * Copyright (c) 2004,2005 Joerg Wunsch.
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+/*
+ * Copyright (c) 1994, 1995 Gordon W. Ross
+ * Copyright (c) 1994 Theo de Raadt
+ * All rights reserved.
+ * Copyright (c) 1987, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Symmetric Computer Systems.
+ *
+ * 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.
+ * This product includes software developed by Theo de Raadt.
+ * 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.
+ *
+ * from: $NetBSD: disksubr.c,v 1.13 2000/12/17 22:39:18 pk $
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/disk.h>
+#include <sys/ioctl.h>
+#include <sys/sun_disklabel.h>
+#include <sys/wait.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <libgeom.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define _PATH_TMPFILE "/tmp/EdDk.XXXXXXXXXX"
+#define _PATH_BOOT "/boot/boot1"
+
+static int bflag;
+static int Bflag;
+static int cflag;
+static int eflag;
+static int hflag;
+static int nflag;
+static int Rflag;
+static int wflag;
+
+static off_t mediasize;
+static uint32_t sectorsize;
+
+struct tags {
+ const char *name;
+ unsigned int id;
+};
+
+static int check_label(struct sun_disklabel *sl);
+static void read_label(struct sun_disklabel *sl, const char *disk);
+static void write_label(struct sun_disklabel *sl, const char *disk,
+ const char *bootpath);
+static void edit_label(struct sun_disklabel *sl, const char *disk,
+ const char *bootpath);
+static int parse_label(struct sun_disklabel *sl, const char *file);
+static void print_label(struct sun_disklabel *sl, const char *disk, FILE *out);
+
+static int parse_size(struct sun_disklabel *sl, int part, char *size);
+static int parse_offset(struct sun_disklabel *sl, int part, char *offset);
+
+static const char *flagname(unsigned int tag);
+static const char *tagname(unsigned int tag);
+static unsigned int parse_flag(struct sun_disklabel *sl, int part,
+ const char *flag);
+static unsigned int parse_tag(struct sun_disklabel *sl, int part,
+ const char *tag);
+static const char *make_h_number(uintmax_t u);
+
+static void usage(void);
+
+extern char *__progname;
+
+static struct tags knowntags[] = {
+ { "unassigned", VTOC_UNASSIGNED },
+ { "boot", VTOC_BOOT },
+ { "root", VTOC_ROOT },
+ { "swap", VTOC_SWAP },
+ { "usr", VTOC_USR },
+ { "backup", VTOC_BACKUP },
+ { "stand", VTOC_STAND },
+ { "var", VTOC_VAR },
+ { "home", VTOC_HOME },
+ { "altsctr", VTOC_ALTSCTR },
+ { "cache", VTOC_CACHE },
+ { "VxVM_pub", VTOC_VXVM_PUB },
+ { "VxVM_priv", VTOC_VXVM_PRIV },
+};
+
+static struct tags knownflags[] = {
+ { "wm", 0 },
+ { "wu", VTOC_UNMNT },
+ { "rm", VTOC_RONLY },
+ { "ru", VTOC_UNMNT | VTOC_RONLY },
+};
+
+/*
+ * Disk label editor for sun disklabels.
+ */
+int
+main(int ac, char **av)
+{
+ struct sun_disklabel sl;
+ const char *bootpath;
+ const char *proto;
+ const char *disk;
+ int ch;
+
+ bootpath = _PATH_BOOT;
+ while ((ch = getopt(ac, av, "b:BcehnrRw")) != -1)
+ switch (ch) {
+ case 'b':
+ bflag = 1;
+ bootpath = optarg;
+ break;
+ case 'B':
+ Bflag = 1;
+ break;
+ case 'c':
+ cflag = 1;
+ break;
+ case 'e':
+ eflag = 1;
+ break;
+ case 'h':
+ hflag = 1;
+ break;
+ case 'n':
+ nflag = 1;
+ break;
+ case 'r':
+ fprintf(stderr, "Obsolete -r flag ignored\n");
+ break;
+ case 'R':
+ Rflag = 1;
+ break;
+ case 'w':
+ wflag = 1;
+ break;
+ default:
+ usage();
+ break;
+ }
+ if (bflag && !Bflag)
+ usage();
+ if (nflag && !(Bflag || eflag || Rflag || wflag))
+ usage();
+ if (eflag && (Rflag || wflag))
+ usage();
+ if (eflag)
+ hflag = 0;
+ ac -= optind;
+ av += optind;
+ if (ac == 0)
+ usage();
+ bzero(&sl, sizeof(sl));
+ disk = av[0];
+ if (wflag) {
+ if (ac != 2 || strcmp(av[1], "auto") != 0)
+ usage();
+ read_label(&sl, disk);
+ bzero(sl.sl_part, sizeof(sl.sl_part));
+ sl.sl_part[SUN_RAWPART].sdkp_cyloffset = 0;
+ sl.sl_part[SUN_RAWPART].sdkp_nsectors = sl.sl_ncylinders *
+ sl.sl_ntracks * sl.sl_nsectors;
+ write_label(&sl, disk, bootpath);
+ } else if (eflag) {
+ if (ac != 1)
+ usage();
+ read_label(&sl, disk);
+ if (sl.sl_magic != SUN_DKMAGIC)
+ errx(1, "%s%s has no sun disklabel", _PATH_DEV, disk);
+ edit_label(&sl, disk, bootpath);
+ } else if (Rflag) {
+ if (ac != 2)
+ usage();
+ proto = av[1];
+ read_label(&sl, disk);
+ if (parse_label(&sl, proto) != 0)
+ errx(1, "%s: invalid label", proto);
+ write_label(&sl, disk, bootpath);
+ } else if (Bflag) {
+ read_label(&sl, disk);
+ if (sl.sl_magic != SUN_DKMAGIC)
+ errx(1, "%s%s has no sun disklabel", _PATH_DEV, disk);
+ write_label(&sl, disk, bootpath);
+ } else {
+ read_label(&sl, disk);
+ if (sl.sl_magic != SUN_DKMAGIC)
+ errx(1, "%s%s has no sun disklabel", _PATH_DEV, disk);
+ print_label(&sl, disk, stdout);
+ }
+ return (0);
+}
+
+static int
+check_label(struct sun_disklabel *sl)
+{
+ uint64_t nsectors;
+ uint64_t ostart;
+ uint64_t start;
+ uint64_t oend;
+ uint64_t end;
+ int havevtoc;
+ int warnonly;
+ int i;
+ int j;
+
+ havevtoc = sl->sl_vtoc_sane == SUN_VTOC_SANE;
+
+ nsectors = sl->sl_ncylinders * sl->sl_ntracks * sl->sl_nsectors;
+ if (sl->sl_part[SUN_RAWPART].sdkp_cyloffset != 0 ||
+ sl->sl_part[SUN_RAWPART].sdkp_nsectors != nsectors) {
+ warnx("partition c is incorrect, must start at 0 and cover "
+ "whole disk");
+ return (1);
+ }
+ if (havevtoc && sl->sl_vtoc_map[2].svtoc_tag != VTOC_BACKUP) {
+ warnx("partition c must have tag \"backup\"");
+ return (1);
+ }
+ for (i = 0; i < SUN_NPART; i++) {
+ if (i == 2 || sl->sl_part[i].sdkp_nsectors == 0)
+ continue;
+ start = (uint64_t)sl->sl_part[i].sdkp_cyloffset *
+ sl->sl_ntracks * sl->sl_nsectors;
+ end = start + sl->sl_part[i].sdkp_nsectors;
+ if (end > nsectors) {
+ warnx("partition %c extends past end of disk",
+ 'a' + i);
+ return (1);
+ }
+ if (havevtoc) {
+ if (sl->sl_vtoc_map[i].svtoc_tag == VTOC_BACKUP) {
+ warnx("only partition c is allowed to have "
+ "tag \"backup\"");
+ return (1);
+ }
+ }
+ for (j = 0; j < SUN_NPART; j++) {
+ /*
+ * Overlaps for unmountable partitions are
+ * non-fatal but will be warned anyway.
+ */
+ warnonly = havevtoc &&
+ ((sl->sl_vtoc_map[i].svtoc_flag & VTOC_UNMNT) != 0 ||
+ (sl->sl_vtoc_map[j].svtoc_flag & VTOC_UNMNT) != 0);
+
+ if (j == 2 || j == i ||
+ sl->sl_part[j].sdkp_nsectors == 0)
+ continue;
+ ostart = (uint64_t)sl->sl_part[j].sdkp_cyloffset *
+ sl->sl_ntracks * sl->sl_nsectors;
+ oend = ostart + sl->sl_part[j].sdkp_nsectors;
+ if ((start <= ostart && end >= oend) ||
+ (start > ostart && start < oend) ||
+ (end > ostart && end < oend)) {
+ warnx("partition %c overlaps partition %c",
+ 'a' + i, 'a' + j);
+ if (!warnonly)
+ return (1);
+ }
+ }
+ }
+ return (0);
+}
+
+static void
+read_label(struct sun_disklabel *sl, const char *disk)
+{
+ char path[MAXPATHLEN];
+ uint32_t fwsectors;
+ uint32_t fwheads;
+ char buf[SUN_SIZE];
+ int fd, error;
+
+ snprintf(path, sizeof(path), "%s%s", _PATH_DEV, disk);
+ if ((fd = open(path, O_RDONLY)) < 0)
+ err(1, "open %s", path);
+ if (read(fd, buf, sizeof(buf)) != sizeof(buf))
+ err(1, "read");
+ error = sunlabel_dec(buf, sl);
+ if (ioctl(fd, DIOCGMEDIASIZE, &mediasize) != 0)
+ if (error)
+ err(1, "%s: ioctl(DIOCGMEDIASIZE) failed", disk);
+ if (ioctl(fd, DIOCGSECTORSIZE, &sectorsize) != 0) {
+ if (error)
+ err(1, "%s: DIOCGSECTORSIZE failed", disk);
+ else
+ sectorsize = 512;
+ }
+ if (error) {
+ bzero(sl, sizeof(*sl));
+ if (ioctl(fd, DIOCGFWSECTORS, &fwsectors) != 0)
+ fwsectors = 63;
+ if (ioctl(fd, DIOCGFWHEADS, &fwheads) != 0) {
+ if (mediasize <= 63 * 1024 * sectorsize)
+ fwheads = 1;
+ else if (mediasize <= 63 * 16 * 1024 * sectorsize)
+ fwheads = 16;
+ else
+ fwheads = 255;
+ }
+ sl->sl_rpm = 3600;
+ sl->sl_pcylinders = mediasize / (fwsectors * fwheads *
+ sectorsize);
+ sl->sl_sparespercyl = 0;
+ sl->sl_interleave = 1;
+ sl->sl_ncylinders = sl->sl_pcylinders - 2;
+ sl->sl_acylinders = 2;
+ sl->sl_nsectors = fwsectors;
+ sl->sl_ntracks = fwheads;
+ sl->sl_part[SUN_RAWPART].sdkp_cyloffset = 0;
+ sl->sl_part[SUN_RAWPART].sdkp_nsectors = sl->sl_ncylinders *
+ sl->sl_ntracks * sl->sl_nsectors;
+ if (mediasize > (off_t)4999L * 1024L * 1024L) {
+ sprintf(sl->sl_text,
+ "FreeBSD%jdG cyl %u alt %u hd %u sec %u",
+ (intmax_t)(mediasize + 512 * 1024 * 1024) /
+ (1024 * 1024 * 1024),
+ sl->sl_ncylinders, sl->sl_acylinders,
+ sl->sl_ntracks, sl->sl_nsectors);
+ } else {
+ sprintf(sl->sl_text,
+ "FreeBSD%jdM cyl %u alt %u hd %u sec %u",
+ (intmax_t)(mediasize + 512 * 1024) / (1024 * 1024),
+ sl->sl_ncylinders, sl->sl_acylinders,
+ sl->sl_ntracks, sl->sl_nsectors);
+ }
+ }
+ close(fd);
+}
+
+static void
+write_label(struct sun_disklabel *sl, const char *disk, const char *bootpath)
+{
+ char path[MAXPATHLEN];
+ char boot[SUN_BOOTSIZE];
+ char buf[SUN_SIZE];
+ const char *errstr;
+ off_t off;
+ int bfd;
+ int fd;
+ int i;
+ struct gctl_req *grq;
+
+ sl->sl_magic = SUN_DKMAGIC;
+
+ if (check_label(sl) != 0)
+ errx(1, "invalid label");
+
+ bzero(buf, sizeof(buf));
+ sunlabel_enc(buf, sl);
+
+ if (nflag) {
+ print_label(sl, disk, stdout);
+ return;
+ }
+ if (Bflag) {
+ if ((bfd = open(bootpath, O_RDONLY)) < 0)
+ err(1, "open %s", bootpath);
+ i = read(bfd, boot, sizeof(boot));
+ if (i < 0)
+ err(1, "read");
+ else if (i != sizeof (boot))
+ errx(1, "read wrong size boot code (%d)", i);
+ close(bfd);
+ }
+ snprintf(path, sizeof(path), "%s%s", _PATH_DEV, disk);
+ fd = open(path, O_RDWR);
+ if (fd < 0) {
+ grq = gctl_get_handle();
+ gctl_ro_param(grq, "verb", -1, "write label");
+ gctl_ro_param(grq, "class", -1, "SUN");
+ gctl_ro_param(grq, "geom", -1, disk);
+ gctl_ro_param(grq, "label", sizeof buf, buf);
+ errstr = gctl_issue(grq);
+ if (errstr != NULL)
+ errx(1, "%s", errstr);
+ gctl_free(grq);
+ if (Bflag) {
+ grq = gctl_get_handle();
+ gctl_ro_param(grq, "verb", -1, "write bootcode");
+ gctl_ro_param(grq, "class", -1, "SUN");
+ gctl_ro_param(grq, "geom", -1, disk);
+ gctl_ro_param(grq, "bootcode", sizeof boot, boot);
+ errstr = gctl_issue(grq);
+ if (errstr != NULL)
+ errx(1, "%s", errstr);
+ gctl_free(grq);
+ }
+ } else {
+ if (lseek(fd, 0, SEEK_SET) < 0)
+ err(1, "lseek");
+ if (write(fd, buf, sizeof(buf)) != sizeof(buf))
+ err (1, "write");
+ if (Bflag) {
+ for (i = 0; i < SUN_NPART; i++) {
+ if (sl->sl_part[i].sdkp_nsectors == 0)
+ continue;
+ off = sl->sl_part[i].sdkp_cyloffset *
+ sl->sl_ntracks * sl->sl_nsectors * 512;
+ /*
+ * Ignore first SUN_SIZE bytes of boot code to
+ * avoid overwriting the label.
+ */
+ if (lseek(fd, off + SUN_SIZE, SEEK_SET) < 0)
+ err(1, "lseek");
+ if (write(fd, boot + SUN_SIZE,
+ sizeof(boot) - SUN_SIZE) !=
+ sizeof(boot) - SUN_SIZE)
+ err(1, "write");
+ }
+ }
+ close(fd);
+ }
+ exit(0);
+}
+
+static void
+edit_label(struct sun_disklabel *sl, const char *disk, const char *bootpath)
+{
+ char tmpfil[] = _PATH_TMPFILE;
+ const char *editor;
+ int status;
+ FILE *fp;
+ pid_t pid;
+ pid_t r;
+ int fd;
+ int c;
+
+ if ((fd = mkstemp(tmpfil)) < 0)
+ err(1, "mkstemp");
+ if ((fp = fdopen(fd, "w")) == NULL)
+ err(1, "fdopen");
+ print_label(sl, disk, fp);
+ fflush(fp);
+ for (;;) {
+ if ((pid = fork()) < 0)
+ err(1, "fork");
+ if (pid == 0) {
+ if ((editor = getenv("EDITOR")) == NULL)
+ editor = _PATH_VI;
+ execlp(editor, editor, tmpfil, (char *)NULL);
+ err(1, "execlp %s", editor);
+ }
+ status = 0;
+ while ((r = wait(&status)) > 0 && r != pid)
+ ;
+ if (WIFEXITED(status)) {
+ if (parse_label(sl, tmpfil) == 0) {
+ fclose(fp);
+ unlink(tmpfil);
+ write_label(sl, disk, bootpath);
+ return;
+ }
+ printf("re-edit the label? [y]: ");
+ fflush(stdout);
+ c = getchar();
+ if (c != EOF && c != '\n')
+ while (getchar() != '\n')
+ ;
+ if (c == 'n') {
+ fclose(fp);
+ unlink(tmpfil);
+ return;
+ }
+ }
+ }
+ fclose(fp);
+ unlink(tmpfil);
+ return;
+}
+
+static int
+parse_label(struct sun_disklabel *sl, const char *file)
+{
+ char offset[32];
+ char size[32];
+ char flag[32];
+ char tag[32];
+ char buf[128];
+ char text[128];
+ char volname[SUN_VOLNAME_LEN + 1];
+ struct sun_disklabel sl1;
+ char *bp;
+ const char *what;
+ uint8_t part;
+ FILE *fp;
+ int line;
+ int rv;
+ int wantvtoc;
+ unsigned alt, cyl, hd, nr, sec;
+
+ line = wantvtoc = 0;
+ if ((fp = fopen(file, "r")) == NULL)
+ err(1, "fopen");
+ sl1 = *sl;
+ bzero(&sl1.sl_part, sizeof(sl1.sl_part));
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ /*
+ * In order to recognize a partition entry, we search
+ * for lines starting with a single letter followed by
+ * a colon as their first non-white characters. We
+ * silently ignore any other lines, so any comment etc.
+ * lines in the label template will be ignored.
+ *
+ * XXX We should probably also recognize the geometry
+ * fields on top, and allow changing the geometry
+ * emulated by this disk.
+ */
+ for (bp = buf; isspace(*bp); bp++)
+ ;
+ if (strncmp(bp, "text:", strlen("text:")) == 0) {
+ bp += strlen("text:");
+ rv = sscanf(bp,
+ " %s cyl %u alt %u hd %u sec %u",
+ text, &cyl, &alt, &hd, &sec);
+ if (rv != 5) {
+ warnx("%s, line %d: text label does not "
+ "contain required fields",
+ file, line + 1);
+ fclose(fp);
+ return (1);
+ }
+ if (alt != 2) {
+ warnx("%s, line %d: # alt must be equal 2",
+ file, line + 1);
+ fclose(fp);
+ return (1);
+ }
+ if (cyl == 0 || cyl > USHRT_MAX) {
+ what = "cyl";
+ nr = cyl;
+ unreasonable:
+ warnx("%s, line %d: # %s %d unreasonable",
+ file, line + 1, what, nr);
+ fclose(fp);
+ return (1);
+ }
+ if (hd == 0 || hd > USHRT_MAX) {
+ what = "hd";
+ nr = hd;
+ goto unreasonable;
+ }
+ if (sec == 0 || sec > USHRT_MAX) {
+ what = "sec";
+ nr = sec;
+ goto unreasonable;
+ }
+ if (mediasize == 0)
+ warnx("unit size unknown, no sector count "
+ "check could be done");
+ else if ((uintmax_t)(cyl + alt) * sec * hd >
+ (uintmax_t)mediasize / sectorsize) {
+ warnx("%s, line %d: sector count %ju exceeds "
+ "unit size %ju",
+ file, line + 1,
+ (uintmax_t)(cyl + alt) * sec * hd,
+ (uintmax_t)mediasize / sectorsize);
+ fclose(fp);
+ return (1);
+ }
+ sl1.sl_pcylinders = cyl + alt;
+ sl1.sl_ncylinders = cyl;
+ sl1.sl_acylinders = alt;
+ sl1.sl_nsectors = sec;
+ sl1.sl_ntracks = hd;
+ memset(sl1.sl_text, 0, sizeof(sl1.sl_text));
+ snprintf(sl1.sl_text, sizeof(sl1.sl_text),
+ "%s cyl %u alt %u hd %u sec %u",
+ text, cyl, alt, hd, sec);
+ continue;
+ }
+ if (strncmp(bp, "volume name:", strlen("volume name:")) == 0) {
+ wantvtoc = 1; /* Volume name requires VTOC. */
+ bp += strlen("volume name:");
+#if SUN_VOLNAME_LEN != 8
+# error "scanf field width does not match SUN_VOLNAME_LEN"
+#endif
+ /*
+ * We set the field length to one more than
+ * SUN_VOLNAME_LEN to allow detecting an
+ * overflow.
+ */
+ memset(volname, 0, sizeof volname);
+ rv = sscanf(bp, " %9[^\n]", volname);
+ if (rv != 1) {
+ /* Clear the volume name. */
+ memset(sl1.sl_vtoc_volname, 0,
+ SUN_VOLNAME_LEN);
+ } else {
+ memcpy(sl1.sl_vtoc_volname, volname,
+ SUN_VOLNAME_LEN);
+ if (volname[SUN_VOLNAME_LEN] != '\0')
+ warnx(
+"%s, line %d: volume name longer than %d characters, truncating",
+ file, line + 1, SUN_VOLNAME_LEN);
+ }
+ continue;
+ }
+ if (strlen(bp) < 2 || bp[1] != ':') {
+ line++;
+ continue;
+ }
+ rv = sscanf(bp, "%c: %30s %30s %30s %30s",
+ &part, size, offset, tag, flag);
+ if (rv < 3) {
+ syntaxerr:
+ warnx("%s: syntax error on line %d",
+ file, line + 1);
+ fclose(fp);
+ return (1);
+ }
+ if (parse_size(&sl1, part - 'a', size) ||
+ parse_offset(&sl1, part - 'a', offset))
+ goto syntaxerr;
+ if (rv > 3) {
+ wantvtoc = 1;
+ if (rv == 5 && parse_flag(&sl1, part - 'a', flag))
+ goto syntaxerr;
+ if (parse_tag(&sl1, part - 'a', tag))
+ goto syntaxerr;
+ }
+ line++;
+ }
+ fclose(fp);
+ if (wantvtoc) {
+ sl1.sl_vtoc_sane = SUN_VTOC_SANE;
+ sl1.sl_vtoc_vers = SUN_VTOC_VERSION;
+ sl1.sl_vtoc_nparts = SUN_NPART;
+ } else {
+ sl1.sl_vtoc_sane = 0;
+ sl1.sl_vtoc_vers = 0;
+ sl1.sl_vtoc_nparts = 0;
+ bzero(&sl1.sl_vtoc_map, sizeof(sl1.sl_vtoc_map));
+ }
+ *sl = sl1;
+ return (check_label(sl));
+}
+
+static int
+parse_size(struct sun_disklabel *sl, int part, char *size)
+{
+ uintmax_t nsectors;
+ uintmax_t total;
+ uintmax_t n;
+ char *p;
+ int i;
+
+ nsectors = 0;
+ n = strtoumax(size, &p, 10);
+ if (*p != '\0') {
+ if (strcmp(size, "*") == 0) {
+ total = sl->sl_ncylinders * sl->sl_ntracks *
+ sl->sl_nsectors;
+ for (i = 0; i < part; i++) {
+ if (i == 2)
+ continue;
+ nsectors += sl->sl_part[i].sdkp_nsectors;
+ }
+ n = total - nsectors;
+ } else if (p[1] == '\0' && (p[0] == 'C' || p[0] == 'c')) {
+ n = n * sl->sl_ntracks * sl->sl_nsectors;
+ } else if (p[1] == '\0' && (p[0] == 'K' || p[0] == 'k')) {
+ n = roundup((n * 1024) / 512,
+ sl->sl_ntracks * sl->sl_nsectors);
+ } else if (p[1] == '\0' && (p[0] == 'M' || p[0] == 'm')) {
+ n = roundup((n * 1024 * 1024) / 512,
+ sl->sl_ntracks * sl->sl_nsectors);
+ } else if (p[1] == '\0' && (p[0] == 'S' || p[0] == 's')) {
+ /* size in sectors, no action neded */
+ } else if (p[1] == '\0' && (p[0] == 'G' || p[0] == 'g')) {
+ n = roundup((n * 1024 * 1024 * 1024) / 512,
+ sl->sl_ntracks * sl->sl_nsectors);
+ } else
+ return (-1);
+ } else if (cflag) {
+ n = n * sl->sl_ntracks * sl->sl_nsectors;
+ }
+ sl->sl_part[part].sdkp_nsectors = n;
+ return (0);
+}
+
+static int
+parse_offset(struct sun_disklabel *sl, int part, char *offset)
+{
+ uintmax_t nsectors;
+ uintmax_t n;
+ char *p;
+ int i;
+
+ nsectors = 0;
+ n = strtoumax(offset, &p, 10);
+ if (*p != '\0') {
+ if (strcmp(offset, "*") == 0) {
+ for (i = 0; i < part; i++) {
+ if (i == 2)
+ continue;
+ nsectors += sl->sl_part[i].sdkp_nsectors;
+ }
+ n = nsectors / (sl->sl_nsectors * sl->sl_ntracks);
+ } else
+ return (-1);
+ }
+ sl->sl_part[part].sdkp_cyloffset = n;
+ return (0);
+}
+
+static void
+print_label(struct sun_disklabel *sl, const char *disk, FILE *out)
+{
+ int i, j;
+ int havevtoc;
+ uintmax_t secpercyl;
+ /* Long enough to hex-encode each character. */
+ char volname[4 * SUN_VOLNAME_LEN + 1];
+
+ havevtoc = sl->sl_vtoc_sane == SUN_VTOC_SANE;
+ secpercyl = sl->sl_nsectors * sl->sl_ntracks;
+
+ fprintf(out,
+"# /dev/%s:\n"
+"text: %s\n"
+"bytes/sector: %d\n"
+"sectors/cylinder: %ju\n",
+ disk,
+ sl->sl_text,
+ sectorsize,
+ secpercyl);
+ if (eflag)
+ fprintf(out,
+ "# max sectors/unit (including alt cylinders): %ju\n",
+ (uintmax_t)mediasize / sectorsize);
+ fprintf(out,
+"sectors/unit: %ju\n",
+ secpercyl * sl->sl_ncylinders);
+ if (havevtoc && sl->sl_vtoc_volname[0] != '\0') {
+ for (i = j = 0; i < SUN_VOLNAME_LEN; i++) {
+ if (sl->sl_vtoc_volname[i] == '\0')
+ break;
+ if (isprint(sl->sl_vtoc_volname[i]))
+ volname[j++] = sl->sl_vtoc_volname[i];
+ else
+ j += sprintf(volname + j, "\\x%02X",
+ sl->sl_vtoc_volname[i]);
+ }
+ volname[j] = '\0';
+ fprintf(out, "volume name: %s\n", volname);
+ }
+ fprintf(out,
+"\n"
+"%d partitions:\n"
+"#\n",
+ SUN_NPART);
+ if (!hflag) {
+ fprintf(out, "# Size is in %s.", cflag? "cylinders": "sectors");
+ if (eflag)
+ fprintf(out,
+" Use %%d%c, %%dK, %%dM or %%dG to specify in %s,\n"
+"# kilobytes, megabytes or gigabytes respectively, or '*' to specify rest of\n"
+"# disk.\n",
+ cflag? 's': 'c',
+ cflag? "sectors": "cylinders");
+ else
+ putc('\n', out);
+ fprintf(out, "# Offset is in cylinders.");
+ if (eflag)
+ fprintf(out,
+" Use '*' to calculate offsets automatically.\n"
+"#\n");
+ else
+ putc('\n', out);
+ }
+ if (havevtoc)
+ fprintf(out,
+"# size offset tag flag\n"
+"# ---------- ---------- ---------- ----\n"
+ );
+ else
+ fprintf(out,
+"# size offset\n"
+"# ---------- ----------\n"
+ );
+
+ for (i = 0; i < SUN_NPART; i++) {
+ if (sl->sl_part[i].sdkp_nsectors == 0)
+ continue;
+ if (hflag) {
+ fprintf(out, " %c: %10s",
+ 'a' + i,
+ make_h_number((uintmax_t)
+ sl->sl_part[i].sdkp_nsectors * 512));
+ fprintf(out, " %10s",
+ make_h_number((uintmax_t)
+ sl->sl_part[i].sdkp_cyloffset * 512
+ * secpercyl));
+ } else {
+ fprintf(out, " %c: %10ju %10u",
+ 'a' + i,
+ sl->sl_part[i].sdkp_nsectors / (cflag? secpercyl: 1),
+ sl->sl_part[i].sdkp_cyloffset);
+ }
+ if (havevtoc)
+ fprintf(out, " %11s %5s",
+ tagname(sl->sl_vtoc_map[i].svtoc_tag),
+ flagname(sl->sl_vtoc_map[i].svtoc_flag));
+ putc('\n', out);
+ }
+}
+
+static void
+usage(void)
+{
+
+ fprintf(stderr, "usage:"
+"\t%s [-r] [-c | -h] disk\n"
+"\t\t(to read label)\n"
+"\t%s -B [-b boot1] [-n] disk\n"
+"\t\t(to install boot program only)\n"
+"\t%s -R [-B [-b boot1]] [-r] [-n] [-c] disk protofile\n"
+"\t\t(to restore label)\n"
+"\t%s -e [-B [-b boot1]] [-r] [-n] [-c] disk\n"
+"\t\t(to edit label)\n"
+"\t%s -w [-B [-b boot1]] [-r] [-n] disk type\n"
+"\t\t(to write default label)\n",
+ __progname,
+ __progname,
+ __progname,
+ __progname,
+ __progname);
+ exit(1);
+}
+
+/*
+ * Return VTOC tag and flag names for tag or flag ID, resp.
+ */
+static const char *
+tagname(unsigned int tag)
+{
+ static char buf[32];
+ size_t i;
+ struct tags *tp;
+
+ for (i = 0, tp = knowntags;
+ i < sizeof(knowntags) / sizeof(struct tags);
+ i++, tp++)
+ if (tp->id == tag)
+ return (tp->name);
+
+ sprintf(buf, "%u", tag);
+
+ return (buf);
+}
+
+static const char *
+flagname(unsigned int flag)
+{
+ static char buf[32];
+ size_t i;
+ struct tags *tp;
+
+ for (i = 0, tp = knownflags;
+ i < sizeof(knownflags) / sizeof(struct tags);
+ i++, tp++)
+ if (tp->id == flag)
+ return (tp->name);
+
+ sprintf(buf, "%u", flag);
+
+ return (buf);
+}
+
+static unsigned int
+parse_tag(struct sun_disklabel *sl, int part, const char *tag)
+{
+ struct tags *tp;
+ char *endp;
+ size_t i;
+ unsigned long l;
+
+ for (i = 0, tp = knowntags;
+ i < sizeof(knowntags) / sizeof(struct tags);
+ i++, tp++)
+ if (strcmp(tp->name, tag) == 0) {
+ sl->sl_vtoc_map[part].svtoc_tag = (uint16_t)tp->id;
+ return (0);
+ }
+
+ l = strtoul(tag, &endp, 0);
+ if (*tag != '\0' && *endp == '\0') {
+ sl->sl_vtoc_map[part].svtoc_tag = (uint16_t)l;
+ return (0);
+ }
+
+ return (-1);
+}
+
+static unsigned int
+parse_flag(struct sun_disklabel *sl, int part, const char *flag)
+{
+ struct tags *tp;
+ char *endp;
+ size_t i;
+ unsigned long l;
+
+ for (i = 0, tp = knownflags;
+ i < sizeof(knownflags) / sizeof(struct tags);
+ i++, tp++)
+ if (strcmp(tp->name, flag) == 0) {
+ sl->sl_vtoc_map[part].svtoc_flag = (uint16_t)tp->id;
+ return (0);
+ }
+
+ l = strtoul(flag, &endp, 0);
+ if (*flag != '\0' && *endp == '\0') {
+ sl->sl_vtoc_map[part].svtoc_flag = (uint16_t)l;
+ return (0);
+ }
+
+ return (-1);
+}
+
+/*
+ * Convert argument into `human readable' byte number form.
+ */
+static const char *
+make_h_number(uintmax_t u)
+{
+ static char buf[32];
+ double d;
+
+ if (u == 0) {
+ strcpy(buf, "0B");
+ } else if (u > 2000000000UL) {
+ d = (double)u / 1e9;
+ sprintf(buf, "%.1fG", d);
+ } else if (u > 2000000UL) {
+ d = (double)u / 1e6;
+ sprintf(buf, "%.1fM", d);
+ } else {
+ d = (double)u / 1e3;
+ sprintf(buf, "%.1fK", d);
+ }
+
+ return (buf);
+}
diff --git a/sbin/swapon/Makefile b/sbin/swapon/Makefile
new file mode 100644
index 0000000..e9074be
--- /dev/null
+++ b/sbin/swapon/Makefile
@@ -0,0 +1,13 @@
+# @(#)Makefile 8.1 (Berkeley) 6/5/93
+# $FreeBSD$
+
+PROG= swapon
+MAN= swapon.8
+LINKS= ${BINDIR}/swapon ${BINDIR}/swapoff
+LINKS+= ${BINDIR}/swapon ${BINDIR}/swapctl
+MLINKS= swapon.8 swapoff.8
+MLINKS+=swapon.8 swapctl.8
+
+LIBADD= util
+
+.include <bsd.prog.mk>
diff --git a/sbin/swapon/swapon.8 b/sbin/swapon/swapon.8
new file mode 100644
index 0000000..c4286d7
--- /dev/null
+++ b/sbin/swapon/swapon.8
@@ -0,0 +1,223 @@
+.\" Copyright (c) 1980, 1991, 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.
+.\" 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.
+.\"
+.\" @(#)swapon.8 8.1 (Berkeley) 6/5/93
+.\" $FreeBSD$
+.\"
+.Dd November 22, 2013
+.Dt SWAPON 8
+.Os
+.Sh NAME
+.Nm swapon , swapoff , swapctl
+.Nd "specify devices for paging and swapping"
+.Sh SYNOPSIS
+.Nm swapon
+.Oo Fl F Ar fstab
+.Oc
+.Fl aLq | Ar
+.Nm swapoff
+.Oo Fl F Ar fstab
+.Oc
+.Fl aLq | Ar
+.Nm swapctl
+.Op Fl AghklmsU
+.Oo
+.Fl a Ar
+|
+.Fl d Ar
+.Oc
+.Sh DESCRIPTION
+The
+.Nm swapon , swapoff
+and
+.Nm swapctl
+utilities are used to control swap devices in the system.
+At boot time all swap entries in
+.Pa /etc/fstab
+are added automatically when the system goes multi-user.
+Swap devices use a fixed interleave; the maximum number of devices
+is unlimited.
+There is no priority mechanism.
+.Pp
+The
+.Nm swapon
+utility adds the specified swap devices to the system.
+If the
+.Fl a
+option is used, all swap devices in
+.Pa /etc/fstab
+will be added, unless their
+.Dq noauto
+or
+.Dq late
+option is also set.
+If the
+.Fl L
+option is specified,
+swap devices with the
+.Dq late
+option will be added as well as ones with no option.
+If the
+.Fl q
+option is used,
+informational messages will not be
+written to standard output when a swap device is added.
+.Pp
+The
+.Nm swapoff
+utility removes the specified swap devices from the system.
+If the
+.Fl a
+option is used, all swap devices in
+.Pa /etc/fstab
+will be removed, unless their
+.Dq noauto
+or
+.Dq late
+option is also set.
+If the
+.Fl L
+option is specified,
+swap devices with the
+.Dq late
+option will be removed as well as ones with no option.
+If the
+.Fl q
+option is used,
+informational messages will not be
+written to standard output when a swap device is removed.
+Note that
+.Nm swapoff
+will fail and refuse to remove a swap device if there is insufficient
+VM (memory + remaining swap devices) to run the system.
+The
+.Nm swapoff
+utility
+must move swapped pages out of the device being removed which could
+lead to high system loads for a period of time, depending on how
+much data has been swapped out to that device.
+.Pp
+Other options supported by both
+.Nm swapon
+and
+.Nm swapoff
+are as follows:
+.Bl -tag -width indent
+.It Fl F Ar fstab
+Specify the
+.Pa fstab
+file to use.
+.El
+.Pp
+The
+.Nm swapctl
+utility exists primarily for those familiar with other
+.Bx Ns s
+and may be
+used to add, remove, or list swap devices.
+Note that the
+.Fl a
+option is used differently in
+.Nm swapctl
+and indicates that a specific list of devices should be added.
+The
+.Fl d
+option indicates that a specific list should be removed.
+The
+.Fl A
+and
+.Fl U
+options to
+.Nm swapctl
+operate on all swap entries in
+.Pa /etc/fstab
+which do not have their
+.Dq noauto
+option set.
+.Pp
+Swap information can be generated using the
+.Xr swapinfo 8
+utility,
+.Nm pstat
+.Fl s ,
+or
+.Nm swapctl
+.Fl l .
+The
+.Nm swapctl
+utility has the following options for listing swap:
+.Bl -tag -width indent
+.It Fl h
+Output values in human-readable form.
+.It Fl g
+Output values in gigabytes.
+.It Fl k
+Output values in kilobytes.
+.It Fl m
+Output values in megabytes.
+.It Fl l
+List the devices making up system swap.
+.It Fl s
+Print a summary line for system swap.
+.Pp
+The
+.Ev BLOCKSIZE
+environment variable is used if not specifically
+overridden.
+512 byte blocks are used by default.
+.El
+.Sh FILES
+.Bl -tag -width ".Pa /dev/{ada,da}?s?b" -compact
+.It Pa /dev/{ada,da}?s?b
+standard paging devices
+.It Pa /dev/md?
+memory disk devices
+.It Pa /etc/fstab
+.Tn ASCII
+file system description table
+.El
+.Sh DIAGNOSTICS
+These utilities may fail for the reasons described in
+.Xr swapon 2 .
+.Sh SEE ALSO
+.Xr swapon 2 ,
+.Xr fstab 5 ,
+.Xr init 8 ,
+.Xr mdconfig 8 ,
+.Xr pstat 8 ,
+.Xr rc 8
+.Sh HISTORY
+The
+.Nm swapon
+utility appeared in
+.Bx 4.0 .
+The
+.Nm swapoff
+and
+.Nm swapctl
+utilities appeared in
+.Fx 5.1 .
diff --git a/sbin/swapon/swapon.c b/sbin/swapon/swapon.c
new file mode 100644
index 0000000..033c40a
--- /dev/null
+++ b/sbin/swapon/swapon.c
@@ -0,0 +1,851 @@
+/*-
+ * Copyright (c) 1980, 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.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if 0
+#ifndef lint
+static const char copyright[] =
+"@(#) Copyright (c) 1980, 1993\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static char sccsid[] = "@(#)swapon.c 8.1 (Berkeley) 6/5/93";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/mdioctl.h>
+#include <sys/stat.h>
+#include <sys/sysctl.h>
+#include <sys/wait.h>
+#include <vm/vm_param.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <fnmatch.h>
+#include <fstab.h>
+#include <libgen.h>
+#include <libutil.h>
+#include <limits.h>
+#include <paths.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+static void usage(void);
+static const char *swap_on_off(const char *, int, char *);
+static const char *swap_on_off_gbde(const char *, int);
+static const char *swap_on_off_geli(const char *, char *, int);
+static const char *swap_on_off_md(const char *, char *, int);
+static const char *swap_on_off_sfile(const char *, int);
+static void swaplist(int, int, int);
+static int run_cmd(int *, const char *, ...) __printflike(2, 3);
+
+static enum { SWAPON, SWAPOFF, SWAPCTL } orig_prog, which_prog = SWAPCTL;
+
+static int qflag;
+
+int
+main(int argc, char **argv)
+{
+ struct fstab *fsp;
+ const char *swfile;
+ char *ptr;
+ int ret, ch, doall;
+ int sflag, lflag, late, hflag;
+ const char *etc_fstab;
+
+ sflag = lflag = late = hflag = 0;
+ if ((ptr = strrchr(argv[0], '/')) == NULL)
+ ptr = argv[0];
+ if (strstr(ptr, "swapon") != NULL)
+ which_prog = SWAPON;
+ else if (strstr(ptr, "swapoff") != NULL)
+ which_prog = SWAPOFF;
+ orig_prog = which_prog;
+
+ doall = 0;
+ etc_fstab = NULL;
+ while ((ch = getopt(argc, argv, "AadghklLmqsUF:")) != -1) {
+ switch(ch) {
+ case 'A':
+ if (which_prog == SWAPCTL) {
+ doall = 1;
+ which_prog = SWAPON;
+ } else
+ usage();
+ break;
+ case 'a':
+ if (which_prog == SWAPON || which_prog == SWAPOFF)
+ doall = 1;
+ else
+ which_prog = SWAPON;
+ break;
+ case 'd':
+ if (which_prog == SWAPCTL)
+ which_prog = SWAPOFF;
+ else
+ usage();
+ break;
+ case 'g':
+ hflag = 'G';
+ break;
+ case 'h':
+ hflag = 'H';
+ break;
+ case 'k':
+ hflag = 'K';
+ break;
+ case 'l':
+ lflag = 1;
+ break;
+ case 'L':
+ late = 1;
+ break;
+ case 'm':
+ hflag = 'M';
+ break;
+ case 'q':
+ if (which_prog == SWAPON || which_prog == SWAPOFF)
+ qflag = 1;
+ break;
+ case 's':
+ sflag = 1;
+ break;
+ case 'U':
+ if (which_prog == SWAPCTL) {
+ doall = 1;
+ which_prog = SWAPOFF;
+ } else
+ usage();
+ break;
+ case 'F':
+ etc_fstab = optarg;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ }
+ argv += optind;
+
+ ret = 0;
+ swfile = NULL;
+ if (etc_fstab != NULL)
+ setfstab(etc_fstab);
+ if (which_prog == SWAPON || which_prog == SWAPOFF) {
+ if (doall) {
+ while ((fsp = getfsent()) != NULL) {
+ if (strcmp(fsp->fs_type, FSTAB_SW) != 0)
+ continue;
+ if (strstr(fsp->fs_mntops, "noauto") != NULL)
+ continue;
+ if (which_prog != SWAPOFF &&
+ strstr(fsp->fs_mntops, "late") &&
+ late == 0)
+ continue;
+ swfile = swap_on_off(fsp->fs_spec, 1,
+ fsp->fs_mntops);
+ if (swfile == NULL) {
+ ret = 1;
+ continue;
+ }
+ if (qflag == 0) {
+ printf("%s: %sing %s as swap device\n",
+ getprogname(),
+ (which_prog == SWAPOFF) ?
+ "remov" : "add", swfile);
+ }
+ }
+ } else if (*argv == NULL)
+ usage();
+ for (; *argv; ++argv) {
+ swfile = swap_on_off(*argv, 0, NULL);
+ if (swfile == NULL) {
+ ret = 1;
+ continue;
+ }
+ if (orig_prog == SWAPCTL) {
+ printf("%s: %sing %s as swap device\n",
+ getprogname(),
+ (which_prog == SWAPOFF) ? "remov" : "add",
+ swfile);
+ }
+ }
+ } else {
+ if (lflag || sflag)
+ swaplist(lflag, sflag, hflag);
+ else
+ usage();
+ }
+ exit(ret);
+}
+
+static const char *
+swap_on_off(const char *name, int doingall, char *mntops)
+{
+ char base[PATH_MAX];
+
+ /* Swap on vnode-backed md(4) device. */
+ if (mntops != NULL &&
+ (fnmatch(_PATH_DEV MD_NAME "[0-9]*", name, 0) == 0 ||
+ fnmatch(MD_NAME "[0-9]*", name, 0) == 0 ||
+ strncmp(_PATH_DEV MD_NAME, name,
+ sizeof(_PATH_DEV) + sizeof(MD_NAME)) == 0 ||
+ strncmp(MD_NAME, name, sizeof(MD_NAME)) == 0))
+ return (swap_on_off_md(name, mntops, doingall));
+
+ basename_r(name, base);
+
+ /* Swap on encrypted device by GEOM_BDE. */
+ if (fnmatch("*.bde", base, 0) == 0)
+ return (swap_on_off_gbde(name, doingall));
+
+ /* Swap on encrypted device by GEOM_ELI. */
+ if (fnmatch("*.eli", base, 0) == 0)
+ return (swap_on_off_geli(name, mntops, doingall));
+
+ /* Swap on special file. */
+ return (swap_on_off_sfile(name, doingall));
+}
+
+/* Strip off .bde or .eli suffix from swap device name */
+static char *
+swap_basename(const char *name)
+{
+ char *dname, *p;
+
+ dname = strdup(name);
+ p = strrchr(dname, '.');
+ /* assert(p != NULL); */
+ *p = '\0';
+
+ return (dname);
+}
+
+static const char *
+swap_on_off_gbde(const char *name, int doingall)
+{
+ const char *ret;
+ char pass[64 * 2 + 1];
+ unsigned char bpass[64];
+ char *dname;
+ int i, error;
+
+ dname = swap_basename(name);
+ if (dname == NULL)
+ return (NULL);
+
+ if (which_prog == SWAPON) {
+ arc4random_buf(bpass, sizeof(bpass));
+ for (i = 0; i < (int)sizeof(bpass); i++)
+ sprintf(&pass[2 * i], "%02x", bpass[i]);
+ pass[sizeof(pass) - 1] = '\0';
+
+ error = run_cmd(NULL, "%s init %s -P %s", _PATH_GBDE,
+ dname, pass);
+ if (error) {
+ /* bde device found. Ignore it. */
+ free(dname);
+ if (qflag == 0)
+ warnx("%s: Device already in use", name);
+ return (NULL);
+ }
+ error = run_cmd(NULL, "%s attach %s -p %s", _PATH_GBDE,
+ dname, pass);
+ free(dname);
+ if (error) {
+ warnx("gbde (attach) error: %s", name);
+ return (NULL);
+ }
+ }
+
+ ret = swap_on_off_sfile(name, doingall);
+
+ if (which_prog == SWAPOFF) {
+ error = run_cmd(NULL, "%s detach %s", _PATH_GBDE, dname);
+ free(dname);
+ if (error) {
+ /* bde device not found. Ignore it. */
+ if (qflag == 0)
+ warnx("%s: Device not found", name);
+ return (NULL);
+ }
+ }
+
+ return (ret);
+}
+
+/* Build geli(8) arguments from mntops */
+static char *
+swap_on_geli_args(const char *mntops)
+{
+ const char *aalgo, *ealgo, *keylen_str, *sectorsize_str;
+ const char *aflag, *eflag, *lflag, *sflag;
+ char *p, *args, *token, *string, *ops;
+ int argsize, pagesize;
+ size_t pagesize_len;
+ u_long ul;
+
+ /* Use built-in defaults for geli(8). */
+ aalgo = ealgo = keylen_str = "";
+ aflag = eflag = lflag = "";
+
+ /* We will always specify sectorsize. */
+ sflag = " -s ";
+ sectorsize_str = NULL;
+
+ if (mntops != NULL) {
+ string = ops = strdup(mntops);
+
+ while ((token = strsep(&string, ",")) != NULL) {
+ if ((p = strstr(token, "aalgo=")) == token) {
+ aalgo = p + sizeof("aalgo=") - 1;
+ aflag = " -a ";
+ } else if ((p = strstr(token, "ealgo=")) == token) {
+ ealgo = p + sizeof("ealgo=") - 1;
+ eflag = " -e ";
+ } else if ((p = strstr(token, "keylen=")) == token) {
+ keylen_str = p + sizeof("keylen=") - 1;
+ errno = 0;
+ ul = strtoul(keylen_str, &p, 10);
+ if (errno == 0) {
+ if (*p != '\0' || ul > INT_MAX)
+ errno = EINVAL;
+ }
+ if (errno) {
+ warn("Invalid keylen: %s", keylen_str);
+ free(ops);
+ return (NULL);
+ }
+ lflag = " -l ";
+ } else if ((p = strstr(token, "sectorsize=")) == token) {
+ sectorsize_str = p + sizeof("sectorsize=") - 1;
+ errno = 0;
+ ul = strtoul(sectorsize_str, &p, 10);
+ if (errno == 0) {
+ if (*p != '\0' || ul > INT_MAX)
+ errno = EINVAL;
+ }
+ if (errno) {
+ warn("Invalid sectorsize: %s",
+ sectorsize_str);
+ free(ops);
+ return (NULL);
+ }
+ } else if (strcmp(token, "sw") != 0) {
+ warnx("Invalid option: %s", token);
+ free(ops);
+ return (NULL);
+ }
+ }
+ } else
+ ops = NULL;
+
+ /*
+ * If we do not have a sector size at this point, fill in
+ * pagesize as sector size.
+ */
+ if (sectorsize_str == NULL) {
+ /* Use pagesize as default sectorsize. */
+ pagesize = getpagesize();
+ pagesize_len = snprintf(NULL, 0, "%d", pagesize) + 1;
+ p = alloca(pagesize_len);
+ snprintf(p, pagesize_len, "%d", pagesize);
+ sectorsize_str = p;
+ }
+
+ argsize = asprintf(&args, "%s%s%s%s%s%s%s%s -d",
+ aflag, aalgo, eflag, ealgo, lflag, keylen_str,
+ sflag, sectorsize_str);
+
+ free(ops);
+ return (args);
+}
+
+static const char *
+swap_on_off_geli(const char *name, char *mntops, int doingall)
+{
+ struct stat sb;
+ char *dname, *args;
+ int error;
+
+ error = stat(name, &sb);
+
+ if (which_prog == SWAPON) do {
+ /* Skip if the .eli device already exists. */
+ if (error == 0)
+ break;
+
+ args = swap_on_geli_args(mntops);
+ if (args == NULL)
+ return (NULL);
+
+ dname = swap_basename(name);
+ if (dname == NULL) {
+ free(args);
+ return (NULL);
+ }
+
+ error = run_cmd(NULL, "%s onetime%s %s", _PATH_GELI, args,
+ dname);
+
+ free(dname);
+ free(args);
+
+ if (error) {
+ /* error occured during creation. */
+ if (qflag == 0)
+ warnx("%s: Invalid parameters", name);
+ return (NULL);
+ }
+ } while (0);
+
+ return (swap_on_off_sfile(name, doingall));
+}
+
+static const char *
+swap_on_off_md(const char *name, char *mntops, int doingall)
+{
+ FILE *sfd;
+ int fd, mdunit, error;
+ const char *ret;
+ static char mdpath[PATH_MAX], linebuf[PATH_MAX];
+ char *p, *vnodefile;
+ size_t linelen;
+ u_long ul;
+
+ fd = -1;
+ sfd = NULL;
+ if (strlen(name) == (sizeof(MD_NAME) - 1))
+ mdunit = -1;
+ else {
+ errno = 0;
+ ul = strtoul(name + 2, &p, 10);
+ if (errno == 0) {
+ if (*p != '\0' || ul > INT_MAX)
+ errno = EINVAL;
+ }
+ if (errno) {
+ warn("Bad device unit: %s", name);
+ return (NULL);
+ }
+ mdunit = (int)ul;
+ }
+
+ vnodefile = NULL;
+ if ((p = strstr(mntops, "file=")) != NULL) {
+ vnodefile = strdup(p + sizeof("file=") - 1);
+ p = strchr(vnodefile, ',');
+ if (p != NULL)
+ *p = '\0';
+ }
+ if (vnodefile == NULL) {
+ warnx("file option not found for %s", name);
+ return (NULL);
+ }
+
+ if (which_prog == SWAPON) {
+ if (mdunit == -1) {
+ error = run_cmd(&fd, "%s -l -n -f %s",
+ _PATH_MDCONFIG, vnodefile);
+ if (error == 0) {
+ /* md device found. Ignore it. */
+ close(fd);
+ if (!qflag)
+ warnx("%s: Device already in use",
+ vnodefile);
+ free(vnodefile);
+ return (NULL);
+ }
+ error = run_cmd(&fd, "%s -a -t vnode -n -f %s",
+ _PATH_MDCONFIG, vnodefile);
+ if (error) {
+ warnx("mdconfig (attach) error: file=%s",
+ vnodefile);
+ free(vnodefile);
+ return (NULL);
+ }
+ sfd = fdopen(fd, "r");
+ if (sfd == NULL) {
+ warn("mdconfig (attach) fdopen error");
+ ret = NULL;
+ goto err;
+ }
+ p = fgetln(sfd, &linelen);
+ if (p == NULL &&
+ (linelen < 2 || linelen > sizeof(linebuf))) {
+ warn("mdconfig (attach) unexpected output");
+ ret = NULL;
+ goto err;
+ }
+ strncpy(linebuf, p, linelen);
+ linebuf[linelen - 1] = '\0';
+ errno = 0;
+ ul = strtoul(linebuf, &p, 10);
+ if (errno == 0) {
+ if (*p != '\0' || ul > INT_MAX)
+ errno = EINVAL;
+ }
+ if (errno) {
+ warn("mdconfig (attach) unexpected output: %s",
+ linebuf);
+ ret = NULL;
+ goto err;
+ }
+ mdunit = (int)ul;
+ } else {
+ error = run_cmd(&fd, "%s -l -n -f %s -u %d",
+ _PATH_MDCONFIG, vnodefile, mdunit);
+ if (error == 0) {
+ /* md device found. Ignore it. */
+ close(fd);
+ if (qflag == 0)
+ warnx("md%d on %s: Device already "
+ "in use", mdunit, vnodefile);
+ free(vnodefile);
+ return (NULL);
+ }
+ error = run_cmd(NULL, "%s -a -t vnode -u %d -f %s",
+ _PATH_MDCONFIG, mdunit, vnodefile);
+ if (error) {
+ warnx("mdconfig (attach) error: "
+ "md%d on file=%s", mdunit, vnodefile);
+ free(vnodefile);
+ return (NULL);
+ }
+ }
+ } else /* SWAPOFF */ {
+ if (mdunit == -1) {
+ error = run_cmd(&fd, "%s -l -n -f %s",
+ _PATH_MDCONFIG, vnodefile);
+ if (error) {
+ /* md device not found. Ignore it. */
+ close(fd);
+ if (!qflag)
+ warnx("md on %s: Device not found",
+ vnodefile);
+ free(vnodefile);
+ return (NULL);
+ }
+ sfd = fdopen(fd, "r");
+ if (sfd == NULL) {
+ warn("mdconfig (list) fdopen error");
+ ret = NULL;
+ goto err;
+ }
+ p = fgetln(sfd, &linelen);
+ if (p == NULL &&
+ (linelen < 2 || linelen > sizeof(linebuf) - 1)) {
+ warn("mdconfig (list) unexpected output");
+ ret = NULL;
+ goto err;
+ }
+ strncpy(linebuf, p, linelen);
+ linebuf[linelen - 1] = '\0';
+ p = strchr(linebuf, ' ');
+ if (p != NULL)
+ *p = '\0';
+ errno = 0;
+ ul = strtoul(linebuf, &p, 10);
+ if (errno == 0) {
+ if (*p != '\0' || ul > INT_MAX)
+ errno = EINVAL;
+ }
+ if (errno) {
+ warn("mdconfig (list) unexpected output: %s",
+ linebuf);
+ ret = NULL;
+ goto err;
+ }
+ mdunit = (int)ul;
+ } else {
+ error = run_cmd(&fd, "%s -l -n -f %s -u %d",
+ _PATH_MDCONFIG, vnodefile, mdunit);
+ if (error) {
+ /* md device not found. Ignore it. */
+ close(fd);
+ if (!qflag)
+ warnx("md%d on %s: Device not found",
+ mdunit, vnodefile);
+ free(vnodefile);
+ return (NULL);
+ }
+ }
+ }
+ snprintf(mdpath, sizeof(mdpath), "%s%s%d", _PATH_DEV,
+ MD_NAME, mdunit);
+ mdpath[sizeof(mdpath) - 1] = '\0';
+ ret = swap_on_off_sfile(mdpath, doingall);
+
+ if (which_prog == SWAPOFF) {
+ if (ret != NULL) {
+ error = run_cmd(NULL, "%s -d -u %d",
+ _PATH_MDCONFIG, mdunit);
+ if (error)
+ warn("mdconfig (detach) detach failed: %s%s%d",
+ _PATH_DEV, MD_NAME, mdunit);
+ }
+ }
+err:
+ if (sfd != NULL)
+ fclose(sfd);
+ if (fd != -1)
+ close(fd);
+ free(vnodefile);
+ return (ret);
+}
+
+static int
+run_cmd(int *ofd, const char *cmdline, ...)
+{
+ va_list ap;
+ char **argv, **argvp, *cmd, *p;
+ int argc, pid, status, rv;
+ int pfd[2], nfd, dup2dn;
+
+ va_start(ap, cmdline);
+ rv = vasprintf(&cmd, cmdline, ap);
+ if (rv == -1) {
+ warn("%s", __func__);
+ return (rv);
+ }
+ va_end(ap);
+
+ for (argc = 1, p = cmd; (p = strchr(p, ' ')) != NULL; p++)
+ argc++;
+ argv = (char **)malloc(sizeof(*argv) * (argc + 1));
+ for (p = cmd, argvp = argv; (*argvp = strsep(&p, " ")) != NULL;)
+ if (**argvp != '\0' && (++argvp > &argv[argc])) {
+ *argvp = NULL;
+ break;
+ }
+ /* The argv array ends up NULL-terminated here. */
+#if 0
+ {
+ int i;
+
+ fprintf(stderr, "DEBUG: running:");
+ /* Should be equivalent to 'cmd' (before strsep, of course). */
+ for (i = 0; argv[i] != NULL; i++)
+ fprintf(stderr, " %s", argv[i]);
+ fprintf(stderr, "\n");
+ }
+#endif
+ dup2dn = 1;
+ if (ofd != NULL) {
+ if (pipe(&pfd[0]) == -1) {
+ warn("%s: pipe", __func__);
+ return (-1);
+ }
+ *ofd = pfd[0];
+ dup2dn = 0;
+ }
+ pid = fork();
+ switch (pid) {
+ case 0:
+ /* Child process. */
+ if (ofd != NULL)
+ if (dup2(pfd[1], STDOUT_FILENO) < 0)
+ err(1, "dup2 in %s", __func__);
+ nfd = open(_PATH_DEVNULL, O_RDWR);
+ if (nfd == -1)
+ err(1, "%s: open %s", __func__, _PATH_DEVNULL);
+ if (dup2(nfd, STDIN_FILENO) < 0)
+ err(1, "%s: dup2", __func__);
+ if (dup2dn && dup2(nfd, STDOUT_FILENO) < 0)
+ err(1, "%s: dup2", __func__);
+ if (dup2(nfd, STDERR_FILENO) < 0)
+ err(1, "%s: dup2", __func__);
+ execv(argv[0], argv);
+ warn("exec: %s", argv[0]);
+ _exit(-1);
+ case -1:
+ err(1, "%s: fork", __func__);
+ }
+ free(cmd);
+ free(argv);
+ while (waitpid(pid, &status, 0) != pid)
+ ;
+ return (WEXITSTATUS(status));
+}
+
+static const char *
+swap_on_off_sfile(const char *name, int doingall)
+{
+ int error;
+
+ if (which_prog == SWAPON)
+ error = swapon(name);
+ else /* SWAPOFF */
+ error = swapoff(name);
+
+ if (error == -1) {
+ switch (errno) {
+ case EBUSY:
+ if (doingall == 0)
+ warnx("%s: Device already in use", name);
+ break;
+ case EINVAL:
+ if (which_prog == SWAPON)
+ warnx("%s: NSWAPDEV limit reached", name);
+ else if (doingall == 0)
+ warn("%s", name);
+ break;
+ default:
+ warn("%s", name);
+ break;
+ }
+ return (NULL);
+ }
+ return (name);
+}
+
+static void
+usage(void)
+{
+
+ fprintf(stderr, "usage: %s ", getprogname());
+ switch(orig_prog) {
+ case SWAPON:
+ case SWAPOFF:
+ fprintf(stderr, "[-F fstab] -aLq | file ...\n");
+ break;
+ case SWAPCTL:
+ fprintf(stderr, "[-AghklmsU] [-a file ... | -d file ...]\n");
+ break;
+ }
+ exit(1);
+}
+
+static void
+sizetobuf(char *buf, size_t bufsize, int hflag, long long val, int hlen,
+ long blocksize)
+{
+ char tmp[16];
+
+ if (hflag == 'H') {
+ humanize_number(tmp, 5, (int64_t)val, "", HN_AUTOSCALE,
+ HN_B | HN_NOSPACE | HN_DECIMAL);
+ snprintf(buf, bufsize, "%*s", hlen, tmp);
+ } else
+ snprintf(buf, bufsize, "%*lld", hlen, val / blocksize);
+}
+
+static void
+swaplist(int lflag, int sflag, int hflag)
+{
+ size_t mibsize, size;
+ struct xswdev xsw;
+ int hlen, mib[16], n, pagesize;
+ long blocksize;
+ long long total = 0;
+ long long used = 0;
+ long long tmp_total;
+ long long tmp_used;
+ char buf[32];
+
+ pagesize = getpagesize();
+ switch(hflag) {
+ case 'G':
+ blocksize = 1024 * 1024 * 1024;
+ strlcpy(buf, "1GB-blocks", sizeof(buf));
+ hlen = 10;
+ break;
+ case 'H':
+ blocksize = -1;
+ strlcpy(buf, "Bytes", sizeof(buf));
+ hlen = 10;
+ break;
+ case 'K':
+ blocksize = 1024;
+ strlcpy(buf, "1kB-blocks", sizeof(buf));
+ hlen = 10;
+ break;
+ case 'M':
+ blocksize = 1024 * 1024;
+ strlcpy(buf, "1MB-blocks", sizeof(buf));
+ hlen = 10;
+ break;
+ default:
+ getbsize(&hlen, &blocksize);
+ snprintf(buf, sizeof(buf), "%ld-blocks", blocksize);
+ break;
+ }
+
+ mibsize = nitems(mib);
+ if (sysctlnametomib("vm.swap_info", mib, &mibsize) == -1)
+ err(1, "sysctlnametomib()");
+
+ if (lflag) {
+ printf("%-13s %*s %*s\n",
+ "Device:",
+ hlen, buf,
+ hlen, "Used:");
+ }
+
+ for (n = 0; ; ++n) {
+ mib[mibsize] = n;
+ size = sizeof xsw;
+ if (sysctl(mib, mibsize + 1, &xsw, &size, NULL, 0) == -1)
+ break;
+ if (xsw.xsw_version != XSWDEV_VERSION)
+ errx(1, "xswdev version mismatch");
+
+ tmp_total = (long long)xsw.xsw_nblks * pagesize;
+ tmp_used = (long long)xsw.xsw_used * pagesize;
+ total += tmp_total;
+ used += tmp_used;
+ if (lflag) {
+ sizetobuf(buf, sizeof(buf), hflag, tmp_total, hlen,
+ blocksize);
+ printf("/dev/%-8s %s ", devname(xsw.xsw_dev, S_IFCHR),
+ buf);
+ sizetobuf(buf, sizeof(buf), hflag, tmp_used, hlen,
+ blocksize);
+ printf("%s\n", buf);
+ }
+ }
+ if (errno != ENOENT)
+ err(1, "sysctl()");
+
+ if (sflag) {
+ sizetobuf(buf, sizeof(buf), hflag, total, hlen, blocksize);
+ printf("Total: %s ", buf);
+ sizetobuf(buf, sizeof(buf), hflag, used, hlen, blocksize);
+ printf("%s\n", buf);
+ }
+}
+
diff --git a/sbin/sysctl/Makefile b/sbin/sysctl/Makefile
new file mode 100644
index 0000000..06ff9b4
--- /dev/null
+++ b/sbin/sysctl/Makefile
@@ -0,0 +1,8 @@
+# @(#)Makefile 8.1 (Berkeley) 6/6/93
+# $FreeBSD$
+
+PROG= sysctl
+WARNS?= 3
+MAN= sysctl.8
+
+.include <bsd.prog.mk>
diff --git a/sbin/sysctl/sysctl.8 b/sbin/sysctl/sysctl.8
new file mode 100644
index 0000000..cb8145e
--- /dev/null
+++ b/sbin/sysctl/sysctl.8
@@ -0,0 +1,324 @@
+.\" Copyright (c) 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.
+.\" 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.
+.\"
+.\" From: @(#)sysctl.8 8.1 (Berkeley) 6/6/93
+.\" $FreeBSD$
+.\"
+.Dd February 12, 2015
+.Dt SYSCTL 8
+.Os
+.Sh NAME
+.Nm sysctl
+.Nd get or set kernel state
+.Sh SYNOPSIS
+.Nm
+.Op Fl bdehiNnoRTqx
+.Op Fl B Ar bufsize
+.Op Fl f Ar filename
+.Ar name Ns Op = Ns Ar value
+.Ar ...
+.Nm
+.Op Fl bdehNnoRTqx
+.Op Fl B Ar bufsize
+.Fl a
+.Sh DESCRIPTION
+The
+.Nm
+utility retrieves kernel state and allows processes with appropriate
+privilege to set kernel state.
+The state to be retrieved or set is described using a
+.Dq Management Information Base
+.Pq Dq MIB
+style name, described as a dotted set of
+components.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl A
+Equivalent to
+.Fl o a
+(for compatibility).
+.It Fl a
+List all the currently available non-opaque values.
+This option is ignored if one or more variable names are specified on
+the command line.
+.It Fl b
+Force the value of the variable(s) to be output in raw, binary format.
+No names are printed and no terminating newlines are output.
+This is mostly useful with a single variable.
+.It Fl B Ar bufsize
+Set the buffer size to read from the
+.Nm
+to
+.Ar bufsize .
+This is necessary for a
+.Nm
+that has variable length, and the probe value of 0 is a valid length, such as
+.Va kern.arandom .
+.It Fl d
+Print the description of the variable instead of its value.
+.It Fl e
+Separate the name and the value of the variable(s) with
+.Ql = .
+This is useful for producing output which can be fed back to the
+.Nm
+utility.
+This option is ignored if either
+.Fl N
+or
+.Fl n
+is specified, or a variable is being set.
+.It Fl f Ar filename
+Specify a file which contains a pair of name and value in each line.
+.Nm
+reads and processes the specified file first and then processes the name
+and value pairs in the command line argument.
+.It Fl h
+Format output for human, rather than machine, readability.
+.It Fl i
+Ignore unknown OIDs.
+The purpose is to make use of
+.Nm
+for collecting data from a variety of machines (not all of which
+are necessarily running exactly the same software) easier.
+.It Fl N
+Show only variable names, not their values.
+This is particularly useful with shells that offer programmable
+completion.
+To enable completion of variable names in
+.Xr zsh 1 Pq Pa ports/shells/zsh ,
+use the following code:
+.Bd -literal -offset indent
+listsysctls () { set -A reply $(sysctl -AN ${1%.*}) }
+compctl -K listsysctls sysctl
+.Ed
+.Pp
+To enable completion of variable names in
+.Xr tcsh 1 ,
+use:
+.Pp
+.Dl "complete sysctl 'n/*/`sysctl -Na`/'"
+.It Fl n
+Show only variable values, not their names.
+This option is useful for setting shell variables.
+For instance, to save the pagesize in variable
+.Va psize ,
+use:
+.Pp
+.Dl "set psize=`sysctl -n hw.pagesize`"
+.It Fl o
+Show opaque variables (which are normally suppressed).
+The format and length are printed, as well as a hex dump of the first
+sixteen bytes of the value.
+.It Fl q
+Suppress some warnings generated by
+.Nm
+to standard error.
+.It Fl T
+Display only variables that are settable via loader (CTLFLAG_TUN).
+.It Fl W
+Display only writable variables that are not statistical.
+Useful for determining the set of runtime tunable sysctls.
+.It Fl X
+Equivalent to
+.Fl x a
+(for compatibility).
+.It Fl x
+As
+.Fl o ,
+but prints a hex dump of the entire value instead of just the first
+few bytes.
+.El
+.Pp
+The information available from
+.Nm
+consists of integers, strings, and opaque types.
+The
+.Nm
+utility
+only knows about a couple of opaque types, and will resort to hexdumps
+for the rest.
+The opaque information is much more useful if retrieved by special
+purpose programs such as
+.Xr ps 1 ,
+.Xr systat 1 ,
+and
+.Xr netstat 1 .
+.Pp
+Some of the variables which cannot be modified during normal system
+operation can be initialized via
+.Xr loader 8
+tunables.
+This can for example be done by setting them in
+.Xr loader.conf 5 .
+Please refer to
+.Xr loader.conf 5
+for more information on which tunables are available and how to set them.
+.Pp
+The string and integer information is summarized below.
+For a detailed description of these variable see
+.Xr sysctl 3 .
+.Pp
+The changeable column indicates whether a process with appropriate
+privilege can change the value.
+String and integer values can be set using
+.Nm .
+.Bl -column security.bsd.unprivileged_read_msgbuf integerxxx
+.It Sy "Name Type Changeable"
+.It "kern.ostype string no"
+.It "kern.osrelease string no"
+.It "kern.osrevision integer no"
+.It "kern.version string no"
+.It "kern.maxvnodes integer yes"
+.It "kern.maxproc integer no"
+.It "kern.maxprocperuid integer yes"
+.It "kern.maxfiles integer yes"
+.It "kern.maxfilesperproc integer yes"
+.It "kern.argmax integer no"
+.It "kern.securelevel integer raise only"
+.It "kern.hostname string yes"
+.It "kern.hostid integer yes"
+.It "kern.clockrate struct no"
+.It "kern.posix1version integer no"
+.It "kern.ngroups integer no"
+.It "kern.job_control integer no"
+.It "kern.saved_ids integer no"
+.It "kern.boottime struct no"
+.It "kern.domainname string yes"
+.It "kern.filedelay integer yes"
+.It "kern.dirdelay integer yes"
+.It "kern.metadelay integer yes"
+.It "kern.osreldate string no"
+.It "kern.bootfile string yes"
+.It "kern.corefile string yes"
+.It "kern.logsigexit integer yes"
+.It "security.bsd.suser_enabled integer yes"
+.It "security.bsd.see_other_uids integer yes"
+.It "security.bsd.unprivileged_proc_debug integer yes"
+.It "security.bsd.unprivileged_read_msgbuf integer yes"
+.It "vm.loadavg struct no"
+.It "hw.machine string no"
+.It "hw.model string no"
+.It "hw.ncpu integer no"
+.It "hw.byteorder integer no"
+.It "hw.physmem integer no"
+.It "hw.usermem integer no"
+.It "hw.pagesize integer no"
+.It "hw.floatingpoint integer no"
+.It "hw.machine_arch string no"
+.It "hw.realmem integer no"
+.It "machdep.adjkerntz integer yes"
+.It "machdep.disable_rtc_set integer yes"
+.It "machdep.guessed_bootdev string no"
+.It "user.cs_path string no"
+.It "user.bc_base_max integer no"
+.It "user.bc_dim_max integer no"
+.It "user.bc_scale_max integer no"
+.It "user.bc_string_max integer no"
+.It "user.coll_weights_max integer no"
+.It "user.expr_nest_max integer no"
+.It "user.line_max integer no"
+.It "user.re_dup_max integer no"
+.It "user.posix2_version integer no"
+.It "user.posix2_c_bind integer no"
+.It "user.posix2_c_dev integer no"
+.It "user.posix2_char_term integer no"
+.It "user.posix2_fort_dev integer no"
+.It "user.posix2_fort_run integer no"
+.It "user.posix2_localedef integer no"
+.It "user.posix2_sw_dev integer no"
+.It "user.posix2_upe integer no"
+.It "user.stream_max integer no"
+.It "user.tzname_max integer no"
+.El
+.Sh FILES
+.Bl -tag -width ".In netinet/icmp_var.h" -compact
+.It In sys/sysctl.h
+definitions for top level identifiers, second level kernel and hardware
+identifiers, and user level identifiers
+.It In sys/socket.h
+definitions for second level network identifiers
+.It In sys/gmon.h
+definitions for third level profiling identifiers
+.It In vm/vm_param.h
+definitions for second level virtual memory identifiers
+.It In netinet/in.h
+definitions for third level Internet identifiers and
+fourth level IP identifiers
+.It In netinet/icmp_var.h
+definitions for fourth level ICMP identifiers
+.It In netinet/udp_var.h
+definitions for fourth level UDP identifiers
+.El
+.Sh EXAMPLES
+For example, to retrieve the maximum number of processes allowed
+in the system, one would use the following request:
+.Pp
+.Dl "sysctl kern.maxproc"
+.Pp
+To set the maximum number of processes allowed
+per uid to 1000, one would use the following request:
+.Pp
+.Dl "sysctl kern.maxprocperuid=1000"
+.Pp
+Information about the system clock rate may be obtained with:
+.Pp
+.Dl "sysctl kern.clockrate"
+.Pp
+Information about the load average history may be obtained with:
+.Pp
+.Dl "sysctl vm.loadavg"
+.Pp
+More variables than these exist, and the best and likely only place
+to search for their deeper meaning is undoubtedly the source where
+they are defined.
+.Sh COMPATIBILITY
+The
+.Fl w
+option has been deprecated and is silently ignored.
+.Sh SEE ALSO
+.Xr sysctl 3 ,
+.Xr loader.conf 5 ,
+.Xr sysctl.conf 5 ,
+.Xr loader 8
+.Sh HISTORY
+A
+.Nm
+utility first appeared in
+.Bx 4.4 .
+.Pp
+In
+.Fx 2.2 ,
+.Nm
+was significantly remodeled.
+.Sh BUGS
+The
+.Nm
+utility presently exploits an undocumented interface to the kernel
+sysctl facility to traverse the sysctl tree and to retrieve format
+and name information.
+This correct interface is being thought about for the time being.
diff --git a/sbin/sysctl/sysctl.c b/sbin/sysctl/sysctl.c
new file mode 100644
index 0000000..f321120
--- /dev/null
+++ b/sbin/sysctl/sysctl.c
@@ -0,0 +1,997 @@
+/*
+ * Copyright (c) 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.
+ * 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 lint
+static const char copyright[] =
+"@(#) Copyright (c) 1993\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)from: sysctl.c 8.1 (Berkeley) 6/6/93";
+#endif
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <sys/sysctl.h>
+#include <sys/vmmeter.h>
+
+#ifdef __amd64__
+#include <sys/efi.h>
+#include <machine/metadata.h>
+#endif
+
+#if defined(__amd64__) || defined(__i386__)
+#include <machine/pc/bios.h>
+#endif
+
+#include <assert.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+static const char *conffile;
+
+static int aflag, bflag, Bflag, dflag, eflag, hflag, iflag;
+static int Nflag, nflag, oflag, qflag, Tflag, Wflag, xflag;
+
+static int oidfmt(int *, int, char *, u_int *);
+static int parsefile(const char *);
+static int parse(const char *, int);
+static int show_var(int *, int);
+static int sysctl_all(int *oid, int len);
+static int name2oid(const char *, int *);
+
+static int strIKtoi(const char *, char **);
+
+static int ctl_sign[CTLTYPE+1] = {
+ [CTLTYPE_INT] = 1,
+ [CTLTYPE_LONG] = 1,
+ [CTLTYPE_S64] = 1,
+};
+
+static int ctl_size[CTLTYPE+1] = {
+ [CTLTYPE_INT] = sizeof(int),
+ [CTLTYPE_UINT] = sizeof(u_int),
+ [CTLTYPE_LONG] = sizeof(long),
+ [CTLTYPE_ULONG] = sizeof(u_long),
+ [CTLTYPE_S64] = sizeof(int64_t),
+ [CTLTYPE_U64] = sizeof(uint64_t),
+};
+
+static const char *ctl_typename[CTLTYPE+1] = {
+ [CTLTYPE_INT] = "integer",
+ [CTLTYPE_UINT] = "unsigned integer",
+ [CTLTYPE_LONG] = "long integer",
+ [CTLTYPE_ULONG] = "unsigned long",
+ [CTLTYPE_S64] = "int64_t",
+ [CTLTYPE_U64] = "uint64_t",
+};
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr, "%s\n%s\n",
+ "usage: sysctl [-bdehiNnoqTWx] [ -B <bufsize> ] [-f filename] name[=value] ...",
+ " sysctl [-bdehNnoqTWx] [ -B <bufsize> ] -a");
+ exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+ int ch;
+ int warncount = 0;
+
+ setlocale(LC_NUMERIC, "");
+ setbuf(stdout,0);
+ setbuf(stderr,0);
+
+ while ((ch = getopt(argc, argv, "AabB:def:hiNnoqTwWxX")) != -1) {
+ switch (ch) {
+ case 'A':
+ /* compatibility */
+ aflag = oflag = 1;
+ break;
+ case 'a':
+ aflag = 1;
+ break;
+ case 'b':
+ bflag = 1;
+ break;
+ case 'B':
+ Bflag = strtol(optarg, NULL, 0);
+ break;
+ case 'd':
+ dflag = 1;
+ break;
+ case 'e':
+ eflag = 1;
+ break;
+ case 'f':
+ conffile = optarg;
+ break;
+ case 'h':
+ hflag = 1;
+ break;
+ case 'i':
+ iflag = 1;
+ break;
+ case 'N':
+ Nflag = 1;
+ break;
+ case 'n':
+ nflag = 1;
+ break;
+ case 'o':
+ oflag = 1;
+ break;
+ case 'q':
+ qflag = 1;
+ break;
+ case 'T':
+ Tflag = 1;
+ break;
+ case 'w':
+ /* compatibility */
+ /* ignored */
+ break;
+ case 'W':
+ Wflag = 1;
+ break;
+ case 'X':
+ /* compatibility */
+ aflag = xflag = 1;
+ break;
+ case 'x':
+ xflag = 1;
+ break;
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (Nflag && nflag)
+ usage();
+ if (aflag && argc == 0)
+ exit(sysctl_all(0, 0));
+ if (argc == 0 && conffile == NULL)
+ usage();
+
+ warncount = 0;
+ if (conffile != NULL)
+ warncount += parsefile(conffile);
+
+ while (argc-- > 0)
+ warncount += parse(*argv++, 0);
+
+ return (warncount);
+}
+
+/*
+ * Parse a name into a MIB entry.
+ * Lookup and print out the MIB entry if it exists.
+ * Set a new value if requested.
+ */
+static int
+parse(const char *string, int lineno)
+{
+ int len, i, j;
+ const void *newval;
+ const char *newvalstr = NULL;
+ int intval;
+ unsigned int uintval;
+ long longval;
+ unsigned long ulongval;
+ size_t newsize = Bflag;
+ int64_t i64val;
+ uint64_t u64val;
+ int mib[CTL_MAXNAME];
+ char *cp, *bufp, buf[BUFSIZ], *endptr = NULL, fmt[BUFSIZ], line[BUFSIZ];
+ u_int kind;
+
+ if (lineno)
+ snprintf(line, sizeof(line), " at line %d", lineno);
+ else
+ line[0] = '\0';
+
+ cp = buf;
+ if (snprintf(buf, BUFSIZ, "%s", string) >= BUFSIZ) {
+ warnx("oid too long: '%s'%s", string, line);
+ return (1);
+ }
+ bufp = strsep(&cp, "=:");
+ if (cp != NULL) {
+ /* Tflag just lists tunables, do not allow assignment */
+ if (Tflag || Wflag) {
+ warnx("Can't set variables when using -T or -W");
+ usage();
+ }
+ while (isspace(*cp))
+ cp++;
+ /* Strip a pair of " or ' if any. */
+ switch (*cp) {
+ case '\"':
+ case '\'':
+ if (cp[strlen(cp) - 1] == *cp)
+ cp[strlen(cp) - 1] = '\0';
+ cp++;
+ }
+ newvalstr = cp;
+ newsize = strlen(cp);
+ }
+ len = name2oid(bufp, mib);
+
+ if (len < 0) {
+ if (iflag)
+ return (0);
+ if (qflag)
+ return (1);
+ else {
+ warn("unknown oid '%s'%s", bufp, line);
+ return (1);
+ }
+ }
+
+ if (oidfmt(mib, len, fmt, &kind)) {
+ warn("couldn't find format of oid '%s'%s", bufp, line);
+ if (iflag)
+ return (1);
+ else
+ exit(1);
+ }
+
+ if (newvalstr == NULL || dflag) {
+ if ((kind & CTLTYPE) == CTLTYPE_NODE) {
+ if (dflag) {
+ i = show_var(mib, len);
+ if (!i && !bflag)
+ putchar('\n');
+ }
+ sysctl_all(mib, len);
+ } else {
+ i = show_var(mib, len);
+ if (!i && !bflag)
+ putchar('\n');
+ }
+ } else {
+ if ((kind & CTLTYPE) == CTLTYPE_NODE) {
+ warnx("oid '%s' isn't a leaf node%s", bufp, line);
+ return (1);
+ }
+
+ if (!(kind & CTLFLAG_WR)) {
+ if (kind & CTLFLAG_TUN) {
+ warnx("oid '%s' is a read only tunable%s", bufp, line);
+ warnx("Tunable values are set in /boot/loader.conf");
+ } else
+ warnx("oid '%s' is read only%s", bufp, line);
+ return (1);
+ }
+
+ switch (kind & CTLTYPE) {
+ case CTLTYPE_INT:
+ case CTLTYPE_UINT:
+ case CTLTYPE_LONG:
+ case CTLTYPE_ULONG:
+ case CTLTYPE_S64:
+ case CTLTYPE_U64:
+ if (strlen(newvalstr) == 0) {
+ warnx("empty numeric value");
+ return (1);
+ }
+ /* FALLTHROUGH */
+ case CTLTYPE_STRING:
+ break;
+ default:
+ warnx("oid '%s' is type %d,"
+ " cannot set that%s", bufp,
+ kind & CTLTYPE, line);
+ return (1);
+ }
+
+ errno = 0;
+
+ switch (kind & CTLTYPE) {
+ case CTLTYPE_INT:
+ if (strcmp(fmt, "IK") == 0)
+ intval = strIKtoi(newvalstr, &endptr);
+ else
+ intval = (int)strtol(newvalstr, &endptr,
+ 0);
+ newval = &intval;
+ newsize = sizeof(intval);
+ break;
+ case CTLTYPE_UINT:
+ uintval = (int) strtoul(newvalstr, &endptr, 0);
+ newval = &uintval;
+ newsize = sizeof(uintval);
+ break;
+ case CTLTYPE_LONG:
+ longval = strtol(newvalstr, &endptr, 0);
+ newval = &longval;
+ newsize = sizeof(longval);
+ break;
+ case CTLTYPE_ULONG:
+ ulongval = strtoul(newvalstr, &endptr, 0);
+ newval = &ulongval;
+ newsize = sizeof(ulongval);
+ break;
+ case CTLTYPE_STRING:
+ newval = newvalstr;
+ break;
+ case CTLTYPE_S64:
+ i64val = strtoimax(newvalstr, &endptr, 0);
+ newval = &i64val;
+ newsize = sizeof(i64val);
+ break;
+ case CTLTYPE_U64:
+ u64val = strtoumax(newvalstr, &endptr, 0);
+ newval = &u64val;
+ newsize = sizeof(u64val);
+ break;
+ default:
+ /* NOTREACHED */
+ abort();
+ }
+
+ if (errno != 0 || endptr == newvalstr ||
+ (endptr != NULL && *endptr != '\0')) {
+ warnx("invalid %s '%s'%s", ctl_typename[kind & CTLTYPE],
+ newvalstr, line);
+ return (1);
+ }
+
+ i = show_var(mib, len);
+ if (sysctl(mib, len, 0, 0, newval, newsize) == -1) {
+ if (!i && !bflag)
+ putchar('\n');
+ switch (errno) {
+ case EOPNOTSUPP:
+ warnx("%s: value is not available%s",
+ string, line);
+ return (1);
+ case ENOTDIR:
+ warnx("%s: specification is incomplete%s",
+ string, line);
+ return (1);
+ case ENOMEM:
+ warnx("%s: type is unknown to this program%s",
+ string, line);
+ return (1);
+ default:
+ warn("%s%s", string, line);
+ return (1);
+ }
+ }
+ if (!bflag)
+ printf(" -> ");
+ i = nflag;
+ nflag = 1;
+ j = show_var(mib, len);
+ if (!j && !bflag)
+ putchar('\n');
+ nflag = i;
+ }
+
+ return (0);
+}
+
+static int
+parsefile(const char *filename)
+{
+ FILE *file;
+ char line[BUFSIZ], *p, *pq, *pdq;
+ int warncount = 0, lineno = 0;
+
+ file = fopen(filename, "r");
+ if (file == NULL)
+ err(EX_NOINPUT, "%s", filename);
+ while (fgets(line, sizeof(line), file) != NULL) {
+ lineno++;
+ p = line;
+ pq = strchr(line, '\'');
+ pdq = strchr(line, '\"');
+ /* Replace the first # with \0. */
+ while((p = strchr(p, '#')) != NULL) {
+ if (pq != NULL && p > pq) {
+ if ((p = strchr(pq+1, '\'')) != NULL)
+ *(++p) = '\0';
+ break;
+ } else if (pdq != NULL && p > pdq) {
+ if ((p = strchr(pdq+1, '\"')) != NULL)
+ *(++p) = '\0';
+ break;
+ } else if (p == line || *(p-1) != '\\') {
+ *p = '\0';
+ break;
+ }
+ p++;
+ }
+ /* Trim spaces */
+ p = line + strlen(line) - 1;
+ while (p >= line && isspace((int)*p)) {
+ *p = '\0';
+ p--;
+ }
+ p = line;
+ while (isspace((int)*p))
+ p++;
+ if (*p == '\0')
+ continue;
+ else
+ warncount += parse(p, lineno);
+ }
+ fclose(file);
+
+ return (warncount);
+}
+
+/* These functions will dump out various interesting structures. */
+
+static int
+S_clockinfo(size_t l2, void *p)
+{
+ struct clockinfo *ci = (struct clockinfo*)p;
+
+ if (l2 != sizeof(*ci)) {
+ warnx("S_clockinfo %zu != %zu", l2, sizeof(*ci));
+ return (1);
+ }
+ printf(hflag ? "{ hz = %'d, tick = %'d, profhz = %'d, stathz = %'d }" :
+ "{ hz = %d, tick = %d, profhz = %d, stathz = %d }",
+ ci->hz, ci->tick, ci->profhz, ci->stathz);
+ return (0);
+}
+
+static int
+S_loadavg(size_t l2, void *p)
+{
+ struct loadavg *tv = (struct loadavg*)p;
+
+ if (l2 != sizeof(*tv)) {
+ warnx("S_loadavg %zu != %zu", l2, sizeof(*tv));
+ return (1);
+ }
+ printf(hflag ? "{ %'.2f %'.2f %'.2f }" : "{ %.2f %.2f %.2f }",
+ (double)tv->ldavg[0]/(double)tv->fscale,
+ (double)tv->ldavg[1]/(double)tv->fscale,
+ (double)tv->ldavg[2]/(double)tv->fscale);
+ return (0);
+}
+
+static int
+S_timeval(size_t l2, void *p)
+{
+ struct timeval *tv = (struct timeval*)p;
+ time_t tv_sec;
+ char *p1, *p2;
+
+ if (l2 != sizeof(*tv)) {
+ warnx("S_timeval %zu != %zu", l2, sizeof(*tv));
+ return (1);
+ }
+ printf(hflag ? "{ sec = %'jd, usec = %'ld } " :
+ "{ sec = %jd, usec = %ld } ",
+ (intmax_t)tv->tv_sec, tv->tv_usec);
+ tv_sec = tv->tv_sec;
+ p1 = strdup(ctime(&tv_sec));
+ for (p2=p1; *p2 ; p2++)
+ if (*p2 == '\n')
+ *p2 = '\0';
+ fputs(p1, stdout);
+ free(p1);
+ return (0);
+}
+
+static int
+S_vmtotal(size_t l2, void *p)
+{
+ struct vmtotal *v = (struct vmtotal *)p;
+ int pageKilo = getpagesize() / 1024;
+
+ if (l2 != sizeof(*v)) {
+ warnx("S_vmtotal %zu != %zu", l2, sizeof(*v));
+ return (1);
+ }
+
+ printf(
+ "\nSystem wide totals computed every five seconds:"
+ " (values in kilobytes)\n");
+ printf("===============================================\n");
+ printf(
+ "Processes:\t\t(RUNQ: %hd Disk Wait: %hd Page Wait: "
+ "%hd Sleep: %hd)\n",
+ v->t_rq, v->t_dw, v->t_pw, v->t_sl);
+ printf(
+ "Virtual Memory:\t\t(Total: %dK Active: %dK)\n",
+ v->t_vm * pageKilo, v->t_avm * pageKilo);
+ printf("Real Memory:\t\t(Total: %dK Active: %dK)\n",
+ v->t_rm * pageKilo, v->t_arm * pageKilo);
+ printf("Shared Virtual Memory:\t(Total: %dK Active: %dK)\n",
+ v->t_vmshr * pageKilo, v->t_avmshr * pageKilo);
+ printf("Shared Real Memory:\t(Total: %dK Active: %dK)\n",
+ v->t_rmshr * pageKilo, v->t_armshr * pageKilo);
+ printf("Free Memory:\t%dK", v->t_free * pageKilo);
+
+ return (0);
+}
+
+#ifdef __amd64__
+#define efi_next_descriptor(ptr, size) \
+ ((struct efi_md *)(((uint8_t *) ptr) + size))
+
+static int
+S_efi_map(size_t l2, void *p)
+{
+ struct efi_map_header *efihdr;
+ struct efi_md *map;
+ const char *type;
+ size_t efisz;
+ int ndesc, i;
+
+ static const char *types[] = {
+ "Reserved",
+ "LoaderCode",
+ "LoaderData",
+ "BootServicesCode",
+ "BootServicesData",
+ "RuntimeServicesCode",
+ "RuntimeServicesData",
+ "ConventionalMemory",
+ "UnusableMemory",
+ "ACPIReclaimMemory",
+ "ACPIMemoryNVS",
+ "MemoryMappedIO",
+ "MemoryMappedIOPortSpace",
+ "PalCode"
+ };
+
+ /*
+ * Memory map data provided by UEFI via the GetMemoryMap
+ * Boot Services API.
+ */
+ if (l2 < sizeof(*efihdr)) {
+ warnx("S_efi_map length less than header");
+ return (1);
+ }
+ efihdr = p;
+ efisz = (sizeof(struct efi_map_header) + 0xf) & ~0xf;
+ map = (struct efi_md *)((uint8_t *)efihdr + efisz);
+
+ if (efihdr->descriptor_size == 0)
+ return (0);
+ if (l2 != efisz + efihdr->memory_size) {
+ warnx("S_efi_map length mismatch %zu vs %zu", l2, efisz +
+ efihdr->memory_size);
+ return (1);
+ }
+ ndesc = efihdr->memory_size / efihdr->descriptor_size;
+
+ printf("\n%23s %12s %12s %8s %4s",
+ "Type", "Physical", "Virtual", "#Pages", "Attr");
+
+ for (i = 0; i < ndesc; i++,
+ map = efi_next_descriptor(map, efihdr->descriptor_size)) {
+ if (map->md_type <= EFI_MD_TYPE_PALCODE)
+ type = types[map->md_type];
+ else
+ type = "<INVALID>";
+ printf("\n%23s %012lx %12p %08lx ", type, map->md_phys,
+ map->md_virt, map->md_pages);
+ if (map->md_attr & EFI_MD_ATTR_UC)
+ printf("UC ");
+ if (map->md_attr & EFI_MD_ATTR_WC)
+ printf("WC ");
+ if (map->md_attr & EFI_MD_ATTR_WT)
+ printf("WT ");
+ if (map->md_attr & EFI_MD_ATTR_WB)
+ printf("WB ");
+ if (map->md_attr & EFI_MD_ATTR_UCE)
+ printf("UCE ");
+ if (map->md_attr & EFI_MD_ATTR_WP)
+ printf("WP ");
+ if (map->md_attr & EFI_MD_ATTR_RP)
+ printf("RP ");
+ if (map->md_attr & EFI_MD_ATTR_XP)
+ printf("XP ");
+ if (map->md_attr & EFI_MD_ATTR_RT)
+ printf("RUNTIME");
+ }
+ return (0);
+}
+#endif
+
+#if defined(__amd64__) || defined(__i386__)
+static int
+S_bios_smap_xattr(size_t l2, void *p)
+{
+ struct bios_smap_xattr *smap, *end;
+
+ if (l2 % sizeof(*smap) != 0) {
+ warnx("S_bios_smap_xattr %zu is not a multiple of %zu", l2,
+ sizeof(*smap));
+ return (1);
+ }
+
+ end = (struct bios_smap_xattr *)((char *)p + l2);
+ for (smap = p; smap < end; smap++)
+ printf("\nSMAP type=%02x, xattr=%02x, base=%016jx, len=%016jx",
+ smap->type, smap->xattr, (uintmax_t)smap->base,
+ (uintmax_t)smap->length);
+ return (0);
+}
+#endif
+
+static int
+strIKtoi(const char *str, char **endptrp)
+{
+ int kelv;
+ float temp;
+ size_t len;
+ const char *p;
+
+ assert(errno == 0);
+
+ len = strlen(str);
+ /* caller already checked this */
+ assert(len > 0);
+
+ p = &str[len - 1];
+ if (*p == 'C' || *p == 'F') {
+ temp = strtof(str, endptrp);
+ if (*endptrp != str && *endptrp == p && errno == 0) {
+ if (*p == 'F')
+ temp = (temp - 32) * 5 / 9;
+ *endptrp = NULL;
+ return (temp * 10 + 2732);
+ }
+ } else {
+ kelv = (int)strtol(str, endptrp, 10);
+ if (*endptrp != str && *endptrp == p && errno == 0) {
+ *endptrp = NULL;
+ return (kelv);
+ }
+ }
+
+ errno = ERANGE;
+ return (0);
+}
+
+/*
+ * These functions uses a presently undocumented interface to the kernel
+ * to walk the tree and get the type so it can print the value.
+ * This interface is under work and consideration, and should probably
+ * be killed with a big axe by the first person who can find the time.
+ * (be aware though, that the proper interface isn't as obvious as it
+ * may seem, there are various conflicting requirements.
+ */
+
+static int
+name2oid(const char *name, int *oidp)
+{
+ int oid[2];
+ int i;
+ size_t j;
+
+ oid[0] = 0;
+ oid[1] = 3;
+
+ j = CTL_MAXNAME * sizeof(int);
+ i = sysctl(oid, 2, oidp, &j, name, strlen(name));
+ if (i < 0)
+ return (i);
+ j /= sizeof(int);
+ return (j);
+}
+
+static int
+oidfmt(int *oid, int len, char *fmt, u_int *kind)
+{
+ int qoid[CTL_MAXNAME+2];
+ u_char buf[BUFSIZ];
+ int i;
+ size_t j;
+
+ qoid[0] = 0;
+ qoid[1] = 4;
+ memcpy(qoid + 2, oid, len * sizeof(int));
+
+ j = sizeof(buf);
+ i = sysctl(qoid, len + 2, buf, &j, 0, 0);
+ if (i)
+ err(1, "sysctl fmt %d %zu %d", i, j, errno);
+
+ if (kind)
+ *kind = *(u_int *)buf;
+
+ if (fmt)
+ strcpy(fmt, (char *)(buf + sizeof(u_int)));
+ return (0);
+}
+
+/*
+ * This formats and outputs the value of one variable
+ *
+ * Returns zero if anything was actually output.
+ * Returns one if didn't know what to do with this.
+ * Return minus one if we had errors.
+ */
+static int
+show_var(int *oid, int nlen)
+{
+ u_char buf[BUFSIZ], *val, *oval, *p;
+ char name[BUFSIZ], fmt[BUFSIZ];
+ const char *sep, *sep1;
+ int qoid[CTL_MAXNAME+2];
+ uintmax_t umv;
+ intmax_t mv;
+ int i, hexlen, sign, ctltype;
+ size_t intlen;
+ size_t j, len;
+ u_int kind;
+ int (*func)(size_t, void *);
+
+ /* Silence GCC. */
+ umv = mv = intlen = 0;
+
+ bzero(buf, BUFSIZ);
+ bzero(fmt, BUFSIZ);
+ bzero(name, BUFSIZ);
+ qoid[0] = 0;
+ memcpy(qoid + 2, oid, nlen * sizeof(int));
+
+ qoid[1] = 1;
+ j = sizeof(name);
+ i = sysctl(qoid, nlen + 2, name, &j, 0, 0);
+ if (i || !j)
+ err(1, "sysctl name %d %zu %d", i, j, errno);
+
+ oidfmt(oid, nlen, fmt, &kind);
+ /* if Wflag then only list sysctls that are writeable and not stats. */
+ if (Wflag && ((kind & CTLFLAG_WR) == 0 || (kind & CTLFLAG_STATS) != 0))
+ return 1;
+
+ /* if Tflag then only list sysctls that are tuneables. */
+ if (Tflag && (kind & CTLFLAG_TUN) == 0)
+ return 1;
+
+ if (Nflag) {
+ printf("%s", name);
+ return (0);
+ }
+
+ if (eflag)
+ sep = "=";
+ else
+ sep = ": ";
+
+ if (dflag) { /* just print description */
+ qoid[1] = 5;
+ j = sizeof(buf);
+ i = sysctl(qoid, nlen + 2, buf, &j, 0, 0);
+ if (!nflag)
+ printf("%s%s", name, sep);
+ printf("%s", buf);
+ return (0);
+ }
+ /* find an estimate of how much we need for this var */
+ if (Bflag)
+ j = Bflag;
+ else {
+ j = 0;
+ i = sysctl(oid, nlen, 0, &j, 0, 0);
+ j += j; /* we want to be sure :-) */
+ }
+
+ val = oval = malloc(j + 1);
+ if (val == NULL) {
+ warnx("malloc failed");
+ return (1);
+ }
+ ctltype = (kind & CTLTYPE);
+ len = j;
+ i = sysctl(oid, nlen, val, &len, 0, 0);
+ if (i != 0 || (len == 0 && ctltype != CTLTYPE_STRING)) {
+ free(oval);
+ return (1);
+ }
+
+ if (bflag) {
+ fwrite(val, 1, len, stdout);
+ free(oval);
+ return (0);
+ }
+ val[len] = '\0';
+ p = val;
+ sign = ctl_sign[ctltype];
+ intlen = ctl_size[ctltype];
+
+ switch (ctltype) {
+ case CTLTYPE_STRING:
+ if (!nflag)
+ printf("%s%s", name, sep);
+ printf("%.*s", (int)len, p);
+ free(oval);
+ return (0);
+
+ case CTLTYPE_INT:
+ case CTLTYPE_UINT:
+ case CTLTYPE_LONG:
+ case CTLTYPE_ULONG:
+ case CTLTYPE_S64:
+ case CTLTYPE_U64:
+ if (!nflag)
+ printf("%s%s", name, sep);
+ hexlen = 2 + (intlen * CHAR_BIT + 3) / 4;
+ sep1 = "";
+ while (len >= intlen) {
+ switch (kind & CTLTYPE) {
+ case CTLTYPE_INT:
+ case CTLTYPE_UINT:
+ umv = *(u_int *)p;
+ mv = *(int *)p;
+ break;
+ case CTLTYPE_LONG:
+ case CTLTYPE_ULONG:
+ umv = *(u_long *)p;
+ mv = *(long *)p;
+ break;
+ case CTLTYPE_S64:
+ case CTLTYPE_U64:
+ umv = *(uint64_t *)p;
+ mv = *(int64_t *)p;
+ break;
+ }
+ fputs(sep1, stdout);
+ if (xflag)
+ printf("%#0*jx", hexlen, umv);
+ else if (!sign)
+ printf(hflag ? "%'ju" : "%ju", umv);
+ else if (fmt[1] == 'K') {
+ if (mv < 0)
+ printf("%jd", mv);
+ else
+ printf("%.1fC", (mv - 2732.0) / 10);
+ } else
+ printf(hflag ? "%'jd" : "%jd", mv);
+ sep1 = " ";
+ len -= intlen;
+ p += intlen;
+ }
+ free(oval);
+ return (0);
+
+ case CTLTYPE_OPAQUE:
+ i = 0;
+ if (strcmp(fmt, "S,clockinfo") == 0)
+ func = S_clockinfo;
+ else if (strcmp(fmt, "S,timeval") == 0)
+ func = S_timeval;
+ else if (strcmp(fmt, "S,loadavg") == 0)
+ func = S_loadavg;
+ else if (strcmp(fmt, "S,vmtotal") == 0)
+ func = S_vmtotal;
+#ifdef __amd64__
+ else if (strcmp(fmt, "S,efi_map_header") == 0)
+ func = S_efi_map;
+#endif
+#if defined(__amd64__) || defined(__i386__)
+ else if (strcmp(fmt, "S,bios_smap_xattr") == 0)
+ func = S_bios_smap_xattr;
+#endif
+ else
+ func = NULL;
+ if (func) {
+ if (!nflag)
+ printf("%s%s", name, sep);
+ i = (*func)(len, p);
+ free(oval);
+ return (i);
+ }
+ /* FALLTHROUGH */
+ default:
+ if (!oflag && !xflag) {
+ free(oval);
+ return (1);
+ }
+ if (!nflag)
+ printf("%s%s", name, sep);
+ printf("Format:%s Length:%zu Dump:0x", fmt, len);
+ while (len-- && (xflag || p < val + 16))
+ printf("%02x", *p++);
+ if (!xflag && len > 16)
+ printf("...");
+ free(oval);
+ return (0);
+ }
+ free(oval);
+ return (1);
+}
+
+static int
+sysctl_all(int *oid, int len)
+{
+ int name1[22], name2[22];
+ int i, j;
+ size_t l1, l2;
+
+ name1[0] = 0;
+ name1[1] = 2;
+ l1 = 2;
+ if (len) {
+ memcpy(name1+2, oid, len * sizeof(int));
+ l1 += len;
+ } else {
+ name1[2] = 1;
+ l1++;
+ }
+ for (;;) {
+ l2 = sizeof(name2);
+ j = sysctl(name1, l1, name2, &l2, 0, 0);
+ if (j < 0) {
+ if (errno == ENOENT)
+ return (0);
+ else
+ err(1, "sysctl(getnext) %d %zu", j, l2);
+ }
+
+ l2 /= sizeof(int);
+
+ if (len < 0 || l2 < (unsigned int)len)
+ return (0);
+
+ for (i = 0; i < len; i++)
+ if (name2[i] != oid[i])
+ return (0);
+
+ i = show_var(name2, l2);
+ if (!i && !bflag)
+ putchar('\n');
+
+ memcpy(name1+2, name2, l2 * sizeof(int));
+ l1 = 2 + l2;
+ }
+}
diff --git a/sbin/tests/Makefile b/sbin/tests/Makefile
new file mode 100644
index 0000000..a298f87
--- /dev/null
+++ b/sbin/tests/Makefile
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+.include <bsd.own.mk>
+
+TESTSDIR= ${TESTSBASE}/sbin
+
+.PATH: ${.CURDIR:H:H}/tests
+KYUAFILE= yes
+
+.include <bsd.test.mk>
diff --git a/sbin/tunefs/Makefile b/sbin/tunefs/Makefile
new file mode 100644
index 0000000..07fe3b1
--- /dev/null
+++ b/sbin/tunefs/Makefile
@@ -0,0 +1,10 @@
+# @(#)Makefile 8.1 (Berkeley) 6/5/93
+# $FreeBSD$
+
+PROG= tunefs
+LIBADD= ufs
+MAN= tunefs.8
+
+WARNS= 3
+
+.include <bsd.prog.mk>
diff --git a/sbin/tunefs/tunefs.8 b/sbin/tunefs/tunefs.8
new file mode 100644
index 0000000..a58c174
--- /dev/null
+++ b/sbin/tunefs/tunefs.8
@@ -0,0 +1,208 @@
+.\" Copyright (c) 1983, 1991, 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.
+.\" 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.
+.\"
+.\" @(#)tunefs.8 8.2 (Berkeley) 12/11/93
+.\" $FreeBSD$
+.\"
+.Dd June 22, 2011
+.Dt TUNEFS 8
+.Os
+.Sh NAME
+.Nm tunefs
+.Nd tune up an existing UFS file system
+.Sh SYNOPSIS
+.Nm
+.Op Fl A
+.Op Fl a Cm enable | disable
+.Op Fl e Ar maxbpg
+.Op Fl f Ar avgfilesize
+.Op Fl j Cm enable | disable
+.Op Fl J Cm enable | disable
+.Op Fl k Ar held-for-metadata-blocks
+.Op Fl L Ar volname
+.Op Fl l Cm enable | disable
+.Op Fl m Ar minfree
+.Op Fl N Cm enable | disable
+.Op Fl n Cm enable | disable
+.Op Fl o Cm space | time
+.Op Fl p
+.Op Fl s Ar avgfpdir
+.Op Fl S Ar size
+.Op Fl t Cm enable | disable
+.Ar special | filesystem
+.Sh DESCRIPTION
+The
+.Nm
+utility is designed to change the dynamic parameters of a UFS file system
+which affect the layout policies.
+The
+.Nm
+utility cannot be run on an active file system.
+To change an active file system,
+it must be downgraded to read-only or unmounted.
+.Pp
+The parameters which are to be changed are indicated by the flags
+given below:
+.Bl -tag -width indent
+.It Fl A
+The file system has several backups of the super-block.
+Specifying
+this option will cause all backups to be modified as well as the
+primary super-block.
+This is potentially dangerous - use with caution.
+.It Fl a Cm enable | disable
+Turn on/off the administrative POSIX.1e ACL enable flag.
+.It Fl e Ar maxbpg
+Indicate the maximum number of blocks any single file can
+allocate out of a cylinder group before it is forced to begin
+allocating blocks from another cylinder group.
+Typically this value is set to about one quarter of the total blocks
+in a cylinder group.
+The intent is to prevent any single file from using up all the
+blocks in a single cylinder group,
+thus degrading access times for all files subsequently allocated
+in that cylinder group.
+The effect of this limit is to cause big files to do long seeks
+more frequently than if they were allowed to allocate all the blocks
+in a cylinder group before seeking elsewhere.
+For file systems with exclusively large files,
+this parameter should be set higher.
+.It Fl f Ar avgfilesize
+Specify the expected average file size.
+.It Fl j Cm enable | disable
+Turn on/off soft updates journaling.
+.It Fl J Cm enable | disable
+Turn on/off gjournal flag.
+.It Fl k Ar held-for-metadata-blocks
+Set the amount of space to be held for metadata blocks.
+When set, the file system preference routines will try to save
+the specified amount of space immediately following the inode blocks
+in each cylinder group for use by metadata blocks.
+Clustering the metadata blocks speeds up random file access
+and decreases the running time of
+.Xr fsck 8 .
+While this option can be set at any time,
+it is most effective if set before any data is loaded into the file system.
+By default
+.Xr newfs 8
+sets it to half of the space reserved to minfree.
+.It Fl L Ar volname
+Add/modify an optional file system volume label.
+.It Fl l Cm enable | disable
+Turn on/off MAC multilabel flag.
+.It Fl m Ar minfree
+Specify the percentage of space held back
+from normal users; the minimum free space threshold.
+The default value used is 8%.
+Note that lowering the threshold can adversely affect performance:
+.Bl -bullet
+.It
+Settings of 5% and less force space optimization to
+always be used which will greatly increase the overhead for file
+writes.
+.It
+The file system's ability to avoid fragmentation will be reduced
+when the total free space, including the reserve, drops below 15%.
+As free space approaches zero, throughput can degrade by up to a
+factor of three over the performance obtained at a 10% threshold.
+.El
+.Pp
+If the value is raised above the current usage level,
+users will be unable to allocate files until enough files have
+been deleted to get under the higher threshold.
+.It Fl N Cm enable | disable
+Turn on/off the administrative NFSv4 ACL enable flag.
+.It Fl n Cm enable | disable
+Turn on/off soft updates.
+.It Fl o Cm space | time
+The file system can either try to minimize the time spent
+allocating blocks, or it can attempt to minimize the space
+fragmentation on the disk.
+Optimization for space has much
+higher overhead for file writes.
+The kernel normally changes the preference automatically as
+the percent fragmentation changes on the file system.
+.It Fl p
+Show a summary of what the current tunable settings
+are on the selected file system.
+More detailed information can be
+obtained from the
+.Xr dumpfs 8
+utility.
+.It Fl s Ar avgfpdir
+Specify the expected number of files per directory.
+.It Fl S Ar size
+Specify the softdep journal size in bytes.
+The minimum is 4M.
+.It Fl t Cm enable | disable
+Turn on/off the TRIM enable flag.
+If enabled, and if the underlying device supports the BIO_DELETE
+command, the file system will send a delete request to the underlying
+device for each freed block.
+The trim enable flag is typically set when the underlying device
+uses flash-memory as the device can use the delete command to
+pre-zero or at least avoid copying blocks that have been deleted.
+.El
+.Pp
+At least one of the above flags is required.
+.Sh FILES
+.Bl -tag -width ".Pa /etc/fstab"
+.It Pa /etc/fstab
+read this to determine the device file for a
+specified mount point.
+.El
+.Sh SEE ALSO
+.Xr fs 5 ,
+.Xr dumpfs 8 ,
+.Xr gjournal 8 ,
+.Xr growfs 8 ,
+.Xr newfs 8
+.Rs
+.%A M. McKusick
+.%A W. Joy
+.%A S. Leffler
+.%A R. Fabry
+.%T "A Fast File System for UNIX"
+.%J "ACM Transactions on Computer Systems 2"
+.%N 3
+.%P pp 181-197
+.%D August 1984
+.%O "(reprinted in the BSD System Manager's Manual, SMM:5)"
+.Re
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Bx 4.2 .
+.Sh BUGS
+This utility does not work on active file systems.
+To change the root file system, the system must be rebooted
+after the file system is tuned.
+.\" Take this out and a Unix Daemon will dog your steps from now until
+.\" the time_t's wrap around.
+.Pp
+You can tune a file system, but you cannot tune a fish.
diff --git a/sbin/tunefs/tunefs.c b/sbin/tunefs/tunefs.c
new file mode 100644
index 0000000..15c6cf0
--- /dev/null
+++ b/sbin/tunefs/tunefs.c
@@ -0,0 +1,1135 @@
+/*
+ * Copyright (c) 1983, 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.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if 0
+#ifndef lint
+static const char copyright[] =
+"@(#) Copyright (c) 1983, 1993\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static char sccsid[] = "@(#)tunefs.c 8.2 (Berkeley) 4/19/94";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*
+ * tunefs: change layout parameters to an existing file system.
+ */
+#include <sys/param.h>
+#include <sys/mount.h>
+#include <sys/disklabel.h>
+#include <sys/stat.h>
+
+#include <ufs/ufs/ufsmount.h>
+#include <ufs/ufs/dinode.h>
+#include <ufs/ffs/fs.h>
+#include <ufs/ufs/dir.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <fcntl.h>
+#include <fstab.h>
+#include <libufs.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+/* the optimization warning string template */
+#define OPTWARN "should optimize for %s with minfree %s %d%%"
+
+static int blocks;
+static char clrbuf[MAXBSIZE];
+static struct uufsd disk;
+#define sblock disk.d_fs
+
+static void usage(void);
+static void printfs(void);
+static int journal_alloc(int64_t size);
+static void journal_clear(void);
+static void sbdirty(void);
+
+int
+main(int argc, char *argv[])
+{
+ const char *avalue, *jvalue, *Jvalue, *Lvalue, *lvalue, *Nvalue, *nvalue;
+ const char *tvalue;
+ const char *special, *on;
+ const char *name;
+ int active;
+ int Aflag, aflag, eflag, evalue, fflag, fvalue, jflag, Jflag, kflag;
+ int kvalue, Lflag, lflag, mflag, mvalue, Nflag, nflag, oflag, ovalue;
+ int pflag, sflag, svalue, Svalue, tflag;
+ int ch, found_arg, i;
+ const char *chg[2];
+ struct ufs_args args;
+ struct statfs stfs;
+
+ if (argc < 3)
+ usage();
+ Aflag = aflag = eflag = fflag = jflag = Jflag = kflag = Lflag = 0;
+ lflag = mflag = Nflag = nflag = oflag = pflag = sflag = tflag = 0;
+ avalue = jvalue = Jvalue = Lvalue = lvalue = Nvalue = nvalue = NULL;
+ evalue = fvalue = mvalue = ovalue = svalue = Svalue = 0;
+ active = 0;
+ found_arg = 0; /* At least one arg is required. */
+ while ((ch = getopt(argc, argv, "Aa:e:f:j:J:k:L:l:m:N:n:o:ps:S:t:"))
+ != -1)
+ switch (ch) {
+
+ case 'A':
+ found_arg = 1;
+ Aflag++;
+ break;
+
+ case 'a':
+ found_arg = 1;
+ name = "POSIX.1e ACLs";
+ avalue = optarg;
+ if (strcmp(avalue, "enable") &&
+ strcmp(avalue, "disable")) {
+ errx(10, "bad %s (options are %s)",
+ name, "`enable' or `disable'");
+ }
+ aflag = 1;
+ break;
+
+ case 'e':
+ found_arg = 1;
+ name = "maximum blocks per file in a cylinder group";
+ evalue = atoi(optarg);
+ if (evalue < 1)
+ errx(10, "%s must be >= 1 (was %s)",
+ name, optarg);
+ eflag = 1;
+ break;
+
+ case 'f':
+ found_arg = 1;
+ name = "average file size";
+ fvalue = atoi(optarg);
+ if (fvalue < 1)
+ errx(10, "%s must be >= 1 (was %s)",
+ name, optarg);
+ fflag = 1;
+ break;
+
+ case 'j':
+ found_arg = 1;
+ name = "softdep journaled file system";
+ jvalue = optarg;
+ if (strcmp(jvalue, "enable") &&
+ strcmp(jvalue, "disable")) {
+ errx(10, "bad %s (options are %s)",
+ name, "`enable' or `disable'");
+ }
+ jflag = 1;
+ break;
+
+ case 'J':
+ found_arg = 1;
+ name = "gjournaled file system";
+ Jvalue = optarg;
+ if (strcmp(Jvalue, "enable") &&
+ strcmp(Jvalue, "disable")) {
+ errx(10, "bad %s (options are %s)",
+ name, "`enable' or `disable'");
+ }
+ Jflag = 1;
+ break;
+
+ case 'k':
+ found_arg = 1;
+ name = "space to hold for metadata blocks";
+ kvalue = atoi(optarg);
+ if (kvalue < 0)
+ errx(10, "bad %s (%s)", name, optarg);
+ kflag = 1;
+ break;
+
+ case 'L':
+ found_arg = 1;
+ name = "volume label";
+ Lvalue = optarg;
+ i = -1;
+ while (isalnum(Lvalue[++i]));
+ if (Lvalue[i] != '\0') {
+ errx(10,
+ "bad %s. Valid characters are alphanumerics.",
+ name);
+ }
+ if (strlen(Lvalue) >= MAXVOLLEN) {
+ errx(10, "bad %s. Length is longer than %d.",
+ name, MAXVOLLEN - 1);
+ }
+ Lflag = 1;
+ break;
+
+ case 'l':
+ found_arg = 1;
+ name = "multilabel MAC file system";
+ lvalue = optarg;
+ if (strcmp(lvalue, "enable") &&
+ strcmp(lvalue, "disable")) {
+ errx(10, "bad %s (options are %s)",
+ name, "`enable' or `disable'");
+ }
+ lflag = 1;
+ break;
+
+ case 'm':
+ found_arg = 1;
+ name = "minimum percentage of free space";
+ mvalue = atoi(optarg);
+ if (mvalue < 0 || mvalue > 99)
+ errx(10, "bad %s (%s)", name, optarg);
+ mflag = 1;
+ break;
+
+ case 'N':
+ found_arg = 1;
+ name = "NFSv4 ACLs";
+ Nvalue = optarg;
+ if (strcmp(Nvalue, "enable") &&
+ strcmp(Nvalue, "disable")) {
+ errx(10, "bad %s (options are %s)",
+ name, "`enable' or `disable'");
+ }
+ Nflag = 1;
+ break;
+
+ case 'n':
+ found_arg = 1;
+ name = "soft updates";
+ nvalue = optarg;
+ if (strcmp(nvalue, "enable") != 0 &&
+ strcmp(nvalue, "disable") != 0) {
+ errx(10, "bad %s (options are %s)",
+ name, "`enable' or `disable'");
+ }
+ nflag = 1;
+ break;
+
+ case 'o':
+ found_arg = 1;
+ name = "optimization preference";
+ if (strcmp(optarg, "space") == 0)
+ ovalue = FS_OPTSPACE;
+ else if (strcmp(optarg, "time") == 0)
+ ovalue = FS_OPTTIME;
+ else
+ errx(10,
+ "bad %s (options are `space' or `time')",
+ name);
+ oflag = 1;
+ break;
+
+ case 'p':
+ found_arg = 1;
+ pflag = 1;
+ break;
+
+ case 's':
+ found_arg = 1;
+ name = "expected number of files per directory";
+ svalue = atoi(optarg);
+ if (svalue < 1)
+ errx(10, "%s must be >= 1 (was %s)",
+ name, optarg);
+ sflag = 1;
+ break;
+
+ case 'S':
+ found_arg = 1;
+ name = "Softdep Journal Size";
+ Svalue = atoi(optarg);
+ if (Svalue < SUJ_MIN)
+ errx(10, "%s must be >= %d (was %s)",
+ name, SUJ_MIN, optarg);
+ break;
+
+ case 't':
+ found_arg = 1;
+ name = "trim";
+ tvalue = optarg;
+ if (strcmp(tvalue, "enable") != 0 &&
+ strcmp(tvalue, "disable") != 0) {
+ errx(10, "bad %s (options are %s)",
+ name, "`enable' or `disable'");
+ }
+ tflag = 1;
+ break;
+
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+ if (found_arg == 0 || argc != 1)
+ usage();
+
+ on = special = argv[0];
+ if (ufs_disk_fillout(&disk, special) == -1)
+ goto err;
+ if (disk.d_name != special) {
+ if (statfs(special, &stfs) != 0)
+ warn("Can't stat %s", special);
+ if (strcmp(special, stfs.f_mntonname) == 0)
+ active = 1;
+ }
+
+ if (pflag) {
+ printfs();
+ exit(0);
+ }
+ if (Lflag) {
+ name = "volume label";
+ strlcpy(sblock.fs_volname, Lvalue, MAXVOLLEN);
+ }
+ if (aflag) {
+ name = "POSIX.1e ACLs";
+ if (strcmp(avalue, "enable") == 0) {
+ if (sblock.fs_flags & FS_ACLS) {
+ warnx("%s remains unchanged as enabled", name);
+ } else if (sblock.fs_flags & FS_NFS4ACLS) {
+ warnx("%s and NFSv4 ACLs are mutually "
+ "exclusive", name);
+ } else {
+ sblock.fs_flags |= FS_ACLS;
+ warnx("%s set", name);
+ }
+ } else if (strcmp(avalue, "disable") == 0) {
+ if ((~sblock.fs_flags & FS_ACLS) ==
+ FS_ACLS) {
+ warnx("%s remains unchanged as disabled",
+ name);
+ } else {
+ sblock.fs_flags &= ~FS_ACLS;
+ warnx("%s cleared", name);
+ }
+ }
+ }
+ if (eflag) {
+ name = "maximum blocks per file in a cylinder group";
+ if (sblock.fs_maxbpg == evalue)
+ warnx("%s remains unchanged as %d", name, evalue);
+ else {
+ warnx("%s changes from %d to %d",
+ name, sblock.fs_maxbpg, evalue);
+ sblock.fs_maxbpg = evalue;
+ }
+ }
+ if (fflag) {
+ name = "average file size";
+ if (sblock.fs_avgfilesize == (unsigned)fvalue) {
+ warnx("%s remains unchanged as %d", name, fvalue);
+ }
+ else {
+ warnx("%s changes from %d to %d",
+ name, sblock.fs_avgfilesize, fvalue);
+ sblock.fs_avgfilesize = fvalue;
+ }
+ }
+ if (jflag) {
+ name = "soft updates journaling";
+ if (strcmp(jvalue, "enable") == 0) {
+ if ((sblock.fs_flags & (FS_DOSOFTDEP | FS_SUJ)) ==
+ (FS_DOSOFTDEP | FS_SUJ)) {
+ warnx("%s remains unchanged as enabled", name);
+ } else if (sblock.fs_clean == 0) {
+ warnx("%s cannot be enabled until fsck is run",
+ name);
+ } else if (journal_alloc(Svalue) != 0) {
+ warnx("%s can not be enabled", name);
+ } else {
+ sblock.fs_flags |= FS_DOSOFTDEP | FS_SUJ;
+ warnx("%s set", name);
+ }
+ } else if (strcmp(jvalue, "disable") == 0) {
+ if ((~sblock.fs_flags & FS_SUJ) == FS_SUJ) {
+ warnx("%s remains unchanged as disabled", name);
+ } else {
+ journal_clear();
+ sblock.fs_flags &= ~FS_SUJ;
+ sblock.fs_sujfree = 0;
+ warnx("%s cleared but soft updates still set.",
+ name);
+
+ warnx("remove .sujournal to reclaim space");
+ }
+ }
+ }
+ if (Jflag) {
+ name = "gjournal";
+ if (strcmp(Jvalue, "enable") == 0) {
+ if (sblock.fs_flags & FS_GJOURNAL) {
+ warnx("%s remains unchanged as enabled", name);
+ } else {
+ sblock.fs_flags |= FS_GJOURNAL;
+ warnx("%s set", name);
+ }
+ } else if (strcmp(Jvalue, "disable") == 0) {
+ if ((~sblock.fs_flags & FS_GJOURNAL) ==
+ FS_GJOURNAL) {
+ warnx("%s remains unchanged as disabled",
+ name);
+ } else {
+ sblock.fs_flags &= ~FS_GJOURNAL;
+ warnx("%s cleared", name);
+ }
+ }
+ }
+ if (kflag) {
+ name = "space to hold for metadata blocks";
+ if (sblock.fs_metaspace == kvalue)
+ warnx("%s remains unchanged as %d", name, kvalue);
+ else {
+ kvalue = blknum(&sblock, kvalue);
+ if (kvalue > sblock.fs_fpg / 2) {
+ kvalue = blknum(&sblock, sblock.fs_fpg / 2);
+ warnx("%s cannot exceed half the file system "
+ "space", name);
+ }
+ warnx("%s changes from %jd to %d",
+ name, sblock.fs_metaspace, kvalue);
+ sblock.fs_metaspace = kvalue;
+ }
+ }
+ if (lflag) {
+ name = "multilabel";
+ if (strcmp(lvalue, "enable") == 0) {
+ if (sblock.fs_flags & FS_MULTILABEL) {
+ warnx("%s remains unchanged as enabled", name);
+ } else {
+ sblock.fs_flags |= FS_MULTILABEL;
+ warnx("%s set", name);
+ }
+ } else if (strcmp(lvalue, "disable") == 0) {
+ if ((~sblock.fs_flags & FS_MULTILABEL) ==
+ FS_MULTILABEL) {
+ warnx("%s remains unchanged as disabled",
+ name);
+ } else {
+ sblock.fs_flags &= ~FS_MULTILABEL;
+ warnx("%s cleared", name);
+ }
+ }
+ }
+ if (mflag) {
+ name = "minimum percentage of free space";
+ if (sblock.fs_minfree == mvalue)
+ warnx("%s remains unchanged as %d%%", name, mvalue);
+ else {
+ warnx("%s changes from %d%% to %d%%",
+ name, sblock.fs_minfree, mvalue);
+ sblock.fs_minfree = mvalue;
+ if (mvalue >= MINFREE && sblock.fs_optim == FS_OPTSPACE)
+ warnx(OPTWARN, "time", ">=", MINFREE);
+ if (mvalue < MINFREE && sblock.fs_optim == FS_OPTTIME)
+ warnx(OPTWARN, "space", "<", MINFREE);
+ }
+ }
+ if (Nflag) {
+ name = "NFSv4 ACLs";
+ if (strcmp(Nvalue, "enable") == 0) {
+ if (sblock.fs_flags & FS_NFS4ACLS) {
+ warnx("%s remains unchanged as enabled", name);
+ } else if (sblock.fs_flags & FS_ACLS) {
+ warnx("%s and POSIX.1e ACLs are mutually "
+ "exclusive", name);
+ } else {
+ sblock.fs_flags |= FS_NFS4ACLS;
+ warnx("%s set", name);
+ }
+ } else if (strcmp(Nvalue, "disable") == 0) {
+ if ((~sblock.fs_flags & FS_NFS4ACLS) ==
+ FS_NFS4ACLS) {
+ warnx("%s remains unchanged as disabled",
+ name);
+ } else {
+ sblock.fs_flags &= ~FS_NFS4ACLS;
+ warnx("%s cleared", name);
+ }
+ }
+ }
+ if (nflag) {
+ name = "soft updates";
+ if (strcmp(nvalue, "enable") == 0) {
+ if (sblock.fs_flags & FS_DOSOFTDEP)
+ warnx("%s remains unchanged as enabled", name);
+ else if (sblock.fs_clean == 0) {
+ warnx("%s cannot be enabled until fsck is run",
+ name);
+ } else {
+ sblock.fs_flags |= FS_DOSOFTDEP;
+ warnx("%s set", name);
+ }
+ } else if (strcmp(nvalue, "disable") == 0) {
+ if ((~sblock.fs_flags & FS_DOSOFTDEP) == FS_DOSOFTDEP)
+ warnx("%s remains unchanged as disabled", name);
+ else {
+ sblock.fs_flags &= ~FS_DOSOFTDEP;
+ warnx("%s cleared", name);
+ }
+ }
+ }
+ if (oflag) {
+ name = "optimization preference";
+ chg[FS_OPTSPACE] = "space";
+ chg[FS_OPTTIME] = "time";
+ if (sblock.fs_optim == ovalue)
+ warnx("%s remains unchanged as %s", name, chg[ovalue]);
+ else {
+ warnx("%s changes from %s to %s",
+ name, chg[sblock.fs_optim], chg[ovalue]);
+ sblock.fs_optim = ovalue;
+ if (sblock.fs_minfree >= MINFREE &&
+ ovalue == FS_OPTSPACE)
+ warnx(OPTWARN, "time", ">=", MINFREE);
+ if (sblock.fs_minfree < MINFREE && ovalue == FS_OPTTIME)
+ warnx(OPTWARN, "space", "<", MINFREE);
+ }
+ }
+ if (sflag) {
+ name = "expected number of files per directory";
+ if (sblock.fs_avgfpdir == (unsigned)svalue) {
+ warnx("%s remains unchanged as %d", name, svalue);
+ }
+ else {
+ warnx("%s changes from %d to %d",
+ name, sblock.fs_avgfpdir, svalue);
+ sblock.fs_avgfpdir = svalue;
+ }
+ }
+ if (tflag) {
+ name = "issue TRIM to the disk";
+ if (strcmp(tvalue, "enable") == 0) {
+ if (sblock.fs_flags & FS_TRIM)
+ warnx("%s remains unchanged as enabled", name);
+ else {
+ sblock.fs_flags |= FS_TRIM;
+ warnx("%s set", name);
+ }
+ } else if (strcmp(tvalue, "disable") == 0) {
+ if ((~sblock.fs_flags & FS_TRIM) == FS_TRIM)
+ warnx("%s remains unchanged as disabled", name);
+ else {
+ sblock.fs_flags &= ~FS_TRIM;
+ warnx("%s cleared", name);
+ }
+ }
+ }
+
+ if (sbwrite(&disk, Aflag) == -1)
+ goto err;
+ ufs_disk_close(&disk);
+ if (active) {
+ bzero(&args, sizeof(args));
+ if (mount("ufs", on,
+ stfs.f_flags | MNT_UPDATE | MNT_RELOAD, &args) < 0)
+ err(9, "%s: reload", special);
+ warnx("file system reloaded");
+ }
+ exit(0);
+err:
+ if (disk.d_error != NULL)
+ errx(11, "%s: %s", special, disk.d_error);
+ else
+ err(12, "%s", special);
+}
+
+static void
+sbdirty(void)
+{
+ disk.d_fs.fs_flags |= FS_UNCLEAN | FS_NEEDSFSCK;
+ disk.d_fs.fs_clean = 0;
+}
+
+static ufs2_daddr_t
+journal_balloc(void)
+{
+ ufs2_daddr_t blk;
+ struct cg *cgp;
+ int valid;
+ static int contig = 1;
+
+ cgp = &disk.d_cg;
+ for (;;) {
+ blk = cgballoc(&disk);
+ if (blk > 0)
+ break;
+ /*
+ * If we failed to allocate a block from this cg, move to
+ * the next.
+ */
+ if (cgwrite(&disk) < 0) {
+ warn("Failed to write updated cg");
+ return (-1);
+ }
+ while ((valid = cgread(&disk)) == 1) {
+ /*
+ * Try to minimize fragmentation by requiring a minimum
+ * number of blocks present.
+ */
+ if (cgp->cg_cs.cs_nbfree > 256 * 1024)
+ break;
+ if (contig == 0 && cgp->cg_cs.cs_nbfree)
+ break;
+ }
+ if (valid)
+ continue;
+ /*
+ * Try once through looking only for large contiguous regions
+ * and again taking any space we can find.
+ */
+ if (contig) {
+ contig = 0;
+ disk.d_ccg = 0;
+ warnx("Journal file fragmented.");
+ continue;
+ }
+ warnx("Failed to find sufficient free blocks for the journal");
+ return -1;
+ }
+ if (bwrite(&disk, fsbtodb(&sblock, blk), clrbuf,
+ sblock.fs_bsize) <= 0) {
+ warn("Failed to initialize new block");
+ return -1;
+ }
+ return (blk);
+}
+
+/*
+ * Search a directory block for the SUJ_FILE.
+ */
+static ino_t
+dir_search(ufs2_daddr_t blk, int bytes)
+{
+ char block[MAXBSIZE];
+ struct direct *dp;
+ int off;
+
+ if (bread(&disk, fsbtodb(&sblock, blk), block, bytes) <= 0) {
+ warn("Failed to read dir block");
+ return (-1);
+ }
+ for (off = 0; off < bytes; off += dp->d_reclen) {
+ dp = (struct direct *)&block[off];
+ if (dp->d_reclen == 0)
+ break;
+ if (dp->d_ino == 0)
+ continue;
+ if (dp->d_namlen != strlen(SUJ_FILE))
+ continue;
+ if (bcmp(dp->d_name, SUJ_FILE, dp->d_namlen) != 0)
+ continue;
+ return (dp->d_ino);
+ }
+
+ return (0);
+}
+
+/*
+ * Search in the ROOTINO for the SUJ_FILE. If it exists we can not enable
+ * journaling.
+ */
+static ino_t
+journal_findfile(void)
+{
+ struct ufs1_dinode *dp1;
+ struct ufs2_dinode *dp2;
+ ino_t ino;
+ int mode;
+ void *ip;
+ int i;
+
+ if (getino(&disk, &ip, ROOTINO, &mode) != 0) {
+ warn("Failed to get root inode");
+ return (-1);
+ }
+ dp2 = ip;
+ dp1 = ip;
+ if (sblock.fs_magic == FS_UFS1_MAGIC) {
+ if ((off_t)dp1->di_size >= lblktosize(&sblock, NDADDR)) {
+ warnx("ROOTINO extends beyond direct blocks.");
+ return (-1);
+ }
+ for (i = 0; i < NDADDR; i++) {
+ if (dp1->di_db[i] == 0)
+ break;
+ if ((ino = dir_search(dp1->di_db[i],
+ sblksize(&sblock, (off_t)dp1->di_size, i))) != 0)
+ return (ino);
+ }
+ } else {
+ if ((off_t)dp2->di_size >= lblktosize(&sblock, NDADDR)) {
+ warnx("ROOTINO extends beyond direct blocks.");
+ return (-1);
+ }
+ for (i = 0; i < NDADDR; i++) {
+ if (dp2->di_db[i] == 0)
+ break;
+ if ((ino = dir_search(dp2->di_db[i],
+ sblksize(&sblock, (off_t)dp2->di_size, i))) != 0)
+ return (ino);
+ }
+ }
+
+ return (0);
+}
+
+static void
+dir_clear_block(const char *block, off_t off)
+{
+ struct direct *dp;
+
+ for (; off < sblock.fs_bsize; off += DIRBLKSIZ) {
+ dp = (struct direct *)&block[off];
+ dp->d_ino = 0;
+ dp->d_reclen = DIRBLKSIZ;
+ dp->d_type = DT_UNKNOWN;
+ }
+}
+
+/*
+ * Insert the journal at inode 'ino' into directory blk 'blk' at the first
+ * free offset of 'off'. DIRBLKSIZ blocks after off are initialized as
+ * empty.
+ */
+static int
+dir_insert(ufs2_daddr_t blk, off_t off, ino_t ino)
+{
+ struct direct *dp;
+ char block[MAXBSIZE];
+
+ if (bread(&disk, fsbtodb(&sblock, blk), block, sblock.fs_bsize) <= 0) {
+ warn("Failed to read dir block");
+ return (-1);
+ }
+ bzero(&block[off], sblock.fs_bsize - off);
+ dp = (struct direct *)&block[off];
+ dp->d_ino = ino;
+ dp->d_reclen = DIRBLKSIZ;
+ dp->d_type = DT_REG;
+ dp->d_namlen = strlen(SUJ_FILE);
+ bcopy(SUJ_FILE, &dp->d_name, strlen(SUJ_FILE));
+ dir_clear_block(block, off + DIRBLKSIZ);
+ if (bwrite(&disk, fsbtodb(&sblock, blk), block, sblock.fs_bsize) <= 0) {
+ warn("Failed to write dir block");
+ return (-1);
+ }
+ return (0);
+}
+
+/*
+ * Extend a directory block in 'blk' by copying it to a full size block
+ * and inserting the new journal inode into .sujournal.
+ */
+static int
+dir_extend(ufs2_daddr_t blk, ufs2_daddr_t nblk, off_t size, ino_t ino)
+{
+ char block[MAXBSIZE];
+
+ if (bread(&disk, fsbtodb(&sblock, blk), block,
+ roundup(size, sblock.fs_fsize)) <= 0) {
+ warn("Failed to read dir block");
+ return (-1);
+ }
+ dir_clear_block(block, size);
+ if (bwrite(&disk, fsbtodb(&sblock, nblk), block, sblock.fs_bsize)
+ <= 0) {
+ warn("Failed to write dir block");
+ return (-1);
+ }
+
+ return (dir_insert(nblk, size, ino));
+}
+
+/*
+ * Insert the journal file into the ROOTINO directory. We always extend the
+ * last frag
+ */
+static int
+journal_insertfile(ino_t ino)
+{
+ struct ufs1_dinode *dp1;
+ struct ufs2_dinode *dp2;
+ void *ip;
+ ufs2_daddr_t nblk;
+ ufs2_daddr_t blk;
+ ufs_lbn_t lbn;
+ int size;
+ int mode;
+ int off;
+
+ if (getino(&disk, &ip, ROOTINO, &mode) != 0) {
+ warn("Failed to get root inode");
+ sbdirty();
+ return (-1);
+ }
+ dp2 = ip;
+ dp1 = ip;
+ blk = 0;
+ size = 0;
+ nblk = journal_balloc();
+ if (nblk <= 0)
+ return (-1);
+ /*
+ * For simplicity sake we aways extend the ROOTINO into a new
+ * directory block rather than searching for space and inserting
+ * into an existing block. However, if the rootino has frags
+ * have to free them and extend the block.
+ */
+ if (sblock.fs_magic == FS_UFS1_MAGIC) {
+ lbn = lblkno(&sblock, dp1->di_size);
+ off = blkoff(&sblock, dp1->di_size);
+ blk = dp1->di_db[lbn];
+ size = sblksize(&sblock, (off_t)dp1->di_size, lbn);
+ } else {
+ lbn = lblkno(&sblock, dp2->di_size);
+ off = blkoff(&sblock, dp2->di_size);
+ blk = dp2->di_db[lbn];
+ size = sblksize(&sblock, (off_t)dp2->di_size, lbn);
+ }
+ if (off != 0) {
+ if (dir_extend(blk, nblk, off, ino) == -1)
+ return (-1);
+ } else {
+ blk = 0;
+ if (dir_insert(nblk, 0, ino) == -1)
+ return (-1);
+ }
+ if (sblock.fs_magic == FS_UFS1_MAGIC) {
+ dp1->di_blocks += (sblock.fs_bsize - size) / DEV_BSIZE;
+ dp1->di_db[lbn] = nblk;
+ dp1->di_size = lblktosize(&sblock, lbn+1);
+ } else {
+ dp2->di_blocks += (sblock.fs_bsize - size) / DEV_BSIZE;
+ dp2->di_db[lbn] = nblk;
+ dp2->di_size = lblktosize(&sblock, lbn+1);
+ }
+ if (putino(&disk) < 0) {
+ warn("Failed to write root inode");
+ return (-1);
+ }
+ if (cgwrite(&disk) < 0) {
+ warn("Failed to write updated cg");
+ sbdirty();
+ return (-1);
+ }
+ if (blk) {
+ if (cgbfree(&disk, blk, size) < 0) {
+ warn("Failed to write cg");
+ return (-1);
+ }
+ }
+
+ return (0);
+}
+
+static int
+indir_fill(ufs2_daddr_t blk, int level, int *resid)
+{
+ char indirbuf[MAXBSIZE];
+ ufs1_daddr_t *bap1;
+ ufs2_daddr_t *bap2;
+ ufs2_daddr_t nblk;
+ int ncnt;
+ int cnt;
+ int i;
+
+ bzero(indirbuf, sizeof(indirbuf));
+ bap1 = (ufs1_daddr_t *)indirbuf;
+ bap2 = (void *)bap1;
+ cnt = 0;
+ for (i = 0; i < NINDIR(&sblock) && *resid != 0; i++) {
+ nblk = journal_balloc();
+ if (nblk <= 0)
+ return (-1);
+ cnt++;
+ if (sblock.fs_magic == FS_UFS1_MAGIC)
+ *bap1++ = nblk;
+ else
+ *bap2++ = nblk;
+ if (level != 0) {
+ ncnt = indir_fill(nblk, level - 1, resid);
+ if (ncnt <= 0)
+ return (-1);
+ cnt += ncnt;
+ } else
+ (*resid)--;
+ }
+ if (bwrite(&disk, fsbtodb(&sblock, blk), indirbuf,
+ sblock.fs_bsize) <= 0) {
+ warn("Failed to write indirect");
+ return (-1);
+ }
+ return (cnt);
+}
+
+/*
+ * Clear the flag bits so the journal can be removed.
+ */
+static void
+journal_clear(void)
+{
+ struct ufs1_dinode *dp1;
+ struct ufs2_dinode *dp2;
+ ino_t ino;
+ int mode;
+ void *ip;
+
+ ino = journal_findfile();
+ if (ino == (ino_t)-1 || ino == 0) {
+ warnx("Journal file does not exist");
+ return;
+ }
+ printf("Clearing journal flags from inode %ju\n", (uintmax_t)ino);
+ if (getino(&disk, &ip, ino, &mode) != 0) {
+ warn("Failed to get journal inode");
+ return;
+ }
+ dp2 = ip;
+ dp1 = ip;
+ if (sblock.fs_magic == FS_UFS1_MAGIC)
+ dp1->di_flags = 0;
+ else
+ dp2->di_flags = 0;
+ if (putino(&disk) < 0) {
+ warn("Failed to write journal inode");
+ return;
+ }
+}
+
+static int
+journal_alloc(int64_t size)
+{
+ struct ufs1_dinode *dp1;
+ struct ufs2_dinode *dp2;
+ ufs2_daddr_t blk;
+ void *ip;
+ struct cg *cgp;
+ int resid;
+ ino_t ino;
+ int blks;
+ int mode;
+ time_t utime;
+ int i;
+
+ cgp = &disk.d_cg;
+ ino = 0;
+
+ /*
+ * If the journal file exists we can't allocate it.
+ */
+ ino = journal_findfile();
+ if (ino == (ino_t)-1)
+ return (-1);
+ if (ino > 0) {
+ warnx("Journal file %s already exists, please remove.",
+ SUJ_FILE);
+ return (-1);
+ }
+ /*
+ * If the user didn't supply a size pick one based on the filesystem
+ * size constrained with hardcoded MIN and MAX values. We opt for
+ * 1/1024th of the filesystem up to MAX but not exceeding one CG and
+ * not less than the MIN.
+ */
+ if (size == 0) {
+ size = (sblock.fs_size * sblock.fs_bsize) / 1024;
+ size = MIN(SUJ_MAX, size);
+ if (size / sblock.fs_fsize > sblock.fs_fpg)
+ size = sblock.fs_fpg * sblock.fs_fsize;
+ size = MAX(SUJ_MIN, size);
+ /* fsck does not support fragments in journal files. */
+ size = roundup(size, sblock.fs_bsize);
+ }
+ resid = blocks = size / sblock.fs_bsize;
+ if (sblock.fs_cstotal.cs_nbfree < blocks) {
+ warn("Insufficient free space for %jd byte journal", size);
+ return (-1);
+ }
+ /*
+ * Find a cg with enough blocks to satisfy the journal
+ * size. Presently the journal does not span cgs.
+ */
+ while (cgread(&disk) == 1) {
+ if (cgp->cg_cs.cs_nifree == 0)
+ continue;
+ ino = cgialloc(&disk);
+ if (ino <= 0)
+ break;
+ printf("Using inode %ju in cg %d for %jd byte journal\n",
+ (uintmax_t)ino, cgp->cg_cgx, size);
+ if (getino(&disk, &ip, ino, &mode) != 0) {
+ warn("Failed to get allocated inode");
+ sbdirty();
+ goto out;
+ }
+ /*
+ * We leave fields unrelated to the number of allocated
+ * blocks and size uninitialized. This causes legacy
+ * fsck implementations to clear the inode.
+ */
+ dp2 = ip;
+ dp1 = ip;
+ time(&utime);
+ if (sblock.fs_magic == FS_UFS1_MAGIC) {
+ bzero(dp1, sizeof(*dp1));
+ dp1->di_size = size;
+ dp1->di_mode = IFREG | IREAD;
+ dp1->di_nlink = 1;
+ dp1->di_flags = SF_IMMUTABLE | SF_NOUNLINK | UF_NODUMP;
+ dp1->di_atime = utime;
+ dp1->di_mtime = utime;
+ dp1->di_ctime = utime;
+ } else {
+ bzero(dp2, sizeof(*dp2));
+ dp2->di_size = size;
+ dp2->di_mode = IFREG | IREAD;
+ dp2->di_nlink = 1;
+ dp2->di_flags = SF_IMMUTABLE | SF_NOUNLINK | UF_NODUMP;
+ dp2->di_atime = utime;
+ dp2->di_mtime = utime;
+ dp2->di_ctime = utime;
+ dp2->di_birthtime = utime;
+ }
+ for (i = 0; i < NDADDR && resid; i++, resid--) {
+ blk = journal_balloc();
+ if (blk <= 0)
+ goto out;
+ if (sblock.fs_magic == FS_UFS1_MAGIC) {
+ dp1->di_db[i] = blk;
+ dp1->di_blocks++;
+ } else {
+ dp2->di_db[i] = blk;
+ dp2->di_blocks++;
+ }
+ }
+ for (i = 0; i < NIADDR && resid; i++) {
+ blk = journal_balloc();
+ if (blk <= 0)
+ goto out;
+ blks = indir_fill(blk, i, &resid) + 1;
+ if (blks <= 0) {
+ sbdirty();
+ goto out;
+ }
+ if (sblock.fs_magic == FS_UFS1_MAGIC) {
+ dp1->di_ib[i] = blk;
+ dp1->di_blocks += blks;
+ } else {
+ dp2->di_ib[i] = blk;
+ dp2->di_blocks += blks;
+ }
+ }
+ if (sblock.fs_magic == FS_UFS1_MAGIC)
+ dp1->di_blocks *= sblock.fs_bsize / disk.d_bsize;
+ else
+ dp2->di_blocks *= sblock.fs_bsize / disk.d_bsize;
+ if (putino(&disk) < 0) {
+ warn("Failed to write inode");
+ sbdirty();
+ return (-1);
+ }
+ if (cgwrite(&disk) < 0) {
+ warn("Failed to write updated cg");
+ sbdirty();
+ return (-1);
+ }
+ if (journal_insertfile(ino) < 0) {
+ sbdirty();
+ return (-1);
+ }
+ sblock.fs_sujfree = 0;
+ return (0);
+ }
+ warnx("Insufficient free space for the journal.");
+out:
+ return (-1);
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "%s\n%s\n%s\n%s\n%s\n%s\n",
+"usage: tunefs [-A] [-a enable | disable] [-e maxbpg] [-f avgfilesize]",
+" [-J enable | disable] [-j enable | disable] [-k metaspace]",
+" [-L volname] [-l enable | disable] [-m minfree]",
+" [-N enable | disable] [-n enable | disable]",
+" [-o space | time] [-p] [-s avgfpdir] [-t enable | disable]",
+" special | filesystem");
+ exit(2);
+}
+
+static void
+printfs(void)
+{
+ warnx("POSIX.1e ACLs: (-a) %s",
+ (sblock.fs_flags & FS_ACLS)? "enabled" : "disabled");
+ warnx("NFSv4 ACLs: (-N) %s",
+ (sblock.fs_flags & FS_NFS4ACLS)? "enabled" : "disabled");
+ warnx("MAC multilabel: (-l) %s",
+ (sblock.fs_flags & FS_MULTILABEL)? "enabled" : "disabled");
+ warnx("soft updates: (-n) %s",
+ (sblock.fs_flags & FS_DOSOFTDEP)? "enabled" : "disabled");
+ warnx("soft update journaling: (-j) %s",
+ (sblock.fs_flags & FS_SUJ)? "enabled" : "disabled");
+ warnx("gjournal: (-J) %s",
+ (sblock.fs_flags & FS_GJOURNAL)? "enabled" : "disabled");
+ warnx("trim: (-t) %s",
+ (sblock.fs_flags & FS_TRIM)? "enabled" : "disabled");
+ warnx("maximum blocks per file in a cylinder group: (-e) %d",
+ sblock.fs_maxbpg);
+ warnx("average file size: (-f) %d",
+ sblock.fs_avgfilesize);
+ warnx("average number of files in a directory: (-s) %d",
+ sblock.fs_avgfpdir);
+ warnx("minimum percentage of free space: (-m) %d%%",
+ sblock.fs_minfree);
+ warnx("space to hold for metadata blocks: (-k) %jd",
+ sblock.fs_metaspace);
+ warnx("optimization preference: (-o) %s",
+ sblock.fs_optim == FS_OPTSPACE ? "space" : "time");
+ if (sblock.fs_minfree >= MINFREE &&
+ sblock.fs_optim == FS_OPTSPACE)
+ warnx(OPTWARN, "time", ">=", MINFREE);
+ if (sblock.fs_minfree < MINFREE &&
+ sblock.fs_optim == FS_OPTTIME)
+ warnx(OPTWARN, "space", "<", MINFREE);
+ warnx("volume label: (-L) %s",
+ sblock.fs_volname);
+}
diff --git a/sbin/umount/Makefile b/sbin/umount/Makefile
new file mode 100644
index 0000000..e472b41
--- /dev/null
+++ b/sbin/umount/Makefile
@@ -0,0 +1,15 @@
+# @(#)Makefile 8.4 (Berkeley) 6/22/95
+#
+# $FreeBSD$
+
+PROG= umount
+SRCS= umount.c vfslist.c mounttab.c
+MAN= umount.8
+
+MOUNT= ${.CURDIR}/../mount
+UMNTALL= ${.CURDIR}/../../usr.sbin/rpc.umntall
+CFLAGS+= -I${MOUNT} -I${UMNTALL}
+
+.PATH: ${MOUNT} ${UMNTALL}
+
+.include <bsd.prog.mk>
diff --git a/sbin/umount/umount.8 b/sbin/umount/umount.8
new file mode 100644
index 0000000..e62bcc5
--- /dev/null
+++ b/sbin/umount/umount.8
@@ -0,0 +1,149 @@
+.\" Copyright (c) 1980, 1989, 1991, 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.
+.\" 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.
+.\"
+.\" @(#)umount.8 8.2 (Berkeley) 5/8/95
+.\" $FreeBSD$
+.\"
+.Dd November 22, 2014
+.Dt UMOUNT 8
+.Os
+.Sh NAME
+.Nm umount
+.Nd unmount file systems
+.Sh SYNOPSIS
+.Nm
+.Op Fl fv
+.Ar special ... | node ... | fsid ...
+.Nm
+.Fl a | A
+.Op Fl F Ar fstab
+.Op Fl fv
+.Op Fl h Ar host
+.Op Fl t Ar type
+.Sh DESCRIPTION
+The
+.Nm
+utility calls the
+.Xr unmount 2
+system call to remove a file system from the file system tree.
+The file system can be specified by its
+.Ar special
+device or remote node (rhost:path), the path to the mount point
+.Ar node
+or by the file system ID
+.Ar fsid
+as reported by
+.Dq mount -v
+when run by root.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl a
+All the file systems described in
+.Xr fstab 5
+are unmounted.
+.It Fl A
+All the currently mounted file systems except
+the root are unmounted.
+.It Fl F Ar fstab
+Specify the
+.Pa fstab
+file to use.
+.It Fl f
+The file system is forcibly unmounted.
+Active special devices continue to work,
+but all other files return errors if further accesses are attempted.
+The root file system cannot be forcibly unmounted.
+For NFS, a forced dismount can take up to 1 minute or more to
+complete against an unresponsive server and may throw away
+data not yet written to the server for this case.
+.It Fl h Ar host
+Only file systems mounted from the specified host will be
+unmounted.
+This option implies the
+.Fl A
+option and, unless otherwise specified with the
+.Fl t
+option, will only unmount
+.Tn NFS
+file systems.
+.It Fl t Ar type
+Is used to indicate the actions should only be taken on
+file systems of the specified type.
+More than one type may be specified in a comma separated list.
+The list of file system types can be prefixed with
+.Dq no
+to specify the file system types for which action should
+.Em not
+be taken.
+For example, the
+.Nm
+command:
+.Bd -literal -offset indent
+umount -a -t nfs,nullfs
+.Ed
+.Pp
+unmounts all file systems of the type
+.Tn NFS
+and
+.Tn NULLFS
+that are listed in the
+.Xr fstab 5
+file.
+.It Fl v
+Verbose, additional information is printed out as each file system
+is unmounted.
+.El
+.Sh ENVIRONMENT
+.Bl -tag -width ".Ev PATH_FSTAB"
+.It Ev PATH_FSTAB
+If the environment variable
+.Ev PATH_FSTAB
+is set, all operations are performed against the specified file.
+.Ev PATH_FSTAB
+will not be honored if the process environment or memory address space is
+considered
+.Dq tainted .
+(See
+.Xr issetugid 2
+for more information.)
+.El
+.Sh FILES
+.Bl -tag -width /etc/fstab -compact
+.It Pa /etc/fstab
+file system table
+.El
+.Sh SEE ALSO
+.Xr unmount 2 ,
+.Xr fstab 5 ,
+.Xr autounmountd 8 ,
+.Xr mount 8
+.Sh HISTORY
+A
+.Nm
+utility appeared in
+.At v6 .
diff --git a/sbin/umount/umount.c b/sbin/umount/umount.c
new file mode 100644
index 0000000..521bbc8
--- /dev/null
+++ b/sbin/umount/umount.c
@@ -0,0 +1,615 @@
+/*-
+ * Copyright (c) 1980, 1989, 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.
+ * 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 lint
+static const char copyright[] =
+"@(#) Copyright (c) 1980, 1989, 1993\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)umount.c 8.8 (Berkeley) 5/8/95";
+#endif
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/mount.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+
+#include <netdb.h>
+#include <rpc/rpc.h>
+#include <rpcsvc/mount.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fstab.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "mounttab.h"
+
+typedef enum { FIND, REMOVE, CHECKUNIQUE } dowhat;
+
+static struct addrinfo *nfshost_ai = NULL;
+static int fflag, vflag;
+static char *nfshost;
+
+struct statfs *checkmntlist(char *);
+int checkvfsname (const char *, char **);
+struct statfs *getmntentry(const char *fromname, const char *onname,
+ fsid_t *fsid, dowhat what);
+char **makevfslist (const char *);
+size_t mntinfo (struct statfs **);
+int namematch (struct addrinfo *);
+int parsehexfsid(const char *hex, fsid_t *fsid);
+int sacmp (void *, void *);
+int umountall (char **);
+int checkname (char *, char **);
+int umountfs(struct statfs *sfs);
+void usage (void);
+int xdr_dir (XDR *, char *);
+
+int
+main(int argc, char *argv[])
+{
+ int all, errs, ch, mntsize, error;
+ char **typelist = NULL;
+ struct statfs *mntbuf, *sfs;
+ struct addrinfo hints;
+
+ all = errs = 0;
+ while ((ch = getopt(argc, argv, "AaF:fh:t:v")) != -1)
+ switch (ch) {
+ case 'A':
+ all = 2;
+ break;
+ case 'a':
+ all = 1;
+ break;
+ case 'F':
+ setfstab(optarg);
+ break;
+ case 'f':
+ fflag = MNT_FORCE;
+ break;
+ case 'h': /* -h implies -A. */
+ all = 2;
+ nfshost = optarg;
+ break;
+ case 't':
+ if (typelist != NULL)
+ err(1, "only one -t option may be specified");
+ typelist = makevfslist(optarg);
+ break;
+ case 'v':
+ vflag = 1;
+ break;
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* Start disks transferring immediately. */
+ if ((fflag & MNT_FORCE) == 0)
+ sync();
+
+ if ((argc == 0 && !all) || (argc != 0 && all))
+ usage();
+
+ /* -h implies "-t nfs" if no -t flag. */
+ if ((nfshost != NULL) && (typelist == NULL))
+ typelist = makevfslist("nfs");
+
+ if (nfshost != NULL) {
+ memset(&hints, 0, sizeof hints);
+ error = getaddrinfo(nfshost, NULL, &hints, &nfshost_ai);
+ if (error)
+ errx(1, "%s: %s", nfshost, gai_strerror(error));
+ }
+
+ switch (all) {
+ case 2:
+ if ((mntsize = mntinfo(&mntbuf)) <= 0)
+ break;
+ /*
+ * We unmount the nfs-mounts in the reverse order
+ * that they were mounted.
+ */
+ for (errs = 0, mntsize--; mntsize > 0; mntsize--) {
+ sfs = &mntbuf[mntsize];
+ if (checkvfsname(sfs->f_fstypename, typelist))
+ continue;
+ if (strcmp(sfs->f_mntonname, "/dev") == 0)
+ continue;
+ if (umountfs(sfs) != 0)
+ errs = 1;
+ }
+ free(mntbuf);
+ break;
+ case 1:
+ if (setfsent() == 0)
+ err(1, "%s", getfstab());
+ errs = umountall(typelist);
+ break;
+ case 0:
+ for (errs = 0; *argv != NULL; ++argv)
+ if (checkname(*argv, typelist) != 0)
+ errs = 1;
+ break;
+ }
+ exit(errs);
+}
+
+int
+umountall(char **typelist)
+{
+ struct xvfsconf vfc;
+ struct fstab *fs;
+ int rval;
+ char *cp;
+ static int firstcall = 1;
+
+ if ((fs = getfsent()) != NULL)
+ firstcall = 0;
+ else if (firstcall)
+ errx(1, "fstab reading failure");
+ else
+ return (0);
+ do {
+ /* Ignore the root. */
+ if (strcmp(fs->fs_file, "/") == 0)
+ continue;
+ /*
+ * !!!
+ * Historic practice: ignore unknown FSTAB_* fields.
+ */
+ if (strcmp(fs->fs_type, FSTAB_RW) &&
+ strcmp(fs->fs_type, FSTAB_RO) &&
+ strcmp(fs->fs_type, FSTAB_RQ))
+ continue;
+ /* Ignore unknown file system types. */
+ if (getvfsbyname(fs->fs_vfstype, &vfc) == -1)
+ continue;
+ if (checkvfsname(fs->fs_vfstype, typelist))
+ continue;
+
+ /*
+ * We want to unmount the file systems in the reverse order
+ * that they were mounted. So, we save off the file name
+ * in some allocated memory, and then call recursively.
+ */
+ if ((cp = malloc((size_t)strlen(fs->fs_file) + 1)) == NULL)
+ err(1, "malloc failed");
+ (void)strcpy(cp, fs->fs_file);
+ rval = umountall(typelist);
+ rval = checkname(cp, typelist) || rval;
+ free(cp);
+ return (rval);
+ } while ((fs = getfsent()) != NULL);
+ return (0);
+}
+
+/*
+ * Do magic checks on mountpoint/device/fsid, and then call unmount(2).
+ */
+int
+checkname(char *mntname, char **typelist)
+{
+ char buf[MAXPATHLEN];
+ struct statfs sfsbuf;
+ struct stat sb;
+ struct statfs *sfs;
+ char *delimp;
+ dev_t dev;
+ int len;
+
+ /*
+ * 1. Check if the name exists in the mounttable.
+ */
+ sfs = checkmntlist(mntname);
+ /*
+ * 2. Remove trailing slashes if there are any. After that
+ * we look up the name in the mounttable again.
+ */
+ if (sfs == NULL) {
+ len = strlen(mntname);
+ while (len > 1 && mntname[len - 1] == '/')
+ mntname[--len] = '\0';
+ sfs = checkmntlist(mntname);
+ }
+ /*
+ * 3. Check if the deprecated NFS syntax with an '@' has been used
+ * and translate it to the ':' syntax. Look up the name in the
+ * mount table again.
+ */
+ if (sfs == NULL && (delimp = strrchr(mntname, '@')) != NULL) {
+ snprintf(buf, sizeof(buf), "%s:%.*s", delimp + 1,
+ (int)(delimp - mntname), mntname);
+ len = strlen(buf);
+ while (len > 1 && buf[len - 1] == '/')
+ buf[--len] = '\0';
+ sfs = checkmntlist(buf);
+ }
+ /*
+ * 4. Resort to a statfs(2) call. This is the last check so that
+ * hung NFS filesystems for example can be unmounted without
+ * potentially blocking forever in statfs() as long as the
+ * filesystem is specified unambiguously. This covers all the
+ * hard cases such as symlinks and mismatches between the
+ * mount list and reality.
+ * We also do this if an ambiguous mount point was specified.
+ */
+ if (sfs == NULL || (getmntentry(NULL, mntname, NULL, FIND) != NULL &&
+ getmntentry(NULL, mntname, NULL, CHECKUNIQUE) == NULL)) {
+ if (statfs(mntname, &sfsbuf) != 0) {
+ warn("%s: statfs", mntname);
+ } else if (stat(mntname, &sb) != 0) {
+ warn("%s: stat", mntname);
+ } else if (S_ISDIR(sb.st_mode)) {
+ /* Check that `mntname' is the root directory. */
+ dev = sb.st_dev;
+ snprintf(buf, sizeof(buf), "%s/..", mntname);
+ if (stat(buf, &sb) != 0) {
+ warn("%s: stat", buf);
+ } else if (sb.st_dev == dev) {
+ warnx("%s: not a file system root directory",
+ mntname);
+ return (1);
+ } else
+ sfs = &sfsbuf;
+ }
+ }
+ if (sfs == NULL) {
+ warnx("%s: unknown file system", mntname);
+ return (1);
+ }
+ if (checkvfsname(sfs->f_fstypename, typelist))
+ return (1);
+ return (umountfs(sfs));
+}
+
+/*
+ * NFS stuff and unmount(2) call
+ */
+int
+umountfs(struct statfs *sfs)
+{
+ char fsidbuf[64];
+ enum clnt_stat clnt_stat;
+ struct timeval try;
+ struct addrinfo *ai, hints;
+ int do_rpc;
+ CLIENT *clp;
+ char *nfsdirname, *orignfsdirname;
+ char *hostp, *delimp;
+
+ ai = NULL;
+ do_rpc = 0;
+ hostp = NULL;
+ nfsdirname = delimp = orignfsdirname = NULL;
+ memset(&hints, 0, sizeof hints);
+
+ if (strcmp(sfs->f_fstypename, "nfs") == 0) {
+ if ((nfsdirname = strdup(sfs->f_mntfromname)) == NULL)
+ err(1, "strdup");
+ orignfsdirname = nfsdirname;
+ if (*nfsdirname == '[' &&
+ (delimp = strchr(nfsdirname + 1, ']')) != NULL &&
+ *(delimp + 1) == ':') {
+ hostp = nfsdirname + 1;
+ nfsdirname = delimp + 2;
+ } else if ((delimp = strrchr(nfsdirname, ':')) != NULL) {
+ hostp = nfsdirname;
+ nfsdirname = delimp + 1;
+ }
+ if (hostp != NULL) {
+ *delimp = '\0';
+ getaddrinfo(hostp, NULL, &hints, &ai);
+ if (ai == NULL) {
+ warnx("can't get net id for host");
+ }
+ }
+
+ /*
+ * Check if we have to start the rpc-call later.
+ * If there are still identical nfs-names mounted,
+ * we skip the rpc-call. Obviously this has to
+ * happen before unmount(2), but it should happen
+ * after the previous namecheck.
+ * A non-NULL return means that this is the last
+ * mount from mntfromname that is still mounted.
+ */
+ if (getmntentry(sfs->f_mntfromname, NULL, NULL,
+ CHECKUNIQUE) != NULL)
+ do_rpc = 1;
+ }
+
+ if (!namematch(ai)) {
+ free(orignfsdirname);
+ return (1);
+ }
+ /* First try to unmount using the file system ID. */
+ snprintf(fsidbuf, sizeof(fsidbuf), "FSID:%d:%d", sfs->f_fsid.val[0],
+ sfs->f_fsid.val[1]);
+ if (unmount(fsidbuf, fflag | MNT_BYFSID) != 0) {
+ /* XXX, non-root users get a zero fsid, so don't warn. */
+ if (errno != ENOENT || sfs->f_fsid.val[0] != 0 ||
+ sfs->f_fsid.val[1] != 0)
+ warn("unmount of %s failed", sfs->f_mntonname);
+ if (errno != ENOENT) {
+ free(orignfsdirname);
+ return (1);
+ }
+ /* Compatibility for old kernels. */
+ if (sfs->f_fsid.val[0] != 0 || sfs->f_fsid.val[1] != 0)
+ warnx("retrying using path instead of file system ID");
+ if (unmount(sfs->f_mntonname, fflag) != 0) {
+ warn("unmount of %s failed", sfs->f_mntonname);
+ free(orignfsdirname);
+ return (1);
+ }
+ }
+ /* Mark this this file system as unmounted. */
+ getmntentry(NULL, NULL, &sfs->f_fsid, REMOVE);
+ if (vflag)
+ (void)printf("%s: unmount from %s\n", sfs->f_mntfromname,
+ sfs->f_mntonname);
+ /*
+ * Report to mountd-server which nfsname
+ * has been unmounted.
+ */
+ if (ai != NULL && !(fflag & MNT_FORCE) && do_rpc) {
+ clp = clnt_create(hostp, MOUNTPROG, MOUNTVERS3, "udp");
+ if (clp == NULL) {
+ warnx("%s: %s", hostp,
+ clnt_spcreateerror("MOUNTPROG"));
+ free(orignfsdirname);
+ return (1);
+ }
+ clp->cl_auth = authsys_create_default();
+ try.tv_sec = 20;
+ try.tv_usec = 0;
+ clnt_stat = clnt_call(clp, MOUNTPROC_UMNT, (xdrproc_t)xdr_dir,
+ nfsdirname, (xdrproc_t)xdr_void, (caddr_t)0, try);
+ if (clnt_stat != RPC_SUCCESS) {
+ warnx("%s: %s", hostp,
+ clnt_sperror(clp, "RPCMNT_UMOUNT"));
+ free(orignfsdirname);
+ return (1);
+ }
+ /*
+ * Remove the unmounted entry from /var/db/mounttab.
+ */
+ if (read_mtab()) {
+ clean_mtab(hostp, nfsdirname, vflag);
+ if(!write_mtab(vflag))
+ warnx("cannot remove mounttab entry %s:%s",
+ hostp, nfsdirname);
+ free_mtab();
+ }
+ auth_destroy(clp->cl_auth);
+ clnt_destroy(clp);
+ }
+ free(orignfsdirname);
+ return (0);
+}
+
+struct statfs *
+getmntentry(const char *fromname, const char *onname, fsid_t *fsid, dowhat what)
+{
+ static struct statfs *mntbuf;
+ static size_t mntsize = 0;
+ static char *mntcheck = NULL;
+ struct statfs *sfs, *foundsfs;
+ int i, count;
+
+ if (mntsize <= 0) {
+ if ((mntsize = mntinfo(&mntbuf)) <= 0)
+ return (NULL);
+ }
+ if (mntcheck == NULL) {
+ if ((mntcheck = calloc(mntsize + 1, sizeof(int))) == NULL)
+ err(1, "calloc");
+ }
+ /*
+ * We want to get the file systems in the reverse order
+ * that they were mounted. Unmounted file systems are marked
+ * in a table called 'mntcheck'.
+ */
+ count = 0;
+ foundsfs = NULL;
+ for (i = mntsize - 1; i >= 0; i--) {
+ if (mntcheck[i])
+ continue;
+ sfs = &mntbuf[i];
+ if (fromname != NULL && strcmp(sfs->f_mntfromname,
+ fromname) != 0)
+ continue;
+ if (onname != NULL && strcmp(sfs->f_mntonname, onname) != 0)
+ continue;
+ if (fsid != NULL && bcmp(&sfs->f_fsid, fsid,
+ sizeof(*fsid)) != 0)
+ continue;
+
+ switch (what) {
+ case CHECKUNIQUE:
+ foundsfs = sfs;
+ count++;
+ continue;
+ case REMOVE:
+ mntcheck[i] = 1;
+ break;
+ default:
+ break;
+ }
+ return (sfs);
+ }
+
+ if (what == CHECKUNIQUE && count == 1)
+ return (foundsfs);
+ return (NULL);
+}
+
+int
+sacmp(void *sa1, void *sa2)
+{
+ void *p1, *p2;
+ int len;
+
+ if (((struct sockaddr *)sa1)->sa_family !=
+ ((struct sockaddr *)sa2)->sa_family)
+ return (1);
+
+ switch (((struct sockaddr *)sa1)->sa_family) {
+ case AF_INET:
+ p1 = &((struct sockaddr_in *)sa1)->sin_addr;
+ p2 = &((struct sockaddr_in *)sa2)->sin_addr;
+ len = 4;
+ break;
+ case AF_INET6:
+ p1 = &((struct sockaddr_in6 *)sa1)->sin6_addr;
+ p2 = &((struct sockaddr_in6 *)sa2)->sin6_addr;
+ len = 16;
+ if (((struct sockaddr_in6 *)sa1)->sin6_scope_id !=
+ ((struct sockaddr_in6 *)sa2)->sin6_scope_id)
+ return (1);
+ break;
+ default:
+ return (1);
+ }
+
+ return memcmp(p1, p2, len);
+}
+
+int
+namematch(struct addrinfo *ai)
+{
+ struct addrinfo *aip;
+
+ if (nfshost == NULL || nfshost_ai == NULL)
+ return (1);
+
+ while (ai != NULL) {
+ aip = nfshost_ai;
+ while (aip != NULL) {
+ if (sacmp(ai->ai_addr, aip->ai_addr) == 0)
+ return (1);
+ aip = aip->ai_next;
+ }
+ ai = ai->ai_next;
+ }
+
+ return (0);
+}
+
+struct statfs *
+checkmntlist(char *mntname)
+{
+ struct statfs *sfs;
+ fsid_t fsid;
+
+ sfs = NULL;
+ if (parsehexfsid(mntname, &fsid) == 0)
+ sfs = getmntentry(NULL, NULL, &fsid, FIND);
+ if (sfs == NULL)
+ sfs = getmntentry(NULL, mntname, NULL, FIND);
+ if (sfs == NULL)
+ sfs = getmntentry(mntname, NULL, NULL, FIND);
+ return (sfs);
+}
+
+size_t
+mntinfo(struct statfs **mntbuf)
+{
+ static struct statfs *origbuf;
+ size_t bufsize;
+ int mntsize;
+
+ mntsize = getfsstat(NULL, 0, MNT_NOWAIT);
+ if (mntsize <= 0)
+ return (0);
+ bufsize = (mntsize + 1) * sizeof(struct statfs);
+ if ((origbuf = malloc(bufsize)) == NULL)
+ err(1, "malloc");
+ mntsize = getfsstat(origbuf, (long)bufsize, MNT_NOWAIT);
+ *mntbuf = origbuf;
+ return (mntsize);
+}
+
+/*
+ * Convert a hexadecimal filesystem ID to an fsid_t.
+ * Returns 0 on success.
+ */
+int
+parsehexfsid(const char *hex, fsid_t *fsid)
+{
+ char hexbuf[3];
+ int i;
+
+ if (strlen(hex) != sizeof(*fsid) * 2)
+ return (-1);
+ hexbuf[2] = '\0';
+ for (i = 0; i < (int)sizeof(*fsid); i++) {
+ hexbuf[0] = hex[i * 2];
+ hexbuf[1] = hex[i * 2 + 1];
+ if (!isxdigit(hexbuf[0]) || !isxdigit(hexbuf[1]))
+ return (-1);
+ ((u_char *)fsid)[i] = strtol(hexbuf, NULL, 16);
+ }
+ return (0);
+}
+
+/*
+ * xdr routines for mount rpc's
+ */
+int
+xdr_dir(XDR *xdrsp, char *dirp)
+{
+
+ return (xdr_string(xdrsp, &dirp, MNTPATHLEN));
+}
+
+void
+usage(void)
+{
+
+ (void)fprintf(stderr, "%s\n%s\n",
+ "usage: umount [-fv] special ... | node ... | fsid ...",
+ " umount -a | -A [-F fstab] [-fv] [-h host] [-t type]");
+ exit(1);
+}
OpenPOWER on IntegriCloud